vmware-nsx/vmware_nsx/plugins/dvs/plugin.py
Shih-Hao Li 4632986463 Add metadata proxy support in NSX/T plugin
This will provide metadata access for
(1) instances created on a isolated DHCP-enabled subnet,
(2) instances created on a DHCP-disabled subnet connected with a router.

Closes-bug: #1524473
Related-Bug: #1526084
Change-Id: I62c41c97ddd7e51ea554f96e9e4c23f1b2c8f17f
2015-12-15 18:30:01 -08:00

380 lines
17 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.
import uuid
from oslo_log import log as logging
from oslo_utils import excutils
from neutron.api import extensions as neutron_extensions
from neutron.api.v2 import attributes as attr
from neutron.common import exceptions as n_exc
from neutron.db import agentschedulers_db
from neutron.db import allowedaddresspairs_db as addr_pair_db
from neutron.db import db_base_plugin_v2
from neutron.db import external_net_db
from neutron.db import l3_db
from neutron.db import portbindings_db
from neutron.db import portsecurity_db
from neutron.db import securitygroups_db
from neutron.extensions import allowedaddresspairs as addr_pair
from neutron.extensions import multiprovidernet as mpnet
from neutron.extensions import portbindings as pbin
from neutron.extensions import portsecurity as psec
from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as ext_sg
from neutron.plugins.common import constants
from neutron.plugins.common import utils
import vmware_nsx
from vmware_nsx._i18n import _, _LE, _LW
from vmware_nsx.common import config # noqa
from vmware_nsx.common import exceptions as nsx_exc
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.dhcp_meta import modes as dhcpmeta_modes
from vmware_nsx.dvs import dvs
from vmware_nsx.dvs import dvs_utils
LOG = logging.getLogger(__name__)
class NsxDvsV2(addr_pair_db.AllowedAddressPairsMixin,
agentschedulers_db.DhcpAgentSchedulerDbMixin,
db_base_plugin_v2.NeutronDbPluginV2,
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):
supported_extension_aliases = ["allowed-address-pairs",
"binding",
"external-net",
"multi-provider",
"port-security",
"provider",
"quotas",
"router",
"security-group"]
__native_bulk_support = True
__native_pagination_support = True
__native_sorting_support = True
def __init__(self):
super(NsxDvsV2, self).__init__()
LOG.debug('Driver support: DVS: %s' % dvs_utils.dvs_is_enabled())
neutron_extensions.append_api_extensions_path(
[vmware_nsx.NSX_EXT_PATH])
self._dvs = dvs.DvsManager()
# Common driver code
self.base_binding_dict = {
pbin.VIF_TYPE: nsx_constants.VIF_TYPE_DVS,
pbin.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
pbin.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}
self.setup_dhcpmeta_access()
def _extend_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.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(_LW("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'] = str(uuid.uuid4())
vlan_tag = 0
if net_data.get(pnet.NETWORK_TYPE) == c_utils.NetworkTypes.VLAN:
vlan_tag = net_data.get(pnet.SEGMENTATION_ID, 0)
dvs_id = self._dvs_get_id(net_data)
self._dvs.add_port_group(dvs_id, vlan_tag)
try:
with context.session.begin(subtransactions=True):
new_net = super(NsxDvsV2, self).create_network(context,
network)
# Process port security extension
self._process_network_port_security_create(
context, net_data, new_net)
nsx_db.add_network_binding(
context.session, new_net['id'],
net_data.get(pnet.NETWORK_TYPE),
'dvs',
vlan_tag)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Failed to create network'))
self._dvs.delete_port_group(dvs_id)
new_net[pnet.NETWORK_TYPE] = net_data.get(pnet.NETWORK_TYPE)
new_net[pnet.PHYSICAL_NETWORK] = 'dvs'
new_net[pnet.SEGMENTATION_ID] = vlan_tag
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 = attr.is_attr_set(segmentation_id)
if not context.is_admin:
err_msg = _("Only and 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:
if segmentation_id_set:
err_msg = _("Segmentation ID cannot be specified with "
"flat 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")
elif (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)
with context.session.begin(subtransactions=True):
nsx_db.delete_network_bindings(context.session, id)
super(NsxDvsV2, self).delete_network(context, id)
try:
self._dvs.delete_port_group(dvs_id)
except Exception:
LOG.exception(_LE('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 context.session.begin(subtransactions=True):
# 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_network_dict_provider(context, net_result)
return self._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 context.session.begin(subtransactions=True):
networks = (
super(NsxDvsV2, self).get_networks(
context, filters, fields, sorts,
limit, marker, page_reverse))
for net in networks:
self._extend_network_dict_provider(context, net)
return [self._fields(network, fields) for network in networks]
def update_network(self, context, id, network):
raise nsx_exc.NsxPluginException(
err_msg=_("Unable to update DVS network"))
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 context.session.begin(subtransactions=True):
# First we allocate port in neutron database
neutron_db = super(NsxDvsV2, self).create_port(context, port)
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 attr.is_attr_set(port_data.get(ext_sg.SECURITYGROUPS)):
raise psec.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 attr.is_attr_set(port_data.get(addr_pair.ADDRESS_PAIRS)):
if not port_security:
raise addr_pair.AddressPairAndPortSecurityRequired()
else:
self._process_create_allowed_address_pairs(
context, neutron_db,
port_data[addr_pair.ADDRESS_PAIRS])
else:
# remove ATTR_NOT_SPECIFIED
port_data[addr_pair.ADDRESS_PAIRS] = []
LOG.debug("create_port completed on NSX for tenant "
"%(tenant_id)s: (%(id)s)", port_data)
self._process_portbindings_create_and_update(context,
port['port'],
port_data)
# DB Operation is complete, perform DVS operation
port_data = port['port']
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 context.session.begin(subtransactions=True):
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_pair.AddressPairAndPortSecurityRequired()
elif not delete_addr_pairs:
# check if address pairs are in db
ret_port[addr_pair.ADDRESS_PAIRS] = (
self.get_allowed_address_pairs(context, id))
if ret_port[addr_pair.ADDRESS_PAIRS]:
raise addr_pair.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_pair.ADDRESS_PAIRS])
if psec.PORTSECURITY in port['port']:
self._process_port_port_security_update(
context, port['port'], ret_port)
LOG.debug("Updating port: %s", 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 context.session.begin(subtransactions=True):
# 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 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)