[apic_aim] ML2Plus SubnetPool and AddressScope support

Add mechanism driver and extension driver support the the SubnetPool
and AddressScope resources, including ensure_tenant.

Change-Id: Icb00eb223d1e503f946ffba54de51e89ba260a1d
This commit is contained in:
Robert Kukura 2016-07-25 14:02:12 -04:00
parent 2498dc10eb
commit a6b918934a
14 changed files with 1486 additions and 9 deletions

View File

@ -19,6 +19,70 @@ import six
from neutron.plugins.ml2 import driver_api
@six.add_metaclass(abc.ABCMeta)
class SubnetPoolContext(object):
"""Context passed to MechanismDrivers for changes to subnet pool
resources.
A SubnetPoolContext instance wraps a subnet pool resource. It
provides helper methods for accessing other relevant
information. Results from expensive operations are cached so that
other MechanismDrivers can freely access the same information.
"""
@abc.abstractproperty
def current(self):
"""Return the subnet pool in its current configuration.
Return the subnet pool with all its properties 'current' at
the time the context was established.
"""
pass
@abc.abstractproperty
def original(self):
"""Return the subnet pool in its original configuration.
Return the subnet pool, with all its properties set to their
original values prior to a call to update_address_scope. Method is
only valid within calls to update_address_scope_precommit and
update_address_scope_postcommit.
"""
pass
@six.add_metaclass(abc.ABCMeta)
class AddressScopeContext(object):
"""Context passed to MechanismDrivers for changes to address scope
resources.
An AddressScopeContext instance wraps an address scope
resource. It provides helper methods for accessing other relevant
information. Results from expensive operations are cached so that
other MechanismDrivers can freely access the same information.
"""
@abc.abstractproperty
def current(self):
"""Return the address scope in its current configuration.
Return the address scope with all its properties 'current' at
the time the context was established.
"""
pass
@abc.abstractproperty
def original(self):
"""Return the address scope in its original configuration.
Return the address scope, with all its properties set to their
original values prior to a call to update_address_scope. Method is
only valid within calls to update_address_scope_precommit and
update_address_scope_postcommit.
"""
pass
@six.add_metaclass(abc.ABCMeta)
class MechanismDriver(driver_api.MechanismDriver):
@ -39,9 +103,280 @@ class MechanismDriver(driver_api.MechanismDriver):
"""
pass
# TODO(rkukura): Add precommit/postcommit calls for address_scope,
# subnet_pool, and other resources.
def create_subnetpool_precommit(self, context):
"""Allocate resources for a new subnet pool.
:param context: SubnetPoolContext instance describing the new
subnet pool.
Create a new subnet pool, allocating resources as necessary in
the database. Called inside transaction context on
session. Call cannot block. Raising an exception will result
in a rollback of the current transaction.
"""
pass
def create_subnetpool_postcommit(self, context):
"""Create a subnet pool.
:param context: SubnetPoolContext instance describing the new
subnet pool.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Raising an exception will
cause the deletion of the resource.
"""
pass
def update_subnetpool_precommit(self, context):
"""Update resources of a subnet pool.
:param context: SubnetPoolContext instance describing the new
state of the subnet pool, as well as the original state prior
to the update_subnetpool call.
Update values of a subnet pool, updating the associated
resources in the database. Called inside transaction context
on session. Raising an exception will result in rollback of
the transaction.
update_subnetpool_precommit is called for all changes to the
subnet pool state. It is up to the mechanism driver to ignore
state or state changes that it does not know or care about.
"""
pass
def update_subnetpool_postcommit(self, context):
"""Update a subnet pool.
:param context: SubnetPoolContext instance describing the new
state of the subnet pool, as well as the original state prior
to the update_subnetpool call.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Raising an exception will
cause the deletion of the resource.
update_subnetpool_postcommit is called for all changes to the
subnet pool state. It is up to the mechanism driver to ignore
state or state changes that it does not know or care about.
"""
pass
def delete_subnetpool_precommit(self, context):
"""Delete resources for a subnet pool.
:param context: SubnetPoolContext instance describing the
current state of the subnet pool, prior to the call to delete
it.
Delete subnet pool resources previously allocated by this
mechanism driver for a subnet pool. Called inside transaction
context on session. Runtime errors are not expected, but
raising an exception will result in rollback of the
transaction.
"""
pass
def delete_subnetpool_postcommit(self, context):
"""Delete a subnet pool.
:param context: SubnetPoolContext instance describing the
current state of the subnet pool, prior to the call to delete
it.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Runtime errors are not
expected, and will not prevent the resource from being
deleted.
"""
pass
def create_address_scope_precommit(self, context):
"""Allocate resources for a new address scope.
:param context: AddressScopeContext instance describing the
new address scope.
Create a new address scope, allocating resources as necessary
in the database. Called inside transaction context on
session. Call cannot block. Raising an exception will result
in a rollback of the current transaction.
"""
pass
def create_address_scope_postcommit(self, context):
"""Create an address scope.
:param context: AddressScopeContext instance describing the
new address scope.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Raising an exception will
cause the deletion of the resource.
"""
pass
def update_address_scope_precommit(self, context):
"""Update resources of an address scope.
:param context: AddressScopeContext instance describing the
new state of the address scope, as well as the original state
prior to the update_address_scope call.
Update values of an address scope, updating the associated
resources in the database. Called inside transaction context
on session. Raising an exception will result in rollback of
the transaction.
update_address_scope_precommit is called for all changes to
the address scope state. It is up to the mechanism driver to
ignore state or state changes that it does not know or care
about.
"""
pass
def update_address_scope_postcommit(self, context):
"""Update an address scope.
:param context: AddressScopeContext instance describing the
new state of the address scope, as well as the original state
prior to the update_address_scope call.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Raising an exception will
cause the deletion of the resource.
update_address_scope_postcommit is called for all changes to
the address scope state. It is up to the mechanism driver to
ignore state or state changes that it does not know or care
about.
"""
pass
def delete_address_scope_precommit(self, context):
"""Delete resources for an address scope.
:param context: AddressScopeContext instance describing the
current state of the address scope, prior to the call to
delete it.
Delete address scope resources previously allocated by this
mechanism driver for an address scope. Called inside
transaction context on session. Runtime errors are not
expected, but raising an exception will result in rollback of
the transaction.
"""
pass
def delete_address_scope_postcommit(self, context):
"""Delete an address scope.
:param context: AddressScopeContext instance describing the
current state of the address scope, prior to the call to
delete it.
Called after the transaction commits. Call can block, though
will block the entire process so care should be taken to not
drastically affect performance. Runtime errors are not
expected, and will not prevent the resource from being
deleted.
"""
pass
# REVISIT(rkukura): Add precommit/postcommit calls for other
# resources implemented in ML2, such as security groups and
# security group rules?
# TODO(rkukura): Extend ExtensionDriver for address_scope,
# subnet_pool, and other resources.
@six.add_metaclass(abc.ABCMeta)
class ExtensionDriver(driver_api.ExtensionDriver):
def process_create_subnetpool(self, plugin_context, data, result):
"""Process extended attributes for create subnet pool.
:param plugin_context: plugin request context
:param data: dictionary of incoming subnet pool data
:param result: subnet pool dictionary to extend
Called inside transaction context on plugin_context.session to
validate and persist any extended subnet pool attributes
defined by this driver. Extended attribute values must also be
added to result.
"""
pass
def process_update_subnetpool(self, plugin_context, data, result):
"""Process extended attributes for update subnet pool.
:param plugin_context: plugin request context
:param data: dictionary of incoming subnet pool data
:param result: subnet pool dictionary to extend
Called inside transaction context on plugin_context.session to
validate and update any extended subnet pool attributes
defined by this driver. Extended attribute values, whether
updated or not, must also be added to result.
"""
pass
def extend_subnetpool_dict(self, session, base_model, result):
"""Add extended attributes to subnet pool dictionary.
:param session: database session
:param base_model: subnet pool model data
:param result: subnet pool dictionary to extend
Called inside transaction context on session to add any
extended attributes defined by this driver to a subnet pool
dictionary to be used for mechanism driver calls and/or
returned as the result of a subnet pool operation.
"""
pass
def process_create_address_scope(self, plugin_context, data, result):
"""Process extended attributes for create address scope.
:param plugin_context: plugin request context
:param data: dictionary of incoming address scope data
:param result: address scope dictionary to extend
Called inside transaction context on plugin_context.session to
validate and persist any extended address scope attributes
defined by this driver. Extended attribute values must also be
added to result.
"""
pass
def process_update_address_scope(self, plugin_context, data, result):
"""Process extended attributes for update address scope.
:param plugin_context: plugin request context
:param data: dictionary of incoming address scope data
:param result: address scope dictionary to extend
Called inside transaction context on plugin_context.session to
validate and update any extended address scope attributes
defined by this driver. Extended attribute values, whether
updated or not, must also be added to result.
"""
pass
def extend_address_scope_dict(self, session, base_model, result):
"""Add extended attributes to address scope dictionary.
:param session: database session
:param base_model: address scope model data
:param result: address scope dictionary to extend
Called inside transaction context on session to add any
extended attributes defined by this driver to an address scope
dictionary to be used for mechanism driver calls and/or
returned as the result of an address scope operation.
"""
pass

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.plugins.ml2 import driver_context as ml2_context
from gbpservice.neutron.plugins.ml2plus import driver_api as api
class SubnetPoolContext(ml2_context.MechanismDriverContext,
api.SubnetPoolContext):
def __init__(self, plugin, plugin_context, subnetpool,
original_subnetpool=None):
super(SubnetPoolContext, self).__init__(plugin, plugin_context)
self._subnetpool = subnetpool
self._original_subnetpool = original_subnetpool
@property
def current(self):
return self._subnetpool
@property
def original(self):
return self._original_subnetpool
class AddressScopeContext(ml2_context.MechanismDriverContext,
api.AddressScopeContext):
def __init__(self, plugin, plugin_context, address_scope,
original_address_scope=None):
super(AddressScopeContext, self).__init__(plugin, plugin_context)
self._address_scope = address_scope
self._original_address_scope = original_address_scope
@property
def current(self):
return self._address_scope
@property
def original(self):
return self._original_address_scope

View File

@ -0,0 +1,23 @@
# 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.extensions import address_scope
class Patched_address_scope(address_scope.Address_scope):
def update_attributes_map(self, attributes):
super(Patched_address_scope, self).update_attributes_map(
attributes,
extension_attrs_map=address_scope.RESOURCE_ATTRIBUTE_MAP)

View File

@ -16,9 +16,11 @@
from gbpservice.neutron.plugins.ml2plus import driver_api
from neutron._i18n import _LE
from neutron._i18n import _LI
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import managers
from oslo_log import log
from oslo_utils import excutils
LOG = log.getLogger(__name__)
@ -28,6 +30,37 @@ class MechanismManager(managers.MechanismManager):
def __init__(self):
super(MechanismManager, self).__init__()
def _call_on_extended_drivers(self, method_name, context,
continue_on_failure=False):
"""Call a method on all extended mechanism drivers.
:param method_name: name of the method to call
:param context: context parameter to pass to each method call
:param continue_on_failure: whether or not to continue to call
all mechanism drivers once one has raised an exception
:raises: neutron.plugins.ml2.common.MechanismDriverError
if any mechanism driver call fails.
"""
error = False
for driver in self.ordered_mech_drivers:
if isinstance(driver.obj, driver_api.MechanismDriver):
try:
getattr(driver.obj, method_name)(context)
except Exception:
LOG.exception(
_LE("Mechanism driver '%(name)s' failed in "
"%(method)s"),
{'name': driver.name, 'method': method_name}
)
error = True
if not continue_on_failure:
break
if error:
raise ml2_exc.MechanismDriverError(
method=method_name
)
def ensure_tenant(self, plugin_context, tenant_id):
for driver in self.ordered_mech_drivers:
if isinstance(driver.obj, driver_api.MechanismDriver):
@ -37,3 +70,108 @@ class MechanismManager(managers.MechanismManager):
LOG.exception(_LE("Mechanism driver '%s' failed in "
"ensure_tenant"), driver.name)
raise ml2_exc.MechanismDriverError(method="ensure_tenant")
def create_subnetpool_precommit(self, context):
self._call_on_extended_drivers("create_subnetpool_precommit",
context)
def create_subnetpool_postcommit(self, context):
self._call_on_extended_drivers("create_subnetpool_postcommit",
context)
def update_subnetpool_precommit(self, context):
self._call_on_extended_drivers("update_subnetpool_precommit",
context)
def update_subnetpool_postcommit(self, context):
self._call_on_extended_drivers("update_subnetpool_postcommit",
context)
def delete_subnetpool_precommit(self, context):
self._call_on_extended_drivers("delete_subnetpool_precommit",
context)
def delete_subnetpool_postcommit(self, context):
self._call_on_extended_drivers("delete_subnetpool_postcommit",
context)
def create_address_scope_precommit(self, context):
self._call_on_extended_drivers("create_address_scope_precommit",
context)
def create_address_scope_postcommit(self, context):
self._call_on_extended_drivers("create_address_scope_postcommit",
context)
def update_address_scope_precommit(self, context):
self._call_on_extended_drivers("update_address_scope_precommit",
context)
def update_address_scope_postcommit(self, context):
self._call_on_extended_drivers("update_address_scope_postcommit",
context)
def delete_address_scope_precommit(self, context):
self._call_on_extended_drivers("delete_address_scope_precommit",
context)
def delete_address_scope_postcommit(self, context):
self._call_on_extended_drivers("delete_address_scope_postcommit",
context)
class ExtensionManager(managers.ExtensionManager):
def __init__(self):
super(ExtensionManager, self).__init__()
def _call_on_extended_drivers(self, method_name, plugin_context, data,
result):
"""Call a method on all extended extension drivers."""
for driver in self.ordered_ext_drivers:
if isinstance(driver.obj, driver_api.ExtensionDriver):
try:
getattr(driver.obj, method_name)(plugin_context, data,
result)
except Exception:
with excutils.save_and_reraise_exception():
LOG.info(_LI("Extension driver '%(name)s' failed in "
"%(method)s"),
{'name': driver.name, 'method': method_name})
def _call_on_dict_extended_drivers(self, method_name, session, base_model,
result):
for driver in self.ordered_ext_drivers:
if isinstance(driver.obj, driver_api.ExtensionDriver):
try:
getattr(driver.obj, method_name)(session, base_model,
result)
except Exception:
LOG.error(_LE("Extension driver '%(name)s' failed in "
"%(method)s"),
{'name': driver.name, 'method': method_name})
raise ml2_exc.ExtensionDriverError(driver=driver.name)
def process_create_subnetpool(self, plugin_context, data, result):
self._call_on_extended_drivers("process_create_subnetpool",
plugin_context, data, result)
def process_update_subnetpool(self, plugin_context, data, result):
self._call_on_extended_drivers("process_update_subnetpool",
plugin_context, data, result)
def extend_subnetpool_dict(self, session, base_model, result):
self._call_on_dict_extended_drivers("extend_subnetpool_dict",
session, base_model, result)
def process_create_address_scope(self, plugin_context, data, result):
self._call_on_extended_drivers("process_create_address_scope",
plugin_context, data, result)
def process_update_address_scope(self, plugin_context, data, result):
self._call_on_extended_drivers("process_update_address_scope",
plugin_context, data, result)
def extend_address_scope_dict(self, session, base_model, result):
self._call_on_dict_extended_drivers("extend_address_scope_dict",
session, base_model, result)

View File

@ -0,0 +1,32 @@
# 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 gbpservice.neutron.plugins.ml2plus import extension_overrides
# Monkeypatch Neutron to allow overriding its own extension
# descriptors. Note that extension descriptor classes cannot be
# monkeypatched directly because they are loaded explicitly by file
# name and then used immediately.
_real_get_extensions_path = extensions.get_extensions_path
def get_extensions_path(service_plugins=None):
path = _real_get_extensions_path(service_plugins)
return extension_overrides.__path__[0] + ':' + path
extensions.get_extensions_path = get_extensions_path

View File

@ -13,17 +13,24 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron._i18n import _LE
from neutron._i18n import _LI
from neutron.api.v2 import attributes
from neutron.db import db_base_plugin_v2
from neutron.db import models_v2
from neutron.db import securitygroups_db
from neutron.extensions import address_scope as as_ext
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import managers as ml2_managers
from neutron.plugins.ml2 import plugin as ml2_plugin
from neutron.quota import resource_registry
from oslo_log import log
from oslo_utils import excutils
from sqlalchemy import inspect
from gbpservice.neutron.plugins.ml2plus import driver_context
from gbpservice.neutron.plugins.ml2plus import managers
from gbpservice.neutron.plugins.ml2plus import patch_neutron # noqa
LOG = log.getLogger(__name__)
@ -59,7 +66,7 @@ class Ml2PlusPlugin(ml2_plugin.Ml2Plugin):
LOG.info(_LI("Ml2Plus initializing"))
# First load drivers, then initialize DB, then initialize drivers
self.type_manager = ml2_managers.TypeManager()
self.extension_manager = ml2_managers.ExtensionManager()
self.extension_manager = managers.ExtensionManager()
self.mechanism_manager = managers.MechanismManager()
super(ml2_plugin.Ml2Plugin, self).__init__()
self.type_manager.initialize()
@ -71,6 +78,12 @@ class Ml2PlusPlugin(ml2_plugin.Ml2Plugin):
self._verify_service_plugins_requirements()
LOG.info(_LI("Modular L2 Plugin (extended) initialization complete"))
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attributes.SUBNETPOOLS, ['_ml2_md_extend_subnetpool_dict'])
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
as_ext.ADDRESS_SCOPES, ['_ml2_md_extend_address_scope_dict'])
def _ml2_md_extend_network_dict(self, result, netdb):
session = inspect(netdb).session
with session.begin(subtransactions=True):
@ -87,6 +100,29 @@ class Ml2PlusPlugin(ml2_plugin.Ml2Plugin):
self.extension_manager.extend_subnet_dict(
session, subnetdb, result)
def _ml2_md_extend_subnetpool_dict(self, result, subnetpooldb):
session = inspect(subnetpooldb).session
with session.begin(subtransactions=True):
self.extension_manager.extend_subnetpool_dict(
session, subnetpooldb, result)
def _ml2_md_extend_address_scope_dict(self, result, address_scopedb):
session = inspect(address_scopedb).session
with session.begin(subtransactions=True):
self.extension_manager.extend_address_scope_dict(
session, address_scopedb, result)
# Base version does not call _apply_dict_extend_functions()
def _make_address_scope_dict(self, address_scope, fields=None):
res = {'id': address_scope['id'],
'name': address_scope['name'],
'tenant_id': address_scope['tenant_id'],
'shared': address_scope['shared'],
'ip_version': address_scope['ip_version']}
self._apply_dict_extend_functions(as_ext.ADDRESS_SCOPES, res,
address_scope)
return self._fields(res, fields)
def create_network(self, context, network):
self._ensure_tenant(context, network[attributes.NETWORK])
return super(Ml2PlusPlugin, self).create_network(context, network)
@ -117,9 +153,110 @@ class Ml2PlusPlugin(ml2_plugin.Ml2Plugin):
return super(Ml2PlusPlugin, self).create_port_bulk(context,
ports)
# TODO(rkukura): Override address_scope, subnet_pool, and any
# other needed resources to ensure tenant and invoke mechanism and
# extension drivers.
def create_subnetpool(self, context, subnetpool):
self._ensure_tenant(context, subnetpool[attributes.SUBNETPOOL])
session = context.session
with session.begin(subtransactions=True):
result = super(Ml2PlusPlugin, self).create_subnetpool(context,
subnetpool)
self.extension_manager.process_create_subnetpool(
context, subnetpool[attributes.SUBNETPOOL], result)
mech_context = driver_context.SubnetPoolContext(
self, context, result)
self.mechanism_manager.create_subnetpool_precommit(mech_context)
try:
self.mechanism_manager.create_subnetpool_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
with excutils.save_and_reraise_exception():
LOG.error(_LE("mechanism_manager.create_subnetpool_postcommit "
"failed, deleting subnetpool '%s'"),
result['id'])
self.delete_subnetpool(context, result['id'])
return result
# REVISIT(rkukura): Is create_subnetpool_bulk() needed?
def update_subnetpool(self, context, id, subnetpool):
session = context.session
with session.begin(subtransactions=True):
original_subnetpool = super(Ml2PlusPlugin, self).get_subnetpool(
context, id)
updated_subnetpool = super(Ml2PlusPlugin, self).update_subnetpool(
context, id, subnetpool)
self.extension_manager.process_update_subnetpool(
context, subnetpool[attributes.SUBNETPOOL],
updated_subnetpool)
mech_context = driver_context.SubnetPoolContext(
self, context, updated_subnetpool,
original_subnetpool=original_subnetpool)
self.mechanism_manager.update_subnetpool_precommit(mech_context)
self.mechanism_manager.update_subnetpool_postcommit(mech_context)
return updated_subnetpool
def delete_subnetpool(self, context, id):
session = context.session
with session.begin(subtransactions=True):
subnetpool = super(Ml2PlusPlugin, self).get_subnetpool(context, id)
super(Ml2PlusPlugin, self).delete_subnetpool(context, id)
mech_context = driver_context.SubnetPoolContext(
self, context, subnetpool)
self.mechanism_manager.delete_subnetpool_precommit(mech_context)
self.mechanism_manager.delete_subnetpool_postcommit(mech_context)
def create_address_scope(self, context, address_scope):
self._ensure_tenant(context, address_scope[as_ext.ADDRESS_SCOPE])
session = context.session
with session.begin(subtransactions=True):
result = super(Ml2PlusPlugin, self).create_address_scope(
context, address_scope)
self.extension_manager.process_create_address_scope(
context, address_scope[as_ext.ADDRESS_SCOPE], result)
mech_context = driver_context.AddressScopeContext(
self, context, result)
self.mechanism_manager.create_address_scope_precommit(
mech_context)
try:
self.mechanism_manager.create_address_scope_postcommit(
mech_context)
except ml2_exc.MechanismDriverError:
with excutils.save_and_reraise_exception():
LOG.error(_LE("mechanism_manager.create_address_scope_"
"postcommit failed, deleting address_scope"
" '%s'"),
result['id'])
self.delete_address_scope(context, result['id'])
return result
# REVISIT(rkukura): Is create_address_scope_bulk() needed?
def update_address_scope(self, context, id, address_scope):
session = context.session
with session.begin(subtransactions=True):
original_address_scope = super(Ml2PlusPlugin,
self).get_address_scope(context, id)
updated_address_scope = super(Ml2PlusPlugin,
self).update_address_scope(
context, id, address_scope)
self.extension_manager.process_update_address_scope(
context, address_scope[as_ext.ADDRESS_SCOPE],
updated_address_scope)
mech_context = driver_context.AddressScopeContext(
self, context, updated_address_scope,
original_address_scope=original_address_scope)
self.mechanism_manager.update_address_scope_precommit(mech_context)
self.mechanism_manager.update_address_scope_postcommit(mech_context)
return updated_address_scope
def delete_address_scope(self, context, id):
session = context.session
with session.begin(subtransactions=True):
address_scope = super(Ml2PlusPlugin, self).get_address_scope(
context, id)
super(Ml2PlusPlugin, self).delete_address_scope(context, id)
mech_context = driver_context.AddressScopeContext(
self, context, address_scope)
self.mechanism_manager.delete_address_scope_precommit(mech_context)
self.mechanism_manager.delete_address_scope_postcommit(mech_context)
def _ensure_tenant(self, context, resource):
tenant_id = resource['tenant_id']

View File

@ -0,0 +1,176 @@
# 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.api.v2 import attributes
from neutron.db import address_scope_db as as_db
from neutron.db import model_base
from neutron.db import models_v2
import oslo_db.sqlalchemy.session
import sqlalchemy as sa
from sqlalchemy import orm
from gbpservice.neutron.plugins.ml2plus import driver_api
from gbpservice.neutron.tests.unit.plugins.ml2plus import (
extensions as test_extensions)
class TestExtensionDriverBase(driver_api.ExtensionDriver):
_supported_extension_aliases = 'ml2plus_fake_extension'
def initialize(self):
# REVISIT(rkukura): Needed?
extensions.append_api_extensions_path(test_extensions.__path__)
@property
def extension_alias(self):
return self._supported_extension_aliases
class TestExtensionDriver(TestExtensionDriverBase):
def initialize(self):
super(TestExtensionDriver, self).initialize()
self.subnetpool_extension = 'Test_SubnetPool_Extension'
self.address_scope_extension = 'Test_AddressScope_Extension'
def _check_create(self, session, data, result):
assert(isinstance(session, oslo_db.sqlalchemy.session.Session))
assert(isinstance(data, dict))
assert('id' not in data)
assert(isinstance(result, dict))
assert(result['id'] is not None)
def _check_update(self, session, data, result):
assert(isinstance(session, oslo_db.sqlalchemy.session.Session))
assert(isinstance(data, dict))
assert(isinstance(result, dict))
assert(result['id'] is not None)
def _check_extend(self, session, result, db_entry,
expected_db_entry_class):
assert(isinstance(session, oslo_db.sqlalchemy.session.Session))
assert(isinstance(result, dict))
assert(result['id'] is not None)
assert(isinstance(db_entry, expected_db_entry_class))
assert(db_entry.id == result['id'])
def process_create_subnetpool(self, plugin_context, data, result):
session = plugin_context.session
self._check_create(session, data, result)
result['subnetpool_extension'] = self.subnetpool_extension + '_create'
def process_update_subnetpool(self, plugin_context, data, result):
session = plugin_context.session
self._check_update(session, data, result)
self.subnetpool_extension = data['subnetpool_extension']
result['subnetpool_extension'] = self.subnetpool_extension + '_update'
def extend_subnetpool_dict(self, session, subnetpool_db, result):
self._check_extend(session, result, subnetpool_db,
models_v2.SubnetPool)
result['subnetpool_extension'] = self.subnetpool_extension + '_extend'
def process_create_address_scope(self, plugin_context, data, result):
session = plugin_context.session
self._check_create(session, data, result)
result['address_scope_extension'] = (self.address_scope_extension +
'_create')
def process_update_address_scope(self, plugin_context, data, result):
session = plugin_context.session
self._check_update(session, data, result)
self.address_scope_extension = data['address_scope_extension']
result['address_scope_extension'] = (self.address_scope_extension +
'_update')
def extend_address_scope_dict(self, session, address_scope_db, result):
self._check_extend(session, result, address_scope_db,
as_db.AddressScope)
result['address_scope_extension'] = (self.address_scope_extension +
'_extend')
class TestSubnetPoolExtension(model_base.BASEV2):
subnetpool_id = sa.Column(sa.String(36),
sa.ForeignKey('subnetpools.id',
ondelete="CASCADE"),
primary_key=True)
value = sa.Column(sa.String(64))
subnetpool = orm.relationship(
models_v2.SubnetPool,
backref=orm.backref('extension', cascade='delete', uselist=False))
class TestAddressScopeExtension(model_base.BASEV2):
address_scope_id = sa.Column(sa.String(36),
sa.ForeignKey('address_scopes.id',
ondelete="CASCADE"),
primary_key=True)
value = sa.Column(sa.String(64))
address_scope = orm.relationship(
as_db.AddressScope,
backref=orm.backref('extension', cascade='delete', uselist=False))
class TestDBExtensionDriver(TestExtensionDriverBase):
def _get_value(self, data, key):
value = data[key]
if not attributes.is_attr_set(value):
value = ''
return value
def process_create_subnetpool(self, plugin_context, data, result):
session = plugin_context.session
value = self._get_value(data, 'subnetpool_extension')
record = TestSubnetPoolExtension(subnetpool_id=result['id'],
value=value)
session.add(record)
result['subnetpool_extension'] = value
def process_update_subnetpool(self, plugin_context, data, result):
session = plugin_context.session
record = (session.query(TestSubnetPoolExtension).
filter_by(subnetpool_id=result['id']).one())
value = data.get('subnetpool_extension')
if value and value != record.value:
record.value = value
result['subnetpool_extension'] = record.value
def extend_subnetpool_dict(self, session, subnetpool_db, result):
value = (subnetpool_db.extension.value
if subnetpool_db.extension else '')
result['subnetpool_extension'] = value
def process_create_address_scope(self, plugin_context, data, result):
session = plugin_context.session
value = self._get_value(data, 'address_scope_extension')
record = TestAddressScopeExtension(address_scope_id=result['id'],
value=value)
session.add(record)
result['address_scope_extension'] = value
def process_update_address_scope(self, plugin_context, data, result):
session = plugin_context.session
record = (session.query(TestAddressScopeExtension).
filter_by(address_scope_id=result['id']).one())
value = data.get('address_scope_extension')
if value and value != record.value:
record.value = value
result['address_scope_extension'] = record.value
def extend_address_scope_dict(self, session, address_scope_db, result):
value = (address_scope_db.extension.value
if address_scope_db.extension else '')
result['address_scope_extension'] = value

View File

@ -35,3 +35,65 @@ class LoggerPlusMechanismDriver(driver_api.MechanismDriver,
def ensure_tenant(self, plugin_context, tenant_id):
LOG.info(_LI("ensure_tenant called with tenant_id %s"), tenant_id)
def _log_subnetpool_call(self, method_name, context):
LOG.info(_("%(method)s called with subnetpool settings %(current)s "
"(original settings %(original)s)"),
{'method': method_name,
'current': context.current,
'original': context.original})
def create_subnetpool_precommit(self, context):
self._log_subnetpool_call("create_subnetpool_precommit",
context)
def create_subnetpool_postcommit(self, context):
self._log_subnetpool_call("create_subnetpool_postcommit",
context)
def update_subnetpool_precommit(self, context):
self._log_subnetpool_call("update_subnetpool_precommit",
context)
def update_subnetpool_postcommit(self, context):
self._log_subnetpool_call("update_subnetpool_postcommit",
context)
def delete_subnetpool_precommit(self, context):
self._log_subnetpool_call("delete_subnetpool_precommit",
context)
def delete_subnetpool_postcommit(self, context):
self._log_subnetpool_call("delete_subnetpool_postcommit",
context)
def _log_address_scope_call(self, method_name, context):
LOG.info(_("%(method)s called with address_scope settings %(current)s "
"(original settings %(original)s)"),
{'method': method_name,
'current': context.current,
'original': context.original})
def create_address_scope_precommit(self, context):
self._log_address_scope_call("create_address_scope_precommit",
context)
def create_address_scope_postcommit(self, context):
self._log_address_scope_call("create_address_scope_postcommit",
context)
def update_address_scope_precommit(self, context):
self._log_address_scope_call("update_address_scope_precommit",
context)
def update_address_scope_postcommit(self, context):
self._log_address_scope_call("update_address_scope_postcommit",
context)
def delete_address_scope_precommit(self, context):
self._log_address_scope_call("delete_address_scope_precommit",
context)
def delete_address_scope_postcommit(self, context):
self._log_address_scope_call("delete_address_scope_postcommit",
context)

View File

@ -0,0 +1,62 @@
# 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 _
from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.extensions import address_scope as as_ext
EXTENDED_ATTRIBUTES_2_0 = {
attr.SUBNETPOOLS: {
'subnetpool_extension': {'allow_post': True,
'allow_put': True,
'default': attr.ATTR_NOT_SPECIFIED,
'is_visible': True,
'enforce_policy': True},
},
as_ext.ADDRESS_SCOPES: {
'address_scope_extension': {'allow_post': True,
'allow_put': True,
'default': attr.ATTR_NOT_SPECIFIED,
'is_visible': True,
'enforce_policy': True},
},
}
class Fake_extension(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "ML2Plus fake extension"
@classmethod
def get_alias(cls):
return "ml2plus_fake_extension"
@classmethod
def get_description(cls):
return _("Adds test attributes to ML2Plus resources.")
@classmethod
def get_updated(cls):
return "2016-08-02T10:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}

View File

@ -0,0 +1,312 @@
# 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.
import mock
import uuid
from neutron import context
from neutron import manager
from neutron.plugins.ml2 import config
from gbpservice.neutron.tests.unit.plugins.ml2plus.drivers import (
extension_test as ext_test)
from gbpservice.neutron.tests.unit.plugins.ml2plus import test_plugin
class ExtensionDriverTestCase(test_plugin.Ml2PlusPluginV2TestCase):
_extension_drivers = ['test_ml2plus']
def setUp(self):
config.cfg.CONF.set_override('extension_drivers',
self._extension_drivers,
group='ml2')
super(ExtensionDriverTestCase, self).setUp()
self._plugin = manager.NeutronManager.get_plugin()
self._ctxt = context.get_admin_context()
def _verify_subnetpool_create(self, code, exc_reason):
tenant_id = str(uuid.uuid4())
data = {'subnetpool': {'prefixes': ['10.0.0.0/8'],
'name': 'sp1',
'tenant_id': tenant_id}}
req = self.new_create_request('subnetpools', data)
res = req.get_response(self.api)
self.assertEqual(code, res.status_int)
subnetpool = self.deserialize(self.fmt, res)
if exc_reason:
self.assertEqual(exc_reason,
subnetpool['NeutronError']['type'])
return (subnetpool, tenant_id)
def _verify_subnetpool_update(self, subnetpool, code, exc_reason):
sp_id = subnetpool['subnetpool']['id']
new_name = 'a_brand_new_name'
data = {'subnetpool': {'name': new_name}}
req = self.new_update_request('subnetpools', data, sp_id)
res = req.get_response(self.api)
self.assertEqual(code, res.status_int)
error = self.deserialize(self.fmt, res)
self.assertEqual(exc_reason,
error['NeutronError']['type'])
def test_faulty_process_create_subnetpool(self):
with mock.patch.object(ext_test.TestExtensionDriver,
'process_create_subnetpool',
side_effect=TypeError):
subnetpool, tenant_id = self._verify_subnetpool_create(
500, 'HTTPInternalServerError')
# Verify the operation is rolled back
query_params = "tenant_id=%s" % tenant_id
subnetpools = self._list('subnetpools', query_params=query_params)
self.assertFalse(subnetpools['subnetpools'])
def test_faulty_process_update_subnetpool(self):
with mock.patch.object(ext_test.TestExtensionDriver,
'process_update_subnetpool',
side_effect=TypeError):
subnetpool, tid = self._verify_subnetpool_create(201, None)
self._verify_subnetpool_update(subnetpool, 500,
'HTTPInternalServerError')
def test_faulty_extend_subnetpool_dict(self):
with mock.patch.object(ext_test.TestExtensionDriver,
'extend_subnetpool_dict',
side_effect=[None, None, TypeError]):
subnetpool, tid = self._verify_subnetpool_create(201, None)
self._verify_subnetpool_update(subnetpool, 400,
'ExtensionDriverError')
def test_subnetpool_attr(self):
with self.subnetpool(['10.0.0.0/8'], name='sp1',
tenant_id='t1') as subnetpool:
# Test create subnetpool
ent = subnetpool['subnetpool'].get('subnetpool_extension')
self.assertIsNotNone(ent)
# Test list subnetpools
res = self._list('subnetpools')
val = res['subnetpools'][0].get('subnetpool_extension')
self.assertEqual('Test_SubnetPool_Extension_extend', val)
# Test subnetpool update
data = {'subnetpool':
{'subnetpool_extension':
'Test_SubnetPool_Extension_Update'}}
res = self._update('subnetpools', subnetpool['subnetpool']['id'],
data)
val = res['subnetpool'].get('subnetpool_extension')
self.assertEqual('Test_SubnetPool_Extension_Update_update', val)
def test_extend_subnetpool_dict(self):
with mock.patch.object(
ext_test.TestExtensionDriver,
'process_update_subnetpool') as pus, mock.patch.object(
ext_test.TestExtensionDriver,
'extend_subnetpool_dict') as esd, self.subnetpool(
['10.0.0.0/8'], name='sp1',
tenant_id='t1') as subnetpool:
subnetpool_id = subnetpool['subnetpool']['id']
subnetpool_data = {'subnetpool': {'id': subnetpool_id}}
self._plugin.update_subnetpool(self._ctxt, subnetpool_id,
subnetpool_data)
self.assertTrue(pus.called)
self.assertTrue(esd.called)
def _verify_address_scope_create(self, code, exc_reason):
tenant_id = str(uuid.uuid4())
data = {'address_scope': {'ip_version': 4,
'name': 'as1',
'tenant_id': tenant_id}}
req = self.new_create_request('address-scopes', data)
res = req.get_response(self.ext_api)
self.assertEqual(code, res.status_int)
address_scope = self.deserialize(self.fmt, res)
if exc_reason:
self.assertEqual(exc_reason,
address_scope['NeutronError']['type'])
return (address_scope, tenant_id)
def _verify_address_scope_update(self, address_scope, code, exc_reason):
as_id = address_scope['address_scope']['id']
new_name = 'a_brand_new_name'
data = {'address_scope': {'name': new_name}}
req = self.new_update_request('address-scopes', data, as_id)
res = req.get_response(self.ext_api)
self.assertEqual(code, res.status_int)
error = self.deserialize(self.fmt, res)
self.assertEqual(exc_reason,
error['NeutronError']['type'])
def test_faulty_process_create_address_scope(self):
with mock.patch.object(ext_test.TestExtensionDriver,
'process_create_address_scope',
side_effect=TypeError):
address_scope, tenant_id = self._verify_address_scope_create(
500, 'HTTPInternalServerError')
# Verify the operation is rolled back
query_params = "tenant_id=%s" % tenant_id
address_scopes = self._list('address-scopes',
query_params=query_params)
self.assertFalse(address_scopes['address_scopes'])
def test_faulty_process_update_address_scope(self):
with mock.patch.object(ext_test.TestExtensionDriver,
'process_update_address_scope',
side_effect=TypeError):
address_scope, tid = self._verify_address_scope_create(201, None)
self._verify_address_scope_update(address_scope, 500,
'HTTPInternalServerError')
def test_faulty_extend_address_scope_dict(self):
with mock.patch.object(ext_test.TestExtensionDriver,
'extend_address_scope_dict',
side_effect=[None, None, TypeError]):
address_scope, tid = self._verify_address_scope_create(201, None)
self._verify_address_scope_update(address_scope, 400,
'ExtensionDriverError')
def test_address_scope_attr(self):
with self.address_scope(4, name='as1',
tenant_id='t1') as address_scope:
# Test create address_scope
ent = address_scope['address_scope'].get('address_scope_extension')
self.assertIsNotNone(ent)
# Test list address_scopes
res = self._list('address-scopes')
val = res['address_scopes'][0].get('address_scope_extension')
self.assertEqual('Test_AddressScope_Extension_extend', val)
# Test address_scope update
data = {'address_scope':
{'address_scope_extension':
'Test_AddressScope_Extension_Update'}}
res = self._update('address-scopes',
address_scope['address_scope']['id'], data)
val = res['address_scope'].get('address_scope_extension')
self.assertEqual('Test_AddressScope_Extension_Update_update', val)
def test_extend_address_scope_dict(self):
with mock.patch.object(
ext_test.TestExtensionDriver,
'process_update_address_scope') as puas, mock.patch.object(
ext_test.TestExtensionDriver,
'extend_address_scope_dict') as easd, self.address_scope(
4, name='as1', tenant_id='t1') as address_scope:
address_scope_id = address_scope['address_scope']['id']
address_scope_data = {'address_scope': {'id': address_scope_id}}
self._plugin.update_address_scope(self._ctxt, address_scope_id,
address_scope_data)
self.assertTrue(puas.called)
self.assertTrue(easd.called)
class DBExtensionDriverTestCase(test_plugin.Ml2PlusPluginV2TestCase):
_extension_drivers = ['testdb_ml2plus']
def setUp(self):
config.cfg.CONF.set_override('extension_drivers',
self._extension_drivers,
group='ml2')
super(DBExtensionDriverTestCase, self).setUp()
self._plugin = manager.NeutronManager.get_plugin()
self._ctxt = context.get_admin_context()
def test_subnetpool_attr(self):
with self.subnetpool(['10.0.0.0/8'], name='sp1',
tenant_id='t1') as subnetpool:
# Test create with default value.
sp_id = subnetpool['subnetpool']['id']
val = subnetpool['subnetpool']['subnetpool_extension']
self.assertEqual("", val)
res = self._show('subnetpools', sp_id)
val = res['subnetpool']['subnetpool_extension']
self.assertEqual("", val)
# Test list.
res = self._list('subnetpools')
val = res['subnetpools'][0]['subnetpool_extension']
self.assertEqual("", val)
# Test create with explicit value.
data = {'subnetpool':
{'prefixes': ['10.0.0.0/8'],
'name': 'sp2',
'tenant_id': 't1',
'subnetpool_extension': 'abc'}}
req = self.new_create_request('subnetpools', data, self.fmt)
res = req.get_response(self.api)
subnetpool = self.deserialize(self.fmt, res)
subnetpool_id = subnetpool['subnetpool']['id']
val = subnetpool['subnetpool']['subnetpool_extension']
self.assertEqual("abc", val)
res = self._show('subnetpools', subnetpool_id)
val = res['subnetpool']['subnetpool_extension']
self.assertEqual("abc", val)
# Test update.
data = {'subnetpool': {'subnetpool_extension': "def"}}
res = self._update('subnetpools', subnetpool_id, data)
val = res['subnetpool']['subnetpool_extension']
self.assertEqual("def", val)
res = self._show('subnetpools', subnetpool_id)
val = res['subnetpool']['subnetpool_extension']
self.assertEqual("def", val)
def test_address_scope_attr(self):
with self.address_scope(4, name='as1',
tenant_id='t1') as address_scope:
# Test create with default value.
as_id = address_scope['address_scope']['id']
val = address_scope['address_scope']['address_scope_extension']
self.assertEqual("", val)
res = self._show('address-scopes', as_id)
val = res['address_scope']['address_scope_extension']
self.assertEqual("", val)
# Test list.
res = self._list('address-scopes')
val = res['address_scopes'][0]['address_scope_extension']
self.assertEqual("", val)
# Test create with explicit value.
data = {'address_scope':
{'ip_version': 4,
'name': 'as2',
'tenant_id': 't1',
'address_scope_extension': 'abc'}}
req = self.new_create_request('address-scopes', data, self.fmt)
res = req.get_response(self.ext_api)
address_scope = self.deserialize(self.fmt, res)
address_scope_id = address_scope['address_scope']['id']
val = address_scope['address_scope']['address_scope_extension']
self.assertEqual("abc", val)
res = self._show('address-scopes', address_scope_id)
val = res['address_scope']['address_scope_extension']
self.assertEqual("abc", val)
# Test update.
data = {'address_scope': {'address_scope_extension': "def"}}
res = self._update('address-scopes', address_scope_id, data)
val = res['address_scope']['address_scope_extension']
self.assertEqual("def", val)
res = self._show('address-scopes', address_scope_id)
val = res['address_scope']['address_scope_extension']
self.assertEqual("def", val)

View File

@ -15,9 +15,12 @@
import mock
from neutron.api import extensions
from neutron import manager
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 gbpservice.neutron.tests.unit.plugins.ml2plus.drivers import (
mechanism_logger as mech_logger)
@ -28,7 +31,7 @@ PLUGIN_NAME = 'gbpservice.neutron.plugins.ml2plus.plugin.Ml2PlusPlugin'
# This is just a quick sanity test that basic ML2 plugin functionality
# is preserved.
class Ml2PlusPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
class Ml2PlusPluginV2TestCase(test_address_scope.AddressScopeTestCase):
def setUp(self):
# Enable the test mechanism driver to ensure that
@ -41,6 +44,8 @@ class Ml2PlusPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
['physnet1:1000:1099'],
group='ml2_type_vlan')
super(Ml2PlusPluginV2TestCase, self).setUp(PLUGIN_NAME)
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
self.port_create_status = 'DOWN'
self.plugin = manager.NeutronManager.get_plugin()
self.plugin.start_rpc_listeners()
@ -136,6 +141,145 @@ class TestEnsureTenant(Ml2PlusPluginV2TestCase):
any_order=True)
self.assertEqual(2, et.call_count)
def test_subnetpool(self):
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'ensure_tenant') as et:
self._make_subnetpool(self.fmt, ['10.0.0.0/8'], name='sp1',
tenant_id='t1')
et.assert_called_once_with(mock.ANY, 't1')
def test_address_scope(self):
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'ensure_tenant') as et:
self._make_address_scope(self.fmt, 4, name='as1', tenant_id='t1')
et.assert_called_once_with(mock.ANY, 't1')
class TestSubnetPool(Ml2PlusPluginV2TestCase):
def test_create(self):
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'create_subnetpool_precommit') as pre:
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'create_subnetpool_postcommit') as post:
self._make_subnetpool(self.fmt, ['10.0.0.0/8'], name='sp1',
tenant_id='t1')
self.assertEqual(1, pre.call_count)
self.assertEqual('sp1',
pre.call_args[0][0].current['name'])
self.assertIsNone(pre.call_args[0][0].original)
self.assertEqual(1, post.call_count)
self.assertEqual('sp1',
post.call_args[0][0].current['name'])
self.assertIsNone(post.call_args[0][0].original)
def test_update(self):
subnetpool = self._make_subnetpool(
self.fmt, ['10.0.0.0/8'], name='sp1', tenant_id='t1')['subnetpool']
data = {'subnetpool': {'name': 'newnameforsubnetpool'}}
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'update_subnetpool_precommit') as pre:
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'update_subnetpool_postcommit') as post:
res = self._update('subnetpools', subnetpool['id'],
data)['subnetpool']
self.assertEqual('newnameforsubnetpool', res['name'])
self.assertEqual(1, pre.call_count)
self.assertEqual('newnameforsubnetpool',
pre.call_args[0][0].current['name'])
self.assertEqual('sp1',
pre.call_args[0][0].original['name'])
self.assertEqual(1, post.call_count)
self.assertEqual('newnameforsubnetpool',
post.call_args[0][0].current['name'])
self.assertEqual('sp1',
post.call_args[0][0].original['name'])
def test_delete(self):
subnetpool = self._make_subnetpool(
self.fmt, ['10.0.0.0/8'], name='sp1', tenant_id='t1')['subnetpool']
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'delete_subnetpool_precommit') as pre:
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'delete_subnetpool_postcommit') as post:
self._delete('subnetpools', subnetpool['id'])
self.assertEqual(1, pre.call_count)
self.assertEqual('sp1',
pre.call_args[0][0].current['name'])
self.assertIsNone(pre.call_args[0][0].original)
self.assertEqual(1, post.call_count)
self.assertEqual('sp1',
post.call_args[0][0].current['name'])
self.assertIsNone(post.call_args[0][0].original)
class TestAddressScope(Ml2PlusPluginV2TestCase):
def test_create(self):
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'create_address_scope_precommit') as pre:
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'create_address_scope_postcommit') as post:
self._make_address_scope(self.fmt, 4, name='as1',
tenant_id='t1')
self.assertEqual(1, pre.call_count)
self.assertEqual('as1',
pre.call_args[0][0].current['name'])
self.assertIsNone(pre.call_args[0][0].original)
self.assertEqual(1, post.call_count)
self.assertEqual('as1',
post.call_args[0][0].current['name'])
self.assertIsNone(post.call_args[0][0].original)
def test_update(self):
address_scope = self._make_address_scope(
self.fmt, 4, name='as1', tenant_id='t1')['address_scope']
data = {'address_scope': {'name': 'newnameforaddress_scope'}}
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'update_address_scope_precommit') as pre:
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'update_address_scope_postcommit') as post:
res = self._update('address-scopes', address_scope['id'],
data)['address_scope']
self.assertEqual('newnameforaddress_scope', res['name'])
self.assertEqual(1, pre.call_count)
self.assertEqual('newnameforaddress_scope',
pre.call_args[0][0].current['name'])
self.assertEqual('as1',
pre.call_args[0][0].original['name'])
self.assertEqual(1, post.call_count)
self.assertEqual('newnameforaddress_scope',
post.call_args[0][0].current['name'])
self.assertEqual('as1',
post.call_args[0][0].original['name'])
def test_delete(self):
address_scope = self._make_address_scope(
self.fmt, 4, name='as1', tenant_id='t1')['address_scope']
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'delete_address_scope_precommit') as pre:
with mock.patch.object(mech_logger.LoggerPlusMechanismDriver,
'delete_address_scope_postcommit') as post:
self._delete('address-scopes', address_scope['id'])
self.assertEqual(1, pre.call_count)
self.assertEqual('as1',
pre.call_args[0][0].current['name'])
self.assertIsNone(pre.call_args[0][0].original)
self.assertEqual(1, post.call_count)
self.assertEqual('as1',
post.call_args[0][0].current['name'])
self.assertIsNone(post.call_args[0][0].original)
class TestMl2BasicGet(test_plugin.TestBasicGet,
Ml2PlusPluginV2TestCase):

View File

@ -78,6 +78,8 @@ neutron.ml2.mechanism_drivers =
stitching_gbp = gbpservice.neutron.plugins.ml2.drivers.grouppolicy.stitching.driver:TrafficStitchingMechanismGBPDriver
neutron.ml2.extension_drivers =
apic_aim = gbpservice.neutron.plugins.ml2plus.drivers.apic_aim.extension_driver:ApicExtensionDriver
test_ml2plus = gbpservice.neutron.tests.unit.plugins.ml2plus.drivers.extension_test:TestExtensionDriver
testdb_ml2plus = gbpservice.neutron.tests.unit.plugins.ml2plus.drivers.extension_test:TestDBExtensionDriver
gbpservice.neutron.servicechain.servicechain_drivers =
dummy = gbpservice.neutron.services.servicechain.plugins.msc.drivers.dummy_driver:NoopDriver
simplechain_driver = gbpservice.neutron.services.servicechain.plugins.msc.drivers.simplechain_driver:SimpleChainDriver