NSXv port-binding support

The current implementation doesn't correctly process some port-binding
attributes such as 'portbinding:profile' and 'portbinding:vif_details'.
This patch add the required support to process and persist the missing
port-binding information.
The new fields are modified and queried by nova, and will allow us to support
for SR-IOV passthrough networking.

In order to avoid DB migrations, this implementation will utilize the
existing 'ml2_port_bindings' table to hold the extra port binding information,
current tables that contains partial information (e.g - 'portbindingports'
for port's 'binding:host_id') will be kept and maintained by the plugin to
preserve backward compatibility.

Change-Id: I779b577737565860a53461114c9822d7b3908cb3
This commit is contained in:
Roey Chen 2017-08-28 09:40:20 -07:00
parent 1401b25294
commit 065ec89b91
3 changed files with 187 additions and 69 deletions

View File

@ -0,0 +1,160 @@
# Copyright 2017 VMware, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from oslo_serialization import jsonutils
from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import portbindings as pbin
from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.api import validators
from neutron_lib import constants
from neutron_lib import exceptions
from neutron_lib.plugins import directory
from neutron.db import _resource_extend as resource_extend
from neutron.db import api as db_api
from neutron.db import portbindings_db as pbin_db
from neutron.plugins.ml2 import models as pbin_model
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
LOG = logging.getLogger(__name__)
FLAT_VLAN = 0
SUPPORTED_VNIC_TYPES = (pbin.VNIC_NORMAL,
pbin.VNIC_DIRECT,
pbin.VNIC_DIRECT_PHYSICAL)
VNIC_TYPES_DIRECT_PASSTHROUGH = (pbin.VNIC_DIRECT, pbin.VNIC_DIRECT_PHYSICAL)
@resource_extend.has_resource_extenders
class NsxPortBindingMixin(pbin_db.PortBindingMixin):
def _validate_port_vnic_type(self, context, port_data, network_id):
vnic_type = port_data.get(pbin.VNIC_TYPE)
if vnic_type and vnic_type not in SUPPORTED_VNIC_TYPES:
err_msg = _("Invalid port vnic-type '%(vnic_type)s'."
"Supported vnic-types are %(valid_types)s."
) % {'vnic_type': vnic_type,
'valid_types': SUPPORTED_VNIC_TYPES}
raise exceptions.InvalidInput(error_message=err_msg)
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)
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)
if not self._validate_network_type(context, network_id,
supported_network_types):
msg_info = {
'vnic_types': VNIC_TYPES_DIRECT_PASSTHROUGH,
'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
raise exceptions.InvalidInput(error_message=err_msg)
def _process_portbindings_create_and_update(self, context, port, port_res):
super(NsxPortBindingMixin,
self)._process_portbindings_create_and_update(
context, port, port_res)
port_id = port_res['id']
org_vnic_type = nsxv_db.get_nsxv_ext_attr_port_vnic_type(
context.session, port_id)
vnic_type = port.get(pbin.VNIC_TYPE, org_vnic_type)
cap_port_filter = (port.get(pbin.VNIC_TYPE, org_vnic_type)
== pbin.VNIC_NORMAL)
vif_details = {pbin.CAP_PORT_FILTER: cap_port_filter}
network = self.get_network(context, port_res['network_id'])
if network.get(pnet.NETWORK_TYPE) == c_utils.NsxVNetworkTypes.FLAT:
vif_details[pbin.VIF_DETAILS_VLAN] = FLAT_VLAN
elif network.get(pnet.NETWORK_TYPE) == c_utils.NsxVNetworkTypes.VLAN:
vif_details[pbin.VIF_DETAILS_VLAN] = network[pnet.SEGMENTATION_ID]
with db_api.context_manager.writer.using(context):
port_binding = context.session.query(
pbin_model.PortBinding).filter_by(port_id=port_id).first()
if not port_binding:
port_binding = pbin_model.PortBinding(
port_id=port_id,
vif_type=nsx_constants.VIF_TYPE_DVS)
context.session.add(port_binding)
port_binding.host = port_res[pbin.HOST_ID] or ''
port_binding.vnic_type = vnic_type
port_binding.vif_details = jsonutils.dumps(vif_details)
nsxv_db.update_nsxv_port_ext_attributes(
context.session, port_id, vnic_type)
profile = port.get(pbin.PROFILE, constants.ATTR_NOT_SPECIFIED)
if validators.is_attr_set(profile) or profile is None:
port_binding.profile = (jsonutils.dumps(profile)
if profile else "")
port_res[pbin.VNIC_TYPE] = vnic_type
self.extend_port_portbinding(port_res, port_binding)
def extend_port_portbinding(self, port_res, binding):
port_res[pbin.PROFILE] = self._get_profile(binding)
port_res[pbin.VIF_TYPE] = binding.vif_type
port_res[pbin.VIF_DETAILS] = self._get_vif_details(binding)
def _get_vif_details(self, binding):
if binding.vif_details:
try:
return jsonutils.loads(binding.vif_details)
except Exception:
LOG.error("Serialized vif_details DB value '%(value)s' "
"for port %(port)s is invalid",
{'value': binding.vif_details,
'port': binding.port_id})
return {}
def _get_profile(self, binding):
if binding.profile:
try:
return jsonutils.loads(binding.profile)
except Exception:
LOG.error("Serialized profile DB value '%(value)s' for "
"port %(port)s is invalid",
{'value': binding.profile,
'port': binding.port_id})
return {}
@staticmethod
@resource_extend.extends([port_def.COLLECTION_NAME])
def _extend_port_portbinding(port_res, port_db):
plugin = directory.get_plugin()
plugin.extend_port_dict_binding(port_res, port_db)
if port_db.nsx_port_attributes:
port_res[pbin.VNIC_TYPE] = port_db.nsx_port_attributes.vnic_type
if port_db.port_binding:
plugin.extend_port_portbinding(port_res, port_db.port_binding)

View File

@ -870,6 +870,15 @@ def add_nsxv_port_ext_attributes(session, port_id,
return binding
def get_nsxv_ext_attr_port_vnic_type(session, port_id):
try:
binding = session.query(nsxv_models.NsxvPortExtAttributes).filter_by(
port_id=port_id).one()
return binding['vnic_type']
except exc.NoResultFound:
return pbin.VNIC_NORMAL
def update_nsxv_port_ext_attributes(session, port_id,
vnic_type=pbin.VNIC_NORMAL):
try:

View File

@ -21,7 +21,6 @@ from neutron_lib.api.definitions import extra_dhcp_opt as ext_edo
from neutron_lib.api.definitions import network as net_def
from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import port_security as psec
from neutron_lib.api.definitions import portbindings as pbin
from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.api.definitions import subnet as subnet_def
from neutron_lib.api import validators
@ -67,7 +66,6 @@ from neutron.db import l3_gwmode_db
from neutron.db.models import l3 as l3_db_models
from neutron.db.models import securitygroup as securitygroup_model # noqa
from neutron.db import models_v2
from neutron.db import portbindings_db
from neutron.db import portsecurity_db
from neutron.db import quota_db # noqa
from neutron.db import securitygroups_db
@ -98,7 +96,6 @@ from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import l3_rpc_agent_api
from vmware_nsx.common import locking
from vmware_nsx.common import managers as nsx_managers
from vmware_nsx.common import nsx_constants
from vmware_nsx.common import nsxv_constants
from vmware_nsx.common import utils as c_utils
from vmware_nsx.db import (
@ -107,6 +104,7 @@ from vmware_nsx.db import (
routertype as rt_rtr)
from vmware_nsx.db import db as nsx_db
from vmware_nsx.db import extended_security_group as extended_secgroup
from vmware_nsx.db import nsx_portbindings_db as pbin_db
from vmware_nsx.db import nsxv_db
from vmware_nsx.db import vnic_index_db
from vmware_nsx.extensions import (
@ -157,7 +155,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
extradhcpopt_db.ExtraDhcpOptMixin,
router_az_db.RouterAvailabilityZoneMixin,
l3_gwmode_db.L3_NAT_db_mixin,
portbindings_db.PortBindingMixin,
pbin_db.NsxPortBindingMixin,
portsecurity_db.PortSecurityDbMixin,
extend_sg_rule.ExtendedSecurityGroupRuleMixin,
securitygroups_db.SecurityGroupDbMixin,
@ -1666,27 +1664,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
context, port['port'], created_port)
return created_port
def _validate_port_direct_vnic_type(self, context, port_data):
vnic_type = port_data.get(pbin.VNIC_TYPE)
has_vnic_type = validators.is_attr_set(vnic_type)
if has_vnic_type and vnic_type in [pbin.VNIC_DIRECT,
pbin.VNIC_DIRECT_PHYSICAL]:
if not self._validate_network_type(
context, port_data['network_id'],
[c_utils.NsxVNetworkTypes.VLAN,
c_utils.NsxVNetworkTypes.FLAT,
c_utils.NsxVNetworkTypes.PORTGROUP]):
err_msg = _("'%s' vnic-type is only supported"
"for networks of type 'vlan', 'flat' or "
"'portgroup'.") % vnic_type
raise n_exc.InvalidInput(error_message=err_msg)
return vnic_type
elif has_vnic_type and vnic_type != pbin.VNIC_NORMAL:
err_msg = _("Invalid vnic-type %s."
"Supported vnic-types are 'normal', 'direct' and "
"'direct-physical'.") % vnic_type
raise n_exc.InvalidInput(error_message=err_msg)
def _validate_extra_dhcp_options(self, opts):
if not opts:
return
@ -1719,6 +1696,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._validate_extra_dhcp_options(dhcp_opts)
self._validate_max_ips_per_port(port_data.get('fixed_ips', []),
port_data.get('device_owner'))
direct_vnic_type = self._validate_port_vnic_type(
context, port_data, port_data['network_id'])
with db_api.context_manager.writer.using(context):
# First we allocate port in neutron database
@ -1726,9 +1705,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._extension_manager.process_create_port(
context, port_data, neutron_db)
direct_vnic_type = self._validate_port_direct_vnic_type(context,
port_data)
# Port port-security is decided based on port's vnic_type and ports
# network port-security state (unless explicitly requested
# differently by the user).
@ -1757,6 +1733,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._process_port_port_security_create(
context, port_data, neutron_db)
self._process_portbindings_create_and_update(
context, port_data, neutron_db)
# Update fields obtained from neutron db (eg: MAC address)
port["port"].update(neutron_db)
has_ip = self._ip_on_port(neutron_db)
@ -1786,15 +1765,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
port_data,
ssgids)
if direct_vnic_type:
nsxv_db.update_nsxv_port_ext_attributes(
session=context.session,
port_id=port_data['id'],
vnic_type=direct_vnic_type)
self._process_portbindings_create_and_update(context,
port['port'],
port_data)
neutron_db[addr_pair.ADDRESS_PAIRS] = (
self._process_create_allowed_address_pairs(
context, neutron_db,
@ -2013,30 +1983,28 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
comp_owner_update = ('device_owner' in port_data and
port_data['device_owner'].startswith('compute:'))
direct_vnic_type = self._validate_port_vnic_type(
context, port_data, original_port['network_id'])
if direct_vnic_type and has_port_security:
err_msg = _("Security features are not supported for "
"ports with direct/direct-physical VNIC type.")
raise n_exc.InvalidInput(error_message=err_msg)
with db_api.context_manager.writer.using(context):
ret_port = super(NsxVPluginV2, self).update_port(
context, id, port)
self._extension_manager.process_update_port(
context, port_data, ret_port)
self._process_portbindings_create_and_update(
context, port_data, ret_port)
# copy values over - except fixed_ips as
# they've already been processed
updates_fixed_ips = port['port'].pop('fixed_ips', [])
ret_port.update(port['port'])
has_ip = self._ip_on_port(ret_port)
direct_vnic_type = self._validate_port_direct_vnic_type(context,
ret_port)
if direct_vnic_type and has_port_security:
err_msg = _("Security features are not supported for "
"ports with direct/direct-physical VNIC type.")
raise n_exc.InvalidInput(error_message=err_msg)
if direct_vnic_type:
nsxv_db.update_nsxv_port_ext_attributes(
session=context.session,
port_id=ret_port['id'],
vnic_type=direct_vnic_type)
# checks that if update adds/modify security groups,
# then port has ip and port-security
if not (has_ip and has_port_security):
@ -2056,11 +2024,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
pvd_sg_changed = self._process_port_update_provider_security_group(
context, port, original_port, ret_port)
LOG.debug("Updating port: %s", port)
self._process_portbindings_create_and_update(context,
port['port'],
ret_port)
update_assigned_addresses = False
if addr_pair.ADDRESS_PAIRS in attrs:
update_assigned_addresses = self.update_address_pairs_on_port(
@ -2293,20 +2256,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._delete_dhcp_static_binding(context, neutron_db_port)
@staticmethod
@resource_extend.extends([port_def.COLLECTION_NAME])
def _extend_nsx_port_dict_binding(result, portdb):
result[pbin.VIF_TYPE] = nsx_constants.VIF_TYPE_DVS
port_attr = portdb.get('nsx_port_attributes')
if port_attr:
result[pbin.VNIC_TYPE] = port_attr.vnic_type
else:
result[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL
result[pbin.VIF_DETAILS] = {
# TODO(rkukura): Replace with new VIF security details
# security-groups extension supported by this plugin
pbin.CAP_PORT_FILTER: True}
def base_delete_subnet(self, context, subnet_id):
with locking.LockManager.get_lock('neutron-base-subnet'):
super(NsxVPluginV2, self).delete_subnet(context, subnet_id)