diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index a66e3192cd..cbbed0151a 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -370,10 +370,6 @@ nsx_v3_and_p = [ default="169.254.169.254/31", help=_("The metadata route used for native metadata proxy " "service.")), - 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.StrOpt('dns_domain', default='openstacklocal', help=_("Domain to use for building the hostnames.")), @@ -402,6 +398,9 @@ nsx_v3_and_p = [ help=_("This is the scope of the tag that will be used for " "finding the objects uuids on the NSX during plugin " "init.")), + cfg.IntOpt('dhcp_lease_time', + default=86400, + help=_("DHCP default lease time.")), ] nsx_v3_opts = nsx_v3_and_p + [ @@ -459,9 +458,6 @@ nsx_v3_opts = nsx_v3_and_p + [ default=True, help=_("If true, DHCP and metadata proxy services will be " "provided by NSX backend.")), - cfg.IntOpt('dhcp_lease_time', - default=86400, - help=_("DHCP default lease time.")), cfg.ListOpt('switching_profiles', default=[], help=_("Optional parameter defining a list switching profiles " @@ -476,6 +472,10 @@ nsx_v3_opts = nsx_v3_and_p + [ help=_("When True, port security will be set to False for " "newly created ENS networks and ports, overriding " "user settings")), + 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.ListOpt('housekeeping_jobs', default=['orphaned_dhcp_server', 'orphaned_logical_switch', 'orphaned_logical_router', 'mismatch_logical_port', diff --git a/vmware_nsx/plugins/common_v3/availability_zones.py b/vmware_nsx/plugins/common_v3/availability_zones.py index d83552f0cc..e9db3da5c2 100644 --- a/vmware_nsx/plugins/common_v3/availability_zones.py +++ b/vmware_nsx/plugins/common_v3/availability_zones.py @@ -85,10 +85,6 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone): if nameservers: self.nameservers = nameservers - dhcp_relay_service = az_info.get('dhcp_relay_service') - if dhcp_relay_service: - self.dhcp_relay_service = dhcp_relay_service - def init_defaults(self): # Should be implemented by children pass diff --git a/vmware_nsx/plugins/common_v3/plugin.py b/vmware_nsx/plugins/common_v3/plugin.py index 48fa3e4f3f..4812b33515 100644 --- a/vmware_nsx/plugins/common_v3/plugin.py +++ b/vmware_nsx/plugins/common_v3/plugin.py @@ -27,6 +27,7 @@ from six import moves from neutron.db import agentschedulers_db from neutron.db import allowedaddresspairs_db as addr_pair_db from neutron.db.availability_zone import router as router_az_db +from neutron.db import db_base_plugin_v2 from neutron.db import dns_db from neutron.db import external_net_db from neutron.db import extradhcpopt_db @@ -34,6 +35,7 @@ from neutron.db import extraroute_db from neutron.db import l3_attrs_db from neutron.db import l3_db from neutron.db import l3_gwmode_db +from neutron.db import models_v2 from neutron.db import portbindings_db from neutron.db import portsecurity_db from neutron.db import securitygroups_db @@ -42,6 +44,7 @@ from neutron.extensions import securitygroup as ext_sg from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef from neutron_lib.api.definitions import availability_zone as az_def from neutron_lib.api.definitions import external_net as extnet_apidef +from neutron_lib.api.definitions import extra_dhcp_opt as ext_edo from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import portbindings as pbin from neutron_lib.api.definitions import provider_net as pnet @@ -79,6 +82,7 @@ from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils from vmware_nsxlib.v3 import exceptions as nsx_lib_exc from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts +from vmware_nsxlib.v3 import utils as nsxlib_utils LOG = logging.getLogger(__name__) @@ -115,20 +119,32 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, super(NsxPluginV3Base, self).__init__() self._network_vlans = plugin_utils.parse_network_vlan_ranges( self._get_conf_attr('network_vlan_ranges')) + self._native_dhcp_enabled = False def _init_native_dhcp(self): if not self.nsxlib: + self._native_dhcp_enabled = False return - try: - for az in self.get_azs_list(): - self.nsxlib.native_dhcp_profile.get( - az._native_dhcp_profile_uuid) - except nsx_lib_exc.ManagerError: - with excutils.save_and_reraise_exception(): - LOG.error("Unable to retrieve DHCP Profile %s, " + self._native_dhcp_enabled = True + for az in self.get_azs_list(): + if not az._native_dhcp_profile_uuid: + LOG.error("Unable to retrieve DHCP Profile %s for " + "availability zone %s, " "native DHCP service is not supported", - az._native_dhcp_profile_uuid) + az.name, az.dhcp_profile) + self._native_dhcp_enabled = False + + def _init_native_metadata(self): + if not self.nsxlib: + return + + for az in self.get_azs_list(): + if not az._native_md_proxy_uuid: + LOG.error("Unable to retrieve Metadata Proxy %s for " + "availability zone %s, " + "native metadata service is not supported", + az.name, az.metadata_proxy) def _extend_fault_map(self): """Extends the Neutron Fault Map. @@ -150,19 +166,6 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, webob.exc.HTTPBadRequest, }) - def _init_native_metadata(self): - if not self.nsxlib: - return - - try: - for az in self.get_azs_list(): - self.nsxlib.native_md_proxy.get(az._native_md_proxy_uuid) - except nsx_lib_exc.ManagerError: - with excutils.save_and_reraise_exception(): - LOG.error("Unable to retrieve Metadata Proxy %s, " - "native metadata service is not supported", - az._native_md_proxy_uuid) - def _get_conf_attr(self, attr): plugin_cfg = getattr(cfg.CONF, self.cfg_group) return getattr(plugin_cfg, attr) @@ -458,6 +461,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._assert_on_external_net_with_compute(port_data) self._assert_on_port_admin_state(port_data, device_owner) + self._validate_extra_dhcp_options(port_data.get(ext_edo.EXTRADHCPOPTS)) def _assert_on_vpn_port_change(self, port_data): if port_data['device_owner'] == ipsec_utils.VPN_PORT_OWNER: @@ -554,6 +558,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, port_data.get('fixed_ips', []), device_owner) self._assert_on_vpn_port_change(original_port) self._assert_on_lb_port_fixed_ip_change(port_data, orig_dev_owner) + self._validate_extra_dhcp_options(port_data.get(ext_edo.EXTRADHCPOPTS)) def _get_dhcp_port_name(self, net_name, net_id): return utils.get_name_and_uuid('%s-%s' % ('dhcp', @@ -1334,6 +1339,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # Disable native DHCP service on the backend for this network. # First delete the DHCP port in this network. Then delete the # corresponding LogicalDhcpServer for this network. + self._ensure_native_dhcp() dhcp_service = nsx_db.get_nsx_service_binding( context.session, network_id, nsxlib_consts.SERVICE_DHCP) if not dhcp_service: @@ -1379,6 +1385,226 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, LOG.error("Unable to delete DHCP server mapping for " "network %s", network_id) + def _filter_ipv4_dhcp_fixed_ips(self, context, fixed_ips): + ips = [] + for fixed_ip in fixed_ips: + if netaddr.IPNetwork(fixed_ip['ip_address']).version != 4: + continue + with db_api.CONTEXT_READER.using(context): + subnet = self.get_subnet(context, fixed_ip['subnet_id']) + if subnet['enable_dhcp']: + ips.append(fixed_ip) + return ips + + def _add_dhcp_binding(self, context, port): + if not utils.is_port_dhcp_configurable(port): + return + dhcp_service = nsx_db.get_nsx_service_binding( + context.session, port['network_id'], nsxlib_consts.SERVICE_DHCP) + if not dhcp_service: + return + for fixed_ip in self._filter_ipv4_dhcp_fixed_ips( + context, port['fixed_ips']): + binding = self._add_dhcp_binding_on_server( + context, dhcp_service['nsx_service_id'], fixed_ip['subnet_id'], + fixed_ip['ip_address'], port) + try: + nsx_db.add_neutron_nsx_dhcp_binding( + context.session, port['id'], fixed_ip['subnet_id'], + fixed_ip['ip_address'], dhcp_service['nsx_service_id'], + binding['id']) + except (db_exc.DBError, sql_exc.TimeoutError): + LOG.error("Failed to add mapping of DHCP binding " + "%(binding)s for port %(port)s, deleting " + "DHCP binding on server", + {'binding': binding['id'], 'port': port['id']}) + fake_db_binding = { + 'port_id': port['id'], + 'nsx_service_id': dhcp_service['nsx_service_id'], + 'nsx_binding_id': binding['id']} + self._delete_dhcp_binding_on_server(context, fake_db_binding) + + def _add_dhcp_binding_on_server(self, context, dhcp_service_id, subnet_id, + ip, port): + try: + hostname = 'host-%s' % ip.replace('.', '-') + subnet = self.get_subnet(context, subnet_id) + gateway_ip = subnet.get('gateway_ip') + options = self._get_dhcp_options( + context, ip, port.get(ext_edo.EXTRADHCPOPTS), + port['network_id'], subnet) + binding = self.nsxlib.dhcp_server.create_binding( + dhcp_service_id, port['mac_address'], ip, hostname, + self._get_conf_attr('dhcp_lease_time'), options, gateway_ip) + LOG.debug("Created static binding (mac: %(mac)s, ip: %(ip)s, " + "gateway: %(gateway)s, options: %(options)s) for port " + "%(port)s on logical DHCP server %(server)s", + {'mac': port['mac_address'], 'ip': ip, + 'gateway': gateway_ip, 'options': options, + 'port': port['id'], + 'server': dhcp_service_id}) + return binding + except nsx_lib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.error("Unable to create static binding (mac: %(mac)s, " + "ip: %(ip)s, gateway: %(gateway)s, options: " + "%(options)s) for port %(port)s on logical DHCP " + "server %(server)s", + {'mac': port['mac_address'], 'ip': ip, + 'gateway': gateway_ip, 'options': options, + 'port': port['id'], + 'server': dhcp_service_id}) + + def _delete_dhcp_binding(self, context, port): + # Do not check device_owner here because Nova may have already + # deleted that before Neutron's port deletion. + bindings = nsx_db.get_nsx_dhcp_bindings(context.session, port['id']) + for binding in bindings: + self._delete_dhcp_binding_on_server(context, binding) + try: + nsx_db.delete_neutron_nsx_dhcp_binding( + context.session, binding['port_id'], + binding['nsx_binding_id']) + except db_exc.DBError: + LOG.error("Unable to delete mapping of DHCP binding " + "%(binding)s for port %(port)s", + {'binding': binding['nsx_binding_id'], + 'port': binding['port_id']}) + + def _delete_dhcp_binding_on_server(self, context, binding): + try: + self.nsxlib.dhcp_server.delete_binding( + binding['nsx_service_id'], binding['nsx_binding_id']) + LOG.debug("Deleted static binding for port %(port)s) on " + "logical DHCP server %(server)s", + {'port': binding['port_id'], + 'server': binding['nsx_service_id']}) + except nsx_lib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.error("Unable to delete static binding for port " + "%(port)s) on logical DHCP server %(server)s", + {'port': binding['port_id'], + 'server': binding['nsx_service_id']}) + + def _find_dhcp_binding(self, subnet_id, ip_address, bindings): + for binding in bindings: + if (subnet_id == binding['subnet_id'] and + ip_address == binding['ip_address']): + return binding + + def _update_dhcp_binding(self, context, old_port, new_port): + # First check if any IPv4 address in fixed_ips is changed. + # Then update DHCP server setting or DHCP static binding + # depending on the port type. + # Note that Neutron allows a port with multiple IPs in the + # same subnet. But backend DHCP server may not support that. + if (utils.is_port_dhcp_configurable(old_port) != + utils.is_port_dhcp_configurable(new_port)): + # Note that the device_owner could be changed, + # but still needs DHCP binding. + if utils.is_port_dhcp_configurable(old_port): + self._delete_dhcp_binding(context, old_port) + else: + self._add_dhcp_binding(context, new_port) + return + + # Collect IPv4 DHCP addresses from original and updated fixed_ips + # in the form of [(subnet_id, ip_address)]. + old_fixed_ips = set([(fixed_ip['subnet_id'], fixed_ip['ip_address']) + for fixed_ip in self._filter_ipv4_dhcp_fixed_ips( + context, old_port['fixed_ips'])]) + new_fixed_ips = set([(fixed_ip['subnet_id'], fixed_ip['ip_address']) + for fixed_ip in self._filter_ipv4_dhcp_fixed_ips( + context, new_port['fixed_ips'])]) + # Find out the subnet/IP differences before and after the update. + ips_to_add = list(new_fixed_ips - old_fixed_ips) + ips_to_delete = list(old_fixed_ips - new_fixed_ips) + ip_change = (ips_to_add or ips_to_delete) + + if (old_port["device_owner"] == constants.DEVICE_OWNER_DHCP and + ip_change): + # Update backend DHCP server address if the IP address of a DHCP + # port is changed. + if len(new_fixed_ips) != 1: + msg = _("Can only configure one IP address on a DHCP server") + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) + # Locate the backend DHCP server for this DHCP port. + dhcp_service = nsx_db.get_nsx_service_binding( + context.session, old_port['network_id'], + nsxlib_consts.SERVICE_DHCP) + if dhcp_service: + new_ip = ips_to_add[0][1] + try: + self.nsxlib.dhcp_server.update( + dhcp_service['nsx_service_id'], + server_ip=new_ip) + LOG.debug("Updated IP %(ip)s for logical DHCP server " + "%(server)s", + {'ip': new_ip, + 'server': dhcp_service['nsx_service_id']}) + except nsx_lib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.error("Unable to update IP %(ip)s for logical " + "DHCP server %(server)s", + {'ip': new_ip, + 'server': dhcp_service['nsx_service_id']}) + elif utils.is_port_dhcp_configurable(old_port): + # Update static DHCP bindings for a compute port. + bindings = nsx_db.get_nsx_dhcp_bindings(context.session, + old_port['id']) + dhcp_opts = new_port.get(ext_edo.EXTRADHCPOPTS) + dhcp_opts_changed = (old_port[ext_edo.EXTRADHCPOPTS] != + new_port[ext_edo.EXTRADHCPOPTS]) + if ip_change: + # If IP address is changed, update associated DHCP bindings, + # metadata route, and default hostname. + # Mac address (if changed) will be updated at the same time. + if ([subnet_id for (subnet_id, ip) in ips_to_add] == + [subnet_id for (subnet_id, ip) in ips_to_delete]): + # No change on subnet_id, just update corresponding IPs. + for i, (subnet_id, ip) in enumerate(ips_to_delete): + binding = self._find_dhcp_binding(subnet_id, ip, + bindings) + if binding: + subnet = self.get_subnet(context, + binding['subnet_id']) + self._update_dhcp_binding_on_server( + context, binding, new_port['mac_address'], + ips_to_add[i][1], old_port['network_id'], + dhcp_opts=dhcp_opts, subnet=subnet) + # Update DB IP + nsx_db.update_nsx_dhcp_bindings(context.session, + old_port['id'], + ip, + ips_to_add[i][1]) + else: + for (subnet_id, ip) in ips_to_delete: + binding = self._find_dhcp_binding(subnet_id, ip, + bindings) + if binding: + self._delete_dhcp_binding_on_server(context, + binding) + if ips_to_add: + dhcp_service = nsx_db.get_nsx_service_binding( + context.session, new_port['network_id'], + nsxlib_consts.SERVICE_DHCP) + if dhcp_service: + for (subnet_id, ip) in ips_to_add: + self._add_dhcp_binding_on_server( + context, dhcp_service['nsx_service_id'], + subnet_id, ip, new_port) + elif (old_port['mac_address'] != new_port['mac_address'] or + dhcp_opts_changed): + # If only Mac address/dhcp opts is changed, + # update it in all associated DHCP bindings. + for binding in bindings: + subnet = self.get_subnet(context, binding['subnet_id']) + self._update_dhcp_binding_on_server( + context, binding, new_port['mac_address'], + binding['ip_address'], old_port['network_id'], + dhcp_opts=dhcp_opts, subnet=subnet) + def _cleanup_port(self, context, port_id, nsx_port_id=None): # Clean up neutron port and nsx manager port if provided # Does not handle cleanup of policy port @@ -1481,6 +1707,25 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # Validate against the configured AZs return self.validate_obj_azs(availability_zones) + def _ensure_nsxlib(self, feature): + if not self.nsxlib: + msg = (_("%s is not supported since passthough API is disabled") % + feature) + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) + + def _ensure_native_dhcp(self): + self._ensure_nsxlib("Native DHCP") + if not self._native_dhcp_enabled: + msg = (_("Native DHCP is not supported since dhcp_profile is not" + " provided in plugin configuration")) + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) + + def _get_net_dhcp_relay(self, context, net_id): + """Should be implemented by each plugin""" + pass + def _create_subnet(self, context, subnet): self._validate_host_routes_input(subnet) @@ -1489,6 +1734,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, if (native_metadata and subnet['subnet'].get('enable_dhcp', False)): self._validate_external_subnet(context, subnet['subnet']['network_id']) + self._ensure_native_dhcp() lock = 'nsxv3_network_' + subnet['subnet']['network_id'] ddi_support, ddi_type = self._is_ddi_supported_on_net_with_type( context, subnet['subnet']['network_id']) @@ -1515,9 +1761,8 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, context, created_subnet['id']) self._extension_manager.process_create_subnet(context, subnet['subnet'], created_subnet) - dhcp_relay = self.get_network_az_by_net_id( - context, - subnet['subnet']['network_id']).dhcp_relay_service + dhcp_relay = self._get_net_dhcp_relay( + context, subnet['subnet']['network_id']) if not dhcp_relay: if self.nsxlib: try: @@ -1531,6 +1776,8 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, else: msg = (_("Native DHCP is not supported since " "passthough API is disabled")) + self._enable_native_dhcp(context, network, + created_subnet) msg = None else: msg = (_("Can not create more than one DHCP-enabled " @@ -1698,6 +1945,266 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return super(NsxPluginV3Base, self).delete_subnet(context, subnet_id) + def _update_subnet(self, context, subnet_id, subnet): + updated_subnet = None + orig_subnet = self.get_subnet(context, subnet_id) + self._validate_host_routes_input( + subnet, + orig_enable_dhcp=orig_subnet['enable_dhcp'], + orig_host_routes=orig_subnet['host_routes']) + if self._has_native_dhcp_metadata(): + enable_dhcp = subnet['subnet'].get('enable_dhcp') + if (enable_dhcp is not None and + enable_dhcp != orig_subnet['enable_dhcp']): + self._ensure_native_dhcp() + lock = 'nsxv3_network_' + orig_subnet['network_id'] + with locking.LockManager.get_lock(lock): + network = self._get_network( + context, orig_subnet['network_id']) + if enable_dhcp: + (ddi_support, + ddi_type) = self._is_ddi_supported_on_net_with_type( + context, orig_subnet['network_id']) + if ddi_support: + if self._has_no_dhcp_enabled_subnet( + context, network): + updated_subnet = super( + NsxPluginV3Base, self).update_subnet( + context, subnet_id, subnet) + self._extension_manager.process_update_subnet( + context, subnet['subnet'], updated_subnet) + self._enable_native_dhcp(context, network, + updated_subnet) + msg = None + else: + msg = (_("Multiple DHCP-enabled subnets is " + "not allowed in network %s") % + orig_subnet['network_id']) + else: + msg = (_("Native DHCP is not supported for " + "%(type)s network %(id)s") % + {'id': orig_subnet['network_id'], + 'type': ddi_type}) + if msg: + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) + elif self._has_single_dhcp_enabled_subnet(context, + network): + self._disable_native_dhcp(context, network['id']) + updated_subnet = super( + NsxPluginV3Base, self).update_subnet( + context, subnet_id, subnet) + self._extension_manager.process_update_subnet( + context, subnet['subnet'], updated_subnet) + + if not updated_subnet: + updated_subnet = super(NsxPluginV3Base, self).update_subnet( + context, subnet_id, subnet) + self._extension_manager.process_update_subnet( + context, subnet['subnet'], updated_subnet) + + # Check if needs to update logical DHCP server for native DHCP. + if (self._has_native_dhcp_metadata() and + updated_subnet['enable_dhcp']): + self._ensure_native_dhcp() + kwargs = {} + for key in ('dns_nameservers', 'gateway_ip', 'host_routes'): + if key in subnet['subnet']: + value = subnet['subnet'][key] + if value != orig_subnet[key]: + kwargs[key] = value + if key != 'dns_nameservers': + kwargs['options'] = None + if 'options' in kwargs: + sr, gw_ip = self.nsxlib.native_dhcp.build_static_routes( + updated_subnet.get('gateway_ip'), + updated_subnet.get('cidr'), + updated_subnet.get('host_routes', [])) + kwargs['options'] = {'option121': {'static_routes': sr}} + kwargs.pop('host_routes', None) + if (gw_ip is not None and 'gateway_ip' not in kwargs and + gw_ip != updated_subnet['gateway_ip']): + kwargs['gateway_ip'] = gw_ip + if kwargs: + dhcp_service = nsx_db.get_nsx_service_binding( + context.session, orig_subnet['network_id'], + nsxlib_consts.SERVICE_DHCP) + if dhcp_service: + try: + self.nsxlib.dhcp_server.update( + dhcp_service['nsx_service_id'], **kwargs) + except nsx_lib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.error( + "Unable to update logical DHCP server " + "%(server)s for network %(network)s", + {'server': dhcp_service['nsx_service_id'], + 'network': orig_subnet['network_id']}) + if 'options' in kwargs: + # Need to update the static binding of every VM in + # this logical DHCP server. + bindings = nsx_db.get_nsx_dhcp_bindings_by_service( + context.session, dhcp_service['nsx_service_id']) + for binding in bindings: + port = self._get_port(context, binding['port_id']) + dhcp_opts = port.get(ext_edo.EXTRADHCPOPTS) + self._update_dhcp_binding_on_server( + context, binding, port['mac_address'], + binding['ip_address'], + port['network_id'], + gateway_ip=kwargs.get('gateway_ip', False), + dhcp_opts=dhcp_opts, + options=kwargs.get('options'), + subnet=updated_subnet) + + return updated_subnet + + def _has_active_port(self, context, network_id): + ports_in_use = context.session.query(models_v2.Port).filter_by( + network_id=network_id).all() + return not all([p.device_owner in + db_base_plugin_v2.AUTO_DELETE_PORT_OWNERS + for p in ports_in_use]) if ports_in_use else False + + def _delete_network_disable_dhcp(self, context, network_id): + # Disable native DHCP and delete DHCP ports before network deletion + lock = 'nsxv3_network_' + network_id + with locking.LockManager.get_lock(lock): + # Disable native DHCP if there is no other existing port + # besides DHCP port. + if not self._has_active_port(context, network_id): + self._disable_native_dhcp(context, network_id) + + def _retry_delete_network(self, context, network_id): + """This method attempts to retry the delete on a network if there are + AUTO_DELETE_PORT_OWNERS left. This is to avoid a race condition + between delete_network and the dhcp creating a port on the network. + """ + first_try = True + while True: + try: + with db_api.CONTEXT_WRITER.using(context): + self._process_l3_delete(context, network_id) + return super(NsxPluginV3Base, self).delete_network( + context, network_id) + except n_exc.NetworkInUse: + # There is a race condition in delete_network() that we need + # to work around here. delete_network() issues a query to + # automatically delete DHCP ports and then checks to see if any + # ports exist on the network. If a network is created and + # deleted quickly, such as when running tempest, the DHCP agent + # may be creating its port for the network around the same time + # that the network is deleted. This can result in the DHCP + # port getting created in between these two queries in + # delete_network(). To work around that, we'll call + # delete_network() a second time if we get a NetworkInUse + # exception but the only port(s) that exist are ones that + # delete_network() is supposed to automatically delete. + if not first_try: + # We tried once to work around the known race condition, + # but we still got the exception, so something else is + # wrong that we can't recover from. + raise + first_try = False + if self._has_active_port(context, network_id): + # There is a port on the network that is not going to be + # automatically deleted (such as a tenant created port), so + # we have nothing else to do but raise the exception. + raise + + def _get_dhcp_options(self, context, ip, extra_dhcp_opts, net_id, + subnet): + # Always add option121. + net_az = self.get_network_az_by_net_id(context, net_id) + options = {'option121': {'static_routes': [ + {'network': '%s' % net_az.native_metadata_route, + 'next_hop': '0.0.0.0'}, + {'network': '%s' % net_az.native_metadata_route, + 'next_hop': ip}]}} + if subnet: + sr, gateway_ip = self.nsxlib.native_dhcp.build_static_routes( + subnet.get('gateway_ip'), subnet.get('cidr'), + subnet.get('host_routes', [])) + options['option121']['static_routes'].extend(sr) + # Adding extra options only if configured on port + if extra_dhcp_opts: + other_opts = [] + for opt in extra_dhcp_opts: + opt_name = opt['opt_name'] + if opt['opt_value'] is not None: + # None value means - delete this option. Since we rebuild + # the options from scratch, it can be ignored. + opt_val = opt['opt_value'] + if opt_name == 'classless-static-route': + # Add to the option121 static routes + net, ip = opt_val.split(',') + options['option121']['static_routes'].append({ + 'network': net, 'next_hop': ip}) + else: + other_opts.append({ + 'code': nsxlib_utils.get_dhcp_opt_code(opt_name), + 'values': [opt_val]}) + if other_opts: + options['others'] = other_opts + return options + + def _update_dhcp_binding_on_server(self, context, binding, mac, ip, + net_id, gateway_ip=False, + dhcp_opts=None, options=None, + subnet=None): + try: + data = {'mac_address': mac, 'ip_address': ip} + if ip != binding['ip_address']: + data['host_name'] = 'host-%s' % ip.replace('.', '-') + data['options'] = self._get_dhcp_options( + context, ip, dhcp_opts, net_id, + subnet) + elif (dhcp_opts is not None or + options is not None): + data['options'] = self._get_dhcp_options( + context, ip, dhcp_opts, net_id, + subnet) + if gateway_ip is not False: + # Note that None is valid for gateway_ip, means deleting it. + data['gateway_ip'] = gateway_ip + + self.nsxlib.dhcp_server.update_binding( + binding['nsx_service_id'], binding['nsx_binding_id'], **data) + LOG.debug("Updated static binding (mac: %(mac)s, ip: %(ip)s, " + "gateway: %(gateway)s) for port %(port)s on " + "logical DHCP server %(server)s", + {'mac': mac, 'ip': ip, 'gateway': gateway_ip, + 'port': binding['port_id'], + 'server': binding['nsx_service_id']}) + except nsx_lib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.error("Unable to update static binding (mac: %(mac)s, " + "ip: %(ip)s, gateway: %(gateway)s) for port " + "%(port)s on logical DHCP server %(server)s", + {'mac': mac, 'ip': ip, 'gateway': gateway_ip, + 'port': binding['port_id'], + 'server': binding['nsx_service_id']}) + + def _validate_extra_dhcp_options(self, opts): + if not opts or not self._has_native_dhcp_metadata(): + return + for opt in opts: + opt_name = opt['opt_name'] + opt_val = opt['opt_value'] + if opt_name == 'classless-static-route': + # separate validation for option121 + if opt_val is not None: + try: + net, ip = opt_val.split(',') + except Exception: + msg = (_("Bad value %(val)s for DHCP option " + "%(name)s") % {'name': opt_name, + 'val': opt_val}) + raise n_exc.InvalidInput(error_message=msg) + elif not nsxlib_utils.get_dhcp_opt_code(opt_name): + msg = (_("DHCP option %s is not supported") % opt_name) + raise n_exc.InvalidInput(error_message=msg) + def _is_vlan_router_interface_supported(self): """Should be implemented by each plugin""" diff --git a/vmware_nsx/plugins/nsx_p/availability_zones.py b/vmware_nsx/plugins/nsx_p/availability_zones.py index 5948651813..ba11f5724a 100644 --- a/vmware_nsx/plugins/nsx_p/availability_zones.py +++ b/vmware_nsx/plugins/nsx_p/availability_zones.py @@ -129,8 +129,6 @@ class NsxPAvailabilityZone(v3_az.NsxV3AvailabilityZone): auto_config=True, is_mandatory=True, search_scope=search_scope) - self.dhcp_relay_service = cfg.CONF.nsx_p.dhcp_relay_service - # If passthrough api is supported, also initialize those NSX objects if nsxlib: self._translate_dhcp_profile(nsxlib, search_scope=search_scope) diff --git a/vmware_nsx/plugins/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index f7b64f921b..f691fb7063 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -30,6 +30,7 @@ from neutron.extensions import securitygroup as ext_sg from neutron.quota import resource_registry from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef from neutron_lib.api.definitions import external_net +from neutron_lib.api.definitions import extra_dhcp_opt as ext_edo from neutron_lib.api.definitions import l3 as l3_apidef from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import vlantransparent as vlan_apidef @@ -483,12 +484,15 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): return created_net def delete_network(self, context, network_id): + if cfg.CONF.nsx_p.allow_passthrough: + self._delete_network_disable_dhcp(context, network_id) + is_nsx_net = self._network_is_nsx_net(context, network_id) is_external_net = self._network_is_external(context, network_id) - with db_api.CONTEXT_WRITER.using(context): - self._process_l3_delete(context, network_id) - super(NsxPolicyPlugin, self).delete_network( - context, network_id) + + # First call DB operation for delete network as it will perform + # checks on active ports + self._retry_delete_network(context, network_id) # MD Proxy is currently supported by the passthrough api only. # Use it to delete mdproxy ports @@ -568,18 +572,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): super(NsxPolicyPlugin, self).delete_subnet(context, subnet_id) def update_subnet(self, context, subnet_id, subnet): - updated_subnet = None - orig = self._get_subnet(context, subnet_id) - self._validate_host_routes_input(subnet, - orig_enable_dhcp=orig['enable_dhcp'], - orig_host_routes=orig['routes']) - # TODO(asarfaty): Handle dhcp updates on the policy manager - updated_subnet = super(NsxPolicyPlugin, self).update_subnet( - context, subnet_id, subnet) - self._extension_manager.process_update_subnet( - context, subnet['subnet'], updated_subnet) - - return updated_subnet + return self._update_subnet(context, subnet_id, subnet) def _build_port_address_bindings(self, context, port_data): psec_on, has_ip = self._determine_port_security_and_has_ip(context, @@ -743,7 +736,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): self._process_portbindings_create_and_update( context, port['port'], port_data, vif_type=self._vif_type_by_vnic_type(direct_vnic_type)) - + self._process_port_create_extra_dhcp_opts( + context, port_data, + port_data.get(ext_edo.EXTRADHCPOPTS)) self._process_port_create_security_group(context, port_data, sgids) self._process_port_create_provider_security_group( context, port_data, psgids) @@ -790,6 +785,18 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): self._extend_nsx_port_dict_binding(context, port_data) self._remove_provider_security_groups_from_list(port_data) + # Add Mac/IP binding to native DHCP server and neutron DB. + if cfg.CONF.nsx_p.allow_passthrough: + try: + self._add_dhcp_binding(context, port_data) + except nsx_lib_exc.ManagerError: + # Rollback create port + self.delete_port(context, port_data['id'], + force_delete_dhcp=True) + msg = _('Unable to create port. Please contact admin') + LOG.exception(msg) + raise nsx_exc.NsxPluginException(err_msg=msg) + kwargs = {'context': context, 'port': neutron_db} registry.notify(resources.PORT, events.AFTER_CREATE, self, **kwargs) return port_data @@ -805,8 +812,13 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): # a l3 router. If so, we should prevent deletion here if l3_port_check: self.prevent_l3_port_deletion(context, port_id) - self.disassociate_floatingips(context, port_id) - super(NsxPolicyPlugin, self).delete_port(context, port_id) + port = self.get_port(context, port_id) + # Prevent DHCP port deletion if native support is enabled + if (cfg.CONF.nsx_p.allow_passthrough and + not force_delete_dhcp and + port['device_owner'] in [const.DEVICE_OWNER_DHCP]): + msg = (_('Can not delete DHCP port %s') % port_id) + raise n_exc.BadRequest(resource='port', msg=msg) if not self._network_is_external(context, net_id): try: @@ -824,6 +836,14 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): "due to %(e)s", {'id': port_id, 'e': ex}) # Do not fail the neutron action + self.disassociate_floatingips(context, port_id) + + # Remove Mac/IP binding from native DHCP server and neutron DB. + if cfg.CONF.nsx_p.allow_passthrough: + self._delete_dhcp_binding(context, port) + + super(NsxPolicyPlugin, self).delete_port(context, port_id) + def _update_port_on_backend(self, context, lport_id, original_port, updated_port, is_psec_on, qos_policy_id): @@ -871,6 +891,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): validate_port_sec=validate_port_sec, direct_vnic_type=direct_vnic_type) + self._update_extra_dhcp_opts_on_port(context, port_id, port, + updated_port) + sec_grp_updated = self.update_security_group_on_port( context, port_id, port, original_port, updated_port) @@ -919,6 +942,10 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): context, port_id, original_port, updated_port, port_security, sec_grp_updated) + # Update DHCP bindings. + if cfg.CONF.nsx_p.allow_passthrough: + self._update_dhcp_binding(context, original_port, updated_port) + # Make sure the port revision is updated if 'revision_number' in updated_port: port_model = self._get_port(context, port_id) @@ -2004,3 +2031,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): 'tz': tz_uuid, 'net': sub['network_id']}) raise n_exc.InvalidInput(error_message=msg) + + def _get_net_dhcp_relay(self, context, net_id): + # No dhcp relay support yet + return None diff --git a/vmware_nsx/plugins/nsx_v3/availability_zones.py b/vmware_nsx/plugins/nsx_v3/availability_zones.py index 4bbf54949e..98b04ec91c 100644 --- a/vmware_nsx/plugins/nsx_v3/availability_zones.py +++ b/vmware_nsx/plugins/nsx_v3/availability_zones.py @@ -45,6 +45,10 @@ class NsxV3AvailabilityZone(v3_az.NsxV3AvailabilityZone): if edge_cluster: self.edge_cluster = edge_cluster + dhcp_relay_service = az_info.get('dhcp_relay_service') + if dhcp_relay_service: + self.dhcp_relay_service = dhcp_relay_service + def init_defaults(self): # use the default configuration self.metadata_proxy = cfg.CONF.nsx_v3.metadata_proxy diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index e9f27cb67c..19a231fd80 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -36,7 +36,6 @@ from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.rpc.handlers import dhcp_rpc from neutron.api.rpc.handlers import metadata_rpc from neutron.db import agents_db -from neutron.db import db_base_plugin_v2 from neutron.db import l3_db from neutron.db.models import l3 as l3_db_models from neutron.db.models import securitygroup as securitygroup_model # noqa @@ -61,7 +60,6 @@ from oslo_log import log from oslo_utils import excutils from oslo_utils import importutils from oslo_utils import uuidutils -from sqlalchemy import exc as sql_exc from vmware_nsx._i18n import _ from vmware_nsx.api_replay import utils as api_replay_utils @@ -772,7 +770,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, def _setup_dhcp(self): """Initialize components to support DHCP.""" - #TODO(asarfaty): move to common code and use in policy plugin too self.network_scheduler = importutils.import_object( cfg.CONF.network_scheduler_driver ) @@ -1041,58 +1038,9 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, # Update the default port security to False if not set net_data[psec.PORTSECURITY] = False - def _has_active_port(self, context, network_id): - ports_in_use = context.session.query(models_v2.Port).filter_by( - network_id=network_id).all() - return not all([p.device_owner in - db_base_plugin_v2.AUTO_DELETE_PORT_OWNERS - for p in ports_in_use]) if ports_in_use else False - - def _retry_delete_network(self, context, network_id): - """This method attempts to retry the delete on a network if there are - AUTO_DELETE_PORT_OWNERS left. This is to avoid a race condition - between delete_network and the dhcp creating a port on the network. - """ - first_try = True - while True: - try: - with db_api.CONTEXT_WRITER.using(context): - self._process_l3_delete(context, network_id) - return super(NsxV3Plugin, self).delete_network( - context, network_id) - except n_exc.NetworkInUse: - # There is a race condition in delete_network() that we need - # to work around here. delete_network() issues a query to - # automatically delete DHCP ports and then checks to see if any - # ports exist on the network. If a network is created and - # deleted quickly, such as when running tempest, the DHCP agent - # may be creating its port for the network around the same time - # that the network is deleted. This can result in the DHCP - # port getting created in between these two queries in - # delete_network(). To work around that, we'll call - # delete_network() a second time if we get a NetworkInUse - # exception but the only port(s) that exist are ones that - # delete_network() is supposed to automatically delete. - if not first_try: - # We tried once to work around the known race condition, - # but we still got the exception, so something else is - # wrong that we can't recover from. - raise - first_try = False - if self._has_active_port(context, network_id): - # There is a port on the network that is not going to be - # automatically deleted (such as a tenant created port), so - # we have nothing else to do but raise the exception. - raise - def delete_network(self, context, network_id): if cfg.CONF.nsx_v3.native_dhcp_metadata: - lock = 'nsxv3_network_' + network_id - with locking.LockManager.get_lock(lock): - # Disable native DHCP if there is no other existing port - # besides DHCP port. - if not self._has_active_port(context, network_id): - self._disable_native_dhcp(context, network_id) + self._delete_network_disable_dhcp(context, network_id) nsx_net_id = self._get_network_nsx_id(context, network_id) is_nsx_net = self._network_is_nsx_net(context, network_id) @@ -1281,187 +1229,18 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, "the NSX: %(e)s", {'id': network['id'], 'e': e}) def create_subnet(self, context, subnet): - self._validate_host_routes_input(subnet) - # TODO(berlin): public external subnet announcement - if (cfg.CONF.nsx_v3.native_dhcp_metadata and - subnet['subnet'].get('enable_dhcp', False)): - self._validate_external_subnet(context, - subnet['subnet']['network_id']) - lock = 'nsxv3_network_' + subnet['subnet']['network_id'] - ddi_support, ddi_type = self._is_ddi_supported_on_net_with_type( - context, subnet['subnet']['network_id']) - with locking.LockManager.get_lock(lock): - # Check if it is on an overlay network and is the first - # DHCP-enabled subnet to create. - if ddi_support: - network = self._get_network( - context, subnet['subnet']['network_id']) - if self._has_no_dhcp_enabled_subnet(context, network): - created_subnet = super( - NsxV3Plugin, self).create_subnet(context, subnet) - try: - # This can be called only after the super create - # since we need the subnet pool to be translated - # to allocation pools - self._validate_address_space( - context, created_subnet) - except n_exc.InvalidInput: - # revert the subnet creation - with excutils.save_and_reraise_exception(): - super(NsxV3Plugin, self).delete_subnet( - context, created_subnet['id']) - self._extension_manager.process_create_subnet(context, - subnet['subnet'], created_subnet) - dhcp_relay = self.get_network_az_by_net_id( - context, - subnet['subnet']['network_id']).dhcp_relay_service - if not dhcp_relay: - self._enable_native_dhcp(context, network, - created_subnet) - msg = None - else: - msg = (_("Can not create more than one DHCP-enabled " - "subnet in network %s") % - subnet['subnet']['network_id']) - else: - msg = _("Native DHCP is not supported for %(type)s " - "network %(id)s") % { - 'id': subnet['subnet']['network_id'], - 'type': ddi_type} - if msg: - LOG.error(msg) - raise n_exc.InvalidInput(error_message=msg) - else: - created_subnet = super(NsxV3Plugin, self).create_subnet( - context, subnet) - try: - # This can be called only after the super create - # since we need the subnet pool to be translated - # to allocation pools - self._validate_address_space(context, created_subnet) - except n_exc.InvalidInput: - # revert the subnet creation - with excutils.save_and_reraise_exception(): - super(NsxV3Plugin, self).delete_subnet( - context, created_subnet['id']) - return created_subnet + return self._create_subnet(context, subnet) def delete_subnet(self, context, subnet_id): # Call common V3 code to delete the subnet super(NsxV3Plugin, self).delete_subnet(context, subnet_id) def update_subnet(self, context, subnet_id, subnet): - updated_subnet = None - orig_subnet = self.get_subnet(context, subnet_id) - self._validate_host_routes_input( - subnet, - orig_enable_dhcp=orig_subnet['enable_dhcp'], - orig_host_routes=orig_subnet['host_routes']) - if cfg.CONF.nsx_v3.native_dhcp_metadata: - enable_dhcp = subnet['subnet'].get('enable_dhcp') - if (enable_dhcp is not None and - enable_dhcp != orig_subnet['enable_dhcp']): - lock = 'nsxv3_network_' + orig_subnet['network_id'] - with locking.LockManager.get_lock(lock): - network = self._get_network( - context, orig_subnet['network_id']) - if enable_dhcp: - (ddi_support, - ddi_type) = self._is_ddi_supported_on_net_with_type( - context, orig_subnet['network_id']) - if ddi_support: - if self._has_no_dhcp_enabled_subnet( - context, network): - updated_subnet = super( - NsxV3Plugin, self).update_subnet( - context, subnet_id, subnet) - self._extension_manager.process_update_subnet( - context, subnet['subnet'], updated_subnet) - self._enable_native_dhcp(context, network, - updated_subnet) - msg = None - else: - msg = (_("Multiple DHCP-enabled subnets is " - "not allowed in network %s") % - orig_subnet['network_id']) - else: - msg = (_("Native DHCP is not supported for " - "%(type)s network %(id)s") % - {'id': orig_subnet['network_id'], - 'type': ddi_type}) - if msg: - LOG.error(msg) - raise n_exc.InvalidInput(error_message=msg) - elif self._has_single_dhcp_enabled_subnet(context, - network): - self._disable_native_dhcp(context, network['id']) - updated_subnet = super( - NsxV3Plugin, self).update_subnet( - context, subnet_id, subnet) - self._extension_manager.process_update_subnet( - context, subnet['subnet'], updated_subnet) - - if not updated_subnet: - updated_subnet = super(NsxV3Plugin, self).update_subnet( - context, subnet_id, subnet) - self._extension_manager.process_update_subnet( - context, subnet['subnet'], updated_subnet) - - # Check if needs to update logical DHCP server for native DHCP. - if (cfg.CONF.nsx_v3.native_dhcp_metadata and - updated_subnet['enable_dhcp']): - kwargs = {} - for key in ('dns_nameservers', 'gateway_ip', 'host_routes'): - if key in subnet['subnet']: - value = subnet['subnet'][key] - if value != orig_subnet[key]: - kwargs[key] = value - if key != 'dns_nameservers': - kwargs['options'] = None - if 'options' in kwargs: - sr, gw_ip = self.nsxlib.native_dhcp.build_static_routes( - updated_subnet.get('gateway_ip'), - updated_subnet.get('cidr'), - updated_subnet.get('host_routes', [])) - kwargs['options'] = {'option121': {'static_routes': sr}} - kwargs.pop('host_routes', None) - if (gw_ip is not None and 'gateway_ip' not in kwargs and - gw_ip != updated_subnet['gateway_ip']): - kwargs['gateway_ip'] = gw_ip - if kwargs: - dhcp_service = nsx_db.get_nsx_service_binding( - context.session, orig_subnet['network_id'], - nsxlib_consts.SERVICE_DHCP) - if dhcp_service: - try: - self.nsxlib.dhcp_server.update( - dhcp_service['nsx_service_id'], **kwargs) - except nsx_lib_exc.ManagerError: - with excutils.save_and_reraise_exception(): - LOG.error( - "Unable to update logical DHCP server " - "%(server)s for network %(network)s", - {'server': dhcp_service['nsx_service_id'], - 'network': orig_subnet['network_id']}) - if 'options' in kwargs: - # Need to update the static binding of every VM in - # this logical DHCP server. - bindings = nsx_db.get_nsx_dhcp_bindings_by_service( - context.session, dhcp_service['nsx_service_id']) - for binding in bindings: - port = self._get_port(context, binding['port_id']) - dhcp_opts = port.get(ext_edo.EXTRADHCPOPTS) - self._update_dhcp_binding_on_server( - context, binding, port['mac_address'], - binding['ip_address'], - port['network_id'], - gateway_ip=kwargs.get('gateway_ip', False), - dhcp_opts=dhcp_opts, - options=kwargs.get('options'), - subnet=updated_subnet) - + updated_subnet = self._update_subnet(context, + subnet_id, + subnet) if (cfg.CONF.nsx_v3.metadata_on_demand and - not cfg.CONF.nsx_v3.native_dhcp_metadata): + not self._has_native_dhcp_metadata()): # If enable_dhcp is changed on a subnet attached to a router, # update internal metadata network accordingly. if 'enable_dhcp' in subnet['subnet']: @@ -1716,323 +1495,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, LOG.warning(err_msg) raise n_exc.InvalidInput(error_message=err_msg) - def _filter_ipv4_dhcp_fixed_ips(self, context, fixed_ips): - ips = [] - for fixed_ip in fixed_ips: - if netaddr.IPNetwork(fixed_ip['ip_address']).version != 4: - continue - with db_api.CONTEXT_READER.using(context): - subnet = self.get_subnet(context, fixed_ip['subnet_id']) - if subnet['enable_dhcp']: - ips.append(fixed_ip) - return ips - - def _add_dhcp_binding(self, context, port): - #TODO(asarfaty): move to common code and use in policy plugin too - if not utils.is_port_dhcp_configurable(port): - return - dhcp_service = nsx_db.get_nsx_service_binding( - context.session, port['network_id'], nsxlib_consts.SERVICE_DHCP) - if not dhcp_service: - return - for fixed_ip in self._filter_ipv4_dhcp_fixed_ips( - context, port['fixed_ips']): - binding = self._add_dhcp_binding_on_server( - context, dhcp_service['nsx_service_id'], fixed_ip['subnet_id'], - fixed_ip['ip_address'], port) - try: - nsx_db.add_neutron_nsx_dhcp_binding( - context.session, port['id'], fixed_ip['subnet_id'], - fixed_ip['ip_address'], dhcp_service['nsx_service_id'], - binding['id']) - except (db_exc.DBError, sql_exc.TimeoutError): - LOG.error("Failed to add mapping of DHCP binding " - "%(binding)s for port %(port)s, deleting " - "DHCP binding on server", - {'binding': binding['id'], 'port': port['id']}) - fake_db_binding = { - 'port_id': port['id'], - 'nsx_service_id': dhcp_service['nsx_service_id'], - 'nsx_binding_id': binding['id']} - self._delete_dhcp_binding_on_server(context, fake_db_binding) - - def _validate_extra_dhcp_options(self, opts): - if not opts or not cfg.CONF.nsx_v3.native_dhcp_metadata: - return - for opt in opts: - opt_name = opt['opt_name'] - opt_val = opt['opt_value'] - if opt_name == 'classless-static-route': - # separate validation for option121 - if opt_val is not None: - try: - net, ip = opt_val.split(',') - except Exception: - msg = (_("Bad value %(val)s for DHCP option " - "%(name)s") % {'name': opt_name, - 'val': opt_val}) - raise n_exc.InvalidInput(error_message=msg) - elif not self.nsxlib.dhcp_server.get_dhcp_opt_code(opt_name): - msg = (_("DHCP option %s is not supported") % opt_name) - raise n_exc.InvalidInput(error_message=msg) - - def _get_dhcp_options(self, context, ip, extra_dhcp_opts, net_id, - subnet): - #TODO(asarfaty): move to common code and use in policy plugin too - # Always add option121. - net_az = self.get_network_az_by_net_id(context, net_id) - options = {'option121': {'static_routes': [ - {'network': '%s' % net_az.native_metadata_route, - 'next_hop': '0.0.0.0'}, - {'network': '%s' % net_az.native_metadata_route, - 'next_hop': ip}]}} - if subnet: - sr, gateway_ip = self.nsxlib.native_dhcp.build_static_routes( - subnet.get('gateway_ip'), subnet.get('cidr'), - subnet.get('host_routes', [])) - options['option121']['static_routes'].extend(sr) - # Adding extra options only if configured on port - if extra_dhcp_opts: - other_opts = [] - for opt in extra_dhcp_opts: - opt_name = opt['opt_name'] - if opt['opt_value'] is not None: - # None value means - delete this option. Since we rebuild - # the options from scratch, it can be ignored. - opt_val = opt['opt_value'] - if opt_name == 'classless-static-route': - # Add to the option121 static routes - net, ip = opt_val.split(',') - options['option121']['static_routes'].append({ - 'network': net, 'next_hop': ip}) - else: - other_opts.append({ - 'code': self.nsxlib.dhcp_server.get_dhcp_opt_code( - opt_name), - 'values': [opt_val]}) - if other_opts: - options['others'] = other_opts - return options - - def _add_dhcp_binding_on_server(self, context, dhcp_service_id, subnet_id, - ip, port): - #TODO(asarfaty): move to common code and use in policy plugin too - try: - hostname = 'host-%s' % ip.replace('.', '-') - subnet = self.get_subnet(context, subnet_id) - gateway_ip = subnet.get('gateway_ip') - options = self._get_dhcp_options( - context, ip, port.get(ext_edo.EXTRADHCPOPTS), - port['network_id'], subnet) - binding = self.nsxlib.dhcp_server.create_binding( - dhcp_service_id, port['mac_address'], ip, hostname, - cfg.CONF.nsx_v3.dhcp_lease_time, options, gateway_ip) - LOG.debug("Created static binding (mac: %(mac)s, ip: %(ip)s, " - "gateway: %(gateway)s, options: %(options)s) for port " - "%(port)s on logical DHCP server %(server)s", - {'mac': port['mac_address'], 'ip': ip, - 'gateway': gateway_ip, 'options': options, - 'port': port['id'], - 'server': dhcp_service_id}) - return binding - except nsx_lib_exc.ManagerError: - with excutils.save_and_reraise_exception(): - LOG.error("Unable to create static binding (mac: %(mac)s, " - "ip: %(ip)s, gateway: %(gateway)s, options: " - "%(options)s) for port %(port)s on logical DHCP " - "server %(server)s", - {'mac': port['mac_address'], 'ip': ip, - 'gateway': gateway_ip, 'options': options, - 'port': port['id'], - 'server': dhcp_service_id}) - - def _delete_dhcp_binding(self, context, port): - # Do not check device_owner here because Nova may have already - # deleted that before Neutron's port deletion. - bindings = nsx_db.get_nsx_dhcp_bindings(context.session, port['id']) - for binding in bindings: - self._delete_dhcp_binding_on_server(context, binding) - try: - nsx_db.delete_neutron_nsx_dhcp_binding( - context.session, binding['port_id'], - binding['nsx_binding_id']) - except db_exc.DBError: - LOG.error("Unable to delete mapping of DHCP binding " - "%(binding)s for port %(port)s", - {'binding': binding['nsx_binding_id'], - 'port': binding['port_id']}) - - def _delete_dhcp_binding_on_server(self, context, binding): - try: - self.nsxlib.dhcp_server.delete_binding( - binding['nsx_service_id'], binding['nsx_binding_id']) - LOG.debug("Deleted static binding for port %(port)s) on " - "logical DHCP server %(server)s", - {'port': binding['port_id'], - 'server': binding['nsx_service_id']}) - except nsx_lib_exc.ManagerError: - with excutils.save_and_reraise_exception(): - LOG.error("Unable to delete static binding for port " - "%(port)s) on logical DHCP server %(server)s", - {'port': binding['port_id'], - 'server': binding['nsx_service_id']}) - - def _find_dhcp_binding(self, subnet_id, ip_address, bindings): - for binding in bindings: - if (subnet_id == binding['subnet_id'] and - ip_address == binding['ip_address']): - return binding - - def _update_dhcp_binding(self, context, old_port, new_port): - # First check if any IPv4 address in fixed_ips is changed. - # Then update DHCP server setting or DHCP static binding - # depending on the port type. - # Note that Neutron allows a port with multiple IPs in the - # same subnet. But backend DHCP server may not support that. - - if (utils.is_port_dhcp_configurable(old_port) != - utils.is_port_dhcp_configurable(new_port)): - # Note that the device_owner could be changed, - # but still needs DHCP binding. - if utils.is_port_dhcp_configurable(old_port): - self._delete_dhcp_binding(context, old_port) - else: - self._add_dhcp_binding(context, new_port) - return - - # Collect IPv4 DHCP addresses from original and updated fixed_ips - # in the form of [(subnet_id, ip_address)]. - old_fixed_ips = set([(fixed_ip['subnet_id'], fixed_ip['ip_address']) - for fixed_ip in self._filter_ipv4_dhcp_fixed_ips( - context, old_port['fixed_ips'])]) - new_fixed_ips = set([(fixed_ip['subnet_id'], fixed_ip['ip_address']) - for fixed_ip in self._filter_ipv4_dhcp_fixed_ips( - context, new_port['fixed_ips'])]) - # Find out the subnet/IP differences before and after the update. - ips_to_add = list(new_fixed_ips - old_fixed_ips) - ips_to_delete = list(old_fixed_ips - new_fixed_ips) - ip_change = (ips_to_add or ips_to_delete) - - if old_port["device_owner"] == const.DEVICE_OWNER_DHCP and ip_change: - # Update backend DHCP server address if the IP address of a DHCP - # port is changed. - if len(new_fixed_ips) != 1: - msg = _("Can only configure one IP address on a DHCP server") - LOG.error(msg) - raise n_exc.InvalidInput(error_message=msg) - # Locate the backend DHCP server for this DHCP port. - dhcp_service = nsx_db.get_nsx_service_binding( - context.session, old_port['network_id'], - nsxlib_consts.SERVICE_DHCP) - if dhcp_service: - new_ip = ips_to_add[0][1] - try: - self.nsxlib.dhcp_server.update( - dhcp_service['nsx_service_id'], - server_ip=new_ip) - LOG.debug("Updated IP %(ip)s for logical DHCP server " - "%(server)s", - {'ip': new_ip, - 'server': dhcp_service['nsx_service_id']}) - except nsx_lib_exc.ManagerError: - with excutils.save_and_reraise_exception(): - LOG.error("Unable to update IP %(ip)s for logical " - "DHCP server %(server)s", - {'ip': new_ip, - 'server': dhcp_service['nsx_service_id']}) - elif utils.is_port_dhcp_configurable(old_port): - # Update static DHCP bindings for a compute port. - bindings = nsx_db.get_nsx_dhcp_bindings(context.session, - old_port['id']) - dhcp_opts = new_port.get(ext_edo.EXTRADHCPOPTS) - dhcp_opts_changed = (old_port[ext_edo.EXTRADHCPOPTS] != - new_port[ext_edo.EXTRADHCPOPTS]) - if ip_change: - # If IP address is changed, update associated DHCP bindings, - # metadata route, and default hostname. - # Mac address (if changed) will be updated at the same time. - if ([subnet_id for (subnet_id, ip) in ips_to_add] == - [subnet_id for (subnet_id, ip) in ips_to_delete]): - # No change on subnet_id, just update corresponding IPs. - for i, (subnet_id, ip) in enumerate(ips_to_delete): - binding = self._find_dhcp_binding(subnet_id, ip, - bindings) - if binding: - subnet = self.get_subnet(context, - binding['subnet_id']) - self._update_dhcp_binding_on_server( - context, binding, new_port['mac_address'], - ips_to_add[i][1], old_port['network_id'], - dhcp_opts=dhcp_opts, subnet=subnet) - # Update DB IP - nsx_db.update_nsx_dhcp_bindings(context.session, - old_port['id'], - ip, - ips_to_add[i][1]) - else: - for (subnet_id, ip) in ips_to_delete: - binding = self._find_dhcp_binding(subnet_id, ip, - bindings) - if binding: - self._delete_dhcp_binding_on_server(context, - binding) - if ips_to_add: - dhcp_service = nsx_db.get_nsx_service_binding( - context.session, new_port['network_id'], - nsxlib_consts.SERVICE_DHCP) - if dhcp_service: - for (subnet_id, ip) in ips_to_add: - self._add_dhcp_binding_on_server( - context, dhcp_service['nsx_service_id'], - subnet_id, ip, new_port) - elif (old_port['mac_address'] != new_port['mac_address'] or - dhcp_opts_changed): - # If only Mac address/dhcp opts is changed, - # update it in all associated DHCP bindings. - for binding in bindings: - subnet = self.get_subnet(context, binding['subnet_id']) - self._update_dhcp_binding_on_server( - context, binding, new_port['mac_address'], - binding['ip_address'], old_port['network_id'], - dhcp_opts=dhcp_opts, subnet=subnet) - - def _update_dhcp_binding_on_server(self, context, binding, mac, ip, - net_id, gateway_ip=False, - dhcp_opts=None, options=None, - subnet=None): - try: - data = {'mac_address': mac, 'ip_address': ip} - if ip != binding['ip_address']: - data['host_name'] = 'host-%s' % ip.replace('.', '-') - data['options'] = self._get_dhcp_options( - context, ip, dhcp_opts, net_id, - subnet) - elif (dhcp_opts is not None or - options is not None): - data['options'] = self._get_dhcp_options( - context, ip, dhcp_opts, net_id, - subnet) - if gateway_ip is not False: - # Note that None is valid for gateway_ip, means deleting it. - data['gateway_ip'] = gateway_ip - - self.nsxlib.dhcp_server.update_binding( - binding['nsx_service_id'], binding['nsx_binding_id'], **data) - LOG.debug("Updated static binding (mac: %(mac)s, ip: %(ip)s, " - "gateway: %(gateway)s) for port %(port)s on " - "logical DHCP server %(server)s", - {'mac': mac, 'ip': ip, 'gateway': gateway_ip, - 'port': binding['port_id'], - 'server': binding['nsx_service_id']}) - except nsx_lib_exc.ManagerError: - with excutils.save_and_reraise_exception(): - LOG.error("Unable to update static binding (mac: %(mac)s, " - "ip: %(ip)s, gateway: %(gateway)s) for port " - "%(port)s on logical DHCP server %(server)s", - {'mac': mac, 'ip': ip, 'gateway': gateway_ip, - 'port': binding['port_id'], - 'server': binding['nsx_service_id']}) - def _update_lport_with_security_groups(self, context, lport_id, original, updated): # translate the neutron sg ids to nsx ids, and call nsxlib @@ -2062,9 +1524,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, # validate the new port parameters self._validate_create_port(context, port_data) - # Add the plugin specific validations - dhcp_opts = port_data.get(ext_edo.EXTRADHCPOPTS) - self._validate_extra_dhcp_options(dhcp_opts) self._assert_on_dhcp_relay_without_router(context, port_data) is_ens_tz_port = self._is_ens_tz_port(context, port_data) if is_ens_tz_port: @@ -2097,7 +1556,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, context, port['port'], port_data, vif_type=self._vif_type_by_vnic_type(direct_vnic_type)) self._process_port_create_extra_dhcp_opts( - context, port_data, dhcp_opts) + context, port_data, + port_data.get(ext_edo.EXTRADHCPOPTS)) # handle adding security groups to port self._process_port_create_security_group( @@ -2461,8 +1921,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, self._assert_on_dhcp_relay_without_router(context, port_data, original_port) is_ens_tz_port = self._is_ens_tz_port(context, original_port) - dhcp_opts = port_data.get(ext_edo.EXTRADHCPOPTS) - self._validate_extra_dhcp_options(dhcp_opts) direct_vnic_type = self._validate_port_vnic_type( context, port_data, original_port['network_id']) @@ -3954,3 +3412,7 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, def _get_neutron_net_ids_by_nsx_id(self, context, lswitch_id): return nsx_db.get_net_ids(context.session, lswitch_id) + + def _get_net_dhcp_relay(self, context, net_id): + return self.get_network_az_by_net_id( + context, net_id).dhcp_relay_service diff --git a/vmware_nsx/tests/unit/nsx_p/test_dhcp_metadata.py b/vmware_nsx/tests/unit/nsx_p/test_dhcp_metadata.py index 621b96c3c1..2c4eabc511 100644 --- a/vmware_nsx/tests/unit/nsx_p/test_dhcp_metadata.py +++ b/vmware_nsx/tests/unit/nsx_p/test_dhcp_metadata.py @@ -114,8 +114,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): def _verify_dhcp_binding(self, subnet, port_data, update_data, assert_data): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Verify if DHCP binding is updated. with mock.patch( 'vmware_nsxlib.v3.resources.LogicalDhcpServer.update_binding' @@ -308,8 +306,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): False) def test_dhcp_service_with_update_dhcp_subnet(self): - # TODO(asarfaty) : Enable this test after update subnet is supported - return # Test if DHCP service is enabled on a network when a DHCP-disabled # subnet is updated to DHCP-enabled. with self.network() as network: @@ -325,8 +321,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): True) def test_dhcp_service_with_update_multiple_dhcp_subnets(self): - # TODO(asarfaty) : Enable this test after update subnet is supported - return # Test if a DHCP-disabled subnet cannot be updated to DHCP-enabled # if a DHCP-enabled subnet already exists in the same network. with self.network() as network: @@ -344,8 +338,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): data) def test_dhcp_service_with_update_dhcp_port(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP server IP is updated when the corresponding DHCP port # IP is changed. with mock.patch.object(nsx_resources.LogicalDhcpServer, @@ -367,8 +359,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): dhcp_service['nsx_service_id'], server_ip=new_ip) def test_dhcp_binding_with_create_port(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is added when a compute port is created. with mock.patch.object(nsx_resources.LogicalDhcpServer, 'create_binding', @@ -403,8 +393,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): subnet['subnet']['gateway_ip']) def test_dhcp_binding_with_create_port_with_opts(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is added when a compute port is created # with extra options. opt_name = 'interface-mtu' @@ -448,8 +436,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): subnet['subnet']['gateway_ip']) def test_dhcp_binding_with_create_port_with_opts121(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is added when a compute port is created # with extra option121. with mock.patch.object(nsx_resources.LogicalDhcpServer, @@ -491,8 +477,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): subnet['subnet']['gateway_ip']) def test_dhcp_binding_with_create_port_with_bad_opts(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return with self.subnet(enable_dhcp=True) as subnet: device_owner = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'None' device_id = uuidutils.generate_uuid() @@ -523,8 +507,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): self.plugin.create_port, ctx, data) def test_dhcp_binding_with_disable_enable_dhcp(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is preserved after DHCP is disabled and # re-enabled on a subnet. with self.subnet(enable_dhcp=True) as subnet: @@ -559,8 +541,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): dhcp_bindings[0]['nsx_service_id']) def test_dhcp_binding_with_delete_port(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is removed when the associated compute port # is deleted. with mock.patch.object(nsx_resources.LogicalDhcpServer, @@ -580,8 +560,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): dhcp_binding['nsx_binding_id']) def test_dhcp_binding_with_update_port_delete_ip(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is deleted when the IP of the associated # compute port is deleted. with mock.patch.object(nsx_resources.LogicalDhcpServer, @@ -604,8 +582,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): dhcp_binding['nsx_binding_id']) def test_dhcp_binding_with_update_port_ip(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is updated when the IP of the associated # compute port is changed. with self.subnet(cidr='10.0.0.0/24', enable_dhcp=True) as subnet: @@ -631,8 +607,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): assert_data) def test_dhcp_binding_with_update_port_mac(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is updated when the Mac of the associated # compute port is changed. with self.subnet(enable_dhcp=True) as subnet: @@ -683,8 +657,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): assert_data) def test_update_port_with_update_dhcp_opt(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test updating extra-dhcp-opts via port update. with self.subnet(cidr='10.0.0.0/24', enable_dhcp=True) as subnet: mac_address = '11:22:33:44:55:66' @@ -717,8 +689,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): assert_data) def test_update_port_with_adding_dhcp_opt(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test adding extra-dhcp-opts via port update. with self.subnet(cidr='10.0.0.0/24', enable_dhcp=True) as subnet: mac_address = '11:22:33:44:55:66' @@ -752,8 +722,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): assert_data) def test_update_port_with_deleting_dhcp_opt(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test adding extra-dhcp-opts via port update. with self.subnet(cidr='10.0.0.0/24', enable_dhcp=True) as subnet: mac_address = '11:22:33:44:55:66' @@ -864,8 +832,6 @@ class NsxNativeDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin): network['network']['tenant_id'], False) def test_dhcp_binding_with_create_az_port(self): - # TODO(asarfaty)- Enabled this once port dhcp binding is supported - return # Test if DHCP binding is added when a compute port is created. with mock.patch.object(nsx_resources.LogicalDhcpServer, 'create_binding', diff --git a/vmware_nsx/tests/unit/nsx_p/test_plugin.py b/vmware_nsx/tests/unit/nsx_p/test_plugin.py index 80e751f6cc..ed185dc565 100644 --- a/vmware_nsx/tests/unit/nsx_p/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_p/test_plugin.py @@ -132,6 +132,11 @@ class NsxPPluginTestCaseMixin( "get_id_by_name_or_id", return_value=NSX_DHCP_PROFILE_ID).start() + mock.patch( + "vmware_nsxlib.v3.resources.LogicalDhcpServer." + "get_id_by_name_or_id", + return_value=_return_same).start() + mock.patch( "vmware_nsxlib.v3.core_resources.NsxLibMetadataProxy." "get_id_by_name_or_id", @@ -145,10 +150,17 @@ class NsxPPluginTestCaseMixin( "vmware_nsxlib.v3.resources.LogicalDhcpServer.create", side_effect=_return_id_key).start() + mock.patch( + "vmware_nsxlib.v3.resources.LogicalDhcpServer.update", + side_effect=_return_id_key).start() + mock.patch( "vmware_nsxlib.v3.resources.LogicalDhcpServer.create_binding", side_effect=_return_id_key).start() + mock.patch("vmware_nsxlib.v3.resources.LogicalDhcpServer." + "update_binding").start() + mock.patch("vmware_nsxlib.v3.NsxLib." "get_id_by_resource_and_tag").start() @@ -602,8 +614,18 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, def test_update_port_add_additional_ip(self): self.skipTest('Multiple fixed ips on a port are not supported') + def test_delete_network_port_exists_owned_by_network_race(self): + self.skipTest('Skip need to address in future') + + def test_delete_network_port_exists_owned_by_network_port_not_found(self): + self.skipTest('Skip need to address in future') + + def test_delete_network_port_exists_owned_by_network(self): + self.skipTest('Skip need to address in future') + @with_disable_dhcp def test_duplicate_mac_generation(self): + self.skipTest('No DHCP v6 Support yet') return super(NsxPTestPorts, self).test_duplicate_mac_generation() @with_disable_dhcp @@ -1028,6 +1050,9 @@ class NsxPTestSubnets(test_db_base_plugin_v2.TestSubnetsV2, super(NsxPTestSubnets, self).\ test_delete_subnet_with_other_subnet_on_network_still_in_use() + def test_delete_subnet_port_exists_owned_by_network(self): + self.skipTest('No support for multiple ips') + def test_create_subnet_dhcpv6_stateless_with_ip_already_allocated(self): self.skipTest('No DHCP v6 Support yet') diff --git a/vmware_nsx/tests/unit/nsx_v3/test_plugin.py b/vmware_nsx/tests/unit/nsx_v3/test_plugin.py index f6d9d2230c..8a9be02aa9 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_plugin.py @@ -287,15 +287,20 @@ class NsxV3PluginTestCaseMixin(test_plugin.NeutronDbPluginV2TestCase, self.plugin.init_availability_zones() self.plugin._translate_configured_names_to_uuids() + def _enable_native_dhcp_md(self): + cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + cfg.CONF.set_override('dhcp_agent_notification', False) + self.plugin._init_dhcp_metadata() + def _enable_dhcp_relay(self): # Add the relay service to the config and availability zones - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') 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._initialize_azs() + self._enable_native_dhcp_md() class TestNetworksV2(test_plugin.TestNetworksV2, NsxV3PluginTestCaseMixin): @@ -322,7 +327,7 @@ class TestNetworksV2(test_plugin.TestNetworksV2, NsxV3PluginTestCaseMixin): self.assertListEqual(az_hints, zone) def test_network_failure_rollback(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() self.plugin = directory.get_plugin() with mock.patch.object(self.plugin.nsxlib.logical_port, 'create', side_effect=api_exc.NsxApiException): @@ -854,7 +859,7 @@ class TestSubnetsV2(test_plugin.TestSubnetsV2, NsxV3PluginTestCaseMixin): self.skipTest('Multiple fixed ips on a port are not supported') def test_subnet_native_dhcp_subnet_enabled(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() with self.network() as network: with mock.patch.object(self.plugin, '_enable_native_dhcp') as enable_dhcp,\ @@ -863,7 +868,7 @@ class TestSubnetsV2(test_plugin.TestSubnetsV2, NsxV3PluginTestCaseMixin): self.assertTrue(enable_dhcp.called) def test_subnet_native_dhcp_subnet_disabled(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() with self.network() as network: with mock.patch.object(self.plugin, '_enable_native_dhcp') as enable_dhcp,\ @@ -882,7 +887,7 @@ class TestSubnetsV2(test_plugin.TestSubnetsV2, NsxV3PluginTestCaseMixin): self.assertFalse(enable_dhcp.called) def test_subnet_native_dhcp_flat_subnet_disabled(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() providernet_args = {pnet.NETWORK_TYPE: 'flat'} with mock.patch('vmware_nsxlib.v3.core_resources.NsxLibTransportZone.' 'get_transport_type', return_value='VLAN'): @@ -902,7 +907,7 @@ class TestSubnetsV2(test_plugin.TestSubnetsV2, NsxV3PluginTestCaseMixin): context.get_admin_context(), data) def test_subnet_native_dhcp_flat_subnet_enabled(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() providernet_args = {pnet.NETWORK_TYPE: 'flat'} with mock.patch('vmware_nsxlib.v3.core_resources.NsxLibTransportZone.' 'get_transport_type', return_value='VLAN'): @@ -994,7 +999,7 @@ class TestPortsV2(test_plugin.TestPortsV2, NsxV3PluginTestCaseMixin, data['port']['fixed_ips']) def test_delete_dhcp_port(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() with self.subnet(): pl = directory.get_plugin() ctx = context.Context(user_id=None, tenant_id=self._tenant_id, @@ -1485,7 +1490,7 @@ class TestPortsV2(test_plugin.TestPortsV2, NsxV3PluginTestCaseMixin, self.assertEqual(port2['port']['id'], ports_data['ports'][0]['id']) def test_port_failure_rollback_dhcp_exception(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() self.plugin = directory.get_plugin() with mock.patch.object(self.plugin, '_add_dhcp_binding', side_effect=nsxlib_exc.ManagerError): @@ -1495,7 +1500,7 @@ class TestPortsV2(test_plugin.TestPortsV2, NsxV3PluginTestCaseMixin, self.assertListEqual([], networks) def test_port_DB_failure_rollback_dhcp_exception(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() self.plugin = directory.get_plugin() with mock.patch('vmware_nsx.db.db.add_neutron_nsx_dhcp_binding', side_effect=db_exc.DBError),\ @@ -1671,7 +1676,7 @@ class TestPortsV2(test_plugin.TestPortsV2, NsxV3PluginTestCaseMixin, self.assertTrue(res['port'][psec.PORTSECURITY]) def test_update_dhcp_port_device_owner(self): - cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + self._enable_native_dhcp_md() with self.subnet(): pl = directory.get_plugin() ctx = context.Context(user_id=None, tenant_id=self._tenant_id,