NSX|v3: DHCP Relay support

Support DHCP relay by configuring the relay service per
network availability zone, or globally.
When a router interface port is created, the relay service
will be added to it.
DHCP traffic on the subnet will go through the DHCP server
configured in the dhcp relay service on the NSX, if it is
connected to the router.

Also add admin utility to update exsiting router ports when the
dhcp relay configuration changes.

A future patch will take care of firewall rules allowint the dhcp traffic.

Change-Id: I626b3377e71c269600a47b3bd805eed9d58bad82
This commit is contained in:
Adit Sarfaty 2017-07-11 15:49:29 +03:00
parent dd49c633ce
commit 5dac3f4a4c
13 changed files with 135 additions and 3 deletions

View File

@ -193,6 +193,7 @@ function neutron_plugin_configure_service {
_nsxv3_ini_set native_metadata_route $NATIVE_METADATA_ROUTE _nsxv3_ini_set native_metadata_route $NATIVE_METADATA_ROUTE
_nsxv3_ini_set dhcp_profile $DHCP_PROFILE_UUID _nsxv3_ini_set dhcp_profile $DHCP_PROFILE_UUID
_nsxv3_ini_set metadata_proxy $METADATA_PROXY_UUID _nsxv3_ini_set metadata_proxy $METADATA_PROXY_UUID
_nsxv3_ini_set dhcp_relay_service $DHCP_RELAY_SERVICE
iniset $NEUTRON_CONF DEFAULT dhcp_agent_notification False iniset $NEUTRON_CONF DEFAULT dhcp_agent_notification False
fi fi
if [[ "$NSX_USE_CLIENT_CERT_AUTH" == "True" ]]; then if [[ "$NSX_USE_CLIENT_CERT_AUTH" == "True" ]]; then

View File

@ -13,3 +13,4 @@ NSX_MANAGER=<FILL_IN>
NSX_CONTROLLERS=<FILL_IN> NSX_CONTROLLERS=<FILL_IN>
DHCP_PROFILE_UUID=<FILL_IN> DHCP_PROFILE_UUID=<FILL_IN>
METADATA_PROXY_UUID=<FILL_IN> METADATA_PROXY_UUID=<FILL_IN>
DHCP_RELAY_SERVICE=<FILL_IN>

View File

@ -109,6 +109,7 @@ DEFAULT_EDGE_CLUSTER_UUID=<edge-cluster-uuid>
# Enabled native DHCP support from NSX backend # Enabled native DHCP support from NSX backend
DHCP_PROFILE_UUID=<dhcp-profile-uuid> DHCP_PROFILE_UUID=<dhcp-profile-uuid>
DHCP_RELAY_SERVICE=<dhcp-relay-service>
METADATA_PROXY_UUID=<metadata-proxy-uuid> METADATA_PROXY_UUID=<metadata-proxy-uuid>
METADATA_PROXY_SHARED_SECRET=<metadata-proxy-secret> METADATA_PROXY_SHARED_SECRET=<metadata-proxy-secret>
METADATA_PROXY_USE_HTTPS=False METADATA_PROXY_USE_HTTPS=False

View File

@ -278,6 +278,10 @@ Routers
nsxadmin -r routers -o nsx-update-rules nsxadmin -r routers -o nsx-update-rules
- Update DHCP relay service on NSX router ports according to the current configuration::
nsxadmin -r routers -o nsx-update-dhcp-relay
Orphaned Routers Orphaned Routers
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,11 @@
---
prelude: >
The NSX-v3 plugin supports DHCP relay service per network
availability zones.
features:
- The NSX-v3 plugin supports DHCP relay service per network
availability zones. When a router interface port is created,
the relay service will be added to it.
DHCP traffic on the subnet will go through the DHCP server
configured in the dhcp relay service on the NSX, if it is
connected to the router.

View File

@ -399,6 +399,10 @@ nsx_v3_opts = [
"that will be used to enable native metadata service. " "that will be used to enable native metadata service. "
"It needs to be created in NSX before starting Neutron " "It needs to be created in NSX before starting Neutron "
"with the NSX plugin.")), "with the NSX plugin.")),
cfg.StrOpt('dhcp_relay_service',
help=_("(Optional) This is the name or UUID of the NSX dhcp "
"relay service that will be used to enable DHCP relay "
"on router ports.")),
cfg.BoolOpt('log_security_groups_blocked_traffic', cfg.BoolOpt('log_security_groups_blocked_traffic',
default=False, default=False,
help=_("(Optional) Indicates whether distributed-firewall " help=_("(Optional) Indicates whether distributed-firewall "
@ -793,6 +797,10 @@ nsxv3_az_opts = [
cfg.ListOpt('switching_profiles', cfg.ListOpt('switching_profiles',
help=_("(Optional) list switching profiles uuids that will be " help=_("(Optional) list switching profiles uuids that will be "
"attached to all neutron created nsx ports.")), "attached to all neutron created nsx ports.")),
cfg.StrOpt('dhcp_relay_service',
help=_("(Optional) This is the name or UUID of the NSX dhcp "
"relay service that will be used to enable DHCP relay "
"on router ports.")),
] ]
# Register the configuration options # Register the configuration options

View File

@ -20,6 +20,7 @@ from vmware_nsx.common import availability_zones as common_az
from vmware_nsx.common import config from vmware_nsx.common import config
from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsxlib.v3 import core_resources from vmware_nsxlib.v3 import core_resources
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
DEFAULT_NAME = common_az.DEFAULT_NAME DEFAULT_NAME = common_az.DEFAULT_NAME
@ -78,6 +79,10 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone):
if self.switching_profiles is None: if self.switching_profiles is None:
self.switching_profiles = cfg.CONF.nsx_v3.switching_profiles self.switching_profiles = cfg.CONF.nsx_v3.switching_profiles
self.dhcp_relay_service = az_info.get('dhcp_relay_service')
if self.dhcp_relay_service is None:
self.dhcp_relay_service = cfg.CONF.nsx_v3.dhcp_relay_service
def init_default_az(self): def init_default_az(self):
# use the default configuration # use the default configuration
self.metadata_proxy = cfg.CONF.nsx_v3.metadata_proxy self.metadata_proxy = cfg.CONF.nsx_v3.metadata_proxy
@ -88,6 +93,7 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone):
self.default_overlay_tz = cfg.CONF.nsx_v3.default_overlay_tz self.default_overlay_tz = cfg.CONF.nsx_v3.default_overlay_tz
self.default_vlan_tz = cfg.CONF.nsx_v3.default_vlan_tz self.default_vlan_tz = cfg.CONF.nsx_v3.default_vlan_tz
self.switching_profiles = cfg.CONF.nsx_v3.switching_profiles self.switching_profiles = cfg.CONF.nsx_v3.switching_profiles
self.dhcp_relay_service = cfg.CONF.nsx_v3.dhcp_relay_service
def translate_configured_names_to_uuids(self, nsxlib): def translate_configured_names_to_uuids(self, nsxlib):
# Mandatory configurations (in AZ or inherited from global values) # Mandatory configurations (in AZ or inherited from global values)
@ -171,6 +177,23 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone):
nsx_profile.get('id'))) nsx_profile.get('id')))
self.switching_profiles_objs = profiles self.switching_profiles_objs = profiles
if (self.dhcp_relay_service and
nsxlib.feature_supported(nsxlib_consts.FEATURE_DHCP_RELAY)):
relay_id = None
if cfg.CONF.nsx_v3.init_objects_by_tags:
# Find the TZ by its tag
relay_id = nsxlib.get_id_by_resource_and_tag(
nsxlib.relay_service.resource_type,
cfg.CONF.nsx_v3.search_objects_scope,
self.dhcp_relay_service)
if not relay_id:
# Find the service by its name or id
relay_id = nsxlib.relay_service.get_id_by_name_or_id(
self.dhcp_relay_service)
self.dhcp_relay_service = relay_id
else:
self.dhcp_relay_service = None
class NsxV3AvailabilityZones(common_az.ConfiguredAvailabilityZones): class NsxV3AvailabilityZones(common_az.ConfiguredAvailabilityZones):

