diff --git a/etc/nsx.ini b/etc/nsx.ini index ca4eb74043..04e05e1715 100644 --- a/etc/nsx.ini +++ b/etc/nsx.ini @@ -199,6 +199,15 @@ # logged. # log_security_groups_allowed_traffic = False +# (Optional) In some cases the Neutron router is not present to provide the +# metadata IP but the DHCP server can be used to provide this info. Setting +# this value will force the DHCP edge server to append specific host routes +# to the DHCP request. If this option is set, then the metadata service will +# be activated for all the dhcp enabled networks. +# Note: this option can only be supported at NSX manager version 6.2.3 or +# higher +# dhcp_force_metadata = True + [nsx] # Maximum number of ports for each bridged logical switch # The recommended value for this parameter varies with NSX version diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index f9ebe0656d..ce26819629 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -417,6 +417,14 @@ nsxv_opts = [ default=False, help=_("Indicates whether distributed-firewall " "security-groups allowed traffic is logged")), + cfg.BoolOpt('dhcp_force_metadata', default=True, + help=_("In some cases the Neutron router is not present to " + "provide the metadata IP but the DHCP server can be " + "used to provide this info. Setting this value will " + "force the DHCP server to append specific host routes " + "to the DHCP request. If this option is set, then the " + "metadata service will be activated for all the " + "dhcp enabled networks.")), ] # Register the configuration options diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 5228d73551..b6e8674ece 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -1196,6 +1196,24 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, return False + def _get_dhcp_ip_addr_from_subnet(self, context, subnet_id): + dhcp_port_filters = {'fixed_ips': {'subnet_id': [subnet_id]}, + 'device_owner': [constants.DEVICE_OWNER_DHCP]} + dhcp_ports = self.get_ports(context, filters=dhcp_port_filters) + if dhcp_ports and dhcp_ports[0].get('fixed_ips'): + return dhcp_ports[0]['fixed_ips'][0]['ip_address'] + + def is_dhcp_metadata(self, context, subnet_id): + try: + subnet = self.get_subnet(context, subnet_id) + except n_exc.SubnetNotFound: + LOG.debug("subnet %s not found to determine its dhcp meta", + subnet_id) + return False + return bool(subnet['enable_dhcp'] and + self.metadata_proxy_handler and + cfg.CONF.nsxv.dhcp_force_metadata) + def create_subnet(self, context, subnet): """Create subnet on nsx_v provider network. @@ -1284,7 +1302,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, update_dns_search_domain = self._process_subnet_ext_attr_update( context.session, subnet, s) if (gateway_ip != subnet['gateway_ip'] or update_dns_search_domain or - set(orig['dns_nameservers']) != set(subnet['dns_nameservers'])): + set(orig['dns_nameservers']) != set(subnet['dns_nameservers']) or + enable_dhcp and not subnet['enable_dhcp']): # Need to ensure that all of the subnet attributes will be reloaded # when creating the edge bindings. Without adding this the original # subnet details are provided. diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py index b80f743fcb..470f78eb9e 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py @@ -17,6 +17,7 @@ from distutils import version import eventlet import netaddr import random +import six from sqlalchemy import exc as db_base_exc import time @@ -115,6 +116,19 @@ class EdgeManager(object): self.per_interface_rp_filter = self._get_per_edge_rp_filter_state() self.worker_pool = eventlet.GreenPool(WORKER_POOL_SIZE) self._check_backup_edge_pools() + self._validate_new_features() + + def _validate_new_features(self): + self.is_dhcp_opt_enabled = False + + ver = self.nsxv_manager.vcns.get_version() + if version.LooseVersion(ver) >= version.LooseVersion('6.2.3'): + self.is_dhcp_opt_enabled = True + elif cfg.CONF.nsxv.dhcp_force_metadata: + LOG.warning(_LW("Skipping dhcp_force_metadata param since dhcp " + "option feature can only be supported at version " + "6.2.3 or higher")) + self.is_dhcp_opt_enabled = False def _get_per_edge_rp_filter_state(self): ver = self.nsxv_manager.vcns.get_version() @@ -726,10 +740,50 @@ class EdgeManager(object): subnet_id) if sub_binding: static_config['domainName'] = sub_binding.dns_search_domain + self.handle_meta_static_route( + context, subnet_id, [static_config]) static_bindings.append(static_config) return static_bindings + def add_host_route_on_static_bindings(self, static_bindings, + dest_cidr, nexthop): + """Add one host route on a bulk of static bindings config. + + We can add host route on VM via dhcp option121. this func can only + works at NSXv version 6.2.3 or higher. + """ + for binding in static_bindings: + if 'dhcpOptions' not in six.iterkeys(binding): + binding['dhcpOptions'] = {} + if 'option121' not in six.iterkeys(binding['dhcpOptions']): + binding['dhcpOptions']['option121'] = {'staticRoutes': []} + binding_opt121 = binding['dhcpOptions']['option121'] + if 'staticRoutes' not in six.iterkeys(binding_opt121): + binding_opt121['staticRoutes'] = [] + binding_opt121['staticRoutes'].append({ + 'destinationSubnet': dest_cidr, + 'router': nexthop}) + return static_bindings + + def handle_meta_static_route(self, context, subnet_id, static_bindings): + is_dhcp_option121 = ( + self.is_dhcp_opt_enabled and + self.nsxv_plugin.is_dhcp_metadata( + context, subnet_id)) + if is_dhcp_option121: + dhcp_ip = self.nsxv_plugin._get_dhcp_ip_addr_from_subnet( + context, subnet_id) + if dhcp_ip: + self.add_host_route_on_static_bindings( + static_bindings, + '169.254.169.254/32', + dhcp_ip) + else: + LOG.error(_LE("Failed to find the dhcp port on subnet " + "%s to do metadata host route insertion"), + subnet_id) + def update_dhcp_service_config(self, context, edge_id): """Reconfigure the DHCP to the edge.""" # Get all networks attached to the edge @@ -737,16 +791,23 @@ class EdgeManager(object): context.session, edge_id) dhcp_networks = [edge_vnic_binding.network_id for edge_vnic_binding in edge_vnic_bindings] - ports = self.nsxv_plugin.get_ports( - context.elevated(), filters={'network_id': dhcp_networks}) - inst_ports = [port - for port in ports - if port['device_owner'].startswith("compute")] + + subnets = self.nsxv_plugin.get_subnets( + context.elevated(), filters={'network_id': dhcp_networks, + 'enable_dhcp': [True]}) + static_bindings = [] - for port in inst_ports: - static_bindings.extend( - self.create_static_binding( - context.elevated(), port)) + for subnet in subnets: + ports = self.nsxv_plugin.get_ports( + context.elevated(), + filters={'network_id': [subnet['network_id']], + 'fixed_ips': {'subnet_id': [subnet['id']]}}) + inst_ports = [port for port in ports + if port['device_owner'].startswith('compute')] + for port in inst_ports: + static_bindings.extend( + self.create_static_binding( + context.elevated(), port)) dhcp_request = { 'featureType': "dhcp_4.0", 'enabled': True, diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index 207fc0d52b..243cd8d1b5 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -1912,6 +1912,29 @@ class L3NatTestCaseBase(test_l3_plugin.L3NatTestCaseMixin): None, expected_code=error_code) + def test_subnet_dhcp_metadata_with_update(self): + cfg.CONF.set_override('dhcp_force_metadata', True, group='nsxv') + self.plugin_instance.metadata_proxy_handler = mock.Mock() + with self.subnet(cidr="10.0.0.0/24", enable_dhcp=True) as s1: + subnet_id = s1['subnet']['id'] + is_dhcp_meta = self.plugin_instance.is_dhcp_metadata( + context.get_admin_context(), subnet_id) + self.assertTrue(is_dhcp_meta) + port_data = {'port': {'tenant_id': s1['subnet']['tenant_id'], + 'network_id': s1['subnet']['network_id'], + 'device_owner': 'compute:None'}} + req = self.new_create_request( + 'ports', port_data).get_response(self.api) + port_req = self.deserialize(self.fmt, req) + subnet_data = {'subnet': {'enable_dhcp': False}} + self.new_update_request( + 'subnets', subnet_data, + s1['subnet']['id']).get_response(self.api) + is_dhcp_meta = self.plugin_instance.is_dhcp_metadata( + context.get_admin_context(), subnet_id) + self.assertFalse(is_dhcp_meta) + self.new_delete_request('ports', port_req['port']['id']) + def test_router_delete_ipv6_slaac_subnet_inuse_returns_409(self): self.skipTest('No DHCP v6 Support yet') diff --git a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py index aa503a0021..702cefc41c 100644 --- a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py +++ b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py @@ -1130,3 +1130,10 @@ class FakeVcns(object): } response = {} return (header, response) + + def get_routes(self, edge_id): + header = { + 'status': 204 + } + response = {'staticRoutes': {'staticRoutes': []}} + return (header, response)