# Copyright 2012 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 neutron_lib.api.definitions import allowedaddresspairs as addr_apidef from neutron_lib.api.definitions import port_security as psec from neutron_lib.db import utils as db_utils from neutron_lib.exceptions import allowedaddresspairs as addr_exc from neutron_lib.exceptions import port_security as psec_exc from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import uuidutils from neutron.api import extensions as neutron_extensions from neutron.db import _resource_extend as resource_extend from neutron.db import agentschedulers_db from neutron.db import allowedaddresspairs_db as addr_pair_db from neutron.db import api as db_api from neutron.db import dns_db from neutron.db import external_net_db from neutron.db import l3_db 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 securitygroups_db from neutron.db import vlantransparent_db as vlan_ext_db from neutron.extensions import providernet from neutron.extensions import securitygroup as ext_sg from neutron.quota import resource_registry from neutron_lib.api.definitions import multiprovidernet as mpnet_apidef 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 vlantransparent as vlan_apidef from neutron_lib.api import validators from neutron_lib import constants from neutron_lib import exceptions as n_exc from neutron_lib.plugins import utils import vmware_nsx from vmware_nsx._i18n import _ from vmware_nsx.common import config # noqa from vmware_nsx.common import managers as nsx_managers from vmware_nsx.common import nsx_constants from vmware_nsx.common import utils as c_utils from vmware_nsx.db import db as nsx_db from vmware_nsx.db import nsxv_db from vmware_nsx.dhcp_meta import modes as dhcpmeta_modes from vmware_nsx.dvs import dvs from vmware_nsx.dvs import dvs_utils from vmware_nsx.extensions import projectpluginmap from vmware_nsx.plugins.common import plugin as nsx_plugin_common from vmware_nsx.plugins.nsx import utils as tvd_utils LOG = logging.getLogger(__name__) @resource_extend.has_resource_extenders class NsxDvsV2(addr_pair_db.AllowedAddressPairsMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin, nsx_plugin_common.NsxPluginBase, dhcpmeta_modes.DhcpMetadataAccess, external_net_db.External_net_db_mixin, l3_db.L3_NAT_dbonly_mixin, portbindings_db.PortBindingMixin, portsecurity_db.PortSecurityDbMixin, securitygroups_db.SecurityGroupDbMixin, dns_db.DNSDbMixin, vlan_ext_db.Vlantransparent_db_mixin): supported_extension_aliases = ["allowed-address-pairs", "binding", "external-net", "multi-provider", "port-security", "provider", "quotas", "router", "security-group", "vlan-transparent"] __native_bulk_support = True __native_pagination_support = True __native_sorting_support = True @resource_registry.tracked_resources( network=models_v2.Network, port=models_v2.Port, subnet=models_v2.Subnet, subnetpool=models_v2.SubnetPool, security_group=securitygroup_model.SecurityGroup, security_group_rule=securitygroup_model.SecurityGroupRule) def __init__(self): self._is_sub_plugin = tvd_utils.is_tvd_core_plugin() dvs_utils.dvs_register_exceptions() super(NsxDvsV2, self).__init__() if self._is_sub_plugin: extension_drivers = cfg.CONF.nsx_tvd.dvs_extension_drivers else: extension_drivers = cfg.CONF.nsx_extension_drivers self._extension_manager = nsx_managers.ExtensionManager( extension_drivers=extension_drivers) LOG.debug('Driver support: DVS: %s' % dvs_utils.dvs_is_enabled()) self._extension_manager.initialize() self.supported_extension_aliases.extend( self._extension_manager.extension_aliases()) neutron_extensions.append_api_extensions_path( [vmware_nsx.NSX_EXT_PATH]) self.cfg_group = 'dvs' # group name for dvs section in nsx.ini self._dvs = dvs.SingleDvsManager() self.setup_dhcpmeta_access() @staticmethod def plugin_type(): return projectpluginmap.NsxPlugins.DVS @staticmethod def is_tvd_plugin(): return False @staticmethod def _extend_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 _extend_get_network_dict_provider(self, context, network, multiprovider=None, bindings=None): if not bindings: bindings = nsx_db.get_network_bindings(context.session, network['id']) if not multiprovider: multiprovider = nsx_db.is_multiprovider_network(context.session, network['id']) # With NSX plugin 'normal' overlay networks will have no binding # TODO(salvatore-orlando) make sure users can specify a distinct # phy_uuid as 'provider network' for STT net type if bindings: if not multiprovider: # network came in through provider networks api network[pnet.NETWORK_TYPE] = bindings[0].binding_type network[pnet.PHYSICAL_NETWORK] = bindings[0].phy_uuid network[pnet.SEGMENTATION_ID] = bindings[0].vlan_id else: # network come in though multiprovider networks api network[mpnet_apidef.SEGMENTS] = [ {pnet.NETWORK_TYPE: binding.binding_type, pnet.PHYSICAL_NETWORK: binding.phy_uuid, pnet.SEGMENTATION_ID: binding.vlan_id} for binding in bindings] def _dvs_get_id(self, net_data): if net_data['name'] == '': return net_data['id'] else: # Maximum name length is 80 characters. 'id' length is 36 # maximum prefix for name is 43 return '%s-%s' % (net_data['name'][:43], net_data['id']) def _dvs_create_network(self, context, network): net_data = network['network'] if net_data['admin_state_up'] is False: LOG.warning("Network with admin_state_up=False are not yet " "supported by this plugin. Ignoring setting for " "network %s", net_data.get('name', '')) net_data['id'] = uuidutils.generate_uuid() vlan_tag = 0 if net_data.get(pnet.NETWORK_TYPE) == c_utils.NetworkTypes.VLAN: vlan_tag = net_data.get(pnet.SEGMENTATION_ID, 0) trunk_mode = False # vlan transparent can be an object if not set. if net_data.get(vlan_apidef.VLANTRANSPARENT) is True: trunk_mode = True net_id = None if net_data.get(pnet.NETWORK_TYPE) == c_utils.NetworkTypes.PORTGROUP: net_id = net_data.get(pnet.PHYSICAL_NETWORK) pg_info = self._dvs.get_port_group_info(net_id) if pg_info.get('name') != net_data.get('name'): err_msg = (_("Portgroup name %(dvpg)s must match network " "name %(network)s") % {'dvpg': pg_info.get('name'), 'network': net_data.get('name')}) raise n_exc.InvalidInput(error_message=err_msg) dvpg_moref = self._dvs.net_id_to_moref(net_id) dvs_id = dvpg_moref.value else: dvs_id = self._dvs_get_id(net_data) try: self._dvs.add_port_group(dvs_id, vlan_tag, trunk_mode=trunk_mode) except dvs_utils.DvsOperationBulkFault: LOG.warning('One or more hosts may not be configured') try: with db_api.context_manager.writer.using(context): new_net = super(NsxDvsV2, self).create_network(context, network) self._extension_manager.process_create_network( context, net_data, new_net) # Process port security extension self._process_network_port_security_create( context, net_data, new_net) # Process vlan transparent extension net_db = self._get_network(context, new_net['id']) net_db['vlan_transparent'] = trunk_mode net_data['vlan_transparent'] = trunk_mode resource_extend.apply_funcs('networks', net_data, net_db) nsx_db.add_network_binding( context.session, new_net['id'], net_data.get(pnet.NETWORK_TYPE), net_id or 'dvs', vlan_tag) except Exception: with excutils.save_and_reraise_exception(): LOG.exception('Failed to create network') if (net_data.get(pnet.NETWORK_TYPE) != c_utils.NetworkTypes.PORTGROUP): self._dvs.delete_port_group(dvs_id) new_net[pnet.NETWORK_TYPE] = net_data.get(pnet.NETWORK_TYPE) new_net[pnet.PHYSICAL_NETWORK] = net_id or 'dvs' new_net[pnet.SEGMENTATION_ID] = vlan_tag # this extra lookup is necessary to get the # latest db model for the extension functions net_model = self._get_network(context, net_data['id']) resource_extend.apply_funcs('networks', new_net, net_model) self.handle_network_dhcp_access(context, new_net, action='create_network') return new_net def _validate_network(self, context, net_data): network_type = net_data.get(pnet.NETWORK_TYPE) segmentation_id = net_data.get(pnet.SEGMENTATION_ID) segmentation_id_set = validators.is_attr_set(segmentation_id) if not context.is_admin: err_msg = _("Only an admin can create a DVS provider " "network") raise n_exc.InvalidInput(error_message=err_msg) err_msg = None if (network_type == c_utils.NetworkTypes.FLAT or network_type == c_utils.NetworkTypes.PORTGROUP): if segmentation_id_set: err_msg = (_("Segmentation ID cannot be specified with " "%s network type"), network_type) elif network_type == c_utils.NetworkTypes.VLAN: if not segmentation_id_set: err_msg = _("Segmentation ID must be specified with " "vlan network type") if (segmentation_id_set and not utils.is_valid_vlan_tag(segmentation_id)): err_msg = (_("%(segmentation_id)s out of range " "(%(min_id)s through %(max_id)s)") % {'segmentation_id': segmentation_id, 'min_id': constants.MIN_VLAN_TAG, 'max_id': constants.MAX_VLAN_TAG}) else: err_msg = (_("%(net_type_param)s %(net_type_value)s not " "supported") % {'net_type_param': pnet.NETWORK_TYPE, 'net_type_value': network_type}) if err_msg: raise n_exc.InvalidInput(error_message=err_msg) def create_network(self, context, network): self._validate_network(context, network['network']) return self._dvs_create_network(context, network) def _dvs_delete_network(self, context, id): network = self._get_network(context, id) dvs_id = self._dvs_get_id(network) bindings = nsx_db.get_network_bindings(context.session, id) with db_api.context_manager.writer.using(context): nsx_db.delete_network_bindings(context.session, id) super(NsxDvsV2, self).delete_network(context, id) try: if (not bindings or bindings[0].binding_type != c_utils.NetworkTypes.PORTGROUP): self._dvs.delete_port_group(dvs_id) except Exception: LOG.exception('Unable to delete DVS port group %s', id) self.handle_network_dhcp_access(context, id, action='delete_network') def delete_network(self, context, id): self._dvs_delete_network(context, id) def _dvs_get_network(self, context, id, fields=None): with db_api.context_manager.reader.using(context): # goto to the plugin DB and fetch the network network = self._get_network(context, id) # Don't do field selection here otherwise we won't be able # to add provider networks fields net_result = self._make_network_dict(network, context=context) self._extend_get_network_dict_provider(context, net_result) return db_utils.resource_fields(net_result, fields) def get_network(self, context, id, fields=None): return self._dvs_get_network(context, id, fields=None) def get_networks(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): filters = filters or {} with db_api.context_manager.reader.using(context): networks = ( super(NsxDvsV2, self).get_networks( context, filters, fields, sorts, limit, marker, page_reverse)) for net in networks: self._extend_get_network_dict_provider(context, net) return (networks if not fields else [db_utils.resource_fields(network, fields) for network in networks]) def update_network(self, context, id, network): net_attrs = network['network'] providernet._raise_if_updates_provider_attributes(net_attrs) with db_api.context_manager.writer.using(context): net_res = super(NsxDvsV2, self).update_network(context, id, network) self._extension_manager.process_update_network(context, net_attrs, net_res) # Process port security extension self._process_network_port_security_update( context, net_attrs, net_res) self._extend_get_network_dict_provider(context, net_res) return net_res def _process_vnic_type(self, context, port_data, port_id): vnic_type = port_data.get(pbin.VNIC_TYPE) if validators.is_attr_set(vnic_type): if (vnic_type != pbin.VNIC_NORMAL and vnic_type != pbin.VNIC_DIRECT and vnic_type != pbin.VNIC_DIRECT_PHYSICAL): err_msg = _("Only direct, direct-physical and normal VNIC " "types supported") raise n_exc.InvalidInput(error_message=err_msg) nsxv_db.update_nsxv_port_ext_attributes( session=context.session, port_id=port_id, vnic_type=vnic_type) def create_port(self, context, port): # If PORTSECURITY is not the default value ATTR_NOT_SPECIFIED # then we pass the port to the policy engine. The reason why we don't # pass the value to the policy engine when the port is # ATTR_NOT_SPECIFIED is for the case where a port is created on a # shared network that is not owned by the tenant. port_data = port['port'] with db_api.context_manager.writer.using(context): # First we allocate port in neutron database neutron_db = super(NsxDvsV2, self).create_port(context, port) self._extension_manager.process_create_port( context, port_data, neutron_db) port_security = self._get_network_security_binding( context, neutron_db['network_id']) port_data[psec.PORTSECURITY] = port_security self._process_port_port_security_create( 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) # security group extension checks if has_ip: self._ensure_default_security_group_on_port(context, port) elif validators.is_attr_set(port_data.get(ext_sg.SECURITYGROUPS)): raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups() port_data[ext_sg.SECURITYGROUPS] = ( self._get_security_groups_on_port(context, port)) self._process_port_create_security_group( context, port_data, port_data[ext_sg.SECURITYGROUPS]) self._process_portbindings_create_and_update(context, port['port'], port_data) # allowed address pair checks if validators.is_attr_set(port_data.get( addr_apidef.ADDRESS_PAIRS)): if not port_security: raise addr_exc.AddressPairAndPortSecurityRequired() else: self._process_create_allowed_address_pairs( context, neutron_db, port_data[addr_apidef.ADDRESS_PAIRS]) else: # remove ATTR_NOT_SPECIFIED port_data[addr_apidef.ADDRESS_PAIRS] = [] self._process_portbindings_create_and_update(context, port['port'], port_data) self._process_vnic_type(context, port_data, neutron_db['id']) LOG.debug("create_port completed on NSX for tenant " "%(tenant_id)s: (%(id)s)", port_data) # DB Operation is complete, perform DVS operation port_data = port['port'] # this extra lookup is necessary to get the # latest db model for the extension functions port_model = self._get_port(context, port_data['id']) resource_extend.apply_funcs('ports', port_data, port_model) self._extend_port_dict_binding(port_data, port_model) self.handle_port_dhcp_access(context, port_data, action='create_port') return port_data def update_port(self, context, id, port): delete_addr_pairs = self._check_update_deletes_allowed_address_pairs( port) has_addr_pairs = self._check_update_has_allowed_address_pairs(port) with db_api.context_manager.writer.using(context): ret_port = super(NsxDvsV2, self).update_port( context, id, port) # Save current mac learning state to check whether it's # being updated or not # copy values over - except fixed_ips as # they've already been processed port['port'].pop('fixed_ips', None) ret_port.update(port['port']) # populate port_security setting if psec.PORTSECURITY not in port['port']: ret_port[psec.PORTSECURITY] = self._get_port_security_binding( context, id) # validate port security and allowed address pairs if not ret_port[psec.PORTSECURITY]: # has address pairs in request if has_addr_pairs: raise addr_exc.AddressPairAndPortSecurityRequired() elif not delete_addr_pairs: # check if address pairs are in db ret_port[addr_apidef.ADDRESS_PAIRS] = ( self.get_allowed_address_pairs(context, id)) if ret_port[addr_apidef.ADDRESS_PAIRS]: raise addr_exc.AddressPairAndPortSecurityRequired() if delete_addr_pairs or has_addr_pairs: # delete address pairs and read them in self._delete_allowed_address_pairs(context, id) self._process_create_allowed_address_pairs( context, ret_port, ret_port[addr_apidef.ADDRESS_PAIRS]) if psec.PORTSECURITY in port['port']: self._process_port_port_security_update( context, port['port'], ret_port) self._process_vnic_type(context, port['port'], id) LOG.debug("Updating port: %s", port) self._extension_manager.process_update_port( context, port['port'], ret_port) self._process_portbindings_create_and_update(context, port['port'], ret_port) return ret_port def delete_port(self, context, id, l3_port_check=True, nw_gw_port_check=True): """Deletes a port on a specified Virtual Network. If the port contains a remote interface attachment, the remote interface is first un-plugged and then the port is deleted. :returns: None :raises: exception.PortInUse :raises: exception.PortNotFound :raises: exception.NetworkNotFound """ neutron_db_port = self.get_port(context, id) with db_api.context_manager.writer.using(context): # metadata_dhcp_host_route self.handle_port_metadata_access( context, neutron_db_port, is_delete=True) super(NsxDvsV2, self).delete_port(context, id) self.handle_port_dhcp_access( context, neutron_db_port, action='delete_port') def get_ports(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): filters = filters or {} with db_api.context_manager.reader.using(context): ports = ( super(NsxDvsV2, self).get_ports( context, filters, fields, sorts, limit, marker, page_reverse)) # Add port extensions for port in ports: if 'id' in port: port_model = self._get_port(context, port['id']) resource_extend.apply_funcs('ports', port, port_model) self._extend_port_dict_binding(port, port_model) return (ports if not fields else [db_utils.resource_fields(port, fields) for port in ports]) def get_port(self, context, id, fields=None): port = super(NsxDvsV2, self).get_port(context, id, fields=None) if 'id' in port: port_model = self._get_port(context, port['id']) resource_extend.apply_funcs('ports', port, port_model) self._extend_port_dict_binding(port, port_model) else: port[pbin.VIF_TYPE] = nsx_constants.VIF_TYPE_DVS return db_utils.resource_fields(port, fields) def create_router(self, context, router): # DVS backend cannot support logical router msg = (_("Unable to create router %s with DVS") % router['router']['name']) raise n_exc.BadRequest(resource="router", msg=msg) def get_network_availability_zones(self, net_db): """Api to comply with the NSX-TVD plugin""" return []