View File

@ -3260,13 +3260,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
port, resource_type='os-neutron-rport-id', port, resource_type='os-neutron-rport-id',
project_name=context.tenant_name) project_name=context.tenant_name)
tags.append({'scope': 'os-subnet-id', 'tag': subnet['id']}) tags.append({'scope': 'os-subnet-id', 'tag': subnet['id']})
net_az = self.get_network_az_by_net_id(context, network_id)
self._routerlib.create_logical_router_intf_port_by_ls_id( self._routerlib.create_logical_router_intf_port_by_ls_id(
logical_router_id=nsx_router_id, logical_router_id=nsx_router_id,
display_name=display_name, display_name=display_name,
tags=tags, tags=tags,
ls_id=nsx_net_id, ls_id=nsx_net_id,
logical_switch_port_id=nsx_port_id, logical_switch_port_id=nsx_port_id,
address_groups=address_groups) address_groups=address_groups,
relay_service_uuid=net_az.dhcp_relay_service)
if router_db.gw_port and not router_db.enable_snat: if router_db.gw_port and not router_db.enable_snat:
# TODO(berlin): Announce the subnet on tier0 if enable_snat # TODO(berlin): Announce the subnet on tier0 if enable_snat

View File

@ -347,6 +347,7 @@ class EdgeFwaasV3Driver(fwaas_base.FwaasDriverBase):
nsx_router_id, section_id = self._get_backend_router_and_fw_section( nsx_router_id, section_id = self._get_backend_router_and_fw_section(
context, router_id) context, router_id)
#TODO(asarfaty) add dhcp relay allow rules here
# Add default drop all rule at the end # Add default drop all rule at the end
old_default_rule = self.nsx_firewall.get_default_rule( old_default_rule = self.nsx_firewall.get_default_rule(
section_id) section_id)

