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):