Merge "NSX|V3: Add support for 'direct' vnic types"

This commit is contained in:
Zuul 2018-09-05 16:16:16 +00:00 committed by Gerrit Code Review
commit 48481fdd6d
3 changed files with 206 additions and 36 deletions

View File

@ -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 ''

View File

@ -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

View File

@ -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):