[apic_aim] Map neutron resources to AIM, part 3

Implements an L3 service plugin, apic_aim_l3, that, in conjunction
with the apic_aim mechanism driver, maps each Neutron router to an AIM
Contract and ContractSubject whose DNs and status are exposed via
extended attributes similar to those on the core Neutron resources. An
"any" Filter and FilterEntry are created per-tenant, and referenced in
this contract, allowing all traffic from EPGs providing and consuming
this contract to be routed.

The add_router_interface and remove_router_interface methods are stubs
that will be implemented in the next patch set. They will manage the
mapping of router interfaces to AIM Subnets, along with having the
default EPGs associated with those interfaces provide and consume the
router's Contract.

The corresponding GBP policy driver's extension is renamed
apic_aim_gbp for consistency with the apic_aim and apic_aim_l3
extensions at the Neutron level, and all extensions are now in the
gbpservice.neutron.extensions module.

The GBP policy driver's unit tests are updated to account for the
Filter and FilterEntry resources created by the mechanism driver.

The apic_aim unit tests wipe the AIM DB in tearDown, and use the
aci_integration_manager branch of the apicapi repo.

The GBP devstack plugin, when ENABLE_APIC_AIM=True, configures neutron
to use the apic_aim_l3 service plugin, and installs the
aci_integration_manager branch of the apicapi repo.

Change-Id: I1b7f0c80e66d55d58c27fe9e4cb461f62aec3c42
This commit is contained in:
Robert Kukura 2016-08-24 14:12:52 -04:00
parent ceebaff57d
commit e382e7611f
18 changed files with 620 additions and 63 deletions

View File

