vmware-nsx/vmware_nsx/plugins/dvs/plugin.py

573 lines
26 KiB
Python

# 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 agentschedulers_db
from neutron.db import allowedaddresspairs_db as addr_pair_db
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.db import api as db_api
from neutron_lib.db import resource_extend
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 _add_port_group(self, dvs_id, net_data, vlan_tag, trunk_mode):
if validators.is_attr_set(net_data.get(pnet.PHYSICAL_NETWORK)):
dvs_name = net_data.get(pnet.PHYSICAL_NETWORK)
dvs_moref = self._dvs.dvs.get_dvs_moref_by_name(dvs_name)
self._dvs.dvs.add_port_group(dvs_moref, dvs_id, vlan_tag,
trunk_mode=trunk_mode)
else:
dvs_name = dvs_utils.dvs_name_get()
self._dvs.add_port_group(dvs_id, vlan_tag,
trunk_mode=trunk_mode)
return dvs_name
def _get_portgroup_info(self, net_id):
pg_info, dvpg_moref = self._dvs.dvs.get_port_group_info(None, net_id)
return pg_info, dvpg_moref
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', '<unknown>'))
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 = dvs_name = None
if net_data.get(pnet.NETWORK_TYPE) == c_utils.NetworkTypes.PORTGROUP:
net_id = net_data.get(pnet.PHYSICAL_NETWORK)
pg_info, dvpg_moref = self._get_portgroup_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)
dvs_id = dvpg_moref.value
else:
dvs_id = self._dvs_get_id(net_data)
try:
dvs_name = self._add_port_group(dvs_id, net_data, 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_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_name,
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._delete_port_group(dvs_id, dvs_name)
new_net[pnet.NETWORK_TYPE] = net_data.get(pnet.NETWORK_TYPE)
new_net[pnet.PHYSICAL_NETWORK] = net_id or dvs_name
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)
network_type_set = validators.is_attr_set(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 not network_type_set:
err_msg = _("Network provider information must be "
"specified")
raise n_exc.InvalidInput(error_message=err_msg)
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 _delete_port_group(self, dvs_id, dvs_name):
if dvs_name == dvs_utils.dvs_name_get():
self._dvs.delete_port_group(dvs_id)
else:
dvs_moref = self._dvs.dvs.get_dvs_moref_by_name(dvs_name)
self._dvs.dvs.delete_port_group(dvs_moref, dvs_id)
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_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):
dvs_name = bindings[0].phy_uuid
self._delete_port_group(dvs_id, dvs_name)
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_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_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_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_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_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_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_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 []