NSX|P: Support Tier0 / Transport zone related actions

- ENS validations
- Check uplink ips confilicts with external subnets
- Validate that rotuer GW and Subnets belong to the same TZ

Change-Id: Idf256f8eeb66bc669b72da3410db4552134fa1af
This commit is contained in:
Adit Sarfaty 2019-01-07 12:02:11 +02:00
parent f1f4f1abe7
commit 89d7b863f3
4 changed files with 236 additions and 63 deletions

View File

@ -449,16 +449,16 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
if is_external_net:
raise nsx_exc.QoSOnExternalNet()
is_ens_tz_port = self._is_ens_tz_port(context, port_data)
if is_ens_tz_port:
self._validate_ens_create_port(context, port_data)
# External network validations:
if is_external_net:
self._assert_on_external_net_with_compute(port_data)
self._assert_on_port_admin_state(port_data, device_owner)
is_ens_tz_port = self._is_ens_tz_port(context, port_data)
if is_ens_tz_port:
self._validate_ens_create_port(context, port_data)
def _assert_on_vpn_port_change(self, port_data):
if port_data['device_owner'] == ipsec_utils.VPN_PORT_OWNER:
msg = _('Can not update/delete VPNaaS port %s') % port_data['id']
@ -529,8 +529,6 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
device_owner = (port_data['device_owner']
if 'device_owner' in port_data
else original_port.get('device_owner'))
is_ens_tz_port = self._is_ens_tz_port(context, original_port)
# QoS validations
if qos_selected:
self._validate_qos_policy_id(
@ -538,6 +536,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
if is_external_net:
raise nsx_exc.QoSOnExternalNet()
self._assert_on_illegal_port_with_qos(device_owner)
is_ens_tz_port = self._is_ens_tz_port(context, original_port)
if is_ens_tz_port:
err_msg = _("Cannot configure QOS on ENS networks")
raise n_exc.InvalidInput(error_message=err_msg)
@ -721,9 +720,13 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
"""
pass
def _is_ens_tz_net(self, context, network_id):
"""Should be implemented by each plugin"""
pass
def _is_ens_tz_net(self, context, net_id):
"""Return True if the network is based on an END transport zone"""
tz_id = self._get_net_tz(context, net_id)
if tz_id:
# Check the mode of this TZ
return self._is_ens_tz(tz_id)
return False
def _is_ens_tz_port(self, context, port_data):
# Check the host-switch-mode of the TZ connected to the ports network
@ -1017,7 +1020,25 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return (ipaddress, netmask, nexthop)
def _validate_router_gw(self, context, router_id, info, org_enable_snat):
def _get_tier0_uuid_by_net_id(self, context, network_id):
if not network_id:
return
network = self.get_network(context, network_id)
if not network.get(pnet.PHYSICAL_NETWORK):
az = self.get_network_az(network)
return az._default_tier0_router
else:
return network.get(pnet.PHYSICAL_NETWORK)
def _validate_router_tz(self, context, tier0_uuid, subnets):
"""Ensure the related GW (Tier0 router) belongs to the same TZ
as the subnets attached to the Tier1 router
Should be implemented by each plugin.
"""
pass
def _validate_router_gw_and_tz(self, context, router_id, info,
org_enable_snat, router_subnets):
# Ensure that a router cannot have SNAT disabled if there are
# floating IP's assigned
if (info and 'enable_snat' in info and
@ -1027,6 +1048,15 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
msg = _("Unable to set SNAT disabled. Floating IPs assigned")
raise n_exc.InvalidInput(error_message=msg)
# Ensure that the router GW tier0 belongs to the same TZ as the
# subnets of its interfaces
if info and info.get('network_id'):
new_tier0_uuid = self._get_tier0_uuid_by_net_id(context.elevated(),
info['network_id'])
if new_tier0_uuid:
self._validate_router_tz(context, new_tier0_uuid,
router_subnets)
def _get_update_router_gw_actions(
self,
org_tier0_uuid, orgaddr, org_enable_snat,
@ -1250,8 +1280,8 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
project_name=context.tenant_name)
dhcp_server = None
dhcp_port_profiles = []
if (not self._is_ens_tz_net(context, network['id']) and
not self._has_native_dhcp_metadata()):
if (not self._has_native_dhcp_metadata() and
not self._is_ens_tz_net(context, network['id'])):
dhcp_port_profiles.append(self._dhcp_profile)
try:
dhcp_server = self.nsxlib.dhcp_server.create(**server_data)

View File

@ -32,7 +32,6 @@ from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef
from neutron_lib.api.definitions import external_net
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 provider_net as pnet
from neutron_lib.api.definitions import vlantransparent as vlan_apidef
from neutron_lib.api import validators
from neutron_lib.callbacks import events
@ -372,10 +371,15 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
"""
return True
def _validate_ens_net_portsecurity(self, net_data):
"""ENS security features are always enabled on NSX versions which
the policy plugin supports.
So no validation is needed
"""
pass
def create_network(self, context, network):
net_data = network['network']
#TODO(asarfaty): add ENS support
external = net_data.get(external_net.EXTERNAL)
is_external_net = validators.is_attr_set(external) and external
tenant_id = net_data['tenant_id']
@ -970,16 +974,6 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
return (ports if not fields else
[db_utils.resource_fields(port, fields) for port in ports])
def _get_tier0_uuid_by_net_id(self, context, network_id):
if not network_id:
return
network = self.get_network(context, network_id)
if not network.get(pnet.PHYSICAL_NETWORK):
az = self.get_network_az(network)
return az._default_tier0_router
else:
return network.get(pnet.PHYSICAL_NETWORK)
def _get_tier0_uuid_by_router(self, context, router):
network_id = router.gw_port_id and router.gw_port.network_id
return self._get_tier0_uuid_by_net_id(context, network_id)
@ -1059,7 +1053,10 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
orgaddr, orgmask, _orgnexthop = (
self._get_external_attachment_info(
context, router))
self._validate_router_gw(context, router_id, info, org_enable_snat)
router_subnets = self._find_router_subnets(
context.elevated(), router_id)
self._validate_router_gw_and_tz(context, router_id, info,
org_enable_snat, router_subnets)
# First update the neutron DB
super(NsxPolicyPlugin, self)._update_router_gw_info(
@ -1268,7 +1265,6 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
return updated_router
def add_router_interface(self, context, router_id, interface_info):
LOG.info("Adding router %s interface %s", router_id, interface_info)
network_id = self._get_interface_network(context, interface_info)
extern_net = self._network_is_external(context, network_id)
router_db = self._get_router(context, router_id)
@ -1287,8 +1283,15 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
self._validate_interface_address_scope(context, router_db, info)
# TODO(annak): Validate TZ
try:
# Check GW & subnets TZ
subnets = self._find_router_subnets(context.elevated(), router_id)
tier0_uuid = self._get_tier0_uuid_by_router(
context.elevated(), router_db)
#TODO(asarfaty): it is enough to validate only the new subnet,
# and not all
self._validate_router_tz(context.elevated(), tier0_uuid, subnets)
#TODO(asarfaty): adding the segment name even though it was not
# changed because otherwise the NSX will set it to default.
# This code should be removed once NSX supports it.
@ -1943,26 +1946,61 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
return True
if binding.binding_type == utils.NsxV3NetworkTypes.NSX_NETWORK:
# check the backend network
segment = self.nsxpolicy.segments.get(binding.phy_uuid)
segment = self.nsxpolicy.segment.get(binding.phy_uuid)
tz = self._get_nsx_net_tz_id(segment)
if tz:
type = self.nsxpolicy.transport_zone.get_transport_type(
tz)
return type == nsxlib_consts.TRANSPORT_TYPE_OVERLAY
def _is_ens_tz_net(self, context, net_id):
#TODO(annak): handle ENS case
return False
def _is_ens_tz(self, tz_id):
mode = self.nsxpolicy.transport_zone.get_host_switch_mode(tz_id)
return mode == nsxlib_consts.HOST_SWITCH_MODE_ENS
def _has_native_dhcp_metadata(self):
return True
def _get_tier0_uplink_ips(self, tier0_id):
#TODO(annak): implement
return []
return self.nsxpolicy.tier0.get_uplink_ips(tier0_id)
def _is_vlan_router_interface_supported(self):
return True
def _get_neutron_net_ids_by_nsx_id(self, context, lswitch_id):
return [lswitch_id]
def _get_net_tz(self, context, net_id):
bindings = nsx_db.get_network_bindings(context.session, net_id)
if bindings:
bind_type = bindings[0].binding_type
if bind_type == utils.NsxV3NetworkTypes.NSX_NETWORK:
# If it is an NSX network, return the TZ of the backend segment
segment_id = bindings[0].phy_uuid
return self.nsxpolicy.segment.get_transport_zone_id(segment_id)
elif bind_type == utils.NetworkTypes.L3_EXT:
# External network has tier0 as phy_uuid
return
else:
return bindings[0].phy_uuid
else:
# Get the default one for the network AZ
az = self.get_network_az_by_net_id(context, net_id)
return az._default_overlay_tz_uuid
def _validate_router_tz(self, context, tier0_uuid, subnets):
# make sure the related GW (Tier0 router) belongs to the same TZ
# as the subnets attached to the Tier1 router
if not subnets or not tier0_uuid:
return
tier0_tzs = self.nsxpolicy.tier0.get_transport_zones(tier0_uuid)
if not tier0_tzs:
return
for sub in subnets:
tz_uuid = self._get_net_tz(context, sub['network_id'])
if tz_uuid not in tier0_tzs:
msg = (_("Tier0 router %(rtr)s transport zone should match "
"transport zone %(tz)s of the network %(net)s") % {
'rtr': tier0_uuid,
'tz': tz_uuid,
'net': sub['network_id']})
raise n_exc.InvalidInput(error_message=msg)

View File

@ -1030,7 +1030,7 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
if cfg.CONF.nsx_v3.disable_port_security_for_ens:
# Override the port-security to False
if net_data[psec.PORTSECURITY]:
LOG.warning("Disabling port security for bew network")
LOG.warning("Disabling port security for new network")
# Set the port security to False
net_data[psec.PORTSECURITY] = False
@ -1658,19 +1658,11 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
def _get_net_tz(self, context, net_id):
mappings = nsx_db.get_nsx_switch_ids(context.session, net_id)
if mappings:
nsx_net_id = nsx_net_id = mappings[0]
nsx_net_id = mappings[0]
if nsx_net_id:
nsx_net = self.nsxlib.logical_switch.get(nsx_net_id)
return nsx_net.get('transport_zone_id')
def _is_ens_tz_net(self, context, net_id):
#Check the host-switch-mode of the TZ connected to network
tz_id = self._get_net_tz(context, net_id)
if tz_id:
# Check the mode of this TZ
return self._is_ens_tz(tz_id)
return False
def _is_ens_tz(self, tz_id):
mode = self.nsxlib.transport_zone.get_host_switch_mode(tz_id)
return mode == self.nsxlib.transport_zone.HOST_SWITCH_MODE_ENS
@ -2622,16 +2614,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
return (ports if not fields else
[db_utils.resource_fields(port, fields) for port in ports])
def _get_tier0_uuid_by_net_id(self, context, network_id):
if not network_id:
return
network = self.get_network(context, network_id)
if not network.get(pnet.PHYSICAL_NETWORK):
az = self.get_network_az(network)
return az._default_tier0_router
else:
return network.get(pnet.PHYSICAL_NETWORK)
def _get_tier0_uuid_by_router(self, context, router):
network_id = router.gw_port_id and router.gw_port.network_id
return self._get_tier0_uuid_by_net_id(context, network_id)
@ -2700,16 +2682,10 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
self._get_external_attachment_info(
context, router))
self._validate_router_gw(context, router_id, info, org_enable_snat)
router_subnets = self._find_router_subnets(
context.elevated(), router_id)
if info and info.get('network_id'):
new_tier0_uuid = self._get_tier0_uuid_by_net_id(context.elevated(),
info['network_id'])
if new_tier0_uuid:
self._validate_router_tz(context, new_tier0_uuid,
router_subnets)
self._validate_router_gw_and_tz(context, router_id, info,
org_enable_snat, router_subnets)
# TODO(berlin): For nonat use case, we actually don't need a gw port
# which consumes one external ip. But after looking at the DB logic
@ -3312,7 +3288,7 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
resource_type = (None if overlay_net else
nsxlib_consts.LROUTERPORT_CENTRALIZED)
# IF this is an ENS case - check GW & subnets
# Check GW & subnets TZ
subnets = self._find_router_subnets(context.elevated(),
router_id)
tier0_uuid = self._get_tier0_uuid_by_router(context.elevated(),

View File

@ -483,6 +483,67 @@ class NsxPTestNetworks(test_db_base_plugin_v2.TestNetworksV2,
res = self.plugin.get_network(self.ctx, net['id'])
self.assertEqual(policy_id, res['qos_policy_id'])
def test_create_ens_network_with_qos(self):
cfg.CONF.set_override('ens_support', True, 'nsx_v3')
mock_ens = mock.patch('vmware_nsxlib.v3.policy'
'.core_resources.NsxPolicyTransportZoneApi'
'.get_host_switch_mode', return_value='ENS')
mock_tz = mock.patch('vmware_nsxlib.v3'
'.core_resources.NsxLibLogicalSwitch.get',
return_value={'transport_zone_id': 'xxx'})
mock_tt = mock.patch('vmware_nsxlib.v3.policy'
'.core_resources.NsxPolicyTransportZoneApi'
'.get_transport_type', return_value='VLAN')
policy_id = uuidutils.generate_uuid()
data = {'network': {
'name': 'qos_net',
'tenant_id': 'some_tenant',
'provider:network_type': 'flat',
'provider:physical_network': 'xxx',
'qos_policy_id': policy_id,
'port_security_enabled': False}}
with mock_ens, mock_tz, mock_tt,\
mock.patch.object(self.plugin, '_validate_qos_policy_id'):
self.assertRaises(n_exc.InvalidInput,
self.plugin.create_network,
context.get_admin_context(), data)
def test_update_ens_network_with_qos(self):
cfg.CONF.set_override('ens_support', True, 'nsx_v3')
mock_ens = mock.patch('vmware_nsxlib.v3.policy'
'.core_resources.NsxPolicyTransportZoneApi'
'.get_host_switch_mode', return_value='ENS')
mock_tz = mock.patch('vmware_nsxlib.v3'
'.core_resources.NsxLibLogicalSwitch.get',
return_value={'transport_zone_id': 'xxx'})
mock_tt = mock.patch('vmware_nsxlib.v3.policy'
'.core_resources.NsxPolicyTransportZoneApi'
'.get_transport_type', return_value='VLAN')
data = {'network': {
'name': 'qos_net',
'tenant_id': 'some_tenant',
'provider:network_type': 'flat',
'provider:physical_network': 'xxx',
'admin_state_up': True,
'shared': False,
'port_security_enabled': False}}
with mock_ens, mock_tz, mock_tt,\
mock.patch.object(self.plugin, '_validate_qos_policy_id'):
network = self.plugin.create_network(context.get_admin_context(),
data)
policy_id = uuidutils.generate_uuid()
data = {'network': {
'id': network['id'],
'admin_state_up': True,
'shared': False,
'port_security_enabled': False,
'tenant_id': 'some_tenant',
'qos_policy_id': policy_id}}
self.assertRaises(n_exc.InvalidInput,
self.plugin.update_network,
context.get_admin_context(),
network['id'], data)
class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
NsxPPluginTestCaseMixin):
@ -696,6 +757,39 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
self.assertRaises(n_exc.InvalidInput,
self.plugin.create_port, self.ctx, data)
def test_create_port_ens_with_qos_fail(self):
with self.network() as network:
with self.subnet(network=network, cidr='10.0.0.0/24'):
policy_id = uuidutils.generate_uuid()
mock_ens = mock.patch(
'vmware_nsxlib.v3.policy.core_resources.'
'NsxPolicyTransportZoneApi.get_host_switch_mode',
return_value='ENS')
mock_tz = mock.patch(
'vmware_nsxlib.v3.core_resources.NsxLibLogicalSwitch.get',
return_value={'transport_zone_id': 'xxx'})
mock_tt = mock.patch(
'vmware_nsxlib.v3.policy.core_resources.'
'NsxPolicyTransportZoneApi.get_transport_type',
return_value='VLAN')
data = {'port': {
'network_id': network['network']['id'],
'tenant_id': self._tenant_id,
'name': 'qos_port',
'admin_state_up': True,
'device_id': 'fake_device',
'device_owner': 'fake_owner',
'fixed_ips': [],
'port_security_enabled': False,
'mac_address': '00:00:00:00:00:01',
'qos_policy_id': policy_id}
}
# Cannot add qos policy to this type of port
with mock_ens, mock_tz, mock_tt, \
mock.patch.object(self.plugin, '_validate_qos_policy_id'):
self.assertRaises(n_exc.InvalidInput,
self.plugin.create_port, self.ctx, data)
def test_create_port_with_mac_learning_true(self):
plugin = directory.get_plugin()
ctx = context.get_admin_context()
@ -949,6 +1043,41 @@ class NsxPTestSubnets(test_db_base_plugin_v2.TestSubnetsV2,
def test_subnet_update_ipv4_and_ipv6_pd_slaac_subnets(self):
self.skipTest('Multiple fixed ips on a port are not supported')
def test_create_external_subnet_with_conflicting_t0_address(self):
with self._create_l3_ext_network() as network:
data = {'subnet': {'network_id': network['network']['id'],
'cidr': '172.20.1.0/24',
'name': 'sub1',
'enable_dhcp': False,
'dns_nameservers': None,
'allocation_pools': None,
'tenant_id': 'tenant_one',
'host_routes': None,
'ip_version': 4}}
with mock.patch.object(self.plugin.nsxpolicy.tier0,
'get_uplink_ips',
return_value=['172.20.1.60']):
self.assertRaises(n_exc.InvalidInput,
self.plugin.create_subnet,
context.get_admin_context(), data)
def test_create_external_subnet_with_non_conflicting_t0_address(self):
with self._create_l3_ext_network() as network:
data = {'subnet': {'network_id': network['network']['id'],
'cidr': '172.20.1.0/24',
'name': 'sub1',
'enable_dhcp': False,
'dns_nameservers': None,
'allocation_pools': None,
'tenant_id': 'tenant_one',
'host_routes': None,
'ip_version': 4}}
with mock.patch.object(self.plugin.nsxpolicy.tier0,
'get_uplink_ips',
return_value=['172.20.2.60']):
self.plugin.create_subnet(
context.get_admin_context(), data)
class NsxPTestSecurityGroup(common_v3.FixExternalNetBaseTest,
NsxPPluginTestCaseMixin,