NSX|P: Support policy DHCP v6

Change-Id: Ibe4936b6f0b64e67cb3c7838d0f1a16304eb1180
This commit is contained in:
asarfaty 2020-02-09 09:37:09 +02:00 committed by Adit Sarfaty
parent 35b9312a79
commit 5b84dd5e4d
7 changed files with 972 additions and 253 deletions

View File

@ -231,6 +231,7 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
'ip_version': subnet.ip_version, 'ip_version': subnet.ip_version,
'network_id': subnet.network_id, 'network_id': subnet.network_id,
'gateway_ip': subnet.gateway_ip, 'gateway_ip': subnet.gateway_ip,
'enable_dhcp': subnet.enable_dhcp,
'ipv6_address_mode': subnet.ipv6_address_mode} 'ipv6_address_mode': subnet.ipv6_address_mode}
for subnet in db_subnets] for subnet in db_subnets]
return subnets return subnets

View File

@ -1489,15 +1489,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
LOG.warning(err_msg) LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg) raise n_exc.InvalidInput(error_message=err_msg)
def _build_dhcp_server_config(self, context, network, subnet, port, az): def _get_network_dns_domain(self, az, network):
name = self.nsxlib.native_dhcp.build_server_name(
network['name'], network['id'])
net_tags = self.nsxlib.build_v3_tags_payload(
network, resource_type='os-neutron-net-id',
project_name=context.tenant_name)
dns_domain = None dns_domain = None
if network.get('dns_domain'): if network.get('dns_domain'):
net_dns = network['dns_domain'] net_dns = network['dns_domain']
@ -1507,6 +1499,18 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
dns_domain = net_dns.dns_domain dns_domain = net_dns.dns_domain
if not dns_domain or not validators.is_attr_set(dns_domain): if not dns_domain or not validators.is_attr_set(dns_domain):
dns_domain = az.dns_domain dns_domain = az.dns_domain
return dns_domain
def _build_dhcp_server_config(self, context, network, subnet, port, az):
name = self.nsxlib.native_dhcp.build_server_name(
network['name'], network['id'])
net_tags = self.nsxlib.build_v3_tags_payload(
network, resource_type='os-neutron-net-id',
project_name=context.tenant_name)
dns_domain = self._get_network_dns_domain(az, network)
dns_nameservers = subnet['dns_nameservers'] dns_nameservers = subnet['dns_nameservers']
if not dns_nameservers or not validators.is_attr_set(dns_nameservers): if not dns_nameservers or not validators.is_attr_set(dns_nameservers):
@ -2032,15 +2036,15 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
native_metadata = self._has_native_dhcp_metadata() native_metadata = self._has_native_dhcp_metadata()
default_enable_dhcp = (orig_subnet.get('enable_dhcp', False) default_enable_dhcp = (orig_subnet.get('enable_dhcp', False)
if orig_subnet else False) if orig_subnet else False)
# DHCPv6 is not yet supported, but slaac is # DHCPv6 is not yet supported, but slaac is.
# When configuring slaac, neutron requires the user # When configuring slaac, neutron requires the user
# to enable dhcp, however plugin code does not consider # to enable dhcp, however plugin code does not consider
# slaac as dhcp. # slaac as dhcp.
return (native_metadata and return (native_metadata and
subnet.get('enable_dhcp', default_enable_dhcp) and subnet.get('enable_dhcp', default_enable_dhcp) and
subnet.get('ipv6_address_mode') != 'slaac') subnet.get('ipv6_address_mode') != constants.IPV6_SLAAC)
def _validate_subnet_ip_version(self, subnet): def _validate_mp_subnet_ip_version(self, subnet):
# This validation only needs to be called at create, # This validation only needs to be called at create,
# since ip version and ipv6 mode attributes are read only # since ip version and ipv6 mode attributes are read only
if subnet.get('ip_version') == 4: if subnet.get('ip_version') == 4:
@ -2048,10 +2052,9 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return return
enable_dhcp = subnet.get('enable_dhcp', False) enable_dhcp = subnet.get('enable_dhcp', False)
is_slaac = (subnet.get('ipv6_address_mode') == 'slaac') is_slaac = (subnet.get('ipv6_address_mode') == constants.IPV6_SLAAC)
if enable_dhcp and not is_slaac: if enable_dhcp and not is_slaac:
# No DHCPv6 support yet # No DHCPv6 support with the MP DHCP
# TODO(asarfaty): add ipv6 support for policy plugin
msg = _("DHCPv6 is not supported") msg = _("DHCPv6 is not supported")
LOG.error(msg) LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg) raise n_exc.InvalidInput(error_message=msg)
@ -2085,7 +2088,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
def _create_subnet_with_mp_dhcp(self, context, subnet): def _create_subnet_with_mp_dhcp(self, context, subnet):
self._validate_number_of_subnet_static_routes(subnet) self._validate_number_of_subnet_static_routes(subnet)
self._validate_host_routes_input(subnet) self._validate_host_routes_input(subnet)
self._validate_subnet_ip_version(subnet['subnet']) self._validate_mp_subnet_ip_version(subnet['subnet'])
net_id = subnet['subnet']['network_id'] net_id = subnet['subnet']['network_id']
network = self._get_network(context, net_id) network = self._get_network(context, net_id)
@ -2505,6 +2508,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
def _build_static_routes(self, gateway_ip, cidr, host_routes): def _build_static_routes(self, gateway_ip, cidr, host_routes):
# The following code is based on _generate_opts_per_subnet() in # The following code is based on _generate_opts_per_subnet() in
# neutron/agent/linux/dhcp.py. It prepares DHCP options for a subnet. # neutron/agent/linux/dhcp.py. It prepares DHCP options for a subnet.
# This code is for IPv4 only (IPv6 dhcp does not support options)
# Add route for directly connected network. # Add route for directly connected network.
static_routes = [{'network': cidr, 'next_hop': '0.0.0.0'}] static_routes = [{'network': cidr, 'next_hop': '0.0.0.0'}]
@ -2649,13 +2653,11 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
def _has_no_dhcp_enabled_subnet(self, context, network): def _has_no_dhcp_enabled_subnet(self, context, network):
# Check if there is no DHCP-enabled subnet in the network. # Check if there is no DHCP-enabled subnet in the network.
for subnet in network.subnets: for subnet in network.subnets:
if subnet.enable_dhcp and subnet.ipv6_address_mode != 'slaac': if (subnet.enable_dhcp and
subnet.ipv6_address_mode != constants.IPV6_SLAAC):
return False return False
return True return True
def _has_dhcp_enabled_subnet(self, context, network):
return not self._has_no_dhcp_enabled_subnet(context, network)
def _has_single_dhcp_enabled_subnet(self, context, network): def _has_single_dhcp_enabled_subnet(self, context, network):
# Check if there is only one DHCP-enabled subnet in the network. # Check if there is only one DHCP-enabled subnet in the network.
count = 0 count = 0

View File

