From 065ec89b913763233eefd8b268a113e58c90534b Mon Sep 17 00:00:00 2001 From: Roey Chen Date: Mon, 28 Aug 2017 09:40:20 -0700 Subject: [PATCH] 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 --- vmware_nsx/db/nsx_portbindings_db.py | 160 +++++++++++++++++++++++++++ vmware_nsx/db/nsxv_db.py | 9 ++ vmware_nsx/plugins/nsx_v/plugin.py | 87 +++------------ 3 files changed, 187 insertions(+), 69 deletions(-) create mode 100644 vmware_nsx/db/nsx_portbindings_db.py diff --git a/vmware_nsx/db/nsx_portbindings_db.py b/vmware_nsx/db/nsx_portbindings_db.py new file mode 100644 index 0000000000..645b0af92e --- /dev/null +++ b/vmware_nsx/db/nsx_portbindings_db.py @@ -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) diff --git a/vmware_nsx/db/nsxv_db.py b/vmware_nsx/db/nsxv_db.py index 05e4ec202e..ba7a2772b6 100644 --- a/vmware_nsx/db/nsxv_db.py +++ b/vmware_nsx/db/nsxv_db.py @@ -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: diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 5f842c10d1..f8ccfe876e 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -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)