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.
 
 
 

4362 lines
196 KiB

# Copyright 2018 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 re
import time
import netaddr
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log
from oslo_utils import excutils
from oslo_utils import uuidutils
from neutron.common import ipv6_utils
from neutron.db import agents_db
from neutron.db import l3_db
from neutron.db.models import l3 as l3_db_models
from neutron.db.models import securitygroup as securitygroup_model
from neutron.db import models_v2
from neutron.extensions import securitygroup as ext_sg
from neutron.quota import resource_registry
from neutron_lib.api.definitions import address_scope
from neutron_lib.api.definitions import agent as agent_apidef
from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef
from neutron_lib.api.definitions import availability_zone as az_apidef
from neutron_lib.api.definitions import dhcpagentscheduler
from neutron_lib.api.definitions import external_net
from neutron_lib.api.definitions import extra_dhcp_opt as ext_edo
from neutron_lib.api.definitions import extraroute
from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.api.definitions import network_availability_zone
from neutron_lib.api.definitions import port_security as psec
from neutron_lib.api.definitions import portbindings as pbin_apidef
from neutron_lib.api.definitions import provider_net as pnet_apidef
from neutron_lib.api.definitions import router_availability_zone
from neutron_lib.api.definitions import vlantransparent as vlan_apidef
from neutron_lib.api import extensions
from neutron_lib.api import validators
from neutron_lib.callbacks import events
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 n_context
from neutron_lib.db import api as db_api
from neutron_lib.db import resource_extend
from neutron_lib.db import utils as db_utils
from neutron_lib import exceptions as n_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 vmware_nsx._i18n import _
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 nsxv_constants
from vmware_nsx.common import utils
from vmware_nsx.db import db as nsx_db
from vmware_nsx.extensions import api_replay
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 secgroup_rule_local_ip_prefix as sg_prefix
from vmware_nsx.extensions import securitygrouplogging as sg_logging
from vmware_nsx.plugins.common_v3 import plugin as nsx_plugin_common
from vmware_nsx.plugins.nsx_p import availability_zones as nsxp_az
from vmware_nsx.plugins.nsx_p import utils as plugin_utils
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_p import fwaas_callbacks_v2
from vmware_nsx.services.lbaas import lb_const
from vmware_nsx.services.lbaas.nsx_p.implementation import healthmonitor_mgr
from vmware_nsx.services.lbaas.nsx_p.implementation import l7policy_mgr
from vmware_nsx.services.lbaas.nsx_p.implementation import l7rule_mgr
from vmware_nsx.services.lbaas.nsx_p.implementation import lb_utils
from vmware_nsx.services.lbaas.nsx_p.implementation import listener_mgr
from vmware_nsx.services.lbaas.nsx_p.implementation import loadbalancer_mgr
from vmware_nsx.services.lbaas.nsx_p.implementation import member_mgr
from vmware_nsx.services.lbaas.nsx_p.implementation import pool_mgr
from vmware_nsx.services.lbaas.octavia import constants as oct_const
from vmware_nsx.services.lbaas.octavia import octavia_listener
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.qos.nsx_v3 import pol_utils as qos_utils
from vmware_nsx.services.trunk.nsx_p import driver as trunk_driver
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
from vmware_nsxlib.v3.policy import constants as policy_constants
from vmware_nsxlib.v3.policy import core_defs as policy_defs
from vmware_nsxlib.v3.policy import transaction as policy_trans
from vmware_nsxlib.v3.policy import utils as p_utils
from vmware_nsxlib.v3 import security
from vmware_nsxlib.v3 import utils as nsxlib_utils
LOG = log.getLogger(__name__)
NSX_P_SECURITY_GROUP_TAG = 'os-security-group'
NSX_P_GLOBAL_DOMAIN_ID = policy_constants.DEFAULT_DOMAIN
NSX_P_DEFAULT_GROUP = 'os_default_group'
NSX_P_DEFAULT_GROUP_DESC = 'Default Group for the openstack plugin'
NSX_P_DEFAULT_SECTION = 'os_default_section'
NSX_P_DEFAULT_SECTION_DESC = ('This section is handled by OpenStack to '
'contain default rules on security-groups.')
NSX_P_DEFAULT_SECTION_CATEGORY = policy_constants.CATEGORY_APPLICATION
NSX_P_REGULAR_SECTION_CATEGORY = policy_constants.CATEGORY_ENVIRONMENT
NSX_P_PROVIDER_SECTION_CATEGORY = policy_constants.CATEGORY_INFRASTRUCTURE
NSX_P_PORT_RESOURCE_TYPE = 'os-neutron-port-id'
NSX_P_EXCLUDE_LIST_GROUP = 'neutron_excluded_ports_group'
NSX_P_EXCLUDE_LIST_TAG = 'Exclude-Port'
SPOOFGUARD_PROFILE_ID = 'neutron-spoofguard-profile'
NO_SPOOFGUARD_PROFILE_ID = policy_defs.SpoofguardProfileDef.DEFAULT_PROFILE
MAC_DISCOVERY_PROFILE_ID = 'neutron-mac-discovery-profile'
NO_MAC_DISCOVERY_PROFILE_ID = (
policy_defs.MacDiscoveryProfileDef.DEFAULT_PROFILE)
NO_SEG_SECURITY_PROFILE_ID = 'neutron-no-segment-security-profile'
SEG_SECURITY_PROFILE_ID = (
policy_defs.SegmentSecurityProfileDef.DEFAULT_PROFILE)
SLAAC_NDRA_PROFILE_ID = 'neutron-slaac-profile'
NO_SLAAC_NDRA_PROFILE_ID = 'neutron-no-slaac-profile'
STATELESS_DHCP_NDRA_PROFILE_ID = 'neutron-stateless-dhcp-profile'
STATEFUL_DHCP_NDRA_PROFILE_ID = 'neutron-stateful-dhcp-profile'
IPV6_RA_SERVICE = 'neutron-ipv6-ra'
IPV6_ROUTER_ADV_RULE_NAME = 'all-ipv6'
# Priorities for NAT rules: (FIP specific rules should come before GW rules)
NAT_RULE_PRIORITY_FIP = 2000
NAT_RULE_PRIORITY_GW = 3000
NSX_P_CLIENT_SSL_PROFILE = 'neutron-client-ssl-profile'
# Cache for mapping between network ids in neutron and NSX (MP)
NET_NEUTRON_2_NSX_ID_CACHE = {}
NET_NSX_2_NEUTRON_ID_CACHE = {}
@resource_extend.has_resource_extenders
class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
__native_bulk_support = True
__native_pagination_support = True
__native_sorting_support = True
supported_extension_aliases = [addr_apidef.ALIAS,
address_scope.ALIAS,
"quotas",
pbin_apidef.ALIAS,
ext_edo.ALIAS,
agent_apidef.ALIAS,
dhcpagentscheduler.ALIAS,
"ext-gw-mode",
"security-group",
sg_prefix.ALIAS,
psec.ALIAS,
pnet_apidef.ALIAS,
external_net.ALIAS,
extraroute.ALIAS,
l3_apidef.ALIAS,
az_apidef.ALIAS,
network_availability_zone.ALIAS,
router_availability_zone.ALIAS,
"subnet_allocation",
sg_logging.ALIAS,
provider_sg.ALIAS,
"port-security-groups-filtering",
mac_ext.ALIAS,
"advanced-service-providers"]
@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.init_is_complete = False
self._is_sub_plugin = False
self.octavia_listener = None
self.octavia_stats_collector = None
nsxlib_utils.set_is_attr_callback(validators.is_attr_set)
self._extend_fault_map()
extension_drivers = cfg.CONF.nsx_extension_drivers
self._extension_manager = managers.ExtensionManager(
extension_drivers=extension_drivers)
self.cfg_group = 'nsx_p' # group name for nsx_p section in nsx.ini
self.init_availability_zones()
self.nsxpolicy = v3_utils.get_nsxpolicy_wrapper()
# NOTE: This is needed for passthrough APIs, should be removed when
# policy has full support
self.nsxlib = self.nsxpolicy.get_nsxlib_passthrough()
super(NsxPolicyPlugin, self).__init__()
# Bind the dummy L3 notifications
self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI()
LOG.info("Starting NsxPolicyPlugin")
self._extension_manager.initialize()
self.supported_extension_aliases.extend(
self._extension_manager.extension_aliases())
# Support transparent VLANS only if the global configuration flag
# vlan_transparent is True
if cfg.CONF.vlan_transparent:
self.supported_extension_aliases.append(vlan_apidef.ALIAS)
# Support api-reply for migration environments to the policy plugin
if cfg.CONF.api_replay_mode:
self.supported_extension_aliases.append(api_replay.ALIAS)
nsxlib_utils.set_inject_headers_callback(v3_utils.inject_headers)
self._validate_nsx_policy_version()
self._validate_config()
self._init_default_config()
self._prepare_default_rules()
self._init_profiles()
self._prepare_exclude_list()
self._init_dhcp_metadata()
# Init QoS
qos_driver.register(qos_utils.PolicyQosNotificationsHandler())
# Register NSXP trunk driver to support trunk extensions
self.trunk_driver = trunk_driver.NsxpTrunkDriver.create(self)
registry.subscribe(self.spawn_complete,
resources.PROCESS,
events.AFTER_SPAWN)
# 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 _validate_config(self):
if cfg.CONF.ipam_driver != 'internal':
msg = _("External IPAM drivers not supported with nsxp plugin")
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
def _enable_ipv6_routing(self):
# Ipv6 is disabled by default in NSX and should be enabled
if self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_NSX_POLICY_GLOBAL_CONFIG):
self.nsxpolicy.global_config.enable_ipv6()
return
if cfg.CONF.nsx_p.allow_passthrough:
self.nsxlib.global_routing.enable_ipv6()
else:
LOG.warning("Unable to switch on Ipv6 forwarding. Ipv6 "
"connectivity might be broken.")
def _init_default_config(self):
self._enable_ipv6_routing()
# Validate other mandatory configuration
if not cfg.CONF.nsx_p.dhcp_profile:
raise cfg.RequiredOptError("dhcp_profile",
group=cfg.OptGroup('nsx_p'))
if not cfg.CONF.nsx_p.metadata_proxy:
raise cfg.RequiredOptError("metadata_proxy",
group=cfg.OptGroup('nsx_p'))
# If using tags to find the objects, make sure tag scope is configured
if (cfg.CONF.nsx_p.init_objects_by_tags and
not cfg.CONF.nsx_p.search_objects_scope):
raise cfg.RequiredOptError("search_objects_scope",
group=cfg.OptGroup('nsx_p'))
# Init AZ resources
search_scope = (cfg.CONF.nsx_p.search_objects_scope
if cfg.CONF.nsx_p.init_objects_by_tags else None)
for az in self.get_azs_list():
az.translate_configured_names_to_uuids(
self.nsxpolicy, nsxlib=self.nsxlib, search_scope=search_scope)
az.validate_availability_zone(self.nsxpolicy, nsxlib=self.nsxlib)
# WAF is currently not supported by the NSX
self._waf_profile_uuid = None
try:
self.nsxpolicy.mixed_service.get(IPV6_RA_SERVICE)
except nsx_lib_exc.ResourceNotFound:
# create or override ipv6 RA service
unicast_ra = self.nsxpolicy.icmp_service.build_entry(
'unicast RA', IPV6_RA_SERVICE, 'unicast',
version=6, icmp_type=134)
multicast_ra = self.nsxpolicy.icmp_service.build_entry(
'multicast RA', IPV6_RA_SERVICE, 'multicast',
version=6, icmp_type=151)
try:
self.nsxpolicy.mixed_service.create_or_overwrite(
IPV6_RA_SERVICE, IPV6_RA_SERVICE,
entries=[unicast_ra, multicast_ra])
except nsx_lib_exc.StaleRevision as e:
# This means that another controller is also creating this
LOG.info("Failed to configure mixed_service: %s", e)
except nsx_lib_exc.ManagerError:
msg = _("Failed to configure RA service for IPv6 connectivity")
LOG.error(msg)
raise nsx_exc.NsxPluginException(err_msg=msg)
def _init_backend_resource(self, resource_api, name_or_id,
search_scope=None):
resource_type = resource_api.entry_def.resource_type()
if not name_or_id:
return None
try:
# Check if the configured value is the ID
resource_api.get(name_or_id, silent=True)
return name_or_id
except nsx_lib_exc.ResourceNotFound:
# Search by tags
if search_scope:
resource_id = self.nsxpolicy.get_id_by_resource_and_tag(
resource_type,
search_scope,
name_or_id)
if resource_id:
return resource_id
# Check if the configured value is the name
resource = resource_api.get_by_name(name_or_id)
if resource:
return resource['id']
msg = (_("Could not find %(type)s %(id)s") % {
'type': resource_type, 'id': name_or_id})
raise nsx_exc.NsxPluginException(err_msg=msg)
def get_waf_profile_path_and_mode(self):
# WAF is currently not supported by the NSX
return None, None
def _init_dhcp_metadata(self):
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)
default_az = self.get_default_az()
if default_az.use_policy_dhcp:
self.use_policy_dhcp = True
LOG.info("The policy plugin will use policy based DHCP v4/6")
else:
self._init_native_dhcp()
self.use_policy_dhcp = False
LOG.info("The policy plugin will use MP based DHCP v4")
self._init_native_metadata()
def init_availability_zones(self):
self._availability_zones_data = nsxp_az.NsxPAvailabilityZones()
def _validate_nsx_policy_version(self):
self._nsx_version = self.nsxpolicy.get_version()
LOG.info("NSX Version: %s", self._nsx_version)
if (not self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_NSX_POLICY_NETWORKING) or
not utils.is_nsx_version_2_5_0(self._nsx_version)):
msg = (_("The NSX Policy plugin requires version 2.5 "
"(current version %(ver)s)") % {'ver': self._nsx_version})
raise nsx_exc.NsxPluginException(err_msg=msg)
def _init_profiles(self):
"""Find/Create segment profiles this plugin will use"""
# Spoofguard profile (find it or create)
try:
self.nsxpolicy.spoofguard_profile.get(SPOOFGUARD_PROFILE_ID)
except nsx_lib_exc.ResourceNotFound:
try:
self.nsxpolicy.spoofguard_profile.create_or_overwrite(
SPOOFGUARD_PROFILE_ID,
profile_id=SPOOFGUARD_PROFILE_ID,
address_binding_whitelist=True,
tags=self.nsxpolicy.build_v3_api_version_tag())
except nsx_lib_exc.StaleRevision as e:
# This means that another controller is also creating this
LOG.info("Failed to configure spoofguard_profile: %s", e)
# No Port security spoofguard profile
# (default NSX profile. just verify it exists)
try:
self.nsxpolicy.spoofguard_profile.get(NO_SPOOFGUARD_PROFILE_ID)
except nsx_lib_exc.ResourceNotFound:
msg = (_("Cannot find spoofguard profile %s") %
NO_SPOOFGUARD_PROFILE_ID)
raise nsx_exc.NsxPluginException(err_msg=msg)
# Mac discovery profile (find it or create)
try:
self.nsxpolicy.mac_discovery_profile.get(
MAC_DISCOVERY_PROFILE_ID)
except nsx_lib_exc.ResourceNotFound:
try:
self.nsxpolicy.mac_discovery_profile.create_or_overwrite(
MAC_DISCOVERY_PROFILE_ID,
profile_id=MAC_DISCOVERY_PROFILE_ID,
mac_change_enabled=True,
mac_learning_enabled=True,
tags=self.nsxpolicy.build_v3_api_version_tag())
except nsx_lib_exc.StaleRevision as e:
# This means that another controller is also creating this
LOG.info("Failed to configure mac_discovery_profile: %s", e)
# No Mac discovery profile profile
# (default NSX profile. just verify it exists)
try:
self.nsxpolicy.mac_discovery_profile.get(
NO_MAC_DISCOVERY_PROFILE_ID)
except nsx_lib_exc.ResourceNotFound:
msg = (_("Cannot find MAC discovery profile %s") %
NO_MAC_DISCOVERY_PROFILE_ID)
raise nsx_exc.NsxPluginException(err_msg=msg)
# No Port security segment-security profile (find it or create)
try:
self.nsxpolicy.segment_security_profile.get(
NO_SEG_SECURITY_PROFILE_ID)
except nsx_lib_exc.ResourceNotFound:
try:
self.nsxpolicy.segment_security_profile.create_or_overwrite(
NO_SEG_SECURITY_PROFILE_ID,
profile_id=NO_SEG_SECURITY_PROFILE_ID,
bpdu_filter_enable=False,
dhcp_client_block_enabled=False,
dhcp_client_block_v6_enabled=False,
dhcp_server_block_enabled=False,
dhcp_server_block_v6_enabled=False,
non_ip_traffic_block_enabled=False,
ra_guard_enabled=False,
rate_limits_enabled=False,
tags=self.nsxpolicy.build_v3_api_version_tag())
except nsx_lib_exc.StaleRevision as e:
# This means that another controller is also creating this
LOG.info("Failed to configure segment_security_profile: %s", e)
# Port security segment-security profile
# (default NSX profile. just verify it exists)
try:
self.nsxpolicy.segment_security_profile.get(
SEG_SECURITY_PROFILE_ID)
except nsx_lib_exc.ResourceNotFound:
msg = (_("Cannot find segment security profile %s") %
SEG_SECURITY_PROFILE_ID)
raise nsx_exc.NsxPluginException(err_msg=msg)
# Find or create all neutron NDRA profiles
ndra_profiles = {
SLAAC_NDRA_PROFILE_ID: policy_constants.IPV6_RA_MODE_SLAAC_RA,
STATELESS_DHCP_NDRA_PROFILE_ID:
policy_constants.IPV6_RA_MODE_SLAAC_DHCP,
STATEFUL_DHCP_NDRA_PROFILE_ID: policy_constants.IPV6_RA_MODE_DHCP,
NO_SLAAC_NDRA_PROFILE_ID: policy_constants.IPV6_RA_MODE_DISABLED
}
for profile in ndra_profiles:
try:
self.nsxpolicy.ipv6_ndra_profile.get(profile)
except nsx_lib_exc.ResourceNotFound:
try:
self.nsxpolicy.ipv6_ndra_profile.create_or_overwrite(
profile,
profile_id=profile,
ra_mode=ndra_profiles[profile],
tags=self.nsxpolicy.build_v3_api_version_tag())
except nsx_lib_exc.StaleRevision as e:
# This means that another controller is also creating this
LOG.info("Failed to configure ipv6_ndra_profile %s: %s",
profile, e)
self.client_ssl_profile = None
LOG.debug("Initializing NSX-P Load Balancer default profiles")
try:
self._init_lb_profiles()
except Exception as e:
msg = (_("Unable to initialize NSX-P lb profiles: "
"Reason: %(reason)s") % {'reason': str(e)})
raise nsx_exc.NsxPluginException(err_msg=msg)
@staticmethod
def plugin_type():
return projectpluginmap.NsxPlugins.NSX_P
@staticmethod
def is_tvd_plugin():
return False
def _init_fwaas(self, with_rpc):
if self.fwaas_callbacks:
# already initialized
return
if fwaas_utils.is_fwaas_v2_plugin_enabled():
LOG.info("NSXp FWaaS v2 plugin enabled")
self.fwaas_callbacks = fwaas_callbacks_v2.NsxpFwaasCallbacksV2(
with_rpc)
def _get_octavia_stats_getter(self):
return listener_mgr.stats_getter
def _get_octavia_status_getter(self):
return loadbalancer_mgr.status_getter
def _init_lb_profiles(self):
ssl_profile_client = self.nsxpolicy.load_balancer.client_ssl_profile
with locking.LockManager.get_lock('nsxp_lb_profiles_init'):
try:
ssl_profile_client.get(NSX_P_CLIENT_SSL_PROFILE)
except nsx_lib_exc.ResourceNotFound:
try:
ssl_profile_client.create_or_overwrite(
NSX_P_CLIENT_SSL_PROFILE,
client_ssl_profile_id=NSX_P_CLIENT_SSL_PROFILE,
description='Neutron LB Client SSL Profile',
tags=self.nsxpolicy.build_v3_api_version_tag())
except nsx_lib_exc.StaleRevision as e:
# This means that another controller is also creating this
LOG.info("Failed to configure LB client_ssl_profile: %s",
e)
self.client_ssl_profile = NSX_P_CLIENT_SSL_PROFILE
def spawn_complete(self, resource, event, trigger, payload=None):
# Init the FWaaS support with RPC listeners for the original process
self._init_fwaas(with_rpc=True)
self._init_octavia()
self.octavia_stats_collector = (
octavia_listener.NSXOctaviaStatisticsCollector(
self,
self._get_octavia_stats_getter(),
self._get_octavia_status_getter()))
def _init_octavia(self):
octavia_objects = self._get_octavia_objects()
self.octavia_listener = octavia_listener.NSXOctaviaListener(
**octavia_objects)
def _get_octavia_objects(self):
return {
'loadbalancer': loadbalancer_mgr.EdgeLoadBalancerManagerFromDict(),
'listener': listener_mgr.EdgeListenerManagerFromDict(),
'pool': pool_mgr.EdgePoolManagerFromDict(),
'member': member_mgr.EdgeMemberManagerFromDict(),
'healthmonitor':
healthmonitor_mgr.EdgeHealthMonitorManagerFromDict(),
'l7policy': l7policy_mgr.EdgeL7PolicyManagerFromDict(),
'l7rule': l7rule_mgr.EdgeL7RuleManagerFromDict()}
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.nsxpolicy.reinitialize_cluster(resource, event, trigger,
payload=payload)
# Init the FWaaS support without RPC listeners
# for the spawn workers
self._init_fwaas(with_rpc=False)
# Init octavia listener and endpoints
self._init_octavia()
self.init_is_complete = True
def _setup_rpc(self):
self.endpoints = [agents_db.AgentExtRpcCallback()]
def _net_nsx_name(self, network):
name = utils.get_name_and_uuid(network.get('name') or 'network',
network['id'])
# remove illegal characters in segment names: ;|=,~@'
name = re.sub("[;|=,~@\']", '', name)
return name
def _create_network_on_backend(self, context, net_data,
transparent_vlan, provider_data, az,
request_data):
net_data['id'] = net_data.get('id') or uuidutils.generate_uuid()
# update the network name to indicate the neutron id too.
net_name = self._net_nsx_name(net_data)
tags = self.nsxpolicy.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']})
if transparent_vlan:
# all vlan tags are allowed for guest vlan
vlan_ids = ["0-%s" % const.MAX_VLAN_TAG]
elif provider_data['vlan_id']:
vlan_ids = [provider_data['vlan_id']]
else:
vlan_ids = None
kwargs = {
'segment_id': net_data['id'],
'description': net_data.get('description'),
'vlan_ids': vlan_ids,
'transport_zone_id': provider_data['physical_net'],
'tags': tags}
if (not admin_state and
self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_NSX_POLICY_ADMIN_STATE)):
kwargs['admin_state'] = admin_state
if az.use_policy_md:
kwargs['metadata_proxy_id'] = az._native_md_proxy_uuid
# Set the segment vni for nsx-v portgroups migration
if (cfg.CONF.api_replay_mode and
validators.is_attr_set(request_data.get('vni'))):
kwargs['overlay_id'] = request_data['vni']
self.nsxpolicy.segment.create_or_overwrite(
net_name, **kwargs)
if (not admin_state and cfg.CONF.nsx_p.allow_passthrough and
not self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_NSX_POLICY_ADMIN_STATE)):
# This api uses the passthrough api
self.nsxpolicy.segment.set_admin_state(
net_data['id'], admin_state)
def _tier0_validator(self, tier0_uuid):
# Fail if the tier0 uuid was not found on the NSX
try:
self.nsxpolicy.tier0.get(tier0_uuid)
except Exception:
msg = (_("Cannot create external network as Tier0 %s was not "
"found") % tier0_uuid)
raise n_exc.InvalidInput(error_message=msg)
def _get_nsx_net_tz_id(self, nsx_net):
return nsx_net['transport_zone_path'].split('/')[-1]
def _allow_ens_networks(self):
return True
def _ens_psec_supported(self):
"""ENS security features are always enabled on NSX versions which
the policy plugin supports.
"""
return True
def _ens_qos_supported(self):
return self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_ENS_WITH_QOS)
def _validate_ens_net_portsecurity(self, net_data):
"""ENS security features are always enabled on NSX versions which
the policy plugin supports.
So no validation is needed
"""
pass
def _assert_on_resource_admin_state_down(self, resource_data):
"""Network & port admin state is only supported with passthrough api"""
if (not cfg.CONF.nsx_p.allow_passthrough and
resource_data.get("admin_state_up") is False):
err_msg = (_("admin_state_up=False is not supported when "
"passthrough is disabled"))
LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)
def create_network(self, context, network):
net_data = network['network']
external = net_data.get(external_net.EXTERNAL)
is_external_net = validators.is_attr_set(external) and external
tenant_id = net_data['tenant_id']
# validate the availability zone, and get the AZ object
az = self._validate_obj_az_on_creation(context, net_data, 'network')
self._ensure_default_security_group(context, tenant_id)
vlt = False
if extensions.is_extension_supported(self, 'vlan-transparent'):
vlt = vlan_apidef.get_vlan_transparent(net_data)
self._validate_create_network(context, net_data)
self._assert_on_resource_admin_state_down(net_data)
if is_external_net:
is_provider_net, net_type, physical_net, vlan_id = (
self._validate_external_net_create(
net_data, az._default_tier0_router,
self._tier0_validator))
provider_data = {'is_provider_net': is_provider_net,
'net_type': net_type,
'physical_net': physical_net,
'vlan_id': vlan_id}
is_backend_network = False
else:
provider_data = self._validate_provider_create(
context, net_data, az,
self.nsxpolicy.transport_zone,
self.nsxpolicy.segment,
transparent_vlan=vlt)
if (provider_data['is_provider_net'] and
provider_data['net_type'] ==
utils.NsxV3NetworkTypes.NSX_NETWORK):
is_backend_network = False
else:
is_backend_network = True
# Create the neutron network
with db_api.CONTEXT_WRITER.using(context):
# Create network in Neutron
created_net = super(NsxPolicyPlugin, self).create_network(
context, network)
net_id = created_net['id']
if extensions.is_extension_supported(self, 'vlan-transparent'):
super(NsxPolicyPlugin, self).update_network(
context, net_id,
{'network': {'vlan_transparent': vlt}})
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)
self._add_az_to_net(context, net_id, net_data)
if provider_data['is_provider_net']:
# Save provider network fields, needed by get_network()
net_bindings = [nsx_db.add_network_binding(
context.session, net_id,
provider_data['net_type'],
provider_data['physical_net'],
provider_data['vlan_id'])]
self._extend_network_dict_provider(context, created_net,
bindings=net_bindings)
# Create the backend NSX network
if is_backend_network:
try:
self._create_network_on_backend(
context, created_net, vlt, provider_data, az, net_data)
except Exception as e:
LOG.exception("Failed to create NSX network network: %s", e)
with excutils.save_and_reraise_exception():
super(NsxPolicyPlugin, self).delete_network(
context, net_id)
# this extra lookup is necessary to get the
# latest db model for the extension functions
net_model = self._get_network(context, net_id)
resource_extend.apply_funcs('networks', created_net, net_model)
# MD Proxy is currently supported by the passthrough api only
if (is_backend_network and not az.use_policy_md and
cfg.CONF.nsx_p.allow_passthrough):
# The new segment was not realized yet. Waiting for a bit.
time.sleep(cfg.CONF.nsx_p.realization_wait_sec)
nsx_net_id = self._get_network_nsx_id(context, net_id)
if not nsx_net_id:
msg = ("Unable to obtain backend network id for metadata "
"proxy creation for network %s" % net_id)
LOG.error(msg)
raise nsx_exc.NsxPluginException(err_msg=msg)
try:
self._create_net_mp_mdproxy_port(
context, created_net, az, nsx_net_id)
except Exception as e:
msg = ("Failed to complete network creation. error: %s" % e)
LOG.exception(msg)
self.delete_network(context, net_id)
raise nsx_exc.NsxPluginException(err_msg=msg)
# Update the QoS policy (will affect only future compute ports)
qos_com_utils.set_qos_policy_on_new_net(
context, net_data, created_net)
if net_data.get(qos_consts.QOS_POLICY_ID):
LOG.info("QoS Policy %(qos)s will be applied to future compute "
"ports of network %(net)s",
{'qos': net_data[qos_consts.QOS_POLICY_ID],
'net': created_net['id']})
return created_net
def _check_internal_network(self, net):
if net.get('tenant_id') == nsxv_constants.INTERNAL_TENANT_ID:
msg = _("This network was created during the migration for "
"internal usage and cannot be deleted")
raise n_exc.InvalidInput(error_message=msg)
def delete_network(self, context, network_id):
is_external_net = self._network_is_external(context, network_id)
network = self._get_network(context, network_id)
self._check_internal_network(network)
if not is_external_net:
# First disable DHCP & delete its port
if self.use_policy_dhcp:
lock = 'nsxp_network_' + network_id
with locking.LockManager.get_lock(lock):
if not self._has_active_port(context, network_id):
self._disable_network_dhcp(context, network)
elif cfg.CONF.nsx_p.allow_passthrough:
self._delete_network_disable_dhcp(context, network_id)
is_nsx_net = self._network_is_nsx_net(context, network_id)
# Call DB operation for delete network as it will perform
# checks on active ports
self._retry_delete_network(context, network_id)
# Delete MD proxy port. This is relevant only if the plugin used
# MP MD proxy when this network is created.
# If not - the port will not be found, and it is ok.
# Note(asarfaty): In the future this code can be removed.
# TODO(asarfaty): For migrated networks when the DB was not cleaned up
# This may actually delete a port the policy now control
if (not is_external_net and not is_nsx_net and
cfg.CONF.nsx_p.allow_passthrough):
self._delete_nsx_port_by_network(network_id)
# Delete the network segment from the backend
if not is_external_net and not is_nsx_net:
try:
self.nsxpolicy.segment.delete(network_id)
# In case of migrated network, a dhcp server config with
# the same id should also be deleted
self.nsxpolicy.dhcp_server_config.delete(network_id)
except nsx_lib_exc.ResourceNotFound:
# If the resource was not found on the backend do not worry
# about it. The conditions has already been logged, so there
# is no need to do further logging
pass
except nsx_lib_exc.ManagerError as e:
# If there is a failure in deleting the resource, fail the
# neutron operation even though the neutron object was already
# deleted. This way the user will be aware of zombie resources
# that may fail future actions.
msg = (_("Backend segment deletion for neutron network %(id)s "
"failed. The object was however removed from the "
"Neutron database: %(e)s") %
{'id': network_id, 'e': e})
raise nsx_exc.NsxPluginException(err_msg=msg)
# Remove from caches
if network_id in NET_NEUTRON_2_NSX_ID_CACHE:
nsx_id = NET_NEUTRON_2_NSX_ID_CACHE[network_id]
del NET_NEUTRON_2_NSX_ID_CACHE[network_id]
if nsx_id in NET_NSX_2_NEUTRON_ID_CACHE:
del NET_NSX_2_NEUTRON_ID_CACHE[nsx_id]
def update_network(self, context, network_id, network):
original_net = super(NsxPolicyPlugin, self).get_network(
context, network_id)
net_data = network['network']
# Validate the updated parameters
self._validate_update_network(context, network_id, original_net,
net_data)
self._assert_on_resource_admin_state_down(net_data)
# Neutron does not support changing provider network values
utils.raise_if_updates_provider_attributes(net_data)
extern_net = self._network_is_external(context, network_id)
is_nsx_net = self._network_is_nsx_net(context, network_id)
# Update the neutron network
updated_net = super(NsxPolicyPlugin, self).update_network(
context, network_id, network)
self._extension_manager.process_update_network(context, net_data,
updated_net)
if psec.PORTSECURITY in net_data:
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 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, network_id, net_data[qos_consts.QOS_POLICY_ID])
updated_net[qos_consts.QOS_POLICY_ID] = net_data[
qos_consts.QOS_POLICY_ID]
if net_data[qos_consts.QOS_POLICY_ID]:
LOG.info("QoS Policy %(qos)s will be applied to future "
"compute ports of network %(net)s",
{'qos': net_data[qos_consts.QOS_POLICY_ID],
'net': network_id})
# Update the backend segment
if (not extern_net and not is_nsx_net and
('name' in net_data or 'description' in net_data or
'admin_state_up' in net_data)):
net_name = self._net_nsx_name(updated_net)
kwargs = {'name': net_name,
'description': updated_net.get('description', '')}
if 'admin_state_up' in net_data:
if (self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_NSX_POLICY_ADMIN_STATE)):
kwargs['admin_state'] = net_data['admin_state_up']
elif cfg.CONF.nsx_p.allow_passthrough:
# Update admin state using the passthrough api
self.nsxpolicy.segment.set_admin_state(
network_id, net_data['admin_state_up'])
try:
self.nsxpolicy.segment.update(network_id, **kwargs)
except nsx_lib_exc.ManagerError:
LOG.exception("Unable to update NSX backend, rolling "
"back changes on neutron")
with excutils.save_and_reraise_exception():
# remove the AZ from the network before rollback because
# it is read only, and breaks the rollback
if 'availability_zone_hints' in original_net:
del original_net['availability_zone_hints']
super(NsxPolicyPlugin, self).update_network(
context, network_id, {'network': original_net})
return updated_net
def _get_subnets_nd_profile(self, subnets, additional_profile=None):
profiles = []
if additional_profile:
profiles.append(additional_profile)
for sub in subnets:
profiles.append(self._get_subnet_ndra_profile(sub))
# If there is 1 stateful/stateless DHCP subnet (cannot have both)
# use this profile
if STATEFUL_DHCP_NDRA_PROFILE_ID in profiles:
return STATEFUL_DHCP_NDRA_PROFILE_ID
elif STATELESS_DHCP_NDRA_PROFILE_ID in profiles:
return STATELESS_DHCP_NDRA_PROFILE_ID
elif SLAAC_NDRA_PROFILE_ID in profiles:
# if there is slaac subnet and no DHCP subnet use SLAAC
return SLAAC_NDRA_PROFILE_ID
return NO_SLAAC_NDRA_PROFILE_ID
def _update_slaac_on_router(self, context, router_id,
subnet, router_subnets, delete=False):
# TODO(annak): redesign when policy supports downlink-level
# ndra profile attachment
# This code is optimised to deal with concurrency challenges
# (which can not be always solved by lock because the plugin
# can run on different hosts).
# We prefer to make another backend call for attaching the
# profile even if it is already attached, than rely on DB
# to have an accurate picture of existing subnets.
# This method assumes that all the v6 subnets have the same
# ipv6_address_mode.
# Otherwise, earlier validation would already fail.
if subnet.get('ip_version') == 4:
# This subnet will not affect the ND profile
return
# Fetch other overlay interface networks
# (VLAN advertising is attached on interface level)
ipv6_overlay_subnets = [s for s in router_subnets
if s['id'] != subnet['id'] and
s.get('ip_version') == 6 and
s.get('enable_dhcp') and
self._is_overlay_network(context,
s['network_id'])]
if delete:
# 'subnet' was already removed from the router_subnets list before
# calling this method
if ipv6_overlay_subnets:
# If there is another ipv6 overlay - select the profile by its
# address mode
profile_id = self._get_subnets_nd_profile(ipv6_overlay_subnets)
else:
# this was the last ipv6 subnet connected -
# need to disable slaac on router
profile_id = NO_SLAAC_NDRA_PROFILE_ID
else:
profile_id = self._get_subnet_ndra_profile(subnet)
# Check the other subnets too
if (ipv6_overlay_subnets and
profile_id in [NO_SLAAC_NDRA_PROFILE_ID,
SLAAC_NDRA_PROFILE_ID]):
profile_id = self._get_subnets_nd_profile(
ipv6_overlay_subnets, additional_profile=profile_id)
self.nsxpolicy.tier1.update(router_id, ipv6_ndra_profile_id=profile_id)
def _validate_net_dhcp_edge_cluster(self, context, network, az):
"""Validate that the dhcp server edge cluster match the one of
the network TZ
"""
if not self.nsxlib:
# Cannot validate the TZ because the fabric apis are available
# only via the nsxlib
return
net_tz = self._get_net_tz(context, network['id'])
dhcp_ec_path = self.nsxpolicy.dhcp_server_config.get(
az._policy_dhcp_server_config).get('edge_cluster_path')
if not dhcp_ec_path:
LOG.warning("DHCP server config %s is missing an edge cluster",
az._policy_dhcp_server_config)
return
ec_id = p_utils.path_to_id(dhcp_ec_path)
try:
ec_tzs = plugin_utils.get_edge_cluster_tzs(
self.nsxpolicy, self.nsxlib, ec_id)
except nsx_lib_exc.ResourceNotFound as e:
# Do not fail neutron action init if this code fails
LOG.warning("Failed to get edge cluster %s transport zones: %s",
ec_id, e)
return
if net_tz not in ec_tzs:
msg = (_('Network TZ %(tz)s does not match DHCP server '
'edge cluster %(ec)s') %
{'tz': net_tz, 'ec': ec_id})
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
def _create_subnet_dhcp_port(self, context, az, network, subnet):
port = self._get_net_dhcp_port(context, network['id'])
if port:
# If the port already exist (with another subnet) - update it with
# the additional ip
port['fixed_ips'].append({'subnet_id': subnet['id']})
super(NsxPolicyPlugin, self).update_port(
context, port['id'],
{'port': {'fixed_ips': port['fixed_ips']}})
return
port_data = {
"name": "",
"admin_state_up": True,
"device_id": network['id'],
"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(NsxPolicyPlugin, 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)
self._process_portbindings_create_and_update(
context, port_data, neutron_port)
def _delete_subnet_dhcp_port(self, context, net_id, subnet_id=None):
dhcp_port = self._get_net_dhcp_port(context, net_id)
if dhcp_port:
if subnet_id:
# deleting just this subnets dhcp
if len(dhcp_port['fixed_ips']) > 1:
new_fixed_ips = [ip for ip in dhcp_port['fixed_ips']
if ip['subnet_id'] != subnet_id]
super(NsxPolicyPlugin, self).update_port(
context, dhcp_port['id'],
{'port': {'fixed_ips': new_fixed_ips}})
return
# Delete the port itself
self.delete_port(context, dhcp_port['id'],
force_delete_dhcp=True)
def _get_net_dhcp_port(self, context, net_id):
filters = {
'network_id': [net_id],
'device_owner': [const.DEVICE_OWNER_DHCP]
}
dhcp_ports = self.get_ports(context, filters=filters)
return dhcp_ports[0] if dhcp_ports else None
def _get_sunbet_dhcp_server_ip(self, context, net_id, dhcp_subnet_id):
dhcp_port = self._get_net_dhcp_port(context, net_id)
if dhcp_port:
dhcp_server_ips = [fip['ip_address']
for fip in dhcp_port['fixed_ips']
if fip['subnet_id'] == dhcp_subnet_id]
if dhcp_server_ips:
return dhcp_server_ips[0]
def _is_dhcp_network(self, context, net_id):
dhcp_port = self._get_net_dhcp_port(context, net_id)
return True if dhcp_port else False
def _get_segment_subnets(self, context, net_id, net_az=None,
interface_subnets=None,
deleted_dhcp_subnets=None):
"""Get an updated list of segmentSubnet objects to put on the segment
Including router interface subnets (for overlay networks) &
DHCP subnets (if using policy v4/v6 DHCP)
"""
dhcp_subnets = []
if self.use_policy_dhcp:
# Find networks DHCP enabled subnets
with db_api.CONTEXT_READER.using(context):
network = self._get_network(context, net_id)
for subnet in network.subnets:
if(subnet.enable_dhcp and
(subnet.ip_version == 4 or
subnet.ipv6_address_mode != const.IPV6_SLAAC)):
if (deleted_dhcp_subnets and
subnet.id in deleted_dhcp_subnets):
# Skip this one as it is being deleted
continue
dhcp_subnets.append(self.get_subnet(context, subnet.id))
if len(dhcp_subnets) == 2:
# A network an have at most 2 DHCP subnets
break
router_subnets = []
if interface_subnets:
router_subnets = interface_subnets
else:
# Get networks overlay router interfaces
if self._is_overlay_network(context, net_id):
router_ids = self._get_network_router_ids(
context.elevated(), net_id)
if router_ids:
router_id = router_ids[0]
router_subnets = self._load_router_subnet_cidrs_from_db(
context.elevated(), router_id)
seg_subnets = []
dhcp_subnet_ids = []
for dhcp_subnet in dhcp_subnets:
dhcp_subnet_id = dhcp_subnet['id']
dhcp_subnet_ids.append(dhcp_subnet_id)
gw_addr = self._get_gateway_addr_from_subnet(dhcp_subnet)
dhcp_server_ip = self._get_sunbet_dhcp_server_ip(
context, net_id, dhcp_subnet_id)
dhcp_config = None
if dhcp_server_ip:
cidr_prefix = int(dhcp_subnet['cidr'].split('/')[1])
dns_nameservers = dhcp_subnet['dns_nameservers']
if not net_az:
net_az = self.get_network_az_by_net_id(context, net_id)
if (not dns_nameservers or
not validators.is_attr_set(dns_nameservers)):
# Use pre-configured dns server
dns_nameservers = net_az.nameservers
is_ipv6 = True if dhcp_subnet.get('ip_version') == 6 else False
server_ip = "%s/%s" % (dhcp_server_ip, cidr_prefix)
kwargs = {'server_address': server_ip,
'dns_servers': dns_nameservers}
if is_ipv6:
network = self._get_network(context, net_id)
kwargs['domain_names'] = [
self._get_network_dns_domain(net_az, network)]
dhcp_config = policy_defs.SegmentDhcpConfigV6(**kwargs)
else:
dhcp_config = policy_defs.SegmentDhcpConfigV4(**kwargs)
seg_subnet = policy_defs.Subnet(gateway_address=gw_addr,
dhcp_config=dhcp_config)
seg_subnets.append(seg_subnet)
for rtr_subnet in router_subnets:
if rtr_subnet['id'] in dhcp_subnet_ids:
# Do not add the same subnet twice
continue
if rtr_subnet['network_id'] == net_id:
gw_addr = self._get_gateway_addr_from_subnet(rtr_subnet)
seg_subnets.append(
policy_defs.Subnet(gateway_address=gw_addr,
dhcp_config=None))
return seg_subnets
def _enable_subnet_dhcp(self, context, network, subnet, az):
# Allocate a neutron port for the DHCP server
self._create_subnet_dhcp_port(context, az, network, subnet)
# Update the DHCP server on the segment
net_id = network['id']
segment_id = self._get_network_nsx_segment_id(context, net_id)
seg_subnets = self._get_segment_subnets(context, net_id, net_az=az)
dhcp_config = self._get_segment_dhcp_server_config(segment_id, az)
# Update dhcp server config on the segment
self.nsxpolicy.segment.update(
segment_id=segment_id,
dhcp_server_config_id=dhcp_config,
subnets=seg_subnets)
def _get_net_dhcp_subnets(self, context, net_id):
net_dhcp_subnets = []
net_obj = self._get_network(context, net_id)
for subnet in net_obj.subnets:
if(subnet.enable_dhcp and
(subnet.ip_version == 4 or
subnet.ipv6_address_mode != const.IPV6_SLAAC)):
# This is a DHCP subnet
net_dhcp_subnets.append(subnet.id)
return net_dhcp_subnets
def _disable_network_dhcp(self, context, network, subnet_id=None):
net_id = network['id']
net_dhcp_subnets = self._get_net_dhcp_subnets(context, net_id)
segment_id = self._get_network_nsx_segment_id(context, net_id)
if subnet_id and len(net_dhcp_subnets) > 1:
# remove dhcp only from this subnet
seg_subnets = self._get_segment_subnets(
context, net_id, deleted_dhcp_subnets=[subnet_id])
self.nsxpolicy.segment.update(
segment_id,
subnets=seg_subnets)
self._delete_subnet_dhcp_port(context, net_id, subnet_id=subnet_id)
else:
# Remove dhcp server config completely from the segment
seg_subnets = self._get_segment_subnets(
context, net_id, deleted_dhcp_subnets=net_dhcp_subnets)
self.nsxpolicy.segment.update(
segment_id=segment_id,
subnets=seg_subnets,
dhcp_server_config_id=None)
# Delete the neutron DHCP port (and its bindings)
self._delete_subnet_dhcp_port(context, net_id)
def _get_segment_dhcp_server_config(self, segment_id, az):
"""Return the dhcp server config id the segment should be using
Usually it the AZ one, but in case of migrated segments,
the id of the dhcp server config is the same as the segment id
"""
dhcp_server_config = az._policy_dhcp_server_config
try:
seg = self.nsxpolicy.segment.get(segment_id, silent=True)
except Exception:
pass
else:
if seg.get('dhcp_config_path'):
dhcp_server_config = p_utils.path_to_id(
seg['dhcp_config_path'])
return dhcp_server_config
def _update_nsx_net_dhcp(self, context, network, az, subnet=None):
"""Update the DHCP config on a network
Update the segment DHCP config, as well as the dhcp bindings on the
ports.
If just a specific subnet was modified, update only its ports.
"""
net_id = network['id']
segment_id = self._get_network_nsx_segment_id(context, net_id)
seg_subnets = self._get_segment_subnets(context, net_id, net_az=az)
filters = {'network_id': [net_id]}
ports = self.get_ports(context, filters=filters)
dhcp_server_config = self._get_segment_dhcp_server_config(
segment_id, az)
self.nsxpolicy.segment.update(
segment_id=segment_id,
dhcp_server_config_id=dhcp_server_config,
subnets=seg_subnets)
# Update DHCP bindings for all the ports.
for port in ports:
self._add_or_overwrite_port_policy_dhcp_binding(
context, port, segment_id, subnet)
def _validate_net_type_with_dhcp(self, context, network):
ddi_support, ddi_type = self._is_ddi_supported_on_net_with_type(
context, network['id'], network=network)
if not ddi_support:
msg = _("Native DHCP is not supported for %(type)s "
"network %(id)s") % {'id': network['id'],
'type': ddi_type}
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
def _validate_segment_subnets_num(self, context, net_id, subnet_data):
"""Validate no multiple segment subnets on the NSX
The NSX cannot support more than 1 segment subnet of the same ip
version. This include dhcp subnets and overlay router interfaces
"""
if ('enable_dhcp' not in subnet_data or
not subnet_data.get('enable_dhcp')):
# NO DHCP so no new segment subnet
return
ip_ver = subnet_data.get('ip_version', 4)
overlay_net = self._is_overlay_network(context, net_id)
if not overlay_net:
# Since the plugin allows only 1 DHCP subnet, if this is not an
# overlay network, no problem.
return
interface_ports = self._get_network_interface_ports(
context, net_id)
for if_port in interface_ports:
if if_port['fixed_ips']:
interface_ip = if_port['fixed_ips'][0].get('ip_address', '')
interface_sub = if_port['fixed_ips'][0]['subnet_id']
if subnet_data.get('id') != interface_sub:
# Cannot add a subnet with no GW on a routed segment
if not subnet_data.get('gateway_ip'):
msg = (_("Can not create a DHCP subnet without a "
"gateway on routed network %(net)s") %
{'net': net_id})
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
# skip subnets of the wrong version
if ip_ver == 4 and ':' in interface_ip:
continue
if ip_ver == 6 and ':' not in interface_ip:
continue
msg = (_("Can not create a DHCP subnet on network %(net)s "
"as another IPv%(ver)s subnet is attached to a "
"router") % {'net': net_id, 'ver': ip_ver})
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
def _init_ipv6_gateway(self, subnet):
# Override neutron decision to verify that also for ipv6 the first
# ip in the cidr is not used, as the NSX does not support xxxx::0 as a
# segment subnet gateway in versions supporting policy DHCP
if (self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_NSX_POLICY_DHCP) and
subnet.get('gateway_ip') is const.ATTR_NOT_SPECIFIED and
subnet.get('ip_version') == const.IP_VERSION_6 and
subnet.get('cidr') and subnet['cidr'] != const.ATTR_NOT_SPECIFIED):
net = netaddr.IPNetwork(subnet['cidr'])
subnet['gateway_ip'] = str(net.network + 1)
def _validate_subnet_host_routes(self, subnet, orig_subnet=None):
self._validate_number_of_subnet_static_routes(subnet)
if orig_subnet:
self._validate_host_routes_input(
subnet,
orig_enable_dhcp=orig_subnet['enable_dhcp'],
orig_host_routes=orig_subnet['host_routes'])
else:
self._validate_host_routes_input(subnet)
# IPv6 subnets cannot support host routes
if (subnet['subnet'].get('ip_version') == 6 or
(orig_subnet and orig_subnet.get('ip_version') == 6)):
if (validators.is_attr_set(subnet['subnet'].get('host_routes')) and
subnet['subnet']['host_routes']):
err_msg = _("Host routes can only be supported with IPv4 "
"subnets")
raise n_exc.InvalidInput(error_message=err_msg)
def _validate_subnet_gw_ip(self, context, subnet, orig_subnet=None):
# Make sure the gw ip is legal and belongs to the subnet
raw_gw_ip = subnet.get('gateway_ip', const.ATTR_NOT_SPECIFIED)
if orig_subnet and raw_gw_ip == const.ATTR_NOT_SPECIFIED:
# No change in GW ip
raw_gw_ip = orig_subnet.get('gateway_ip')
raw_cidr = subnet.get('cidr', const.ATTR_NOT_SPECIFIED)
if (orig_subnet and
(not raw_cidr or raw_cidr == const.ATTR_NOT_SPECIFIED)):
raw_cidr = orig_subnet.get('cidr')
if (not raw_gw_ip or raw_gw_ip == const.ATTR_NOT_SPECIFIED or
not raw_cidr or raw_cidr == const.ATTR_NOT_SPECIFIED):
if orig_subnet and raw_gw_ip is None:
# in update case, if the subnet is attached to a router the gw
# cannot be removes
interface_ports = self._get_network_interface_ports(
context, orig_subnet['network_id'])
for if_port in interface_ports:
if if_port['fixed_ips']:
interface_sub = if_port['fixed_ips'][0]['subnet_id']
if orig_subnet['id'] == interface_sub:
msg = _('Subnet for router interface must have a '
'gateway IP')
LOG.error(msg)
raise n_exc.BadRequest(resource='router', msg=msg)
# Nothing else to check here
return
gw_ip = netaddr.IPAddress(raw_gw_ip)
cidr = netaddr.IPNetwork(raw_cidr)
if gw_ip.version != cidr.version:
err_msg = (_("Subnet gateway ip version %(ver)s does not match "
"subnet cidr %(cidr)s") %
{'ver': gw_ip.version, 'cidr': raw_cidr})
raise n_exc.InvalidInput(error_message=err_msg)
if gw_ip not in cidr:
err_msg = (_("Subnet gateway ip %(ip)s does not belong to subnet "
"cidr %(cidr)s") %
{'ip': raw_gw_ip, 'cidr': raw_cidr})
raise n_exc.InvalidInput(error_message=err_msg)
if gw_ip.version == const.IP_VERSION_6:
# NSX does not support xxxx::0 as a segment subnet gateway
illegal_gw = str(netaddr.IPNetwork(raw_cidr).network)
if illegal_gw == str(gw_ip):
err_msg = (_("IPv6 Subnet gateway ip %s is not "
"supported") % raw_gw_ip)
raise n_exc.InvalidInput(error_message=err_msg)
def _has_dhcp_enabled_subnet(self, context, network, ip_version=4):
for subnet in network.subnets:
if subnet.enable_dhcp and subnet.ip_version == ip_version:
if ip_version == 4:
return True
elif subnet.ipv6_address_mode != const.IPV6_SLAAC:
return True
return False
@nsx_plugin_common.api_replay_mode_wrapper
def create_subnet(self, context, subnet):
self._init_ipv6_gateway(subnet['subnet'])
if not self.use_policy_dhcp:
# Subnet with MP DHCP
return self._create_subnet_with_mp_dhcp(context, subnet)
self._validate_subnet_host_routes(subnet)
net_id = subnet['subnet']['network_id']
network = self._get_network(context, net_id)
self._validate_single_ipv6_subnet(context, network, subnet['subnet'])
self._validate_subnet_gw_ip(context, subnet['subnet'])
net_az = self.get_network_az_by_net_id(context, net_id)
# Allow manipulation of only 1 subnet of the same network at once
lock = 'nsxp_network_' + net_id
with locking.LockManager.get_lock(lock):
# DHCP validations (before creating the neutron subnet)
with_dhcp = False
if self._subnet_with_native_dhcp(subnet['subnet']):
with_dhcp = True
self._validate_external_subnet(context, net_id)
self._validate_net_dhcp_edge_cluster(context, network, net_az)
self._validate_net_type_with_dhcp(context, network)
ip_version = subnet['subnet'].get('ip_version', 4)
if self._has_dhcp_enabled_subnet(context, network, ip_version):
msg = (_("Can not create more than one DHCP-enabled "
"subnet for IPv%(ver)s in network %(net)s") %
{'ver': ip_version, 'net': net_id})
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
self._validate_segment_subnets_num(
context, net_id, subnet['subnet'])
# Create the neutron subnet.
# Any failure from here and on will require rollback.
created_subnet = super(NsxPolicyPlugin, self).create_subnet(
context, subnet)
try:
# This can be called only after the super create
# since we need the subnet pool to be translated
# to allocation pools
self._validate_address_space(context, created_subnet)
except n_exc.InvalidInput:
# revert the subnet creation
with excutils.save_and_reraise_exception():
super(NsxPolicyPlugin, self).delete_subnet(
context, created_subnet['id'])
self._extension_manager.process_create_subnet(context,
subnet['subnet'], created_subnet)
if with_dhcp:
try:
# Enable the network DHCP on the NSX
self._enable_subnet_dhcp(
context, network, created_subnet, net_az)
except (nsx_lib_exc.ManagerError,
nsx_exc.NsxPluginException) as e:
# Revert the subnet creation: ry to delete the DHCP port
# and the neutron subnet
self._delete_subnet_dhcp_port(
context, net_id, subnet_id=created_subnet['id'])
super(NsxPolicyPlugin, self).delete_subnet(
context, created_subnet['id'])
self.raise_nsxlib_error(e)
return created_subnet
def delete_subnet(self, context, subnet_id):
if not self.use_policy_dhcp:
# Subnet with MP DHCP
return self.delete_subnet_with_mp_dhcp(context, subnet_id)
if self._has_native_dhcp_metadata():
# Ensure that subnet is not deleted if attached to router.
self._subnet_check_ip_allocations_internal_router_ports(
context, subnet_id)
# Ensure that subnet is not deleted if it has ports, before
# changing backend configuration
subnet = self.get_subnet(context, subnet_id)
is_auto_addr_subnet = ipv6_utils.is_auto_address_subnet(subnet)
if not is_auto_addr_subnet:
# for SLAAC auto allocation subnets ports can still exist
self._ensure_no_user_ports_on_subnet(context, subnet_id)
if self._subnet_with_native_dhcp(subnet):
lock = 'nsxp_network_' + subnet['network_id']
with locking.LockManager.get_lock(lock):
# Remove this subnet DHCP config
network = self._get_network(context, subnet['network_id'])
try:
self._disable_network_dhcp(context, network,
subnet_id=subnet_id)
except Exception as e:
LOG.error("Failed to disable DHCP for "
"network %(id)s. Exception: %(e)s",
{'id': network['id'], 'e': e})
# Continue for the neutron subnet deletion
# Delete neutron subnet
super(NsxPolicyPlugin, self).delete_subnet(context, subnet_id)
def update_subnet(self, context, subnet_id, subnet):
if not self.use_policy_dhcp:
# Subnet with MP DHCP
return self.update_subnet_with_mp_dhcp(context, subnet_id, subnet)
subnet_data = subnet['subnet']
updated_subnet = None
orig_subnet = self.get_subnet(context, subnet_id)
self._validate_subnet_host_routes(subnet, orig_subnet=orig_subnet)
self._validate_subnet_gw_ip(context, subnet_data,
orig_subnet=orig_subnet)
net_id = orig_subnet['network_id']
network = self._get_network(context, net_id)
net_az = self.get_network_az_by_net_id(context, net_id)
enable_dhcp = self._subnet_with_native_dhcp(
subnet_data, orig_subnet=orig_subnet)
orig_enable_dhcp = self._subnet_with_native_dhcp(orig_subnet)
if enable_dhcp != orig_enable_dhcp:
# Update subnet with DHCP status change
self._validate_external_subnet(context, net_id)
lock = 'nsxp_network_' + net_id
with locking.LockManager.get_lock(lock):
if enable_dhcp:
self._validate_net_type_with_dhcp(context, network)
ip_version = orig_subnet.get('ip_version', 4)
if self._has_dhcp_enabled_subnet(context, network,
ip_version):
msg = (_("Can not create more than one DHCP-enabled "
"subnet for IPv%(ver)s in network %(net)s") %
{'net': net_id, 'ver': ip_version})
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
updated_data = orig_subnet
updated_data.update(subnet_data)
self._validate_segment_subnets_num(
context, net_id, updated_data)
updated_subnet = super(NsxPolicyPlugin, self).update_subnet(
context, subnet_id, subnet)
self._extension_manager.process_update_subnet(
context, subnet_data, updated_subnet)
try:
if enable_dhcp:
self._enable_subnet_dhcp(context, network,
updated_subnet, net_az)
else:
self._disable_network_dhcp(context, network,
subnet_id=subnet_id)
except (nsx_lib_exc.ManagerError,
nsx_exc.NsxPluginException) as e:
# revert the subnet update
super(NsxPolicyPlugin, self).update_subnet(
context, subnet_id, {'subnet': orig_subnet})
self.raise_nsxlib_error(e)
else:
# No dhcp changes - just call super update
updated_subnet = super(NsxPolicyPlugin, self).update_subnet(
context, subnet_id, subnet)
self._extension_manager.process_update_subnet(
context, subnet_data, updated_subnet)
# Check if needs to update DHCP related NSX resources
if (enable_dhcp and
(not orig_enable_dhcp or
'dns_nameservers' in subnet_data or
'gateway_ip' in subnet_data or
'host_routes' in subnet_data)):
try:
self._update_nsx_net_dhcp(
context, network, net_az, updated_subnet)
except (nsx_lib_exc.ManagerError,
nsx_exc.NsxPluginException) as e:
self.raise_nsxlib_error(e)
return updated_subnet
def _build_port_address_bindings(self, context, port_data):
psec_on, has_ip = self._determine_port_security_and_has_ip(context,
port_data)
if not psec_on:
# returning an empty list will delete all existing bindings
return []
address_bindings = []
for fixed_ip in port_data['fixed_ips']:
ip_addr = fixed_ip['ip_address']
mac_addr = self._format_mac_address(port_data['mac_address'])
binding = self.nsxpolicy.segment_port.build_address_binding(
ip_addr, mac_addr)
address_bindings.append(binding)
# add address binding for link local ipv6 address, otherwise
# neighbor discovery will be blocked by spoofguard.
# for now only one ipv6 address is allowed
if netaddr.IPAddress(ip_addr).version == 6:
lladdr = netaddr.EUI(mac_addr).ipv6_link_local()
binding = self.nsxpolicy.segment_port.build_address_binding(
lladdr, mac_addr)
address_bindings.append(binding)
for pair in port_data.get(addr_apidef.ADDRESS_PAIRS):
binding = self.nsxpolicy.segment_port.build_address_binding(
pair['ip_address'],
self._format_mac_address(pair['mac_address']))
address_bindings.append(binding)
return address_bindings
def _get_network_nsx_id(self, context, network_id):
"""Return the id of this logical switch in the nsx manager
This api waits for the segment to really be realized, and return the ID
of the NSX logical switch.
If it was not realized or timed out retrying, it will return None
The nova api will use this to attach to the instance.
"""
if network_id in NET_NEUTRON_2_NSX_ID_CACHE:
return NET_NEUTRON_2_NSX_ID_CACHE[network_id]
if not self._network_is_external(context, network_id):
segment_id = self._get_network_nsx_segment_id(context, network_id)
try:
nsx_id = self.nsxpolicy.segment.get_realized_logical_switch_id(
segment_id)
# Add result to caches
NET_NEUTRON_2_NSX_ID_CACHE[network_id] = nsx_id
NET_NSX_2_NEUTRON_ID_CACHE[nsx_id] = network_id
return nsx_id
except nsx_lib_exc.ManagerError:
LOG.error("Network %s was not realized", network_id)
# Do not cache this result
else:
# Add empty result to cache
NET_NEUTRON_2_NSX_ID_CACHE[network_id] = None
def _get_network_nsx_segment_id(self, context, network_id):
"""Return the NSX segment ID matching the neutron network id
Usually the NSX ID is the same as the neutron ID. The exception is
when this is a provider NSX_NETWORK, which means the network already
existed on the NSX backend, and it is being consumed by the plugin.
"""
bindings = nsx_db.get_network_bindings(context.session, network_id)
if (bindings and
bindings[0].binding_type == utils.NsxV3NetworkTypes.NSX_NETWORK):
# return the ID of the NSX network
return bindings[0].phy_uuid
return network_id
def _build_port_tags(self, port_data):
sec_groups = []
sec_groups.extend(port_data.get(ext_sg.SECURITYGROUPS, []))
sec_groups.extend(port_data.get(provider_sg.PROVIDER_SECURITYGROUPS,
[]))
tags = []
for sg in sec_groups:
tags = nsxlib_utils.add_v3_tag(tags,
NSX_P_SECURITY_GROUP_TAG,
sg)
return tags
def _do_port_backend_calls(self, name, segment_id,
spoofguard_profile, seg_sec_profile,
mac_discovery_profile, qos_policy_id, **kwargs):
self.nsxpolicy.segment_port.create_or_overwrite(
name, segment_id, **kwargs)
# add the security profiles to the port
self.nsxpolicy.segment_port_security_profiles.create_or_overwrite(
name, segment_id, port_id=kwargs['port_id'],
spoofguard_profile_id=spoofguard_profile,
segment_security_profile_id=seg_sec_profile)
# add the mac discovery profile to the port
self.nsxpolicy.segment_port_discovery_profiles.create_or_overwrite(
name, segment_id, kwargs['port_id'],
mac_discovery_profile_id=mac_discovery_profile)
# Add QoS segment profile (only if QoS is enabled)
if directory.get_plugin(plugin_const.QOS):
self.nsxpolicy.segment_port_qos_profiles.create_or_overwrite(
name, segment_id, kwargs['port_id'],
qos_profile_id=qos_policy_id)
def _create_or_update_port_on_backend(self, context, port_data, is_psec_on,
qos_policy_id, original_port=None):
is_create = original_port is None
is_update = not is_create
name = self._build_port_name(context, port_data)
address_bindings = self._build_port_address_bindings(
context, port_data)
device_owner = port_data.get('device_owner')
vif_id = None
if (cfg.CONF.api_replay_mode and device_owner and
device_owner.startswith('compute:') and port_data.get('vif_id')):
# During api-replay, migrated vm port should have this vif-id
vif_id = port_data['vif_id']
elif (is_create and (not device_owner or
device_owner != l3_db.DEVICE_OWNER_ROUTER_INTF)):
# Set vif_id even if no device owner so that auto-generated
# MP ports won't be created for VMs before neutron sets the vif-id
vif_id = port_data['id']
tags = self._build_port_tags(port_data)
if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
tag_resource_type = 'os-neutron-rport-id'
else:
tag_resource_type = NSX_P_PORT_RESOURCE_TYPE
tags.extend(self.nsxpolicy.build_v3_tags_payload(
port_data, resource_type=tag_resource_type,
project_name=context.tenant_name))
if self._is_excluded_port(device_owner, is_psec_on):
tags.append({'scope': security.PORT_SG_SCOPE,
'tag': NSX_P_EXCLUDE_LIST_TAG})
if self.support_external_port_tagging:
external_tags = self.get_external_tags_for_port(
context, port_data['id'])
if external_tags:
total_len = len(external_tags) + len(tags)
if total_len > nsxlib_utils.MAX_TAGS:
LOG.warning("Cannot add external tags to port %s: "
"too many tags", port_data['id'])
else:
tags.extend(external_tags)
segment_id = self._get_network_nsx_segment_id(
context, port_data['network_id'])
# Calculate the port security profiles
if is_psec_on:
spoofguard_profile = SPOOFGUARD_PROFILE_ID
seg_sec_profile = SEG_SECURITY_PROFILE_ID
else:
spoofguard_profile = NO_SPOOFGUARD_PROFILE_ID
seg_sec_profile = NO_SEG_SECURITY_PROFILE_ID
mac_disc_profile_must = False
if is_psec_on:
address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS)
if validators.is_attr_set(address_pairs) and address_pairs:
mac_disc_profile_must = True
mac_learning_enabled = (
validators.is_attr_set(port_data.get(mac_ext.MAC_LEARNING)) and
port_data.get(mac_ext.MAC_LEARNING) is True)
if mac_disc_profile_must or mac_learning_enabled:
mac_discovery_profile = MAC_DISCOVERY_PROFILE_ID
else:
mac_discovery_profile = NO_MAC_DISCOVERY_PROFILE_ID
# Prepare the args for the segment port creation
kwargs = {'port_id': port_data['id'],
'description': port_data.get('description', ''),
'address_bindings': address_bindings,
'tags': tags}
if vif_id:
kwargs['vif_id'] = vif_id
if (self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_NSX_POLICY_ADMIN_STATE) and
'admin_state_up' in port_data):
kwargs['admin_state'] = port_data['admin_state_up']
if not self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_PARTIAL_UPDATES):
# If partial updates are not supported, using transactions will
# reset the backend segment name
self._do_port_backend_calls(
name, segment_id, spoofguard_profile, seg_sec_profile,
mac_discovery_profile, qos_policy_id, **kwargs)
else:
# Create/ update the backend port in a single transaction
with policy_trans.NsxPolicyTransaction():
self._do_port_backend_calls(
name, segment_id, spoofguard_profile, seg_sec_profile,
mac_discovery_profile, qos_policy_id, **kwargs)
# Update port admin status using passthrough api, only if it changed
# or new port with disabled admin state
if (not self.nsxpolicy.feature_supported(
nsxlib_consts.FEATURE_NSX_POLICY_ADMIN_STATE) and
cfg.CONF.nsx_p.allow_passthrough and
'admin_state_up' in port_data):
new_state = port_data['admin_state_up']
if ((is_create and new_state is False) or
(is_update and
original_port.get('admin_state_up') != new_state)):
# This api uses the passthrough api
self.nsxpolicy.segment_port.set_admin_state(
segment_id, port_data['id'], new_state)
def base_create_port(self, context, port):
neutron_db = super(NsxPolicyPlugin, self).create_port(context, port)
self._extension_manager.process_create_port(
context, port['port'], neutron_db)
return neutron_db
def _is_backend_port(self, context, port_data, delete=False):
is_external_net = self._network_is_external(
context, port_data['network_id'])
device_owner = port_data.get('device_owner')
is_router_interface = (device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF)
is_dhcp_port = (device_owner == const.DEVICE_OWNER_DHCP and
self.use_policy_dhcp)
is_octavia_port = (device_owner == oct_const.DEVICE_OWNER_OCTAVIA)
if is_external_net or is_router_interface or is_dhcp_port:
# DHCP is handled on NSX level
# Router is connected automatically in policy
return False
if not delete and is_octavia_port:
# Octavia vip port should not be created on the NSX.
# Since octavia backend ports from older deployments may exist,
# need to try and delete those.
return False
return True
def _filter_ipv6_dhcp_fixed_ips(self, context, fixed_ips):
ips = []
for fixed_ip in fixed_ips:
if netaddr.IPNetwork(fixed_ip['ip_address']).version != 6:
continue
with db_api.CONTEXT_READER.using(context):
subnet = self.get_subnet(context, fixed_ip['subnet_id'])
if (subnet['enable_dhcp'] and
subnet.get('ipv6_address_mode') != 'slaac'):
ips.append(fixed_ip)
return ips
def _add_or_overwrite_port_policy_dhcp_binding(
self, context, port, segment_id, dhcp_subnet=None):
if not utils.is_port_dhcp_configurable(port):
return
net_id = port['network_id']
for fixed_ip in self._filter_ipv4_dhcp_fixed_ips(
context, port['fixed_ips']):
# There will be only one ipv4 ip here
binding_id = port['id'] + '-ipv4'
name = 'IPv4 binding for port %s' % port['id']
ip = fixed_ip['ip_address']
hostname = 'host-%s' % ip.replace('.', '-')
if dhcp_subnet:
if fixed_ip['subnet_id'] != dhcp_subnet['id']:
continue
subnet = dhcp_subnet
else:
subnet = self.get_subnet(context, fixed_ip['subnet_id'])
gateway_ip = subnet.get('gateway_ip')
options = self._get_dhcp_options(
context, ip, port.get(ext_edo.EXTRADHCPOPTS),
net_id, subnet)
self.nsxpolicy.segment_dhcp_static_bindings.create_or_overwrite_v4(
name, segment_id, binding_id=binding_id,
gateway_address=gateway_ip,
host_name=hostname,
ip_address=ip,
lease_time=cfg.CONF.nsx_p.dhcp_lease_time,
mac_address=port['mac_address'],
options=options)
for fixed_ip in self._filter_ipv6_dhcp_fixed_ips(
context, port['fixed_ips']):
# There will be only one ipv6 ip here
binding_id = port['id'] + '-ipv6'
name = 'IPv6 binding for port %s' % port['id']
ip = fixed_ip['ip_address']
if dhcp_subnet:
if fixed_ip['subnet_id'] != dhcp_subnet['id']:
continue
subnet = dhcp_subnet
else:
subnet = self.get_subnet(context, fixed_ip['subnet_id'])
self.nsxpolicy.segment_dhcp_static_bindings.create_or_overwrite_v6(
name, segment_id, binding_id=binding_id,
ip_addresses=[ip],
lease_time=cfg.CONF.nsx_p.dhcp_lease_time,
mac_address=port['mac_address'])
def _add_port_policy_dhcp_binding(self, context, port):
net_id = port['network_id']
if not self._is_dhcp_network(context, net_id):
return
segment_id = self._get_network_nsx_segment_id(context, net_id)
self._add_or_overwrite_port_policy_dhcp_binding(
context, port, segment_id)
def _delete_port_policy_dhcp_binding(self, context, port):
# Do not check device_owner here because Nova may have already
# deleted that before Neutron's port deletion.
net_id = port['network_id']
if not self._is_dhcp_network(context, net_id):
return
segment_id = self._get_network_nsx_segment_id(context, net_id)
v4_dhcp = v6_dhcp = False
for fixed_ip in port['fixed_ips']:
ip_addr = fixed_ip['ip_address']
if netaddr.IPAddress(ip_addr).version == 6:
v6_dhcp = True
else:
v4_dhcp = True
if v4_dhcp:
try:
bindingv4_id = port['id'] + '-ipv4'
self.nsxpolicy.segment_dhcp_static_bindings.delete(
segment_id, bindingv4_id)
except nsx_lib_exc.ResourceNotFound:
pass
if v6_dhcp:
try:
bindingv6_id = port['id'] + '-ipv6'
self.nsxpolicy.segment_dhcp_static_bindings.delete(
segment_id, bindingv6_id)
except nsx_lib_exc.ResourceNotFound:
pass
def _update_port_policy_dhcp_binding(self, context, old_port, new_port):
# First check if any address in fixed_ips changed.
# Then update DHCP server setting or DHCP static binding
# depending on the port type.
# Note that Neutron allows a port with multiple IPs in the
# same subnet. But backend DHCP server may not support that.
if (utils.is_port_dhcp_configurable(old_port) !=
utils.is_port_dhcp_configurable(new_port)):
# Note that the device_owner could be changed,
# but still needs DHCP binding.
if utils.is_port_dhcp_configurable(old_port):
self._delete_port_policy_dhcp_binding(context, old_port)
else:
self._add_port_policy_dhcp_binding(context, new_port)
return
# Collect IPv4 DHCP addresses from original and updated fixed_ips
# in the form of [(subnet_id, ip_address)].
old_fixed_v4 = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
for fixed_ip in self._filter_ipv4_dhcp_fixed_ips(
context, old_port['fixed_ips'])])
new_fixed_v4 = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
for fixed_ip in self._filter_ipv4_dhcp_fixed_ips(
context, new_port['fixed_ips'])])
old_fixed_v6 = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
for fixed_ip in self._filter_ipv6_dhcp_fixed_ips(
context, old_port['fixed_ips'])])
new_fixed_v6 = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
for fixed_ip in self._filter_ipv6_dhcp_fixed_ips(
context, new_port['fixed_ips'])])
# Find out the subnet/IP differences before and after the update.
v4_to_add = list(new_fixed_v4 - old_fixed_v4)
v4_to_delete = list(old_fixed_v4 - new_fixed_v4)
v6_to_add = list(new_fixed_v6 - old_fixed_v6)
v6_to_delete = list(old_fixed_v6 - new_fixed_v6)
ip_change = (v4_to_add or v4_to_delete or v6_to_add or v6_to_delete)
if (old_port["device_owner"] == const.DEVICE_OWNER_DHCP and
ip_change):
# Update backend DHCP server address if the IP address of a DHCP
# port is changed.
if len(new_fixed_v4) > 1 or len(new_fixed_v6) > 1:
msg = _("Can only configure one IP address on a DHCP server")
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
net_id = old_port['network_id']
network = self.get_network(context, net_id)
net_az = self.get_network_az_by_net_id(context, net_id)
self._update_nsx_net_dhcp(context, network, net_az)
elif utils.is_port_dhcp_configurable(new_port):
dhcp_opts_changed = (old_port[ext_edo.EXTRADHCPOPTS] !=
new_port[ext_edo.EXTRADHCPOPTS])
if (ip_change or dhcp_opts_changed or
old_port['mac_address'] != new_port['mac_address']):
if new_fixed_v4 or new_fixed_v6:
# Recreate the bindings of this port
self._add_port_policy_dhcp_binding(context, new_port)
else:
self._delete_port_policy_dhcp_binding(context, old_port)
def _assert_on_ipv6_port_with_dhcpopts(self, context, port_data,
orig_port=None):
"""IPv6 port only port cannot support EXTRADHCPOPTS"""
# Get the updated EXTRADHCPOPTS
extradhcpopts = None
if ext_edo.EXTRADHCPOPTS in port_data:
extradhcpopts = port_data[ext_edo.EXTRADHCPOPTS]
elif orig_port:
extradhcpopts = orig_port.get(ext_edo.EXTRADHCPOPTS)
if not extradhcpopts:
return
# Get the updated list of fixed ips
fixed_ips = []
if (port_data.get('fixed_ips') and
validators.is_attr_set(port_data['fixed_ips'])):
fixed_ips = port_data['fixed_ips']
elif (orig_port and orig_port.get('fixed_ips') and
validators.is_attr_set(orig_port['fixed_ips'])):
fixed_ips = orig_port['fixed_ips']
# Check if any of the ips belongs to an ipv6 subnet with DHCP
# And no ipv4 subnets
for fixed_ip in fixed_ips:
if netaddr.IPNetwork(fixed_ip['ip_address']).version == 4:
# If there are ipv4 addresses - it is allowed
return
with db_api.CONTEXT_READER.using(context):
subnet = self.get_subnet(context, fixed_ip['subnet_id'])
if (subnet['enable_dhcp'] and
subnet['ipv6_address_mode'] != const.IPV6_SLAAC):
err_msg = (_("%s are not supported for IPv6 ports with "
"DHCP v6") % ext_edo.EXTRADHCPOPTS)
LOG.error(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)
def raise_nsxlib_error(self, e):
"""Raise the proper error when segment port PATCH fails"""
if isinstance(e, nsx_lib_exc.ManagerError):
if e.status_code == 400:
# Try to get the important part of the message logged
try:
msg = e.msg.split('relatedErrors: ')[1][:-1]
except Exception:
msg = e.msg
raise n_exc.InvalidInput(error_message=msg)
raise e
def create_port(self, context, port, l2gw_port_check=False):
port_data = port['port']
# validate the new port parameters
self._validate_create_port(context, port_data)
self._assert_on_resource_admin_state_down(port_data)
# Validate the vnic type (the same types as for the NSX-T plugin)
direct_vnic_type = self._validate_port_vnic_type(
context, port_data, port_data['network_id'],
projectpluginmap.NsxPlugins.NSX_T)
is_external_net = self._network_is_external(
context, port_data['network_id'])
if is_external_net:
self._assert_on_external_net_with_compute(port_data)
# Do this outside of the context writer scope so it can overcome
# failures
if port_data.get('tenant_id'):
self._ensure_default_security_group(context,
port_data['tenant_id'])
with db_api.CONTEXT_WRITER.using(context):
neutron_db = self.base_create_port(context, port)
port["port"].update(neutron_db)
try:
# Validate ipv6 only after fixed_ips are allocated
self._assert_on_ipv6_port_with_dhcpopts(context, port["port"])
except Exception:
with excutils.save_and_reraise_exception():
# rollback
super(NsxPolicyPlugin, self).delete_port(
context, neutron_db['id'])
self.fix_direct_vnic_port_sec(direct_vnic_type, port_data)
(is_psec_on, has_ip, sgids, psgids) = (
self._create_port_preprocess_security(context, port,
port_data, neutron_db,
False))
self._process_portbindings_create_and_update(
context, port['port'], port_data,
vif_type=self._vif_type_by_vnic_type(direct_vnic_type))
self._process_port_create_extra_dhcp_opts(
context, port_data,
port_data.get(ext_edo.EXTRADHCPOPTS))
self._process_port_create_security_group(context, port_data, sgids)
self._process_port_create_provider_security_group(
context, port_data, psgids)
# Handle port mac learning
if validators.is_attr_set(port_data.get(mac_ext.MAC_LEARNING)):
# Make sure mac_learning and port sec are not both enabled
if port_data.get(mac_ext.MAC_LEARNING) and is_psec_on:
msg = _('Mac learning requires that port security be '
'disabled')
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
# save the mac learning value in the DB
self._create_mac_learning_state(context, port_data)
elif mac_ext.MAC_LEARNING in port_data:
# This is due to the fact that the default is
# ATTR_NOT_SPECIFIED
port_data.pop(mac_ext.MAC_LEARNING)
qos_policy_id = self._get_port_qos_policy_id(
context, None, port_data)
if self._is_backend_port(context, port_data):
# router interface port is created automatically by policy
try:
self._create_or_update_port_on_backend(
context, port_data, is_psec_on, qos_policy_id)
except Exception as e:
LOG.error('Failed to create port %(id)s on NSX '
'backend. Exception: %(e)s',
{'id': neutron_db['id'], 'e': e})
super(NsxPolicyPlugin, self).delete_port(
context, neutron_db['id'])
self.raise_nsxlib_error(e)
# Attach the QoS policy to the port in the neutron DB
if qos_policy_id:
qos_com_utils.update_port_policy_binding(context,
neutron_db['id'],
qos_policy_id)
# this extra lookup is necessary to get the
# latest db model for the extension functions
port_model = self._get_port(context, port_data['id'])
resource_extend.apply_funcs('ports', port_data, port_model)
self._extend_nsx_port_dict_binding(context, port_data)
self._remove_provider_security_groups_from_list(port_data)
# Add Mac/IP binding to native DHCP server and neutron DB.
try:
if self.use_policy_dhcp:
self._add_port_policy_dhcp_binding(context, port_data)
elif cfg.CONF.nsx_p.allow_passthrough:
self._add_port_mp_dhcp_binding(context, port_data)
except nsx_lib_exc.ManagerError:
# Rollback create port
self.delete_port(context, port_data['id'],
force_delete_dhcp=True)
msg = _('Unable to create port. Please contact admin')
LOG.exception(msg)
raise nsx_exc.NsxPluginException(err_msg=msg)
kwargs = {'context': context, 'port': neutron_db}
registry.notify(resources.PORT, events.AFTER_CREATE, self, **kwargs)
return port_data
def _delete_port_on_backend(self, context, net_id, port_id):
try:
segment_id = self._get_network_nsx_segment_id(context, net_id)
self.nsxpolicy.segment_port_security_profiles.delete(
segment_id, port_id)
self.nsxpolicy.segment_port_discovery_profiles.delete(
segment_id, port_id)
if directory.get_plugin(plugin_const.QOS):
self.nsxpolicy.segment_port_qos_profiles.delete(
segment_id, port_id)
self.nsxpolicy.segment_port.delete(segment_id, port_id)
except nsx_lib_exc.ResourceNotFound:
# If the resource was not found on the backend do not worry about
# it. The conditions has already been logged, so there is no need
# to do further logging
pass
except nsx_lib_exc.ManagerError as e:
# If there is a failure in deleting the resource.
# In this case the neutron port was not deleted yet.
msg = (_("Backend port deletion for neutron port %(id)s "
"failed: %(e)s") % {'id': port_id, 'e': e})
raise nsx_exc.NsxPluginException(err_msg=msg)
def delete_port(self, context, port_id,
l3_port_check=True, l2gw_port_check=True,
force_delete_dhcp=False,
force_delete_vpn=False):
# first update neutron (this will perform all types of validations)
port_data = self.get_port(context, port_id)
net_id = port_data['network_id']
# 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, port_id)
# Prevent DHCP port deletion if native support is enabled
if (cfg.CONF.nsx_p.allow_passthrough and
not force_delete_dhcp and
port_data['device_owner'] in [const.DEVICE_OWNER_DHCP]):
msg = (_('Can not delete DHCP port %s') % port_id)
raise n_exc.BadRequest(resource='port', msg=msg)
if not force_delete_vpn:
self._assert_on_vpn_port_change(port_data)
self.disassociate_floatingips(context, port_id)
# Remove Mac/IP binding from native DHCP server and neutron DB.
if self.use_policy_dhcp:
self._delete_port_policy_dhcp_binding(context, port_data)
elif cfg.CONF.nsx_p.allow_passthrough:
self._delete_port_mp_dhcp_binding(context, port_data)
super(NsxPolicyPlugin, self).delete_port(context, port_id)
# Delete the backend port last to prevent recreation by another process
if self._is_backend_port(context, port_data, delete=True):
try:
self._delete_port_on_backend(context, net_id, port_id)
except nsx_lib_exc.ResourceNotFound:
# If the resource was not found on the backend do not worry
# about it. The conditions has already been logged, so there
# is no need to do further logging
pass
except nsx_lib_exc.ManagerError as e:
# If there is a failure in deleting the resource, fail the
# neutron operation even though the neutron object was already
# deleted. This way the user will be aware of zombie resources
# that may fail future actions.
msg = (_("Backend segment port deletion for neutron port "
"%(id)s failed. The object was however removed from "
"the Neutron database: %(e)s") %
{'id': port_id, 'e': e})
raise nsx_exc.NsxPluginException(err_msg=msg)
def _update_port_on_backend(self, context, lport_id,
original_port, updated_port,
is_psec_on, qos_policy_id):
# For now port create and update are the same
# Update might evolve with more features
return self._create_or_update_port_on_backend(
context, updated_port, is_psec_on,
qos_policy_id, original_port=original_port)
def update_port(self, context, port_id, port):
with db_api.CONTEXT_WRITER.using(context):
# get the original port, and keep it honest as it is later used
# for notifications
original_port = super(NsxPolicyPlugin, self).get_port(
context, port_id)
self._remove_provider_security_groups_from_list(original_port)
port_data = port['port']
self._validate_update_port(context, port_id, original_port,
port_data)
self._assert_on_resource_admin_state_down(port_data)
validate_port_sec = self._should_validate_port_sec_on_update_port(
port_data)
is_external_net = self._network_is_external(
context, original_port['network_id'])
if is_external_net:
self._assert_on_external_net_with_compute(port_data)
device_owner = (port_data['device_owner']
if 'device_owner' in port_data
else original_port.get('device_owner'))
self._validate_max_ips_per_port(context,
port_data.get('fixed_ips', []),
device_owner)
direct_vnic_type = self._validate_port_vnic_type(
context, port_data, original_port['network_id'])
self._assert_on_ipv6_port_with_dhcpopts(
context, port_data, orig_port=original_port)
updated_port = super(NsxPolicyPlugin, self).update_port(
context, port_id, port)
self._extension_manager.process_update_port(context, port_data,
updated_port)
# copy values over - except fixed_ips as
# they've already been processed
port_data.pop('fixed_ips', None)
updated_port.update(port_data)
updated_port = self._update_port_preprocess_security(
context, port, port_id, updated_port, False,
validate_port_sec=validate_port_sec,
direct_vnic_type=direct_vnic_type)
self._update_extra_dhcp_opts_on_port(context, port_id, port,
updated_port)
sec_grp_updated = self.update_security_group_on_port(
context, port_id, port, original_port, updated_port)
self._process_port_update_provider_security_group(
context, port, original_port, updated_port)
(port_security, has_ip) = self._determine_port_security_and_has_ip(
context, updated_port)
self._process_portbindings_create_and_update(
context, port_data, updated_port,
vif_type=self._vif_type_by_vnic_type(direct_vnic_type))
self._extend_nsx_port_dict_binding(context, updated_port)
mac_learning_state = updated_port.get(mac_ext.MAC_LEARNING)
if mac_learning_state is not None:
if port_security and mac_learning_state:
msg = _('Mac learning requires that port security be '
'disabled')
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
self._update_mac_learning_state(context, port_id,
mac_learning_state)
self._remove_provider_security_groups_from_list(updated_port)
# Update the QoS policy
qos_policy_id = self._get_port_qos_policy_id(
context, original_port, updated_port)
qos_com_utils.update_port_policy_binding(context, port_id,
qos_policy_id)
# update the port in the backend, only if it exists in the DB
# (i.e not external net) and is not router interface
if self._is_backend_port(context, updated_port):
try:
self._update_port_on_backend(context, port_id,
original_port, updated_port,
port_security, qos_policy_id)
except Exception as e:
LOG.error('Failed to update port %(id)s on NSX '
'backend. Exception: %(e)s',
{'id': port_id, 'e': e})
# Rollback the change
with db_api.CONTEXT_WRITER.using(context):
self._revert_neutron_port_update(
context, port_id, original_port, updated_port,
port_security, sec_grp_updated)
self.raise_nsxlib_error(e)
else:
# if this port changed ownership to router interface, it should
# be deleted from policy, since policy handles router connectivity
original_owner = original_port.get('device_owner')
new_owner = port_data.get('device_owner')
if (original_owner != new_owner and
new_owner == const.DEVICE_OWNER_ROUTER_INTF):
self._delete_port_on_backend(context,
original_port['network_id'],
port_id)
# Update DHCP bindings.
if self.use_policy_dhcp:
self._update_port_policy_dhcp_binding(
context, original_port, updated_port)
elif cfg.CONF.nsx_p.allow_passthrough:
self._update_port_mp_dhcp_binding(
context, original_port, updated_port)
# Make sure the port revision is updated
if 'revision_number' in updated_port:
port_model = self._get_port(context, port_id)
updated_port['revision_number'] = port_model.revision_number
# Notifications must be sent after the above transaction is complete
kwargs = {
'context': context,
'port': updated_port,
'mac_address_updated': False,
'original_port': original_port,
}
registry.notify(resources.PORT, events.AFTER_UPDATE, self, **kwargs)
return updated_port
def get_port(self, context, id, fields=None):
port = super(NsxPolicyPlugin, self).get_port(
context, id, fields=None)
self._extend_nsx_port_dict_binding(context, port)
self._extend_qos_port_dict_binding(context, port)
self._remove_provider_security_groups_from_list(port)
return db_utils.resource_fields(port, fields)
def get_ports(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
filters = filters or {}
self._update_filters_with_sec_group(context, filters)
with db_api.CONTEXT_READER.using(context):
ports = (
super(NsxPolicyPlugin, self).get_ports(
context, filters, fields, sorts,
limit, marker, page_reverse))
self._log_get_ports(ports, filters)
# Add port extensions
for port in ports[:]:
self._extend_nsx_port_dict_binding(context, port)
self._extend_qos_port_dict_binding(context, port)
self._remove_provider_security_groups_from_list(port)
return (ports if not fields else
[db_utils.resource_fields(port, fields) for port in ports])
def _add_subnet_snat_rule(self, context, router_id, subnet,
gw_address_scope, gw_ip):
if not self._need_router_snat_rules(context, router_id, subnet,
gw_address_scope):
return
firewall_match = self._get_nat_firewall_match()
self.nsxpolicy.tier1_nat_rule.create_or_overwrite(
'snat for subnet %s' % subnet['id'],
router_id,
nat_rule_id=self._get_snat_rule_id(subnet),
action=policy_constants.NAT_ACTION_SNAT,
sequence_number=NAT_RULE_PRIORITY_GW,
translated_network=gw_ip,
source_network=subnet['cidr'],
firewall_match=firewall_match)
def _get_snat_rule_id(self, subnet):
return 'S-' + subnet['id']
def _get_no_dnat_rule_id(self, subnet):
return 'ND-' + subnet['id']
def _add_subnet_no_dnat_rule(self, context, router_id, subnet):
if not self._need_router_no_dnat_rules(subnet):
return
# Add NO-DNAT rule to allow internal traffic between VMs, even if
# they have floating ips (Only for routers with snat enabled)
self.nsxpolicy.tier1_nat_rule.create_or_overwrite(
'no-dnat for subnet %s' % subnet['id'],
router_id,
nat_rule_id=self._get_no_dnat_rule_id(subnet),
action=policy_constants.NAT_ACTION_NO_DNAT,
sequence_number=NAT_RULE_PRIORITY_GW,
destination_network=subnet['cidr'],
firewall_match=policy_constants.NAT_FIREWALL_MATCH_BYPASS)
def _del_subnet_no_dnat_rule(self, router_id, subnet):
# Delete the previously created NO-DNAT rules
self.nsxpolicy.tier1_nat_rule.delete(
router_id,
nat_rule_id=self._get_no_dnat_rule_id(subnet))
def _del_subnet_snat_rule(self, router_id, subnet):
# Delete the previously created SNAT rules
self.nsxpolicy.tier1_nat_rule.delete(
router_id,
nat_rule_id=self._get_snat_rule_id(subnet))
def _get_router_edge_cluster_path(self, tier0_uuid, router):
# Take the AZ edge cluster if configured
az = self._get_router_az_obj(router)
if az and az._edge_cluster_uuid:
ec_id = az._edge_cluster_uuid
# get the full path of the edge cluster (no backend call)
return self.nsxpolicy.edge_cluster.get_path(ec_id)
# Get the current tier0 edge cluster (cached call)
return self.nsxpolicy.tier0.get_edge_cluster_path(
tier0_uuid)
def _get_router_vlan_interfaces(self, context, router_id):
# return data about VLAN subnet connected to the router
rtr_subnets = self._load_router_subnet_cidrs_from_db(
context, router_id)
vlan_subnets = []
for sub in rtr_subnets:
net_id = sub['network_id']
if not self._is_overlay_network(context, <