vmware-nsx/vmware_nsx/plugins/nsx_mh/plugin.py
Boden R 4d60df9910 use extra_dhcp_opt api-def from neutron-lib
neutron-lib 1.6.0 is out and among other things contains the rehomed
extra_dhcp_opt API definition. This patch switches usage of such
references from neutron to neutron-lib.

Change-Id: I01736dd067b962689a2fe81903a12099dd638e82
2017-05-15 09:11:28 +00:00

2543 lines
128 KiB
Python

#m 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 neutron_lib.api.definitions import port_security as psec
from neutron_lib.api import validators
from neutron_lib import constants
from neutron_lib import context as q_context
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import port_security as psec_exc
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import excutils
import six
from sqlalchemy import exc as sql_exc
from sqlalchemy.orm import exc as sa_exc
import webob.exc
from neutron.api import extensions as neutron_extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import base
from neutron.db import _model_query as model_query
from neutron.db import _resource_extend as resource_extend
from neutron.db import _utils as db_utils
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 db_base_plugin_v2
from neutron.db import dns_db
from neutron.db import external_net_db
from neutron.db import extradhcpopt_db
from neutron.db import extraroute_db
from neutron.db import l3_attrs_db
from neutron.db import l3_db
from neutron.db import l3_dvr_db
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
from neutron.extensions import allowedaddresspairs as addr_pair
from neutron.extensions import external_net as ext_net_extn
from neutron.extensions import extraroute
from neutron.extensions import l3
from neutron.extensions import multiprovidernet as mpnet
from neutron.extensions import providernet
from neutron.extensions import securitygroup as ext_sg
from neutron.plugins.common import utils
from neutron.quota import resource_registry
from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext
from neutron_lib.api.definitions import portbindings as pbin
from neutron_lib.api.definitions import provider_net as pnet
import vmware_nsx
from vmware_nsx._i18n import _
from vmware_nsx.api_client import exception as api_exc
from vmware_nsx.common import config # noqa
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import nsx_utils
from vmware_nsx.common import securitygroups as sg_utils
from vmware_nsx.common import sync
from vmware_nsx.common import utils as c_utils
from vmware_nsx.db import db as nsx_db
from vmware_nsx.db import maclearning as mac_db
from vmware_nsx.db import networkgw_db
from vmware_nsx.db import nsx_models
from vmware_nsx.db import qos_db
from vmware_nsx.dhcp_meta import modes as dhcpmeta_modes
from vmware_nsx.extensions import maclearning as mac_ext
from vmware_nsx.extensions import networkgw
from vmware_nsx.extensions import qos_queue as qos
from vmware_nsx.nsxlib.mh import l2gateway as l2gwlib
from vmware_nsx.nsxlib.mh import queue as queuelib
from vmware_nsx.nsxlib.mh import router as routerlib
from vmware_nsx.nsxlib.mh import secgroup as secgrouplib
from vmware_nsx.nsxlib.mh import switch as switchlib
LOG = logging.getLogger(__name__)
NSX_NOSNAT_RULES_ORDER = 10
NSX_FLOATINGIP_NAT_RULES_ORDER = 224
NSX_EXTGW_NAT_RULES_ORDER = 255
NSX_DEFAULT_NEXTHOP = '1.1.1.1'
class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
agentschedulers_db.DhcpAgentSchedulerDbMixin,
db_base_plugin_v2.NeutronDbPluginV2,
dhcpmeta_modes.DhcpMetadataAccess,
l3_dvr_db.L3_NAT_with_dvr_db_mixin,
external_net_db.External_net_db_mixin,
extradhcpopt_db.ExtraDhcpOptMixin,
extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
mac_db.MacLearningDbMixin,
networkgw_db.NetworkGatewayMixin,
portbindings_db.PortBindingMixin,
portsecurity_db.PortSecurityDbMixin,
qos_db.QoSDbMixin,
securitygroups_db.SecurityGroupDbMixin,
dns_db.DNSDbMixin):
supported_extension_aliases = ["allowed-address-pairs",
"binding",
"dvr",
"ext-gw-mode",
"extraroute",
"mac-learning",
"multi-provider",
"network-gateway",
"port-security",
"provider",
"qos-queue",
"quotas",
"external-net",
"extra_dhcp_opt",
"router",
"security-group",
constants.SUBNET_ALLOCATION_EXT_ALIAS]
__native_bulk_support = True
__native_pagination_support = True
__native_sorting_support = True
# Map nova zones to cluster for easy retrieval
novazone_cluster_map = {}
@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,
router=l3_db_models.Router,
floatingip=l3_db_models.FloatingIP)
def __init__(self):
LOG.warning("The NSX-MH plugin is deprecated and may be removed "
"in the O or the P cycle")
super(NsxPluginV2, self).__init__()
# TODO(salv-orlando): Replace These dicts with
# collections.defaultdict for better handling of default values
# Routines for managing logical ports in NSX
self.port_special_owners = [l3_db.DEVICE_OWNER_ROUTER_GW,
l3_db.DEVICE_OWNER_ROUTER_INTF]
self._port_drivers = {
'create': {constants.DEVICE_OWNER_ROUTER_GW:
self._nsx_create_ext_gw_port,
constants.DEVICE_OWNER_FLOATINGIP:
self._nsx_create_fip_port,
constants.DEVICE_OWNER_ROUTER_INTF:
self._nsx_create_router_port,
constants.DEVICE_OWNER_DVR_INTERFACE:
self._nsx_create_router_port,
networkgw_db.DEVICE_OWNER_NET_GW_INTF:
self._nsx_create_l2_gw_port,
'default': self._nsx_create_port},
'delete': {constants.DEVICE_OWNER_ROUTER_GW:
self._nsx_delete_ext_gw_port,
constants.DEVICE_OWNER_ROUTER_INTF:
self._nsx_delete_router_port,
constants.DEVICE_OWNER_DVR_INTERFACE:
self._nsx_delete_router_port,
constants.DEVICE_OWNER_FLOATINGIP:
self._nsx_delete_fip_port,
networkgw_db.DEVICE_OWNER_NET_GW_INTF:
self._nsx_delete_port,
'default': self._nsx_delete_port}
}
neutron_extensions.append_api_extensions_path(
[vmware_nsx.NSX_EXT_PATH])
self.cfg_group = 'NSX' # group name for nsx section in nsx.ini
self.nsx_opts = cfg.CONF.NSX
self.nsx_sync_opts = cfg.CONF.NSX_SYNC
self.cluster = nsx_utils.create_nsx_cluster(
cfg.CONF,
self.nsx_opts.concurrent_connections,
self.nsx_opts.nsx_gen_timeout)
self.base_binding_dict = {
pbin.VIF_TYPE: pbin.VIF_TYPE_OVS,
pbin.VIF_DETAILS: {
# TODO(rkukura): Replace with new VIF security details
pbin.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}}
self._extend_fault_map()
self.setup_dhcpmeta_access()
# Set this flag to false as the default gateway has not
# been yet updated from the config file
self._is_default_net_gw_in_sync = False
# Create a synchronizer instance for backend sync
self._synchronizer = sync.NsxSynchronizer(
self.safe_reference, self.cluster,
self.nsx_sync_opts.state_sync_interval,
self.nsx_sync_opts.min_sync_req_delay,
self.nsx_sync_opts.min_chunk_size,
self.nsx_sync_opts.max_random_sync_delay)
def _ensure_default_network_gateway(self):
if self._is_default_net_gw_in_sync:
return
# Add the gw in the db as default, and unset any previous default
def_l2_gw_uuid = self.cluster.default_l2_gw_service_uuid
try:
ctx = q_context.get_admin_context()
self._unset_default_network_gateways(ctx)
if not def_l2_gw_uuid:
return
try:
def_network_gw = self._get_network_gateway(ctx,
def_l2_gw_uuid)
except networkgw_db.GatewayNotFound:
# Create in DB only - don't go to backend
def_gw_data = {'id': def_l2_gw_uuid,
'name': 'default L2 gateway service',
'devices': [],
'tenant_id': ctx.tenant_id}
gw_res_name = networkgw.GATEWAY_RESOURCE_NAME.replace('-', '_')
def_network_gw = super(
NsxPluginV2, self).create_network_gateway(
ctx, {gw_res_name: def_gw_data})
# In any case set is as default
self._set_default_network_gateway(ctx, def_network_gw['id'])
# Ensure this method is executed only once
self._is_default_net_gw_in_sync = True
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception("Unable to process default l2 gw service: "
"%s",
def_l2_gw_uuid)
def _build_ip_address_list(self, context, fixed_ips, subnet_ids=None):
"""Build ip_addresses data structure for logical router port.
No need to perform validation on IPs - this has already been
done in the l3_db mixin class.
"""
ip_addresses = []
for ip in fixed_ips:
if not subnet_ids or (ip['subnet_id'] in subnet_ids):
subnet = self._get_subnet(context, ip['subnet_id'])
ip_prefix = '%s/%s' % (ip['ip_address'],
subnet['cidr'].split('/')[1])
ip_addresses.append(ip_prefix)
return ip_addresses
def _create_and_attach_router_port(self, cluster, context,
nsx_router_id, port_data,
attachment_type, attachment,
attachment_vlan=None,
subnet_ids=None):
# Use a fake IP address if gateway port is not 'real'
ip_addresses = (port_data.get('fake_ext_gw') and
['0.0.0.0/31'] or
self._build_ip_address_list(context,
port_data['fixed_ips'],
subnet_ids))
try:
lrouter_port = routerlib.create_router_lport(
cluster, nsx_router_id, port_data.get('tenant_id', 'fake'),
port_data.get('id', 'fake'), port_data.get('name', 'fake'),
port_data.get('admin_state_up', True), ip_addresses,
port_data.get('mac_address'))
LOG.debug("Created NSX router port:%s", lrouter_port['uuid'])
except api_exc.NsxApiException:
LOG.exception("Unable to create port on NSX logical router "
"%s",
nsx_router_id)
raise nsx_exc.NsxPluginException(
err_msg=_("Unable to create logical router port for neutron "
"port id %(port_id)s on router %(nsx_router_id)s") %
{'port_id': port_data.get('id'),
'nsx_router_id': nsx_router_id})
self._update_router_port_attachment(cluster, context, nsx_router_id,
port_data, lrouter_port['uuid'],
attachment_type, attachment,
attachment_vlan)
return lrouter_port
def _update_router_gw_info(self, context, router_id, info):
# NOTE(salvatore-orlando): We need to worry about rollback of NSX
# configuration in case of failures in the process
# Ref. LP bug 1102301
router = self._get_router(context, router_id)
# Check whether SNAT rule update should be triggered
# NSX also supports multiple external networks so there is also
# the possibility that NAT rules should be replaced
current_ext_net_id = router.gw_port_id and router.gw_port.network_id
new_ext_net_id = info and info.get('network_id')
# SNAT should be enabled unless info['enable_snat'] is
# explicitly set to false
enable_snat = new_ext_net_id and info.get('enable_snat', True)
# Remove if ext net removed, changed, or if snat disabled
remove_snat_rules = (current_ext_net_id and
new_ext_net_id != current_ext_net_id or
router.enable_snat and not enable_snat)
# Add rules if snat is enabled, and if either the external network
# changed or snat was previously disabled
# NOTE: enable_snat == True implies new_ext_net_id != None
add_snat_rules = (enable_snat and
(new_ext_net_id != current_ext_net_id or
not router.enable_snat))
router = super(NsxPluginV2, self)._update_router_gw_info(
context, router_id, info, router=router)
# Add/Remove SNAT rules as needed
# Create an elevated context for dealing with metadata access
# cidrs which are created within admin context
ctx_elevated = context.elevated()
if remove_snat_rules or add_snat_rules:
cidrs = self._find_router_subnets_cidrs(ctx_elevated, router_id)
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
if remove_snat_rules:
# Be safe and concede NAT rules might not exist.
# Therefore, use min_num_expected=0
for cidr in cidrs:
routerlib.delete_nat_rules_by_match(
self.cluster, nsx_router_id, "SourceNatRule",
max_num_expected=1, min_num_expected=0,
raise_on_len_mismatch=False,
source_ip_addresses=cidr)
if add_snat_rules:
ip_addresses = self._build_ip_address_list(
ctx_elevated, router.gw_port['fixed_ips'])
# Set the SNAT rule for each subnet (only first IP)
for cidr in cidrs:
cidr_prefix = int(cidr.split('/')[1])
routerlib.create_lrouter_snat_rule(
self.cluster, nsx_router_id,
ip_addresses[0].split('/')[0],
ip_addresses[0].split('/')[0],
order=NSX_EXTGW_NAT_RULES_ORDER - cidr_prefix,
match_criteria={'source_ip_addresses': cidr})
def _update_router_port_attachment(self, cluster, context,
nsx_router_id, port_data,
nsx_router_port_id,
attachment_type,
attachment,
attachment_vlan=None):
if not nsx_router_port_id:
nsx_router_port_id = self._find_router_gw_port(context, port_data)
try:
routerlib.plug_router_port_attachment(cluster, nsx_router_id,
nsx_router_port_id,
attachment,
attachment_type,
attachment_vlan)
LOG.debug("Attached %(att)s to NSX router port %(port)s",
{'att': attachment, 'port': nsx_router_port_id})
except api_exc.NsxApiException:
# Must remove NSX logical port
routerlib.delete_router_lport(cluster, nsx_router_id,
nsx_router_port_id)
LOG.exception("Unable to plug attachment in NSX logical "
"router port %(r_port_id)s, associated with "
"Neutron %(q_port_id)s",
{'r_port_id': nsx_router_port_id,
'q_port_id': port_data.get('id')})
raise nsx_exc.NsxPluginException(
err_msg=(_("Unable to plug attachment in router port "
"%(r_port_id)s for neutron port id %(q_port_id)s "
"on router %(router_id)s") %
{'r_port_id': nsx_router_port_id,
'q_port_id': port_data.get('id'),
'router_id': nsx_router_id}))
def _get_port_by_device_id(self, context, device_id, device_owner):
"""Retrieve ports associated with a specific device id.
Used for retrieving all neutron ports attached to a given router.
"""
port_qry = context.session.query(models_v2.Port)
return port_qry.filter_by(
device_id=device_id,
device_owner=device_owner,).all()
def _find_router_subnets_cidrs(self, context, router_id):
"""Retrieve subnets attached to the specified router."""
ports = self._get_port_by_device_id(context, router_id,
l3_db.DEVICE_OWNER_ROUTER_INTF)
# No need to check for overlapping CIDRs
cidrs = []
for port in ports:
for ip in port.get('fixed_ips', []):
cidrs.append(self._get_subnet(context,
ip.subnet_id).cidr)
return cidrs
def _nsx_find_lswitch_for_port(self, context, port_data):
network = self._get_network(context, port_data['network_id'])
return self._handle_lswitch_selection(
context, self.cluster, network)
def _nsx_create_port_helper(self, session, ls_uuid, port_data,
do_port_security=True):
# Convert Neutron security groups identifiers into NSX security
# profiles identifiers
nsx_sec_profile_ids = [
nsx_utils.get_nsx_security_group_id(
session, self.cluster, neutron_sg_id) for
neutron_sg_id in (port_data[ext_sg.SECURITYGROUPS] or [])]
return switchlib.create_lport(self.cluster,
ls_uuid,
port_data['tenant_id'],
port_data['id'],
port_data['name'],
port_data['device_id'],
port_data['admin_state_up'],
port_data['mac_address'],
port_data['fixed_ips'],
port_data[psec.PORTSECURITY],
nsx_sec_profile_ids,
port_data.get(qos.QUEUE),
port_data.get(mac_ext.MAC_LEARNING),
port_data.get(addr_pair.ADDRESS_PAIRS))
def _handle_create_port_exception(self, context, port_id,
ls_uuid, lp_uuid):
with excutils.save_and_reraise_exception():
# rollback nsx logical port only if it was successfully
# created on NSX. Should this command fail the original
# exception will be raised.
if lp_uuid:
# Remove orphaned port from NSX
switchlib.delete_port(self.cluster, ls_uuid, lp_uuid)
# rollback the neutron-nsx port mapping
nsx_db.delete_neutron_nsx_port_mapping(context.session,
port_id)
LOG.exception("An exception occurred while creating the "
"neutron port %s on the NSX plaform", port_id)
def _nsx_create_port(self, context, port_data):
"""Driver for creating a logical switch port on NSX platform."""
# FIXME(salvatore-orlando): On the NSX platform we do not really have
# external networks. So if as user tries and create a "regular" VIF
# port on an external network we are unable to actually create.
# However, in order to not break unit tests, we need to still create
# the DB object and return success
if self._network_is_external(context, port_data['network_id']):
LOG.info("NSX plugin does not support regular VIF ports on "
"external networks. Port %s will be down.",
port_data['network_id'])
# No need to actually update the DB state - the default is down
return port_data
lport = None
selected_lswitch = None
try:
selected_lswitch = self._nsx_find_lswitch_for_port(context,
port_data)
lport = self._nsx_create_port_helper(context.session,
selected_lswitch['uuid'],
port_data,
True)
nsx_db.add_neutron_nsx_port_mapping(
context.session, port_data['id'],
selected_lswitch['uuid'], lport['uuid'])
if port_data['device_owner'] not in self.port_special_owners:
switchlib.plug_vif_interface(
self.cluster, selected_lswitch['uuid'],
lport['uuid'], "VifAttachment", port_data['id'])
LOG.debug("_nsx_create_port completed for port %(name)s "
"on network %(network_id)s. The new port id is "
"%(id)s.", port_data)
except (api_exc.NsxApiException, n_exc.NeutronException):
self._handle_create_port_exception(
context, port_data['id'],
selected_lswitch and selected_lswitch['uuid'],
lport and lport['uuid'])
except db_exc.DBError as e:
if (port_data['device_owner'] == constants.DEVICE_OWNER_DHCP and
isinstance(e.inner_exception, sql_exc.IntegrityError)):
LOG.warning(
"Concurrent network deletion detected; Back-end "
"Port %(nsx_id)s creation to be rolled back for "
"Neutron port: %(neutron_id)s",
{'nsx_id': lport['uuid'],
'neutron_id': port_data['id']})
if selected_lswitch and lport:
try:
switchlib.delete_port(self.cluster,
selected_lswitch['uuid'],
lport['uuid'])
except n_exc.NotFound:
LOG.debug("NSX Port %s already gone", lport['uuid'])
def _nsx_delete_port(self, context, port_data):
# FIXME(salvatore-orlando): On the NSX platform we do not really have
# external networks. So deleting regular ports from external networks
# does not make sense. However we cannot raise as this would break
# unit tests.
if self._network_is_external(context, port_data['network_id']):
LOG.info("NSX plugin does not support regular VIF ports on "
"external networks. Port %s will be down.",
port_data['network_id'])
return
nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
context.session, self.cluster, port_data['id'])
if not nsx_port_id:
LOG.debug("Port '%s' was already deleted on NSX platform", id)
return
# TODO(bgh): if this is a bridged network and the lswitch we just got
# back will have zero ports after the delete we should garbage collect
# the lswitch.
try:
switchlib.delete_port(self.cluster, nsx_switch_id, nsx_port_id)
LOG.debug("_nsx_delete_port completed for port %(port_id)s "
"on network %(net_id)s",
{'port_id': port_data['id'],
'net_id': port_data['network_id']})
except n_exc.NotFound:
LOG.warning("Port %s not found in NSX", port_data['id'])
def _nsx_delete_router_port(self, context, port_data):
# Delete logical router port
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, port_data['device_id'])
nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
context.session, self.cluster, port_data['id'])
if not nsx_port_id:
LOG.warning(
"Neutron port %(port_id)s not found on NSX backend. "
"Terminating delete operation. A dangling router port "
"might have been left on router %(router_id)s",
{'port_id': port_data['id'],
'router_id': nsx_router_id})
return
try:
routerlib.delete_peer_router_lport(self.cluster,
nsx_router_id,
nsx_switch_id,
nsx_port_id)
except api_exc.NsxApiException:
# Do not raise because the issue might as well be that the
# router has already been deleted, so there would be nothing
# to do here
LOG.exception("Ignoring exception as this means the peer "
"for port '%s' has already been deleted.",
nsx_port_id)
# Delete logical switch port
self._nsx_delete_port(context, port_data)
def _nsx_create_router_port(self, context, port_data):
"""Driver for creating a switch port to be connected to a router."""
# No router ports on external networks!
if self._network_is_external(context, port_data['network_id']):
raise nsx_exc.NsxPluginException(
err_msg=(_("It is not allowed to create router interface "
"ports on external networks as '%s'") %
port_data['network_id']))
ls_port = None
selected_lswitch = None
try:
selected_lswitch = self._nsx_find_lswitch_for_port(
context, port_data)
# Do not apply port security here!
ls_port = self._nsx_create_port_helper(
context.session, selected_lswitch['uuid'],
port_data, False)
# Assuming subnet being attached is on first fixed ip
# element in port data
subnet_id = None
if len(port_data['fixed_ips']):
subnet_id = port_data['fixed_ips'][0]['subnet_id']
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, port_data['device_id'])
# Create peer port on logical router
self._create_and_attach_router_port(
self.cluster, context, nsx_router_id, port_data,
"PatchAttachment", ls_port['uuid'],
subnet_ids=[subnet_id])
nsx_db.add_neutron_nsx_port_mapping(
context.session, port_data['id'],
selected_lswitch['uuid'], ls_port['uuid'])
LOG.debug("_nsx_create_router_port completed for port "
"%(name)s on network %(network_id)s. The new "
"port id is %(id)s.",
port_data)
except (api_exc.NsxApiException, n_exc.NeutronException):
self._handle_create_port_exception(
context, port_data['id'],
selected_lswitch and selected_lswitch['uuid'],
ls_port and ls_port['uuid'])
def _find_router_gw_port(self, context, port_data):
router_id = port_data['device_id']
if not router_id:
raise n_exc.BadRequest(_("device_id field must be populated in "
"order to create an external gateway "
"port for network %s"),
port_data['network_id'])
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
lr_port = routerlib.find_router_gw_port(context, self.cluster,
nsx_router_id)
if not lr_port:
raise nsx_exc.NsxPluginException(
err_msg=(_("The gateway port for the NSX router %s "
"was not found on the backend")
% nsx_router_id))
return lr_port
@lockutils.synchronized('vmware', 'neutron-')
def _nsx_create_ext_gw_port(self, context, port_data):
"""Driver for creating an external gateway port on NSX platform."""
# TODO(salvatore-orlando): Handle NSX resource
# rollback when something goes not quite as expected
lr_port = self._find_router_gw_port(context, port_data)
ip_addresses = self._build_ip_address_list(context,
port_data['fixed_ips'])
# This operation actually always updates a NSX logical port
# instead of creating one. This is because the gateway port
# is created at the same time as the NSX logical router, otherwise
# the fabric status of the NSX router will be down.
# admin_status should always be up for the gateway port
# regardless of what the user specifies in neutron
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, port_data['device_id'])
routerlib.update_router_lport(self.cluster,
nsx_router_id,
lr_port['uuid'],
port_data['tenant_id'],
port_data['id'],
port_data['name'],
True,
ip_addresses)
ext_network = self.get_network(context, port_data['network_id'])
if ext_network.get(pnet.NETWORK_TYPE) == c_utils.NetworkTypes.L3_EXT:
# Update attachment
physical_network = (ext_network[pnet.PHYSICAL_NETWORK] or
self.cluster.default_l3_gw_service_uuid)
self._update_router_port_attachment(
self.cluster, context, nsx_router_id, port_data,
lr_port['uuid'],
"L3GatewayAttachment",
physical_network,
ext_network[pnet.SEGMENTATION_ID])
LOG.debug("_nsx_create_ext_gw_port completed on external network "
"%(ext_net_id)s, attached to router:%(router_id)s. "
"NSX port id is %(nsx_port_id)s",
{'ext_net_id': port_data['network_id'],
'router_id': nsx_router_id,
'nsx_port_id': lr_port['uuid']})
@lockutils.synchronized('vmware', 'neutron-')
def _nsx_delete_ext_gw_port(self, context, port_data):
# TODO(salvatore-orlando): Handle NSX resource
# rollback when something goes not quite as expected
try:
router_id = port_data['device_id']
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
if not nsx_router_id:
LOG.debug("No object found on backend for router %s. This "
"that the router was already deleted and no "
"further action is needed for resetting the "
"external gateway port", router_id)
return
lr_port = self._find_router_gw_port(context, port_data)
# Delete is actually never a real delete, otherwise the NSX
# logical router will stop working
routerlib.update_router_lport(self.cluster,
nsx_router_id,
lr_port['uuid'],
port_data['tenant_id'],
port_data['id'],
port_data['name'],
True,
['0.0.0.0/31'])
# Reset attachment
self._update_router_port_attachment(
self.cluster, context, nsx_router_id, port_data,
lr_port['uuid'],
"L3GatewayAttachment",
self.cluster.default_l3_gw_service_uuid)
LOG.debug("_nsx_delete_ext_gw_port completed on external network "
"%(ext_net_id)s, attached to NSX router:%(router_id)s",
{'ext_net_id': port_data['network_id'],
'router_id': nsx_router_id})
except n_exc.NotFound:
LOG.debug("Logical router resource %s not found "
"on NSX platform : the router may have "
"already been deleted",
port_data['device_id'])
except api_exc.NsxApiException:
raise nsx_exc.NsxPluginException(
err_msg=_("Unable to update logical router"
"on NSX Platform"))
def _nsx_create_l2_gw_port(self, context, port_data):
"""Create a switch port, and attach it to a L2 gateway attachment."""
# FIXME(salvatore-orlando): On the NSX platform we do not really have
# external networks. So if as user tries and create a "regular" VIF
# port on an external network we are unable to actually create.
# However, in order to not break unit tests, we need to still create
# the DB object and return success
if self._network_is_external(context, port_data['network_id']):
LOG.info("NSX plugin does not support regular VIF ports on "
"external networks. Port %s will be down.",
port_data['network_id'])
# No need to actually update the DB state - the default is down
return port_data
lport = None
try:
selected_lswitch = self._nsx_find_lswitch_for_port(
context, port_data)
lport = self._nsx_create_port_helper(
context.session,
selected_lswitch['uuid'],
port_data,
True)
nsx_db.add_neutron_nsx_port_mapping(
context.session, port_data['id'],
selected_lswitch['uuid'], lport['uuid'])
l2gwlib.plug_l2_gw_service(
self.cluster,
selected_lswitch['uuid'],
lport['uuid'],
port_data['device_id'],
int(port_data.get('gw:segmentation_id') or 0))
except Exception:
with excutils.save_and_reraise_exception():
if lport:
switchlib.delete_port(self.cluster,
selected_lswitch['uuid'],
lport['uuid'])
LOG.debug("_nsx_create_l2_gw_port completed for port %(name)s "
"on network %(network_id)s. The new port id "
"is %(id)s.", port_data)
def _nsx_create_fip_port(self, context, port_data):
# As we do not create ports for floating IPs in NSX,
# this is a no-op driver
pass
def _nsx_delete_fip_port(self, context, port_data):
# As we do not create ports for floating IPs in NSX,
# this is a no-op driver
pass
def _extend_fault_map(self):
"""Extends the Neutron Fault Map.
Exceptions specific to the NSX Plugin are mapped to standard
HTTP Exceptions.
"""
base.FAULT_MAP.update({nsx_exc.InvalidNovaZone:
webob.exc.HTTPBadRequest,
nsx_exc.NoMorePortsException:
webob.exc.HTTPBadRequest,
nsx_exc.MaintenanceInProgress:
webob.exc.HTTPServiceUnavailable,
nsx_exc.InvalidSecurityCertificate:
webob.exc.HTTPBadRequest})
def _validate_provider_create(self, context, network):
segments = network.get(mpnet.SEGMENTS)
if not validators.is_attr_set(segments):
return
mpnet.check_duplicate_segments(segments)
for segment in segments:
network_type = segment.get(pnet.NETWORK_TYPE)
physical_network = segment.get(pnet.PHYSICAL_NETWORK)
physical_network_set = validators.is_attr_set(physical_network)
segmentation_id = segment.get(pnet.SEGMENTATION_ID)
network_type_set = validators.is_attr_set(network_type)
segmentation_id_set = validators.is_attr_set(segmentation_id)
# If the physical_network_uuid isn't passed in use the default one.
if not physical_network_set:
physical_network = cfg.CONF.default_tz_uuid
err_msg = None
if not network_type_set:
err_msg = _("%s required") % pnet.NETWORK_TYPE
elif network_type in (c_utils.NetworkTypes.GRE,
c_utils.NetworkTypes.STT,
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:
# Verify segment is not already allocated
bindings = (
nsx_db.get_network_bindings_by_vlanid_and_physical_net(
context.session, segmentation_id,
physical_network)
)
if bindings:
raise n_exc.VlanIdInUse(
vlan_id=segmentation_id,
physical_network=physical_network)
elif network_type == c_utils.NetworkTypes.L3_EXT:
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})
# Network must be external
if not network.get(ext_net_extn.EXTERNAL):
err_msg = (_("The l3_ext provide network type can be "
"used with external networks only"))
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)
# TODO(salvatore-orlando): Validate tranport zone uuid
# which should be specified in physical_network
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 extend_port_dict_binding(self, port_res, port_db):
super(NsxPluginV2, self).extend_port_dict_binding(port_res, port_db)
port_res[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL
def _handle_lswitch_selection(self, context, cluster, network):
# NOTE(salv-orlando): This method used to select a NSX logical switch
# with an available port, and create a new logical switch if
# necessary. As there is no more need to perform switch chaining in
# NSX, the logic for creating a new logical switch has been removed.
max_ports = self.nsx_opts.max_lp_per_overlay_ls
network_bindings = nsx_db.get_network_bindings(
context.session, network['id'])
for network_binding in network_bindings:
if network_binding.binding_type in (c_utils.NetworkTypes.FLAT,
c_utils.NetworkTypes.VLAN):
max_ports = self.nsx_opts.max_lp_per_bridged_ls
# This is still necessary as there could be chained switches in
# the deployment and the code needs to find the first one with
# an available slot for a port
lswitches = nsx_utils.fetch_nsx_switches(
context.session, cluster, network['id'])
try:
return [ls for ls in lswitches
if (ls['_relations']['LogicalSwitchStatus']
['lport_count'] < max_ports)].pop(0)
except IndexError:
# Too bad, no switch where a port can be created
LOG.debug("No switch has available ports (%d checked)",
len(lswitches))
raise nsx_exc.NoMorePortsException(network=network.id)
def _convert_to_nsx_transport_zones(self, cluster, network=None,
bindings=None):
# TODO(salv-orlando): Remove this method and call nsx-utils direct
return nsx_utils.convert_to_nsx_transport_zones(
cluster.default_tz_uuid, network, bindings,
default_transport_type=cfg.CONF.NSX.default_transport_type)
def _convert_to_transport_zones_dict(self, network):
"""Converts the provider request body to multiprovider.
Returns: True if request is multiprovider False if provider
and None if neither.
"""
if any(validators.is_attr_set(network.get(f))
for f in (pnet.NETWORK_TYPE, pnet.PHYSICAL_NETWORK,
pnet.SEGMENTATION_ID)):
if validators.is_attr_set(network.get(mpnet.SEGMENTS)):
raise mpnet.SegmentsSetInConjunctionWithProviders()
# convert to transport zone list
network[mpnet.SEGMENTS] = [
{pnet.NETWORK_TYPE: network[pnet.NETWORK_TYPE],
pnet.PHYSICAL_NETWORK: network[pnet.PHYSICAL_NETWORK],
pnet.SEGMENTATION_ID: network[pnet.SEGMENTATION_ID]}]
del network[pnet.NETWORK_TYPE]
del network[pnet.PHYSICAL_NETWORK]
del network[pnet.SEGMENTATION_ID]
return False
if validators.is_attr_set(mpnet.SEGMENTS):
return True
def create_network(self, context, network):
net_data = network['network']
tenant_id = net_data['tenant_id']
self._ensure_default_security_group(context, tenant_id)
# Process the provider network extension
provider_type = self._convert_to_transport_zones_dict(net_data)
self._validate_provider_create(context, net_data)
# Replace ATTR_NOT_SPECIFIED with None before sending to NSX
for key, value in six.iteritems(network['network']):
if value is constants.ATTR_NOT_SPECIFIED:
net_data[key] = None
# FIXME(arosen) implement admin_state_up = False in NSX
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>'))
transport_zone_config = self._convert_to_nsx_transport_zones(
self.cluster, net_data)
external = net_data.get(ext_net_extn.EXTERNAL)
# NOTE(salv-orlando): Pre-generating uuid for Neutron
# network. This will be removed once the network create operation
# becomes an asynchronous task
net_data['id'] = str(uuid.uuid4())
if (not validators.is_attr_set(external) or
validators.is_attr_set(external) and not external):
lswitch = switchlib.create_lswitch(
self.cluster, net_data['id'],
tenant_id, net_data.get('name'),
transport_zone_config,
shared=net_data.get(attr.SHARED))
with db_api.context_manager.writer.using(context):
new_net = super(NsxPluginV2, self).create_network(context,
network)
# Process port security extension
self._process_network_port_security_create(
context, net_data, new_net)
# DB Operations for setting the network as external
self._process_l3_create(context, new_net, net_data)
# Process QoS queue extension
net_queue_id = net_data.get(qos.QUEUE)
if net_queue_id:
# Raises if not found
self.get_qos_queue(context, net_queue_id)
self._process_network_queue_mapping(
context, new_net, net_queue_id)
# Add mapping between neutron network and NSX switch
if (not validators.is_attr_set(external) or
validators.is_attr_set(external) and not external):
nsx_db.add_neutron_nsx_network_mapping(
context.session, new_net['id'],
lswitch['uuid'])
if (net_data.get(mpnet.SEGMENTS) and
isinstance(provider_type, bool)):
net_bindings = []
for tz in net_data[mpnet.SEGMENTS]:
segmentation_id = tz.get(pnet.SEGMENTATION_ID, 0)
segmentation_id_set = validators.is_attr_set(
segmentation_id)
if not segmentation_id_set:
segmentation_id = 0
net_bindings.append(nsx_db.add_network_binding(
context.session, new_net['id'],
tz.get(pnet.NETWORK_TYPE),
tz.get(pnet.PHYSICAL_NETWORK),
segmentation_id))
if provider_type:
nsx_db.set_multiprovider_network(context.session,
new_net['id'])
self._extend_network_dict_provider(context, new_net,
provider_type,
net_bindings)
# this extra lookup is necessary to get the
# latest db model for the extension functions
net_model = self._get_network(context, new_net['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 delete_network(self, context, id):
external = self._network_is_external(context, id)
# Before removing entry from Neutron DB, retrieve NSX switch
# identifiers for removing them from backend
if not external:
lswitch_ids = nsx_utils.get_nsx_switch_ids(
context.session, self.cluster, id)
self._process_l3_delete(context, id)
nsx_db.delete_network_bindings(context.session, id)
super(NsxPluginV2, self).delete_network(context, id)
# Do not go to NSX for external networks
if not external:
try:
switchlib.delete_networks(self.cluster, id, lswitch_ids)
except n_exc.NotFound:
LOG.warning("The following logical switches were not "
"found on the NSX backend:%s", lswitch_ids)
self.handle_network_dhcp_access(context, id, action='delete_network')
LOG.debug("Delete network complete for network: %s", id)
def get_network(self, context, id, fields=None):
with db_api.context_manager.writer.using(context):
# goto to the plugin DB and fetch the network
network = self._get_network(context, id)
if (self.nsx_sync_opts.always_read_status or
fields and 'status' in fields):
# External networks are not backed by nsx lswitches
if not network.external:
# Perform explicit state synchronization
self._synchronizer.synchronize_network(context, network)
# 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 db_utils.resource_fields(net_result, fields)
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(NsxPluginV2, self).get_networks(
context, filters, fields, sorts,
limit, marker, page_reverse))
for net in networks:
self._extend_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):
providernet._raise_if_updates_provider_attributes(network['network'])
if network["network"].get("admin_state_up") is False:
raise NotImplementedError(_("admin_state_up=False networks "
"are not supported."))
with db_api.context_manager.writer.using(context):
net = super(NsxPluginV2, self).update_network(context, id, network)
if psec.PORTSECURITY in network['network']:
self._process_network_port_security_update(
context, network['network'], net)
net_queue_id = network['network'].get(qos.QUEUE)
if net_queue_id:
self._delete_network_queue_mapping(context, id)
self._process_network_queue_mapping(context, net, net_queue_id)
self._process_l3_update(context, net, network['network'])
self._extend_network_dict_provider(context, net)
# If provided, update port name on backend; treat backend failures as
# not critical (log error, but do not raise)
if 'name' in network['network']:
# in case of chained switches update name only for the first one
nsx_switch_ids = nsx_utils.get_nsx_switch_ids(
context.session, self.cluster, id)
if not nsx_switch_ids or len(nsx_switch_ids) < 1:
LOG.warning("Unable to find NSX mappings for neutron "
"network:%s", id)
try:
switchlib.update_lswitch(self.cluster,
nsx_switch_ids[0],
network['network']['name'])
except api_exc.NsxApiException as e:
LOG.warning("Logical switch update on NSX backend failed. "
"Neutron network id:%(net_id)s; "
"NSX lswitch id:%(lswitch_id)s;"
"Error:%(error)s",
{'net_id': id, 'lswitch_id': nsx_switch_ids[0],
'error': e})
return net
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']
dhcp_opts = port_data.get(edo_ext.EXTRADHCPOPTS, [])
# Set port status as 'DOWN'. This will be updated by backend sync.
port_data['status'] = constants.PORT_STATUS_DOWN
with db_api.context_manager.writer.using(context):
# First we allocate port in neutron database
neutron_db = super(NsxPluginV2, self).create_port(context, port)
neutron_port_id = neutron_db['id']
# Update fields obtained from neutron db (eg: MAC address)
port["port"].update(neutron_db)
self.handle_port_metadata_access(context, neutron_db)
# port security extension checks
(port_security, has_ip) = self._determine_port_security_and_has_ip(
context, port_data)
port_data[psec.PORTSECURITY] = port_security
self._process_port_port_security_create(
context, port_data, neutron_db)
# allowed address pair checks
if validators.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] = []
# security group extension checks
# NOTE: check_update_has_security_groups works fine for
# create operations as well
if port_security and has_ip:
self._ensure_default_security_group_on_port(context, port)
elif self._check_update_has_security_groups(
{'port': port_data}):
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])
# QoS extension checks
port_queue_id = self._check_for_queue_and_create(
context, port_data)
self._process_port_queue_mapping(
context, port_data, port_queue_id)
if (isinstance(port_data.get(mac_ext.MAC_LEARNING), bool)):
self._create_mac_learning_state(context, port_data)
elif mac_ext.MAC_LEARNING in port_data:
port_data.pop(mac_ext.MAC_LEARNING)
self._process_portbindings_create_and_update(context,
port['port'],
port_data)
self._process_port_create_extra_dhcp_opts(context, port_data,
dhcp_opts)
# For some reason the port bindings DB mixin does not handle
# the VNIC_TYPE attribute, which is required by nova for
# setting up VIFs.
context.session.flush()
port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL
# DB Operation is complete, perform NSX operation
try:
port_data = port['port'].copy()
port_create_func = self._port_drivers['create'].get(
port_data['device_owner'],
self._port_drivers['create']['default'])
port_create_func(context, port_data)
LOG.debug("port created on NSX backend for tenant "
"%(tenant_id)s: (%(id)s)", port_data)
except n_exc.NotFound:
LOG.warning("Logical switch for network %s was not "
"found in NSX.", port_data['network_id'])
# Put port in error on neutron DB
with db_api.context_manager.writer.using(context):
port = self._get_port(context, neutron_port_id)
port_data['status'] = constants.PORT_STATUS_ERROR
port['status'] = port_data['status']
context.session.add(port)
except Exception:
# Port must be removed from neutron DB
with excutils.save_and_reraise_exception():
LOG.error("Unable to create port or set port "
"attachment in NSX.")
with db_api.context_manager.writer.using(context):
self.ipam.delete_port(context, neutron_port_id)
# this extra lookup is necessary to get the
# latest db model for the extension functions
port_model = self._get_port(context, neutron_port_id)
resource_extend.apply_funcs('ports', 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_security_groups = self._check_update_deletes_security_groups(
port)
has_security_groups = self._check_update_has_security_groups(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(NsxPluginV2, self).update_port(
context, id, port)
# Save current mac learning state to check whether it's
# being updated or not
old_mac_learning_state = ret_port.get(mac_ext.MAC_LEARNING)
# copy values over - except fixed_ips as
# they've already been processed
port['port'].pop('fixed_ips', None)
ret_port.update(port['port'])
tenant_id = ret_port['tenant_id']
self._update_extra_dhcp_opts_on_port(context, id, port, ret_port)
# populate port_security setting
if psec.PORTSECURITY not in port['port']:
ret_port[psec.PORTSECURITY] = self._get_port_security_binding(
context, id)
has_ip = self._ip_on_port(ret_port)
# 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])
# checks if security groups were updated adding/modifying
# security groups, port security is set and port has ip
if not (has_ip and ret_port[psec.PORTSECURITY]):
if has_security_groups:
raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups()
# Update did not have security groups passed in. Check
# that port does not have any security groups already on it.
filters = {'port_id': [id]}
security_groups = (
super(NsxPluginV2, self)._get_port_security_group_bindings(
context, filters)
)
if security_groups and not delete_security_groups:
raise psec_exc.PortSecurityPortHasSecurityGroup()
if (delete_security_groups or has_security_groups):
# delete the port binding and read it with the new rules.
self._delete_port_security_group_bindings(context, id)
sgids = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, ret_port,
sgids)
if psec.PORTSECURITY in port['port']:
self._process_port_port_security_update(
context, port['port'], ret_port)
port_queue_id = self._check_for_queue_and_create(
context, ret_port)
# Populate the mac learning attribute
new_mac_learning_state = port['port'].get(mac_ext.MAC_LEARNING)
if (new_mac_learning_state is not None and
old_mac_learning_state != new_mac_learning_state):
self._update_mac_learning_state(context, id,
new_mac_learning_state)
ret_port[mac_ext.MAC_LEARNING] = new_mac_learning_state
self._delete_port_queue_mapping(context, ret_port['id'])
self._process_port_queue_mapping(context, ret_port,
port_queue_id)
self._process_portbindings_create_and_update(context,
port['port'],
ret_port)
nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
context.session, self.cluster, id)
# Convert Neutron security groups identifiers into NSX security
# profiles identifiers
nsx_sec_profile_ids = [
nsx_utils.get_nsx_security_group_id(
context.session, self.cluster, neutron_sg_id) for
neutron_sg_id in (ret_port[ext_sg.SECURITYGROUPS] or [])]
# Perform the NSX operation outside of the DB transaction
LOG.debug("Updating port %s on NSX backend", ret_port['id'])
if nsx_port_id:
try:
switchlib.update_port(
self.cluster, nsx_switch_id, nsx_port_id,
id, tenant_id,
ret_port['name'],
ret_port['device_id'],
ret_port['admin_state_up'],
ret_port['mac_address'],
ret_port['fixed_ips'],
ret_port[psec.PORTSECURITY],
nsx_sec_profile_ids,
ret_port[qos.QUEUE],
ret_port.get(mac_ext.MAC_LEARNING),
ret_port.get(addr_pair.ADDRESS_PAIRS))
# Update the port status from nsx. If we fail here hide it
# since the port was successfully updated but we were not
# able to retrieve the status.
ret_port['status'] = switchlib.get_port_status(
self.cluster, nsx_switch_id,
nsx_port_id)
# FIXME(arosen) improve exception handling.
except Exception:
ret_port['status'] = constants.PORT_STATUS_ERROR
LOG.exception("Unable to update port id: %s.",
nsx_port_id)
# If nsx_port_id is not in database or in nsx put in error state.
else:
ret_port['status'] = constants.PORT_STATUS_ERROR
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
"""
# if needed, check to see if this is a port owned by
# a l3 router. If so, we should prevent deletion here
if l3_port_check:
self.prevent_l3_port_deletion(context, id)
neutron_db_port = self.get_port(context, id)
# Perform the same check for ports owned by layer-2 gateways
if nw_gw_port_check:
self.prevent_network_gateway_port_deletion(context,
neutron_db_port)
port_delete_func = self._port_drivers['delete'].get(
neutron_db_port['device_owner'],
self._port_drivers['delete']['default'])
port_delete_func(context, neutron_db_port)
self.disassociate_floatingips(context, id)
with db_api.context_manager.writer.using(context):
queue = self._get_port_queue_bindings(context, {'port_id': [id]})
# metadata_dhcp_host_route
self.handle_port_metadata_access(
context, neutron_db_port, is_delete=True)
super(NsxPluginV2, self).delete_port(context, id)
# Delete qos queue if possible
if queue:
self.delete_qos_queue(context, queue[0]['queue_id'], False)
self.handle_port_dhcp_access(
context, neutron_db_port, action='delete_port')
def get_port(self, context, id, fields=None):
with db_api.context_manager.writer.using(context):
if (self.nsx_sync_opts.always_read_status or
fields and 'status' in fields):
# Perform explicit state synchronization
db_port = self._get_port(context, id)
self._synchronizer.synchronize_port(
context, db_port)
return self._make_port_dict(db_port, fields)
else:
return super(NsxPluginV2, self).get_port(context, id, fields)
def get_router(self, context, id, fields=None):
with db_api.context_manager.writer.using(context):
if (self.nsx_sync_opts.always_read_status or
fields and 'status' in fields):
db_router = self._get_router(context, id)
# Perform explicit state synchronization
self._synchronizer.synchronize_router(
context, db_router)
return self._make_router_dict(db_router, fields)
else:
return super(NsxPluginV2, self).get_router(context, id, fields)
def _create_lrouter(self, context, router, nexthop):
tenant_id = router['tenant_id']
distributed = router.get('distributed')
try:
lrouter = routerlib.create_lrouter(
self.cluster, router['id'],
tenant_id, router['name'], nexthop,
distributed=(validators.is_attr_set(distributed)
and distributed))
except nsx_exc.InvalidVersion:
msg = _("Cannot create a distributed router with the NSX "
"platform currently in execution. Please, try "
"without specifying the 'distributed' attribute.")
LOG.exception(msg)
raise n_exc.BadRequest(resource='router', msg=msg)
except api_exc.NsxApiException:
err_msg = _("Unable to create logical router on NSX Platform")
LOG.exception(err_msg)
raise nsx_exc.NsxPluginException(err_msg=err_msg)
# Create the port here - and update it later if we have gw_info
try:
self._create_and_attach_router_port(
self.cluster, context, lrouter['uuid'], {'fake_ext_gw': True},
"L3GatewayAttachment",
self.cluster.default_l3_gw_service_uuid)
except nsx_exc.NsxPluginException:
LOG.exception("Unable to create L3GW port on logical router "
"%(router_uuid)s. Verify Default Layer-3 "
"Gateway service %(def_l3_gw_svc)s id is "
"correct",
{'router_uuid': lrouter['uuid'],
'def_l3_gw_svc':
self.cluster.default_l3_gw_service_uuid})
# Try and remove logical router from NSX
routerlib.delete_lrouter(self.cluster, lrouter['uuid'])
# Return user a 500 with an apter message
raise nsx_exc.NsxPluginException(
err_msg=(_("Unable to create router %s on NSX backend") %
router['id']))
lrouter['status'] = constants.ACTIVE
return lrouter
def _process_extra_attr_router_create(self, context, router_db, r):
for extra_attr in l3_attrs_db.get_attr_info().keys():
if extra_attr in r:
self.set_extra_attr_value(context, router_db,
extra_attr, r[extra_attr])
def create_router(self, context, router):
# NOTE(salvatore-orlando): We completely override this method in
# order to be able to use the NSX ID as Neutron ID
# TODO(salvatore-orlando): Propose upstream patch for allowing
# 3rd parties to specify IDs as we do with l2 plugin
r = router['router']
has_gw_info = False
tenant_id = r['tenant_id']
# default value to set - nsx wants it (even if we don't have it)
nexthop = NSX_DEFAULT_NEXTHOP
# if external gateway info are set, then configure nexthop to
# default external gateway
if 'external_gateway_info' in r and r.get('external_gateway_info'):
has_gw_info = True
gw_info = r['external_gateway_info']
del r['external_gateway_info']
# The following DB read will be performed again when updating
# gateway info. This is not great, but still better than
# creating NSX router here and updating it later
network_id = (gw_info.get('network_id', None) if gw_info
else None)
if network_id:
ext_net = self._get_network(context, network_id)
if not ext_net.external:
msg = (_("Network '%s' is not a valid external "
"network") % network_id)
raise n_exc.BadRequest(resource='router', msg=msg)
if ext_net.subnets:
ext_subnet = ext_net.subnets[0]
nexthop = ext_subnet.gateway_ip
# NOTE(salv-orlando): Pre-generating uuid for Neutron
# router. This will be removed once the router create operation
# becomes an asynchronous task
neutron_router_id = str(uuid.uuid4())
r['id'] = neutron_router_id
# Populate distributed attribute in order to ensure the appropriate
# type of router is created in the NSX backend
r['distributed'] = l3_dvr_db.is_distributed_router(r)
lrouter = self._create_lrouter(context, r, nexthop)
# TODO(salv-orlando): Deal with backend object removal in case
# of db failures
with db_api.context_manager.writer.using(context):
# Transaction nesting is needed to avoid foreign key violations
# when processing the distributed router binding
with db_api.context_manager.writer.using(context):
router_db = l3_db_models.Router(
id=neutron_router_id,
tenant_id=tenant_id,
name=r['name'],
admin_state_up=r['admin_state_up'],
status=lrouter['status'])
context.session.add(router_db)
self._process_extra_attr_router_create(context, router_db, r)
# Ensure neutron router is moved into the transaction's buffer
context.session.flush()
# Add mapping between neutron and nsx identifiers
nsx_db.add_neutron_nsx_router_mapping(
context.session, router_db['id'], lrouter['uuid'])
if has_gw_info:
# NOTE(salv-orlando): This operation has been moved out of the
# database transaction since it performs several NSX queries,
# ithis ncreasing the risk of deadlocks between eventlet and
# sqlalchemy operations.
# Set external gateway and remove router in case of failure
try:
self._update_router_gw_info(context, router_db['id'],
gw_info)
except (n_exc.NeutronException, api_exc.NsxApiException):
with excutils.save_and_reraise_exception():
# As setting gateway failed, the router must be deleted
# in order to ensure atomicity
router_id = router_db['id']
LOG.warning("Failed to set gateway info for router "
"being created:%s - removing router",
router_id)
self.delete_router(context, router_id)
LOG.info("Create router failed while setting external "
"gateway. Router:%s has been removed from "
"DB and backend",
router_id)
return self._make_router_dict(router_db)
def _update_lrouter(self, context, router_id, name, nexthop, routes=None):
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
return routerlib.update_lrouter(
self.cluster, nsx_router_id, name,
nexthop, routes=routes)
def _update_lrouter_routes(self, context, router_id, routes):
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
routerlib.update_explicit_routes_lrouter(
self.cluster, nsx_router_id, routes)
def update_router(self, context, router_id, router):
# Either nexthop is updated or should be kept as it was before
r = router['router']
nexthop = None
if 'external_gateway_info' in r and r.get('external_gateway_info'):
gw_info = r['external_gateway_info']
# The following DB read will be performed again when updating
# gateway info. This is not great, but still better than
# creating NSX router here and updating it later
network_id = (gw_info.get('network_id', None) if gw_info
else None)
if network_id:
ext_net = self._get_network(context, network_id)
if not ext_net.external:
msg = (_("Network '%s' is not a valid external "
"network") % network_id)
raise n_exc.BadRequest(resource='router', msg=msg)
if ext_net.subnets:
ext_subnet = ext_net.subnets[0]
nexthop = ext_subnet.gateway_ip
try:
for route in r.get('routes', []):
if route['destination'] == '0.0.0.0/0':
msg = _("'routes' cannot contain route '0.0.0.0/0', "
"this must be updated through the default "
"gateway attribute")
raise n_exc.BadRequest(resource='router', msg=msg)
previous_routes = self._update_lrouter(
context, router_id, r.get('name'),
nexthop, routes=r.get('routes'))
# NOTE(salv-orlando): The exception handling below is not correct, but
# unfortunately nsxlib raises a neutron notfound exception when an
# object is not found in the underlying backend
except n_exc.NotFound:
# Put the router in ERROR status
with db_api.context_manager.writer.using(context):
router_db = self._get_router(context, router_id)
router_db['status'] = constants.NET_STATUS_ERROR
raise nsx_exc.NsxPluginException(
err_msg=_("Logical router %s not found "
"on NSX Platform") % router_id)
except api_exc.NsxApiException:
raise nsx_exc.NsxPluginException(
err_msg=_("Unable to update logical router on NSX Platform"))
except nsx_exc.InvalidVersion:
msg = _("Request cannot contain 'routes' with the NSX "
"platform currently in execution. Please, try "
"without specifying the static routes.")
LOG.exception(msg)
raise n_exc.BadRequest(resource='router', msg=msg)
try:
return super(NsxPluginV2, self).update_router(context,
router_id, router)
except (extraroute.InvalidRoutes,
extraroute.RouterInterfaceInUseByRoute,
extraroute.RoutesExhausted):
with excutils.save_and_reraise_exception():
# revert changes made to NSX
self._update_lrouter_routes(
context, router_id, previous_routes)
def _delete_lrouter(self, context, router_id, nsx_router_id):
# The neutron router id (router_id) is ignored in this routine,
# but used in plugins deriving from this one
routerlib.delete_lrouter(self.cluster, nsx_router_id)
def delete_router(self, context, router_id):
with db_api.context_manager.writer.using(context):
# NOTE(salv-orlando): These checks will be repeated anyway when
# calling the superclass. This is wasteful, but is the simplest
# way of ensuring a consistent removal of the router both in
# the neutron Database and in the NSX backend.
self._ensure_router_not_in_use(context, router_id)
# TODO(salv-orlando): This call should have no effect on delete
# router, but if it does, it should not happen within a
# transaction, and it should be restored on rollback
self.handle_router_metadata_access(
context, router_id, interface=None)
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
# It is safe to remove the router from the database, so remove it
# from the backend
if nsx_router_id:
try:
self._delete_lrouter(context, router_id, nsx_router_id)
except n_exc.NotFound:
# This is not a fatal error, but needs to be logged
LOG.warning("Logical router '%s' not found "
"on NSX Platform", router_id)
except api_exc.NsxApiException:
raise nsx_exc.NsxPluginException(
err_msg=(_("Unable to delete logical router '%s' "
"on NSX Platform") % nsx_router_id))
else:
# If no mapping is found it is likely that the logical router does
# not exist anymore in the backend. This is not a fatal condition,
# but will result in an exception is "None" is passed to
# _delete_lrouter
LOG.warning("No mapping found for logical router '%s' "
"on NSX Platform", router_id)
# Remove the NSX mapping first in order to ensure a mapping to
# a non-existent NSX router is not left in the DB in case of
# failure while removing the router from the neutron DB
try:
nsx_db.delete_neutron_nsx_router_mapping(
context.session, router_id)
except db_exc.DBError as d_exc:
# Do not make this error fatal
LOG.warning("Unable to remove NSX mapping for Neutron router "
"%(router_id)s because of the following exception:"
"%(d_exc)s", {'router_id': router_id,
'd_exc': str(d_exc)})
# Perform the actual delete on the Neutron DB
super(NsxPluginV2, self).delete_router(context, router_id)
def _add_subnet_snat_rule(self, context, router, subnet):
gw_port = router.gw_port
if gw_port and router.enable_snat:
# There is a change gw_port might have multiple IPs
# In that case we will consider only the first one
if gw_port.get('fixed_ips'):
snat_ip = gw_port['fixed_ips'][0]['ip_address']
cidr_prefix = int(subnet['cidr'].split('/')[1])
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router['id'])
routerlib.create_lrouter_snat_rule(
self.cluster, nsx_router_id, snat_ip, snat_ip,
order=NSX_EXTGW_NAT_RULES_ORDER - cidr_prefix,
match_criteria={'source_ip_addresses': subnet['cidr']})
def _delete_subnet_snat_rule(self, context, router, subnet):
# Remove SNAT rule if external gateway is configured
if router.gw_port:
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router['id'])
routerlib.delete_nat_rules_by_match(
self.cluster, nsx_router_id, "SourceNatRule",
max_num_expected=1, min_num_expected=1,
raise_on_len_mismatch=False,
source_ip_addresses=subnet['cidr'])
def add_router_interface(self, context, router_id, interface_info):
# When adding interface by port_id we need to create the
# peer port on the nsx logical router in this routine
port_id = interface_info.get('port_id')
router_iface_info = super(NsxPluginV2, self).add_router_interface(
context, router_id, interface_info)
# router_iface_info will always have a subnet_id attribute
subnet_id = router_iface_info['subnet_id']
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
if port_id:
port_data = self.get_port(context, port_id)
# If security groups are present we need to remove them as
# this is a router port and disable port security.
if port_data['security_groups']:
self.update_port(context, port_id,
{'port': {'security_groups': [],
psec.PORTSECURITY: False}})
nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
context.session, self.cluster, port_id)
# Unplug current attachment from lswitch port
switchlib.plug_vif_interface(self.cluster, nsx_switch_id,
nsx_port_id, "NoAttachment")
# Create logical router port and plug patch attachment
self._create_and_attach_router_port(
self.cluster, context, nsx_router_id, port_data,
"PatchAttachment", nsx_port_id, subnet_ids=[subnet_id])
subnet = self._get_subnet(context, subnet_id)
# If there is an external gateway we need to configure the SNAT rule.
# Fetch router from DB
router = self._get_router(context, router_id)
self._add_subnet_snat_rule(context, router, subnet)
routerlib.create_lrouter_nosnat_rule(
self.cluster, nsx_router_id,
order=NSX_NOSNAT_RULES_ORDER,
match_criteria={'destination_ip_addresses': subnet['cidr']})
# Ensure the NSX logical router has a connection to a 'metadata access'
# network (with a proxy listening on its DHCP port), by creating it
# if needed.
self.handle_router_metadata_access(
context, router_id, interface=router_iface_info)
LOG.debug("Add_router_interface completed for subnet:%(subnet_id)s "
"and router:%(router_id)s",
{'subnet_id': subnet_id, 'router_id': router_id})
return router_iface_info
def get_l3_agents_hosting_routers(self, context, routers):
# This method is just a stub added because is required by the l3 dvr
# mixin. That's so much for a management layer which is plugin
# agnostic
return []
def _create_snat_intf_ports_if_not_exists(self, context, router):
# VMware plugins do not need SNAT interface ports
return []
def _add_csnat_router_interface_port(self, context, router, network_id,
subnet_id, do_pop=True):
# VMware plugins do not need SNAT interface ports
return
def _delete_csnat_router_interface_ports(self, context, router,
subnet_id=None):
# VMware plugins do not need SNAT interface ports
return
def remove_router_interface(self, context, router_id, interface_info):
# The code below is duplicated from base class, but comes handy
# as we need to retrieve the router port id before removing the port
subnet = None
subnet_id = None
if 'port_id' in interface_info:
port_id = interface_info['port_id']
# find subnet_id - it is need for removing the SNAT rule
port = self._get_port(context, port_id)
if port.get('fixed_ips'):
subnet_id = port['fixed_ips'][0]['subnet_id']
if not (port['device_owner'] in constants.ROUTER_INTERFACE_OWNERS
and port['device_id'] == router_id):
raise l3.RouterInterfaceNotFound(router_id=router_id,
port_id=port_id)
elif 'subnet_id' in interface_info:
subnet_id = interface_info['subnet_id']
subnet = self._get_subnet(context, subnet_id)
rport_qry = context.session.query(models_v2.Port)
ports = rport_qry.filter_by(
device_id=router_id,
network_id=subnet['network_id']).filter(
models_v2.Port.device_owner.in_(
constants.ROUTER_INTERFACE_OWNERS))
for p in ports:
if p['fixed_ips'][0]['subnet_id'] == subnet_id:
port_id = p['id']
break
else:
raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
subnet_id=subnet_id)
# Finally remove the data from the Neutron DB
# This will also destroy the port on the logical switch
info = super(NsxPluginV2, self).remove_router_interface(
context, router_id, interface_info)
try:
# Ensure the connection to the 'metadata access network'
# is removed (with the network) if this the last subnet
# on the router
self.handle_router_metadata_access(
context, router_id, interface=None)
if not subnet:
subnet = self._get_subnet(context, subnet_id)
router = self._get_router(context, router_id)
# If router is enabled_snat = False there are no snat rules to
# delete.
if router.enable_snat:
self._delete_subnet_snat_rule(context, router, subnet)
# Relax the minimum expected number as the nosnat rules
# do not exist in 2.x deployments
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
routerlib.delete_nat_rules_by_match(
self.cluster, nsx_router_id, "NoSourceNatRule",
max_num_expected=1, min_num_expected=0,
raise_on_len_mismatch=False,
destination_ip_addresses=subnet['cidr'])
except n_exc.NotFound:
LOG.error("Logical router resource %s not found "
"on NSX platform", router_id)
except api_exc.NsxApiException:
raise nsx_exc.NsxPluginException(
err_msg=(_("Unable to update logical router"
"on NSX Platform")))
return info
def _retrieve_and_delete_nat_rules(self, context, floating_ip_address,
internal_ip, nsx_router_id,
min_num_rules_expected=0):
"""Finds and removes NAT rules from a NSX router."""
# NOTE(salv-orlando): The context parameter is ignored in this method
# but used by derived classes
try:
# Remove DNAT rule for the floating IP
routerlib.delete_nat_rules_by_match(
self.cluster, nsx_router_id, "DestinationNatRule",
max_num_expected=1,
min_num_expected=min_num_rules_expected,
destination_ip_addresses=floating_ip_address)
# Remove SNAT rules for the floating IP
routerlib.delete_nat_rules_by_match(
self.cluster, nsx_router_id, "SourceNatRule",
max_num_expected=1,
min_num_expected=min_num_rules_expected,
source_ip_addresses=internal_ip)
routerlib.delete_nat_rules_by_match(
self.cluster, nsx_router_id, "SourceNatRule",
max_num_expected=1,
min_num_expected=min_num_rules_expected,
destination_ip_addresses=internal_ip)
except api_exc.NsxApiException:
with excutils.save_and_reraise_exception():
LOG.exception("An error occurred while removing NAT rules "
"on the NSX platform for floating ip:%s",
floating_ip_address)
except nsx_exc.NatRuleMismatch:
# Do not surface to the user
LOG.warning("An incorrect number of matching NAT rules "
"was found on the NSX platform")
def _remove_floatingip_address(self, context, fip_db):
# Remove floating IP address from logical router port
# Fetch logical port of router's external gateway
router_id = fip_db.router_id
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
nsx_gw_port_id = routerlib.find_router_gw_port(
context, self.cluster, nsx_router_id)['uuid']
ext_neutron_port_db = self._get_port(context.elevated(),
fip_db.floating_port_id)
nsx_floating_ips = self._build_ip_address_list(
context.elevated(), ext_neutron_port_db['fixed_ips'])
routerlib.update_lrouter_port_ips(self.cluster,
nsx_router_id,
nsx_gw_port_id,
ips_to_add=[],
ips_to_remove=nsx_floating_ips)
def _floatingip_status(self, floatingip_db, associated):
if (associated and
floatingip_db['status'] != constants.FLOATINGIP_STATUS_ACTIVE):
return constants.FLOATINGIP_STATUS_ACTIVE
elif (not associated and
floatingip_db['status'] != constants.FLOATINGIP_STATUS_DOWN):
return constants.FLOATINGIP_STATUS_DOWN
# in any case ensure the status is not reset by this method!
return floatingip_db['status']
def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
"""Update floating IP association data.
Overrides method from base class.
The method is augmented for creating NAT rules in the process.
"""
# Store router currently serving the floating IP
old_router_id = floatingip_db.router_id
port_id, internal_ip, router_id = self._check_and_get_fip_assoc(
context, fip, floatingip_db)
floating_ip = floatingip_db['floating_ip_address']
# If there's no association router_id will be None
if router_id:
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, router_id)
self._retrieve_and_delete_nat_rules(
context, floating_ip, internal_ip, nsx_router_id)
nsx_floating_ips = self._build_ip_address_list(
context.elevated(), external_port['fixed_ips'])
floating_ip = floatingip_db['floating_ip_address']
# Retrieve and delete existing NAT rules, if any
if old_router_id:
nsx_old_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, old_router_id)
# Retrieve the current internal ip
_p, _s, old_internal_ip = self._internal_fip_assoc_data(
context, {'id': floatingip_db.id,
'port_id': floatingip_db.fixed_port_id,
'fixed_ip_address': floatingip_db.fixed_ip_address,
'tenant_id': floatingip_db.tenant_id},
floatingip_db.tenant_id)
nsx_gw_port_id = routerlib.find_router_gw_port(
context, self.cluster, nsx_old_router_id)['uuid']
self._retrieve_and_delete_nat_rules(
context, floating_ip, old_internal_ip, nsx_old_router_id)
routerlib.update_lrouter_port_ips(
self.cluster, nsx_old_router_id, nsx_gw_port_id,
ips_to_add=[], ips_to_remove=nsx_floating_ips)
if router_id:
nsx_gw_port_id = routerlib.find_router_gw_port(
context, self.cluster, nsx_router_id)['uuid']
# Re-create NAT rules only if a port id is specified
if fip.get('port_id'):
try:
# Setup DNAT rules for the floating IP
routerlib.create_lrouter_dnat_rule(
self.cluster, nsx_router_id, internal_ip,
order=NSX_FLOATINGIP_NAT_RULES_ORDER,
match_criteria={'destination_ip_addresses':
floating_ip})
# Setup SNAT rules for the floating IP
# Create a SNAT rule for enabling connectivity to the
# floating IP from the same network as the internal port
# Find subnet id for internal_ip from fixed_ips
internal_port = self._get_port(context, port_id)
# Cchecks not needed on statements below since otherwise
# _internal_fip_assoc_data would have raised
subnet_ids = [ip['subnet_id'] for ip in
internal_port['fixed_ips'] if
ip['ip_address'] == internal_ip]
internal_subnet_cidr = self._build_ip_address_list(
context, internal_port['fixed_ips'],
subnet_ids=subnet_ids)[0]
routerlib.create_lrouter_snat_rule(
self.cluster, nsx_router_id, floating_ip, floating_ip,
order=NSX_NOSNAT_RULES_ORDER - 1,
match_criteria={'source_ip_addresses':
internal_subnet_cidr,
'destination_ip_addresses':
internal_ip})
# setup snat rule such that src ip of an IP packet when
# using floating is the floating ip itself.
routerlib.create_lrouter_snat_rule(
self.cluster, nsx_router_id, floating_ip, floating_ip,
order=NSX_FLOATINGIP_NAT_RULES_ORDER,
match_criteria={'source_ip_addresses': internal_ip})
# Add Floating IP address to router_port
routerlib.update_lrouter_port_ips(
self.cluster, nsx_router_id, nsx_gw_port_id,
ips_to_add=nsx_floating_ips, ips_to_remove=[])
except api_exc.NsxApiException:
LOG.exception("An error occurred while creating NAT "
"rules on the NSX platform for floating "
"ip:%(floating_ip)s mapped to "
"internal ip:%(internal_ip)s",
{'floating_ip': floating_ip,
'internal_ip': internal_ip})
msg = _("Failed to update NAT rules for floatingip update")
raise nsx_exc.NsxPluginException(err_msg=msg)
# Update also floating ip status (no need to call base class method)
new_status = self._floatingip_status(floatingip_db, router_id)
floatingip_db.update(
{'fixed_ip_address': internal_ip,
'fixed_port_id': port_id,
'router_id': router_id,
'status': new_status})
return {'fixed_ip_address': internal_ip,
'fixed_port_id': port_id,
'router_id': router_id,
'last_known_router_id': None,
'floating_ip_address': floatingip_db.floating_ip_address,
'floating_network_id': floatingip_db.floating_network_id,
'floating_ip_id': floatingip_db.id,
'context': context}
@lockutils.synchronized('vmware', 'neutron-')
def create_floatingip(self, context, floatingip):
return super(NsxPluginV2, self).create_floatingip(context, floatingip)
@lockutils.synchronized('vmware', 'neutron-')
def update_floatingip(self, context, floatingip_id, floatingip):
return super(NsxPluginV2, self).update_floatingip(context,
floatingip_id,
floatingip)
@lockutils.synchronized('vmware', 'neutron-')
def delete_floatingip(self, context, id):
fip_db = self._get_floatingip(context, id)
# Check whether the floating ip is associated or not
if fip_db.fixed_port_id:
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, fip_db.router_id)
self._retrieve_and_delete_nat_rules(context,
fip_db.floating_ip_address,
fip_db.fixed_ip_address,
nsx_router_id,
min_num_rules_expected=1)
# Remove floating IP address from logical router port
self._remove_floatingip_address(context, fip_db)
return super(NsxPluginV2, self).delete_floatingip(context, id)
def disassociate_floatingips(self, context, port_id):
try:
fip_qry = context.session.query(l3_db_models.FloatingIP)
fip_dbs = fip_qry.filter_by(fixed_port_id=port_id)
for fip_db in fip_dbs:
nsx_router_id = nsx_utils.get_nsx_router_id(
context.session, self.cluster, fip_db.router_id)
self._retrieve_and_delete_nat_rules(context,
fip_db.floating_ip_address,
fip_db.fixed_ip_address,
nsx_router_id,
min_num_rules_expected=1)
self._remove_floatingip_address(context, fip_db)
except sa_exc.NoResultFound:
LOG.debug("The port '%s' is not associated with floating IPs",
port_id)
except n_exc.NotFound:
LOG.warning("Nat rules not found in nsx for port: %s", id)
# NOTE(ihrachys): L3 agent notifications don't make sense for
# NSX VMWare plugin since there is no L3 agent in such setup, so
# disabling them here.
super(NsxPluginV2, self).disassociate_floatingips(
context, port_id, do_notify=False)
def create_network_gateway(self, context, network_gateway):
"""Create a layer-2 network gateway.
Create the gateway service on NSX platform and corresponding data
structures in Neutron datase.
"""
gw_data = network_gateway[networkgw.GATEWAY_RESOURCE_NAME]
tenant_id = gw_data['tenant_id']
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
# Validate provided gateway device list
self._validate_device_list(context, tenant_id, gw_data)
devices = gw_data['devices']
# Populate default physical network where not specified
for device in devices:
if not device.get('interface_name'):
device['interface_name'] = (self.cluster.
nsx_default_interface_name)
try:
# Replace Neutron device identifiers with NSX identifiers
dev_map = dict((dev['id'], dev['interface_name']) for
dev in devices)
nsx_devices = []
for db_device in self._query_gateway_devices(
context, filters={'id': [device['id'] for device in devices]}):
nsx_devices.append(
{'id': db_device['nsx_id'],
'interface_name': dev_map[db_device['id']]})
nsx_res = l2gwlib.create_l2_gw_service(
self.cluster, tenant_id, gw_data['name'], nsx_devices)
nsx_uuid = nsx_res.get('uuid')
except api_exc.Conflict:
raise nsx_exc.L2GatewayAlreadyInUse(gateway=gw_data['name'])
except api_exc.NsxApiException:
err_msg = _("Unable to create l2_gw_service for: %s") % gw_data
LOG.exception(err_msg)
raise nsx_exc.NsxPluginException(err_msg=err_msg)
gw_data['id'] = nsx_uuid
return super(NsxPluginV2, self).create_network_gateway(
context, network_gateway, validate_device_list=False)
def delete_network_gateway(self, context, gateway_id):
"""Remove a layer-2 network gateway.
Remove the gateway service from NSX platform and corresponding data
structures in Neutron datase.
"""
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
with db_api.context_manager.writer.using(context):
try:
super(NsxPluginV2, self).delete_network_gateway(
context, gateway_id)
l2gwlib.delete_l2_gw_service(self.cluster, gateway_id)
except api_exc.ResourceNotFound:
# Do not cause a 500 to be returned to the user if
# the corresponding NSX resource does not exist
LOG.exception("Unable to remove gateway service from "
"NSX plaform - the resource was not found")
def get_network_gateway(self, context, id, fields=None):
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
return super(NsxPluginV2, self).get_network_gateway(context,
id, fields)
def get_network_gateways(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
# Ensure the tenant_id attribute is populated on returned gateways
return super(NsxPluginV2, self).get_network_gateways(
context, filters, fields, sorts, limit, marker, page_reverse)
def update_network_gateway(self, context, id, network_gateway):
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
# Update gateway on backend when there's a name change
name = network_gateway[networkgw.GATEWAY_RESOURCE_NAME].get('name')
if name:
try:
l2gwlib.update_l2_gw_service(self.cluster, id, name)
except api_exc.NsxApiException:
# Consider backend failures as non-fatal, but still warn
# because this might indicate something dodgy is going on
LOG.warning("Unable to update name on NSX backend "
"for network gateway: %s", id)
return super(NsxPluginV2, self).update_network_gateway(
context, id, network_gateway)
def connect_network(self, context, network_gateway_id,
network_mapping_info):
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
try:
return super(NsxPluginV2, self).connect_network(
context, network_gateway_id, network_mapping_info)
except api_exc.Conflict:
raise nsx_exc.L2GatewayAlreadyInUse(gateway=network_gateway_id)
def disconnect_network(self, context, network_gateway_id,
network_mapping_info):
# Ensure the default gateway in the config file is in sync with the db
self._ensure_default_network_gateway()
return super(NsxPluginV2, self).disconnect_network(
context, network_gateway_id, network_mapping_info)
def _get_nsx_device_id(self, context, device_id):
return self._get_gateway_device(context, device_id)['nsx_id']
def _rollback_gw_device(self, context, device_id, gw_data=None,
new_status=None, is_create=False):
LOG.error("Rolling back database changes for gateway device %s "
"because of an error in the NSX backend", device_id)
with db_api.context_manager.writer.using(context):
query = model_query.query_with_hooks(
context, nsx_models.NetworkGatewayDevice).filter(
nsx_models.NetworkGatewayDevice.id == device_id)
if is_create:
query.delete(synchronize_session=False)
else:
super(NsxPluginV2, self).update_gateway_device(
context, device_id,
{networkgw.DEVICE_RESOURCE_NAME: gw_data})
if new_status:
query.update({'status': new_status},
synchronize_session=False)
# TODO(salv-orlando): Handlers for Gateway device operations should be
# moved into the appropriate nsx_handlers package once the code for the
# blueprint nsx-async-backend-communication merges
def create_gateway_device_handler(self, context, gateway_device,
client_certificate):
neutron_id = gateway_device['id']
try:
nsx_res = l2gwlib.create_gateway_device(
self.cluster,
gateway_device['tenant_id'],
gateway_device['name'],
neutron_id,
self.cluster.default_tz_uuid,
gateway_device['connector_type'],
gateway_device['connector_ip'],
client_certificate)
# Fetch status (it needs another NSX API call)
device_status = nsx_utils.get_nsx_device_status(self.cluster,
nsx_res['uuid'])
# set NSX GW device in neutron database and update status
with db_api.context_manager.writer.using(context):
query = model_query.query_with_hooks(
context, nsx_models.NetworkGatewayDevice).filter(
nsx_models.NetworkGatewayDevice.id == neutron_id)
query.update({'status': device_status,
'nsx_id': nsx_res['uuid']},
synchronize_session=False)
LOG.debug("Neutron gateway device: %(neutron_id)s; "
"NSX transport node identifier: %(nsx_id)s; "
"Operational status: %(status)s.",
{'neutron_id': neutron_id,
'nsx_id': nsx_res['uuid'],
'status': device_status})
return device_status
except (nsx_exc.InvalidSecurityCertificate, api_exc.NsxApiException):
with excutils.save_and_reraise_exception():
self._rollback_gw_device(context, neutron_id, is_create=True)
def update_gateway_device_handler(self, context, gateway_device,
old_gateway_device_data,
client_certificate):
nsx_id = gateway_device['nsx_id']
neutron_id = gateway_device['id']
try:
l2gwlib.update_gateway_device(
self.cluster,
nsx_id,
gateway_device['tenant_id'],
gateway_device['name'],
neutron_id,
self.cluster.default_tz_uuid,
gateway_device['connector_type'],
gateway_device['connector_ip'],
client_certificate)
# Fetch status (it needs another NSX API call)
device_status = nsx_utils.get_nsx_device_status(self.cluster,
nsx_id)
# update status
with db_api.context_manager.writer.using(context):
query = model_query.query_with_hooks(
context, nsx_models.NetworkGatewayDevice).filter(
nsx_models.NetworkGatewayDevice.id == neutron_id)
query.update({'status': device_status},
synchronize_session=False)
LOG.debug("Neutron gateway device: %(neutron_id)s; "
"NSX transport node identifier: %(nsx_id)s; "
"Operational status: %(status)s.",
{'neutron_id': neutron_id,
'nsx_id': nsx_id,
'status': device_status})
return device_status
except (nsx_exc.InvalidSecurityCertificate, api_exc.NsxApiException):
with excutils.save_and_reraise_exception():
self._rollback_gw_device(context, neutron_id,
gw_data=old_gateway_device_data)
except n_exc.NotFound:
# The gateway device was probably deleted in the backend.
# The DB change should be rolled back and the status must
# be put in error
with excutils.save_and_reraise_exception():
self._rollback_gw_device(context, neutron_id,
gw_data=old_gateway_device_data,
new_status=networkgw_db.ERROR)
def get_gateway_device(self, context, device_id, fields=None):
# Get device from database
gw_device = super(NsxPluginV2, self).get_gateway_device(
context, device_id, fields, include_nsx_id=True)
# Fetch status from NSX
nsx_id = gw_device['nsx_id']
device_status = nsx_utils.get_nsx_device_status(self.cluster, nsx_id)
# TODO(salv-orlando): Asynchronous sync for gateway device status
# Update status in database
with db_api.context_manager.writer.using(context):
query = model_query.query_with_hooks(
context, nsx_models.NetworkGatewayDevice).filter(
nsx_models.NetworkGatewayDevice.id == device_id)
query.update({'status': device_status},
synchronize_session=False)
gw_device['status'] = device_status
return gw_device
def get_gateway_devices(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
# Get devices from database
devices = super(NsxPluginV2, self).get_gateway_devices(
context, filters, fields, include_nsx_id=True)
# Fetch operational status from NSX, filter by tenant tag
# TODO(salv-orlando): Asynchronous sync for gateway device status
tenant_id = context.tenant_id if not context.is_admin else None
nsx_statuses = nsx_utils.get_nsx_device_statuses(self.cluster,
tenant_id)
# Update statuses in database
with db_api.context_manager.writer.using(context):
for device in devices:
new_status = nsx_statuses.get(device['nsx_id'])
if new_status:
device['status'] = new_status
return devices
def create_gateway_device(self, context, gateway_device):
# NOTE(salv-orlando): client-certificate will not be stored
# in the database
device_data = gateway_device[networkgw.DEVICE_RESOURCE_NAME]
client_certificate = device_data.pop('client_certificate')
gw_device = super(NsxPluginV2, self).create_gateway_device(
context, gateway_device)
# DB operation was successful, perform NSX operation
gw_device['status'] = self.create_gateway_device_handler(
context, gw_device, client_certificate)
return gw_device
def update_gateway_device(self, context, device_id,
gateway_device):
# NOTE(salv-orlando): client-certificate will not be stored
# in the database
client_certificate = (
gateway_device[networkgw.DEVICE_RESOURCE_NAME].pop(
'client_certificate', None))
# Retrive current state from DB in case a rollback should be needed
old_gw_device_data = super(NsxPluginV2, self).get_gateway_device(
context, device_id, include_nsx_id=True)
gw_device = super(NsxPluginV2, self).update_gateway_device(
context, device_id, gateway_device, include_nsx_id=True)
# DB operation was successful, perform NSX operation
gw_device['status'] = self.update_gateway_device_handler(
context, gw_device, old_gw_device_data, client_certificate)
gw_device.pop('nsx_id')
return gw_device
def delete_gateway_device(self, context, device_id):
nsx_device_id = self._get_nsx_device_id(context, device_id)
super(NsxPluginV2, self).delete_gateway_device(
context, device_id)
# DB operation was successful, perform NSX operation
# TODO(salv-orlando): State consistency with neutron DB
# should be ensured even in case of backend failures
try:
l2gwlib.delete_gateway_device(self.cluster, nsx_device_id)
except n_exc.NotFound:
LOG.warning("Removal of gateway device: %(neutron_id)s failed "
"on NSX backend (NSX id:%(nsx_id)s) because the "
"NSX resource was not found",
{'neutron_id': device_id, 'nsx_id': nsx_device_id})
except api_exc.NsxApiException:
with excutils.save_and_reraise_exception():
# In this case a 500 should be returned
LOG.exception("Removal of gateway device: %(neutron_id)s "
"failed on NSX backend (NSX id:%(nsx_id)s). "
"Neutron and NSX states have diverged.",
{'neutron_id': device_id,
'nsx_id': nsx_device_id})
def create_security_group(self, context, security_group, default_sg=False):
"""Create security group.
If default_sg is true that means we are creating a default security
group and we don't need to check if one exists.
"""
s = security_group.get('security_group')
tenant_id = s['tenant_id']
if not default_sg:
self._ensure_default_security_group(context, tenant_id)
# NOTE(salv-orlando): Pre-generating Neutron ID for security group.
neutron_id = str(uuid.uuid4())
nsx_secgroup = secgrouplib.create_security_profile(
self.cluster, tenant_id, neutron_id, s)
with db_api.context_manager.writer.using(context):
s['id'] = neutron_id
sec_group = super(NsxPluginV2, self).create_security_group(
context, security_group, default_sg)
context.session.flush()
# Add mapping between neutron and nsx identifiers
nsx_db.add_neutron_nsx_security_group_mapping(
context.session, neutron_id, nsx_secgroup['uuid'])
return sec_group
def update_security_group(self, context, secgroup_id, security_group):
secgroup = (super(NsxPluginV2, self).
update_security_group(context,
secgroup_id,
security_group))
if ('name' in security_group['security_group'] and
secgroup['name'] != 'default'):
nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id(
context.session, self.cluster, secgroup_id)
try:
name = security_group['security_group']['name']
secgrouplib.update_security_profile(
self.cluster, nsx_sec_profile_id, name)
except (n_exc.NotFound, api_exc.NsxApiException) as e:
# Reverting the DB change is not really worthwhile
# for a mismatch between names. It's the rules that
# we care about.
LOG.error('Error while updating security profile '
'%(uuid)s with name %(name)s: %(error)s.',
{'uuid': secgroup_id, 'name': name, 'error': e})
return secgroup
def delete_security_group(self, context, security_group_id):
"""Delete a security group.
:param security_group_id: security group rule to remove.
"""
with db_api.context_manager.writer.using(context):
security_group = super(NsxPluginV2, self).get_security_group(
context, security_group_id)
if not security_group:
raise ext_sg.SecurityGroupNotFound(id=security_group_id)
if security_group['name'] == 'default' and not context.is_admin:
raise ext_sg.SecurityGroupCannotRemoveDefault()
filters = {'security_group_id': [security_group['id']]}
if super(NsxPluginV2, self)._get_port_security_group_bindings(
context, filters):
raise ext_sg.SecurityGroupInUse(id=security_group['id'])
nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id(
context.session, self.cluster, security_group_id)
try:
secgrouplib.delete_security_profile(
self.cluster, nsx_sec_profile_id)
except n_exc.NotFound:
# The security profile was not found on the backend
# do not fail in this case.
LOG.warning("The NSX security profile %(sec_profile_id)s, "
"associated with the Neutron security group "
"%(sec_group_id)s was not found on the "
"backend",
{'sec_profile_id': nsx_sec_profile_id,
'sec_group_id': security_group_id})
except api_exc.NsxApiException:
# Raise and fail the operation, as there is a problem which
# prevented the sec group from being removed from the backend
LOG.exception("An exception occurred while removing the "
"NSX security profile %(sec_profile_id)s, "
"associated with Netron security group "
"%(sec_group_id)s",
{'sec_profile_id': nsx_sec_profile_id,
'sec_group_id': security_group_id})
raise nsx_exc.NsxPluginException(
_("Unable to remove security group %s from backend"),
security_group['id'])
return super(NsxPluginV2, self).delete_security_group(
context, security_group_id)
def _validate_security_group_rules(self, context, rules):
for rule in rules['security_group_rules']:
r = rule.get('security_group_rule')
port_based_proto = (self._get_ip_proto_number(r['protocol'])
in constants.IP_PROTOCOL_MAP.values())
if (not port_based_proto and
(r['port_range_min'] is not None or
r['port_range_max'] is not None)):
msg = (_("Port values not valid for "
"protocol: %s") % r['protocol'])
raise n_exc.BadRequest(resource='security_group_rule',
msg=msg)
return super(NsxPluginV2, self)._validate_security_group_rules(context,
rules)
def create_security_group_rule(self, context, security_group_rule):
"""Create a single security group rule."""
bulk_rule = {'security_group_rules': [security_group_rule]}
return self.create_security_group_rule_bulk(context, bulk_rule)[0]
def create_security_group_rule_bulk(self, context, security_group_rules):
"""Create security group rules.
:param security_group_rule: list of rules to create
"""
s = security_group_rules.get('security_group_rules')
# TODO(arosen) is there anyway we could avoid having the update of
# the security group rules in nsx outside of this transaction?
with db_api.context_manager.writer.using(context):
security_group_id = self._validate_security_group_rules(
context, security_group_rules)
# Check to make sure security group exists
security_group = super(NsxPluginV2, self).get_security_group(
context, security_group_id)
if not security_group:
raise ext_sg.SecurityGroupNotFound(id=security_group_id)
# Check for duplicate rules
self._check_for_duplicate_rules(context, s)
# gather all the existing security group rules since we need all
# of them to PUT to NSX.
existing_rules = self.get_security_group_rules(
context, {'security_group_id': [security_group['id']]})
combined_rules = sg_utils.merge_security_group_rules_with_current(
context.session, self.cluster, s, existing_rules)
nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id(
context.session, self.cluster, security_group_id)
secgrouplib.update_security_group_rules(self.cluster,
nsx_sec_profile_id,
combined_rules)
return super(
NsxPluginV2, self).create_security_group_rule_bulk_native(
context, security_group_rules)
def delete_security_group_rule(self, context, sgrid):
"""Delete a security group rule
:param sgrid: security group id to remove.
"""
with db_api.context_manager.writer.using(context):
# determine security profile id
security_group_rule = (
super(NsxPluginV2, self).get_security_group_rule(
context, sgrid))
if not security_group_rule:
raise ext_sg.SecurityGroupRuleNotFound(id=sgrid)
sgid = security_group_rule['security_group_id']
current_rules = self.get_security_group_rules(
context, {'security_group_id': [sgid]})
current_rules_nsx = sg_utils.get_security_group_rules_nsx_format(
context.session, self.cluster, current_rules, True)
sg_utils.remove_security_group_with_id_and_id_field(
current_rules_nsx, sgrid)
nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id(
context.session, self.cluster, sgid)
secgrouplib.update_security_group_rules(
self.cluster, nsx_sec_profile_id, current_rules_nsx)
return super(NsxPluginV2, self).delete_security_group_rule(context,
sgrid)
def create_qos_queue(self, context, qos_queue, check_policy=True):
q = qos_queue.get('qos_queue')
self._validate_qos_queue(context, q)
q['id'] = queuelib.create_lqueue(self.cluster, q)
return super(NsxPluginV2, self).create_qos_queue(context, qos_queue)
def delete_qos_queue(self, context, queue_id, raise_in_use=True):
filters = {'queue_id': [queue_id]}
queues = self._get_port_queue_bindings(context, filters)
if queues:
if raise_in_use:
raise qos.QueueInUseByPort()
else:
return
queuelib.delete_lqueue(self.cluster, queue_id)
return super(NsxPluginV2, self).delete_qos_queue(context, queue_id)