A set of Neutron drivers for the VMware NSX.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

4844 lines
234 KiB

# Copyright 2015 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 netaddr
from neutron_lib.agent import topics
from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef
from neutron_lib.api.definitions import availability_zone as az_def
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.api.definitions import port_security as psec
from neutron_lib.api import extensions
from neutron_lib.api import faults
from neutron_lib.api.validators import availability_zone as az_validator
from neutron_lib.exceptions import allowedaddresspairs as addr_exc
from neutron_lib.exceptions import l3 as l3_exc
from neutron_lib.exceptions import port_security as psec_exc
from neutron_lib.plugins import constants as plugin_const
from neutron_lib.plugins import directory
from neutron_lib.services.qos import constants as qos_consts
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.rpc.handlers import dhcp_rpc
from neutron.api.rpc.handlers import metadata_rpc
from neutron.common import rpc as n_rpc
from neutron.db import _resource_extend as resource_extend
from neutron.db import _utils as db_utils
from neutron.db import agents_db
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.availability_zone import router as router_az_db
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_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 securitygroups_db
from neutron.db import vlantransparent_db
from neutron.extensions import providernet
from neutron.extensions import securitygroup as ext_sg
from neutron.quota import resource_registry
from neutron_lib.api.definitions import extra_dhcp_opt as ext_edo
from neutron_lib.api.definitions import portbindings as pbin
from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.api.definitions import vlantransparent as vlan_apidef
from neutron_lib.api import validators
from neutron_lib.callbacks import events
from neutron_lib.callbacks import exceptions as callback_exc
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants as const
from neutron_lib import context as q_context
from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import utils as plugin_utils
from neutron_lib.utils import helpers
from neutron_lib.utils import net as nlib_net
from oslo_config import cfg
from oslo_context import context as context_utils
from oslo_db import exception as db_exc
from oslo_log import log
from oslo_utils import excutils
from oslo_utils import importutils
from oslo_utils import uuidutils
from six import moves
from sqlalchemy import exc as sql_exc
import webob.exc
from vmware_nsx._i18n import _
from vmware_nsx.api_replay import utils as api_replay_utils
from vmware_nsx.common import availability_zones as nsx_com_az
from vmware_nsx.common import config # noqa
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import l3_rpc_agent_api
from vmware_nsx.common import locking
from vmware_nsx.common import managers
from vmware_nsx.common import nsx_constants
from vmware_nsx.common import utils
from vmware_nsx.db import db as nsx_db
from vmware_nsx.db import extended_security_group
from vmware_nsx.db import extended_security_group_rule as extend_sg_rule
from vmware_nsx.db import maclearning as mac_db
from vmware_nsx.dhcp_meta import rpc as nsx_rpc
from vmware_nsx.extensions import advancedserviceproviders as as_providers
from vmware_nsx.extensions import maclearning as mac_ext
from vmware_nsx.extensions import projectpluginmap
from vmware_nsx.extensions import providersecuritygroup as provider_sg
from vmware_nsx.extensions import securitygrouplogging as sg_logging
from vmware_nsx.plugins.common import plugin as nsx_plugin_common
from vmware_nsx.plugins.nsx import utils as tvd_utils
from vmware_nsx.plugins.nsx_v3 import availability_zones as nsx_az
from vmware_nsx.plugins.nsx_v3 import utils as v3_utils
from vmware_nsx.services.fwaas.common import utils as fwaas_utils
from vmware_nsx.services.fwaas.nsx_v3 import fwaas_callbacks_v1
from vmware_nsx.services.fwaas.nsx_v3 import fwaas_callbacks_v2
from vmware_nsx.services.lbaas.nsx_v3 import lb_driver_v2
from vmware_nsx.services.qos.common import utils as qos_com_utils
from vmware_nsx.services.qos.nsx_v3 import driver as qos_driver
from vmware_nsx.services.trunk.nsx_v3 import driver as trunk_driver
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_driver
from vmware_nsxlib.v3 import core_resources as nsx_resources
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
from vmware_nsxlib.v3 import router as nsxlib_router
from vmware_nsxlib.v3 import security
from vmware_nsxlib.v3 import utils as nsxlib_utils
LOG = log.getLogger(__name__)
NSX_V3_PSEC_PROFILE_NAME = 'neutron_port_spoof_guard_profile'
NSX_V3_NO_PSEC_PROFILE_NAME = 'nsx-default-spoof-guard-vif-profile'
NSX_V3_DHCP_PROFILE_NAME = 'neutron_port_dhcp_profile'
NSX_V3_MAC_LEARNING_PROFILE_NAME = 'neutron_port_mac_learning_profile'
NSX_V3_FW_DEFAULT_SECTION = 'OS Default Section for Neutron Security-Groups'
NSX_V3_FW_DEFAULT_NS_GROUP = 'os_default_section_ns_group'
NSX_V3_DEFAULT_SECTION = 'OS-Default-Section'
NSX_V3_EXCLUDED_PORT_NSGROUP_NAME = 'neutron_excluded_port_nsgroup'
NSX_V3_NON_VIF_PROFILE = 'nsx-default-switch-security-non-vif-profile'
NSX_V3_SERVER_SSL_PROFILE = 'nsx-default-server-ssl-profile'
NSX_V3_CLIENT_SSL_PROFILE = 'nsx-default-client-ssl-profile'
# Default UUID for the global OS rule
NSX_V3_OS_DFW_UUID = '00000000-def0-0000-0fed-000000000000'
def inject_headers():
ctx = context_utils.get_current()
if ctx:
ctx_dict = ctx.to_dict()
return {'X-NSX-EUSER': ctx_dict.get('user_identity'),
'X-NSX-EREQID': ctx_dict.get('request_id')}
return {}
def inject_requestid_header():
ctx = context_utils.get_current()
if ctx:
return {'X-NSX-EREQID': ctx.__dict__.get('request_id')}
return {}
# NOTE(asarfaty): the order of inheritance here is important. in order for the
# QoS notification to work, the AgentScheduler init must be called first
# NOTE(arosen): same is true with the ExtendedSecurityGroupPropertiesMixin
# this needs to be above securitygroups_db.SecurityGroupDbMixin.
# FIXME(arosen): we can solve this inheritance order issue by just mixining in
# the classes into a new class to handle the order correctly.
@resource_extend.has_resource_extenders
class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
extended_security_group.ExtendedSecurityGroupPropertiesMixin,
addr_pair_db.AllowedAddressPairsMixin,
nsx_plugin_common.NsxPluginBase,
extend_sg_rule.ExtendedSecurityGroupRuleMixin,
securitygroups_db.SecurityGroupDbMixin,
external_net_db.External_net_db_mixin,
extraroute_db.ExtraRoute_db_mixin,
router_az_db.RouterAvailabilityZoneMixin,
l3_gwmode_db.L3_NAT_db_mixin,
portbindings_db.PortBindingMixin,
portsecurity_db.PortSecurityDbMixin,
extradhcpopt_db.ExtraDhcpOptMixin,
dns_db.DNSDbMixin,
vlantransparent_db.Vlantransparent_db_mixin,
mac_db.MacLearningDbMixin,
nsx_com_az.NSXAvailabilityZonesPluginCommon,
l3_attrs_db.ExtraAttributesMixin):
__native_bulk_support = True
__native_pagination_support = True
__native_sorting_support = True
supported_extension_aliases = ["allowed-address-pairs",
"address-scope",
"quotas",
"binding",
"extra_dhcp_opt",
"agent",
"dhcp_agent_scheduler",
"ext-gw-mode",
"security-group",
"secgroup-rule-local-ip-prefix",
"port-security",
"provider",
"external-net",
"extraroute",
"router",
"availability_zone",
"network_availability_zone",
"router_availability_zone",
"subnet_allocation",
"security-group-logging",
"provider-security-group",
"port-security-groups-filtering"]
@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):
self.fwaas_callbacks = None
self._is_sub_plugin = tvd_utils.is_tvd_core_plugin()
self.init_is_complete = False
nsxlib_utils.set_is_attr_callback(validators.is_attr_set)
self._extend_fault_map()
if self._is_sub_plugin:
extension_drivers = cfg.CONF.nsx_tvd.nsx_v3_extension_drivers
self._update_project_mapping()
else:
extension_drivers = cfg.CONF.nsx_extension_drivers
self._extension_manager = managers.ExtensionManager(
extension_drivers=extension_drivers)
super(NsxV3Plugin, self).__init__()
# Bind the dummy L3 notifications
self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI()
LOG.info("Starting NsxV3Plugin")
self._extension_manager.initialize()
self.supported_extension_aliases.extend(
self._extension_manager.extension_aliases())
self.nsxlib = v3_utils.get_nsxlib_wrapper()
if self.nsxlib.feature_supported(nsxlib_consts.FEATURE_ON_BEHALF_OF):
nsxlib_utils.set_inject_headers_callback(inject_headers)
else:
nsxlib_utils.set_inject_headers_callback(inject_requestid_header)
self.lbv2_driver = self._init_lbv2_driver()
registry.subscribe(
self.on_subnetpool_address_scope_updated,
resources.SUBNETPOOL_ADDRESS_SCOPE, events.AFTER_UPDATE)
self._nsx_version = self.nsxlib.get_version()
LOG.info("NSX Version: %s", self._nsx_version)
self.cfg_group = 'nsx_v3' # group name for nsx_v3 section in nsx.ini
self.tier0_groups_dict = {}
self._network_vlans = plugin_utils.parse_network_vlan_ranges(
cfg.CONF.nsx_v3.network_vlan_ranges)
# Initialize the network availability zones, which will be used only
# when native_dhcp_metadata is True
self.init_availability_zones()
# Translate configured transport zones, routers, dhcp profile and
# metadata proxy names to uuid.
self._translate_configured_names_to_uuids()
self._init_dhcp_metadata()
self._prepare_default_rules()
self._process_security_group_logging()
# init profiles on nsx backend
self._init_nsx_profiles()
# Include exclude NSGroup
LOG.debug("Initializing NSX v3 Excluded Port NSGroup")
self._excluded_port_nsgroup = None
self._excluded_port_nsgroup = self._init_excluded_port_nsgroup()
if not self._excluded_port_nsgroup:
msg = _("Unable to initialize NSX v3 Excluded Port NSGroup %s"
) % NSX_V3_EXCLUDED_PORT_NSGROUP_NAME
raise nsx_exc.NsxPluginException(err_msg=msg)
qos_driver.register()
self.start_rpc_listeners_called = False
self._unsubscribe_callback_events()
if cfg.CONF.api_replay_mode:
self.supported_extension_aliases.append('api-replay')
# Support transparent VLANS from 2.2.0 onwards. The feature is only
# supported if the global configuration flag vlan_transparent is
# True
if cfg.CONF.vlan_transparent:
if self.nsxlib.feature_supported(nsxlib_consts.FEATURE_TRUNK_VLAN):
self.supported_extension_aliases.append("vlan-transparent")
else:
LOG.warning("Current NSX version %s doesn't support "
"transparent vlans", self.nsxlib.get_version())
# Register NSXv3 trunk driver to support trunk extensions
self.trunk_driver = trunk_driver.NsxV3TrunkDriver.create(self)
# subscribe the init complete method last, so it will be called only
# if init was successful
registry.subscribe(self.init_complete,
resources.PROCESS,
events.AFTER_INIT)
def _update_project_mapping(self):
ctx = q_context.get_admin_context()
try:
nsx_db.add_project_plugin_mapping(
ctx.session,
nsx_constants.INTERNAL_V3_TENANT_ID,
projectpluginmap.NsxPlugins.NSX_T)
except db_exc.DBDuplicateEntry:
pass
def _ensure_default_rules(self):
# Include default section NSGroup
LOG.debug("Initializing NSX v3 default section NSGroup")
self._default_section_nsgroup = None
self._default_section_nsgroup = self._init_default_section_nsgroup()
if not self._default_section_nsgroup:
msg = _("Unable to initialize NSX v3 default section NSGroup %s"
) % NSX_V3_FW_DEFAULT_NS_GROUP
raise nsx_exc.NsxPluginException(err_msg=msg)
self.default_section = self._init_default_section_rules()
def _ensure_global_sg_placeholder(self, context):
try:
super(NsxV3Plugin, self).get_security_group(
context, NSX_V3_OS_DFW_UUID, fields=['id'])
except ext_sg.SecurityGroupNotFound:
sec_group = {'security_group':
{'id': NSX_V3_OS_DFW_UUID,
'tenant_id': nsx_constants.INTERNAL_V3_TENANT_ID,
'name': 'NSX Internal',
'description': ''}}
try:
# ensure that the global default is created
super(NsxV3Plugin, self).create_security_group(
context, sec_group, True)
except Exception:
# Treat a race of multiple processing creating the seg group
LOG.warning('Unable to create global security group')
def _prepare_default_rules(self):
ctx = q_context.get_admin_context()
# Need a global placeholder as the DB below has a foreign key to
# this security group
self._ensure_global_sg_placeholder(ctx)
self._ensure_default_rules()
# Validate if there is a race between processes
nsgroup_id, section_id = nsx_db.get_sg_mappings(
ctx.session, NSX_V3_OS_DFW_UUID)
if nsgroup_id is None or section_id is None:
default_ns_group_id = self._default_section_nsgroup.get('id')
try:
nsx_db.save_sg_mappings(ctx,
NSX_V3_OS_DFW_UUID,
default_ns_group_id,
self.default_section)
except Exception:
LOG.warning("Duplicate rules created. Cleaning up!")
# Delete duplicates created
self.nsxlib.firewall_section.delete(self.default_section)
self.nsxlib.ns_group.delete(default_ns_group_id)
# Ensure global variables are updated
self._ensure_default_rules()
@staticmethod
def plugin_type():
return projectpluginmap.NsxPlugins.NSX_T
@staticmethod
def is_tvd_plugin():
return False
def init_complete(self, resource, event, trigger, payload=None):
with locking.LockManager.get_lock('plugin-init-complete'):
if self.init_is_complete:
# Should be called only once per worker
return
# reinitialize the cluster upon fork for api workers to ensure
# each process has its own keepalive loops + state
self.nsxlib.reinitialize_cluster(resource, event, trigger,
payload=payload)
# Init the FWaaS support
self._init_fwaas()
self.init_is_complete = True
def _extend_fault_map(self):
"""Extends the Neutron Fault Map.
Exceptions specific to the NSX Plugin are mapped to standard
HTTP Exceptions.
"""
faults.FAULT_MAP.update({nsx_lib_exc.ManagerError:
webob.exc.HTTPBadRequest,
nsx_lib_exc.ServiceClusterUnavailable:
webob.exc.HTTPServiceUnavailable,
nsx_lib_exc.ClientCertificateNotTrusted:
webob.exc.HTTPBadRequest,
nsx_exc.SecurityGroupMaximumCapacityReached:
webob.exc.HTTPBadRequest,
nsx_lib_exc.NsxLibInvalidInput:
webob.exc.HTTPBadRequest,
nsx_exc.NsxENSPortSecurity:
webob.exc.HTTPBadRequest,
})
def _init_fwaas(self):
if fwaas_utils.is_fwaas_v1_plugin_enabled():
LOG.info("NSXv3 FWaaS v1 plugin enabled")
self.fwaas_callbacks = fwaas_callbacks_v1.Nsxv3FwaasCallbacksV1()
if fwaas_utils.is_fwaas_v2_plugin_enabled():
LOG.info("NSXv3 FWaaS v2 plugin enabled")
self.fwaas_callbacks = fwaas_callbacks_v2.Nsxv3FwaasCallbacksV2()
def _init_lbv2_driver(self):
# Get LBaaSv2 driver during plugin initialization. If the platform
# has a version that doesn't support native loadbalancing, the driver
# will return a NotImplementedManager class.
LOG.debug("Initializing LBaaSv2.0 nsxv3 driver")
if self.nsxlib.feature_supported(nsxlib_consts.FEATURE_LOAD_BALANCER):
return lb_driver_v2.EdgeLoadbalancerDriverV2()
else:
LOG.warning("Current NSX version %(ver)s doesn't support LBaaS",
{'ver': self.nsxlib.get_version()})
return lb_driver_v2.DummyLoadbalancerDriverV2()
def init_availability_zones(self):
self._availability_zones_data = nsx_az.NsxV3AvailabilityZones(
use_tvd_config=self._is_sub_plugin)
def _init_nsx_profiles(self):
LOG.debug("Initializing NSX v3 port spoofguard switching profile")
if not self._init_port_security_profile():
msg = _("Unable to initialize NSX v3 port spoofguard "
"switching profile: %s") % NSX_V3_PSEC_PROFILE_NAME
raise nsx_exc.NsxPluginException(err_msg=msg)
profile_client = self.nsxlib.switching_profile
no_psec_prof = profile_client.find_by_display_name(
NSX_V3_NO_PSEC_PROFILE_NAME)[0]
self._no_psec_profile_id = profile_client.build_switch_profile_ids(
profile_client, no_psec_prof)[0]
LOG.debug("Initializing NSX v3 DHCP switching profile")
try:
self._init_dhcp_switching_profile()
except Exception as e:
msg = (_("Unable to initialize NSX v3 DHCP switching profile: "
"%(id)s. Reason: %(reason)s") % {
'id': NSX_V3_DHCP_PROFILE_NAME,
'reason': str(e)})
raise nsx_exc.NsxPluginException(err_msg=msg)
self._mac_learning_profile = None
# Only create MAC Learning profile when nsxv3 version >= 1.1.0
if self.nsxlib.feature_supported(nsxlib_consts.FEATURE_MAC_LEARNING):
LOG.debug("Initializing NSX v3 Mac Learning switching profile")
try:
self._init_mac_learning_profile()
# Only expose the extension if it is supported
self.supported_extension_aliases.append('mac-learning')
except Exception as e:
LOG.warning("Unable to initialize NSX v3 MAC Learning "
"profile: %(name)s. Reason: %(reason)s",
{'name': NSX_V3_MAC_LEARNING_PROFILE_NAME,
'reason': e})
no_switch_security_prof = profile_client.find_by_display_name(
NSX_V3_NON_VIF_PROFILE)[0]
self._no_switch_security = profile_client.build_switch_profile_ids(
profile_client, no_switch_security_prof)[0]
self.server_ssl_profile = None
self.client_ssl_profile = None
# Only create LB profiles when nsxv3 version >= 2.1.0
if self.nsxlib.feature_supported(nsxlib_consts.FEATURE_LOAD_BALANCER):
LOG.debug("Initializing NSX v3 Load Balancer default profiles")
try:
self._init_lb_profiles()
except Exception as e:
msg = (_("Unable to initialize NSX v3 lb profiles: "
"Reason: %(reason)s") % {'reason': str(e)})
raise nsx_exc.NsxPluginException(err_msg=msg)
def _translate_configured_names_to_uuids(self):
# If using tags to find the objects, make sure tag scope is configured
if (cfg.CONF.nsx_v3.init_objects_by_tags and
not cfg.CONF.nsx_v3.search_objects_scope):
raise cfg.RequiredOptError("search_objects_scope",
group=cfg.OptGroup('nsx_v3'))
# Validate and translate native dhcp profiles per az
if cfg.CONF.nsx_v3.native_dhcp_metadata:
if not cfg.CONF.nsx_v3.dhcp_profile:
raise cfg.RequiredOptError("dhcp_profile",
group=cfg.OptGroup('nsx_v3'))
if not cfg.CONF.nsx_v3.metadata_proxy:
raise cfg.RequiredOptError("metadata_proxy",
group=cfg.OptGroup('nsx_v3'))
# Translate all the uuids in each of the availability
for az in self.get_azs_list():
az.translate_configured_names_to_uuids(self.nsxlib)
def _extend_nsx_port_dict_binding(self, context, port_data):
# Not using the register api for this because we need the context
port_data[pbin.VIF_TYPE] = pbin.VIF_TYPE_OVS
port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL
if 'network_id' in port_data:
port_data[pbin.VIF_DETAILS] = {
# TODO(rkukura): Replace with new VIF security details
pbin.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases,
'nsx-logical-switch-id':
self._get_network_nsx_id(context, port_data['network_id'])}
@nsxlib_utils.retry_upon_exception(
Exception, max_attempts=cfg.CONF.nsx_v3.retries)
def _init_default_section_nsgroup(self):
with locking.LockManager.get_lock('nsxv3_init_default_nsgroup'):
nsgroup = self._get_default_section_nsgroup()
if not nsgroup:
# Create a new NSGroup for default section
membership_criteria = (
self.nsxlib.ns_group.get_port_tag_expression(
security.PORT_SG_SCOPE, NSX_V3_DEFAULT_SECTION))
nsgroup = self.nsxlib.ns_group.create(
NSX_V3_FW_DEFAULT_NS_GROUP,
'OS Default Section Port NSGroup',
tags=self.nsxlib.build_v3_api_version_tag(),
membership_criteria=membership_criteria)
return self._get_default_section_nsgroup()
def _get_default_section_nsgroup(self):
if self._default_section_nsgroup:
return self._default_section_nsgroup
nsgroups = self.nsxlib.ns_group.find_by_display_name(
NSX_V3_FW_DEFAULT_NS_GROUP)
return nsgroups[0] if nsgroups else None
@nsxlib_utils.retry_upon_exception(
Exception, max_attempts=cfg.CONF.nsx_v3.retries)
def _init_excluded_port_nsgroup(self):
with locking.LockManager.get_lock('nsxv3_excluded_port_nsgroup_init'):
nsgroup = self._get_excluded_port_nsgroup()
if not nsgroup:
# Create a new NSGroup for excluded ports.
membership_criteria = (
self.nsxlib.ns_group.get_port_tag_expression(
security.PORT_SG_SCOPE, nsxlib_consts.EXCLUDE_PORT))
nsgroup = self.nsxlib.ns_group.create(
NSX_V3_EXCLUDED_PORT_NSGROUP_NAME,
'Neutron Excluded Port NSGroup',
tags=self.nsxlib.build_v3_api_version_tag(),
membership_criteria=membership_criteria)
# Add this NSGroup to NSX Exclusion List.
self.nsxlib.firewall_section.add_member_to_fw_exclude_list(
nsgroup['id'], nsxlib_consts.NSGROUP)
return self._get_excluded_port_nsgroup()
def _get_excluded_port_nsgroup(self):
if self._excluded_port_nsgroup:
return self._excluded_port_nsgroup
nsgroups = self.nsxlib.ns_group.find_by_display_name(
NSX_V3_EXCLUDED_PORT_NSGROUP_NAME)
return nsgroups[0] if nsgroups else None
def _unsubscribe_callback_events(self):
# l3_db explicitly subscribes to the port delete callback. This
# callback is unsubscribed here since l3 APIs are handled by
# core_plugin instead of an advanced service, in case of NSXv3 plugin,
# and the prevention logic is handled by NSXv3 plugin itself.
registry.unsubscribe(
l3_db.L3_NAT_dbonly_mixin._prevent_l3_port_delete_callback,
resources.PORT,
events.BEFORE_DELETE)
def _validate_dhcp_profile(self, dhcp_profile_uuid):
dhcp_profile = self.nsxlib.switching_profile.get(dhcp_profile_uuid)
if (dhcp_profile.get('resource_type') !=
nsx_resources.SwitchingProfileTypes.SWITCH_SECURITY):
msg = _("Invalid configuration on the backend for DHCP "
"switching profile %s. Switching Profile must be of type "
"'Switch Security'") % dhcp_profile_uuid
raise n_exc.InvalidInput(error_message=msg)
dhcp_filter = dhcp_profile.get('dhcp_filter')
if (not dhcp_filter or dhcp_filter.get('client_block_enabled') or
dhcp_filter.get('server_block_enabled')):
msg = _("Invalid configuration on the backend for DHCP "
"switching profile %s. DHCP Server Block and Client Block "
"must be disabled") % dhcp_profile_uuid
raise n_exc.InvalidInput(error_message=msg)
@nsxlib_utils.retry_upon_exception(
Exception, max_attempts=cfg.CONF.nsx_v3.retries)
def _init_dhcp_switching_profile(self):
with locking.LockManager.get_lock('nsxv3_dhcp_profile_init'):
if not self._get_dhcp_security_profile():
self.nsxlib.switching_profile.create_dhcp_profile(
NSX_V3_DHCP_PROFILE_NAME, 'Neutron DHCP Security Profile',
tags=self.nsxlib.build_v3_api_version_tag())
return self._get_dhcp_security_profile()
def _get_dhcp_security_profile(self):
if hasattr(self, '_dhcp_profile') and self._dhcp_profile:
return self._dhcp_profile
profile = self.nsxlib.switching_profile.find_by_display_name(
NSX_V3_DHCP_PROFILE_NAME)
self._dhcp_profile = nsx_resources.SwitchingProfileTypeId(
profile_type=(nsx_resources.SwitchingProfileTypes.
SWITCH_SECURITY),
profile_id=profile[0]['id']) if profile else None
return self._dhcp_profile
def _init_mac_learning_profile(self):
with locking.LockManager.get_lock('nsxv3_mac_learning_profile_init'):
if not self._get_mac_learning_profile():
self.nsxlib.switching_profile.create_mac_learning_profile(
NSX_V3_MAC_LEARNING_PROFILE_NAME,
'Neutron MAC Learning Profile',
tags=self.nsxlib.build_v3_api_version_tag())
return self._get_mac_learning_profile()
def _get_mac_learning_profile(self):
if (hasattr(self, '_mac_learning_profile')
and self._mac_learning_profile):
return self._mac_learning_profile
profile = self.nsxlib.switching_profile.find_by_display_name(
NSX_V3_MAC_LEARNING_PROFILE_NAME)
self._mac_learning_profile = nsx_resources.SwitchingProfileTypeId(
profile_type=(nsx_resources.SwitchingProfileTypes.
MAC_LEARNING),
profile_id=profile[0]['id']) if profile else None
return self._mac_learning_profile
def _init_lb_profiles(self):
with locking.LockManager.get_lock('nsxv3_lb_profiles_init'):
lb_profiles = self._get_lb_profiles()
if not lb_profiles.get('client_ssl_profile'):
self.nsxlib.load_balancer.client_ssl_profile.create(
NSX_V3_CLIENT_SSL_PROFILE,
'Neutron LB Client SSL Profile',
tags=self.nsxlib.build_v3_api_version_tag())
if not lb_profiles.get('server_ssl_profile'):
self.nsxlib.load_balancer.server_ssl_profile.create(
NSX_V3_SERVER_SSL_PROFILE,
'Neutron LB Server SSL Profile',
tags=self.nsxlib.build_v3_api_version_tag())
def _get_lb_profiles(self):
if not self.client_ssl_profile:
ssl_profile_client = self.nsxlib.load_balancer.client_ssl_profile
profile = ssl_profile_client.find_by_display_name(
NSX_V3_CLIENT_SSL_PROFILE)
self.client_ssl_profile = profile[0]['id'] if profile else None
if not self.server_ssl_profile:
ssl_profile_client = self.nsxlib.load_balancer.server_ssl_profile
profile = ssl_profile_client.find_by_display_name(
NSX_V3_SERVER_SSL_PROFILE)
self.server_ssl_profile = profile[0]['id'] if profile else None
return {'client_ssl_profile': self.client_ssl_profile,
'server_ssl_profile': self.server_ssl_profile}
def _get_port_security_profile_id(self):
return self.nsxlib.switching_profile.build_switch_profile_ids(
self.nsxlib.switching_profile, self._psec_profile)[0]
def _get_port_security_profile(self):
if hasattr(self, '_psec_profile') and self._psec_profile:
return self._psec_profile
profile = self.nsxlib.switching_profile.find_by_display_name(
NSX_V3_PSEC_PROFILE_NAME)
self._psec_profile = profile[0] if profile else None
return self._psec_profile
@nsxlib_utils.retry_upon_exception(
Exception, max_attempts=cfg.CONF.nsx_v3.retries)
def _init_port_security_profile(self):
profile = self._get_port_security_profile()
if profile:
return profile
with locking.LockManager.get_lock('nsxv3_psec_profile_init'):
# NOTE(boden): double-checked locking pattern
profile = self._get_port_security_profile()
if profile:
return profile
self.nsxlib.switching_profile.create_spoofguard_profile(
NSX_V3_PSEC_PROFILE_NAME, 'Neutron Port Security Profile',
whitelist_ports=True, whitelist_switches=False,
tags=self.nsxlib.build_v3_api_version_tag())
return self._get_port_security_profile()
def _process_security_group_logging(self):
def process_security_group_logging(*args, **kwargs):
context = q_context.get_admin_context()
log_all_rules = cfg.CONF.nsx_v3.log_security_groups_allowed_traffic
secgroups = self.get_security_groups(context,
fields=['id',
sg_logging.LOGGING])
for sg in [sg for sg in secgroups
if sg.get(sg_logging.LOGGING) is False]:
nsgroup_id, section_id = nsx_db.get_sg_mappings(
context.session, sg['id'])
if section_id:
try:
self.nsxlib.firewall_section.set_rule_logging(
section_id, logging=log_all_rules)
except nsx_lib_exc.ManagerError:
LOG.error("Failed to update firewall rule logging "
"for rule in section %s", section_id)
utils.spawn_n(process_security_group_logging)
def _init_default_section_rules(self):
with locking.LockManager.get_lock('nsxv3_default_section'):
section_description = ("This section is handled by OpenStack to "
"contain default rules on security-groups.")
section_id = self.nsxlib.firewall_section.init_default(
NSX_V3_FW_DEFAULT_SECTION, section_description,
[self._default_section_nsgroup.get('id')],
cfg.CONF.nsx_v3.log_security_groups_blocked_traffic)
return section_id
def _init_dhcp_metadata(self):
if cfg.CONF.nsx_v3.native_dhcp_metadata:
if cfg.CONF.dhcp_agent_notification:
msg = _("Need to disable dhcp_agent_notification when "
"native_dhcp_metadata is enabled")
raise nsx_exc.NsxPluginException(err_msg=msg)
self._init_native_dhcp()
self._init_native_metadata()
else:
self._setup_dhcp()
self._start_rpc_notifiers()
def _init_native_dhcp(self):
try:
for az in self.get_azs_list():
self.nsxlib.native_dhcp_profile.get(
az._native_dhcp_profile_uuid)
except nsx_lib_exc.ManagerError:
with excutils.save_and_reraise_exception():
LOG.error("Unable to retrieve DHCP Profile %s, "
"native DHCP service is not supported",
az._native_dhcp_profile_uuid)
def _init_native_metadata(self):
try:
for az in self.get_azs_list():
self.nsxlib.native_md_proxy.get(az._native_md_proxy_uuid)
except nsx_lib_exc.ManagerError:
with excutils.save_and_reraise_exception():
LOG.error("Unable to retrieve Metadata Proxy %s, "
"native metadata service is not supported",
az._native_md_proxy_uuid)
def _setup_rpc(self):
self.endpoints = [dhcp_rpc.DhcpRpcCallback(),
agents_db.AgentExtRpcCallback(),
metadata_rpc.MetadataRpcCallback()]
def _setup_dhcp(self):
"""Initialize components to support DHCP."""
self.network_scheduler = importutils.import_object(
cfg.CONF.network_scheduler_driver
)
self.add_periodic_dhcp_agent_status_check()
def _start_rpc_notifiers(self):
"""Initialize RPC notifiers for agents."""
self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
def start_rpc_listeners(self):
if self.start_rpc_listeners_called:
# If called more than once - we should not create it again
return self.conn.consume_in_threads()
self._setup_rpc()
self.topic = topics.PLUGIN
self.conn = n_rpc.create_connection()
self.conn.create_consumer(self.topic, self.endpoints, fanout=False)
self.conn.create_consumer(topics.REPORTS,
[agents_db.AgentExtRpcCallback()],
fanout=False)
self.start_rpc_listeners_called = True
return self.conn.consume_in_threads()
def _validate_provider_create(self, context, network_data, az,
transparent_vlan):
is_provider_net = any(
validators.is_attr_set(network_data.get(f))
for f in (pnet.NETWORK_TYPE,
pnet.PHYSICAL_NETWORK,
pnet.SEGMENTATION_ID))
physical_net = network_data.get(pnet.PHYSICAL_NETWORK)
if not validators.is_attr_set(physical_net):
physical_net = None
vlan_id = network_data.get(pnet.SEGMENTATION_ID)
if not validators.is_attr_set(vlan_id):
vlan_id = None
if vlan_id and transparent_vlan:
err_msg = (_("Segmentation ID cannot be set with transparent "
"vlan!"))
raise n_exc.InvalidInput(error_message=err_msg)
err_msg = None
net_type = network_data.get(pnet.NETWORK_TYPE)
nsxlib_tz = self.nsxlib.transport_zone
tz_type = nsxlib_tz.TRANSPORT_TYPE_VLAN
switch_mode = nsxlib_tz.HOST_SWITCH_MODE_STANDARD
if validators.is_attr_set(net_type):
if net_type == utils.NsxV3NetworkTypes.FLAT:
if vlan_id is not None:
err_msg = (_("Segmentation ID cannot be specified with "
"%s network type") %
utils.NsxV3NetworkTypes.FLAT)
else:
if not transparent_vlan:
# Set VLAN id to 0 for flat networks
vlan_id = '0'
if physical_net is None:
physical_net = az._default_vlan_tz_uuid
elif (net_type == utils.NsxV3NetworkTypes.VLAN and
not transparent_vlan):
# Use default VLAN transport zone if physical network not given
if physical_net is None:
physical_net = az._default_vlan_tz_uuid
# Validate VLAN id
if not vlan_id:
vlan_id = self._generate_segment_id(context,
physical_net,
network_data)
elif not plugin_utils.is_valid_vlan_tag(vlan_id):
err_msg = (_('Segmentation ID %(segmentation_id)s out of '
'range (%(min_id)s through %(max_id)s)') %
{'segmentation_id': vlan_id,
'min_id': const.MIN_VLAN_TAG,
'max_id': const.MAX_VLAN_TAG})
else:
# Verify VLAN id is not already allocated
bindings = (
nsx_db.get_network_bindings_by_vlanid_and_physical_net(
context.session, vlan_id, physical_net)
)
if bindings:
raise n_exc.VlanIdInUse(
vlan_id=vlan_id, physical_network=physical_net)
elif (net_type == utils.NsxV3NetworkTypes.VLAN and
transparent_vlan):
# Use default VLAN transport zone if physical network not given
if physical_net is None:
physical_net = az._default_vlan_tz_uuid
elif net_type == utils.NsxV3NetworkTypes.GENEVE:
if vlan_id:
err_msg = (_("Segmentation ID cannot be specified with "
"%s network type") %
utils.NsxV3NetworkTypes.GENEVE)
tz_type = nsxlib_tz.TRANSPORT_TYPE_OVERLAY
elif net_type == utils.NsxV3NetworkTypes.NSX_NETWORK:
# Linking neutron networks to an existing NSX logical switch
if physical_net is None:
err_msg = (_("Physical network must be specified with "
"%s network type") % net_type)
# Validate the logical switch existence
try:
nsx_net = self.nsxlib.logical_switch.get(physical_net)
switch_mode = nsxlib_tz.get_host_switch_mode(
nsx_net['transport_zone_id'])
except nsx_lib_exc.ResourceNotFound:
err_msg = (_('Logical switch %s does not exist') %
physical_net)
# make sure no other neutron network is using it
bindings = (
nsx_db.get_network_bindings_by_vlanid_and_physical_net(
context.elevated().session, 0, physical_net))
if bindings:
err_msg = (_('Logical switch %s is already used by '
'another network') % physical_net)
else:
err_msg = (_('%(net_type_param)s %(net_type_value)s not '
'supported') %
{'net_type_param': pnet.NETWORK_TYPE,
'net_type_value': net_type})
elif is_provider_net:
# FIXME: Ideally provider-network attributes should be checked
# at the NSX backend. For now, the network_type is required,
# so the plugin can do a quick check locally.
err_msg = (_('%s is required for creating a provider network') %
pnet.NETWORK_TYPE)
else:
net_type = None
if physical_net is None:
# Default to transport type overlay
physical_net = az._default_overlay_tz_uuid
# validate the transport zone existence and type
if (not err_msg and physical_net and
net_type != utils.NsxV3NetworkTypes.NSX_NETWORK):
if is_provider_net:
try:
backend_type = nsxlib_tz.get_transport_type(
physical_net)
except nsx_lib_exc.ResourceNotFound:
err_msg = (_('Transport zone %s does not exist') %
physical_net)
else:
if backend_type != tz_type:
err_msg = (_('%(tz)s transport zone is required for '
'creating a %(net)s provider network') %
{'tz': tz_type, 'net': net_type})
if not err_msg:
switch_mode = nsxlib_tz.get_host_switch_mode(physical_net)
if err_msg:
raise n_exc.InvalidInput(error_message=err_msg)
return {'is_provider_net': is_provider_net,
'net_type': net_type,
'physical_net': physical_net,
'vlan_id': vlan_id,
'switch_mode': switch_mode}
def _get_edge_cluster(self, tier0_uuid):
self.nsxlib.router.validate_tier0(self.tier0_groups_dict, tier0_uuid)
tier0_info = self.tier0_groups_dict[tier0_uuid]
return tier0_info['edge_cluster_uuid']
def _validate_external_net_create(self, net_data, az):
if not validators.is_attr_set(net_data.get(pnet.PHYSICAL_NETWORK)):
tier0_uuid = az._default_tier0_router
else:
tier0_uuid = net_data[pnet.PHYSICAL_NETWORK]
if ((validators.is_attr_set(net_data.get(pnet.NETWORK_TYPE)) and
net_data.get(pnet.NETWORK_TYPE) != utils.NetworkTypes.L3_EXT) or
validators.is_attr_set(net_data.get(pnet.SEGMENTATION_ID))):
msg = _("Invalid provider network configuration")
raise n_exc.InvalidInput(error_message=msg)
self.nsxlib.router.validate_tier0(self.tier0_groups_dict, tier0_uuid)
return (True, utils.NetworkTypes.L3_EXT, tier0_uuid, 0)
def _create_network_at_the_backend(self, context, net_data, az,
transparent_vlan):
provider_data = self._validate_provider_create(context, net_data, az,
transparent_vlan)
if (provider_data['switch_mode'] ==
self.nsxlib.transport_zone.HOST_SWITCH_MODE_ENS):
if not cfg.CONF.nsx_v3.ens_support:
raise NotImplementedError(_("ENS support is disabled"))
if net_data.get(psec.PORTSECURITY):
raise nsx_exc.NsxENSPortSecurity()
# set the default port security to False
net_data[psec.PORTSECURITY] = False
if (provider_data['is_provider_net'] and
provider_data['net_type'] == utils.NsxV3NetworkTypes.NSX_NETWORK):
# Network already exists on the NSX backend
nsx_id = provider_data['physical_net']
else:
# Create network on the backend
neutron_net_id = net_data.get('id') or uuidutils.generate_uuid()
# To ensure that the correct tag will be set
net_data['id'] = neutron_net_id
# update the network name to indicate the neutron id too.
net_name = utils.get_name_and_uuid(net_data['name'] or 'network',
neutron_net_id)
tags = self.nsxlib.build_v3_tags_payload(
net_data, resource_type='os-neutron-net-id',
project_name=context.tenant_name)
admin_state = net_data.get('admin_state_up', True)
LOG.debug('create_network: %(net_name)s, %(physical_net)s, '
'%(tags)s, %(admin_state)s, %(vlan_id)s',
{'net_name': net_name,
'physical_net': provider_data['physical_net'],
'tags': tags,
'admin_state': admin_state,
'vlan_id': provider_data['vlan_id']})
trunk_vlan_range = None
if transparent_vlan:
# all vlan tags are allowed for guest vlan
trunk_vlan_range = [0, const.MAX_VLAN_TAG]
nsx_result = self.nsxlib.logical_switch.create(
net_name, provider_data['physical_net'], tags,
admin_state=admin_state,
vlan_id=provider_data['vlan_id'],
description=net_data.get('description'),
trunk_vlan_range=trunk_vlan_range)
nsx_id = nsx_result['id']
return (provider_data['is_provider_net'],
provider_data['net_type'],
provider_data['physical_net'],
provider_data['vlan_id'],
nsx_id)
def _is_ddi_supported_on_network(self, context, network_id):
# NSX current does not support transparent VLAN ports for
# DHCP and metadata
if cfg.CONF.vlan_transparent:
net = self.get_network(context, network_id)
if net.get('vlan_transparent') is True:
return False
return (self.nsxlib.feature_supported(
nsxlib_consts.FEATURE_VLAN_ROUTER_INTERFACE) or
self._is_overlay_network(context, network_id))
def _is_overlay_network(self, context, network_id):
"""Return True if this is an overlay network
1. No binding ("normal" overlay networks will have no binding)
2. Geneve network
3. nsx network where the backend network is connected to an overlay TZ
"""
bindings = nsx_db.get_network_bindings(context.session, network_id)
# With NSX plugin, "normal" overlay networks will have no binding
if not bindings:
# check the backend network
# TODO(asarfaty): Keep TZ type in DB to avoid going to the backend
az = self.get_network_az_by_net_id(context, network_id)
ls = self.nsxlib.logical_switch.get(az._default_overlay_tz_uuid)
tz = ls.get('transport_zone_id')
if tz:
backend_type = self.nsxlib.transport_zone.get_transport_type(
tz)
return (backend_type ==
self.nsxlib.transport_zone.TRANSPORT_TYPE_OVERLAY)
return True
binding = bindings[0]
if binding.binding_type == utils.NsxV3NetworkTypes.GENEVE:
return True
if binding.binding_type == utils.NsxV3NetworkTypes.NSX_NETWORK:
# check the backend network
# TODO(asarfaty): Keep TZ type in DB to avoid going to the backend
ls = self.nsxlib.logical_switch.get(binding.phy_uuid)
tz = ls.get('transport_zone_id')
if tz:
backend_type = self.nsxlib.transport_zone.get_transport_type(
tz)
return (backend_type ==
self.nsxlib.transport_zone.TRANSPORT_TYPE_OVERLAY)
return False
def _extend_network_dict_provider(self, context, network, bindings=None):
if 'id' not in network:
return
if not bindings:
bindings = nsx_db.get_network_bindings(context.session,
network['id'])
# With NSX plugin, "normal" overlay networks will have no binding
if bindings:
# 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
def _assert_on_external_net_with_qos(self, net_data):
# Prevent creating/update external network with QoS policy
if validators.is_attr_set(net_data.get(qos_consts.QOS_POLICY_ID)):
err_msg = _("Cannot configure QOS on external networks")
raise n_exc.InvalidInput(error_message=err_msg)
def get_subnets(self, context, filters=None, fields=None, sorts=None,
limit=None, marker=None, page_reverse=False):
filters = filters or {}
lswitch_ids = filters.pop(as_providers.ADV_SERVICE_PROVIDERS, [])
if lswitch_ids:
# This is a request from Nova for metadata processing.
# Find the corresponding neutron network for each logical switch.
network_ids = filters.pop('network_id', [])
context = context.elevated()
for lswitch_id in lswitch_ids:
network_ids += nsx_db.get_net_ids(context.session, lswitch_id)
filters['network_id'] = network_ids
return super(NsxV3Plugin, self).get_subnets(
context, filters, fields, sorts, limit, marker, page_reverse)
def _network_is_nsx_net(self, context, network_id):
bindings = nsx_db.get_network_bindings(context.session, network_id)
if not bindings:
return False
return (bindings[0].binding_type ==
utils.NsxV3NetworkTypes.NSX_NETWORK)
def _generate_segment_id(self, context, physical_network, net_data):
bindings = nsx_db.get_network_bindings_by_phy_uuid(
context.session, physical_network)
vlan_ranges = self._network_vlans.get(physical_network, [])
if vlan_ranges:
vlan_ids = set()
for vlan_min, vlan_max in vlan_ranges:
vlan_ids |= set(moves.range(vlan_min, vlan_max + 1))
else:
vlan_min = const.MIN_VLAN_TAG
vlan_max = const.MAX_VLAN_TAG
vlan_ids = set(moves.range(vlan_min, vlan_max + 1))
used_ids_in_range = set([binding.vlan_id for binding in bindings
if binding.vlan_id in vlan_ids])
free_ids = list(vlan_ids ^ used_ids_in_range)
if len(free_ids) == 0:
raise n_exc.NoNetworkAvailable()
net_data[pnet.SEGMENTATION_ID] = free_ids[0]
return net_data[pnet.SEGMENTATION_ID]
def _get_mdproxy_port_name(self, net_name, net_id):
return utils.get_name_and_uuid('%s-%s' % ('mdproxy',
net_name or 'network'),
net_id)
def create_network(self, context, network):
net_data = network['network']
external = net_data.get(extnet_apidef.EXTERNAL)
is_backend_network = False
is_ddi_network = False
tenant_id = net_data['tenant_id']
# validate the availability zone, and get the AZ object
if az_def.AZ_HINTS in net_data:
self._validate_availability_zones_forced(
context, 'network', net_data[az_def.AZ_HINTS])
az = self.get_obj_az_by_hints(net_data)
self._ensure_default_security_group(context, tenant_id)
# Update the transparent vlan if configured
vlt = False
if extensions.is_extension_supported(self, 'vlan-transparent'):
vlt = vlan_apidef.get_vlan_transparent(net_data)
nsx_net_id = None
if validators.is_attr_set(external) and external:
self._assert_on_external_net_with_qos(net_data)
is_provider_net, net_type, physical_net, vlan_id = (
self._validate_external_net_create(net_data, az))
else:
is_provider_net, net_type, physical_net, vlan_id, nsx_net_id = (
self._create_network_at_the_backend(context, net_data, az,
vlt))
is_backend_network = True
try:
rollback_network = False
with db_api.context_manager.writer.using(context):
# Create network in Neutron
created_net = super(NsxV3Plugin, self).create_network(context,
network)
self._extension_manager.process_create_network(
context, net_data, created_net)
if psec.PORTSECURITY not in net_data:
net_data[psec.PORTSECURITY] = True
self._process_network_port_security_create(
context, net_data, created_net)
self._process_l3_create(context, created_net, net_data)
if az_def.AZ_HINTS in net_data:
# Update the AZ hints in the neutron object
az_hints = az_validator.convert_az_list_to_string(
net_data[az_def.AZ_HINTS])
super(NsxV3Plugin, self).update_network(
context,
created_net['id'],
{'network': {az_def.AZ_HINTS: az_hints}})
if is_provider_net:
# Save provider network fields, needed by get_network()
net_bindings = [nsx_db.add_network_binding(
context.session, created_net['id'],
net_type, physical_net, vlan_id)]
self._extend_network_dict_provider(context, created_net,
bindings=net_bindings)
if is_backend_network:
# Add neutron-id <-> nsx-id mapping to the DB
# after the network creation is done
neutron_net_id = created_net['id']
nsx_db.add_neutron_nsx_network_mapping(
context.session,
neutron_net_id,
nsx_net_id)
if extensions.is_extension_supported(self, 'vlan-transparent'):
super(NsxV3Plugin, self).update_network(context,
created_net['id'],
{'network': {'vlan_transparent': vlt}})
rollback_network = True
is_ddi_network = self._is_ddi_supported_on_network(
context, created_net['id'])
if (is_backend_network and
cfg.CONF.nsx_v3.native_dhcp_metadata and
is_ddi_network):
# Enable native metadata proxy for this network.
tags = self.nsxlib.build_v3_tags_payload(
created_net, resource_type='os-neutron-net-id',
project_name=context.tenant_name)
name = self._get_mdproxy_port_name(created_net['name'],
created_net['id'])
md_port = self.nsxlib.logical_port.create(
nsx_net_id, az._native_md_proxy_uuid,
tags=tags, name=name,
attachment_type=nsxlib_consts.ATTACHMENT_MDPROXY)
LOG.debug("Created MD-Proxy logical port %(port)s "
"for network %(network)s",
{'port': md_port['id'],
'network': created_net['id']})
except Exception:
with excutils.save_and_reraise_exception():
# Undo creation on the backend
LOG.exception('Failed to create network')
if (nsx_net_id and
net_type != utils.NsxV3NetworkTypes.NSX_NETWORK):
self.nsxlib.logical_switch.delete(nsx_net_id)
if (cfg.CONF.nsx_v3.native_dhcp_metadata and
is_backend_network and is_ddi_network):
# Delete the mdproxy port manually
self._delete_network_nsx_dhcp_port(created_net['id'])
if rollback_network:
super(NsxV3Plugin, self).delete_network(
context, created_net['id'])
# this extra lookup is necessary to get the
# latest db model for the extension functions
net_model = self._get_network(context, created_net['id'])
resource_extend.apply_funcs('networks', created_net, net_model)
# Update the QoS policy (will affect only future compute ports)
qos_com_utils.set_qos_policy_on_new_net(
context, net_data, created_net)
return created_net
def _has_active_port(self, context, network_id):
ports_in_use = context.session.query(models_v2.Port).filter_by(
network_id=network_id).all()
return not all([p.device_owner in
db_base_plugin_v2.AUTO_DELETE_PORT_OWNERS
for p in ports_in_use]) if ports_in_use else False
def _retry_delete_network(self, context, network_id):
"""This method attempts to retry the delete on a network if there are
AUTO_DELETE_PORT_OWNERS left. This is to avoid a race condition
between delete_network and the dhcp creating a port on the network.
"""
first_try = True
while True:
try:
with db_api.context_manager.writer.using(context):
self._process_l3_delete(context, network_id)
return super(NsxV3Plugin, self).delete_network(
context, network_id)
except n_exc.NetworkInUse:
# There is a race condition in delete_network() that we need
# to work around here. delete_network() issues a query to
# automatically delete DHCP ports and then checks to see if any
# ports exist on the network. If a network is created and
# deleted quickly, such as when running tempest, the DHCP agent
# may be creating its port for the network around the same time
# that the network is deleted. This can result in the DHCP
# port getting created in between these two queries in
# delete_network(). To work around that, we'll call
# delete_network() a second time if we get a NetworkInUse
# exception but the only port(s) that exist are ones that
# delete_network() is supposed to automatically delete.
if not first_try:
# We tried once to work around the known race condition,
# but we still got the exception, so something else is
# wrong that we can't recover from.
raise
first_try = False
if self._has_active_port(context, network_id):
# There is a port on the network that is not going to be
# automatically deleted (such as a tenant created port), so
# we have nothing else to do but raise the exception.
raise
def _delete_network_nsx_dhcp_port(self, network_id):
port_id = self.nsxlib.get_id_by_resource_and_tag(
self.nsxlib.logical_port.resource_type,
'os-neutron-net-id', network_id)
if port_id:
self.nsxlib.logical_port.delete(port_id)
def delete_network(self, context, network_id):
if cfg.CONF.nsx_v3.native_dhcp_metadata:
lock = 'nsxv3_network_' + network_id
with locking.LockManager.get_lock(lock):
# Disable native DHCP if there is no other existing port
# besides DHCP port.
if not self._has_active_port(context, network_id):
self._disable_native_dhcp(context, network_id)
nsx_net_id = self._get_network_nsx_id(context, network_id)
is_nsx_net = self._network_is_nsx_net(context, network_id)
is_ddi_network = self._is_ddi_supported_on_network(context, network_id)
# First call DB operation for delete network as it will perform
# checks on active ports
self._retry_delete_network(context, network_id)
if (not self._network_is_external(context, network_id) and
not is_nsx_net):
# TODO(salv-orlando): Handle backend failure, possibly without
# requiring us to un-delete the DB object. For instance, ignore
# failures occurring if logical switch is not found
self.nsxlib.logical_switch.delete(nsx_net_id)
else:
if (cfg.CONF.nsx_v3.native_dhcp_metadata and is_nsx_net and
is_ddi_network):
# Delete the mdproxy port manually
self._delete_network_nsx_dhcp_port(network_id)
# TODO(berlin): delete subnets public announce on the network
def _get_network_nsx_id(self, context, neutron_id):
# get the nsx switch id from the DB mapping
mappings = nsx_db.get_nsx_switch_ids(context.session, neutron_id)
if not mappings or len(mappings) == 0:
LOG.debug("Unable to find NSX mappings for neutron "
"network %s.", neutron_id)
# fallback in case we didn't find the id in the db mapping
# This should not happen, but added here in case the network was
# created before this code was added.
return neutron_id
else:
return mappings[0]
def update_network(self, context, id, network):
original_net = super(NsxV3Plugin, self).get_network(context, id)
net_data = network['network']
# Neutron does not support changing provider network values
providernet._raise_if_updates_provider_attributes(net_data)
extern_net = self._network_is_external(context, id)
is_nsx_net = self._network_is_nsx_net(context, id)
if extern_net:
self._assert_on_external_net_with_qos(net_data)
updated_net = super(NsxV3Plugin, self).update_network(context, id,
network)
self._extension_manager.process_update_network(context, net_data,
updated_net)
if psec.PORTSECURITY in net_data:
# do not allow to enable port security on ENS networks
if (net_data[psec.PORTSECURITY] and
not original_net[psec.PORTSECURITY] and
self._is_ens_tz_net(context, id)):
raise nsx_exc.NsxENSPortSecurity()
self._process_network_port_security_update(
context, net_data, updated_net)
self._process_l3_update(context, updated_net, network['network'])
self._extend_network_dict_provider(context, updated_net)
if (not extern_net and not is_nsx_net and
('name' in net_data or 'admin_state_up' in net_data or
'description' in net_data)):
try:
# get the nsx switch id from the DB mapping
nsx_id = self._get_network_nsx_id(context, id)
net_name = net_data.get('name',
original_net.get('name')) or 'network'
self.nsxlib.logical_switch.update(
nsx_id,
name=utils.get_name_and_uuid(net_name, id),
admin_state=net_data.get('admin_state_up'),
description=net_data.get('description'))
# Backend does not update the admin state of the ports on
# the switch when the switch's admin state changes. Do not
# update the admin state of the ports in neutron either.
except nsx_lib_exc.ManagerError:
LOG.exception("Unable to update NSX backend, rolling "
"back changes on neutron")
with excutils.save_and_reraise_exception():
super(NsxV3Plugin, self).update_network(
context, id, {'network': original_net})
if qos_consts.QOS_POLICY_ID in net_data:
# attach the policy to the network in neutron DB
#(will affect only future compute ports)
qos_com_utils.update_network_policy_binding(
context, id, net_data[qos_consts.QOS_POLICY_ID])
if not extern_net and not is_nsx_net:
# update the network name & attributes in related NSX objects:
if 'name' in net_data or 'dns_domain' in net_data:
# update the dhcp server after finding it by tags
self._update_dhcp_server_on_net_update(context, updated_net)
if 'name' in net_data:
# update the mdproxy port after finding it by tags
self._update_mdproxy_port_on_net_update(context, updated_net)
# update the DHCP port after finding it by tags
self._update_dhcp_port_on_net_update(context, updated_net)
return updated_net
def _update_dhcp_port_on_net_update(self, context, network):
"""Update the NSX DHCP port when the neutron network changes"""
dhcp_service = nsx_db.get_nsx_service_binding(
context.session, network['id'], nsxlib_consts.SERVICE_DHCP)
if dhcp_service and dhcp_service['port_id']:
# get the neutron port id and search by it
port_tag = [{'scope': 'os-neutron-dport-id',
'tag': dhcp_service['port_id']}]
dhcpports = self.nsxlib.search_by_tags(
tags=port_tag,
resource_type=self.nsxlib.logical_port.resource_type)
if dhcpports['results']:
# There should be only 1 dhcp port
# update the port name by the new network name
name = self._get_dhcp_port_name(network['name'], network['id'])
try:
self.nsxlib.logical_port.update(
dhcpports['results'][0]['id'],
False, name=name, attachment_type=False)
except Exception as e:
LOG.warning("Failed to update network %(id)s DHCP port "
"on the NSX: %(e)s", {'id': network['id'],
'e': e})
def _update_mdproxy_port_on_net_update(self, context, network):
"""Update the NSX MDPROXY port when the neutron network changes"""
net_tag = [{'scope': 'os-neutron-net-id', 'tag': network['id']}]
# find the logical port by the neutron network id & attachment
mdproxy_list = self.nsxlib.search_by_tags(
tags=net_tag,
resource_type=self.nsxlib.logical_port.resource_type)
if not mdproxy_list['results']:
return
for port in mdproxy_list['results']:
if (port.get('attachment') and
port['attachment'].get('attachment_type') == 'METADATA_PROXY'):
# update the port name by the new network name
name = self._get_mdproxy_port_name(network['name'],
network['id'])
try:
self.nsxlib.logical_port.update(
port['id'], False, name=name, attachment_type=False)
except Exception as e:
LOG.warning("Failed to update network %(id)s mdproxy port "
"on the NSX: %(e)s", {'id': network['id'],
'e': e})
# There should be only 1 mdproxy port so it is safe to return
return
def _update_dhcp_server_on_net_update(self, context, network):
"""Update the NSX DHCP server when the neutron network changes"""
net_tag = [{'scope': 'os-neutron-net-id', 'tag': network['id']}]
# Find the DHCP server by the neutron network tag
dhcp_srv_list = self.nsxlib.search_by_tags(
tags=net_tag,
resource_type=self.nsxlib.dhcp_server.resource_type)
if dhcp_srv_list['results']:
# Calculate the new name and domain by the network data
dhcp_name = self.nsxlib.native_dhcp.build_server_name(
network['name'], network['id'])
az = self.get_network_az_by_net_id(context, network['id'])
domain_name = self.nsxlib.native_dhcp.build_server_domain_name(
network.get('dns_domain'), az.dns_domain)
try:
# There should be only 1 dhcp server
# Update its name and domain
self.nsxlib.dhcp_server.update(
dhcp_srv_list['results'][0]['id'],
name=dhcp_name,
domain_name=domain_name)
except Exception as e:
LOG.warning("Failed to update network %(id)s dhcp server on "
"the NSX: %(e)s", {'id': network['id'], 'e': e})
def _has_no_dhcp_enabled_subnet(self, context, network):
# Check if there is no DHCP-enabled subnet in the network.
for subnet in network.subnets:
if subnet.enable_dhcp:
return False
return True
def _has_single_dhcp_enabled_subnet(self, context, network):
# Check if there is only one DHCP-enabled subnet in the network.
count = 0
for subnet in network.subnets:
if subnet.enable_dhcp:
count += 1
if count > 1:
return False
return True if count == 1 else False
def _enable_native_dhcp(self, context, network, subnet):
# Enable native DHCP service on the backend for this network.
# First create a Neutron DHCP port and use its assigned IP
# address as the DHCP server address in an API call to create a
# LogicalDhcpServer on the backend. Then create the corresponding
# logical port for the Neutron port with DHCP attachment as the
# LogicalDhcpServer UUID.
# Delete obsolete settings if exist. This could happen when a
# previous failed transaction was rolled back. But the backend
# entries are still there.
self._disable_native_dhcp(context, network['id'])
# Get existing ports on subnet.
existing_ports = super(NsxV3Plugin, self).get_ports(
context, filters={'network_id': [network['id']],
'fixed_ips': {'subnet_id': [subnet['id']]}})
az = self.get_network_az_by_net_id(context, network['id'])
port_data = {
"name": "",
"admin_state_up": True,
"device_id": az._native_dhcp_profile_uuid,
"device_owner": const.DEVICE_OWNER_DHCP,
"network_id": network['id'],
"tenant_id": network["tenant_id"],
"mac_address": const.ATTR_NOT_SPECIFIED,
"fixed_ips": [{"subnet_id": subnet['id']}],
psec.PORTSECURITY: False
}
# Create the DHCP port (on neutron only) and update its port security
port = {'port': port_data}
neutron_port = super(NsxV3Plugin, self).create_port(context, port)
is_ens_tz_port = self._is_ens_tz_port(context, port_data)
self._create_port_preprocess_security(context, port, port_data,
neutron_port, is_ens_tz_port)
net_tags = self.nsxlib.build_v3_tags_payload(
network, resource_type='os-neutron-net-id',
project_name=context.tenant_name)
server_data = self.nsxlib.native_dhcp.build_server_config(
network, subnet, neutron_port, net_tags,
default_dns_nameservers=az.nameservers,
default_dns_domain=az.dns_domain)
server_data['dhcp_profile_id'] = az._native_dhcp_profile_uuid
nsx_net_id = self._get_network_nsx_id(context, network['id'])
port_tags = self.nsxlib.build_v3_tags_payload(
neutron_port, resource_type='os-neutron-dport-id',
project_name=context.tenant_name)
dhcp_server = None
dhcp_port_profiles = []
if not self._is_ens_tz_net(context, network['id']):
dhcp_port_profiles.append(self._dhcp_profile)
try:
dhcp_server = self.nsxlib.dhcp_server.create(**server_data)
LOG.debug("Created logical DHCP server %(server)s for network "
"%(network)s",
{'server': dhcp_server['id'], 'network': network['id']})
name = self._get_port_name(context, port_data)
nsx_port = self.nsxlib.logical_port.create(
nsx_net_id, dhcp_server['id'], tags=port_tags, name=name,
attachment_type=nsxlib_consts.ATTACHMENT_DHCP,
switch_profile_ids=dhcp_port_profiles)
LOG.debug("Created DHCP logical port %(port)s for "
"network %(network)s",
{'port': nsx_port['id'], 'network': network['id']})
except nsx_lib_exc.ManagerError:
with excutils.save_and_reraise_exception():
LOG.error("Unable to create logical DHCP server for "
"network %s", network['id'])
if dhcp_server:
self.nsxlib.dhcp_server.delete(dhcp_server['id'])
super(NsxV3Plugin, self).delete_port(
context, neutron_port['id'])
try:
# Add neutron_port_id -> nsx_port_id mapping to the DB.
nsx_db.add_neutron_nsx_port_mapping(
context.session, neutron_port['id'], nsx_net_id,
nsx_port['id'])
# Add neutron_net_id -> dhcp_service_id mapping to the DB.
nsx_db.add_neutron_nsx_service_binding(
context.session, network['id'], neutron_port['id'],
nsxlib_consts.SERVICE_DHCP, dhcp_server['id'])
except (db_exc.DBError, sql_exc.TimeoutError):
with excutils.save_and_reraise_exception():
LOG.error("Failed to create mapping for DHCP port %s,"
"deleting port and logical DHCP server",
neutron_port['id'])
self.nsxlib.dhcp_server.delete(dhcp_server['id'])
self._cleanup_port(context, neutron_port['id'], nsx_port['id'])
# Configure existing ports to work with the new DHCP server
try:
for port_data in existing_ports:
self._add_dhcp_binding(context, port_data)
except Exception:
LOG.error('Unable to create DHCP bindings for existing ports '
'on subnet %s', subnet['id'])
def _disable_native_dhcp(self, context, network_id):
# Disable native DHCP service on the backend for this network.
# First delete the DHCP port in this network. Then delete the
# corresponding LogicalDhcpServer for this network.
dhcp_service = nsx_db.get_nsx_service_binding(
context.session, network_id, nsxlib_consts.SERVICE_DHCP)
if not dhcp_service:
return
if dhcp_service['port_id']:
try:
self.delete_port(context, dhcp_service['port_id'],
force_delete_dhcp=True)
except Exception:
# This could happen when the port has been manually deleted.
LOG.error("Failed to delete DHCP port %(port)s for "
"network %(network)s",
{'port': dhcp_service['port_id'],
'network': network_id})
else:
LOG.error("DHCP port is not configured for network %s",
network_id)
try:
self.nsxlib.dhcp_server.delete(dhcp_service['nsx_service_id'])
LOG.debug("Deleted logical DHCP server %(server)s for network "
"%(network)s",
{'server': dhcp_service['nsx_service_id'],
'network': network_id})
except nsx_lib_exc.ManagerError:
with excutils.save_and_reraise_exception():
LOG.error("Unable to delete logical DHCP server %(server)s "
"for network %(network)s",
{'server': dhcp_service['nsx_service_id'],
'network': network_id})
try:
# Delete neutron_id -> dhcp_service_id mapping from the DB.
nsx_db.delete_neutron_nsx_service_binding(
context.session, network_id, nsxlib_consts.SERVICE_DHCP)
# Delete all DHCP bindings under this DHCP server from the DB.
nsx_db.delete_neutron_nsx_dhcp_bindings_by_service_id(
context.session, dhcp_service['nsx_service_id'])
except db_exc.DBError:
with excutils.save_and_reraise_exception():
LOG.error("Unable to delete DHCP server mapping for "
"network %s", network_id)
def _validate_address_space(self, context, subnet):
cidr = subnet.get('cidr')
if (not validators.is_attr_set(cidr) or
netaddr.IPNetwork(cidr).version != 4):
return
# Check if subnet overlaps with shared address space.
# This is checked on the backend when attaching subnet to a router.
if netaddr.IPSet([cidr]) & netaddr.IPSet(['100.64.0.0/10']):
msg = _("Subnet overlaps with shared address space 100.64.0.0/10")
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
# Ensure that the NSX uplink does not lie on the same subnet as
# the external subnet
filters = {'id': [subnet['network_id']],
'router:external': [True]}
nets = self.get_networks(context, filters=filters)
for net in nets:
tier0 = net.get(pnet.PHYSICAL_NETWORK)
if tier0:
ports = self.nsxlib.logical_router_port.get_by_router_id(tier0)
for port in ports:
if (port.get('resource_type') ==
'LogicalRouterUpLinkPort'):
for subnet in port.get('subnets', []):
for ip_address in subnet.get('ip_addresses'):
if (netaddr.IPAddress(ip_address) in
netaddr.IPNetwork(cidr)):
msg = _("External subnet cannot "
"overlap with T0 router "
"address!")
LOG.error(msg)
raise n_exc.InvalidInput(
error_message=msg)
def _create_bulk_with_callback(self, resource, context, request_items,
post_create_func=None, rollback_func=None):
# This is a copy of the _create_bulk() in db_base_plugin_v2.py,
# but extended with user-provided callback functions.
objects = []
collection = "%ss" % resource
items = request_items[collection]
try:
with db_api.context_manager.writer.using(context):
for item in items:
obj_creator = getattr(self, 'create_%s' % resource)
obj = obj_creator(context, item)
objects.append(obj)
if post_create_func:
# The user-provided post_create function is called
# after a new object is created.
post_create_func(obj)
except Exception:
if rollback_func:
# The user-provided rollback function is called when an
# exception occurred.
for obj in objects:
rollback_func(obj)
# Note that the session.rollback() function is called here.
# session.rollback() will invoke transaction.rollback() on
# the transaction this session maintains. The latter will
# deactive the transaction and clear the session's cache.
#
# But depending on where the exception occurred,
# transaction.rollback() may have already been called
# internally before reaching here.
#
# For example, if the exception happened under a
# "with session.begin(subtransactions=True):" statement
# anywhere in the middle of processing obj_creator(),
# transaction.__exit__() will invoke transaction.rollback().
# Thus when the exception reaches here, the session's cache
# is already empty.
context.session.rollback()
with excutils.save_and_reraise_exception():
LOG.error("An exception occurred while creating "
"the %(resource)s:%(item)s",
{'resource': resource, 'item': item})
return objects
def _post_create_subnet(self, context, subnet):
LOG.debug("Collect native DHCP entries for network %s",
subnet['network_id'])
dhcp_service = nsx_db.get_nsx_service_binding(
context.session, subnet['network_id'], nsxlib_consts.SERVICE_DHCP)
if dhcp_service:
_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
context.session, dhcp_service['port_id'])
return {'nsx_port_id': nsx_port_id,
'nsx_service_id': dhcp_service['nsx_service_id']}
def _rollback_subnet(self, subnet, dhcp_info):
LOG.debug("Rollback native DHCP entries for network %s",
subnet['network_id'])
if dhcp_info:
try:
self.nsxlib.logical_port.delete(dhcp_info['nsx_port_id'])
except Exception as e:
LOG.error("Failed to delete logical port %(id)s "
"during rollback. Exception: %(e)s",
{'id': dhcp_info['nsx_port_id'], 'e': e})
try:
self.nsxlib.dhcp_server.delete(dhcp_info['nsx_service_id'])
except Exception as e:
LOG.error("Failed to delete logical DHCP server %(id)s "
"during rollback. Exception: %(e)s",
{'id': dhcp_info['nsx_service_id'], 'e': e})
def create_subnet_bulk(self, context, subnets):
# Maintain a local cache here because when the rollback function
# is called, the cache in the session may have already been cleared.
_subnet_dhcp_info = {}
def _post_create(subnet):
if subnet['enable_dhcp']:
_subnet_dhcp_info[subnet['id']] = self._post_create_subnet(
context, subnet)
def _rollback(subnet):
if subnet['enable_dhcp'] and subnet['id'] in _subnet_dhcp_info:
self._rollback_subnet(subnet, _subnet_dhcp_info[subnet['id']])
del _subnet_dhcp_info[subnet['id']]
if cfg.CONF.nsx_v3.native_dhcp_metadata:
return self._create_bulk_with_callback('subnet', context, subnets,
_post_create, _rollback)
else:
return self._create_bulk('subnet', context, subnets)
def create_subnet(self, context, subnet):
self._validate_address_space(context, subnet['subnet'])
# TODO(berlin): public external subnet announcement
if (cfg.CONF.nsx_v3.native_dhcp_metadata and
subnet['subnet'].get('enable_dhcp', False)):
self._validate_external_subnet(context,
subnet['subnet']['network_id'])
lock = 'nsxv3_network_' + subnet['subnet']['network_id']
with locking.LockManager.get_lock(lock):
# Check if it is on an overlay network and is the first
# DHCP-enabled subnet to create.
if self._is_ddi_supported_on_network(
context, subnet['subnet']['network_id']):
network = self._get_network(
context, subnet['subnet']['network_id'])
if self._has_no_dhcp_enabled_subnet(context, network):
created_subnet = super(
NsxV3Plugin, self).create_subnet(context, subnet)
self._extension_manager.process_create_subnet(context,
subnet['subnet'], created_subnet)
dhcp_relay = self.get_network_az_by_net_id(
context,
subnet['subnet']['network_id']).dhcp_relay_service
if not dhcp_relay:
self._enable_native_dhcp(context, network,
created_subnet)
msg = None
else:
msg = (_("Can not create more than one DHCP-enabled "
"subnet in network %s") %
subnet['subnet']['network_id'])
else:
msg = _("Native DHCP is not supported for non-overlay "
"network %s") % subnet['subnet']['network_id']
if msg:
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
else:
created_subnet = super(NsxV3Plugin, self).create_subnet(
context, subnet)
return created_subnet
def delete_subnet(self, context, subnet_id):
# TODO(berlin): cancel public external subnet announcement
if cfg.CONF.nsx_v3.native_dhcp_metadata:
# Ensure that subnet is not deleted if attached to router.
self._subnet_check_ip_allocations_internal_router_ports(
context, subnet_id)
subnet = self.get_subnet(context, subnet_id)
if subnet['enable_dhcp']:
lock = 'nsxv3_network_' + subnet['network_id']
with locking.LockManager.get_lock(lock):
# Check if it is the last DHCP-enabled subnet to delete.
network = self._get_network(context, subnet['network_id'])
if self._has_single_dhcp_enabled_subnet(context, network):
try:
self._disable_native_dhcp(context, network['id'])
except Exception as e:
LOG.error("Failed to disable native DHCP for"
"network %(id)s. Exception: %(e)s",
{'id': network['id'], 'e': e})
super(NsxV3Plugin, self).delete_subnet(
context, subnet_id)
return
super(NsxV3Plugin, self).delete_subnet(context, subnet_id)
def update_subnet(self, context, subnet_id, subnet):
updated_subnet = None
if cfg.CONF.nsx_v3.native_dhcp_metadata:
orig_subnet = self.get_subnet(context, subnet_id)
enable_dhcp = subnet['subnet'].get('enable_dhcp')
if (enable_dhcp is not None and
enable_dhcp != orig_subnet['enable_dhcp']):
lock = 'nsxv3_network_' + orig_subnet['network_id']
with locking.LockManager.get_lock(lock):
network = self._get_network(
context, orig_subnet['network_id'])
if enable_dhcp:
if self._is_ddi_supported_on_network(
context, orig_subnet['network_id']):
if self._has_no_dhcp_enabled_subnet(
context, network):
updated_subnet = super(
NsxV3Plugin, self).update_subnet(
context, subnet_id, subnet)
self._extension_manager.process_update_subnet(
context, subnet['subnet'], updated_subnet)
self._enable_native_dhcp(context, network,
updated_subnet)
msg = None
else:
msg = (_("Multiple DHCP-enabled subnets is "
"not allowed in network %s") %
orig_subnet['network_id'])
else:
msg = (_("Native DHCP is not supported for "
"non-overlay network %s") %
orig_subnet['network_id'])
if msg:
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
elif self._has_single_dhcp_enabled_subnet(context,
network):
self._disable_native_dhcp(context, network['id'])
updated_subnet = super(
NsxV3Plugin, self).update_subnet(
context, subnet_id, subnet)
self._extension_manager.process_update_subnet(
context, subnet['subnet'], updated_subnet)
if not updated_subnet:
updated_subnet = super(NsxV3Plugin, self).update_subnet(
context, subnet_id, subnet)
self._extension_manager.process_update_subnet(
context, subnet['subnet'], updated_subnet)
# Check if needs to update logical DHCP server for native DHCP.
if (cfg.CONF.nsx_v3.native_dhcp_metadata and
updated_subnet['enable_dhcp']):
kwargs = {}
for key in ('dns_nameservers', 'gateway_ip', 'host_routes'):
if key in subnet['subnet']:
value = subnet['subnet'][key]
if value != orig_subnet[key]:
kwargs[key] = value
if key != 'dns_nameservers':
kwargs['options'] = None
if 'options' in kwargs:
sr, gw_ip = self.nsxlib.native_dhcp.build_static_routes(
updated_subnet.get('gateway_ip'),
updated_subnet.get('cidr'),
updated_subnet.get('host_routes', []))
kwargs['options'] = {'option121': {'static_routes': sr}}
kwargs.pop('host_routes', None)
if (gw_ip is not None and 'gateway_ip' not in kwargs and
gw_ip != updated_subnet['gateway_ip']):
kwargs['gateway_ip'] = gw_ip
if kwargs:
dhcp_service = nsx_db.get_nsx_service_binding(
context.session, orig_subnet['network_id'],
nsxlib_consts.SERVICE_DHCP)
if dhcp_service:
try:
self.nsxlib.dhcp_server.update(
dhcp_service['nsx_service_id'], **kwargs)
except nsx_lib_exc.ManagerError:
with excutils.save_and_reraise_exception():
LOG.error(
"Unable to update logical DHCP server "
"%(server)s for network %(network)s",
{'server': dhcp_service['nsx_service_id'],
'network': orig_subnet['network_id']})
if 'options' in kwargs:
# Need to update the static binding of every VM in
# this logical DHCP server.
bindings = nsx_db.get_nsx_dhcp_bindings_by_service(
context.session, dhcp_service['nsx_service_id'])
for binding in bindings:
port = self._get_port(context, binding['port_id'])
dhcp_opts = port.get(ext_edo.EXTRADHCPOPTS)
self._update_dhcp_binding_on_server(
context, binding, port['mac_address'],
binding['ip_address'],
port['network_id'],
gateway_ip=kwargs.get('gateway_ip', False),
dhcp_opts=dhcp_opts,
options=kwargs.get('options'),
subnet=updated_subnet)
if (cfg.CONF.nsx_v3.metadata_on_demand and
not cfg.CONF.nsx_v3.native_dhcp_metadata):
# If enable_dhcp is changed on a subnet attached to a router,
# update internal metadata network accordingly.
if 'enable_dhcp' in subnet['subnet']:
port_filters = {'device_owner': const.ROUTER_INTERFACE_OWNERS,
'fixed_ips': {'subnet_id': [subnet_id]}}
ports = self.get_ports(context, filters=port_filters)
for port in ports:
nsx_rpc.handle_router_metadata_access(
self, context, port['device_id'],
interface=not updated_subnet['enable_dhcp'])
return updated_subnet
def _build_address_bindings(self, port):
address_bindings = []
for fixed_ip in port['fixed_ips']:
# NOTE(arosen): nsx-v3 doesn't seem to handle ipv6 addresses
# currently so for now we remove them here and do not pass
# them to the backend which would raise an error.
if netaddr.IPNetwork(fixed_ip['ip_address']).version == 6:
continue
address_bindings.append(nsx_resources.PacketAddressClassifier(
fixed_ip['ip_address'], port['mac_address'], None))
for pair in port.get(addr_apidef.ADDRESS_PAIRS):
address_bindings.append(nsx_resources.PacketAddressClassifier(
pair['ip_address'], pair['mac_address'], None))
return address_bindings
def _extend_get_network_dict_provider(self, context, network):
self._extend_network_dict_provider(context, network)
network[qos_consts.QOS_POLICY_ID] = (qos_com_utils.
get_network_policy_id(context, network['id']))
def get_network(self, context, id, fields=None):
with db_api.context_manager.reader.using(context):
# Get network from Neutron database
network = self._get_network(context, id)
# Don't do field selection here otherwise we won't be able to add
# provider networks fields
net = self._make_network_dict(network, context=context)
self._extend_get_network_dict_provider(context, net)
return db_utils.resource_fields(net, fields)
def get_networks(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
# Get networks from Neutron database
filters = filters or {}
with db_api.context_manager.reader.using(context):
networks = (
super(NsxV3Plugin, self).get_networks(
context, filters, fields, sorts,
limit, marker, page_reverse))
# Add provider network fields
for net in networks:
self._extend_get_network_dict_provider(context, net)
return (networks if not fields else
[db_utils.resource_fields(network,
fields) for network in networks])
def _get_dhcp_port_name(self, net_name, net_id):
return utils.get_name_and_uuid('%s-%s' % ('dhcp',
net_name or 'network'),
net_id)
def _get_port_name(self, context, port_data):
device_owner = port_data.get('device_owner')
device_id = port_data.get('device_id')
if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF and device_id:
router = self._get_router(context, device_id)
name = utils.get_name_and_uuid(
router['name'] or 'router', port_data['id'], tag='port')
elif device_owner == const.DEVICE_OWNER_DHCP:
network = self.get_network(context, port_data['network_id'])
name = self._get_dhcp_port_name(network['name'],
network['id'])
elif device_owner.startswith(const.DEVICE_OWNER_COMPUTE_PREFIX):
name = utils.get_name_and_uuid(
port_data['name'] or 'instance-port', port_data['id'])
else:
name = port_data['name']
return name
def _get_qos_profile_id(self, context, policy_id):
switch_profile_id = nsx_db.get_switch_profile_by_qos_policy(
context.session, policy_id)
nsxlib_qos = self.nsxlib.qos_switching_profile
qos_profile = nsxlib_qos.get(switch_profile_id)
if qos_profile:
profile_ids = nsxlib_qos.build_switch_profile_ids(
self.nsxlib.switching_profile, qos_profile)
if profile_ids and len(profile_ids) > 0:
# We have only 1 QoS profile, so this array is of size 1
return profile_ids[0]
# Didn't find it
err_msg = _("Could not find QoS switching profile for policy "
"%s") % policy_id
LOG.error(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)
def _is_excluded_port(self, device_owner, port_security):
if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
return False
if device_owner == const.DEVICE_OWNER_DHCP:
if not cfg.CONF.nsx_v3.native_dhcp_metadata:
return True
elif not port_security:
return True
return False
def _create_port_at_the_backend(self, context, port_data,
l2gw_port_check, psec_is_on,
is_ens_tz_port):
device_owner = port_data.get('device_owner')
device_id = port_data.get('device_id')
if device_owner == const.DEVICE_OWNER_DHCP:
resource_type = 'os-neutron-dport-id'
elif device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
resource_type = 'os-neutron-rport-id'
else:
resource_type = 'os-neutron-port-id'
tags = self.nsxlib.build_v3_tags_payload(
port_data, resource_type=resource_type,
project_name=context.tenant_name)
resource_type = self._get_resource_type_for_device_id(
device_owner, device_id)
if resource_type:
tags = nsxlib_utils.add_v3_tag(tags, resource_type, device_id)
add_to_exclude_list = False
if self._is_excluded_port(device_owner, psec_is_on):
if self.nsxlib.feature_supported(
nsxlib_consts.FEATURE_EXCLUDE_PORT_BY_TAG):
tags.append({'scope': security.PORT_SG_SCOPE,
'tag': nsxlib_consts.EXCLUDE_PORT})
else:
add_to_exclude_list = True
elif self.nsxlib.feature_supported(
nsxlib_consts.FEATURE_DYNAMIC_CRITERIA):
# If port has no security-groups then we don't need to add any
# security criteria tag.
if port_data[ext_sg.SECURITYGROUPS]:
tags += self.nsxlib.ns_group.get_lport_tags(
port_data[ext_sg.SECURITYGROUPS] +
port_data[provider_sg.PROVIDER_SECURITYGROUPS])
# Add port to the default list
if (device_owner != l3_db.DEVICE_OWNER_ROUTER_INTF and
device_owner != const.DEVICE_OWNER_DHCP):
tags.append({'scope': security.PORT_SG_SCOPE,
'tag': NSX_V3_DEFAULT_SECTION})
address_bindings = (self._build_address_bindings(port_data)
if psec_is_on else [])
if not device_owner:
# no attachment
attachment_type = None
vif_uuid = None
elif l2gw_port_check:
# Change the attachment type for L2 gateway owned ports.
# NSX backend requires the vif id be set to bridge endpoint id
# for ports plugged into a Bridge Endpoint.
# Also set port security to False, since L2GW port does not have
# an IP address.
vif_uuid = device_id
attachment_type = device_owner
psec_is_on = False
elif device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
# no attachment change
attachment_type = False
vif_uuid = False
else:
# default attachment
attachment_type = nsxlib_consts.ATTACHMENT_VIF
vif_uuid = port_data['id']
profiles = []
# Add availability zone profiles first (so that specific profiles will
# override them)
port_az = self.get_network_az_by_net_id(context,
port_data['network_id'])
if port_az.switching_profiles_objs:
profiles.extend(port_az.switching_profiles_objs)
mac_learning_profile_set = False
if psec_is_on:
address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS)
if validators.is_attr_set(address_pairs) and address_pairs:
mac_learning_profile_set = True
profiles.append(self._get_port_security_profile_id())
if device_owner == const.DEVICE_OWNER_DHCP:
if not is_ens_tz_port:
profiles.append(self._dhcp_profile)
# Add QoS switching profile, if exists
qos_policy_id = None
if validators.is_attr_set(port_data.get(qos_consts.QOS_POLICY_ID)):
qos_policy_id = port_data[qos_consts.QOS_POLICY_ID]
elif device_owner.startswith(const.DEVICE_OWNER_COMPUTE_PREFIX):
# check if the network of this port has a policy
qos_policy_id = qos_com_utils.get_network_policy_id(
context, port_data['network_id'])
if qos_policy_id:
qos_profile_id = self._get_qos_profile_id(context, qos_policy_id)
profiles.append(qos_profile_id)
# Add mac_learning profile if it exists and is configured
if (not is_ens_tz_port and self._mac_learning_profile and
(mac_learning_profile_set or
(validators.is_attr_set(port_data.get(mac_ext.MAC_LEARNING)) and
port_data.get(mac_ext.MAC_LEARNING) is True))):
profiles.append(self._mac_learning_profile)
profiles.append(self._no_switch_security)
name = self._get_port_name(context, port_data)
nsx_net_id = port_data[pbin.VIF_DETAILS]['nsx-logical-switch-id']
try:
result = self.nsxlib.logical_port.create(
nsx_net_id, vif_uuid,
tags=tags,
name=name,
admin_state=port_data['admin_state_up'],
address_bindings=address_bindings,
attachment_type=attachment_type,
switch_profile_ids=profiles,
description=port_data.get('description'))
except nsx_lib_exc.ManagerError as inst:
# we may fail if the QoS is not supported for this port
# (for example - transport zone with KVM)
LOG.exception("Unable to create port on the backend: %s",
inst)
msg = _("Unable to create port on the backend")
raise nsx_exc.NsxPluginException(err_msg=msg)
# Attach the policy to the port in the neutron DB
if qos_policy_id:
qos_com_utils.update_port_policy_binding(context,
port_data['id'],
qos_policy_id)
# Add the port to the exclude list if necessary - this is if
# the version is below 2.0.0
if add_to_exclude_list:
self.nsxlib.firewall_section.add_member_to_fw_exclude_list(
result['id'], nsxlib_consts.TARGET_TYPE_LOGICAL_PORT)
return result
def _validate_address_pairs(self, address_pairs):
for pair in address_pairs:
ip = pair.get('ip_address')
if not utils.is_ipv4_ip_address(ip):
raise nsx_exc.InvalidIPAddress(ip_address=ip)
def _provider_sgs_specified(self, port_data):
# checks if security groups were updated adding/modifying
# security groups, port security is set and port has ip
provider_sgs_specified = (validators.is_attr_set(
port_data.get(provider_sg.PROVIDER_SECURITYGROUPS)) and
port_data.get(provider_sg.PROVIDER_SECURITYGROUPS) != [])
return provider_sgs_specified
def _get_net_tz(self, context, net_id):
mappings = nsx_db.get_nsx_switch_ids(context.session, net_id)
if mappings:
nsx_net_id = nsx_net_id = mappings[0]
if nsx_net_id:
nsx_net = self.nsxlib.logical_switch.get(nsx_net_id)
return nsx_net.get('transport_zone_id')
def _is_ens_tz_net(self, context, net_id):
#Check the host-switch-mode of the TZ connected to network
tz_id = self._get_net_tz(context, net_id)
if tz_id:
# Check the mode of this TZ
mode = self.nsxlib.transport_zone.get_host_switch_mode(tz_id)
return (mode ==
self.nsxlib.transport_zone.HOST_SWITCH_MODE_ENS)
return False
def _is_ens_tz_port(self, context, port_data):
# Check the host-switch-mode of the TZ connected to the ports network
return self._is_ens_tz_net(context, port_data['network_id'])
def _create_port_preprocess_security(
self, context, port, port_data, neutron_db, is_ens_tz_port):
(port_security, has_ip) = self._determine_port_security_and_has_ip(
context, port_data)
port_data[psec.PORTSECURITY] = port_security
# No port security is allowed if the port belongs to an ENS TZ
if port_security and is_ens_tz_port:
raise nsx_exc.NsxENSPortSecurity()
self._process_port_port_security_create(
context, port_data, neutron_db)
# allowed address pair checks
address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS)
if validators.is_attr_set(address_pairs):
if not port_security:
raise addr_exc.AddressPairAndPortSecurityRequired()
else:
self._validate_address_pairs(address_pairs)
self._process_create_allowed_address_pairs(
context, neutron_db,
address_pairs)
else:
# remove ATTR_NOT_SPECIFIED
port_data[addr_apidef.ADDRESS_PAIRS] = []
if port_security and has_ip:
self._ensure_default_security_group_on_port(context, port)
(sgids, psgids) = self._get_port_security_groups_lists(
context, port)
elif (self._check_update_has_security_groups({'port': port_data}) or
self._provider_sgs_specified(port_data) or
self._get_provider_security_groups_on_port(context, port)):
LOG.error("Port has conflicting port security status and "
"security groups")
raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups()
else:
sgids = psgids = []
port_data[ext_sg.SECURITYGROUPS] = (
self._get_security_groups_on_port(context, port))
return port_security, has_ip, sgids, psgids
def _assert_on_external_net_with_compute(self, port_data):
# Prevent creating port with device owner prefix 'compute'
# on external networks.
device_owner = port_data.get('device_owner')
if (device_owner is not None and
device_owner.startswith(const.DEVICE_OWNER_COMPUTE_PREFIX)):
err_msg = _("Unable to update/create a port with an external "
"network")
LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)
def _assert_on_dhcp_relay_without_router(self, context, port_data,
original_port=None):
# Prevent creating/updating port with device owner prefix 'compute'
# on a subnet with dhcp relay but no router.
if not original_port:
original_port = port_data
device_owner = port_data.get('device_owner')
if (device_owner is None or
not device_owner.startswith(const.DEVICE_OWNER_COMPUTE_PREFIX)):
# not a compute port
return
if not self.get_network_az_by_net_id(
context,
original_port['network_id']).dhcp_relay_service:
# No dhcp relay for the net of this port
return
# get the subnet id from the fixed ips of the port
if 'fixed_ips' in port_data and port_data['fixed_ips']:
subnet_id = port_data['fixed_ips'][0]['subnet_id']
elif 'fixed_ips' in original_port and original_port['fixed_ips']:
subnet_id = original_port['fixed_ips'][0]['subnet_id']
else:
return
# check only dhcp enabled subnets
subnet = self.get_subnet(context.elevated(), subnet_id)
if not subnet['enable_dhcp']:
return
# check if the subnet is attached to a router
port_filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF],