diff --git a/vmware_nsx/plugins/common_v3/plugin.py b/vmware_nsx/plugins/common_v3/plugin.py index 2b7344f125..48fa3e4f3f 100644 --- a/vmware_nsx/plugins/common_v3/plugin.py +++ b/vmware_nsx/plugins/common_v3/plugin.py @@ -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) diff --git a/vmware_nsx/plugins/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index d0b2fbe193..f7b64f921b 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -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) diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index edc3361f6b..e9f27cb67c 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -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(), diff --git a/vmware_nsx/tests/unit/nsx_p/test_plugin.py b/vmware_nsx/tests/unit/nsx_p/test_plugin.py index c940cc8e23..80e751f6cc 100644 --- a/vmware_nsx/tests/unit/nsx_p/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_p/test_plugin.py @@ -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,