From 5dac3f4a4c6b5dbc573a027624c7b5dad3f79507 Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Tue, 11 Jul 2017 15:49:29 +0300 Subject: [PATCH] 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 --- devstack/lib/vmware_nsx_v3 | 1 + devstack/localrc_nsx_v3 | 1 + devstack/nsx_v3/controller_local.conf.sample | 1 + doc/source/admin_util.rst | 4 ++ .../notes/nsxv3-dhcp-relay-32cf1ae281e1.yaml | 11 +++++ vmware_nsx/common/config.py | 8 ++++ .../plugins/nsx_v3/availability_zones.py | 23 ++++++++++ vmware_nsx/plugins/nsx_v3/plugin.py | 4 +- .../fwaas/nsx_v3/edge_fwaas_driver.py | 1 + .../admin/plugins/nsxv3/resources/routers.py | 43 +++++++++++++++++++ vmware_nsx/shell/resources.py | 4 +- .../unit/nsx_v3/test_availability_zones.py | 8 +++- vmware_nsx/tests/unit/nsx_v3/test_plugin.py | 29 +++++++++++++ 13 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/nsxv3-dhcp-relay-32cf1ae281e1.yaml diff --git a/devstack/lib/vmware_nsx_v3 b/devstack/lib/vmware_nsx_v3 index 0fe45bae6d..0a5da49b2c 100644 --- a/devstack/lib/vmware_nsx_v3 +++ b/devstack/lib/vmware_nsx_v3 @@ -193,6 +193,7 @@ function neutron_plugin_configure_service { _nsxv3_ini_set native_metadata_route $NATIVE_METADATA_ROUTE _nsxv3_ini_set dhcp_profile $DHCP_PROFILE_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 fi if [[ "$NSX_USE_CLIENT_CERT_AUTH" == "True" ]]; then diff --git a/devstack/localrc_nsx_v3 b/devstack/localrc_nsx_v3 index 8494155b54..d65b2f8a62 100644 --- a/devstack/localrc_nsx_v3 +++ b/devstack/localrc_nsx_v3 @@ -13,3 +13,4 @@ NSX_MANAGER= NSX_CONTROLLERS= DHCP_PROFILE_UUID= METADATA_PROXY_UUID= +DHCP_RELAY_SERVICE= diff --git a/devstack/nsx_v3/controller_local.conf.sample b/devstack/nsx_v3/controller_local.conf.sample index 9359cf547d..6fbe6ba67f 100644 --- a/devstack/nsx_v3/controller_local.conf.sample +++ b/devstack/nsx_v3/controller_local.conf.sample @@ -109,6 +109,7 @@ DEFAULT_EDGE_CLUSTER_UUID= # Enabled native DHCP support from NSX backend DHCP_PROFILE_UUID= +DHCP_RELAY_SERVICE= METADATA_PROXY_UUID= METADATA_PROXY_SHARED_SECRET= METADATA_PROXY_USE_HTTPS=False diff --git a/doc/source/admin_util.rst b/doc/source/admin_util.rst index de5a2eaca9..c3796d4048 100644 --- a/doc/source/admin_util.rst +++ b/doc/source/admin_util.rst @@ -278,6 +278,10 @@ Routers 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 ~~~~~~~~~~~~~~~~~ diff --git a/releasenotes/notes/nsxv3-dhcp-relay-32cf1ae281e1.yaml b/releasenotes/notes/nsxv3-dhcp-relay-32cf1ae281e1.yaml new file mode 100644 index 0000000000..a3e24206ae --- /dev/null +++ b/releasenotes/notes/nsxv3-dhcp-relay-32cf1ae281e1.yaml @@ -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. \ No newline at end of file diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index 07cf1d190b..cd108eac48 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -399,6 +399,10 @@ nsx_v3_opts = [ "that will be used to enable native metadata service. " "It needs to be created in NSX before starting Neutron " "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', default=False, help=_("(Optional) Indicates whether distributed-firewall " @@ -793,6 +797,10 @@ nsxv3_az_opts = [ cfg.ListOpt('switching_profiles', help=_("(Optional) list switching profiles uuids that will be " "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 diff --git a/vmware_nsx/plugins/nsx_v3/availability_zones.py b/vmware_nsx/plugins/nsx_v3/availability_zones.py index a50f7f8bb3..9ab854a472 100644 --- a/vmware_nsx/plugins/nsx_v3/availability_zones.py +++ b/vmware_nsx/plugins/nsx_v3/availability_zones.py @@ -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 exceptions as nsx_exc from vmware_nsxlib.v3 import core_resources +from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts DEFAULT_NAME = common_az.DEFAULT_NAME @@ -78,6 +79,10 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone): if self.switching_profiles is None: 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): # use the default configuration 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_vlan_tz = cfg.CONF.nsx_v3.default_vlan_tz 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): # Mandatory configurations (in AZ or inherited from global values) @@ -171,6 +177,23 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone): nsx_profile.get('id'))) 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): diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 28284537c7..657aa89dd5 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -3260,13 +3260,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, port, resource_type='os-neutron-rport-id', project_name=context.tenant_name) 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( logical_router_id=nsx_router_id, display_name=display_name, tags=tags, ls_id=nsx_net_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: # TODO(berlin): Announce the subnet on tier0 if enable_snat diff --git a/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver.py b/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver.py index 1bffb8c119..eab3dc9796 100644 --- a/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver.py +++ b/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver.py @@ -347,6 +347,7 @@ class EdgeFwaasV3Driver(fwaas_base.FwaasDriverBase): nsx_router_id, section_id = self._get_backend_router_and_fw_section( context, router_id) + #TODO(asarfaty) add dhcp relay allow rules here # Add default drop all rule at the end old_default_rule = self.nsx_firewall.get_default_rule( section_id) diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/routers.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/routers.py index 0d6f40e18c..8df8ffadf4 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/routers.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/routers.py @@ -14,6 +14,7 @@ import sys +from vmware_nsx.common import config # noqa from vmware_nsx.common import utils as nsx_utils from vmware_nsx.db import db as nsx_db 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 import resources as shell 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 l3_db from neutron_lib.callbacks import registry from neutron_lib import context as neutron_context +from oslo_config import cfg from oslo_log import log as logging 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) +@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, constants.ROUTERS, shell.Operations.LIST_MISMATCHES.value) @@ -188,3 +227,7 @@ registry.subscribe(list_orphaned_routers, registry.subscribe(delete_backend_router, constants.ORPHANED_ROUTERS, shell.Operations.NSX_CLEAN.value) + +registry.subscribe(update_dhcp_relay, + constants.ROUTERS, + shell.Operations.NSX_UPDATE_DHCP_RELAY.value) diff --git a/vmware_nsx/shell/resources.py b/vmware_nsx/shell/resources.py index f2ebcb8dad..aabcf97e5d 100644 --- a/vmware_nsx/shell/resources.py +++ b/vmware_nsx/shell/resources.py @@ -49,6 +49,7 @@ class Operations(enum.Enum): NSX_UPDATE_ALL = 'nsx-update-all' NSX_UPDATE_SECRET = 'nsx-update-secret' NSX_UPDATE_RULES = 'nsx-update-rules' + NSX_UPDATE_DHCP_RELAY = 'nsx-update-dhcp-relay' NSX_UPDATE_IP = 'nsx-update-ip' NSX_RECREATE = 'nsx-recreate' NSX_REORDER = 'nsx-reorder' @@ -95,7 +96,8 @@ nsxv3_resources = { Operations.NSX_MIGRATE_EXCLUDE_PORTS.value]), constants.ROUTERS: Resource(constants.ROUTERS, [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, [Operations.LIST.value, Operations.NSX_UPDATE.value]), diff --git a/vmware_nsx/tests/unit/nsx_v3/test_availability_zones.py b/vmware_nsx/tests/unit/nsx_v3/test_availability_zones.py index 0ce6afca67..04330bbf66 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_availability_zones.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_availability_zones.py @@ -41,6 +41,7 @@ class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase): 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("switching_profiles", ["uuid1"], group="nsx_v3") + cfg.CONF.set_override("dhcp_relay_service", "service1", group="nsx_v3") def _config_az(self, metadata_proxy="metadata_proxy1", @@ -50,7 +51,8 @@ class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase): nameservers=["20.1.1.1"], default_overlay_tz='otz', default_vlan_tz='vtz', - switching_profiles=["uuid2"]): + switching_profiles=["uuid2"], + dhcp_relay_service="service2"): if metadata_proxy is not None: cfg.CONF.set_override("metadata_proxy", metadata_proxy, group=self.group_name) @@ -76,6 +78,9 @@ class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase): if switching_profiles is not None: cfg.CONF.set_override("switching_profiles", switching_profiles, 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): self._config_az() @@ -89,6 +94,7 @@ class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase): self.assertEqual("otz", az.default_overlay_tz) self.assertEqual("vtz", az.default_vlan_tz) self.assertEqual(["uuid2"], az.switching_profiles) + self.assertEqual("service2", az.dhcp_relay_service) def test_missing_group_section(self): self.assertRaises( diff --git a/vmware_nsx/tests/unit/nsx_v3/test_plugin.py b/vmware_nsx/tests/unit/nsx_v3/test_plugin.py index ef179ffed1..22fca7f423 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_plugin.py @@ -64,6 +64,7 @@ NSX_TZ_NAME = 'default transport zone' NSX_DHCP_PROFILE_ID = 'default dhcp profile' NSX_METADATA_PROXY_ID = 'default metadata proxy' NSX_SWITCH_PROFILE = 'dummy switch profile' +NSX_DHCP_RELAY_SRV = 'dhcp relay srv' def _mock_create_firewall_rules(*args): @@ -134,6 +135,11 @@ def _mock_nsx_backend_calls(): "get_id_by_name_or_id", 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( "vmware_nsxlib.v3.core_resources.NsxLibMetadataProxy." "get_id_by_name_or_id", @@ -1315,6 +1321,29 @@ class TestL3NatTestCase(L3NatTest, {'router': {'admin_state_up': False}}, 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, L3NatTest):