View File

@ -14,6 +14,7 @@
import sys import sys
from vmware_nsx.common import config # noqa
from vmware_nsx.common import utils as nsx_utils from vmware_nsx.common import utils as nsx_utils
from vmware_nsx.db import db as nsx_db from vmware_nsx.db import db as nsx_db
from vmware_nsx.shell.admin.plugins.common import constants from vmware_nsx.shell.admin.plugins.common import constants
@ -22,11 +23,13 @@ from vmware_nsx.shell.admin.plugins.common import utils as admin_utils
from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils
from vmware_nsx.shell import resources as shell from vmware_nsx.shell import resources as shell
from vmware_nsxlib.v3 import exceptions as nsx_exc from vmware_nsxlib.v3 import exceptions as nsx_exc
from vmware_nsxlib.v3 import nsx_constants
from neutron.db import db_base_plugin_v2 from neutron.db import db_base_plugin_v2
from neutron.db import l3_db from neutron.db import l3_db
from neutron_lib.callbacks import registry from neutron_lib.callbacks import registry
from neutron_lib import context as neutron_context from neutron_lib import context as neutron_context
from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -173,6 +176,42 @@ def delete_backend_router(resource, event, trigger, **kwargs):
LOG.error("Failed to delete backend router %s.", nsx_id) LOG.error("Failed to delete backend router %s.", nsx_id)
@admin_utils.output_header
def update_dhcp_relay(resource, event, trigger, **kwargs):
"""Update all routers dhcp relay service by the current configuration"""
nsxlib = utils.get_connected_nsxlib()
if not nsxlib.feature_supported(nsx_constants.FEATURE_DHCP_RELAY):
version = nsxlib.get_version()
LOG.error("DHCP relay is not supported by NSX version %s", version)
return
# initialize the availability zones and nsxlib
config.register_nsxv3_azs(cfg.CONF, cfg.CONF.nsx_v3.availability_zones)
# get all neutron router interfaces ports
admin_cxt = neutron_context.get_admin_context()
with utils.NsxV3PluginWrapper() as plugin:
filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
ports = plugin.get_ports(admin_cxt, filters=filters)
for port in ports:
# get the backend router port by the tag
nsx_port_id = nsxlib.get_id_by_resource_and_tag(
'LogicalRouterDownLinkPort',
'os-neutron-rport-id', port['id'])
if not nsx_port_id:
LOG.warning("Couldn't find nsx router port for interface %s",
port['id'])
continue
# get the network of this port
network_id = port['network_id']
# check the relay service on the az of the network
az = plugin.get_network_az_by_net_id(admin_cxt, network_id)
nsxlib.logical_router_port.update(
nsx_port_id, relay_service_uuid=az.dhcp_relay_service)
#TODO(asarfaty) also update the firewall rules of the routers
LOG.info("Done.")
registry.subscribe(list_missing_routers, registry.subscribe(list_missing_routers,
constants.ROUTERS, constants.ROUTERS,
shell.Operations.LIST_MISMATCHES.value) shell.Operations.LIST_MISMATCHES.value)
@ -188,3 +227,7 @@ registry.subscribe(list_orphaned_routers,
registry.subscribe(delete_backend_router, registry.subscribe(delete_backend_router,
constants.ORPHANED_ROUTERS, constants.ORPHANED_ROUTERS,
shell.Operations.NSX_CLEAN.value) shell.Operations.NSX_CLEAN.value)
registry.subscribe(update_dhcp_relay,
constants.ROUTERS,
shell.Operations.NSX_UPDATE_DHCP_RELAY.value)

View File

@ -49,6 +49,7 @@ class Operations(enum.Enum):
NSX_UPDATE_ALL = 'nsx-update-all' NSX_UPDATE_ALL = 'nsx-update-all'
NSX_UPDATE_SECRET = 'nsx-update-secret' NSX_UPDATE_SECRET = 'nsx-update-secret'
NSX_UPDATE_RULES = 'nsx-update-rules' NSX_UPDATE_RULES = 'nsx-update-rules'
NSX_UPDATE_DHCP_RELAY = 'nsx-update-dhcp-relay'
NSX_UPDATE_IP = 'nsx-update-ip' NSX_UPDATE_IP = 'nsx-update-ip'
NSX_RECREATE = 'nsx-recreate' NSX_RECREATE = 'nsx-recreate'
NSX_REORDER = 'nsx-reorder' NSX_REORDER = 'nsx-reorder'
@ -95,7 +96,8 @@ nsxv3_resources = {
Operations.NSX_MIGRATE_EXCLUDE_PORTS.value]), Operations.NSX_MIGRATE_EXCLUDE_PORTS.value]),
constants.ROUTERS: Resource(constants.ROUTERS, constants.ROUTERS: Resource(constants.ROUTERS,
[Operations.LIST_MISMATCHES.value, [Operations.LIST_MISMATCHES.value,
Operations.NSX_UPDATE_RULES.value]), Operations.NSX_UPDATE_RULES.value,
Operations.NSX_UPDATE_DHCP_RELAY.value]),
constants.DHCP_BINDING: Resource(constants.DHCP_BINDING, constants.DHCP_BINDING: Resource(constants.DHCP_BINDING,
[Operations.LIST.value, [Operations.LIST.value,
Operations.NSX_UPDATE.value]), Operations.NSX_UPDATE.value]),

