From 6e19bffb4ee0e6ee6fc61eeebe84c6c99c96765a Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Wed, 29 Aug 2018 11:29:52 +0300 Subject: [PATCH] NSX|V3: Add support for 'direct' vnic types The NSX|V3 will support a direct vnic types for VLAN/FLAT networks, without portsecurity. This this case the port VIF type will be DVS, and the network segmentation ID will be added to the VIF details. Change-Id: I4c40485c35c2804465240302023e667fc4642664 --- vmware_nsx/db/nsx_portbindings_db.py | 32 ++++-- vmware_nsx/plugins/nsx_v3/plugin.py | 94 +++++++++++----- vmware_nsx/tests/unit/nsx_v3/test_plugin.py | 116 ++++++++++++++++++++ 3 files changed, 206 insertions(+), 36 deletions(-) diff --git a/vmware_nsx/db/nsx_portbindings_db.py b/vmware_nsx/db/nsx_portbindings_db.py index 3c3865e0f4..ace83674ea 100644 --- a/vmware_nsx/db/nsx_portbindings_db.py +++ b/vmware_nsx/db/nsx_portbindings_db.py @@ -33,6 +33,7 @@ from vmware_nsx._i18n import _ from vmware_nsx.common import nsx_constants from vmware_nsx.common import utils as c_utils from vmware_nsx.db import nsxv_db +from vmware_nsx.extensions import projectpluginmap LOG = logging.getLogger(__name__) @@ -43,12 +44,22 @@ SUPPORTED_VNIC_TYPES = (pbin.VNIC_NORMAL, pbin.VNIC_DIRECT_PHYSICAL) VNIC_TYPES_DIRECT_PASSTHROUGH = (pbin.VNIC_DIRECT, pbin.VNIC_DIRECT_PHYSICAL) +SUPPORTED_V_NETWORK_TYPES = (c_utils.NsxVNetworkTypes.VLAN, + c_utils.NsxVNetworkTypes.FLAT, + c_utils.NsxVNetworkTypes.PORTGROUP) +SUPPORTED_T_NETWORK_TYPES = (c_utils.NsxV3NetworkTypes.VLAN, + c_utils.NsxV3NetworkTypes.FLAT) +#Note(asarfaty): This class is currently used also by the NSX-V3 plugin, +# although it uses the NsxvPortExtAttributes DB table (which can be renamed +# in the future) @resource_extend.has_resource_extenders class NsxPortBindingMixin(pbin_db.PortBindingMixin): - def _validate_port_vnic_type(self, context, port_data, network_id): + def _validate_port_vnic_type( + self, context, port_data, network_id, + plugin_type=projectpluginmap.NsxPlugins.NSX_V): vnic_type = port_data.get(pbin.VNIC_TYPE) if vnic_type and vnic_type not in SUPPORTED_VNIC_TYPES: @@ -60,15 +71,16 @@ class NsxPortBindingMixin(pbin_db.PortBindingMixin): direct_vnic_type = vnic_type in VNIC_TYPES_DIRECT_PASSTHROUGH if direct_vnic_type: self._validate_vnic_type_direct_passthrough_for_network( - context, network_id) + context, network_id, plugin_type) return direct_vnic_type def _validate_vnic_type_direct_passthrough_for_network(self, context, - network_id): - supported_network_types = (c_utils.NsxVNetworkTypes.VLAN, - c_utils.NsxVNetworkTypes.FLAT, - c_utils.NsxVNetworkTypes.PORTGROUP) + network_id, + plugin_type): + supported_network_types = SUPPORTED_V_NETWORK_TYPES + if plugin_type == projectpluginmap.NsxPlugins.NSX_T: + supported_network_types = SUPPORTED_T_NETWORK_TYPES if not self._validate_network_type(context, network_id, supported_network_types): @@ -77,10 +89,12 @@ class NsxPortBindingMixin(pbin_db.PortBindingMixin): 'networks': supported_network_types} err_msg = _("%(vnic_types)s port vnic-types are only supported " "for ports on networks of types " - "%(networks)s.") % msg_info + "%(networks)s") % msg_info raise exceptions.InvalidInput(error_message=err_msg) - def _process_portbindings_create_and_update(self, context, port, port_res): + def _process_portbindings_create_and_update( + self, context, port, port_res, + vif_type=nsx_constants.VIF_TYPE_DVS): super(NsxPortBindingMixin, self)._process_portbindings_create_and_update( context, port, port_res) @@ -105,7 +119,7 @@ class NsxPortBindingMixin(pbin_db.PortBindingMixin): if not port_binding: port_binding = pbin_model.PortBinding( port_id=port_id, - vif_type=nsx_constants.VIF_TYPE_DVS) + vif_type=vif_type) context.session.add(port_binding) port_binding.host = port_res[pbin.HOST_ID] or '' diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 652847baa7..cf1d52d194 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -61,7 +61,6 @@ from neutron.db import securitygroups_db from neutron.db import vlantransparent_db from neutron.extensions import providernet from neutron.extensions import securitygroup as ext_sg -from neutron.plugins.ml2 import models as pbin_model from neutron.quota import resource_registry from neutron_lib.api.definitions import extra_dhcp_opt as ext_edo from neutron_lib.api.definitions import portbindings as pbin @@ -103,6 +102,7 @@ from vmware_nsx.db import db as nsx_db from vmware_nsx.db import extended_security_group from vmware_nsx.db import extended_security_group_rule as extend_sg_rule from vmware_nsx.db import maclearning as mac_db +from vmware_nsx.db import nsx_portbindings_db as pbin_db from vmware_nsx.dhcp_meta import rpc as nsx_rpc from vmware_nsx.extensions import advancedserviceproviders as as_providers from vmware_nsx.extensions import housekeeper as hk_ext @@ -178,6 +178,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, extraroute_db.ExtraRoute_db_mixin, router_az_db.RouterAvailabilityZoneMixin, l3_gwmode_db.L3_NAT_db_mixin, + pbin_db.NsxPortBindingMixin, portbindings_db.PortBindingMixin, portsecurity_db.PortSecurityDbMixin, extradhcpopt_db.ExtraDhcpOptMixin, @@ -585,24 +586,28 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, for az in self.get_azs_list(): az.translate_configured_names_to_uuids(self.nsxlib) - def add_port_binding(self, context, port_id): - port_binding = pbin_model.PortBinding( - port_id=port_id, - vif_type=pbin.VIF_TYPE_OVS) - context.session.add(port_binding) + def _get_network_segmentation_id(self, context, neutron_id): + bindings = nsx_db.get_network_bindings(context.session, neutron_id) + if bindings: + return bindings[0].vlan_id def _extend_nsx_port_dict_binding(self, context, port_data): # Not using the register api for this because we need the context - port_data[pbin.VIF_TYPE] = pbin.VIF_TYPE_OVS - port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL + # Some attributes were already initialized by _extend_port_portbinding + if pbin.VIF_TYPE not in port_data: + port_data[pbin.VIF_TYPE] = pbin.VIF_TYPE_OVS + if pbin.VNIC_TYPE not in port_data: + port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL if 'network_id' in port_data: - port_data[pbin.VIF_DETAILS] = { - pbin.OVS_HYBRID_PLUG: False, - # TODO(rkukura): Replace with new VIF security details - pbin.CAP_PORT_FILTER: - 'security-group' in self.supported_extension_aliases, - 'nsx-logical-switch-id': - self._get_network_nsx_id(context, port_data['network_id'])} + net_id = port_data['network_id'] + if pbin.VIF_DETAILS not in port_data: + port_data[pbin.VIF_DETAILS] = {} + port_data[pbin.VIF_DETAILS][pbin.OVS_HYBRID_PLUG] = False + port_data[pbin.VIF_DETAILS]['nsx-logical-switch-id'] = ( + self._get_network_nsx_id(context, net_id)) + if port_data[pbin.VNIC_TYPE] != pbin.VNIC_NORMAL: + port_data[pbin.VIF_DETAILS]['segmentation-id'] = ( + self._get_network_segmentation_id(context, net_id)) @nsxlib_utils.retry_upon_exception( Exception, max_attempts=cfg.CONF.nsx_v3.retries) @@ -1097,6 +1102,12 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, nsxlib_consts.FEATURE_VLAN_ROUTER_INTERFACE) or self._is_overlay_network(context, network_id)), "non-overlay" + def _validate_network_type(self, context, network_id, net_types): + net = self.get_network(context, network_id) + if net.get(pnet.NETWORK_TYPE) in net_types: + return True + return False + def _is_ddi_supported_on_network(self, context, network_id): result, _ = self._is_ddi_supported_on_net_with_type( context, network_id) @@ -2907,11 +2918,14 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, def base_create_port(self, context, port): neutron_db = super(NsxV3Plugin, self).create_port(context, port) - self.add_port_binding(context, neutron_db['id']) self._extension_manager.process_create_port( context, port['port'], neutron_db) return neutron_db + def _vif_type_by_vnic_type(self, direct_vnic_type): + return (nsx_constants.VIF_TYPE_DVS if direct_vnic_type + else pbin.VIF_TYPE_OVS) + def create_port(self, context, port, l2gw_port_check=False): port_data = port['port'] dhcp_opts = port_data.get(ext_edo.EXTRADHCPOPTS) @@ -2936,6 +2950,10 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, port_data[psec.PORTSECURITY] = False port_data['security_groups'] = [] + direct_vnic_type = self._validate_port_vnic_type( + context, port_data, port_data['network_id'], + projectpluginmap.NsxPlugins.NSX_T) + with db_api.context_manager.writer.using(context): is_external_net = self._network_is_external( context, port_data['network_id']) @@ -2950,12 +2968,26 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, neutron_db = self.base_create_port(context, port) port["port"].update(neutron_db) + if direct_vnic_type: + if validators.is_attr_set(port_data.get(psec.PORTSECURITY)): + # 'direct' and 'direct-physical' vnic types ports requires + # port-security to be disabled. + if port_data[psec.PORTSECURITY]: + err_msg = _("Security features are not supported for " + "ports with direct/direct-physical VNIC " + "type") + raise n_exc.InvalidInput(error_message=err_msg) + else: + # Implicitly disable port-security for direct vnic types. + port_data[psec.PORTSECURITY] = False + (is_psec_on, has_ip, sgids, psgids) = ( self._create_port_preprocess_security(context, port, port_data, neutron_db, is_ens_tz_port)) self._process_portbindings_create_and_update( - context, port['port'], port_data) + context, port['port'], port_data, + vif_type=self._vif_type_by_vnic_type(direct_vnic_type)) self._process_port_create_extra_dhcp_opts( context, port_data, dhcp_opts) @@ -3137,7 +3169,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, def _update_port_preprocess_security( self, context, port, id, updated_port, is_ens_tz_port, - validate_port_sec=True): + validate_port_sec=True, direct_vnic_type=False): delete_addr_pairs = self._check_update_deletes_allowed_address_pairs( port) has_addr_pairs = self._check_update_has_allowed_address_pairs(port) @@ -3172,10 +3204,16 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, context, updated_port, updated_port[addr_apidef.ADDRESS_PAIRS]) - # No port security is allowed if the port belongs to an ENS TZ - if (updated_port[psec.PORTSECURITY] and - psec.PORTSECURITY in port_data and is_ens_tz_port): - raise nsx_exc.NsxENSPortSecurity() + if updated_port[psec.PORTSECURITY] and psec.PORTSECURITY in port_data: + # No port security is allowed if the port belongs to an ENS TZ + if is_ens_tz_port: + raise nsx_exc.NsxENSPortSecurity() + + # No port security is allowed if the port has a direct vnic type + if direct_vnic_type: + err_msg = _("Security features are not supported for " + "ports with direct/direct-physical VNIC type") + raise n_exc.InvalidInput(error_message=err_msg) # checks if security groups were updated adding/modifying # security groups, port security is set and port has ip @@ -3445,6 +3483,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, port_data.get('fixed_ips', []), device_owner) self._assert_on_vpn_port_change(original_port) + direct_vnic_type = self._validate_port_vnic_type( + context, port_data, original_port['network_id']) + updated_port = super(NsxV3Plugin, self).update_port(context, id, port) self._extension_manager.process_update_port(context, port_data, @@ -3456,7 +3497,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, updated_port = self._update_port_preprocess_security( context, port, id, updated_port, is_ens_tz_port, - validate_port_sec=validate_port_sec) + validate_port_sec=validate_port_sec, + direct_vnic_type=direct_vnic_type) self._update_extra_dhcp_opts_on_port(context, id, port, updated_port) @@ -3469,7 +3511,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, (port_security, has_ip) = self._determine_port_security_and_has_ip( context, updated_port) self._process_portbindings_create_and_update( - context, port_data, updated_port) + context, port_data, updated_port, + vif_type=self._vif_type_by_vnic_type(direct_vnic_type)) self._extend_nsx_port_dict_binding(context, updated_port) mac_learning_state = updated_port.get(mac_ext.MAC_LEARNING) if mac_learning_state is not None: @@ -5106,6 +5149,3 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, nsx_router_id, ext_addr, source_net=subnet['cidr'], bypass_firewall=False) - - def extend_port_portbinding(self, port_res, binding): - pass diff --git a/vmware_nsx/tests/unit/nsx_v3/test_plugin.py b/vmware_nsx/tests/unit/nsx_v3/test_plugin.py index cc96764c70..c23ca0b70b 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_plugin.py @@ -1586,6 +1586,122 @@ class TestPortsV2(test_plugin.TestPortsV2, NsxV3PluginTestCaseMixin, self.plugin.create_port, self.ctx, data) + def _test_create_direct_network(self, vlan_id=0): + net_type = vlan_id and 'vlan' or 'flat' + name = 'direct_net' + providernet_args = {pnet.NETWORK_TYPE: net_type, + pnet.PHYSICAL_NETWORK: 'tzuuid'} + if vlan_id: + providernet_args[pnet.SEGMENTATION_ID] = vlan_id + + mock_tt = mock.patch('vmware_nsxlib.v3' + '.core_resources.NsxLibTransportZone' + '.get_transport_type', + return_value='VLAN') + mock_tt.start() + return self.network(name=name, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK, + pnet.SEGMENTATION_ID)) + + def _test_create_port_vnic_direct(self, vlan_id): + with self._test_create_direct_network(vlan_id=vlan_id) as network: + # Check that port security conflicts + kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT, + psec.PORTSECURITY: True} + net_id = network['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE, + psec.PORTSECURITY), + **kwargs) + self.assertEqual(res.status_int, exc.HTTPBadRequest.code) + + # Check that security group conflicts + kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT, + 'security_groups': [ + '4cd70774-cc67-4a87-9b39-7d1db38eb087'], + psec.PORTSECURITY: False} + net_id = network['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE, + psec.PORTSECURITY), + **kwargs) + self.assertEqual(res.status_int, exc.HTTPBadRequest.code) + + # All is kosher so we can create the port + kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT} + net_id = network['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE,), + **kwargs) + port = self.deserialize('json', res) + self.assertEqual("direct", port['port'][portbindings.VNIC_TYPE]) + self.assertEqual("dvs", port['port'][portbindings.VIF_TYPE]) + self.assertEqual( + vlan_id, + port['port'][portbindings.VIF_DETAILS]['segmentation-id']) + + # try to get the same port + req = self.new_show_request('ports', port['port']['id'], self.fmt) + sport = self.deserialize(self.fmt, req.get_response(self.api)) + self.assertEqual("dvs", sport['port'][portbindings.VIF_TYPE]) + self.assertEqual("direct", sport['port'][portbindings.VNIC_TYPE]) + self.assertEqual( + vlan_id, + sport['port'][portbindings.VIF_DETAILS]['segmentation-id']) + + def test_create_port_vnic_direct_flat(self): + self._test_create_port_vnic_direct(0) + + def test_create_port_vnic_direct_vlan(self): + self._test_create_port_vnic_direct(10) + + def test_create_port_vnic_direct_invalid_network(self): + with self.network(name='not vlan/flat') as net: + kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT, + psec.PORTSECURITY: False} + net_id = net['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE, + psec.PORTSECURITY), + **kwargs) + self.assertEqual(exc.HTTPBadRequest.code, res.status_int) + + def test_update_vnic_direct(self): + with self._test_create_direct_network(vlan_id=7) as network: + with self.subnet(network=network) as subnet: + with self.port(subnet=subnet) as port: + # need to do two updates as the update for port security + # disabled requires that it can only change 2 items + data = {'port': {psec.PORTSECURITY: False, + 'security_groups': []}} + req = self.new_update_request('ports', + data, port['port']['id']) + res = self.deserialize('json', req.get_response(self.api)) + self.assertEqual(portbindings.VNIC_NORMAL, + res['port'][portbindings.VNIC_TYPE]) + + data = {'port': {portbindings.VNIC_TYPE: + portbindings.VNIC_DIRECT}} + + req = self.new_update_request('ports', + data, port['port']['id']) + res = self.deserialize('json', req.get_response(self.api)) + self.assertEqual(portbindings.VNIC_DIRECT, + res['port'][portbindings.VNIC_TYPE]) + + def test_port_invalid_vnic_type(self): + with self._test_create_direct_network(vlan_id=7) as network: + kwargs = {portbindings.VNIC_TYPE: 'invalid', + psec.PORTSECURITY: False} + net_id = network['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE, + psec.PORTSECURITY), + **kwargs) + self.assertEqual(res.status_int, exc.HTTPBadRequest.code) + class DHCPOptsTestCase(test_dhcpopts.TestExtraDhcpOpt, NsxV3PluginTestCaseMixin):