@ -4,6 +4,7 @@ function install_apic_aim {
install_apic_ml2
install_aim
install_opflex
install_apicapi
}
function configure_apic_aim {

View File

@ -28,6 +28,8 @@ APICML2_REPO=http://github.com/noironetworks/apic-ml2-driver.git
APICML2_DIR=$DEST/apic_ml2
OPFLEX_REPO=http://github.com/noironetworks/python-opflex-agent.git
OPFLEX_DIR=$DEST/opflexagent
APICAPI_REPO=http://github.com/noironetworks/apicapi.git
APICAPI_DIR=$DEST/apicapi
# Save trace setting
XTRACE=$(set +o | grep xtrace)
@ -90,6 +92,14 @@ function install_apic_ml2 {
mv $APICML2_DIR/_test-requirements.txt $APICML2_DIR/test-requirements.txt
}
function install_apicapi {
git_clone $APICAPI_REPO $APICAPI_DIR $APICAPI_BRANCH
mv $APICAPI_DIR/test-requirements.txt $APICAPI_DIR/_test-requirements.txt
touch $APICAPI_DIR/setup.cfg
setup_develop $APICAPI_DIR
mv $APICAPI_DIR/_test-requirements.txt $APICAPI_DIR/test-requirements.txt
}
# Restore xtrace
$XTRACE

View File

@ -9,5 +9,6 @@ if [[ $ENABLE_APIC_AIM = True ]]; then
Q_ML2_TENANT_NETWORK_TYPE=${Q_ML2_TENANT_NETWORK_TYPE:-opflex}
Q_ML2_PLUGIN_TYPE_DRIVERS=${Q_ML2_PLUGIN_TYPE_DRIVERS:-local,vlan,opflex}
Q_ML2_PLUGIN_MECHANISM_DRIVERS=${Q_ML2_PLUGIN_MECHANISM_DRIVERS:-apic_aim}
Q_ML2_PLUGIN_EXT_DRIVERS=${Q_ML2_PLUGIN_EXT_DRIVERS-apic_aim,port_security}
Q_ML2_PLUGIN_EXT_DRIVERS=${Q_ML2_PLUGIN_EXT_DRIVERS:-apic_aim,port_security}
ML2_L3_PLUGIN=${ML2_L3_PLUGIN:-apic_aim_l3}
fi

View File

@ -9,8 +9,9 @@ ENABLE_NFP=${ENABLE_NFP:-False}
# VM locations
ConfiguratorQcow2Image=${ConfiguratorQcow2Image:-build}
# Enable necessary Neutron plugins, including group_policy and ncp
Q_SERVICE_PLUGIN_CLASSES=neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,group_policy,ncp
# Enable necessary Neutron plugins, including group_policy and ncp (L3
# is set via ML2_L3_PLUGIN, so isn't listed here).
Q_SERVICE_PLUGIN_CLASSES=group_policy,ncp
# Preferred git mirror
GIT_BASE=${GIT_BASE:-https://git.openstack.org}
@ -27,6 +28,7 @@ GBPHEAT_BRANCH=${GBPHEAT_BRANCH:-master}
AIM_BRANCH=${AIM_BRANCH:-master}
APICML2_BRANCH=${APICML2_BRANCH:-master}
OPFLEX_BRANCH=${OPFLEX_BRANCH:-master}
APICAPI_BRANCH=${APICAPI_BRANCH:-aci_integration_manager}
# Enable necessary services, including group-policy (and disable others)
disable_service n-net

View File

@ -23,7 +23,6 @@ DIST_NAMES = 'apic:distinguished_names'
SYNC_STATE = 'apic:synchronization_state'
BD = 'BridgeDomain'
CTX = 'Context'
EPG = 'EndpointGroup'
SUBNET = 'Subnet'
VRF = 'VRF'

View File

@ -12,11 +12,11 @@
from neutron.api import extensions
from gbpservice.neutron.extensions import cisco_apic
from gbpservice.neutron.extensions import group_policy as gp
ALIAS = 'cisco-apic-gbp'
AIM_DRIVER_EXT = 'aim-driver-extensions'
DIST_NAMES = 'apic:distinguished_names'
FORWARD_FILTER_ENTRIES = 'Forward-FilterEntries'
REVERSE_FILTER_ENTRIES = 'Reverse-FilterEntries'
CONTRACT = 'Contract'
@ -24,38 +24,34 @@ CONTRACT_SUBJECT = 'ContractSubject'
EXTENDED_ATTRIBUTES_2_0 = {
gp.POLICY_TARGET_GROUPS: {
DIST_NAMES: {
cisco_apic.DIST_NAMES: {
'allow_post': False, 'allow_put': False, 'is_visible': True},
},
gp.POLICY_RULES: {
DIST_NAMES: {
cisco_apic.DIST_NAMES: {
'allow_post': False, 'allow_put': False, 'is_visible': True},
},
gp.POLICY_RULE_SETS: {
DIST_NAMES: {
cisco_apic.DIST_NAMES: {
'allow_post': False, 'allow_put': False, 'is_visible': True},
},
}
class Aim_driver_ext(extensions.ExtensionDescriptor):
class Cisco_apic_gbp(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "Extensions for AIM driver"
return "Cisco APIC GBP"
@classmethod
def get_alias(cls):
return AIM_DRIVER_EXT
return ALIAS
@classmethod
def get_description(cls):
return _("Adds AIM driver specific attributes to GBP resources.")
@classmethod
def get_namespace(cls):
return ("http://docs.openstack.org/ext/neutron/grouppolicy/"
"aim_driver_ext/api/v1.0")
return _("Extension exposing mapping of GBP resources to Cisco "
"APIC constructs")
@classmethod
def get_updated(cls):

View File

@ -0,0 +1,54 @@
# Copyright (c) 2016 Cisco Systems Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.api import extensions
from neutron.extensions import l3
from gbpservice.neutron.extensions import cisco_apic
ALIAS = 'cisco-apic-l3'
CONTRACT = 'Contract'
CONTRACT_SUBJECT = 'ContractSubject'
EXTENDED_ATTRIBUTES_2_0 = {
l3.ROUTERS: cisco_apic.APIC_ATTRIBUTES
}
class Cisco_apic_l3(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "Cisco APIC L3"
@classmethod
def get_alias(cls):
return ALIAS
@classmethod
def get_description(cls):
return ("Extension exposing mapping of Neutron L3 resources to Cisco "
"APIC constructs")
@classmethod
def get_updated(cls):
return "2016-09-06T12:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}

View File

@ -23,6 +23,7 @@ LOG = None
NAME_TYPE_TENANT = 'tenant'
NAME_TYPE_NETWORK = 'network'
NAME_TYPE_ADDRESS_SCOPE = 'address_scope'
NAME_TYPE_ROUTER = 'router'
NAME_TYPE_POLICY_TARGET_GROUP = 'policy_target_group'
NAME_TYPE_L3_POLICY = 'l3_policy'
NAME_TYPE_L2_POLICY = 'l2_policy'
@ -151,6 +152,10 @@ class APICNameMapper(object):
address_scope_name=None):
return address_scope_name
@mapper(NAME_TYPE_ROUTER)
def router(self, session, router_id, router_name=None):
return router_name
@mapper(NAME_TYPE_POLICY_TARGET_GROUP)
def policy_target_group(self, session, policy_target_group_id,
policy_target_group_name=None):

View File

@ -18,9 +18,8 @@ from neutron.api import extensions
from neutron import manager as n_manager
from oslo_log import log
from gbpservice.neutron import extensions as extensions_pkg
from gbpservice.neutron.plugins.ml2plus import driver_api as api_plus
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import (
extensions as extensions_pkg)
LOG = log.getLogger(__name__)

View File

@ -31,17 +31,20 @@ from opflexagent import constants as ofcst
from opflexagent import rpc as o_rpc
from oslo_log import log
from gbpservice.neutron.extensions import cisco_apic
from gbpservice.neutron.extensions import cisco_apic_l3
from gbpservice.neutron.plugins.ml2plus import driver_api as api_plus
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import apic_mapper
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import cache
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim.extensions import (
cisco_apic)
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import model
LOG = log.getLogger(__name__)
AP_NAME = 'NeutronAP'
ANY_FILTER_NAME = 'AnyFilter'
ANY_FILTER_ENTRY_NAME = 'AnyFilterEntry'
UNROUTED_VRF_NAME = 'UnroutedVRF'
COMMON_TENANT_NAME = 'common'
ROUTER_SUBJECT_NAME = 'route'
AGENT_TYPE_DVS = 'DVS agent'
VIF_TYPE_DVS = 'dvs'
PROMISCUOUS_TYPES = [n_constants.DEVICE_OWNER_DHCP,
@ -79,9 +82,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
self.project_name_cache.ensure_project(tenant_id)
# TODO(rkukura): Move the following to precommit methods so
# AIM tenants and application profiles are created whenever
# needed.
# TODO(rkukura): Move the following to calls made from
# precommit methods so AIM Tenants, ApplicationProfiles, and
# Filters are [re]created whenever needed.
session = plugin_context.session
with session.begin(subtransactions=True):
project_name = self.project_name_cache.get_project_name(tenant_id)
@ -103,6 +106,19 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
if not self.aim.get(aim_ctx, ap):
self.aim.create(aim_ctx, ap)
filter = aim_resource.Filter(tenant_name=tenant_aname,
name=ANY_FILTER_NAME,
display_name='Any Filter')
if not self.aim.get(aim_ctx, filter):
self.aim.create(aim_ctx, filter)
entry = aim_resource.FilterEntry(tenant_name=tenant_aname,
filter_name=ANY_FILTER_NAME,
name=ANY_FILTER_ENTRY_NAME,
display_name='Any FilterEntry')
if not self.aim.get(aim_ctx, entry):
self.aim.create(aim_ctx, entry)
def create_network_precommit(self, context):
LOG.debug("APIC AIM MD creating network: %s", context.current)
@ -124,22 +140,20 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
vrf = self._get_unrouted_vrf(aim_ctx)
bd = aim_resource.BridgeDomain(
tenant_name=tenant_aname,
name=aname,
display_name=dname,
vrf_name=vrf.name,
enable_arp_flood=True,
enable_routing=False,
limit_ip_learn_to_subnets=True)
bd = aim_resource.BridgeDomain(tenant_name=tenant_aname,
name=aname,
display_name=dname,
vrf_name=vrf.name,
enable_arp_flood=True,
enable_routing=False,
limit_ip_learn_to_subnets=True)
self.aim.create(aim_ctx, bd)
epg = aim_resource.EndpointGroup(
tenant_name=tenant_aname,
app_profile_name=AP_NAME,
name=aname,
display_name=dname,
bd_name=aname)
epg = aim_resource.EndpointGroup(tenant_name=tenant_aname,
app_profile_name=AP_NAME,
name=aname,
display_name=dname,
bd_name=aname)
self.aim.create(aim_ctx, epg)
def update_network_precommit(self, context):
@ -268,13 +282,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
LOG.debug("Mapped address_scope_id %(id)s with name %(name)s to "
"%(aname)s",
{'id': id, 'name': name, 'aname': aname})
dname = aim_utils.sanitize_display_name(name)
aim_ctx = aim_context.AimContext(session)
vrf = aim_resource.VRF(
tenant_name=tenant_aname,
name=aname,
display_name=aim_utils.sanitize_display_name(name))
vrf = aim_resource.VRF(tenant_name=tenant_aname,
name=aname,
display_name=dname)
self.aim.create(aim_ctx, vrf)
# ML2Plus does not extend address scope dict after precommit.
@ -358,6 +372,149 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
result[cisco_apic.DIST_NAMES] = {cisco_apic.VRF: vrf.dn}
result[cisco_apic.SYNC_STATE] = sync_state
def create_router(self, context, current):
LOG.debug("APIC AIM MD creating router: %s", current)
session = context.session
tenant_id = current['tenant_id']
tenant_aname = self.name_mapper.tenant(session, tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': tenant_id, 'aname': tenant_aname})
id = current['id']
name = current['name']
aname = self.name_mapper.router(session, id, name)
LOG.debug("Mapped router_id %(id)s with name %(name)s to "
"%(aname)s",
{'id': id, 'name': name, 'aname': aname})
dname = aim_utils.sanitize_display_name(name)
aim_ctx = aim_context.AimContext(session)
contract = aim_resource.Contract(tenant_name=tenant_aname,
name=aname,
display_name=dname)
self.aim.create(aim_ctx, contract)
subject = aim_resource.ContractSubject(tenant_name=tenant_aname,
contract_name=aname,
name=ROUTER_SUBJECT_NAME,
display_name=dname,
bi_filters=[ANY_FILTER_NAME])
self.aim.create(aim_ctx, subject)
# REVISIT(rkukura): Consider having L3 plugin extend router
# dict again after calling this function.
sync_state = cisco_apic.SYNC_SYNCED
sync_state = self._merge_status(aim_ctx, sync_state, contract)
sync_state = self._merge_status(aim_ctx, sync_state, subject)
current[cisco_apic.DIST_NAMES] = {cisco_apic_l3.CONTRACT: contract.dn,
cisco_apic_l3.CONTRACT_SUBJECT:
subject.dn}
current[cisco_apic.SYNC_STATE] = sync_state
def update_router(self, context, current, original):
LOG.debug("APIC AIM MD updating router: %s", current)
if current['name'] != original['name']:
session = context.session
tenant_id = current['tenant_id']
tenant_aname = self.name_mapper.tenant(session, tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': tenant_id, 'aname': tenant_aname})
id = current['id']
name = current['name']
aname = self.name_mapper.router(session, id, name)
LOG.debug("Mapped router_id %(id)s with name %(name)s to "
"%(aname)s",
{'id': id, 'name': name, 'aname': aname})
dname = aim_utils.sanitize_display_name(name)
aim_ctx = aim_context.AimContext(session)
contract = aim_resource.Contract(tenant_name=tenant_aname,
name=aname)
contract = self.aim.update(aim_ctx, contract, display_name=dname)
subject = aim_resource.ContractSubject(tenant_name=tenant_aname,
contract_name=aname,
name=ROUTER_SUBJECT_NAME)
subject = self.aim.update(aim_ctx, subject, display_name=dname)
# REVISIT(rkukura): Update extension attributes?
def delete_router(self, context, current):
LOG.debug("APIC AIM MD deleting router: %s", current)
session = context.session
tenant_id = current['tenant_id']
tenant_aname = self.name_mapper.tenant(session, tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': tenant_id, 'aname': tenant_aname})
id = current['id']
name = current['name']
aname = self.name_mapper.router(session, id, name)
LOG.debug("Mapped router_id %(id)s with name %(name)s to "
"%(aname)s",
{'id': id, 'name': name, 'aname': aname})
aim_ctx = aim_context.AimContext(session)
subject = aim_resource.ContractSubject(tenant_name=tenant_aname,
contract_name=aname,
name=ROUTER_SUBJECT_NAME)
self.aim.delete(aim_ctx, subject)
contract = aim_resource.Contract(tenant_name=tenant_aname,
name=aname)
self.aim.delete(aim_ctx, contract)
self.name_mapper.delete_apic_name(session, id)
def extend_router_dict(self, session, base_model, result):
LOG.debug("APIC AIM MD extending dict for router: %s", result)
tenant_id = result['tenant_id']
tenant_aname = self.name_mapper.tenant(session, tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': tenant_id, 'aname': tenant_aname})
id = result['id']
name = result['name']
aname = self.name_mapper.router(session, id, name)
LOG.debug("Mapped router_id %(id)s with name %(name)s to "
"%(aname)s",
{'id': id, 'name': name, 'aname': aname})
contract = aim_resource.Contract(tenant_name=tenant_aname,
name=aname)
subject = aim_resource.ContractSubject(tenant_name=tenant_aname,
contract_name=aname,
name=ROUTER_SUBJECT_NAME)
aim_ctx = aim_context.AimContext(session)
sync_state = cisco_apic.SYNC_SYNCED
sync_state = self._merge_status(aim_ctx, sync_state, contract)
sync_state = self._merge_status(aim_ctx, sync_state, subject)
result[cisco_apic.DIST_NAMES] = {cisco_apic_l3.CONTRACT: contract.dn,
cisco_apic_l3.CONTRACT_SUBJECT:
subject.dn}
result[cisco_apic.SYNC_STATE] = sync_state
def add_router_interface(self, context, info):
LOG.debug("APIC AIM MD adding router interface: %s", info)
# TODO(rkukura): Implement.
def remove_router_interface(self, context, info):
LOG.debug("APIC AIM MD removing router interface: %s", info)
# TODO(rkukura): Implement.
def bind_port(self, context):
LOG.debug("Attempting to bind port %(port)s on network %(net)s",
{'port': context.current['id'],

View File

@ -0,0 +1,148 @@
# Copyright (c) 2016 Cisco Systems Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron._i18n import _LI
from neutron.api import extensions
from neutron.db import common_db_mixin
from neutron.db import db_base_plugin_v2
from neutron.db import extraroute_db
from neutron.db import l3_gwmode_db
from neutron.extensions import l3
from neutron.plugins.common import constants
from oslo_log import log as logging
from sqlalchemy import inspect
from gbpservice.neutron import extensions as extensions_pkg
LOG = logging.getLogger(__name__)
class ApicL3Plugin(common_db_mixin.CommonDbMixin,
extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin):
supported_extension_aliases = ["router", "ext-gw-mode", "extraroute",
"cisco-apic-l3"]
@staticmethod
def get_plugin_type():
return constants.L3_ROUTER_NAT
@staticmethod
def get_plugin_description():
return _("L3 Router Service Plugin using the APIC via AIM")
def __init__(self):
LOG.info(_LI("APIC AIM L3 Plugin __init__"))
extensions.append_api_extensions_path(extensions_pkg.__path__)
self._mechanism_driver = None
super(ApicL3Plugin, self).__init__()
@property
def _md(self):
if not self._mechanism_driver:
# REVISIT(rkukura): It might be safer to search the MDs by
# class rather than index by name, or to use a class
# variable to find the instance.
mech_mgr = self._core_plugin.mechanism_manager
self._mechanism_driver = mech_mgr.mech_drivers['apic_aim'].obj
return self._mechanism_driver
def _extend_router_dict_apic(self, router_res, router_db):
LOG.debug("APIC AIM L3 Plugin extending router dict: %s", router_res)
session = inspect(router_db).session
self._md.extend_router_dict(session, router_db, router_res)
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
l3.ROUTERS, ['_extend_router_dict_apic'])
def create_router(self, context, router):
LOG.debug("APIC AIM L3 Plugin creating router: %s", router)
self._md.ensure_tenant(context, router['router']['tenant_id'])
with context.session.begin(subtransactions=True):
# REVISIT(rkukura): The base operation may create a port,
# which should generally not be done inside a
# transaction. But we need to ensure atomicity, and are
# generally not concerned with mechanism driver postcommit
# processing. Consider overriding create_router_db()
# instead, and/or reimplementing the base funtionality to
# be completely transaction safe.
result = super(ApicL3Plugin, self).create_router(context, router)
self._md.create_router(context, result)
return result
def update_router(self, context, id, router):
LOG.debug("APIC AIM L3 Plugin updating router %(id)s with: %(router)s",
{'id': id, 'router': router})
with context.session.begin(subtransactions=True):
# REVISIT(rkukura): The base operation sends notification
# RPCs, which should generally not be done inside a
# transaction. But we need to ensure atomicity, and are
# not using an L3 agent. Consider overriding
# create_router_db() instead, and/or reimplementing the
# base funtionality to be completely transaction safe.
original = self.get_router(context, id)
result = super(ApicL3Plugin, self).update_router(context, id,
router)
self._md.update_router(context, result, original)
return result
def delete_router(self, context, id):
LOG.debug("APIC AIM L3 Plugin deleting router: %s", id)
with context.session.begin(subtransactions=True):
# REVISIT(rkukura): The base operation may delete ports
# and sends notification RPCs, which should generally not
# be done inside a transaction. But we need to ensure
# atomicity, are not using an L3 agent, and are generally
# not concerned with mechanism driver postcommit
# processing. Consider reimplementing the base
# funtionality to be completely transaction safe.
router = self.get_router(context, id)
super(ApicL3Plugin, self).delete_router(context, id)
self._md.delete_router(context, router)
def add_router_interface(self, context, router_id, interface_info):
LOG.debug("APIC AIM L3 Plugin adding interface %(interface)s "
"to router %(router)s",
{'interface': interface_info, 'router': router_id})
with context.session.begin(subtransactions=True):
# REVISIT(rkukura): The base operation may create or
# update a port and sends notification RPCs, which should
# generally not be done inside a transaction. But we need
# to ensure atomicity, are not using an L3 agent, and are
# generally not concerned with mechanism driver postcommit
# processing. Consider reimplementing the base
# funtionality to be completely transaction safe.
info = super(ApicL3Plugin, self).add_router_interface(
context, router_id, interface_info)
self._md.add_router_interface(context, info)
return info
def remove_router_interface(self, context, router_id, interface_info):
LOG.debug("APIC AIM L3 Plugin removing interface %(interface)s "
"from router %(router)s",
{'interface': interface_info, 'router': router_id})
with context.session.begin(subtransactions=True):
# REVISIT(rkukura): The base operation may delete or
# update a port and sends notification RPCs, which should
# generally not be done inside a transaction. But we need
# to ensure atomicity, are not using an L3 agent, and are
# generally not concerned with mechanism driver postcommit
# processing. Consider reimplementing the base
# funtionality to be completely transaction safe.
info = super(ApicL3Plugin, self).remove_router_interface(
context, router_id, interface_info)
self._md.remove_router_interface(context, info)
return info

View File

@ -18,12 +18,11 @@ from oslo_concurrency import lockutils
from oslo_log import helpers as log
from oslo_log import log as logging
from gbpservice.neutron.extensions import aim_driver_ext as aim_ext
from gbpservice.neutron.extensions import cisco_apic
from gbpservice.neutron.extensions import cisco_apic_gbp as aim_ext
from gbpservice.neutron.extensions import group_policy as gpolicy
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import (
mechanism_driver as aim_md)
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim.extensions import (
cisco_apic)
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import model
from gbpservice.neutron.services.grouppolicy.common import (
constants as gp_const)

View File

@ -14,7 +14,7 @@ from neutron._i18n import _LI
from neutron import manager as n_manager
from oslo_log import log as logging
from gbpservice.neutron.extensions import aim_driver_ext
from gbpservice.neutron.extensions import cisco_apic_gbp
from gbpservice.neutron.services.grouppolicy import (
group_policy_driver_api as api)
@ -22,8 +22,8 @@ LOG = logging.getLogger(__name__)
class AIMExtensionDriver(api.ExtensionDriver):
_supported_extension_alias = aim_driver_ext.AIM_DRIVER_EXT
_extension_dict = aim_driver_ext.EXTENDED_ATTRIBUTES_2_0
_supported_extension_alias = cisco_apic_gbp.ALIAS
_extension_dict = cisco_apic_gbp.EXTENDED_ATTRIBUTES_2_0
def __init__(self):
LOG.info(_LI("AIM Extension __init__"))

View File

@ -22,10 +22,12 @@ from neutron.api import extensions
from neutron import context
from neutron.db import api as db_api
from neutron import manager
from neutron.plugins.common import constants as service_constants
from neutron.plugins.ml2 import config
from neutron.tests.unit.api import test_extensions
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin
from neutron.tests.unit.extensions import test_address_scope
from neutron.tests.unit.extensions import test_l3
from opflexagent import constants as ofcst
PLUGIN_NAME = 'gbpservice.neutron.plugins.ml2plus.plugin.Ml2PlusPlugin'
@ -66,7 +68,8 @@ class FakeKeystoneClient(object):
self.projects = FakeProjectManager()
class ApicAimTestCase(test_address_scope.AddressScopeTestCase):
class ApicAimTestCase(test_address_scope.AddressScopeTestCase,
test_l3.L3NatTestCaseMixin):
def setUp(self):
# Enable the test mechanism driver to ensure that
# we can successfully call through to all mechanism
@ -87,7 +90,12 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase):
['physnet1:1000:1099'],
group='ml2_type_vlan')
super(ApicAimTestCase, self).setUp(PLUGIN_NAME)
service_plugins = {
'L3_ROUTER_NAT':
'gbpservice.neutron.services.apic_aim.l3_plugin.ApicL3Plugin'}
super(ApicAimTestCase, self).setUp(PLUGIN_NAME,
service_plugins=service_plugins)
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
self.port_create_status = 'DOWN'
@ -102,6 +110,8 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase):
self.plugin.start_rpc_listeners()
self.driver = self.plugin.mechanism_manager.mech_drivers[
'apic_aim'].obj
self.l3_plugin = manager.NeutronManager.get_service_plugins()[
service_constants.L3_ROUTER_NAT]
self.aim_mgr = aim_manager.AimManager()
self._app_profile_name = 'NeutronAP'
self._tenant_name = self._map_name({'id': 'test-tenant',
@ -109,6 +119,11 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase):
self._unrouted_vrf_name = 'UnroutedVRF'
def tearDown(self):
engine = db_api.get_engine()
with engine.begin() as conn:
for table in reversed(
aim_model_base.Base.metadata.sorted_tables):
conn.execute(table.delete())
ksc_client.Client = self.saved_keystone_client
super(ApicAimTestCase, self).tearDown()
@ -167,6 +182,58 @@ class TestAimMapping(ApicAimTestCase):
self.assertIsNone(epg)
return epg
def _get_contract(self, contract_name, tenant_name, should_exist=True):
session = db_api.get_session()
aim_ctx = aim_context.AimContext(session)
contract = aim_resource.Contract(tenant_name=tenant_name,
name=contract_name)
contract = self.aim_mgr.get(aim_ctx, contract)
if should_exist:
self.assertIsNotNone(contract)
else:
self.assertIsNone(contract)
return contract
def _get_subject(self, subject_name, contract_name, tenant_name,
should_exist=True):
session = db_api.get_session()
aim_ctx = aim_context.AimContext(session)
subject = aim_resource.ContractSubject(tenant_name=tenant_name,
contract_name=contract_name,
name=subject_name)
subject = self.aim_mgr.get(aim_ctx, subject)
if should_exist:
self.assertIsNotNone(subject)
else:
self.assertIsNone(subject)
return subject
def _get_filter(self, filter_name, tenant_name, should_exist=True):
session = db_api.get_session()
aim_ctx = aim_context.AimContext(session)
filter = aim_resource.Filter(tenant_name=tenant_name,
name=filter_name)
filter = self.aim_mgr.get(aim_ctx, filter)
if should_exist:
self.assertIsNotNone(filter)
else:
self.assertIsNone(filter)
return filter
def _get_filter_entry(self, entry_name, filter_name, tenant_name,
should_exist=True):
session = db_api.get_session()
aim_ctx = aim_context.AimContext(session)
entry = aim_resource.FilterEntry(tenant_name=tenant_name,
filter_name=filter_name,
name=entry_name)
entry = self.aim_mgr.get(aim_ctx, entry)
if should_exist:
self.assertIsNotNone(entry)
else:
self.assertIsNone(entry)
return entry
def _check_dn(self, resource, aim_resource, key):
dist_names = resource.get('apic:distinguished_names')
self.assertIsInstance(dist_names, dict)
@ -274,6 +341,69 @@ class TestAimMapping(ApicAimTestCase):
self._tenant_name,
should_exist=False)
def _check_router(self, router, orig_router=None):
orig_router = orig_router or router
# REVISIT(rkukura): Check AIM Tenant here?
self.assertEqual('test-tenant', router['tenant_id'])
aname = self._map_name(orig_router)
aim_contract = self._get_contract(aname, self._tenant_name)
self.assertEqual(self._tenant_name, aim_contract.tenant_name)
self.assertEqual(aname, aim_contract.name)
self.assertEqual(router['name'], aim_contract.display_name)
self.assertEqual('context', aim_contract.scope) # REVISIT(rkukura)
self._check_dn(router, aim_contract, 'Contract')
aim_subject = self._get_subject('route', aname, self._tenant_name)
self.assertEqual(self._tenant_name, aim_subject.tenant_name)
self.assertEqual(aname, aim_subject.contract_name)
self.assertEqual('route', aim_subject.name)
self.assertEqual(router['name'], aim_subject.display_name)
self.assertEqual([], aim_subject.in_filters)
self.assertEqual([], aim_subject.out_filters)
self.assertEqual(['AnyFilter'], aim_subject.bi_filters)
self._check_dn(router, aim_subject, 'ContractSubject')
self._check_any_filter()
# REVISIT(rkukura): Anything else to check?
def _check_router_deleted(self, router):
aname = self._map_name(router)
self._get_contract(aname, self._tenant_name, should_exist=False)
self._get_subject('route', aname, self._tenant_name,
should_exist=False)
# REVISIT(rkukura): Anything else to check?
def _check_any_filter(self):
aim_filter = self._get_filter('AnyFilter', self._tenant_name)
self.assertEqual(self._tenant_name, aim_filter.tenant_name)
self.assertEqual('AnyFilter', aim_filter.name)
self.assertEqual('Any Filter', aim_filter.display_name)
aim_entry = self._get_filter_entry('AnyFilterEntry', 'AnyFilter',
self._tenant_name)
self.assertEqual(self._tenant_name, aim_entry.tenant_name)
self.assertEqual('AnyFilter', aim_entry.filter_name)
self.assertEqual('AnyFilterEntry', aim_entry.name)
self.assertEqual('Any FilterEntry', aim_entry.display_name)
self.assertEqual('unspecified', aim_entry.arp_opcode)
self.assertEqual('unspecified', aim_entry.ether_type)
self.assertEqual('unspecified', aim_entry.ip_protocol)
self.assertEqual('unspecified', aim_entry.icmpv4_type)
self.assertEqual('unspecified', aim_entry.icmpv6_type)
self.assertEqual('unspecified', aim_entry.source_from_port)
self.assertEqual('unspecified', aim_entry.source_to_port)
self.assertEqual('unspecified', aim_entry.dest_from_port)
self.assertEqual('unspecified', aim_entry.dest_to_port)
self.assertEqual('unspecified', aim_entry.tcp_flags)
self.assertFalse(aim_entry.stateful)
self.assertFalse(aim_entry.fragment_only)
def test_network_lifecycle(self):
# Test create.
orig_net = self._make_network(self.fmt, 'net1', True)['network']
@ -336,6 +466,58 @@ class TestAimMapping(ApicAimTestCase):
self._delete('address-scopes', a_s_id)
self._check_address_scope_deleted(orig_a_s)
def test_router_lifecycle(self):
# Test create.
orig_router = self._make_router(
self.fmt, 'test-tenant', 'router1')['router']
router_id = orig_router['id']
self._check_router(orig_router)
# Test show.
router = self._show('routers', router_id)['router']
self._check_router(router)
# Test update.
data = {'router': {'name': 'newnameforrouter'}}
router = self._update('routers', router_id, data)['router']
self._check_router(router, orig_router)
# Test delete.
self._delete('routers', router_id)
self._check_router_deleted(orig_router)
def test_router_interface(self):
# Create router.
router = self._make_router(
self.fmt, 'test-tenant', 'router1')['router']
router_id = router['id']
self._check_router(router)
# Create network.
net_resp = self._make_network(self.fmt, 'net1', True)
self._check_unrouted_network(net_resp['network'])
# Create subnet.
subnet = self._make_subnet(self.fmt, net_resp, '10.0.0.1',
'10.0.0.0/24')['subnet']
subnet_id = subnet['id']
self._check_unrouted_subnet(subnet)
# Add subnet to router.
info = self.l3_plugin.add_router_interface(
context.get_admin_context(), router_id, {'subnet_id': subnet_id})
self.assertIn(subnet_id, info['subnet_ids'])
self._check_router(router)
# TODO(rkukura): Check router and subnet extension attributes,
# BD, EPG, etc..
# Remove subnet from router.
info = self.l3_plugin.remove_router_interface(
context.get_admin_context(), router_id, {'subnet_id': subnet_id})
self.assertIn(subnet_id, info['subnet_ids'])
self._check_router(router)
# def test_create_subnet_with_address_scope(self):
# net = self._make_network(self.fmt, 'net1', True)
# name = self._map_name(net['network'])

View File

@ -353,11 +353,11 @@ class TestL2PolicyBase(test_nr_base.TestL2Policy, AIMBaseTestCase):
aim_filters = self.aim_mgr.find(
self._aim_context, aim_resource.Filter,
tenant_name=aim_tenant_name)
self.assertEqual(9, len(aim_filters))
self.assertEqual(10, len(aim_filters)) # 1 belongs to MD
aim_filter_entries = self.aim_mgr.find(
self._aim_context, aim_resource.FilterEntry,
tenant_name=aim_tenant_name)
self.assertEqual(9, len(aim_filter_entries))
self.assertEqual(10, len(aim_filter_entries)) # 1 belongs to MD
entries_attrs = alib.get_service_contract_filter_entries().values()
entries_attrs.extend(alib.get_arp_filter_entry().values())
expected_entries_attrs = []
@ -369,9 +369,11 @@ class TestL2PolicyBase(test_nr_base.TestL2Policy, AIMBaseTestCase):
entries_attrs = [x.__dict__ for x in aim_filter_entries]
observed_entries_attrs = []
for entry in entries_attrs:
observed_entries_attrs.append(
{k: unicode(entry[k]) for k in entry if k not in [
'name', 'display_name', 'filter_name', 'tenant_name']})
# Ignore entry belonging to MD's filter.
if entry['filter_name'] != 'AnyFilter':
observed_entries_attrs.append(
{k: unicode(entry[k]) for k in entry if k not in [
'name', 'display_name', 'filter_name', 'tenant_name']})
self.assertItemsEqual(expected_entries_attrs, observed_entries_attrs)
@ -398,11 +400,11 @@ class TestL2Policy(TestL2PolicyBase):
aim_filters = self.aim_mgr.find(
self._aim_context, aim_resource.Filter,
tenant_name=aim_tenant_name)
self.assertEqual(0, len(aim_filters))
self.assertEqual(1, len(aim_filters)) # belongs to MD
aim_filter_entries = self.aim_mgr.find(
self._aim_context, aim_resource.FilterEntry,
tenant_name=aim_tenant_name)
self.assertEqual(0, len(aim_filter_entries))
self.assertEqual(1, len(aim_filter_entries)) # belongs to MD
def test_l2_policy_lifecycle(self):
self.assertEqual(0, len(self.aim_mgr.find(
@ -482,11 +484,11 @@ class TestL2PolicyRollback(TestL2PolicyBase):
aim_filters = self.aim_mgr.find(
self._aim_context, aim_resource.Filter,
tenant_name=aim_tenant_name)
self.assertEqual(0, len(aim_filters))
self.assertEqual(1, len(aim_filters)) # belongs to MD
aim_filter_entries = self.aim_mgr.find(
self._aim_context, aim_resource.FilterEntry,
tenant_name=aim_tenant_name)
self.assertEqual(0, len(aim_filter_entries))
self.assertEqual(1, len(aim_filter_entries)) # belongs to MD
# restore mock
self.dummy.create_l2_policy_precommit = orig_func
@ -952,10 +954,10 @@ class TestPolicyRuleRollback(TestPolicyRuleBase):
self._gbp_plugin.get_policy_rules(self._context))
aim_filters = self.aim_mgr.find(
self._aim_context, aim_resource.Filter)
self.assertEqual(0, len(aim_filters))
self.assertEqual(1, len(aim_filters)) # belongs to MD
aim_filter_entries = self.aim_mgr.find(
self._aim_context, aim_resource.FilterEntry)
self.assertEqual(0, len(aim_filter_entries))
self.assertEqual(1, len(aim_filter_entries)) # belongs to MD
# restore mock
self.dummy.create_policy_rule_precommit = orig_func

View File

@ -54,6 +54,7 @@ neutron.service_plugins =
ncp = gbpservice.neutron.services.servicechain.plugins.ncp.plugin:NodeCompositionPlugin
apic_gbp_l3 = gbpservice.neutron.services.l3_router.l3_apic:ApicGBPL3ServicePlugin
nfp_fwaas = gbpservice.contrib.nfp.service_plugins.firewall.nfp_fwaas_plugin.NFPFirewallPlugin
apic_aim_l3 = gbpservice.neutron.services.apic_aim.l3_plugin:ApicL3Plugin
gbpservice.neutron.group_policy.extension_drivers =
test = gbpservice.neutron.tests.unit.services.grouppolicy.test_extension_driver_api:TestExtensionDriver
proxy_group = gbpservice.neutron.services.grouppolicy.drivers.extensions.proxy_group_driver:ProxyGroupDriver

View File

@ -4,7 +4,8 @@
setuptools>=19.2
-e git+https://git.openstack.org/openstack/neutron.git@stable/mitaka#egg=neutron
-e git+https://github.com/noironetworks/apicapi.git@master#egg=apicapi
# TODO(rkukura): Switch apicapi back to master branch eventually.
-e git+https://github.com/noironetworks/apicapi.git@aci_integration_manager#egg=apicapi
-e git+https://github.com/noironetworks/python-opflex-agent.git@master#egg=python-opflexagent-agent
-e git+https://github.com/noironetworks/apic-ml2-driver.git@master#egg=apic_ml2
-e git+https://git.openstack.org/openstack/python-group-based-policy-client@master#egg=gbpclient