2436 lines
112 KiB
Python
2436 lines
112 KiB
Python
# 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 hashlib
|
|
import re
|
|
import six
|
|
|
|
from aim import aim_manager
|
|
from aim.api import resource as aim_resource
|
|
from aim import context as aim_context
|
|
from aim import utils as aim_utils
|
|
from neutron.agent.linux import dhcp
|
|
from neutron.db import api as db_api
|
|
from neutron import policy
|
|
from neutron_lib import constants as n_constants
|
|
from neutron_lib import context as n_context
|
|
from neutron_lib import exceptions as n_exc
|
|
from oslo_concurrency import lockutils
|
|
from oslo_config import cfg
|
|
from oslo_log import helpers as log
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
|
|
from gbpservice.common import utils as gbp_utils
|
|
from gbpservice.network.neutronv2 import local_api
|
|
from gbpservice.neutron.db.grouppolicy import group_policy_db as gpdb
|
|
from gbpservice.neutron.db.grouppolicy import group_policy_mapping_db as gpmdb
|
|
from gbpservice.neutron.extensions import cisco_apic
|
|
from gbpservice.neutron.extensions import cisco_apic_gbp as aim_ext
|
|
from gbpservice.neutron.extensions import cisco_apic_l3
|
|
from gbpservice.neutron.extensions import group_policy as gpolicy
|
|
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import (
|
|
exceptions as md_exc)
|
|
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import (
|
|
mechanism_driver as md)
|
|
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import apic_mapper
|
|
from gbpservice.neutron.services.grouppolicy.common import (
|
|
constants as gp_const)
|
|
from gbpservice.neutron.services.grouppolicy.common import constants as g_const
|
|
from gbpservice.neutron.services.grouppolicy.common import exceptions as exc
|
|
from gbpservice.neutron.services.grouppolicy.drivers import (
|
|
neutron_resources as nrd)
|
|
from gbpservice.neutron.services.grouppolicy.drivers.cisco.apic import (
|
|
aim_mapping_rpc as aim_rpc)
|
|
from gbpservice.neutron.services.grouppolicy.drivers.cisco.apic import (
|
|
apic_mapping_lib as alib)
|
|
from gbpservice.neutron.services.grouppolicy.drivers.cisco.apic import (
|
|
nova_client as nclient)
|
|
from gbpservice.neutron.services.grouppolicy import plugin as gbp_plugin
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
FORWARD = 'Forward'
|
|
REVERSE = 'Reverse'
|
|
FILTER_DIRECTIONS = {FORWARD: False, REVERSE: True}
|
|
FORWARD_FILTER_ENTRIES = 'Forward-FilterEntries'
|
|
REVERSE_FILTER_ENTRIES = 'Reverse-FilterEntries'
|
|
AUTO_PTG_NAME_PREFIX = 'ptg-for-l2p-%s'
|
|
# Note that this prefix should not exceede 4 characters
|
|
AUTO_PTG_PREFIX = 'auto'
|
|
AUTO_PTG_ID_PREFIX = AUTO_PTG_PREFIX + '%s'
|
|
|
|
# Definitions duplicated from apicapi lib
|
|
APIC_OWNED = 'apic_owned_'
|
|
PROMISCUOUS_TYPES = [n_constants.DEVICE_OWNER_DHCP,
|
|
n_constants.DEVICE_OWNER_LOADBALANCER]
|
|
# TODO(ivar): define a proper promiscuous API
|
|
PROMISCUOUS_SUFFIX = 'promiscuous'
|
|
|
|
CONTRACTS = 'contracts'
|
|
CONTRACT_SUBJECTS = 'contract_subjects'
|
|
FILTERS = 'filters'
|
|
FILTER_ENTRIES = 'filter_entries'
|
|
ENFORCED = aim_resource.EndpointGroup.POLICY_ENFORCED
|
|
UNENFORCED = aim_resource.EndpointGroup.POLICY_UNENFORCED
|
|
DEFAULT_SG_NAME = 'gbp_default'
|
|
COMMON_TENANT_AIM_RESOURCES = [aim_resource.Contract.__name__,
|
|
aim_resource.ContractSubject.__name__,
|
|
aim_resource.Filter.__name__,
|
|
aim_resource.FilterEntry.__name__]
|
|
# REVISIT: override add_router_interface L3 API check for now
|
|
NO_VALIDATE = cisco_apic_l3.OVERRIDE_NETWORK_ROUTING_TOPOLOGY_VALIDATION
|
|
|
|
# REVISIT: Auto-PTG is currently config driven to align with the
|
|
# config driven behavior of the older driver but is slated for
|
|
# removal.
|
|
opts = [
|
|
cfg.BoolOpt('create_auto_ptg',
|
|
default=True,
|
|
help=_("Automatically create a PTG when a L2 Policy "
|
|
"gets created. This is currently an aim_mapping "
|
|
"policy driver specific feature.")),
|
|
cfg.BoolOpt('create_per_l3p_implicit_contracts',
|
|
default=True,
|
|
help=_("This configuration is set to True to migrate a "
|
|
"deployment that has l3_policies without implicit "
|
|
"AIM contracts (these are deployments which have "
|
|
"AIM implicit contracts per tenant). A Neutron server "
|
|
"restart is required for this configuration to take "
|
|
"effect. The creation of the implicit contracts "
|
|
"happens at the time of the AIM policy driver "
|
|
"initialization. The configuration can be set to "
|
|
"False to avoid recreating the implicit contracts "
|
|
"on subsequent Neutron server restarts. This "
|
|
"option will be removed in the O release")),
|
|
cfg.BoolOpt('advertise_mtu',
|
|
default=True,
|
|
help=_('If True, advertise network MTU values if core plugin '
|
|
'calculates them. MTU is advertised to running '
|
|
'instances via DHCP and RA MTU options.')),
|
|
]
|
|
|
|
cfg.CONF.register_opts(opts, "aim_mapping")
|
|
|
|
|
|
class InvalidVrfForDualStackAddressScopes(exc.GroupPolicyBadRequest):
|
|
message = _("User-specified address scopes for both address families, "
|
|
"(IPv4 and IPv6) must use the same ACI VRF.")
|
|
|
|
|
|
class AutoPTGDeleteNotSupported(exc.GroupPolicyBadRequest):
|
|
message = _("Auto PTG %(id)s cannot be deleted.")
|
|
|
|
|
|
class ExplicitAPGAssociationNotSupportedForAutoPTG(
|
|
exc.GroupPolicyBadRequest):
|
|
message = _("Explicit APG association not supported for Auto PTG, "
|
|
"with AIM GBP driver")
|
|
|
|
|
|
class SharedAttributeUpdateNotSupported(exc.GroupPolicyBadRequest):
|
|
message = _("Resource shared attribute update not supported with AIM "
|
|
"GBP driver for resource of type %(type)s")
|
|
|
|
|
|
class IncorrectSubnetpoolUpdate(exc.GroupPolicyBadRequest):
|
|
message = _("Subnetpool %(subnetpool_id)s cannot be disassociated "
|
|
"from L3 Policy %(l3p_id)s since it has allocated subnet(s) "
|
|
"associated with that L3 Policy")
|
|
|
|
|
|
class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin):
|
|
"""AIM Mapping Orchestration driver.
|
|
|
|
This driver maps GBP resources to the ACI-Integration-Module (AIM).
|
|
"""
|
|
|
|
@log.log_method_call
|
|
def initialize(self):
|
|
LOG.info("APIC AIM Policy Driver initializing")
|
|
super(AIMMappingDriver, self).initialize()
|
|
self._apic_aim_mech_driver = None
|
|
self._apic_segmentation_label_driver = None
|
|
self._apic_allowed_vm_name_driver = None
|
|
self._aim = None
|
|
self._name_mapper = None
|
|
self.create_auto_ptg = cfg.CONF.aim_mapping.create_auto_ptg
|
|
if self.create_auto_ptg:
|
|
LOG.info('Auto PTG creation configuration set, '
|
|
'this will result in automatic creation of a PTG '
|
|
'per L2 Policy')
|
|
self.create_per_l3p_implicit_contracts = (
|
|
cfg.CONF.aim_mapping.create_per_l3p_implicit_contracts)
|
|
self.advertise_mtu = cfg.CONF.aim_mapping.advertise_mtu
|
|
local_api.QUEUE_OUT_OF_PROCESS_NOTIFICATIONS = True
|
|
if self.create_per_l3p_implicit_contracts:
|
|
LOG.info('Implicit AIM contracts will be created '
|
|
'for l3_policies which do not have them.')
|
|
self._create_per_l3p_implicit_contracts()
|
|
|
|
@log.log_method_call
|
|
def start_rpc_listeners(self):
|
|
return self.setup_opflex_rpc_listeners()
|
|
|
|
@property
|
|
def aim_mech_driver(self):
|
|
if not self._apic_aim_mech_driver:
|
|
self._apic_aim_mech_driver = (
|
|
self._core_plugin.mechanism_manager.mech_drivers[
|
|
'apic_aim'].obj)
|
|
return self._apic_aim_mech_driver
|
|
|
|
@property
|
|
def aim(self):
|
|
if not self._aim:
|
|
self._aim = self.aim_mech_driver.aim
|
|
return self._aim
|
|
|
|
@property
|
|
def name_mapper(self):
|
|
if not self._name_mapper:
|
|
self._name_mapper = self.aim_mech_driver.name_mapper
|
|
return self._name_mapper
|
|
|
|
@property
|
|
def apic_segmentation_label_driver(self):
|
|
if not self._apic_segmentation_label_driver:
|
|
ext_drivers = self.gbp_plugin.extension_manager.ordered_ext_drivers
|
|
for driver in ext_drivers:
|
|
if 'apic_segmentation_label' == driver.name:
|
|
self._apic_segmentation_label_driver = (
|
|
driver.obj)
|
|
break
|
|
return self._apic_segmentation_label_driver
|
|
|
|
@property
|
|
def apic_allowed_vm_name_driver(self):
|
|
if self._apic_allowed_vm_name_driver is False:
|
|
return False
|
|
if not self._apic_allowed_vm_name_driver:
|
|
ext_drivers = (self.gbp_plugin.extension_manager.
|
|
ordered_ext_drivers)
|
|
for driver in ext_drivers:
|
|
if 'apic_allowed_vm_name' == driver.name:
|
|
self._apic_allowed_vm_name_driver = driver.obj
|
|
break
|
|
if not self._apic_allowed_vm_name_driver:
|
|
self._apic_allowed_vm_name_driver = False
|
|
return self._apic_allowed_vm_name_driver
|
|
|
|
@log.log_method_call
|
|
def ensure_tenant(self, plugin_context, tenant_id):
|
|
self.aim_mech_driver.ensure_tenant(plugin_context, tenant_id)
|
|
|
|
def aim_display_name(self, name):
|
|
return aim_utils.sanitize_display_name(name)
|
|
|
|
def _use_implicit_address_scope(self, context, ip_version, **kwargs):
|
|
# Ensure ipv4 and ipv6 address scope have same vrf
|
|
kwargs = {}
|
|
if context.saved_scope_vrf:
|
|
kwargs.update({cisco_apic.DIST_NAMES: context.saved_scope_vrf})
|
|
address_scope = super(AIMMappingDriver,
|
|
self)._use_implicit_address_scope(context,
|
|
ip_version,
|
|
**kwargs)
|
|
context.saved_scope_vrf = address_scope[cisco_apic.DIST_NAMES]
|
|
return address_scope
|
|
|
|
# TODO(tbachman): remove once non-isomorphic address scopes
|
|
# are supported
|
|
def _validate_address_scopes(self, context):
|
|
l3p_db = context._plugin._get_l3_policy(
|
|
context._plugin_context, context.current['id'])
|
|
v4_scope_id = l3p_db['address_scope_v4_id']
|
|
v6_scope_id = l3p_db['address_scope_v6_id']
|
|
if v4_scope_id and v6_scope_id:
|
|
v4_scope = self._get_address_scope(
|
|
context._plugin_context, v4_scope_id)
|
|
v6_scope = self._get_address_scope(
|
|
context._plugin_context, v6_scope_id)
|
|
if (v4_scope[cisco_apic.DIST_NAMES][cisco_apic.VRF] !=
|
|
v6_scope[cisco_apic.DIST_NAMES][cisco_apic.VRF]):
|
|
raise InvalidVrfForDualStackAddressScopes()
|
|
|
|
@log.log_method_call
|
|
def create_l3_policy_precommit(self, context):
|
|
l3p_req = context.current
|
|
self._check_l3policy_ext_segment(context, l3p_req)
|
|
|
|
# save VRF DN from v4 family address scope, if implicitly created,
|
|
# as we will need to reuse it if we also implicitly create a v6
|
|
# address scopes
|
|
context.saved_scope_vrf = None
|
|
self._create_l3p_subnetpools(context)
|
|
# reset the temporarily saved scope
|
|
context.saved_scope_vrf = None
|
|
|
|
self._validate_address_scopes(context)
|
|
|
|
# REVISIT: Check if the following constraint still holds
|
|
if len(l3p_req['routers']) > 1:
|
|
raise exc.L3PolicyMultipleRoutersNotSupported()
|
|
# REVISIT: Validate non overlapping IPs in the same tenant.
|
|
# Currently this validation is not required for the
|
|
# AIM driver, and since the AIM driver is the only
|
|
# driver inheriting from this driver, we are okay
|
|
# without the check.
|
|
self._reject_invalid_router_access(context)
|
|
if not l3p_req['routers']:
|
|
self._use_implicit_router(context)
|
|
if not context.current['external_segments']:
|
|
self._use_implicit_external_segment(context)
|
|
external_segments = context.current['external_segments']
|
|
if external_segments:
|
|
self._plug_l3p_routers_to_ext_segment(context, l3p_req,
|
|
external_segments)
|
|
self._create_implicit_contracts(context, l3p_req)
|
|
|
|
@log.log_method_call
|
|
def update_l3_policy_precommit(self, context):
|
|
self._reject_shared_update(context, 'l3_policy')
|
|
if context.current['routers'] != context.original['routers']:
|
|
raise exc.L3PolicyRoutersUpdateNotSupported()
|
|
# Currently there is no support for router update in l3p update.
|
|
# Added this check just in case it is supported in future.
|
|
self._reject_invalid_router_access(context)
|
|
|
|
self._validate_in_use_by_nsp(context)
|
|
self._update_l3p_subnetpools(context)
|
|
|
|
# TODO(Sumit): For extra safety add validation for address_scope change
|
|
l3p_orig = context.original
|
|
l3p_curr = context.current
|
|
self._check_l3policy_ext_segment(context, l3p_curr)
|
|
old_segment_dict = l3p_orig['external_segments']
|
|
new_segment_dict = l3p_curr['external_segments']
|
|
if (l3p_curr['external_segments'] !=
|
|
l3p_orig['external_segments']):
|
|
new_segments = set(new_segment_dict.keys())
|
|
old_segments = set(old_segment_dict.keys())
|
|
removed = old_segments - new_segments
|
|
self._unplug_l3p_routers_from_ext_segment(context,
|
|
l3p_curr,
|
|
removed)
|
|
added_dict = {s: new_segment_dict[s]
|
|
for s in (new_segments - old_segments)}
|
|
if added_dict:
|
|
self._plug_l3p_routers_to_ext_segment(context,
|
|
l3p_curr,
|
|
added_dict)
|
|
|
|
@log.log_method_call
|
|
def delete_l3_policy_precommit(self, context):
|
|
external_segments = context.current['external_segments']
|
|
if external_segments:
|
|
self._unplug_l3p_routers_from_ext_segment(context,
|
|
context.current, external_segments.keys())
|
|
l3p_db = context._plugin._get_l3_policy(
|
|
context._plugin_context, context.current['id'])
|
|
|
|
v4v6subpools = {4: l3p_db.subnetpools_v4, 6: l3p_db.subnetpools_v6}
|
|
|
|
for k, v in six.iteritems(v4v6subpools):
|
|
subpools = [sp.subnetpool_id for sp in v]
|
|
for sp_id in subpools:
|
|
self._db_plugin(
|
|
context._plugin)._remove_subnetpool_from_l3_policy(
|
|
context._plugin_context, l3p_db['id'], sp_id,
|
|
ip_version=k)
|
|
self._cleanup_subnetpool(context._plugin_context, sp_id)
|
|
|
|
for ascp in self.L3P_ADDRESS_SCOPE_KEYS.values():
|
|
if l3p_db[ascp]:
|
|
ascp_id = l3p_db[ascp]
|
|
l3p_db.update({ascp: None})
|
|
self._cleanup_address_scope(context._plugin_context, ascp_id)
|
|
|
|
for router_id in context.current['routers']:
|
|
self._db_plugin(context._plugin)._remove_router_from_l3_policy(
|
|
context._plugin_context, l3p_db['id'], router_id)
|
|
self._cleanup_router(context._plugin_context, router_id)
|
|
self._delete_implicit_contracts(context, context.current)
|
|
|
|
@log.log_method_call
|
|
def get_l3_policy_status(self, context):
|
|
# Not all of the neutron resources that l3_policy maps to
|
|
# has a status attribute, hence we derive the status
|
|
# from the AIM resources that the neutron resources map to
|
|
session = context._plugin_context.session
|
|
l3p_db = context._plugin._get_l3_policy(
|
|
context._plugin_context, context.current['id'])
|
|
mapped_aim_resources = []
|
|
# Note: Subnetpool is not mapped to any AIM resource, hence it is not
|
|
# considered for deriving the status
|
|
mapped_status = []
|
|
|
|
for ascp in self.L3P_ADDRESS_SCOPE_KEYS.values():
|
|
if l3p_db[ascp]:
|
|
ascp_id = l3p_db[ascp]
|
|
ascope = self._get_address_scope(
|
|
context._plugin_context, ascp_id)
|
|
vrf_dn = ascope[cisco_apic.DIST_NAMES][cisco_apic.VRF]
|
|
aim_vrf = self._get_vrf_by_dn(context, vrf_dn)
|
|
mapped_aim_resources.append(aim_vrf)
|
|
|
|
routers = [router.router_id for router in l3p_db.routers]
|
|
for router_id in routers:
|
|
# elevated context is used here to enable router retrieval in
|
|
# shared L3P cases wherein the call to get_l3_policy might be
|
|
# made in the context of a different tenant
|
|
router = self._get_router(
|
|
context._plugin_context.elevated(), router_id)
|
|
mapped_status.append(
|
|
{'status': self._map_ml2plus_status(router)})
|
|
|
|
mapped_status.append({'status': self._merge_aim_status(
|
|
session, mapped_aim_resources)})
|
|
context.current['status'] = self._merge_gbp_status(mapped_status)
|
|
|
|
@log.log_method_call
|
|
def create_l2_policy_precommit(self, context):
|
|
self._reject_invalid_network_access(context)
|
|
self._reject_non_shared_net_on_shared_l2p(context)
|
|
l2p_db = context._plugin._get_l2_policy(
|
|
context._plugin_context, context.current['id'])
|
|
if not context.current['l3_policy_id']:
|
|
self._create_implicit_l3_policy(context)
|
|
l2p_db['l3_policy_id'] = context.current['l3_policy_id']
|
|
l3p_db = context._plugin._get_l3_policy(
|
|
context._plugin_context, l2p_db['l3_policy_id'])
|
|
if not context.current['network_id']:
|
|
self._use_implicit_network(
|
|
context, address_scope_v4=l3p_db['address_scope_v4_id'],
|
|
address_scope_v6=l3p_db['address_scope_v6_id'])
|
|
l2p_db['network_id'] = context.current['network_id']
|
|
l2p = context.current
|
|
net = self._get_network(context._plugin_context,
|
|
l2p['network_id'])
|
|
default_epg_dn = net[cisco_apic.DIST_NAMES][cisco_apic.EPG]
|
|
self._configure_contracts_for_default_epg(
|
|
context, l3p_db, default_epg_dn)
|
|
if self.create_auto_ptg:
|
|
default_epg = self._get_epg_by_dn(context, default_epg_dn)
|
|
desc = "System created PTG for L2P (UUID: %s)" % l2p['id']
|
|
data = {
|
|
"id": self._get_auto_ptg_id(l2p['id']),
|
|
"name": self._get_auto_ptg_name(l2p),
|
|
"description": desc,
|
|
"l2_policy_id": l2p['id'],
|
|
"proxied_group_id": None,
|
|
"proxy_type": None,
|
|
"proxy_group_id": n_constants.ATTR_NOT_SPECIFIED,
|
|
"network_service_policy_id": None,
|
|
"service_management": False,
|
|
"shared": l2p['shared'],
|
|
"intra_ptg_allow":
|
|
self._map_policy_enforcement_pref(default_epg),
|
|
}
|
|
self._create_policy_target_group(context._plugin_context, data)
|
|
|
|
@log.log_method_call
|
|
def delete_l2_policy_precommit(self, context):
|
|
l2p_id = context.current['id']
|
|
auto_ptg_id = self._get_auto_ptg_id(l2p_id)
|
|
try:
|
|
auto_ptg = context._plugin._get_policy_target_group(
|
|
context._plugin_context, auto_ptg_id)
|
|
self._process_subnets_for_ptg_delete(
|
|
context, auto_ptg, l2p_id)
|
|
if auto_ptg['l2_policy_id']:
|
|
auto_ptg.update({'l2_policy_id': None})
|
|
# REVISIT: Consider calling the actual GBP plugin
|
|
# instead of it's base DB mixin class.
|
|
self._db_plugin(
|
|
context._plugin).delete_policy_target_group(
|
|
context._plugin_context, auto_ptg['id'])
|
|
except gpolicy.PolicyTargetGroupNotFound:
|
|
LOG.info("Auto PTG with ID %(id)s for "
|
|
"for L2P %(l2p)s not found. If create_auto_ptg "
|
|
"configuration was not set at the time of the L2P "
|
|
"creation, you can safely ignore this, else this "
|
|
"could potentially be indication of an error.",
|
|
{'id': auto_ptg_id, 'l2p': l2p_id})
|
|
super(AIMMappingDriver, self).delete_l2_policy_precommit(context)
|
|
|
|
@log.log_method_call
|
|
def get_l2_policy_status(self, context):
|
|
l2p_db = context._plugin._get_l2_policy(
|
|
context._plugin_context, context.current['id'])
|
|
net = self._get_network(context._plugin_context,
|
|
l2p_db['network_id'])
|
|
|
|
if net:
|
|
context.current['status'] = net['status']
|
|
default_epg_dn = net[cisco_apic.DIST_NAMES][cisco_apic.EPG]
|
|
l3p_db = context._plugin._get_l3_policy(
|
|
context._plugin_context, l2p_db['l3_policy_id'])
|
|
aim_resources = self._get_implicit_contracts_for_default_epg(
|
|
context, l3p_db, default_epg_dn)
|
|
aim_resources_list = []
|
|
for k in aim_resources.keys():
|
|
if not aim_resources[k] or not all(
|
|
x for x in aim_resources[k]):
|
|
# We expected a AIM mapped resource but did not find
|
|
# it, so something seems to be wrong
|
|
context.current['status'] = gp_const.STATUS_ERROR
|
|
return
|
|
aim_resources_list.extend(aim_resources[k])
|
|
merged_aim_status = self._merge_aim_status(
|
|
context._plugin_context.session, aim_resources_list)
|
|
context.current['status'] = self._merge_gbp_status(
|
|
[context.current, {'status': merged_aim_status}])
|
|
else:
|
|
context.current['status'] = gp_const.STATUS_ERROR
|
|
|
|
@log.log_method_call
|
|
def create_policy_target_group_precommit(self, context):
|
|
session = context._plugin_context.session
|
|
|
|
if self._is_auto_ptg(context.current):
|
|
if context.current['application_policy_group_id']:
|
|
raise ExplicitAPGAssociationNotSupportedForAutoPTG()
|
|
self._use_implicit_subnet(context)
|
|
self._handle_create_network_service_policy(context)
|
|
return
|
|
|
|
if context.current['subnets']:
|
|
raise alib.ExplicitSubnetAssociationNotSupported()
|
|
|
|
if not context.current['l2_policy_id']:
|
|
self._create_implicit_l2_policy(context)
|
|
ptg_db = context._plugin._get_policy_target_group(
|
|
context._plugin_context, context.current['id'])
|
|
ptg_db['l2_policy_id'] = l2p_id = context.current['l2_policy_id']
|
|
else:
|
|
l2p_id = context.current['l2_policy_id']
|
|
|
|
l2p_db = context._plugin._get_l2_policy(
|
|
context._plugin_context, l2p_id)
|
|
|
|
net = self._get_network(
|
|
context._plugin_context, l2p_db['network_id'])
|
|
|
|
self._use_implicit_subnet(context)
|
|
|
|
self._handle_create_network_service_policy(context)
|
|
|
|
bd = self.aim_mech_driver.get_bd_for_network(session, net)
|
|
|
|
provided_contracts = self._get_aim_contract_names(
|
|
session, context.current['provided_policy_rule_sets'])
|
|
consumed_contracts = self._get_aim_contract_names(
|
|
session, context.current['consumed_policy_rule_sets'])
|
|
|
|
self._create_aim_ap_for_ptg_conditionally(context, context.current)
|
|
aim_epg = self._aim_endpoint_group(
|
|
session, context.current, bd.name, bd.tenant_name,
|
|
provided_contracts=provided_contracts,
|
|
consumed_contracts=consumed_contracts,
|
|
policy_enforcement_pref=
|
|
self._get_policy_enforcement_pref(context.current))
|
|
|
|
# AIM EPG will be persisted in the following call
|
|
self._add_implicit_svc_contracts_to_epg(context, l2p_db, aim_epg)
|
|
|
|
@log.log_method_call
|
|
def update_policy_target_group_precommit(self, context):
|
|
self._reject_shared_update(context, 'policy_target_group')
|
|
session = context._plugin_context.session
|
|
old_provided_contracts = self._get_aim_contract_names(
|
|
session, context.original['provided_policy_rule_sets'])
|
|
old_consumed_contracts = self._get_aim_contract_names(
|
|
session, context.original['consumed_policy_rule_sets'])
|
|
new_provided_contracts = self._get_aim_contract_names(
|
|
session, context.current['provided_policy_rule_sets'])
|
|
new_consumed_contracts = self._get_aim_contract_names(
|
|
session, context.current['consumed_policy_rule_sets'])
|
|
|
|
if (context.current['network_service_policy_id'] !=
|
|
context.original['network_service_policy_id']):
|
|
self._validate_nat_pool_for_nsp(context)
|
|
self._handle_nsp_update_on_ptg(context)
|
|
|
|
# The "original" version of the ptg is being used here since we
|
|
# want to retrieve the aim_epg based on the existing AP that is
|
|
# a part of its indentity
|
|
aim_epg = self._get_aim_endpoint_group(session, context.original)
|
|
if aim_epg:
|
|
if not self._is_auto_ptg(context.current):
|
|
aim_epg.display_name = (
|
|
self.aim_display_name(context.current['name']))
|
|
if (context.current['application_policy_group_id'] !=
|
|
context.original['application_policy_group_id']):
|
|
ap = self._create_aim_ap_for_ptg_conditionally(
|
|
context, context.current)
|
|
aim_epg = self._move_epg_to_new_ap(context, aim_epg, ap)
|
|
self._delete_aim_ap_for_ptg_conditionally(
|
|
context, context.original)
|
|
elif context.current['application_policy_group_id']:
|
|
raise ExplicitAPGAssociationNotSupportedForAutoPTG()
|
|
aim_epg.policy_enforcement_pref = (
|
|
self._get_policy_enforcement_pref(context.current))
|
|
aim_epg.provided_contract_names = (
|
|
list((set(aim_epg.provided_contract_names) -
|
|
set(old_provided_contracts)) |
|
|
set(new_provided_contracts)))
|
|
aim_epg.consumed_contract_names = (
|
|
list((set(aim_epg.consumed_contract_names) -
|
|
set(old_consumed_contracts)) |
|
|
set(new_consumed_contracts)))
|
|
|
|
self._add_contracts_for_epg(
|
|
aim_context.AimContext(session), aim_epg)
|
|
|
|
@log.log_method_call
|
|
def update_policy_target_group_postcommit(self, context):
|
|
if (context.current['application_policy_group_id'] !=
|
|
context.original['application_policy_group_id']):
|
|
ptargets = context._plugin.get_policy_targets(
|
|
context._plugin_context, {'policy_target_group_id':
|
|
[context.current['id']]})
|
|
for pt in ptargets:
|
|
self.aim_mech_driver._notify_port_update(
|
|
context._plugin_context, pt['port_id'])
|
|
|
|
@log.log_method_call
|
|
def delete_policy_target_group_precommit(self, context):
|
|
plugin_context = context._plugin_context
|
|
auto_ptg_id = self._get_auto_ptg_id(context.current['l2_policy_id'])
|
|
context.nsp_cleanup_ipaddress = self._get_ptg_policy_ipaddress_mapping(
|
|
context._plugin_context, context.current['id'])
|
|
context.nsp_cleanup_fips = self._get_ptg_policy_fip_mapping(
|
|
context._plugin_context, context.current['id'])
|
|
if context.current['id'] == auto_ptg_id:
|
|
raise AutoPTGDeleteNotSupported(id=context.current['id'])
|
|
ptg_db = context._plugin._get_policy_target_group(
|
|
plugin_context, context.current['id'])
|
|
session = context._plugin_context.session
|
|
|
|
aim_ctx = self._get_aim_context(context)
|
|
epg = self._aim_endpoint_group(session, context.current)
|
|
self.aim.delete(aim_ctx, epg)
|
|
self._process_subnets_for_ptg_delete(
|
|
context, ptg_db, context.current['l2_policy_id'])
|
|
|
|
self._delete_aim_ap_for_ptg_conditionally(context, ptg_db)
|
|
ptg_db.update({'application_policy_group_id': None})
|
|
|
|
if ptg_db['l2_policy_id']:
|
|
l2p_id = ptg_db['l2_policy_id']
|
|
ptg_db.update({'l2_policy_id': None})
|
|
l2p_db = context._plugin._get_l2_policy(
|
|
plugin_context, l2p_id)
|
|
if not l2p_db['policy_target_groups'] or (
|
|
(len(l2p_db['policy_target_groups']) == 1) and (
|
|
self._is_auto_ptg(l2p_db['policy_target_groups'][0]))):
|
|
self._cleanup_l2_policy(context, l2p_id)
|
|
|
|
if ptg_db['network_service_policy_id']:
|
|
ptg_db.update({'network_service_policy_id': None})
|
|
# REVISIT: Note that the RMD puts the following call in
|
|
# try/except block since in deployment it was observed
|
|
# that there are certain situations when the
|
|
# sa_exc.ObjectDeletedError is thrown.
|
|
self._cleanup_network_service_policy(
|
|
context, ptg_db, context.nsp_cleanup_ipaddress,
|
|
context.nsp_cleanup_fips)
|
|
|
|
@log.log_method_call
|
|
def extend_policy_target_group_dict(self, session, result):
|
|
epg = self._aim_endpoint_group(session, result)
|
|
if epg:
|
|
result[cisco_apic.DIST_NAMES] = {cisco_apic.EPG: epg.dn}
|
|
|
|
@log.log_method_call
|
|
def get_policy_target_group_status(self, context):
|
|
session = context._plugin_context.session
|
|
epg = self._aim_endpoint_group(session, context.current)
|
|
context.current['status'] = self._map_aim_status(session, epg)
|
|
|
|
def _get_application_profiles_mapped_to_apg(self, session, apg):
|
|
aim_ctx = aim_context.AimContext(session)
|
|
ap_name = self.apic_ap_name_for_application_policy_group(
|
|
session, apg['id'])
|
|
return self.aim.find(
|
|
aim_ctx, aim_resource.ApplicationProfile, name=ap_name)
|
|
|
|
@log.log_method_call
|
|
def extend_application_policy_group_dict(self, session, result):
|
|
aim_aps = self._get_application_profiles_mapped_to_apg(session, result)
|
|
dn_list = [ap.dn for ap in aim_aps]
|
|
result[cisco_apic.DIST_NAMES] = {cisco_apic.AP: dn_list}
|
|
|
|
@log.log_method_call
|
|
def get_application_policy_group_status(self, context):
|
|
session = context._plugin_context.session
|
|
aim_aps = self._get_application_profiles_mapped_to_apg(
|
|
session, context.current)
|
|
context.current['status'] = self._merge_aim_status(
|
|
context._plugin_context.session, aim_aps)
|
|
|
|
@log.log_method_call
|
|
def create_policy_target_precommit(self, context):
|
|
ptg = self._get_policy_target_group(
|
|
context._plugin_context, context.current['policy_target_group_id'])
|
|
policy.enforce(context._plugin_context, 'get_policy_target_group',
|
|
ptg, pluralized='policy_target_groups')
|
|
if not context.current['port_id']:
|
|
subnets = self._get_subnets(
|
|
context._plugin_context, {'id': ptg['subnets']})
|
|
self._use_implicit_port(context, subnets=subnets)
|
|
# explicit port case
|
|
else:
|
|
port_context = self._core_plugin.get_bound_port_context(
|
|
context._plugin_context, context.current['port_id'])
|
|
self.aim_mech_driver.associate_domain(port_context)
|
|
self._associate_fip_to_pt(context)
|
|
|
|
@log.log_method_call
|
|
def update_policy_target_precommit(self, context):
|
|
# TODO(Sumit): Implement
|
|
pass
|
|
|
|
@log.log_method_call
|
|
def update_policy_target_postcommit(self, context):
|
|
if self.apic_segmentation_label_driver and (
|
|
set(context.current['segmentation_labels']) != (
|
|
set(context.original['segmentation_labels']))):
|
|
self.aim_mech_driver._notify_port_update(
|
|
context._plugin_context, context.current['port_id'])
|
|
|
|
@log.log_method_call
|
|
def delete_policy_target_precommit(self, context):
|
|
fips = self._get_pt_floating_ip_mapping(
|
|
context._plugin_context, context.current['id'])
|
|
for fip in fips:
|
|
self._delete_fip(context._plugin_context, fip.floatingip_id)
|
|
pt_db = context._plugin._get_policy_target(
|
|
context._plugin_context, context.current['id'])
|
|
if pt_db['port_id']:
|
|
if not self._port_is_owned(context._plugin_context.session,
|
|
pt_db['port_id']):
|
|
port_context = self._core_plugin.get_bound_port_context(
|
|
context._plugin_context, context.current['port_id'])
|
|
self.aim_mech_driver.disassociate_domain(port_context)
|
|
self._cleanup_port(context._plugin_context, pt_db['port_id'])
|
|
|
|
@log.log_method_call
|
|
def update_policy_classifier_precommit(self, context):
|
|
o_dir = context.original['direction']
|
|
c_dir = context.current['direction']
|
|
o_prot = context.original['protocol']
|
|
c_prot = context.current['protocol']
|
|
o_port_min, o_port_max = (
|
|
gpmdb.GroupPolicyMappingDbPlugin._get_min_max_ports_from_range(
|
|
context.original['port_range']))
|
|
c_port_min, c_port_max = (
|
|
gpmdb.GroupPolicyMappingDbPlugin._get_min_max_ports_from_range(
|
|
context.current['port_range']))
|
|
|
|
if ((o_dir == c_dir) and (o_prot == c_prot) and (
|
|
o_port_min == c_port_min) and (o_port_max == c_port_max)):
|
|
# none of the fields relevant to the aim_mapping have changed
|
|
# so no further processing is required
|
|
return
|
|
|
|
prules = self._db_plugin(context._plugin).get_policy_rules(
|
|
context._plugin_context,
|
|
filters={'policy_classifier_id': [context.current['id']]})
|
|
|
|
if not prules:
|
|
# this policy_classifier has not yet been assocaited with
|
|
# a policy_rule and hence will not have any mapped aim
|
|
# resources
|
|
return
|
|
|
|
prule_ids = [x['id'] for x in prules]
|
|
|
|
prule_sets = self._get_prss_for_policy_rules(context, prule_ids)
|
|
|
|
for pr in prules:
|
|
session = context._plugin_context.session
|
|
aim_ctx = self._get_aim_context(context)
|
|
# delete old filter_entries
|
|
self._delete_filter_entries_for_policy_rule(
|
|
session, aim_ctx, pr)
|
|
|
|
aim_filter = self._aim_filter(session, pr)
|
|
aim_reverse_filter = self._aim_filter(
|
|
session, pr, reverse_prefix=True)
|
|
|
|
entries = alib.get_filter_entries_for_policy_classifier(
|
|
context.current)
|
|
|
|
remove_aim_reverse_filter = None
|
|
if not entries['reverse_rules']:
|
|
# the updated classifier's protocol does not have
|
|
# reverse filter_entries
|
|
if self.aim.get(aim_ctx, aim_reverse_filter):
|
|
# so remove the older reverse filter if it exists
|
|
self.aim.delete(aim_ctx, aim_reverse_filter)
|
|
remove_aim_reverse_filter = aim_reverse_filter.name
|
|
# Unset the reverse filter name so that its not
|
|
# used in further processing
|
|
aim_reverse_filter.name = None
|
|
|
|
# create new filter_entries mapping to the updated
|
|
# classifier and associated with aim_filters
|
|
self._create_policy_rule_aim_mappings(
|
|
session, aim_ctx, pr, entries)
|
|
|
|
# update contract_subject to put the filter in the
|
|
# appropriate in/out buckets corresponding to the
|
|
# updated direction of the policy_classifier
|
|
if remove_aim_reverse_filter or (o_dir != c_dir):
|
|
for prs in prule_sets:
|
|
aim_contract_subject = self._get_aim_contract_subject(
|
|
session, prs)
|
|
# Remove the older reverse filter if needed
|
|
for filters in [aim_contract_subject.in_filters,
|
|
aim_contract_subject.out_filters]:
|
|
if remove_aim_reverse_filter in filters:
|
|
filters.remove(remove_aim_reverse_filter)
|
|
if o_dir != c_dir:
|
|
# First remove the filter from the older
|
|
# direction list
|
|
for flist in [aim_contract_subject.in_filters,
|
|
aim_contract_subject.out_filters]:
|
|
for fname in [aim_filter.name,
|
|
aim_reverse_filter.name]:
|
|
if fname in flist:
|
|
flist.remove(fname)
|
|
# Now add it to the relevant direction list(s)
|
|
if c_dir == g_const.GP_DIRECTION_IN:
|
|
aim_contract_subject.in_filters.append(
|
|
aim_filter.name)
|
|
aim_contract_subject.out_filters.append(
|
|
aim_reverse_filter.name)
|
|
elif c_dir == g_const.GP_DIRECTION_OUT:
|
|
aim_contract_subject.in_filters.append(
|
|
aim_reverse_filter.name)
|
|
aim_contract_subject.out_filters.append(
|
|
aim_filter.name)
|
|
else:
|
|
aim_contract_subject.in_filters.append(
|
|
aim_filter.name)
|
|
aim_contract_subject.out_filters.append(
|
|
aim_reverse_filter.name)
|
|
aim_contract_subject.in_filters.append(
|
|
aim_reverse_filter.name)
|
|
aim_contract_subject.out_filters.append(
|
|
aim_filter.name)
|
|
self.aim.create(aim_ctx, aim_contract_subject,
|
|
overwrite=True)
|
|
|
|
@log.log_method_call
|
|
def create_policy_rule_precommit(self, context):
|
|
entries = alib.get_filter_entries_for_policy_rule(context)
|
|
session = context._plugin_context.session
|
|
aim_ctx = self._get_aim_context(context)
|
|
self._create_policy_rule_aim_mappings(
|
|
session, aim_ctx, context.current, entries)
|
|
|
|
@log.log_method_call
|
|
def update_policy_rule_precommit(self, context):
|
|
self.delete_policy_rule_precommit(context)
|
|
self.create_policy_rule_precommit(context)
|
|
|
|
@log.log_method_call
|
|
def delete_policy_rule_precommit(self, context):
|
|
session = context._plugin_context.session
|
|
aim_ctx = self._get_aim_context(context)
|
|
self._delete_filter_entries_for_policy_rule(session,
|
|
aim_ctx, context.current)
|
|
aim_filter = self._aim_filter(session, context.current)
|
|
aim_reverse_filter = self._aim_filter(
|
|
session, context.current, reverse_prefix=True)
|
|
for afilter in filter(None, [aim_filter, aim_reverse_filter]):
|
|
self.aim.delete(aim_ctx, afilter)
|
|
|
|
@log.log_method_call
|
|
def extend_policy_rule_dict(self, session, result):
|
|
result[cisco_apic.DIST_NAMES] = {}
|
|
aim_filter_entries = self._get_aim_filter_entries(session, result)
|
|
for k, v in six.iteritems(aim_filter_entries):
|
|
dn_list = []
|
|
for entry in v:
|
|
dn_list.append(entry.dn)
|
|
if k == FORWARD:
|
|
result[cisco_apic.DIST_NAMES].update(
|
|
{aim_ext.FORWARD_FILTER_ENTRIES: dn_list})
|
|
else:
|
|
result[cisco_apic.DIST_NAMES].update(
|
|
{aim_ext.REVERSE_FILTER_ENTRIES: dn_list})
|
|
|
|
@log.log_method_call
|
|
def get_policy_rule_status(self, context):
|
|
session = context._plugin_context.session
|
|
aim_filters = self._get_aim_filters(session, context.current)
|
|
aim_filter_entries = self._get_aim_filter_entries(
|
|
session, context.current)
|
|
context.current['status'] = self._merge_aim_status(
|
|
session, aim_filters.values() + aim_filter_entries.values())
|
|
|
|
@log.log_method_call
|
|
def create_policy_rule_set_precommit(self, context):
|
|
if context.current['child_policy_rule_sets']:
|
|
raise alib.HierarchicalContractsNotSupported()
|
|
aim_ctx = self._get_aim_context(context)
|
|
session = context._plugin_context.session
|
|
aim_contract = self._aim_contract(session, context.current)
|
|
self.aim.create(aim_ctx, aim_contract)
|
|
rules = self._db_plugin(context._plugin).get_policy_rules(
|
|
context._plugin_context,
|
|
filters={'id': context.current['policy_rules']})
|
|
self._populate_aim_contract_subject(context, aim_contract, rules)
|
|
|
|
@log.log_method_call
|
|
def update_policy_rule_set_precommit(self, context):
|
|
if context.current['child_policy_rule_sets']:
|
|
raise alib.HierarchicalContractsNotSupported()
|
|
session = context._plugin_context.session
|
|
aim_contract = self._aim_contract(session, context.current)
|
|
rules = self._db_plugin(context._plugin).get_policy_rules(
|
|
context._plugin_context,
|
|
filters={'id': context.current['policy_rules']})
|
|
self._populate_aim_contract_subject(
|
|
context, aim_contract, rules)
|
|
|
|
@log.log_method_call
|
|
def delete_policy_rule_set_precommit(self, context):
|
|
aim_ctx = self._get_aim_context(context)
|
|
session = context._plugin_context.session
|
|
aim_contract = self._aim_contract(session, context.current)
|
|
self._delete_aim_contract_subject(aim_ctx, aim_contract)
|
|
self.aim.delete(aim_ctx, aim_contract)
|
|
|
|
@log.log_method_call
|
|
def extend_policy_rule_set_dict(self, session, result):
|
|
result[cisco_apic.DIST_NAMES] = {}
|
|
aim_contract = self._aim_contract(session, result)
|
|
aim_contract_subject = self._aim_contract_subject(aim_contract)
|
|
result[cisco_apic.DIST_NAMES].update(
|
|
{aim_ext.CONTRACT: aim_contract.dn,
|
|
aim_ext.CONTRACT_SUBJECT: aim_contract_subject.dn})
|
|
|
|
@log.log_method_call
|
|
def get_policy_rule_set_status(self, context):
|
|
session = context._plugin_context.session
|
|
aim_contract = self._aim_contract(session, context.current)
|
|
aim_contract_subject = self._aim_contract_subject(aim_contract)
|
|
context.current['status'] = self._merge_aim_status(
|
|
session, [aim_contract, aim_contract_subject])
|
|
|
|
@log.log_method_call
|
|
def create_external_segment_precommit(self, context):
|
|
self._validate_default_external_segment(context)
|
|
if not context.current['subnet_id']:
|
|
raise exc.ImplicitSubnetNotSupported()
|
|
subnet = self._get_subnet(context._plugin_context,
|
|
context.current['subnet_id'])
|
|
network = self._get_network(context._plugin_context,
|
|
subnet['network_id'])
|
|
if not network['router:external']:
|
|
raise exc.InvalidSubnetForES(sub_id=subnet['id'],
|
|
net_id=network['id'])
|
|
db_es = context._plugin._get_external_segment(
|
|
context._plugin_context, context.current['id'])
|
|
db_es.cidr = subnet['cidr']
|
|
db_es.ip_version = subnet['ip_version']
|
|
context.current['cidr'] = db_es.cidr
|
|
context.current['ip_version'] = db_es.ip_version
|
|
|
|
cidrs = sorted([x['destination']
|
|
for x in context.current['external_routes']])
|
|
self._update_network(context._plugin_context,
|
|
subnet['network_id'],
|
|
{cisco_apic.EXTERNAL_CIDRS: cidrs})
|
|
|
|
@log.log_method_call
|
|
def update_external_segment_precommit(self, context):
|
|
# REVISIT: what other attributes should we prevent an update on?
|
|
invalid = ['port_address_translation']
|
|
for attr in invalid:
|
|
if context.current[attr] != context.original[attr]:
|
|
raise exc.InvalidAttributeUpdateForES(attribute=attr)
|
|
|
|
old_cidrs = sorted([x['destination']
|
|
for x in context.original['external_routes']])
|
|
new_cidrs = sorted([x['destination']
|
|
for x in context.current['external_routes']])
|
|
if old_cidrs != new_cidrs:
|
|
subnet = self._get_subnet(context._plugin_context,
|
|
context.current['subnet_id'])
|
|
self._update_network(context._plugin_context,
|
|
subnet['network_id'],
|
|
{cisco_apic.EXTERNAL_CIDRS: new_cidrs})
|
|
|
|
@log.log_method_call
|
|
def delete_external_segment_precommit(self, context):
|
|
subnet = self._get_subnet(context._plugin_context,
|
|
context.current['subnet_id'])
|
|
self._update_network(context._plugin_context,
|
|
subnet['network_id'],
|
|
{cisco_apic.EXTERNAL_CIDRS: ['0.0.0.0/0']})
|
|
|
|
@log.log_method_call
|
|
def create_external_policy_precommit(self, context):
|
|
self._check_external_policy(context, context.current)
|
|
if not context.current['external_segments']:
|
|
self._use_implicit_external_segment(context)
|
|
|
|
routers = self._get_ext_policy_routers(context,
|
|
context.current, context.current['external_segments'])
|
|
for r in routers:
|
|
self._set_router_ext_contracts(context, r, context.current)
|
|
|
|
@log.log_method_call
|
|
def update_external_policy_precommit(self, context):
|
|
ep = context.current
|
|
old_ep = context.original
|
|
self._check_external_policy(context, ep)
|
|
removed_segments = (set(old_ep['external_segments']) -
|
|
set(ep['external_segments']))
|
|
added_segment = (set(ep['external_segments']) -
|
|
set(old_ep['external_segments']))
|
|
if removed_segments:
|
|
routers = self._get_ext_policy_routers(context, ep,
|
|
removed_segments)
|
|
for r in routers:
|
|
self._set_router_ext_contracts(context, r, None)
|
|
|
|
if (added_segment or
|
|
sorted(old_ep['provided_policy_rule_sets']) !=
|
|
sorted(ep['provided_policy_rule_sets']) or
|
|
sorted(old_ep['consumed_policy_rule_sets']) !=
|
|
sorted(ep['consumed_policy_rule_sets'])):
|
|
routers = self._get_ext_policy_routers(context, ep,
|
|
ep['external_segments'])
|
|
for r in routers:
|
|
self._set_router_ext_contracts(context, r, ep)
|
|
|
|
@log.log_method_call
|
|
def delete_external_policy_precommit(self, context):
|
|
routers = self._get_ext_policy_routers(context,
|
|
context.current, context.current['external_segments'])
|
|
for r in routers:
|
|
self._set_router_ext_contracts(context, r, None)
|
|
|
|
@log.log_method_call
|
|
def create_network_service_policy_precommit(self, context):
|
|
self._validate_nsp_parameters(context)
|
|
|
|
@log.log_method_call
|
|
def update_network_service_policy_precommit(self, context):
|
|
self._validate_nsp_parameters(context)
|
|
|
|
@log.log_method_call
|
|
def create_nat_pool_precommit(self, context):
|
|
self._add_nat_pool_to_segment(context)
|
|
self._add_implicit_subnet_for_nat_pool_create(context)
|
|
|
|
@log.log_method_call
|
|
def update_nat_pool_precommit(self, context):
|
|
self._process_ext_segment_update_for_nat_pool(context)
|
|
self._add_implicit_subnet_for_nat_pool_update(context)
|
|
|
|
@log.log_method_call
|
|
def delete_nat_pool_precommit(self, context):
|
|
self._nat_pool_in_use(context)
|
|
np_db = context._plugin._get_nat_pool(
|
|
context._plugin_context, context.current['id'])
|
|
np_db.update({'subnet_id': None})
|
|
self._delete_subnet_on_nat_pool_delete(context)
|
|
|
|
def check_allow_vm_names(self, context, port):
|
|
ok_to_bind = True
|
|
ptg, pt = self._port_id_to_ptg(context._plugin_context, port['id'])
|
|
# enforce the allowed_vm_names rules if possible
|
|
if (ptg and port['device_id'] and
|
|
self.apic_allowed_vm_name_driver):
|
|
l2p = self._get_l2_policy(context._plugin_context,
|
|
ptg['l2_policy_id'])
|
|
l3p = self.gbp_plugin.get_l3_policy(
|
|
context._plugin_context, l2p['l3_policy_id'])
|
|
if l3p.get('allowed_vm_names'):
|
|
ok_to_bind = False
|
|
vm = nclient.NovaClient().get_server(port['device_id'])
|
|
for allowed_vm_name in l3p['allowed_vm_names']:
|
|
match = re.search(allowed_vm_name, vm.name)
|
|
if match:
|
|
ok_to_bind = True
|
|
break
|
|
if not ok_to_bind:
|
|
LOG.warning("Failed to bind the port due to "
|
|
"allowed_vm_names rules %(rules)s "
|
|
"for VM: %(vm)s",
|
|
{'rules': l3p['allowed_vm_names'],
|
|
'vm': vm.name})
|
|
return ok_to_bind
|
|
|
|
def get_ptg_port_ids(self, context, ptg):
|
|
pts = self.gbp_plugin.get_policy_targets(
|
|
context, {'id': ptg['policy_targets']})
|
|
return [x['port_id'] for x in pts]
|
|
|
|
def _reject_shared_update(self, context, type):
|
|
if context.original.get('shared') != context.current.get('shared'):
|
|
raise SharedAttributeUpdateNotSupported(type=type)
|
|
|
|
def _aim_tenant_name(self, session, tenant_id, aim_resource_class=None,
|
|
gbp_resource=None, gbp_obj=None):
|
|
if aim_resource_class and (
|
|
aim_resource_class.__name__ in COMMON_TENANT_AIM_RESOURCES):
|
|
# COMMON_TENANT_AIM_RESOURCES will always be created in the
|
|
# ACI common tenant
|
|
aim_ctx = aim_context.AimContext(session)
|
|
self.aim_mech_driver._ensure_common_tenant(aim_ctx)
|
|
tenant_name = md.COMMON_TENANT_NAME
|
|
else:
|
|
l3p_id = None
|
|
if aim_resource_class.__name__ == (
|
|
aim_resource.EndpointGroup.__name__):
|
|
# the gbp_obj here should be a ptg
|
|
l2p_id = gbp_obj['l2_policy_id']
|
|
if l2p_id:
|
|
l2p_db = session.query(
|
|
gpmdb.L2PolicyMapping).filter_by(id=l2p_id).first()
|
|
l3p_id = l2p_db['l3_policy_id']
|
|
elif aim_resource_class.__name__ == (
|
|
aim_resource.BridgeDomain.__name__):
|
|
# the gbp_obj here should be a l2p
|
|
l3p_id = gbp_obj['l3_policy_id']
|
|
if l3p_id:
|
|
l3p_db = session.query(
|
|
gpmdb.L3PolicyMapping).filter_by(id=l3p_id).first()
|
|
tenant_id = l3p_db['tenant_id']
|
|
tenant_name = self.name_mapper.project(session, tenant_id)
|
|
LOG.debug("Mapped tenant_id %(id)s to %(apic_name)s",
|
|
{'id': tenant_id, 'apic_name': tenant_name})
|
|
return tenant_name
|
|
|
|
def _aim_application_profile_for_ptg(self, context, ptg):
|
|
# This returns a new AIM ApplicationProfile resource if apg_id
|
|
# is set, else returns None
|
|
apg_id = ptg['application_policy_group_id']
|
|
if apg_id:
|
|
apg = context._plugin._get_application_policy_group(
|
|
context._plugin_context, apg_id)
|
|
return self._aim_application_profile(
|
|
context._plugin_context.session, apg)
|
|
|
|
def _aim_application_profile(self, session, apg):
|
|
# This returns a new AIM ApplicationProfile resource
|
|
tenant_id = apg['tenant_id']
|
|
tenant_name = self._aim_tenant_name(
|
|
session, tenant_id,
|
|
aim_resource_class=aim_resource.ApplicationProfile, gbp_obj=apg)
|
|
display_name = self.aim_display_name(apg['name'])
|
|
ap_name = self.apic_ap_name_for_application_policy_group(
|
|
session, apg['id'])
|
|
ap = aim_resource.ApplicationProfile(tenant_name=tenant_name,
|
|
display_name=display_name,
|
|
name=ap_name)
|
|
LOG.debug("Mapped apg_id %(id)s with name %(name)s to %(apic_name)s",
|
|
{'id': apg['id'], 'name': display_name,
|
|
'apic_name': ap_name})
|
|
return ap
|
|
|
|
def _get_aim_application_profile_for_ptg(self, context, ptg):
|
|
# This gets an AP from the AIM DB
|
|
ap = self._aim_application_profile_for_ptg(context, ptg)
|
|
if ap:
|
|
return self._get_aim_application_profile_from_db(
|
|
context._plugin_context.session, ap)
|
|
|
|
def _get_aim_application_profile(self, session, apg):
|
|
# This gets an AP from the AIM DB
|
|
ap = self._aim_application_profile(session, apg)
|
|
return self._get_aim_application_profile_from_db(session, ap)
|
|
|
|
def _get_aim_application_profile_from_db(self, session, ap):
|
|
aim_ctx = aim_context.AimContext(session)
|
|
ap_fetched = self.aim.get(aim_ctx, ap)
|
|
if not ap_fetched:
|
|
LOG.debug("No ApplicationProfile found in AIM DB")
|
|
else:
|
|
LOG.debug("Got ApplicationProfile: %s", ap_fetched.__dict__)
|
|
return ap_fetched
|
|
|
|
def _create_aim_ap_for_ptg_conditionally(self, context, ptg):
|
|
if ptg and ptg['application_policy_group_id'] and not (
|
|
self._get_aim_application_profile_for_ptg(context, ptg)):
|
|
ap = self._aim_application_profile_for_ptg(context, ptg)
|
|
aim_ctx = aim_context.AimContext(context._plugin_context.session)
|
|
self.aim.create(aim_ctx, ap)
|
|
return ap
|
|
|
|
def _move_epg_to_new_ap(self, context, old_epg, new_ap):
|
|
session = context._plugin_context.session
|
|
aim_ctx = aim_context.AimContext(session)
|
|
self.aim.delete(aim_ctx, old_epg)
|
|
old_epg.app_profile_name = (
|
|
self.apic_ap_name_for_application_policy_group(
|
|
session, context.current['application_policy_group_id']))
|
|
self.aim.create(aim_ctx, old_epg)
|
|
return old_epg
|
|
|
|
def _delete_aim_ap_for_ptg_conditionally(self, context, ptg):
|
|
# It is assumed that this method is called after the EPG corresponding
|
|
# to the PTG has been deleted in AIM
|
|
if ptg and ptg['application_policy_group_id']:
|
|
ap = self._aim_application_profile_for_ptg(context, ptg)
|
|
apg_id = ptg['application_policy_group_id']
|
|
apg_db = context._plugin._get_application_policy_group(
|
|
context._plugin_context, apg_id)
|
|
if not apg_db['policy_target_groups'] or (
|
|
len(apg_db['policy_target_groups']) == 1 and (
|
|
apg_db['policy_target_groups'][0]['id'] == ptg['id'])):
|
|
# We lazily create the ApplicationProfile, so we delete
|
|
# it when the last PTG associated with this APG is deleted
|
|
aim_ctx = aim_context.AimContext(
|
|
context._plugin_context.session)
|
|
self.aim.delete(aim_ctx, ap)
|
|
|
|
def _aim_endpoint_group(self, session, ptg, bd_name=None,
|
|
bd_tenant_name=None,
|
|
provided_contracts=None,
|
|
consumed_contracts=None,
|
|
policy_enforcement_pref=UNENFORCED):
|
|
# This returns a new AIM EPG resource
|
|
tenant_id = ptg['tenant_id']
|
|
tenant_name = self._aim_tenant_name(
|
|
session, tenant_id, aim_resource_class=aim_resource.EndpointGroup,
|
|
gbp_obj=ptg)
|
|
id = ptg['id']
|
|
name = ptg['name']
|
|
display_name = self.aim_display_name(ptg['name'])
|
|
ap_name = self.apic_ap_name_for_application_policy_group(
|
|
session, ptg['application_policy_group_id'])
|
|
epg_name = self.apic_epg_name_for_policy_target_group(
|
|
session, id, name)
|
|
LOG.debug("Using application_profile %(ap_name)s "
|
|
"for epg %(epg_name)s",
|
|
{'ap_name': ap_name, 'epg_name': epg_name})
|
|
LOG.debug("Mapped ptg_id %(id)s with name %(name)s to %(apic_name)s",
|
|
{'id': id, 'name': name, 'apic_name': epg_name})
|
|
kwargs = {'tenant_name': str(tenant_name),
|
|
'name': str(epg_name),
|
|
'display_name': display_name,
|
|
'app_profile_name': ap_name,
|
|
'policy_enforcement_pref': policy_enforcement_pref}
|
|
if bd_name:
|
|
kwargs['bd_name'] = bd_name
|
|
if bd_tenant_name:
|
|
kwargs['bd_tenant_name'] = bd_tenant_name
|
|
|
|
if provided_contracts:
|
|
kwargs['provided_contract_names'] = provided_contracts
|
|
|
|
if consumed_contracts:
|
|
kwargs['consumed_contract_names'] = consumed_contracts
|
|
|
|
epg = aim_resource.EndpointGroup(**kwargs)
|
|
return epg
|
|
|
|
def _get_aim_endpoint_group(self, session, ptg):
|
|
# This gets an EPG from the AIM DB
|
|
epg = self._aim_endpoint_group(session, ptg)
|
|
aim_ctx = aim_context.AimContext(session)
|
|
epg_fetched = self.aim.get(aim_ctx, epg)
|
|
if not epg_fetched:
|
|
LOG.debug("No EPG found in AIM DB")
|
|
else:
|
|
LOG.debug("Got epg: %s", vars(epg_fetched))
|
|
return epg_fetched
|
|
|
|
def _aim_filter(self, session, pr, reverse_prefix=False):
|
|
# This returns a new AIM Filter resource
|
|
tenant_id = pr['tenant_id']
|
|
tenant_name = self._aim_tenant_name(session, tenant_id,
|
|
aim_resource.Filter)
|
|
id = pr['id']
|
|
name = pr['name']
|
|
display_name = self.aim_display_name(pr['name'])
|
|
if reverse_prefix:
|
|
filter_name = self.name_mapper.policy_rule(
|
|
session, id, prefix=alib.REVERSE_PREFIX)
|
|
else:
|
|
filter_name = self.name_mapper.policy_rule(session, id)
|
|
LOG.debug("Mapped policy_rule_id %(id)s with name %(name)s to"
|
|
"%(apic_name)s",
|
|
{'id': id, 'name': name, 'apic_name': filter_name})
|
|
kwargs = {'tenant_name': str(tenant_name),
|
|
'name': str(filter_name),
|
|
'display_name': display_name}
|
|
|
|
aim_filter = aim_resource.Filter(**kwargs)
|
|
return aim_filter
|
|
|
|
def _aim_filter_entry(self, session, aim_filter, filter_entry_name,
|
|
filter_entry_attrs):
|
|
# This returns a new AIM FilterEntry resource
|
|
tenant_name = aim_filter.tenant_name
|
|
filter_name = aim_filter.name
|
|
display_name = self.aim_display_name(filter_name)
|
|
kwargs = {'tenant_name': tenant_name,
|
|
'filter_name': filter_name,
|
|
'name': filter_entry_name,
|
|
'display_name': display_name}
|
|
kwargs.update(filter_entry_attrs)
|
|
|
|
aim_filter_entry = aim_resource.FilterEntry(**kwargs)
|
|
return aim_filter_entry
|
|
|
|
def _create_policy_rule_aim_mappings(
|
|
self, session, aim_context, pr, entries):
|
|
if entries['forward_rules']:
|
|
aim_filter = self._aim_filter(session, pr)
|
|
self.aim.create(aim_context, aim_filter, overwrite=True)
|
|
self._create_aim_filter_entries(session, aim_context, aim_filter,
|
|
entries['forward_rules'])
|
|
if entries['reverse_rules']:
|
|
# Also create reverse rule
|
|
aim_filter = self._aim_filter(session, pr,
|
|
reverse_prefix=True)
|
|
self.aim.create(aim_context, aim_filter, overwrite=True)
|
|
self._create_aim_filter_entries(
|
|
session, aim_context, aim_filter, entries['reverse_rules'])
|
|
|
|
def _delete_aim_filter_entries(self, aim_context, aim_filter):
|
|
aim_filter_entries = self.aim.find(
|
|
aim_context, aim_resource.FilterEntry,
|
|
tenant_name=aim_filter.tenant_name,
|
|
filter_name=aim_filter.name)
|
|
for entry in aim_filter_entries:
|
|
self.aim.delete(aim_context, entry)
|
|
|
|
def _delete_filter_entries_for_policy_rule(self, session, aim_context, pr):
|
|
aim_filter = self._aim_filter(session, pr)
|
|
aim_reverse_filter = self._aim_filter(
|
|
session, pr, reverse_prefix=True)
|
|
for afilter in filter(None, [aim_filter, aim_reverse_filter]):
|
|
self._delete_aim_filter_entries(aim_context, afilter)
|
|
|
|
def _create_aim_filter_entries(self, session, aim_ctx, aim_filter,
|
|
filter_entries):
|
|
for k, v in six.iteritems(filter_entries):
|
|
self._create_aim_filter_entry(
|
|
session, aim_ctx, aim_filter, k, v)
|
|
|
|
def _create_aim_filter_entry(self, session, aim_ctx, aim_filter,
|
|
filter_entry_name, filter_entry_attrs,
|
|
overwrite=False):
|
|
aim_filter_entry = self._aim_filter_entry(
|
|
session, aim_filter, filter_entry_name,
|
|
alib.map_to_aim_filter_entry(filter_entry_attrs))
|
|
self.aim.create(aim_ctx, aim_filter_entry, overwrite)
|
|
|
|
def _get_aim_filters(self, session, policy_rule):
|
|
# This gets the Forward and Reverse Filters from the AIM DB
|
|
aim_ctx = aim_context.AimContext(session)
|
|
filters = {}
|
|
for k, v in six.iteritems(FILTER_DIRECTIONS):
|
|
aim_filter = self._aim_filter(session, policy_rule, v)
|
|
aim_filter_fetched = self.aim.get(aim_ctx, aim_filter)
|
|
if not aim_filter_fetched:
|
|
LOG.debug("No %s Filter found in AIM DB", k)
|
|
else:
|
|
LOG.debug("Got Filter: %s", vars(aim_filter_fetched))
|
|
filters[k] = aim_filter_fetched
|
|
return filters
|
|
|
|
def _get_aim_filter_names(self, session, policy_rule):
|
|
# Forward and Reverse AIM Filter names for a Policy Rule
|
|
aim_filters = self._get_aim_filters(session, policy_rule)
|
|
aim_filter_names = [f.name for f in aim_filters.values() if f]
|
|
return aim_filter_names
|
|
|
|
def _get_aim_filter_entries(self, session, policy_rule):
|
|
# This gets the Forward and Reverse FilterEntries from the AIM DB
|
|
aim_ctx = aim_context.AimContext(session)
|
|
filters = self._get_aim_filters(session, policy_rule)
|
|
filters_entries = {}
|
|
for k, v in six.iteritems(filters):
|
|
if v:
|
|
aim_filter_entries = self.aim.find(
|
|
aim_ctx, aim_resource.FilterEntry,
|
|
tenant_name=v.tenant_name, filter_name=v.name)
|
|
if not aim_filter_entries:
|
|
LOG.debug("No %s FilterEntry found in AIM DB", k)
|
|
else:
|
|
LOG.debug("Got FilterEntry: %s", str(aim_filter_entries))
|
|
filters_entries[k] = aim_filter_entries
|
|
return filters_entries
|
|
|
|
def _aim_contract(self, session, policy_rule_set):
|
|
# This returns a new AIM Contract resource
|
|
return aim_resource.Contract(
|
|
tenant_name=self._aim_tenant_name(
|
|
session, policy_rule_set['tenant_id'], aim_resource.Contract),
|
|
name=self.name_mapper.policy_rule_set(
|
|
session, policy_rule_set['id']),
|
|
display_name=policy_rule_set['name'])
|
|
|
|
def _aim_contract_subject(self, aim_contract, in_filters=None,
|
|
out_filters=None, bi_filters=None):
|
|
# This returns a new AIM ContractSubject resource
|
|
if not in_filters:
|
|
in_filters = []
|
|
if not out_filters:
|
|
out_filters = []
|
|
if not bi_filters:
|
|
bi_filters = []
|
|
display_name = self.aim_display_name(aim_contract.name)
|
|
# Since we create one ContractSubject per Contract,
|
|
# ContractSubject is given the Contract name
|
|
kwargs = {'tenant_name': aim_contract.tenant_name,
|
|
'contract_name': aim_contract.name,
|
|
'name': aim_contract.name,
|
|
'display_name': display_name,
|
|
'in_filters': in_filters,
|
|
'out_filters': out_filters,
|
|
'bi_filters': bi_filters}
|
|
|
|
aim_contract_subject = aim_resource.ContractSubject(**kwargs)
|
|
return aim_contract_subject
|
|
|
|
def _populate_aim_contract_subject(self, context, aim_contract,
|
|
policy_rules):
|
|
in_filters, out_filters = [], []
|
|
session = context._plugin_context.session
|
|
for rule in policy_rules:
|
|
aim_filters = self._get_aim_filter_names(session, rule)
|
|
classifier = context._plugin.get_policy_classifier(
|
|
context._plugin_context, rule['policy_classifier_id'])
|
|
if classifier['direction'] == g_const.GP_DIRECTION_IN:
|
|
for fltr in aim_filters:
|
|
if fltr.startswith(alib.REVERSE_PREFIX):
|
|
out_filters.append(fltr)
|
|
else:
|
|
in_filters.append(fltr)
|
|
elif classifier['direction'] == g_const.GP_DIRECTION_OUT:
|
|
for fltr in aim_filters:
|
|
if fltr.startswith(alib.REVERSE_PREFIX):
|
|
in_filters.append(fltr)
|
|
else:
|
|
out_filters.append(fltr)
|
|
else:
|
|
in_filters += aim_filters
|
|
out_filters += aim_filters
|
|
self._populate_aim_contract_subject_by_filters(
|
|
context, aim_contract, in_filters, out_filters)
|
|
|
|
def _populate_aim_contract_subject_by_filters(
|
|
self, context, aim_contract, in_filters=None, out_filters=None,
|
|
bi_filters=None):
|
|
if not in_filters:
|
|
in_filters = []
|
|
if not out_filters:
|
|
out_filters = []
|
|
if not bi_filters:
|
|
bi_filters = []
|
|
aim_ctx = self._get_aim_context(context)
|
|
aim_contract_subject = self._aim_contract_subject(
|
|
aim_contract, in_filters, out_filters, bi_filters)
|
|
self.aim.create(aim_ctx, aim_contract_subject, overwrite=True)
|
|
|
|
def _get_aim_contract(self, session, policy_rule_set):
|
|
# This gets a Contract from the AIM DB
|
|
aim_ctx = aim_context.AimContext(session)
|
|
contract = self._aim_contract(session, policy_rule_set)
|
|
contract_fetched = self.aim.get(aim_ctx, contract)
|
|
if not contract_fetched:
|
|
LOG.debug("No Contract found in AIM DB")
|
|
else:
|
|
LOG.debug("Got Contract: %s", vars(contract_fetched))
|
|
return contract_fetched
|
|
|
|
def _get_aim_contract_names(self, session, prs_id_list):
|
|
contract_list = []
|
|
for prs_id in prs_id_list:
|
|
contract_name = self.name_mapper.policy_rule_set(session, prs_id)
|
|
contract_list.append(contract_name)
|
|
return contract_list
|
|
|
|
def _get_aim_contract_subject(self, session, policy_rule_set):
|
|
# This gets a ContractSubject from the AIM DB
|
|
aim_ctx = aim_context.AimContext(session)
|
|
contract = self._aim_contract(session, policy_rule_set)
|
|
contract_subject = self._aim_contract_subject(contract)
|
|
contract_subject_fetched = self.aim.get(aim_ctx, contract_subject)
|
|
if not contract_subject_fetched:
|
|
LOG.debug("No Contract found in AIM DB")
|
|
else:
|
|
LOG.debug("Got ContractSubject: %s",
|
|
vars(contract_subject_fetched))
|
|
return contract_subject_fetched
|
|
|
|
def _delete_aim_contract_subject(self, aim_context, aim_contract):
|
|
aim_contract_subject = self._aim_contract_subject(aim_contract)
|
|
self.aim.delete(aim_context, aim_contract_subject)
|
|
|
|
def _get_aim_default_endpoint_group(self, session, network):
|
|
return self.aim_mech_driver.get_epg_for_network(session, network)
|
|
|
|
def _get_l2p_subnets(self, context, l2p_id):
|
|
plugin_context = context._plugin_context
|
|
l2p = context._plugin.get_l2_policy(plugin_context, l2p_id)
|
|
# REVISIT: The following should be a get_subnets call via local API
|
|
return self._core_plugin.get_subnets_by_network(
|
|
plugin_context, l2p['network_id'])
|
|
|
|
def _sync_ptg_subnets(self, context, l2p):
|
|
l2p_subnets = [x['id'] for x in
|
|
self._get_l2p_subnets(context, l2p['id'])]
|
|
ptgs = context._plugin._get_policy_target_groups(
|
|
context._plugin_context.elevated(), {'l2_policy_id': [l2p['id']]})
|
|
for sub in l2p_subnets:
|
|
# Add to PTG
|
|
for ptg in ptgs:
|
|
if sub not in ptg['subnets']:
|
|
try:
|
|
(context._plugin.
|
|
_add_subnet_to_policy_target_group(
|
|
context._plugin_context.elevated(),
|
|
ptg['id'], sub))
|
|
except gpolicy.PolicyTargetGroupNotFound as e:
|
|
LOG.warning(e)
|
|
|
|
def _use_implicit_subnet(self, context, force_add=False):
|
|
"""Implicit subnet for AIM.
|
|
|
|
The first PTG in a L2P will allocate a new subnet from the L3P.
|
|
Any subsequent PTG in the same L2P will use the same subnet.
|
|
Additional subnets will be allocated as and when the currently used
|
|
subnet runs out of IP addresses.
|
|
"""
|
|
l2p_id = context.current['l2_policy_id']
|
|
with lockutils.lock(l2p_id, external=True):
|
|
subs = self._get_l2p_subnets(context, l2p_id)
|
|
subs = set([x['id'] for x in subs])
|
|
added = []
|
|
if not subs or force_add:
|
|
l2p = context._plugin.get_l2_policy(
|
|
context._plugin_context, l2p_id)
|
|
name = APIC_OWNED + l2p['name']
|
|
added = super(
|
|
AIMMappingDriver,
|
|
self)._use_implicit_subnet_from_subnetpool(
|
|
context, subnet_specifics={'name': name})
|
|
context.add_subnets(subs - set(context.current['subnets']))
|
|
if added:
|
|
self._sync_ptg_subnets(context, l2p)
|
|
l3p = self._get_l3p_for_l2policy(context, l2p_id)
|
|
for r in l3p['routers']:
|
|
self._attach_router_to_subnets(context._plugin_context,
|
|
r, added)
|
|
|
|
def _create_implicit_contracts(self, context, l3p):
|
|
self._process_contracts_for_default_epg(context, l3p)
|
|
|
|
def _configure_contracts_for_default_epg(self, context, l3p, epg_dn):
|
|
self._process_contracts_for_default_epg(
|
|
context, l3p, epg_dn=epg_dn, create=False, delete=False)
|
|
|
|
def _delete_implicit_contracts(self, context, l3p):
|
|
self._process_contracts_for_default_epg(
|
|
context, l3p, epg_dn=None, create=False, delete=True)
|
|
|
|
def _get_implicit_contracts_for_default_epg(
|
|
self, context, l3p, epg_dn):
|
|
return self._process_contracts_for_default_epg(
|
|
context, l3p, epg_dn=epg_dn, get=True)
|
|
|
|
def _process_contracts_for_default_epg(
|
|
self, context, l3p, epg_dn=None, create=True, delete=False, get=False):
|
|
# get=True overrides the create and delete cases, and returns a dict
|
|
# with the Contracts, ContractSubjects, Filters, and FilterEntries
|
|
# for the default EPG
|
|
# create=True, delete=False means create everything and add Contracts
|
|
# to the default EPG
|
|
# create=False, delete=False means only add Contracts to the default
|
|
# EPG
|
|
# create=False, delete=True means only remove Contracts from the
|
|
# default EPG and delete them
|
|
# create=True, delete=True is not a valid combination
|
|
if create and delete:
|
|
LOG.error("Incorrect use of internal method "
|
|
"_process_contracts_for_default_epg(), create and "
|
|
"delete cannot be True at the same time")
|
|
raise
|
|
session = context._plugin_context.session
|
|
aim_ctx = aim_context.AimContext(session)
|
|
|
|
# Infra Services' FilterEntries and attributes
|
|
infra_entries = alib.get_service_contract_filter_entries()
|
|
# ARP FilterEntry and attributes
|
|
arp_entries = alib.get_arp_filter_entry()
|
|
contracts = {alib.SERVICE_PREFIX: infra_entries,
|
|
alib.IMPLICIT_PREFIX: arp_entries}
|
|
|
|
for contract_name_prefix, entries in six.iteritems(contracts):
|
|
contract_name = self.name_mapper.l3_policy(
|
|
session, l3p['id'], prefix=contract_name_prefix)
|
|
# Create Contract (one per l3_policy)
|
|
aim_contract = aim_resource.Contract(
|
|
tenant_name=self._aim_tenant_name(
|
|
session, l3p['tenant_id'], aim_resource.Contract),
|
|
name=contract_name, display_name=contract_name)
|
|
|
|
if get:
|
|
aim_resources = {}
|
|
aim_resources[FILTERS] = []
|
|
aim_resources[FILTER_ENTRIES] = []
|
|
aim_resources[CONTRACT_SUBJECTS] = []
|
|
contract_fetched = self.aim.get(aim_ctx, aim_contract)
|
|
aim_resources[CONTRACTS] = [contract_fetched]
|
|
else:
|
|
if create:
|
|
self.aim.create(aim_ctx, aim_contract, overwrite=True)
|
|
|
|
if not delete and epg_dn:
|
|
aim_epg = self.aim.get(
|
|
aim_ctx, aim_resource.EndpointGroup.from_dn(epg_dn))
|
|
# Add Contracts to the default EPG
|
|
if contract_name_prefix == alib.IMPLICIT_PREFIX:
|
|
# Default EPG provides and consumes ARP Contract
|
|
self._add_contracts_for_epg(
|
|
aim_ctx, aim_epg,
|
|
provided_contracts=[contract_name],
|
|
consumed_contracts=[contract_name])
|
|
else:
|
|
# Default EPG provides Infra Services' Contract
|
|
self._add_contracts_for_epg(
|
|
aim_ctx, aim_epg,
|
|
provided_contracts=[contract_name])
|
|
continue
|
|
|
|
filter_names = []
|
|
for k, v in six.iteritems(entries):
|
|
filter_name = self.name_mapper.l3_policy(
|
|
session, l3p['id'],
|
|
prefix=''.join([contract_name_prefix, k, '-']))
|
|
# Create Filter (one per l3_policy)
|
|
aim_filter = aim_resource.Filter(
|
|
tenant_name=self._aim_tenant_name(
|
|
session, l3p['tenant_id'], aim_resource.Filter),
|
|
name=filter_name, display_name=filter_name)
|
|
if get:
|
|
filter_fetched = self.aim.get(aim_ctx, aim_filter)
|
|
aim_resources[FILTERS].append(filter_fetched)
|
|
aim_filter_entry = self._aim_filter_entry(
|
|
session, aim_filter, k,
|
|
alib.map_to_aim_filter_entry(v))
|
|
entry_fetched = self.aim.get(aim_ctx, aim_filter_entry)
|
|
aim_resources[FILTER_ENTRIES].append(entry_fetched)
|
|
else:
|
|
if create:
|
|
self.aim.create(aim_ctx, aim_filter, overwrite=True)
|
|
# Create FilterEntries (one per l3_policy) and
|
|
# associate with Filter
|
|
self._create_aim_filter_entry(
|
|
session, aim_ctx, aim_filter, k, v, overwrite=True)
|
|
filter_names.append(aim_filter.name)
|
|
if delete:
|
|
self._delete_aim_filter_entries(aim_ctx, aim_filter)
|
|
self.aim.delete(aim_ctx, aim_filter)
|
|
if get:
|
|
aim_contract_subject = self._aim_contract_subject(aim_contract)
|
|
subject_fetched = self.aim.get(aim_ctx, aim_contract_subject)
|
|
aim_resources[CONTRACT_SUBJECTS].append(subject_fetched)
|
|
return aim_resources
|
|
else:
|
|
if create:
|
|
# Create ContractSubject (one per l3_policy) with relevant
|
|
# Filters, and associate with Contract
|
|
self._populate_aim_contract_subject_by_filters(
|
|
context, aim_contract, bi_filters=filter_names)
|
|
if delete:
|
|
self._delete_aim_contract_subject(aim_ctx, aim_contract)
|
|
self.aim.delete(aim_ctx, aim_contract)
|
|
|
|
def _add_implicit_svc_contracts_to_epg(self, context, l2p, aim_epg):
|
|
session = context._plugin_context.session
|
|
aim_ctx = aim_context.AimContext(session)
|
|
implicit_contract_name = self.name_mapper.l3_policy(
|
|
session, l2p['l3_policy_id'], prefix=alib.IMPLICIT_PREFIX)
|
|
service_contract_name = self.name_mapper.l3_policy(
|
|
session, l2p['l3_policy_id'], prefix=alib.SERVICE_PREFIX)
|
|
self._add_contracts_for_epg(aim_ctx, aim_epg,
|
|
provided_contracts=[implicit_contract_name],
|
|
consumed_contracts=[implicit_contract_name, service_contract_name])
|
|
|
|
def _add_contracts_for_epg(self, aim_ctx, aim_epg, provided_contracts=None,
|
|
consumed_contracts=None):
|
|
if provided_contracts:
|
|
aim_epg.provided_contract_names += provided_contracts
|
|
|
|
if consumed_contracts:
|
|
aim_epg.consumed_contract_names += consumed_contracts
|
|
self.aim.create(aim_ctx, aim_epg, overwrite=True)
|
|
|
|
def _merge_gbp_status(self, gbp_resource_list):
|
|
merged_status = gp_const.STATUS_ACTIVE
|
|
for gbp_resource in gbp_resource_list:
|
|
if gbp_resource['status'] == gp_const.STATUS_BUILD:
|
|
merged_status = gp_const.STATUS_BUILD
|
|
elif gbp_resource['status'] == gp_const.STATUS_ERROR:
|
|
merged_status = gp_const.STATUS_ERROR
|
|
break
|
|
return merged_status
|
|
|
|
def _map_ml2plus_status(self, sync_status):
|
|
if not sync_status:
|
|
# REVIST(Sumit)
|
|
return gp_const.STATUS_BUILD
|
|
if sync_status == cisco_apic.SYNC_ERROR:
|
|
return gp_const.STATUS_ERROR
|
|
elif sync_status == cisco_apic.SYNC_BUILD:
|
|
return gp_const.STATUS_BUILD
|
|
else:
|
|
return gp_const.STATUS_ACTIVE
|
|
|
|
def _process_subnets_for_ptg_delete(self, context, ptg, l2p_id):
|
|
session = context._plugin_context.session
|
|
plugin_context = context._plugin_context
|
|
subnet_ids = [assoc['subnet_id'] for assoc in ptg['subnets']]
|
|
|
|
context._plugin._remove_subnets_from_policy_target_group(
|
|
plugin_context, ptg['id'])
|
|
if subnet_ids:
|
|
for subnet_id in subnet_ids:
|
|
# clean-up subnet if this is the last PTG using the L2P
|
|
if not context._plugin._get_ptgs_for_subnet(
|
|
plugin_context, subnet_id):
|
|
if l2p_id:
|
|
l3p = self._get_l3p_for_l2policy(context, l2p_id)
|
|
for router_id in l3p['routers']:
|
|
# If the subnet interface for this router has
|
|
# already been removed (say manually), the
|
|
# call to Neutron's remove_router_interface
|
|
# will cause the transaction to exit immediately.
|
|
# To avoid this, we first check if this subnet
|
|
# still has an interface on this router.
|
|
if self._get_router_interface_port_by_subnet(
|
|
plugin_context, router_id, subnet_id):
|
|
with session.begin(nested=True):
|
|
self._detach_router_from_subnets(
|
|
plugin_context, router_id, [subnet_id])
|
|
self._cleanup_subnet(plugin_context, subnet_id)
|
|
|
|
def _map_aim_status(self, session, aim_resource_obj):
|
|
# Note that this implementation assumes that this driver
|
|
# is the only policy driver configured, and no merging
|
|
# with any previous status is required.
|
|
aim_ctx = aim_context.AimContext(session)
|
|
aim_status = self.aim.get_status(
|
|
aim_ctx, aim_resource_obj, create_if_absent=False)
|
|
if not aim_status:
|
|
# REVIST(Sumit)
|
|
return gp_const.STATUS_BUILD
|
|
if aim_status.is_error():
|
|
return gp_const.STATUS_ERROR
|
|
elif aim_status.is_build():
|
|
return gp_const.STATUS_BUILD
|
|
else:
|
|
return gp_const.STATUS_ACTIVE
|
|
|
|
def _merge_aim_status(self, session, aim_resource_obj_list):
|
|
# Note that this implementation assumes that this driver
|
|
# is the only policy driver configured, and no merging
|
|
# with any previous status is required.
|
|
# When merging states of multiple AIM objects, the status
|
|
# priority is ERROR > BUILD > ACTIVE.
|
|
merged_status = gp_const.STATUS_ACTIVE
|
|
for aim_obj in aim_resource_obj_list:
|
|
status = self._map_aim_status(session, aim_obj)
|
|
if status != gp_const.STATUS_ACTIVE:
|
|
merged_status = status
|
|
if merged_status == gp_const.STATUS_ERROR:
|
|
break
|
|
return merged_status
|
|
|
|
def _db_plugin(self, plugin_obj):
|
|
return super(gbp_plugin.GroupPolicyPlugin, plugin_obj)
|
|
|
|
def _get_aim_context(self, context):
|
|
if hasattr(context, 'session'):
|
|
session = context.session
|
|
else:
|
|
session = context._plugin_context.session
|
|
return aim_context.AimContext(session)
|
|
|
|
def _is_port_promiscuous(self, plugin_context, port):
|
|
pt = self._port_id_to_pt(plugin_context, port['id'])
|
|
if (pt and pt.get('cluster_id') and
|
|
pt.get('cluster_id') != pt['id']):
|
|
master = self._get_policy_target(plugin_context, pt['cluster_id'])
|
|
if master.get('group_default_gateway'):
|
|
return True
|
|
if (port['device_owner'] in PROMISCUOUS_TYPES or
|
|
port['name'].endswith(PROMISCUOUS_SUFFIX) or
|
|
(pt and pt.get('group_default_gateway'))):
|
|
return True
|
|
if not port.get('port_security_enabled', True):
|
|
return True
|
|
return False
|
|
|
|
def _is_dhcp_optimized(self, plugin_context, port):
|
|
return self.aim_mech_driver.enable_dhcp_opt
|
|
|
|
def _is_metadata_optimized(self, plugin_context, port):
|
|
return self.aim_mech_driver.enable_metadata_opt
|
|
|
|
def _set_dhcp_lease_time(self, details):
|
|
if self.aim_mech_driver.apic_optimized_dhcp_lease_time > 0:
|
|
details['dhcp_lease_time'] = (
|
|
self.aim_mech_driver.apic_optimized_dhcp_lease_time)
|
|
|
|
def _get_port_epg(self, plugin_context, port):
|
|
ptg, pt = self._port_id_to_ptg(plugin_context, port['id'])
|
|
if ptg:
|
|
return self._get_aim_endpoint_group(plugin_context.session, ptg)
|
|
else:
|
|
# Return default EPG based on network
|
|
network = self._get_network(plugin_context, port['network_id'])
|
|
epg = self._get_aim_default_endpoint_group(plugin_context.session,
|
|
network)
|
|
if not epg:
|
|
# Something is wrong, default EPG doesn't exist.
|
|
# TODO(ivar): should rise an exception
|
|
LOG.error("Default EPG doesn't exist for "
|
|
"port %s", port['id'])
|
|
return epg
|
|
|
|
def _get_subnet_details(self, plugin_context, port, details):
|
|
# L2P might not exist for a pure Neutron port
|
|
l2p = self._network_id_to_l2p(plugin_context, port['network_id'])
|
|
# TODO(ivar): support shadow network
|
|
# if not l2p and self._ptg_needs_shadow_network(context, ptg):
|
|
# l2p = self._get_l2_policy(context._plugin_context,
|
|
# ptg['l2_policy_id'])
|
|
|
|
subnets = self._get_subnets(
|
|
plugin_context,
|
|
filters={'id': [ip['subnet_id'] for ip in port['fixed_ips']]})
|
|
for subnet in subnets:
|
|
dhcp_ports = {}
|
|
subnet_dhcp_ips = set()
|
|
for dhcp_port in self._get_ports(
|
|
plugin_context,
|
|
filters={
|
|
'network_id': [subnet['network_id']],
|
|
'device_owner': [n_constants.DEVICE_OWNER_DHCP]}):
|
|
dhcp_ips = set([x['ip_address'] for x in dhcp_port['fixed_ips']
|
|
if x['subnet_id'] == subnet['id']])
|
|
dhcp_ports.setdefault(dhcp_port['mac_address'], list(dhcp_ips))
|
|
subnet_dhcp_ips |= dhcp_ips
|
|
subnet_dhcp_ips = list(subnet_dhcp_ips)
|
|
if not subnet['dns_nameservers']:
|
|
# Use DHCP namespace port IP
|
|
subnet['dns_nameservers'] = subnet_dhcp_ips
|
|
# Set Default & Metadata routes if needed
|
|
default_route = metadata_route = {}
|
|
if subnet['ip_version'] == 4:
|
|
for route in subnet['host_routes']:
|
|
if route['destination'] == '0.0.0.0/0':
|
|
default_route = route
|
|
if route['destination'] == dhcp.METADATA_DEFAULT_CIDR:
|
|
metadata_route = route
|
|
if l2p and not l2p['inject_default_route']:
|
|
# In this case we do not want to send the default route
|
|
# and the metadata route. We also do not want to send
|
|
# the gateway_ip for the subnet.
|
|
if default_route:
|
|
subnet['host_routes'].remove(default_route)
|
|
if metadata_route:
|
|
subnet['host_routes'].remove(metadata_route)
|
|
del subnet['gateway_ip']
|
|
else:
|
|
# Set missing routes
|
|
if not default_route:
|
|
subnet['host_routes'].append(
|
|
{'destination': '0.0.0.0/0',
|
|
'nexthop': subnet['gateway_ip']})
|
|
optimized = self._is_metadata_optimized(plugin_context,
|
|
port)
|
|
# REVISIT: We need to decide if we should provide
|
|
# host-routes for all of the DHCP agents. For now
|
|
# use the first DHCP agent in our list for the
|
|
# metadata host-route next-hop IPs
|
|
if not metadata_route and dhcp_ports and (
|
|
not optimized or (optimized and not default_route)):
|
|
for ip in dhcp_ports[dhcp_ports.keys()[0]]:
|
|
subnet['host_routes'].append(
|
|
{'destination': dhcp.METADATA_DEFAULT_CIDR,
|
|
'nexthop': ip})
|
|
subnet['dhcp_server_ips'] = subnet_dhcp_ips
|
|
if dhcp_ports:
|
|
subnet['dhcp_server_ports'] = dhcp_ports
|
|
return subnets
|
|
|
|
def _send_port_update_notification(self, plugin_context, port):
|
|
self.aim_mech_driver._notify_port_update(plugin_context, port)
|
|
|
|
def _get_aap_details(self, plugin_context, port, details):
|
|
aaps = port['allowed_address_pairs']
|
|
# Set the correct address ownership for this port
|
|
owned_addresses = self._get_owned_addresses(
|
|
plugin_context, port['id'])
|
|
for allowed in aaps:
|
|
if allowed['ip_address'] in owned_addresses:
|
|
# Signal the agent that this particular address is active
|
|
# on its port
|
|
allowed['active'] = True
|
|
return aaps
|
|
|
|
def _get_port_vrf(self, plugin_context, port, details):
|
|
net_db = self._core_plugin._get_network(plugin_context,
|
|
port['network_id'])
|
|
return self.aim_mech_driver.get_vrf_for_network(
|
|
plugin_context.session, net_db)
|
|
|
|
def _get_vrf_subnets(self, plugin_context, vrf_tenant_name, vrf_name,
|
|
details):
|
|
session = plugin_context.session
|
|
result = []
|
|
# get all subnets of the specified VRF
|
|
with session.begin(subtransactions=True):
|
|
# Find VRF's address_scope first
|
|
address_scope_ids = (
|
|
self.aim_mech_driver._get_address_scope_ids_for_vrf(
|
|
session,
|
|
aim_resource.VRF(tenant_name=vrf_tenant_name,
|
|
name=vrf_name)))
|
|
if address_scope_ids:
|
|
for address_scope_id in address_scope_ids:
|
|
subnetpools = self._get_subnetpools(
|
|
plugin_context,
|
|
filters={'address_scope_id': [address_scope_id]})
|
|
for pool in subnetpools:
|
|
result.extend(pool['prefixes'])
|
|
else:
|
|
aim_ctx = aim_context.AimContext(db_session=session)
|
|
if vrf_tenant_name != md.COMMON_TENANT_NAME:
|
|
bds = self.aim.find(aim_ctx, aim_resource.BridgeDomain,
|
|
tenant_name=vrf_tenant_name,
|
|
vrf_name=vrf_name)
|
|
else:
|
|
bds = self.aim.find(aim_ctx, aim_resource.BridgeDomain,
|
|
vrf_name=vrf_name)
|
|
other_vrfs = self.aim.find(aim_ctx, aim_resource.VRF,
|
|
name=vrf_name)
|
|
bd_tenants = set([x.tenant_name for x in bds])
|
|
vrf_tenants = set([x.tenant_name for x in other_vrfs
|
|
if x.tenant_name != vrf_tenant_name])
|
|
valid_tenants = bd_tenants - vrf_tenants
|
|
# Only keep BDs that don't have a VRF with that name
|
|
# already
|
|
bds = [x for x in bds if x.tenant_name in valid_tenants]
|
|
# Retrieve subnets from BDs
|
|
net_ids = []
|
|
for bd in bds:
|
|
try:
|
|
net_ids.append(self.name_mapper.reverse_network(
|
|
session, bd.name))
|
|
except md_exc.InternalError:
|
|
# Check if BD maps to an external network
|
|
ext_ids = self.aim_mech_driver.get_network_ids_for_bd(
|
|
session, bd)
|
|
net_ids.extend(ext_ids)
|
|
# If no external network is found, we ignore reverse
|
|
# mapping failures because there may be APIC BDs in the
|
|
# concerned VRF that Neutron is unaware of. This is
|
|
# especially true for VRFs in the common tenant.
|
|
if net_ids:
|
|
subnets = self._get_subnets(plugin_context,
|
|
{'network_id': net_ids})
|
|
result = [x['cidr'] for x in subnets]
|
|
return result
|
|
|
|
def _get_segmentation_labels(self, plugin_context, port, details):
|
|
pt = self._port_id_to_pt(plugin_context, port['id'])
|
|
if self.apic_segmentation_label_driver and pt and (
|
|
'segmentation_labels' in pt):
|
|
return pt['segmentation_labels']
|
|
|
|
def _get_nat_details(self, plugin_context, port, host, details):
|
|
""" Add information about IP mapping for DNAT/SNAT """
|
|
|
|
fips = []
|
|
ipms = []
|
|
host_snat_ips = []
|
|
|
|
# Find all external networks connected to the port.
|
|
# Handle them depending on whether there is a FIP on that
|
|
# network.
|
|
ext_nets = []
|
|
|
|
port_sn = set([x['subnet_id'] for x in port['fixed_ips']])
|
|
router_intf_ports = self._get_ports(
|
|
plugin_context,
|
|
filters={'device_owner': [n_constants.DEVICE_OWNER_ROUTER_INTF],
|
|
'fixed_ips': {'subnet_id': port_sn}})
|
|
if router_intf_ports:
|
|
routers = self._get_routers(
|
|
plugin_context,
|
|
filters={'device_id': [x['device_id']
|
|
for x in router_intf_ports]})
|
|
ext_nets = self._get_networks(
|
|
plugin_context,
|
|
filters={'id': [r['external_gateway_info']['network_id']
|
|
for r in routers
|
|
if r.get('external_gateway_info')]})
|
|
if not ext_nets:
|
|
return fips, ipms, host_snat_ips
|
|
|
|
# Handle FIPs of owned addresses - find other ports in the
|
|
# network whose address is owned by this port.
|
|
# If those ports have FIPs, then steal them.
|
|
fips_filter = [port['id']]
|
|
active_addrs = [a['ip_address']
|
|
for a in details['allowed_address_pairs']
|
|
if a.get('active')]
|
|
if active_addrs:
|
|
others = self._get_ports(
|
|
plugin_context,
|
|
filters={'network_id': [port['network_id']],
|
|
'fixed_ips': {'ip_address': active_addrs}})
|
|
fips_filter.extend([p['id'] for p in others])
|
|
fips = self._get_fips(plugin_context,
|
|
filters={'port_id': fips_filter})
|
|
|
|
for ext_net in ext_nets:
|
|
dn = ext_net.get(cisco_apic.DIST_NAMES, {}).get(
|
|
cisco_apic.EXTERNAL_NETWORK)
|
|
ext_net_epg_dn = ext_net.get(cisco_apic.DIST_NAMES, {}).get(
|
|
cisco_apic.EPG)
|
|
if not dn or not ext_net_epg_dn:
|
|
continue
|
|
if 'distributed' != ext_net.get(cisco_apic.NAT_TYPE):
|
|
continue
|
|
|
|
# TODO(amitbose) Handle per-tenant NAT EPG
|
|
ext_net_epg = aim_resource.EndpointGroup.from_dn(ext_net_epg_dn)
|
|
|
|
fips_in_ext_net = [fip for fip in fips
|
|
if fip['floating_network_id'] == ext_net['id']]
|
|
if not fips_in_ext_net:
|
|
ext_segment_name = dn.replace('/', ':')
|
|
ipms.append({'external_segment_name': ext_segment_name,
|
|
'nat_epg_name': ext_net_epg.name,
|
|
'nat_epg_app_profile': (
|
|
ext_net_epg.app_profile_name),
|
|
'nat_epg_tenant': ext_net_epg.tenant_name})
|
|
# TODO(amitbose) Set next_hop_ep_tenant for per-tenant NAT EPG
|
|
if host:
|
|
snat_ip = self.aim_mech_driver.get_or_allocate_snat_ip(
|
|
plugin_context, host, ext_net)
|
|
if snat_ip:
|
|
snat_ip['external_segment_name'] = ext_segment_name
|
|
host_snat_ips.append(snat_ip)
|
|
else:
|
|
for f in fips_in_ext_net:
|
|
f['nat_epg_name'] = ext_net_epg.name
|
|
f['nat_epg_app_profile'] = ext_net_epg.app_profile_name
|
|
f['nat_epg_tenant'] = ext_net_epg.tenant_name
|
|
return fips, ipms, host_snat_ips
|
|
|
|
def _get_vrf_by_dn(self, context, vrf_dn):
|
|
aim_context = self._get_aim_context(context)
|
|
vrf = self.aim.get(
|
|
aim_context, aim_resource.VRF.from_dn(vrf_dn))
|
|
return vrf
|
|
|
|
def _check_l3policy_ext_segment(self, context, l3policy):
|
|
if l3policy['external_segments']:
|
|
for allocations in l3policy['external_segments'].values():
|
|
if len(allocations) > 1:
|
|
raise alib.OnlyOneAddressIsAllowedPerExternalSegment()
|
|
# if NAT is disabled, allow only one L3P per ES
|
|
ess = context._plugin.get_external_segments(
|
|
context._plugin_context,
|
|
filters={'id': l3policy['external_segments'].keys()})
|
|
for es in ess:
|
|
ext_net = self._ext_segment_2_ext_network(context, es)
|
|
if (ext_net and
|
|
ext_net.get(cisco_apic.NAT_TYPE) in
|
|
('distributed', 'edge')):
|
|
continue
|
|
if [x for x in es['l3_policies'] if x != l3policy['id']]:
|
|
raise alib.OnlyOneL3PolicyIsAllowedPerExternalSegment()
|
|
|
|
def _check_external_policy(self, context, ep):
|
|
if ep.get('shared', False):
|
|
# REVISIT(amitbose) This could be relaxed
|
|
raise alib.SharedExternalPolicyUnsupported()
|
|
ess = context._plugin.get_external_segments(
|
|
context._plugin_context,
|
|
filters={'id': ep['external_segments']})
|
|
for es in ess:
|
|
other_eps = context._plugin.get_external_policies(
|
|
context._plugin_context,
|
|
filters={'id': es['external_policies'],
|
|
'tenant_id': [ep['tenant_id']]})
|
|
if [x for x in other_eps if x['id'] != ep['id']]:
|
|
raise alib.MultipleExternalPoliciesForL3Policy()
|
|
|
|
def _get_l3p_subnets(self, context, l3policy):
|
|
l2p_sn = []
|
|
for l2p_id in l3policy['l2_policies']:
|
|
l2p_sn.extend(self._get_l2p_subnets(context, l2p_id))
|
|
return l2p_sn
|
|
|
|
def _ext_segment_2_ext_network(self, context, ext_segment):
|
|
subnet = self._get_subnet(context._plugin_context,
|
|
ext_segment['subnet_id'])
|
|
if subnet:
|
|
return self._get_network(context._plugin_context,
|
|
subnet['network_id'])
|
|
|
|
def _map_ext_segment_to_routers(self, context, ext_segments,
|
|
routers):
|
|
net_to_router = {r['external_gateway_info']['network_id']: r
|
|
for r in routers
|
|
if r.get('external_gateway_info')}
|
|
result = {}
|
|
for es in ext_segments:
|
|
sn = self._get_subnet(context._plugin_context, es['subnet_id'])
|
|
router = net_to_router.get(sn['network_id']) if sn else None
|
|
if router:
|
|
result[es['id']] = router
|
|
return result
|
|
|
|
def _plug_l3p_routers_to_ext_segment(self, context, l3policy,
|
|
ext_seg_info):
|
|
plugin_context = context._plugin_context
|
|
es_list = self._get_external_segments(plugin_context,
|
|
filters={'id': ext_seg_info.keys()})
|
|
l3p_subs = self._get_l3p_subnets(context, l3policy)
|
|
|
|
# REVISIT: We are not re-using the first router created
|
|
# implicitly for the L3Policy (or provided explicitly by the
|
|
# user). Consider using that for the first external segment
|
|
|
|
for es in es_list:
|
|
router_id = self._use_implicit_router(context,
|
|
router_name=l3policy['name'] + '-' + es['name'])
|
|
router = self._create_router_gw_for_external_segment(
|
|
context._plugin_context, es, ext_seg_info, router_id)
|
|
if not ext_seg_info[es['id']] or not ext_seg_info[es['id']][0]:
|
|
# Update L3P assigned address
|
|
efi = router['external_gateway_info']['external_fixed_ips']
|
|
assigned_ips = [x['ip_address'] for x in efi
|
|
if x['subnet_id'] == es['subnet_id']]
|
|
context.set_external_fixed_ips(es['id'], assigned_ips)
|
|
if es['external_policies']:
|
|
ext_policy = self._get_external_policies(plugin_context,
|
|
filters={'id': es['external_policies'],
|
|
'tenant_id': [l3policy['tenant_id']]})
|
|
if ext_policy:
|
|
self._set_router_ext_contracts(context, router_id,
|
|
ext_policy[0])
|
|
self._attach_router_to_subnets(plugin_context, router_id, l3p_subs)
|
|
|
|
def _unplug_l3p_routers_from_ext_segment(self, context, l3policy,
|
|
ext_seg_ids):
|
|
plugin_context = context._plugin_context
|
|
es_list = self._get_external_segments(plugin_context,
|
|
filters={'id': ext_seg_ids})
|
|
routers = self._get_routers(plugin_context,
|
|
filters={'id': l3policy['routers']})
|
|
es_2_router = self._map_ext_segment_to_routers(context, es_list,
|
|
routers)
|
|
for r in es_2_router.values():
|
|
router_subs = self._get_router_interface_subnets(plugin_context,
|
|
r['id'])
|
|
self._detach_router_from_subnets(plugin_context, r['id'],
|
|
router_subs)
|
|
context.remove_router(r['id'])
|
|
self._cleanup_router(plugin_context, r['id'])
|
|
|
|
def _get_router_interface_subnets(self, plugin_context, router_id):
|
|
router_ports = self._get_ports(plugin_context,
|
|
filters={'device_owner': [n_constants.DEVICE_OWNER_ROUTER_INTF],
|
|
'device_id': [router_id]})
|
|
return set(y['subnet_id']
|
|
for x in router_ports for y in x['fixed_ips'])
|
|
|
|
def _get_router_interface_port_by_subnet(self, plugin_context,
|
|
router_id, subnet_id):
|
|
router_ports = self._get_ports(plugin_context,
|
|
filters={'device_owner': [n_constants.DEVICE_OWNER_ROUTER_INTF],
|
|
'device_id': [router_id],
|
|
'fixed_ips': {'subnet_id': [subnet_id]}})
|
|
return (router_ports or [None])[0]
|
|
|
|
def _attach_router_to_subnets(self, plugin_context, router_id, subs):
|
|
# On account of sharing configuration, the router and subnets might
|
|
# be in different tenants, hence we need to use admin context
|
|
plugin_context = plugin_context.elevated()
|
|
rtr_sn = self._get_router_interface_subnets(plugin_context, router_id)
|
|
for subnet in subs:
|
|
if subnet['id'] in rtr_sn: # already attached
|
|
continue
|
|
gw_port = self._get_ports(plugin_context,
|
|
filters={'fixed_ips': {'ip_address': [subnet['gateway_ip']],
|
|
'subnet_id': [subnet['id']]}})
|
|
if gw_port:
|
|
# Gateway port is in use, create new interface port
|
|
attrs = {'tenant_id': subnet['tenant_id'],
|
|
'network_id': subnet['network_id'],
|
|
'fixed_ips': [{'subnet_id': subnet['id']}],
|
|
'device_id': '',
|
|
'device_owner': n_constants.DEVICE_OWNER_ROUTER_INTF,
|
|
'mac_address': n_constants.ATTR_NOT_SPECIFIED,
|
|
'name': '%s-%s' % (router_id, subnet['id']),
|
|
'admin_state_up': True}
|
|
try:
|
|
intf_port = self._create_port(plugin_context, attrs)
|
|
except n_exc.NeutronException:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception('Failed to create explicit router '
|
|
'interface port in subnet '
|
|
'%(subnet)s',
|
|
{'subnet': subnet['id']})
|
|
interface_info = {'port_id': intf_port['id'],
|
|
NO_VALIDATE: True}
|
|
try:
|
|
self._add_router_interface(plugin_context, router_id,
|
|
interface_info)
|
|
except n_exc.BadRequest:
|
|
self._delete_port(plugin_context, intf_port['id'])
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception('Attaching router %(router)s to '
|
|
'%(subnet)s with explicit port '
|
|
'%(port) failed',
|
|
{'subnet': subnet['id'],
|
|
'router': router_id,
|
|
'port': intf_port['id']})
|
|
else:
|
|
self._plug_router_to_subnet(plugin_context, subnet['id'],
|
|
router_id)
|
|
|
|
def _plug_router_to_subnet(self, plugin_context, subnet_id, router_id):
|
|
interface_info = {'subnet_id': subnet_id,
|
|
NO_VALIDATE: True}
|
|
if router_id:
|
|
try:
|
|
self._add_router_interface(plugin_context, router_id,
|
|
interface_info)
|
|
except n_exc.BadRequest as e:
|
|
LOG.exception("Adding subnet to router failed, exception:"
|
|
"%s", e)
|
|
raise exc.GroupPolicyInternalError()
|
|
|
|
def _detach_router_from_subnets(self, plugin_context, router_id, sn_ids):
|
|
for subnet_id in sn_ids:
|
|
# Use admin context because router and subnet may be in
|
|
# different tenants
|
|
self._remove_router_interface(plugin_context.elevated(),
|
|
router_id,
|
|
{'subnet_id': subnet_id})
|
|
|
|
def _set_router_ext_contracts(self, context, router_id, ext_policy):
|
|
session = context._plugin_context.session
|
|
prov = []
|
|
cons = []
|
|
if ext_policy:
|
|
prov = self._get_aim_contract_names(session,
|
|
ext_policy['provided_policy_rule_sets'])
|
|
cons = self._get_aim_contract_names(session,
|
|
ext_policy['consumed_policy_rule_sets'])
|
|
attr = {cisco_apic_l3.EXTERNAL_PROVIDED_CONTRACTS: prov,
|
|
cisco_apic_l3.EXTERNAL_CONSUMED_CONTRACTS: cons}
|
|
self._update_router(context._plugin_context, router_id, attr)
|
|
|
|
def _get_ext_policy_routers(self, context, ext_policy, ext_seg_ids):
|
|
plugin_context = context._plugin_context
|
|
es = self._get_external_segments(plugin_context,
|
|
filters={'id': ext_seg_ids})
|
|
subs = self._get_subnets(context._plugin_context,
|
|
filters={'id': [e['subnet_id'] for e in es]})
|
|
ext_net = {s['network_id'] for s in subs}
|
|
l3ps = set([l3p for e in es for l3p in e['l3_policies']])
|
|
l3ps = self._get_l3_policies(plugin_context,
|
|
filters={'id': l3ps,
|
|
'tenant_id': [ext_policy['tenant_id']]})
|
|
routers = self._get_routers(plugin_context,
|
|
filters={'id': [r for l in l3ps for r in l['routers']]})
|
|
return [r['id'] for r in routers
|
|
if (r['external_gateway_info'] or {}).get('network_id') in ext_net]
|
|
|
|
def _get_auto_ptg_name(self, l2p):
|
|
return AUTO_PTG_NAME_PREFIX % l2p['id']
|
|
|
|
def _get_auto_ptg_id(self, l2p_id):
|
|
if l2p_id:
|
|
return AUTO_PTG_ID_PREFIX % hashlib.md5(l2p_id).hexdigest()
|
|
|
|
def _is_auto_ptg(self, ptg):
|
|
return ptg['id'].startswith(AUTO_PTG_PREFIX)
|
|
|
|
def _get_policy_enforcement_pref(self, ptg):
|
|
if ptg['intra_ptg_allow']:
|
|
policy_enforcement_pref = UNENFORCED
|
|
else:
|
|
policy_enforcement_pref = ENFORCED
|
|
return policy_enforcement_pref
|
|
|
|
def _map_policy_enforcement_pref(self, epg):
|
|
if epg.policy_enforcement_pref == UNENFORCED:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def _get_bd_by_dn(self, context, bd_dn):
|
|
aim_context = self._get_aim_context(context)
|
|
bd = self.aim.get(
|
|
aim_context, aim_resource.BridgeDomain.from_dn(bd_dn))
|
|
return bd
|
|
|
|
def _get_epg_by_dn(self, context, epg_dn):
|
|
aim_context = self._get_aim_context(context)
|
|
epg = self.aim.get(
|
|
aim_context, aim_resource.EndpointGroup.from_dn(epg_dn))
|
|
return epg
|
|
|
|
def _get_epg_name_from_dn(self, context, epg_dn):
|
|
aim_context = self._get_aim_context(context)
|
|
default_epg_name = self.aim.get(
|
|
aim_context, aim_resource.EndpointGroup.from_dn(epg_dn)).name
|
|
return default_epg_name
|
|
|
|
def apic_epg_name_for_policy_target_group(self, session, ptg_id,
|
|
name=None, context=None):
|
|
if not context:
|
|
context = gbp_utils.get_current_context()
|
|
# get_network can do a DB write, hence we use a writer
|
|
with db_api.context_manager.writer.using(context):
|
|
ptg_db = session.query(gpmdb.PolicyTargetGroupMapping).filter_by(
|
|
id=ptg_id).first()
|
|
if ptg_db and self._is_auto_ptg(ptg_db):
|
|
l2p_db = session.query(gpmdb.L2PolicyMapping).filter_by(
|
|
id=ptg_db['l2_policy_id']).first()
|
|
network_id = l2p_db['network_id']
|
|
admin_context = n_context.get_admin_context()
|
|
net = self._get_network(admin_context, network_id)
|
|
default_epg_dn = net[cisco_apic.DIST_NAMES][cisco_apic.EPG]
|
|
default_epg_name = self._get_epg_name_from_dn(
|
|
admin_context, default_epg_dn)
|
|
return default_epg_name
|
|
else:
|
|
return ptg_id
|
|
|
|
def apic_ap_name_for_application_policy_group(self, session, apg_id):
|
|
if apg_id:
|
|
return self.name_mapper.application_policy_group(
|
|
session, apg_id)
|
|
else:
|
|
return self.aim_mech_driver.ap_name
|
|
|
|
def _get_default_security_group(self, plugin_context, ptg_id,
|
|
tenant_id):
|
|
filters = {'name': [DEFAULT_SG_NAME], 'tenant_id': [tenant_id]}
|
|
default_group = self._get_sgs(plugin_context, filters)
|
|
return default_group[0]['id'] if default_group else None
|
|
|
|
def _create_default_security_group(self, plugin_context, tenant_id):
|
|
# Allow all
|
|
sg_id = self._get_default_security_group(plugin_context, '', tenant_id)
|
|
ip_v = [(n_constants.IPv4, '0.0.0.0/0'), (n_constants.IPv6, '::/0')]
|
|
if not sg_id:
|
|
sg_name = DEFAULT_SG_NAME
|
|
sg = self._create_gbp_sg(plugin_context, tenant_id, sg_name,
|
|
description='default GBP security group')
|
|
sg_id = sg['id']
|
|
|
|
for v, g in ip_v:
|
|
self._sg_rule(plugin_context, tenant_id, sg_id,
|
|
'ingress', cidr=g, ethertype=v)
|
|
self._sg_rule(plugin_context, tenant_id, sg_id,
|
|
'egress', cidr=g, ethertype=v)
|
|
|
|
def _use_implicit_port(self, context, subnets=None):
|
|
self._create_default_security_group(context._plugin_context,
|
|
context.current['tenant_id'])
|
|
super(AIMMappingDriver, self)._use_implicit_port(
|
|
context, subnets=subnets)
|
|
|
|
def _handle_create_network_service_policy(self, context):
|
|
self._validate_nat_pool_for_nsp(context)
|
|
self._handle_network_service_policy(context)
|
|
|
|
def _get_prss_for_policy_rules(self, context, pr_ids):
|
|
return [self._get_policy_rule_set(
|
|
context._plugin_context, x['id']) for x in (
|
|
context._plugin_context.session.query(
|
|
gpdb.PolicyRuleSet).join(
|
|
gpdb.PRSToPRAssociation,
|
|
gpdb.PRSToPRAssociation.policy_rule_set_id ==
|
|
gpdb.PolicyRuleSet.id).join(
|
|
gpdb.PolicyRule,
|
|
gpdb.PRSToPRAssociation.policy_rule_id ==
|
|
gpdb.PolicyRule.id).filter(
|
|
gpdb.PolicyRule.id.in_(pr_ids)).all())]
|
|
|
|
def _get_port_mtu(self, context, port):
|
|
if self.advertise_mtu:
|
|
network = self._get_network(context, port['network_id'])
|
|
return network.get('mtu')
|
|
return None
|
|
|
|
def _get_dns_domain(self, context, port):
|
|
network = self._get_network(context, port['network_id'])
|
|
return network.get('dns_domain')
|
|
|
|
def _create_per_l3p_implicit_contracts(self):
|
|
admin_context = n_context.get_admin_context()
|
|
context = type('', (object,), {})()
|
|
context._plugin_context = admin_context
|
|
session = admin_context.session
|
|
aim_ctx = aim_context.AimContext(session)
|
|
contract_name_prefix = alib.get_service_contract_filter_entries(
|
|
).keys()[0]
|
|
l3ps = session.query(gpmdb.L3PolicyMapping).all()
|
|
name_mapper = apic_mapper.APICNameMapper()
|
|
aim_mgr = aim_manager.AimManager()
|
|
self._aim = aim_mgr
|
|
self._name_mapper = name_mapper
|
|
orig_aim_tenant_name = self._aim_tenant_name
|
|
|
|
def _aim_tenant_name(self, session, tenant_id, aim_resource_class=None,
|
|
gbp_resource=None, gbp_obj=None):
|
|
attrs = aim_resource.Tenant(name=md.COMMON_TENANT_NAME,
|
|
display_name=aim_utils.sanitize_display_name(
|
|
'CommonTenant'))
|
|
tenant = aim_mgr.get(aim_ctx, attrs)
|
|
if not tenant:
|
|
tenant = aim_mgr.create(aim_ctx, attrs)
|
|
return md.COMMON_TENANT_NAME
|
|
|
|
self._aim_tenant_name = _aim_tenant_name
|
|
|
|
for l3p in l3ps:
|
|
implicit_contract_name = name_mapper.l3_policy(
|
|
session, l3p['id'], prefix=contract_name_prefix)
|
|
if not aim_mgr.find(
|
|
aim_ctx, aim_resource.Contract,
|
|
name=implicit_contract_name):
|
|
self._create_implicit_contracts(context, l3p)
|
|
|
|
self._aim = None
|
|
self._name_mapper = None
|
|
self._aim_tenant_name = orig_aim_tenant_name
|