@ -128,6 +128,8 @@ SEG_SECURITY_PROFILE_ID = (
policy_defs.SegmentSecurityProfileDef.DEFAULT_PROFILE) policy_defs.SegmentSecurityProfileDef.DEFAULT_PROFILE)
SLAAC_NDRA_PROFILE_ID = 'neutron-slaac-profile' SLAAC_NDRA_PROFILE_ID = 'neutron-slaac-profile'
NO_SLAAC_NDRA_PROFILE_ID = 'neutron-no-slaac-profile' NO_SLAAC_NDRA_PROFILE_ID = 'neutron-no-slaac-profile'
STATELESS_DHCP_NDRA_PROFILE_ID = 'neutron-stateless-dhcp-profile'
STATEFUL_DHCP_NDRA_PROFILE_ID = 'neutron-stateful-dhcp-profile'
IPV6_RA_SERVICE = 'neutron-ipv6-ra' IPV6_RA_SERVICE = 'neutron-ipv6-ra'
IPV6_ROUTER_ADV_RULE_NAME = 'all-ipv6' IPV6_ROUTER_ADV_RULE_NAME = 'all-ipv6'
@ -364,9 +366,11 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
default_az = self.get_default_az() default_az = self.get_default_az()
if default_az.use_policy_dhcp: if default_az.use_policy_dhcp:
self.use_policy_dhcp = True self.use_policy_dhcp = True
LOG.info("The policy plugin will use policy based DHCP v4/6")
else: else:
self._init_native_dhcp() self._init_native_dhcp()
self.use_policy_dhcp = False self.use_policy_dhcp = False
LOG.info("The policy plugin will use MP based DHCP v4")
self._init_native_metadata() self._init_native_metadata()
@ -466,35 +470,29 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
SEG_SECURITY_PROFILE_ID) SEG_SECURITY_PROFILE_ID)
raise nsx_exc.NsxPluginException(err_msg=msg) raise nsx_exc.NsxPluginException(err_msg=msg)
# Ipv6 SLAAC NDRA profile (find it or create) # Find or create all neutron NDRA profiles
try: ndra_profiles = {
self.nsxpolicy.ipv6_ndra_profile.get(SLAAC_NDRA_PROFILE_ID) SLAAC_NDRA_PROFILE_ID: policy_constants.IPV6_RA_MODE_SLAAC_RA,
except nsx_lib_exc.ResourceNotFound: STATELESS_DHCP_NDRA_PROFILE_ID:
try: policy_constants.IPV6_RA_MODE_SLAAC_DHCP,
self.nsxpolicy.ipv6_ndra_profile.create_or_overwrite( STATEFUL_DHCP_NDRA_PROFILE_ID: policy_constants.IPV6_RA_MODE_DHCP,
SLAAC_NDRA_PROFILE_ID, NO_SLAAC_NDRA_PROFILE_ID: policy_constants.IPV6_RA_MODE_DISABLED
profile_id=SLAAC_NDRA_PROFILE_ID, }
ra_mode=policy_constants.IPV6_RA_MODE_SLAAC_RA,
tags=self.nsxpolicy.build_v3_api_version_tag())
except nsx_lib_exc.StaleRevision as e:
# This means that another controller is also creating this
LOG.info("Failed to configure ipv6_ndra_profile for SLAAC: %s",
e)
# Verify NO SLAAC NDRA profile (find it or create) for profile in ndra_profiles:
try: try:
self.nsxpolicy.ipv6_ndra_profile.get(NO_SLAAC_NDRA_PROFILE_ID) self.nsxpolicy.ipv6_ndra_profile.get(profile)
except nsx_lib_exc.ResourceNotFound: except nsx_lib_exc.ResourceNotFound:
try: try:
self.nsxpolicy.ipv6_ndra_profile.create_or_overwrite( self.nsxpolicy.ipv6_ndra_profile.create_or_overwrite(
NO_SLAAC_NDRA_PROFILE_ID, profile,
profile_id=NO_SLAAC_NDRA_PROFILE_ID, profile_id=profile,
ra_mode=policy_constants.IPV6_RA_MODE_DISABLED, ra_mode=ndra_profiles[profile],
tags=self.nsxpolicy.build_v3_api_version_tag()) tags=self.nsxpolicy.build_v3_api_version_tag())
except nsx_lib_exc.StaleRevision as e: except nsx_lib_exc.StaleRevision as e:
# This means that another controller is also creating this # This means that another controller is also creating this
LOG.info("Failed to configure ipv6_ndra_profile for NO SLAAC: " LOG.info("Failed to configure ipv6_ndra_profile %s: %s",
"%s", e) profile, e)
self.client_ssl_profile = None self.client_ssl_profile = None
@ -935,6 +933,23 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
return updated_net return updated_net
def _get_subnets_nd_profile(self, subnets, additional_profile=None):
profiles = []
if additional_profile:
profiles.append(additional_profile)
for sub in subnets:
profiles.append(self._get_subnet_ndra_profile(sub))
# If there is 1 stateful/stateless DHCP subnet (cannot have both)
# use this profile
if STATEFUL_DHCP_NDRA_PROFILE_ID in profiles:
return STATEFUL_DHCP_NDRA_PROFILE_ID
elif STATELESS_DHCP_NDRA_PROFILE_ID in profiles:
return STATELESS_DHCP_NDRA_PROFILE_ID
elif SLAAC_NDRA_PROFILE_ID in profiles:
# if there is slaac subnet and no DHCP subnet use SLAAC
return SLAAC_NDRA_PROFILE_ID
return NO_SLAAC_NDRA_PROFILE_ID
def _update_slaac_on_router(self, context, router_id, def _update_slaac_on_router(self, context, router_id,
subnet, router_subnets, delete=False): subnet, router_subnets, delete=False):
# TODO(annak): redesign when policy supports downlink-level # TODO(annak): redesign when policy supports downlink-level
@ -946,33 +961,44 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
# We prefer to make another backend call for attaching the # We prefer to make another backend call for attaching the
# profile even if it is already attached, than rely on DB # profile even if it is already attached, than rely on DB
# to have an accurate picture of existing subnets. # to have an accurate picture of existing subnets.
profile_id = None
slaac_subnet = (subnet.get('ipv6_address_mode') == 'slaac') # This method assumes that all the v6 subnets have the same
# ipv6_address_mode.
# Otherwise, earlier validation would already fail.
if slaac_subnet and not delete: if subnet.get('ip_version') == 4:
# slaac subnet connected - verify slaac is set on router # This subnet will not affect the ND profile
profile_id = SLAAC_NDRA_PROFILE_ID return
if delete: # Fetch other overlay interface networks
router_subnets = self._load_router_subnet_cidrs_from_db( # (VLAN advertising is attached on interface level)
context.elevated(), router_id) ipv6_overlay_subnets = [s for s in router_subnets
# check if there is another slaac overlay subnet that needs
# advertising (vlan advertising is attached on interface level)
slaac_subnets = [s for s in router_subnets
if s['id'] != subnet['id'] and if s['id'] != subnet['id'] and
s.get('ipv6_address_mode') == 'slaac' and s.get('ip_version') == 6 and
s.get('enable_dhcp') and
self._is_overlay_network(context, self._is_overlay_network(context,
s['network_id'])] s['network_id'])]
if delete:
if not slaac_subnets and slaac_subnet: # 'subnet' was already removed from the router_subnets list before
# this was the last slaac subnet connected - # calling this method
if ipv6_overlay_subnets:
# If there is another ipv6 overlay - select the profile by its
# address mode
profile_id = self._get_subnets_nd_profile(ipv6_overlay_subnets)
else:
# this was the last ipv6 subnet connected -
# need to disable slaac on router # need to disable slaac on router
profile_id = NO_SLAAC_NDRA_PROFILE_ID profile_id = NO_SLAAC_NDRA_PROFILE_ID
else:
profile_id = self._get_subnet_ndra_profile(subnet)
# Check the other subnets too
if (ipv6_overlay_subnets and
profile_id in [NO_SLAAC_NDRA_PROFILE_ID,
SLAAC_NDRA_PROFILE_ID]):
profile_id = self._get_subnets_nd_profile(
ipv6_overlay_subnets, additional_profile=profile_id)
if profile_id: self.nsxpolicy.tier1.update(router_id, ipv6_ndra_profile_id=profile_id)
self.nsxpolicy.tier1.update(router_id,
ipv6_ndra_profile_id=profile_id)
def _validate_net_dhcp_edge_cluster(self, context, network, az): def _validate_net_dhcp_edge_cluster(self, context, network, az):
"""Validate that the dhcp server edge cluster match the one of """Validate that the dhcp server edge cluster match the one of
@ -1000,15 +1026,25 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
raise n_exc.InvalidInput(error_message=msg) raise n_exc.InvalidInput(error_message=msg)
def _create_subnet_dhcp_port(self, context, az, network, subnet): def _create_subnet_dhcp_port(self, context, az, network, subnet):
port = self._get_net_dhcp_port(context, network['id'])
if port:
# If the port already exist (with another subnet) - update it with
# the additional ip
port['fixed_ips'].append({'subnet_id': subnet['id']})
super(NsxPolicyPlugin, self).update_port(
context, port['id'],
{'port': {'fixed_ips': port['fixed_ips']}})
return
port_data = { port_data = {
"name": "", "name": "",
"admin_state_up": True, "admin_state_up": True,
"device_id": network['id'], "device_id": network['id'],
"device_owner": const.DEVICE_OWNER_DHCP, "device_owner": const.DEVICE_OWNER_DHCP,
"network_id": network['id'], "network_id": network['id'],
"tenant_id": network["tenant_id"], "tenant_id": network['tenant_id'],
"mac_address": const.ATTR_NOT_SPECIFIED, "mac_address": const.ATTR_NOT_SPECIFIED,
"fixed_ips": [{"subnet_id": subnet['id']}], "fixed_ips": [{'subnet_id': subnet['id']}],
psec.PORTSECURITY: False psec.PORTSECURITY: False
} }
# Create the DHCP port (on neutron only) and update its port security # Create the DHCP port (on neutron only) and update its port security
@ -1020,13 +1056,23 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
self._process_portbindings_create_and_update( self._process_portbindings_create_and_update(
context, port_data, neutron_port) context, port_data, neutron_port)
def _delete_subnet_dhcp_port(self, context, net_id): def _delete_subnet_dhcp_port(self, context, net_id, subnet_id=None):
dhcp_port = self._get_sunbet_dhcp_port(context, net_id) dhcp_port = self._get_net_dhcp_port(context, net_id)
if dhcp_port: if dhcp_port:
if subnet_id:
# deleting just this subnets dhcp
if len(dhcp_port['fixed_ips']) > 1:
new_fixed_ips = [ip for ip in dhcp_port['fixed_ips']
if ip['subnet_id'] != subnet_id]
super(NsxPolicyPlugin, self).update_port(
context, dhcp_port['id'],
{'port': {'fixed_ips': new_fixed_ips}})
return
# Delete the port itself
self.delete_port(context, dhcp_port['id'], self.delete_port(context, dhcp_port['id'],
force_delete_dhcp=True) force_delete_dhcp=True)
def _get_sunbet_dhcp_port(self, context, net_id): def _get_net_dhcp_port(self, context, net_id):
filters = { filters = {
'network_id': [net_id], 'network_id': [net_id],
'device_owner': [const.DEVICE_OWNER_DHCP] 'device_owner': [const.DEVICE_OWNER_DHCP]
@ -1035,7 +1081,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
return dhcp_ports[0] if dhcp_ports else None return dhcp_ports[0] if dhcp_ports else None
def _get_sunbet_dhcp_server_ip(self, context, net_id, dhcp_subnet_id): def _get_sunbet_dhcp_server_ip(self, context, net_id, dhcp_subnet_id):
dhcp_port = self._get_sunbet_dhcp_port(context, net_id) dhcp_port = self._get_net_dhcp_port(context, net_id)
if dhcp_port: if dhcp_port:
dhcp_server_ips = [fip['ip_address'] dhcp_server_ips = [fip['ip_address']
for fip in dhcp_port['fixed_ips'] for fip in dhcp_port['fixed_ips']
@ -1044,33 +1090,39 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
return dhcp_server_ips[0] return dhcp_server_ips[0]
def _is_dhcp_network(self, context, net_id): def _is_dhcp_network(self, context, net_id):
dhcp_port = self._get_sunbet_dhcp_port(context, net_id) dhcp_port = self._get_net_dhcp_port(context, net_id)
return True if dhcp_port else False return True if dhcp_port else False
def _get_segment_subnets(self, context, net_id, net_az=None, def _get_segment_subnets(self, context, net_id, net_az=None,
interface_subnets=None, **kwargs): interface_subnets=None,
"""Get list of segmentSubnet objects to put on the segment deleted_dhcp_subnets=None):
"""Get an updated list of segmentSubnet objects to put on the segment
Including router interface subnets (for overlay networks) & Including router interface subnets (for overlay networks) &
DHCP subnet (if using policy DHCP) DHCP subnets (if using policy v4/v6 DHCP)
""" """
dhcp_subnet = None dhcp_subnets = []
if 'dhcp_subnet' in kwargs:
dhcp_subnet = kwargs['dhcp_subnet']
else:
# Get it from the network
if self.use_policy_dhcp: if self.use_policy_dhcp:
# TODO(asarfaty): Add ipv6 support # Find networks DHCP enabled subnets
with db_api.CONTEXT_READER.using(context):
network = self._get_network(context, net_id) network = self._get_network(context, net_id)
for subnet in network.subnets: for subnet in network.subnets:
if subnet.enable_dhcp and subnet.ip_version == 4: if(subnet.enable_dhcp and
dhcp_subnet = self.get_subnet(context, subnet.id) (subnet.ip_version == 4 or
subnet.ipv6_address_mode != const.IPV6_SLAAC)):
if (deleted_dhcp_subnets and
subnet.id in deleted_dhcp_subnets):
# Skip this one as it is being deleted
continue
dhcp_subnets.append(self.get_subnet(context, subnet.id))
if len(dhcp_subnets) == 2:
# A network an have at most 2 DHCP subnets
break break
router_subnets = [] router_subnets = []
if interface_subnets: if interface_subnets:
router_subnets = interface_subnets router_subnets = interface_subnets
else: else:
# Get it from the network, only if overlay # Get networks overlay router interfaces
if self._is_overlay_network(context, net_id): if self._is_overlay_network(context, net_id):
router_ids = self._get_network_router_ids( router_ids = self._get_network_router_ids(
context.elevated(), net_id) context.elevated(), net_id)
@ -1081,31 +1133,39 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
seg_subnets = [] seg_subnets = []
dhcp_subnet_id = None dhcp_subnet_ids = []
if dhcp_subnet: for dhcp_subnet in dhcp_subnets:
dhcp_subnet_id = dhcp_subnet['id'] dhcp_subnet_id = dhcp_subnet['id']
dhcp_subnet_ids.append(dhcp_subnet_id)
gw_addr = self._get_gateway_addr_from_subnet(dhcp_subnet) gw_addr = self._get_gateway_addr_from_subnet(dhcp_subnet)
cidr_prefix = int(dhcp_subnet['cidr'].split('/')[1]) cidr_prefix = int(dhcp_subnet['cidr'].split('/')[1])
dhcp_server_ip = self._get_sunbet_dhcp_server_ip( dhcp_server_ip = self._get_sunbet_dhcp_server_ip(
context, net_id, dhcp_subnet_id) context, net_id, dhcp_subnet_id)
dns_nameservers = dhcp_subnet['dns_nameservers'] dns_nameservers = dhcp_subnet['dns_nameservers']
if not net_az:
net_az = self.get_network_az_by_net_id(context, net_id)
if (not dns_nameservers or if (not dns_nameservers or
not validators.is_attr_set(dns_nameservers)): not validators.is_attr_set(dns_nameservers)):
# Use pre-configured dns server # Use pre-configured dns server
if not net_az:
net_az = self.get_network_az_by_net_id(context, net_id)
dns_nameservers = net_az.nameservers dns_nameservers = net_az.nameservers
dhcp_config = policy_defs.SegmentDhcpConfig( is_ipv6 = True if dhcp_subnet.get('ip_version') == 6 else False
server_address="%s/%s" % (dhcp_server_ip, cidr_prefix), server_ip = "%s/%s" % (dhcp_server_ip, cidr_prefix)
dns_servers=dns_nameservers, kwargs = {'server_address': server_ip,
is_ipv6=False) # TODO(asarfaty): add ipv6 support 'dns_servers': dns_nameservers}
if is_ipv6:
network = self._get_network(context, net_id)
kwargs['domain_names'] = [
self._get_network_dns_domain(net_az, network)]
dhcp_config = policy_defs.SegmentDhcpConfigV6(**kwargs)
else:
dhcp_config = policy_defs.SegmentDhcpConfigV4(**kwargs)
seg_subnet = policy_defs.Subnet(gateway_address=gw_addr, seg_subnet = policy_defs.Subnet(gateway_address=gw_addr,
dhcp_config=dhcp_config) dhcp_config=dhcp_config)
seg_subnets.append(seg_subnet) seg_subnets.append(seg_subnet)
for rtr_subnet in router_subnets: for rtr_subnet in router_subnets:
if rtr_subnet['id'] == dhcp_subnet_id: if rtr_subnet['id'] in dhcp_subnet_ids:
# Do not add the same subnet twice # Do not add the same subnet twice
continue continue
if rtr_subnet['network_id'] == net_id: if rtr_subnet['network_id'] == net_id:
@ -1124,35 +1184,58 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
net_id = network['id'] net_id = network['id']
segment_id = self._get_network_nsx_segment_id(context, net_id) segment_id = self._get_network_nsx_segment_id(context, net_id)
seg_subnets = self._get_segment_subnets( seg_subnets = self._get_segment_subnets(context, net_id, net_az=az)
context, net_id, net_az=az, dhcp_subnet=subnet)
# Update dhcp server config on the segment # Update dhcp server config on the segment
self.nsxpolicy.segment.update( self.nsxpolicy.segment.update(
segment_id=segment_id, segment_id=segment_id,
dhcp_server_config_id=az._policy_dhcp_server_config, dhcp_server_config_id=az._policy_dhcp_server_config,
subnets=seg_subnets) subnets=seg_subnets)
def _disable_network_dhcp(self, context, network): def _get_net_dhcp_subnets(self, context, net_id):
net_id = network['id'] net_dhcp_subnets = []
net_obj = self._get_network(context, net_id)
for subnet in net_obj.subnets:
if(subnet.enable_dhcp and
(subnet.ip_version == 4 or
subnet.ipv6_address_mode != const.IPV6_SLAAC)):
# This is a DHCP subnet
net_dhcp_subnets.append(subnet.id)
return net_dhcp_subnets
# Remove dhcp server config from the segment def _disable_network_dhcp(self, context, network, subnet_id=None):
segment_id = self._get_network_nsx_segment_id( net_id = network['id']
context, net_id) net_dhcp_subnets = self._get_net_dhcp_subnets(context, net_id)
segment_id = self._get_network_nsx_segment_id(context, net_id)
if subnet_id and len(net_dhcp_subnets) > 1:
# remove dhcp only from this subnet
seg_subnets = self._get_segment_subnets( seg_subnets = self._get_segment_subnets(
context, net_id, dhcp_subnet=None) context, net_id, deleted_dhcp_subnets=[subnet_id])
self.nsxpolicy.segment.update( self.nsxpolicy.segment.update(
segment_id, segment_id,
subnets=seg_subnets)
self._delete_subnet_dhcp_port(context, net_id, subnet_id=subnet_id)
else:
# Remove dhcp server config completly from the segment
seg_subnets = self._get_segment_subnets(
context, net_id, deleted_dhcp_subnets=net_dhcp_subnets)
self.nsxpolicy.segment.update(
segment_id=segment_id,
subnets=seg_subnets, subnets=seg_subnets,
dhcp_server_config_id=None) dhcp_server_config_id=None)
# Delete the neutron DHCP port (and its bindings) # Delete the neutron DHCP port (and its bindings)
self._delete_subnet_dhcp_port(context, net_id) self._delete_subnet_dhcp_port(context, net_id)
def _update_subnet_dhcp(self, context, network, subnet, az): def _update_nsx_net_dhcp(self, context, network, az, subnet=None):
"""Update the DHCP config on a network
Update the segment DHCP config, as well as the dhcp bindings on the
ports.
If just a specific subnet was modified, update only its ports.
"""
net_id = network['id'] net_id = network['id']
segment_id = self._get_network_nsx_segment_id(context, net_id) segment_id = self._get_network_nsx_segment_id(context, net_id)
seg_subnets = self._get_segment_subnets( seg_subnets = self._get_segment_subnets(context, net_id, net_az=az)
context, net_id, net_az=az, dhcp_subnet=subnet)
filters = {'network_id': [net_id]} filters = {'network_id': [net_id]}
ports = self.get_ports(context, filters=filters) ports = self.get_ports(context, filters=filters)
@ -1213,18 +1296,55 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
LOG.error(msg) LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg) raise n_exc.InvalidInput(error_message=msg)
def _init_ipv6_gateway(self, subnet):
# Override neutron decision to verify that also for ipv6 the first
# ip in the cidr is not used, as the NSX does not support xxxx::0 as a
# segment subnet gateway.
if (subnet.get('gateway_ip') is const.ATTR_NOT_SPECIFIED and
subnet.get('ip_version') == const.IP_VERSION_6 and
subnet.get('cidr') and subnet['cidr'] != const.ATTR_NOT_SPECIFIED):
net = netaddr.IPNetwork(subnet['cidr'])
subnet['gateway_ip'] = str(net.network + 1)
def _validate_subnet_host_routes(self, subnet, orig_subnet=None):
self._validate_number_of_subnet_static_routes(subnet)
if orig_subnet:
self._validate_host_routes_input(
subnet,
orig_enable_dhcp=orig_subnet['enable_dhcp'],
orig_host_routes=orig_subnet['host_routes'])
else:
self._validate_host_routes_input(subnet)
# IPv6 subnets cannot support host routes
if (subnet['subnet'].get('ip_version') == 6 or
(orig_subnet and orig_subnet.get('ip_version') == 6)):
if (validators.is_attr_set(subnet['subnet'].get('host_routes')) and
subnet['subnet']['host_routes']):
err_msg = _("Host routes can only be supported with IPv4 "
"subnets")
raise n_exc.InvalidInput(error_message=err_msg)
def _has_dhcp_enabled_subnet(self, context, network, ip_version=4):
for subnet in network.subnets:
if subnet.enable_dhcp and subnet.ip_version == ip_version:
if ip_version == 4:
return True
elif subnet.ipv6_address_mode != const.IPV6_SLAAC:
return True
return False
@nsx_plugin_common.api_replay_mode_wrapper @nsx_plugin_common.api_replay_mode_wrapper
def create_subnet(self, context, subnet): def create_subnet(self, context, subnet):
if not self.use_policy_dhcp: if not self.use_policy_dhcp:
# Subnet with MP DHCP # Subnet with MP DHCP
return self._create_subnet_with_mp_dhcp(context, subnet) return self._create_subnet_with_mp_dhcp(context, subnet)
self._validate_number_of_subnet_static_routes(subnet) self._validate_subnet_host_routes(subnet)
self._validate_host_routes_input(subnet)
self._validate_subnet_ip_version(subnet['subnet'])
net_id = subnet['subnet']['network_id'] net_id = subnet['subnet']['network_id']
network = self._get_network(context, net_id) network = self._get_network(context, net_id)
self._validate_single_ipv6_subnet(context, network, subnet['subnet']) self._validate_single_ipv6_subnet(context, network, subnet['subnet'])
self._init_ipv6_gateway(subnet['subnet'])
net_az = self.get_network_az_by_net_id(context, net_id) net_az = self.get_network_az_by_net_id(context, net_id)
# Allow manipulation of only 1 subnet of the same network at once # Allow manipulation of only 1 subnet of the same network at once
@ -1237,10 +1357,11 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
self._validate_external_subnet(context, net_id) self._validate_external_subnet(context, net_id)
self._validate_net_dhcp_edge_cluster(context, network, net_az) self._validate_net_dhcp_edge_cluster(context, network, net_az)
self._validate_net_type_with_dhcp(context, network) self._validate_net_type_with_dhcp(context, network)
ip_version = subnet['subnet'].get('ip_version', 4)
if self._has_dhcp_enabled_subnet(context, network): if self._has_dhcp_enabled_subnet(context, network, ip_version):
msg = (_("Can not create more than one DHCP-enabled " msg = (_("Can not create more than one DHCP-enabled "
"subnet in network %s") % net_id) "subnet for IPv%(ver)s in network %(net)s") %
{'ver': ip_version, 'net': net_id})
LOG.error(msg) LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg) raise n_exc.InvalidInput(error_message=msg)
self._validate_segment_subnets_num( self._validate_segment_subnets_num(
@ -1273,7 +1394,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
# revert the subnet creation # revert the subnet creation
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
# Try to delete the DHCP port, and the neutron subnet # Try to delete the DHCP port, and the neutron subnet
self._delete_subnet_dhcp_port(context, net_id) self._delete_subnet_dhcp_port(
context, net_id, subnet_id=created_subnet['id'])
super(NsxPolicyPlugin, self).delete_subnet( super(NsxPolicyPlugin, self).delete_subnet(
context, created_subnet['id']) context, created_subnet['id'])
@ -1292,11 +1414,11 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
if self._subnet_with_native_dhcp(subnet): if self._subnet_with_native_dhcp(subnet):
lock = 'nsxp_network_' + subnet['network_id'] lock = 'nsxp_network_' + subnet['network_id']
with locking.LockManager.get_lock(lock): with locking.LockManager.get_lock(lock):
# Check if it is the last DHCP-enabled subnet to delete. # Remove this subnet DHCP config
network = self._get_network(context, subnet['network_id']) network = self._get_network(context, subnet['network_id'])
if self._has_single_dhcp_enabled_subnet(context, network):
try: try:
self._disable_network_dhcp(context, network) self._disable_network_dhcp(context, network,
subnet_id=subnet_id)
except Exception as e: except Exception as e:
LOG.error("Failed to disable DHCP for " LOG.error("Failed to disable DHCP for "
"network %(id)s. Exception: %(e)s", "network %(id)s. Exception: %(e)s",
@ -1312,11 +1434,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
subnet_data = subnet['subnet'] subnet_data = subnet['subnet']
updated_subnet = None updated_subnet = None
orig_subnet = self.get_subnet(context, subnet_id) orig_subnet = self.get_subnet(context, subnet_id)
self._validate_number_of_subnet_static_routes(subnet) self._validate_subnet_host_routes(subnet, orig_subnet=orig_subnet)
self._validate_host_routes_input(
subnet,
orig_enable_dhcp=orig_subnet['enable_dhcp'],
orig_host_routes=orig_subnet['host_routes'])
net_id = orig_subnet['network_id'] net_id = orig_subnet['network_id']
network = self._get_network(context, net_id) network = self._get_network(context, net_id)
@ -1333,10 +1451,12 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
with locking.LockManager.get_lock(lock): with locking.LockManager.get_lock(lock):
if enable_dhcp: if enable_dhcp:
self._validate_net_type_with_dhcp(context, network) self._validate_net_type_with_dhcp(context, network)
ip_version = orig_subnet.get('ip_version', 4)
if self._has_dhcp_enabled_subnet(context, network): if self._has_dhcp_enabled_subnet(context, network,
ip_version):
msg = (_("Can not create more than one DHCP-enabled " msg = (_("Can not create more than one DHCP-enabled "
"subnet in network %s") % net_id) "subnet for IPv%(ver)s in network %(net)s") %
{'net': net_id, 'ver': ip_version})
LOG.error(msg) LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg) raise n_exc.InvalidInput(error_message=msg)
@ -1353,7 +1473,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
self._enable_subnet_dhcp(context, network, self._enable_subnet_dhcp(context, network,
updated_subnet, net_az) updated_subnet, net_az)
else: else:
self._disable_network_dhcp(context, network) self._disable_network_dhcp(context, network,
subnet_id=subnet_id)
except (nsx_lib_exc.ManagerError, nsx_exc.NsxPluginException): except (nsx_lib_exc.ManagerError, nsx_exc.NsxPluginException):
# revert the subnet update # revert the subnet update
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
@ -1373,8 +1494,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
('dns_nameservers' in subnet_data or ('dns_nameservers' in subnet_data or
'gateway_ip' in subnet_data or 'gateway_ip' in subnet_data or
'host_routes' in subnet_data)): 'host_routes' in subnet_data)):
self._update_subnet_dhcp(context, network, self._update_nsx_net_dhcp(context, network, net_az, updated_subnet)
updated_subnet, net_az)
return updated_subnet return updated_subnet
@ -1615,6 +1735,18 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
return True return True
def _filter_ipv6_dhcp_fixed_ips(self, context, fixed_ips):
ips = []
for fixed_ip in fixed_ips:
if netaddr.IPNetwork(fixed_ip['ip_address']).version != 6:
continue
with db_api.CONTEXT_READER.using(context):
subnet = self.get_subnet(context, fixed_ip['subnet_id'])
if (subnet['enable_dhcp'] and
subnet.get('ipv6_address_mode') != 'slaac'):
ips.append(fixed_ip)
return ips
def _add_or_overwrite_port_policy_dhcp_binding( def _add_or_overwrite_port_policy_dhcp_binding(
self, context, port, segment_id, dhcp_subnet=None): self, context, port, segment_id, dhcp_subnet=None):
if not utils.is_port_dhcp_configurable(port): if not utils.is_port_dhcp_configurable(port):
@ -1647,7 +1779,23 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
mac_address=port['mac_address'], mac_address=port['mac_address'],
options=options) options=options)
# TODO(asarfaty): add ipv6 bindings (without options) for fixed_ip in self._filter_ipv6_dhcp_fixed_ips(
context, port['fixed_ips']):
# There will be only one ipv6 ip here
binding_id = port['id'] + '-ipv6'
name = 'IPv6 binding for port %s' % port['id']
ip = fixed_ip['ip_address']
if dhcp_subnet:
if fixed_ip['subnet_id'] != dhcp_subnet['id']:
continue
subnet = dhcp_subnet
else:
subnet = self.get_subnet(context, fixed_ip['subnet_id'])
self.nsxpolicy.segment_dhcp_static_bindings.create_or_overwrite_v6(
name, segment_id, binding_id=binding_id,
ip_addresses=[ip],
lease_time=cfg.CONF.nsx_p.dhcp_lease_time,
mac_address=port['mac_address'])
def _add_port_policy_dhcp_binding(self, context, port): def _add_port_policy_dhcp_binding(self, context, port):
net_id = port['network_id'] net_id = port['network_id']
@ -1690,7 +1838,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
pass pass
def _update_port_policy_dhcp_binding(self, context, old_port, new_port): def _update_port_policy_dhcp_binding(self, context, old_port, new_port):
# First check if any IPv4 address in fixed_ips is changed. # First check if any address in fixed_ips changed.
# Then update DHCP server setting or DHCP static binding # Then update DHCP server setting or DHCP static binding
# depending on the port type. # depending on the port type.
# Note that Neutron allows a port with multiple IPs in the # Note that Neutron allows a port with multiple IPs in the
@ -1707,44 +1855,87 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
# Collect IPv4 DHCP addresses from original and updated fixed_ips # Collect IPv4 DHCP addresses from original and updated fixed_ips
# in the form of [(subnet_id, ip_address)]. # in the form of [(subnet_id, ip_address)].
old_fixed_ips = set([(fixed_ip['subnet_id'], fixed_ip['ip_address']) old_fixed_v4 = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
for fixed_ip in self._filter_ipv4_dhcp_fixed_ips( for fixed_ip in self._filter_ipv4_dhcp_fixed_ips(
context, old_port['fixed_ips'])]) context, old_port['fixed_ips'])])
new_fixed_ips = set([(fixed_ip['subnet_id'], fixed_ip['ip_address']) new_fixed_v4 = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
for fixed_ip in self._filter_ipv4_dhcp_fixed_ips( for fixed_ip in self._filter_ipv4_dhcp_fixed_ips(
context, new_port['fixed_ips'])]) context, new_port['fixed_ips'])])
old_fixed_v6 = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
for fixed_ip in self._filter_ipv6_dhcp_fixed_ips(
context, old_port['fixed_ips'])])
new_fixed_v6 = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
for fixed_ip in self._filter_ipv6_dhcp_fixed_ips(
context, new_port['fixed_ips'])])
# Find out the subnet/IP differences before and after the update. # Find out the subnet/IP differences before and after the update.
ips_to_add = list(new_fixed_ips - old_fixed_ips) v4_to_add = list(new_fixed_v4 - old_fixed_v4)
ips_to_delete = list(old_fixed_ips - new_fixed_ips) v4_to_delete = list(old_fixed_v4 - new_fixed_v4)
ip_change = (ips_to_add or ips_to_delete) v6_to_add = list(new_fixed_v6 - old_fixed_v6)
v6_to_delete = list(old_fixed_v6 - new_fixed_v6)
ip_change = (v4_to_add or v4_to_delete or v6_to_add or v6_to_delete)
if (old_port["device_owner"] == const.DEVICE_OWNER_DHCP and if (old_port["device_owner"] == const.DEVICE_OWNER_DHCP and
ip_change): ip_change):
# Update backend DHCP server address if the IP address of a DHCP # Update backend DHCP server address if the IP address of a DHCP
# port is changed. # port is changed.
if len(new_fixed_ips) != 1: if len(new_fixed_v4) > 1 or len(new_fixed_v6) > 1:
msg = _("Can only configure one IP address on a DHCP server") msg = _("Can only configure one IP address on a DHCP server")
LOG.error(msg) LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg) raise n_exc.InvalidInput(error_message=msg)
fixed_ip = list(new_fixed_ips)[0]
subnet_id = fixed_ip[0]
net_id = old_port['network_id'] net_id = old_port['network_id']
network = self.get_network(context, net_id) network = self.get_network(context, net_id)
subnet = self.get_subnet(context, subnet_id)
net_az = self.get_network_az_by_net_id(context, net_id) net_az = self.get_network_az_by_net_id(context, net_id)
self._update_subnet_dhcp(context, network, subnet, net_az) self._update_nsx_net_dhcp(context, network, net_az)
elif utils.is_port_dhcp_configurable(new_port): elif utils.is_port_dhcp_configurable(new_port):
dhcp_opts_changed = (old_port[ext_edo.EXTRADHCPOPTS] != dhcp_opts_changed = (old_port[ext_edo.EXTRADHCPOPTS] !=
new_port[ext_edo.EXTRADHCPOPTS]) new_port[ext_edo.EXTRADHCPOPTS])
if (ip_change or dhcp_opts_changed or if (ip_change or dhcp_opts_changed or
old_port['mac_address'] != new_port['mac_address']): old_port['mac_address'] != new_port['mac_address']):
if new_fixed_ips: if new_fixed_v4 or new_fixed_v6:
# Recreate the bindings of this port # Recreate the bindings of this port
self._add_port_policy_dhcp_binding(context, new_port) self._add_port_policy_dhcp_binding(context, new_port)
else: else:
self._delete_port_policy_dhcp_binding(context, old_port) self._delete_port_policy_dhcp_binding(context, old_port)
def _assert_on_ipv6_port_with_dhcpopts(self, context, port_data,
orig_port=None):
"""IPv6 port only port cannot support EXTRADHCPOPTS"""
# Get the updated EXTRADHCPOPTS
extradhcpopts = None
if ext_edo.EXTRADHCPOPTS in port_data:
extradhcpopts = port_data[ext_edo.EXTRADHCPOPTS]
elif orig_port:
extradhcpopts = orig_port.get(ext_edo.EXTRADHCPOPTS)
if not extradhcpopts:
return
# Get the updated list of fixed ips
fixed_ips = []
if (port_data.get('fixed_ips') and
validators.is_attr_set(port_data['fixed_ips'])):
fixed_ips = port_data['fixed_ips']
elif (orig_port and orig_port.get('fixed_ips') and
validators.is_attr_set(orig_port['fixed_ips'])):
fixed_ips = orig_port['fixed_ips']
# Check if any of the ips belongs to an ipv6 subnet with DHCP
# And no ipv4 subnets
for fixed_ip in fixed_ips:
if netaddr.IPNetwork(fixed_ip['ip_address']).version == 4:
# If there are ipv4 addresses - it is allowed
return
with db_api.CONTEXT_READER.using(context):
subnet = self.get_subnet(context, fixed_ip['subnet_id'])
if (subnet['enable_dhcp'] and
subnet['ipv6_address_mode'] != const.IPV6_SLAAC):
err_msg = (_("%s are not supported for IPv6 ports with "
"DHCP v6") % ext_edo.EXTRADHCPOPTS)
LOG.error(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)
def create_port(self, context, port, l2gw_port_check=False): def create_port(self, context, port, l2gw_port_check=False):
port_data = port['port'] port_data = port['port']
# validate the new port parameters # validate the new port parameters
@ -1765,6 +1956,15 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
neutron_db = self.base_create_port(context, port) neutron_db = self.base_create_port(context, port)
port["port"].update(neutron_db) port["port"].update(neutron_db)
try:
# Validate ipv6 only after fixed_ips are allocated
self._assert_on_ipv6_port_with_dhcpopts(context, port["port"])
except Exception:
with excutils.save_and_reraise_exception():
# rollback
super(NsxPolicyPlugin, self).delete_port(
context, neutron_db['id'])
self.fix_direct_vnic_port_sec(direct_vnic_type, port_data) self.fix_direct_vnic_port_sec(direct_vnic_type, port_data)
(is_psec_on, has_ip, sgids, psgids) = ( (is_psec_on, has_ip, sgids, psgids) = (
self._create_port_preprocess_security(context, port, self._create_port_preprocess_security(context, port,
@ -1949,6 +2149,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
direct_vnic_type = self._validate_port_vnic_type( direct_vnic_type = self._validate_port_vnic_type(
context, port_data, original_port['network_id']) context, port_data, original_port['network_id'])
self._assert_on_ipv6_port_with_dhcpopts(
context, port_data, orig_port=original_port)
updated_port = super(NsxPolicyPlugin, self).update_port( updated_port = super(NsxPolicyPlugin, self).update_port(
context, port_id, port) context, port_id, port)
@ -2615,6 +2817,50 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
LOG.error(msg) LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg) raise n_exc.InvalidInput(error_message=msg)
def _get_subnet_ndra_profile(self, subnet):
ndra_profile_id = NO_SLAAC_NDRA_PROFILE_ID
if subnet.get('ip_version') == 6 and subnet.get('enable_dhcp', False):
# Subnet with DHCP v6 of some kind
addr_mode = subnet.get('ipv6_address_mode')
if addr_mode == const.IPV6_SLAAC:
ndra_profile_id = SLAAC_NDRA_PROFILE_ID
elif addr_mode == const.DHCPV6_STATELESS:
ndra_profile_id = STATELESS_DHCP_NDRA_PROFILE_ID
else:
# Stateful DHCP v6 is the default
ndra_profile_id = STATEFUL_DHCP_NDRA_PROFILE_ID
return ndra_profile_id
def _validate_interfaces_address_mode(self, context, router_id,
router_subnets, subnet):
"""Validate that all the overlay ipv6 interfaces of the router have
the same ipv6_address_mode, when a new subnet is added
"""
if subnet['enable_dhcp']:
subnet_address_mode = subnet.get('ipv6_address_mode',
const.DHCPV6_STATEFUL)
else:
# Slaac and non-dhcp can coexist
subnet_address_mode = const.IPV6_SLAAC
ipv6_overlay_subnets = [s for s in router_subnets
if s['id'] != subnet['id'] and
s.get('ip_version') == 6 and
s.get('enable_dhcp') and
self._is_overlay_network(context,
s['network_id'])]
for rtr_subnet in ipv6_overlay_subnets:
address_mode = rtr_subnet.get('ipv6_address_mode',
const.DHCPV6_STATEFUL)
if address_mode != subnet_address_mode:
msg = (_("Interface network %(net_id)s with address mode "
"%(am)s conflicts with other interfaces of router "
"%(rtr_id)s") % {'net_id': subnet['network_id'],
'am': subnet_address_mode,
'rtr_id': router_id})
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
@nsx_plugin_common.api_replay_mode_wrapper @nsx_plugin_common.api_replay_mode_wrapper
def add_router_interface(self, context, router_id, interface_info): def add_router_interface(self, context, router_id, interface_info):
# NOTE: In dual stack case, neutron would create a separate interface # NOTE: In dual stack case, neutron would create a separate interface
@ -2656,6 +2902,12 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
self._validate_router_segment_subnets(context, network_id, self._validate_router_segment_subnets(context, network_id,
overlay_net, subnet) overlay_net, subnet)
if subnet and subnet.get('ip_version') == 6 and overlay_net:
orig_rtr_subnets = self._load_router_subnet_cidrs_from_db(
context.elevated(), router_id)
self._validate_interfaces_address_mode(
context, router_id, orig_rtr_subnets, subnet)
# Update the interface of the neutron router # Update the interface of the neutron router
info = super(NsxPolicyPlugin, self).add_router_interface( info = super(NsxPolicyPlugin, self).add_router_interface(
context, router_id, interface_info) context, router_id, interface_info)
@ -2678,7 +2930,6 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
# overlay interface # overlay interface
pol_subnets = self._get_segment_subnets( pol_subnets = self._get_segment_subnets(
context, network_id, interface_subnets=rtr_subnets) context, network_id, interface_subnets=rtr_subnets)
self.nsxpolicy.segment.update(segment_id, self.nsxpolicy.segment.update(segment_id,
tier1_id=router_id, tier1_id=router_id,
subnets=pol_subnets) subnets=pol_subnets)
@ -2696,9 +2947,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
ip_addresses=[rtr_subnet['gateway_ip']], ip_addresses=[rtr_subnet['gateway_ip']],
prefix_len=prefix_len)) prefix_len=prefix_len))
slaac_subnet = (subnet.get('ipv6_address_mode') == 'slaac') ndra_profile_id = self._get_subnet_ndra_profile(subnet)
ndra_profile_id = (SLAAC_NDRA_PROFILE_ID if slaac_subnet
else NO_SLAAC_NDRA_PROFILE_ID)
self.nsxpolicy.tier1.add_segment_interface( self.nsxpolicy.tier1.add_segment_interface(
router_id, segment_id, router_id, segment_id,
segment_id, pol_subnets, segment_id, pol_subnets,
@ -3045,13 +3294,17 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
NSX_P_DEFAULT_GROUP) NSX_P_DEFAULT_GROUP)
except nsx_lib_exc.ResourceNotFound: except nsx_lib_exc.ResourceNotFound:
LOG.info("Going to create default group & " LOG.info("Going to create default group & "
"communication map under the default domain") "communication map")
exists = False
else: else:
LOG.debug("Verified default group already exist") LOG.info("Going to update default group & "
return "communication map")
exists = True
# Create the default group membership criteria to match all neutron # Create the group only if not exists - no need to update it
# ports by scope (and no tag) if not exists:
# Create the default group membership criteria to match all
# neutron ports by scope (and no tag)
scope_and_tag = "%s|" % (NSX_P_PORT_RESOURCE_TYPE) scope_and_tag = "%s|" % (NSX_P_PORT_RESOURCE_TYPE)
conditions = [self.nsxpolicy.group.build_condition( conditions = [self.nsxpolicy.group.build_condition(
cond_val=scope_and_tag, cond_val=scope_and_tag,
@ -3066,13 +3319,13 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
group_id=NSX_P_DEFAULT_GROUP, group_id=NSX_P_DEFAULT_GROUP,
description=NSX_P_DEFAULT_GROUP_DESC, description=NSX_P_DEFAULT_GROUP_DESC,
conditions=conditions) conditions=conditions)
except Exception as e: except Exception as e:
msg = (_("Failed to create NSX default group: %(e)s") % { msg = (_("Failed to create NSX default group: %(e)s") % {
'e': e}) 'e': e})
raise nsx_exc.NsxPluginException(err_msg=msg) raise nsx_exc.NsxPluginException(err_msg=msg)
# create default section and rules # create default section and rules
# (even if already exists - may need to update rules)
logged = cfg.CONF.nsx_p.log_security_groups_blocked_traffic logged = cfg.CONF.nsx_p.log_security_groups_blocked_traffic
scope = [self.nsxpolicy.group.get_path( scope = [self.nsxpolicy.group.get_path(
NSX_P_GLOBAL_DOMAIN_ID, NSX_P_DEFAULT_GROUP)] NSX_P_GLOBAL_DOMAIN_ID, NSX_P_DEFAULT_GROUP)]
@ -3114,6 +3367,28 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
direction=nsxlib_consts.IN_OUT, direction=nsxlib_consts.IN_OUT,
logged=logged) logged=logged)
rule_id += 1 rule_id += 1
dhcpv6_server_rule = self.nsxpolicy.comm_map.build_entry(
'DHCPv6 Request', NSX_P_GLOBAL_DOMAIN_ID,
NSX_P_DEFAULT_SECTION,
rule_id, sequence_number=rule_id,
service_ids=['DHCPv6_Server'],
action=policy_constants.ACTION_ALLOW,
ip_protocol=nsxlib_consts.IPV6,
scope=scope,
direction=nsxlib_consts.OUT,
logged=logged)
rule_id += 1
dhcpv6_client_rule = self.nsxpolicy.comm_map.build_entry(
'DHCPv6 Reply', NSX_P_GLOBAL_DOMAIN_ID,
NSX_P_DEFAULT_SECTION,
rule_id, sequence_number=rule_id,
service_ids=['DHCPv6_Client'],
action=policy_constants.ACTION_ALLOW,
ip_protocol=nsxlib_consts.IPV6,
scope=scope,
direction=nsxlib_consts.IN,
logged=logged)
rule_id += 1
block_rule = self.nsxpolicy.comm_map.build_entry( block_rule = self.nsxpolicy.comm_map.build_entry(
'Block All', NSX_P_GLOBAL_DOMAIN_ID, 'Block All', NSX_P_GLOBAL_DOMAIN_ID,
NSX_P_DEFAULT_SECTION, NSX_P_DEFAULT_SECTION,
@ -3122,7 +3397,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
scope=scope, scope=scope,
direction=nsxlib_consts.IN_OUT, direction=nsxlib_consts.IN_OUT,
logged=logged) logged=logged)
rules = [dhcp_client_rule, dhcp_server_rule, nd_rule, block_rule] rules = [dhcp_client_rule, dhcp_server_rule, dhcpv6_client_rule,
dhcpv6_server_rule, nd_rule, block_rule]
try: try:
# This will not fail if the map already exists # This will not fail if the map already exists
self.nsxpolicy.comm_map.create_with_entries( self.nsxpolicy.comm_map.create_with_entries(

View File

@ -284,38 +284,26 @@ class NsxV3TestSubnets(NsxV3SubnetMixin,
def test_list_subnets_with_parameter(self): def test_list_subnets_with_parameter(self):
super(NsxV3TestSubnets, self).test_list_subnets_with_parameter() super(NsxV3TestSubnets, self).test_list_subnets_with_parameter()
def test_create_subnet_ipv6_gw_is_nw_start_addr(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_gw_is_nw_start_addr_canonicalize(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_gw_is_nw_end_addr(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_first_ip_owned_by_router(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_first_ip_owned_by_non_router(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_with_v6_pd_allocation_pool(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_with_v6_allocation_pool(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_pd_gw_values(self): def test_create_subnet_ipv6_pd_gw_values(self):
self.skipTest('No DHCP v6 Support yet') self.skipTest('Test not suited to the plugin DHCP code')
def test_create_subnet_ipv6_slaac_with_dhcp_port_on_network(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_slaac_with_port_not_found(self): def test_create_subnet_ipv6_slaac_with_port_not_found(self):
self.skipTest('No DHCP v6 Support yet') self.skipTest('Test not suited to the plugin DHCP code')
def test_bulk_create_subnet_ipv6_auto_addr_with_port_on_network(self): def test_bulk_create_subnet_ipv6_auto_addr_with_port_on_network(self):
self.skipTest('No DHCP v6 Support yet') self.skipTest('No Multiple v6 subnets support yet')
def test_create_subnet_dhcpv6_stateless_with_ip_already_allocated(self):
self.skipTest('Test not suited to the plugin DHCP code')
def test_create_subnet_ipv6_slaac_with_dhcp_port_on_network(self):
self.skipTest('Test not suited to the plugin DHCP code')
def test_create_subnet_dhcpv6_stateless_with_port_on_network(self):
self.skipTest('Test not suited to the plugin DHCP code')
def test_delete_subnet_port_exists_owned_by_network(self):
self.skipTest('Test not suited to the plugin DHCP code')
def test_create_subnets_bulk_native_ipv6(self): def test_create_subnets_bulk_native_ipv6(self):
self.skipTest('Multiple IPv6 subnets on one network is not supported') self.skipTest('Multiple IPv6 subnets on one network is not supported')
@ -380,24 +368,6 @@ class NsxV3TestSubnets(NsxV3SubnetMixin,
super(NsxV3TestSubnets, super(NsxV3TestSubnets,
self).test_update_subnet_inconsistent_ipv6_hostroute_np_v4() self).test_update_subnet_inconsistent_ipv6_hostroute_np_v4()
def test_update_subnet_ipv6_ra_mode_fails(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_dhcpv6_stateless_with_port_on_network(self):
self.skipTest('No DHCP v6 Support yet')
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')
def test_create_subnet_ipv6_slaac_with_db_reference_error(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_slaac_with_ip_already_allocated(self):
self.skipTest('No DHCP v6 Support yet')
def test_subnet_update_ipv4_and_ipv6_pd_v6stateless_subnets(self): def test_subnet_update_ipv4_and_ipv6_pd_v6stateless_subnets(self):
self.skipTest('Multiple fixed ips on a port are not supported') self.skipTest('Multiple fixed ips on a port are not supported')

View File

@ -29,6 +29,7 @@ from neutron.tests.unit.extensions import test_l3 as test_l3_plugin
from neutron.tests.unit.extensions import test_securitygroup from neutron.tests.unit.extensions import test_securitygroup
from neutron_lib.api.definitions import external_net as extnet_apidef from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext
from neutron_lib.api.definitions import extraroute as xroute_apidef from neutron_lib.api.definitions import extraroute as xroute_apidef
from neutron_lib.api.definitions import l3_ext_gw_mode as l3_egm_apidef from neutron_lib.api.definitions import l3_ext_gw_mode as l3_egm_apidef
from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import port_security as psec
@ -574,10 +575,6 @@ class NsxPTestPorts(common_v3.NsxV3TestPorts,
def setUp(self, **kwargs): def setUp(self, **kwargs):
super(NsxPTestPorts, self).setUp(**kwargs) super(NsxPTestPorts, self).setUp(**kwargs)
@common_v3.with_disable_dhcp
def test_requested_subnet_id_v4_and_v6(self):
return super(NsxPTestPorts, self).test_requested_subnet_id_v4_and_v6()
@common_v3.with_disable_dhcp @common_v3.with_disable_dhcp
def test_requested_ips_only(self): def test_requested_ips_only(self):
return super(NsxPTestPorts, self).test_requested_ips_only() return super(NsxPTestPorts, self).test_requested_ips_only()
@ -1004,12 +1001,46 @@ class NsxPTestPorts(common_v3.NsxV3TestPorts,
**kwargs) **kwargs)
self.assertEqual(res.status_int, exc.HTTPBadRequest.code) self.assertEqual(res.status_int, exc.HTTPBadRequest.code)
def test_create_ipv6_port(self):
with self.network(name='net') as network:
self._make_v6_subnet(network, constants.DHCPV6_STATEFUL)
res = self._create_port(self.fmt, net_id=network['network']['id'])
port = self.deserialize(self.fmt, res)
self.assertIn('id', port['port'])
def test_create_ipv6_port_with_extra_dhcp(self):
with self.network(name='net') as network:
self._make_v6_subnet(network, constants.DHCPV6_STATEFUL)
opt_list = [{'opt_name': 'bootfile-name',
'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
params = {edo_ext.EXTRADHCPOPTS: opt_list,
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
self._create_port(self.fmt, network['network']['id'],
exc.HTTPBadRequest.code, **params)
def test_update_ipv6_port_with_extra_dhcp(self):
with self.network(name='net') as network:
self._make_v6_subnet(network, constants.DHCPV6_STATEFUL)
res = self._create_port(self.fmt, net_id=network['network']['id'])
port = self.deserialize(self.fmt, res)
self.assertIn('id', port['port'])
opt_list = [{'opt_name': 'bootfile-name',
'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
data = {'port': {edo_ext.EXTRADHCPOPTS: opt_list}}
req = self.new_update_request('ports', data, port['port']['id'])
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertIn('NeutronError', res)
class NsxPTestSubnets(common_v3.NsxV3TestSubnets, class NsxPTestSubnets(common_v3.NsxV3TestSubnets,
NsxPPluginTestCaseMixin): NsxPPluginTestCaseMixin):
def setUp(self, plugin=PLUGIN_NAME, ext_mgr=None): def setUp(self, plugin=PLUGIN_NAME, ext_mgr=None):
super(NsxPTestSubnets, self).setUp(plugin=plugin, ext_mgr=ext_mgr) super(NsxPTestSubnets, self).setUp(plugin=plugin, ext_mgr=ext_mgr)
self.force_slaac = False
def _create_subnet_bulk(self, fmt, number, net_id, name, def _create_subnet_bulk(self, fmt, number, net_id, name,
ip_version=4, **kwargs): ip_version=4, **kwargs):
@ -1033,17 +1064,6 @@ class NsxPTestSubnets(common_v3.NsxV3TestSubnets,
kwargs.update({'override': overrides}) kwargs.update({'override': overrides})
return self._create_bulk(fmt, number, 'subnet', base_data, **kwargs) return self._create_bulk(fmt, number, 'subnet', base_data, **kwargs)
def _test_create_subnet(self, network=None, expected=None, **kwargs):
# Until DHCPv6 is supported, switch all test to slaac-only
#TODO(asarfaty): remove when DHCPv6 is supported
if (self.force_slaac and
'ipv6_ra_mode' in kwargs and 'ipv6_address_mode' in kwargs):
kwargs['ipv6_ra_mode'] = constants.IPV6_SLAAC
kwargs['ipv6_address_mode'] = constants.IPV6_SLAAC
return super(NsxPTestSubnets,
self)._test_create_subnet(network, expected, **kwargs)
def test_create_external_subnet_with_conflicting_t0_address(self): def test_create_external_subnet_with_conflicting_t0_address(self):
with self._create_l3_ext_network() as network: with self._create_l3_ext_network() as network:
data = {'subnet': {'network_id': network['network']['id'], data = {'subnet': {'network_id': network['network']['id'],
@ -1084,6 +1104,119 @@ class NsxPTestSubnets(common_v3.NsxV3TestSubnets,
super(NsxPTestSubnets, super(NsxPTestSubnets,
self).test_create_subnet_ipv6_slaac_with_port_on_network() self).test_create_subnet_ipv6_slaac_with_port_on_network()
def test_create_subnet_ipv6_gw_values(self):
self.skipTest("IPv6 gateway IP is assigned by the plugin")
def test_create_ipv6_subnet_with_host_routes(self):
# IPv6 host routes are not allowed
with self.network() as network:
data = {'subnet': {'network_id': network['network']['id'],
'cidr': '100::/64',
'ip_version': 6,
'tenant_id': network['network']['tenant_id'],
'host_routes': [{'destination': '200::/64',
'nexthop': '100::16'}]}}
subnet_req = self.new_create_request('subnets', data)
res = subnet_req.get_response(self.api)
self.assertEqual(exc.HTTPClientError.code, res.status_int)
def test_update_ipv6_subnet_with_host_routes(self):
# IPv6 host routes are not allowed
with self.network() as network:
data = {'subnet': {'network_id': network['network']['id'],
'cidr': '100::/64',
'ip_version': 6,
'tenant_id': network['network']['tenant_id']}}
subnet_req = self.new_create_request('subnets', data)
subnet = self.deserialize(self.fmt,
subnet_req.get_response(self.api))
sub_id = subnet['subnet']['id']
# update host routes should fail
data = {'subnet': {'host_routes': [{'destination': '200::/64',
'nexthop': '100::16'}]}}
update_req = self.new_update_request('subnets', data, sub_id)
res = update_req.get_response(self.api)
self.assertEqual(exc.HTTPClientError.code, res.status_int)
def _verify_dhcp_service(self, network_id, tenant_id, enabled):
# Verify if DHCP service is enabled on a network.
port_res = self._list_ports('json', 200, network_id,
tenant_id=tenant_id,
device_owner=constants.DEVICE_OWNER_DHCP)
port_list = self.deserialize('json', port_res)
self.assertEqual(len(port_list['ports']) == 1, enabled)
def test_create_dhcpv6_subnet(self):
with mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicySegmentApi.update") as seg_update,\
self.subnet(ip_version=constants.IP_VERSION_6, cidr='fe80::/64',
enable_dhcp=True) as subnet:
self.assertEqual(True, subnet['subnet']['enable_dhcp'])
# verify that the dhcp port was created
self._verify_dhcp_service(subnet['subnet']['network_id'],
subnet['subnet']['tenant_id'], True)
# verify backend calls
seg_update.assert_called_once_with(
dhcp_server_config_id=NSX_DHCP_PROFILE_ID,
segment_id=subnet['subnet']['network_id'],
subnets=[mock.ANY])
def test_subnet_enable_dhcpv6(self):
with self.subnet(ip_version=constants.IP_VERSION_6, cidr='fe80::/64',
enable_dhcp=False) as subnet:
data = {'subnet': {'enable_dhcp': True}}
with mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicySegmentApi.update") as seg_update:
req = self.new_update_request('subnets', data,
subnet['subnet']['id'])
res = self.deserialize('json', req.get_response(self.api))
self.assertEqual(True, res['subnet']['enable_dhcp'])
# verify that the dhcp port was created
self._verify_dhcp_service(subnet['subnet']['network_id'],
subnet['subnet']['tenant_id'], True)
# verify backend calls
seg_update.assert_called_once_with(
dhcp_server_config_id=NSX_DHCP_PROFILE_ID,
segment_id=subnet['subnet']['network_id'],
subnets=[mock.ANY])
def test_subnet_disable_dhcpv6(self):
with self.subnet(ip_version=constants.IP_VERSION_6, cidr='fe80::/64',
enable_dhcp=True) as subnet:
data = {'subnet': {'enable_dhcp': False}}
with mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicySegmentApi.update") as seg_update:
req = self.new_update_request('subnets', data,
subnet['subnet']['id'])
res = self.deserialize('json', req.get_response(self.api))
self.assertEqual(False, res['subnet']['enable_dhcp'])
# verify that the dhcp port was deleted
self._verify_dhcp_service(subnet['subnet']['network_id'],
subnet['subnet']['tenant_id'], False)
# verify backend calls
seg_update.assert_called_once_with(
dhcp_server_config_id=None,
segment_id=subnet['subnet']['network_id'],
subnets=[])
def test_delete_ipv6_dhcp_subnet(self):
with self.subnet(ip_version=constants.IP_VERSION_6, cidr='fe80::/64',
enable_dhcp=True) as subnet:
with mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicySegmentApi.update") as seg_update:
req = self.new_delete_request(
'subnets', subnet['subnet']['id'])
res = req.get_response(self.api)
self.assertEqual(exc.HTTPNoContent.code, res.status_int)
# verify that the dhcp port was deleted
self._verify_dhcp_service(subnet['subnet']['network_id'],
subnet['subnet']['tenant_id'], False)
# verify backend calls
seg_update.assert_called_once_with(
dhcp_server_config_id=None,
segment_id=subnet['subnet']['network_id'],
subnets=[])
class NsxPTestSecurityGroup(common_v3.FixExternalNetBaseTest, class NsxPTestSecurityGroup(common_v3.FixExternalNetBaseTest,
NsxPPluginTestCaseMixin, NsxPPluginTestCaseMixin,
@ -1471,9 +1604,6 @@ class NsxPTestL3NatTestCase(NsxPTestL3NatTest,
def test_router_add_gateway_multiple_subnets_ipv6(self): def test_router_add_gateway_multiple_subnets_ipv6(self):
self.skipTest('not supported') self.skipTest('not supported')
def test_router_add_interface_ipv6_subnet(self):
self.skipTest('DHCPv6 not supported')
def test_slaac_profile_single_subnet(self): def test_slaac_profile_single_subnet(self):
with mock.patch("vmware_nsxlib.v3.policy.core_resources." with mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicyTier1Api.update") as t1_update: "NsxPolicyTier1Api.update") as t1_update:
@ -2268,6 +2398,8 @@ class NsxPTestL3NatTestCase(NsxPTestL3NatTest,
False, pol_const.NAT_FIREWALL_MATCH_EXTERNAL) False, pol_const.NAT_FIREWALL_MATCH_EXTERNAL)
def test_router_interface_with_dhcp_subnet(self): def test_router_interface_with_dhcp_subnet(self):
# Policy DHCP does not allow 1 dhcp subnet and another router
# interface subnet on the same overlay network
with self.router() as r,\ with self.router() as r,\
self.network() as net,\ self.network() as net,\
self.subnet(cidr='20.0.0.0/24', network=net),\ self.subnet(cidr='20.0.0.0/24', network=net),\
@ -2277,3 +2409,252 @@ class NsxPTestL3NatTestCase(NsxPTestL3NatTest,
'add', r['router']['id'], 'add', r['router']['id'],
if_subnet['subnet']['id'], None, if_subnet['subnet']['id'], None,
expected_code=exc.HTTPBadRequest.code) expected_code=exc.HTTPBadRequest.code)
def test_router_interface_ndprofile_ipv4(self):
with self.router() as r,\
self.network() as net,\
self.subnet(cidr='20.0.0.0/24', network=net) as if_subnet,\
mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicyTier1Api.update") as t1_update:
# Adding ipv4 interface
self._router_interface_action(
'add', r['router']['id'],
if_subnet['subnet']['id'], None)
t1_update.assert_not_called()
# Removing ipv4 interface
self._router_interface_action(
'remove', r['router']['id'],
if_subnet['subnet']['id'], None)
t1_update.assert_not_called()
def _test_router_interface_ndprofile(self, profile_with,
enable_dhcp=True, mode='slaac'):
with self.router() as r,\
self.network() as net,\
self.subnet(cidr='2001::/64', network=net,
ip_version=6, enable_dhcp=enable_dhcp,
ipv6_address_mode=mode,
ipv6_ra_mode=mode) as if_subnet,\
mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicyTier1Api.update") as t1_update:
# Adding subnet interface
self._router_interface_action(
'add', r['router']['id'],
if_subnet['subnet']['id'], None)
t1_update.assert_called_with(
r['router']['id'],
ipv6_ndra_profile_id=profile_with)
t1_update.reset_mock()
# Removing subnet interface
self._router_interface_action(
'remove', r['router']['id'],
if_subnet['subnet']['id'], None)
t1_update.assert_called_with(
r['router']['id'],
ipv6_ndra_profile_id=nsx_plugin.NO_SLAAC_NDRA_PROFILE_ID)
def test_router_interface_ndprofile_no_dhcp(self):
self._test_router_interface_ndprofile(
nsx_plugin.NO_SLAAC_NDRA_PROFILE_ID,
enable_dhcp=False, mode=None)
def test_router_interface_ndprofile_slaac(self):
self._test_router_interface_ndprofile(
nsx_plugin.SLAAC_NDRA_PROFILE_ID,
enable_dhcp=True, mode=constants.IPV6_SLAAC)
def test_router_interface_ndprofile_stateful(self):
self._test_router_interface_ndprofile(
nsx_plugin.STATEFUL_DHCP_NDRA_PROFILE_ID,
enable_dhcp=True, mode=constants.DHCPV6_STATEFUL)
def test_router_interface_ndprofile_stateless(self):
self._test_router_interface_ndprofile(
nsx_plugin.STATELESS_DHCP_NDRA_PROFILE_ID,
enable_dhcp=True, mode=constants.DHCPV6_STATELESS)
def _test_router_interfaces_ndprofile(self, sub1_enable_dhcp, sub1_mode,
sub2_enable_dhcp, sub2_mode,
sub1_profile, mixed_profile=None,
successful=True,
sub1_ipversion=6, sub2_ipversion=6):
cidr1 = '2001::/64' if sub1_ipversion == 6 else '201.0.0.0/24'
cidr2 = '2002::/64' if sub2_ipversion == 6 else '202.0.0.0/24'
with self.router() as r,\
self.network() as net1, self.network() as net2,\
self.subnet(cidr=cidr1, network=net1,
ip_version=sub1_ipversion,
enable_dhcp=sub1_enable_dhcp,
ipv6_address_mode=sub1_mode,
ipv6_ra_mode=sub1_mode) as sub1,\
self.subnet(cidr=cidr2, network=net2,
ip_version=sub2_ipversion,
enable_dhcp=sub2_enable_dhcp,
ipv6_address_mode=sub2_mode,
ipv6_ra_mode=sub2_mode) as sub2,\
mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicyTier1Api.update") as t1_update:
# Adding first interface
self._router_interface_action(
'add', r['router']['id'],
sub1['subnet']['id'], None)
if sub1_ipversion == 6:
t1_update.assert_called_with(
r['router']['id'],
ipv6_ndra_profile_id=sub1_profile)
t1_update.reset_mock()
else:
t1_update.assert_not_called()
# Adding the 2nd interface
expected_code = (exc.HTTPBadRequest.code if not successful
else exc.HTTPOk.code)
self._router_interface_action(
'add', r['router']['id'],
sub2['subnet']['id'], None,
expected_code=expected_code)
if not successful:
return
if sub2_ipversion == 6:
t1_update.assert_called_with(
r['router']['id'],
ipv6_ndra_profile_id=mixed_profile)
t1_update.reset_mock()
else:
t1_update.assert_not_called()
# Removing the 2nd interface
self._router_interface_action(
'remove', r['router']['id'],
sub2['subnet']['id'], None)
if sub2_ipversion == 6:
t1_update.assert_called_with(
r['router']['id'],
ipv6_ndra_profile_id=sub1_profile)
else:
t1_update.assert_not_called()
def test_router_interfaces_ndprofile_slaac_slaac(self):
self._test_router_interfaces_ndprofile(
True, constants.IPV6_SLAAC,
True, constants.IPV6_SLAAC,
nsx_plugin.SLAAC_NDRA_PROFILE_ID,
nsx_plugin.SLAAC_NDRA_PROFILE_ID)
def test_router_interfaces_ndprofile_slaac_stateful(self):
self._test_router_interfaces_ndprofile(
True, constants.IPV6_SLAAC,
True, constants.DHCPV6_STATEFUL,
nsx_plugin.SLAAC_NDRA_PROFILE_ID,
None, successful=False)
def test_router_interfaces_ndprofile_slaac_stateless(self):
self._test_router_interfaces_ndprofile(
True, constants.IPV6_SLAAC,
True, constants.DHCPV6_STATELESS,
nsx_plugin.SLAAC_NDRA_PROFILE_ID,
None, successful=False)
def test_router_interfaces_ndprofile_disabled_stateful(self):
self._test_router_interfaces_ndprofile(
False, None,
True, constants.DHCPV6_STATEFUL,
nsx_plugin.NO_SLAAC_NDRA_PROFILE_ID,
nsx_plugin.STATEFUL_DHCP_NDRA_PROFILE_ID)
def test_router_interfaces_ndprofile_disabled_stateless(self):
self._test_router_interfaces_ndprofile(
False, None,
True, constants.DHCPV6_STATELESS,
nsx_plugin.NO_SLAAC_NDRA_PROFILE_ID,
nsx_plugin.STATELESS_DHCP_NDRA_PROFILE_ID)
def test_router_interfaces_ndprofile_stateful_stateless(self):
self._test_router_interfaces_ndprofile(
True, constants.DHCPV6_STATEFUL,
True, constants.DHCPV6_STATELESS,
nsx_plugin.STATEFUL_DHCP_NDRA_PROFILE_ID,
None, successful=False)
def test_router_interfaces_ndprofile_v4_stateless(self):
self._test_router_interfaces_ndprofile(
True, None,
True, constants.DHCPV6_STATELESS,
nsx_plugin.NO_SLAAC_NDRA_PROFILE_ID,
nsx_plugin.STATELESS_DHCP_NDRA_PROFILE_ID,
sub1_ipversion=4)
def test_router_interfaces_ndprofile_stateless_v4(self):
self._test_router_interfaces_ndprofile(
True, constants.DHCPV6_STATELESS,
True, None,
nsx_plugin.STATELESS_DHCP_NDRA_PROFILE_ID,
nsx_plugin.STATELESS_DHCP_NDRA_PROFILE_ID,
sub2_ipversion=4)
def _test_router_vlan_interface_ndprofile(self, profile_with,
enable_dhcp=True, mode='slaac'):
providernet_args = {pnet.NETWORK_TYPE: 'vlan',
pnet.SEGMENTATION_ID: 11}
with mock.patch('vmware_nsxlib.v3.policy.core_resources.'
'NsxPolicyTransportZoneApi.get_transport_type',
return_value=nsx_constants.TRANSPORT_TYPE_VLAN), \
self.network(name='vlan_net',
providernet_args=providernet_args,
arg_list=(pnet.NETWORK_TYPE,
pnet.SEGMENTATION_ID)) as net,\
self.router() as r,\
self.subnet(cidr='2001::/64', network=net,
ip_version=6, enable_dhcp=enable_dhcp,
ipv6_address_mode=mode,
ipv6_ra_mode=mode) as if_subnet,\
self._create_l3_ext_network() as ext_net,\
self.subnet(network=ext_net, cidr='10.0.0.0/16',
enable_dhcp=False) as ext_sub,\
mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicyTier1Api.add_segment_interface") as t1_add,\
mock.patch("vmware_nsxlib.v3.policy.core_resources."
"NsxPolicyTier1Api.remove_segment_interface") as t1_del:
# Add router GW
self._add_external_gateway_to_router(
r['router']['id'],
ext_sub['subnet']['network_id'])
# Adding subnet interface
self._router_interface_action(
'add', r['router']['id'],
if_subnet['subnet']['id'], None)
t1_add.assert_called_once_with(
r['router']['id'], mock.ANY, mock.ANY, [mock.ANY],
profile_with)
# Removing subnet interface
self._router_interface_action(
'remove', r['router']['id'],
if_subnet['subnet']['id'], None)
t1_del.assert_called_once_with(r['router']['id'], mock.ANY)
def test_router_vlan_interface_ndprofile_no_dhcp(self):
self._test_router_vlan_interface_ndprofile(
nsx_plugin.NO_SLAAC_NDRA_PROFILE_ID,
enable_dhcp=False, mode=None)
def test_router_vlan_interface_ndprofile_slaac(self):
self._test_router_vlan_interface_ndprofile(
nsx_plugin.SLAAC_NDRA_PROFILE_ID,
enable_dhcp=True, mode=constants.IPV6_SLAAC)
def test_router_vlan_interface_ndprofile_stateful(self):
self._test_router_vlan_interface_ndprofile(
nsx_plugin.STATEFUL_DHCP_NDRA_PROFILE_ID,
enable_dhcp=True, mode=constants.DHCPV6_STATEFUL)
def test_router_vlan_interface_ndprofile_stateless(self):
self._test_router_vlan_interface_ndprofile(
nsx_plugin.STATELESS_DHCP_NDRA_PROFILE_ID,
enable_dhcp=True, mode=constants.DHCPV6_STATELESS)

View File

@ -104,8 +104,8 @@ class NsxPolicyDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin):
'host_routes': host_routes, 'host_routes': host_routes,
'shared': shared}} 'shared': shared}}
def _bind_name(self, port): def _bind_name(self, port, ip_version=4):
return 'IPv4 binding for port %s' % port['port']['id'] return 'IPv%s binding for port %s' % (ip_version, port['port']['id'])
def _verify_dhcp_service(self, network_id, tenant_id, enabled): def _verify_dhcp_service(self, network_id, tenant_id, enabled):
# Verify if DHCP service is enabled on a network. # Verify if DHCP service is enabled on a network.
@ -174,6 +174,39 @@ class NsxPolicyDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin):
binding_id=port['port']['id'] + '-ipv4', binding_id=port['port']['id'] + '-ipv4',
**binding_data) **binding_data)
def _verify_dhcp_binding_v6(self, subnet, port_data, update_data,
assert_data):
# Verify if DHCP-v6 binding is updated.
with mock.patch('vmware_nsxlib.v3.policy.core_resources.'
'SegmentDhcpStaticBindingConfigApi.'
'create_or_overwrite_v6') as update_dhcp_binding:
device_owner = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'None'
device_id = uuidutils.generate_uuid()
with self.port(subnet=subnet, device_owner=device_owner,
device_id=device_id, **port_data) as port:
binding_name = self._bind_name(port, 6)
ip_address = port['port']['fixed_ips'][0]['ip_address']
binding_data = {'mac_address': port['port']['mac_address'],
'ip_addresses': [ip_address],
'lease_time': 86400}
# Verify the initial bindings call.
update_dhcp_binding.assert_called_once_with(
binding_name, subnet['subnet']['network_id'],
binding_id=port['port']['id'] + '-ipv6',
**binding_data)
update_dhcp_binding.reset_mock()
# Update the port with provided data.
self.plugin.update_port(
context.get_admin_context(), port['port']['id'],
update_data)
# Extend basic binding data with to-be-asserted data.
binding_data.update(assert_data)
# Verify the update call.
update_dhcp_binding.assert_called_once_with(
binding_name, subnet['subnet']['network_id'],
binding_id=port['port']['id'] + '-ipv6',
**binding_data)
def test_dhcp_service_with_create_network(self): def test_dhcp_service_with_create_network(self):
# Test if DHCP service is disabled on a network when it is created. # Test if DHCP service is disabled on a network when it is created.
with self.network() as network: with self.network() as network:
@ -569,6 +602,20 @@ class NsxPolicyDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin):
self._verify_dhcp_binding(subnet, port_data, update_data, self._verify_dhcp_binding(subnet, port_data, update_data,
assert_data) assert_data)
def test_dhcp_binding_v6_with_update_port_ip(self):
# Test if DHCP binding is updated when the IP of the associated
# compute port is changed.
with self.subnet(ip_version=6, cidr='101::/64',
enable_dhcp=True) as subnet:
port_data = {'fixed_ips': [{'subnet_id': subnet['subnet']['id'],
'ip_address': '101::3'}]}
new_ip = '101::4'
update_data = {'port': {'fixed_ips': [
{'subnet_id': subnet['subnet']['id'], 'ip_address': new_ip}]}}
assert_data = {'ip_addresses': [new_ip]}
self._verify_dhcp_binding_v6(subnet, port_data, update_data,
assert_data)
def test_dhcp_binding_with_update_port_mac(self): def test_dhcp_binding_with_update_port_mac(self):
# Test if DHCP binding is updated when the Mac of the associated # Test if DHCP binding is updated when the Mac of the associated
# compute port is changed. # compute port is changed.
@ -874,6 +921,18 @@ class NsxPolicyDhcpTestCase(test_plugin.NsxPPluginTestCaseMixin):
context.get_admin_context(), neutron_subnet['id'], context.get_admin_context(), neutron_subnet['id'],
{'subnet': {'enable_dhcp': True}}) {'subnet': {'enable_dhcp': True}})
def test_create_subnet_with_dhcp_v6_port(self):
with self.subnet(enable_dhcp=True, ip_version=6,
cidr="2002::/64") as subnet:
# find the dhcp port and verify it has port security disabled
ports = self.plugin.get_ports(
context.get_admin_context())
self.assertEqual(1, len(ports))
self.assertEqual('network:dhcp', ports[0]['device_owner'])
self.assertEqual(subnet['subnet']['network_id'],
ports[0]['network_id'])
self.assertEqual(False, ports[0]['port_security_enabled'])
class NsxPolicyMetadataTestCase(test_plugin.NsxPPluginTestCaseMixin): class NsxPolicyMetadataTestCase(test_plugin.NsxPPluginTestCaseMixin):
"""Test native metadata config when using MP MDProxy""" """Test native metadata config when using MP MDProxy"""

View File

@ -1007,6 +1007,36 @@ class TestSubnetsV2(common_v3.NsxV3TestSubnets, NsxV3PluginTestCaseMixin):
self.plugin.update_subnet, self.plugin.update_subnet,
context.get_admin_context(), subnet['id'], data) context.get_admin_context(), subnet['id'], data)
def test_create_subnet_ipv6_gw_is_nw_start_addr(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_gw_is_nw_start_addr_canonicalize(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_gw_is_nw_end_addr(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_first_ip_owned_by_router(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_first_ip_owned_by_non_router(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_with_v6_pd_allocation_pool(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_with_v6_allocation_pool(self):
self.skipTest('No DHCP v6 Support yet')
def test_update_subnet_ipv6_ra_mode_fails(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_slaac_with_ip_already_allocated(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_subnet_ipv6_slaac_with_db_reference_error(self):
self.skipTest('No DHCP v6 Support yet')
class TestPortsV2(common_v3.NsxV3SubnetMixin, class TestPortsV2(common_v3.NsxV3SubnetMixin,
common_v3.NsxV3TestPorts, NsxV3PluginTestCaseMixin, common_v3.NsxV3TestPorts, NsxV3PluginTestCaseMixin,