View File

@ -41,6 +41,7 @@ class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase):
cfg.CONF.set_override("dns_domain", "xxx.com", group="nsx_v3") cfg.CONF.set_override("dns_domain", "xxx.com", group="nsx_v3")
cfg.CONF.set_override("nameservers", ["10.1.1.1"], group="nsx_v3") cfg.CONF.set_override("nameservers", ["10.1.1.1"], group="nsx_v3")
cfg.CONF.set_override("switching_profiles", ["uuid1"], group="nsx_v3") cfg.CONF.set_override("switching_profiles", ["uuid1"], group="nsx_v3")
cfg.CONF.set_override("dhcp_relay_service", "service1", group="nsx_v3")
def _config_az(self, def _config_az(self,
metadata_proxy="metadata_proxy1", metadata_proxy="metadata_proxy1",
@ -50,7 +51,8 @@ class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase):
nameservers=["20.1.1.1"], nameservers=["20.1.1.1"],
default_overlay_tz='otz', default_overlay_tz='otz',
default_vlan_tz='vtz', default_vlan_tz='vtz',
switching_profiles=["uuid2"]): switching_profiles=["uuid2"],
dhcp_relay_service="service2"):
if metadata_proxy is not None: if metadata_proxy is not None:
cfg.CONF.set_override("metadata_proxy", metadata_proxy, cfg.CONF.set_override("metadata_proxy", metadata_proxy,
group=self.group_name) group=self.group_name)
@ -76,6 +78,9 @@ class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase):
if switching_profiles is not None: if switching_profiles is not None:
cfg.CONF.set_override("switching_profiles", switching_profiles, cfg.CONF.set_override("switching_profiles", switching_profiles,
group=self.group_name) group=self.group_name)
if dhcp_relay_service is not None:
cfg.CONF.set_override("dhcp_relay_service", dhcp_relay_service,
group=self.group_name)
def test_simple_availability_zone(self): def test_simple_availability_zone(self):
self._config_az() self._config_az()
@ -89,6 +94,7 @@ class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase):
self.assertEqual("otz", az.default_overlay_tz) self.assertEqual("otz", az.default_overlay_tz)
self.assertEqual("vtz", az.default_vlan_tz) self.assertEqual("vtz", az.default_vlan_tz)
self.assertEqual(["uuid2"], az.switching_profiles) self.assertEqual(["uuid2"], az.switching_profiles)
self.assertEqual("service2", az.dhcp_relay_service)
def test_missing_group_section(self): def test_missing_group_section(self):
self.assertRaises( self.assertRaises(

View File

@ -64,6 +64,7 @@ NSX_TZ_NAME = 'default transport zone'
NSX_DHCP_PROFILE_ID = 'default dhcp profile' NSX_DHCP_PROFILE_ID = 'default dhcp profile'
NSX_METADATA_PROXY_ID = 'default metadata proxy' NSX_METADATA_PROXY_ID = 'default metadata proxy'
NSX_SWITCH_PROFILE = 'dummy switch profile' NSX_SWITCH_PROFILE = 'dummy switch profile'
NSX_DHCP_RELAY_SRV = 'dhcp relay srv'
def _mock_create_firewall_rules(*args): def _mock_create_firewall_rules(*args):
@ -134,6 +135,11 @@ def _mock_nsx_backend_calls():
"get_id_by_name_or_id", "get_id_by_name_or_id",
return_value=NSX_DHCP_PROFILE_ID).start() return_value=NSX_DHCP_PROFILE_ID).start()
mock.patch(
"vmware_nsxlib.v3.core_resources.NsxLibDhcpRelayService."
"get_id_by_name_or_id",
return_value=NSX_DHCP_RELAY_SRV).start()
mock.patch( mock.patch(
"vmware_nsxlib.v3.core_resources.NsxLibMetadataProxy." "vmware_nsxlib.v3.core_resources.NsxLibMetadataProxy."
"get_id_by_name_or_id", "get_id_by_name_or_id",
@ -1315,6 +1321,29 @@ class TestL3NatTestCase(L3NatTest,
{'router': {'admin_state_up': False}}, {'router': {'admin_state_up': False}},
expected_code=exc.HTTPBadRequest.code) expected_code=exc.HTTPBadRequest.code)
def test_router_dhcp_relay(self):
# Add the relay service to the config and availability zones
cfg.CONF.set_override('dhcp_relay_service', NSX_DHCP_RELAY_SRV,
'nsx_v3')
mock_nsx_version = mock.patch.object(
self.plugin.nsxlib, 'feature_supported', return_value=True)
mock_nsx_version.start()
self.plugin.init_availability_zones()
for az in self.plugin.get_azs_list():
az.translate_configured_names_to_uuids(self.plugin.nsxlib)
with self.network() as network:
with self.subnet(network=network) as s1,\
self.router() as r1,\
mock.patch.object(self.plugin.nsxlib.logical_router_port,
'update') as mock_update_port:
self._router_interface_action('add', r1['router']['id'],
s1['subnet']['id'], None)
mock_update_port.assert_called_once_with(
mock.ANY,
relay_service_uuid=NSX_DHCP_RELAY_SRV,
subnets=mock.ANY)
class ExtGwModeTestCase(test_ext_gw_mode.ExtGwModeIntTestCase, class ExtGwModeTestCase(test_ext_gw_mode.ExtGwModeIntTestCase,
L3NatTest): L3NatTest):