Add flavor/service provider support to routers
This is the initial support for flavors and multiple service providers with the built-in L3 service plugin. This patch handles a few key components: * Adds an optional flavor_id to the router data model * Adds a new driver controller that performs the following tasks: * Loads up the configured drivers and 4 default drivers representing the current matrix of ha/dvr options (single node, ha, dvr, and ha+dvr) * Associates every router with a driver based on ha/dvr attributes or the flavor_id if specified Note that the current drivers are very limited because they don't do anything. All of the complex logic for the in-tree drivers is still tied up in the giant mixin the service plugin inherits. Breaking that apart will be in follow-up patches. Partially-Implements: blueprint multi-l3-backends Change-Id: Idce75bf0fc1375dcbbff9b9803fd2fe97d158cff
This commit is contained in:
parent
414f2ffc8d
commit
0e3f4b8335
|
@ -95,6 +95,8 @@ class Router(model_base.HasStandardAttributes, model_base.BASEV2,
|
|||
admin_state_up = sa.Column(sa.Boolean)
|
||||
gw_port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id'))
|
||||
gw_port = orm.relationship(models_v2.Port, lazy='joined')
|
||||
flavor_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey("flavors.id"), nullable=True)
|
||||
attached_ports = orm.relationship(
|
||||
RouterPort,
|
||||
backref='router',
|
||||
|
|
|
@ -1 +1 @@
|
|||
a963b38d82f4
|
||||
3d0e74aa7d37
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# Copyright 2016 Mirantis
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""Add flavor_id to Router
|
||||
|
||||
Revision ID: 3d0e74aa7d37
|
||||
Revises: a963b38d82f4
|
||||
Create Date: 2016-05-05 00:22:47.618593
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from neutron.db import migration
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3d0e74aa7d37'
|
||||
down_revision = 'a963b38d82f4'
|
||||
|
||||
# milestone identifier, used by neutron-db-manage
|
||||
neutron_milestone = [migration.NEWTON]
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('routers',
|
||||
sa.Column('flavor_id',
|
||||
sa.String(length=36),
|
||||
sa.ForeignKey('flavors.id'),
|
||||
nullable=True))
|
|
@ -0,0 +1,55 @@
|
|||
#
|
||||
# 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_lib import constants
|
||||
|
||||
from neutron.api import extensions
|
||||
|
||||
EXTENDED_ATTRIBUTES_2_0 = {
|
||||
'routers': {
|
||||
'flavor_id': {'allow_post': True, 'allow_put': False,
|
||||
'default': constants.ATTR_NOT_SPECIFIED,
|
||||
'is_visible': True, 'enforce_policy': True}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class L3_flavors(extensions.ExtensionDescriptor):
|
||||
"""Extension class supporting flavors for routers."""
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "Router Flavor Extension"
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return 'l3-flavors'
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return "Flavor support for routers."
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return "2016-05-17T00:00:00-00:00"
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return EXTENDED_ATTRIBUTES_2_0
|
||||
else:
|
||||
return {}
|
||||
|
||||
def get_required_extensions(self):
|
||||
return ["router", "flavors"]
|
|
@ -30,9 +30,11 @@ from neutron.db import l3_dvr_ha_scheduler_db
|
|||
from neutron.db import l3_dvrscheduler_db
|
||||
from neutron.db import l3_gwmode_db
|
||||
from neutron.db import l3_hamode_db
|
||||
from neutron.extensions import l3
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.quota import resource_registry
|
||||
from neutron import service
|
||||
from neutron.services.l3_router.service_providers import driver_controller
|
||||
from neutron.services import service_base
|
||||
|
||||
|
||||
|
@ -55,7 +57,8 @@ class L3RouterPlugin(service_base.ServicePluginBase,
|
|||
"""
|
||||
supported_extension_aliases = ["dvr", "router", "ext-gw-mode",
|
||||
"extraroute", "l3_agent_scheduler",
|
||||
"l3-ha", "router_availability_zone"]
|
||||
"l3-ha", "router_availability_zone",
|
||||
"l3-flavors"]
|
||||
|
||||
__native_pagination_support = True
|
||||
__native_sorting_support = True
|
||||
|
@ -76,6 +79,7 @@ class L3RouterPlugin(service_base.ServicePluginBase,
|
|||
rpc_worker = service.RpcWorker([self], worker_process_count=0)
|
||||
|
||||
self.add_worker(rpc_worker)
|
||||
self.l3_driver_controller = driver_controller.DriverController(self)
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def start_rpc_listeners(self):
|
||||
|
@ -111,3 +115,11 @@ class L3RouterPlugin(service_base.ServicePluginBase,
|
|||
return super(L3RouterPlugin, self).create_floatingip(
|
||||
context, floatingip,
|
||||
initial_status=n_const.FLOATINGIP_STATUS_DOWN)
|
||||
|
||||
|
||||
def add_flavor_id(plugin, router_res, router_db):
|
||||
router_res['flavor_id'] = router_db['flavor_id']
|
||||
|
||||
|
||||
common_db_mixin.CommonDbMixin.register_dict_extend_funcs(
|
||||
l3.ROUTERS, [add_flavor_id])
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
# 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 _
|
||||
|
||||
|
||||
class _FeatureFlag(object):
|
||||
|
||||
def is_compatible(self, value):
|
||||
if value == self.requires:
|
||||
return True
|
||||
if value and self.supports:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __init__(self, supports, requires):
|
||||
self.supports = supports
|
||||
self.requires = requires
|
||||
if requires and not supports:
|
||||
raise RuntimeError(_("A driver can't require a feature and not "
|
||||
"support it."))
|
||||
|
||||
UNSUPPORTED = _FeatureFlag(supports=False, requires=False)
|
||||
OPTIONAL = _FeatureFlag(supports=True, requires=False)
|
||||
MANDATORY = _FeatureFlag(supports=True, requires=True)
|
||||
|
||||
|
||||
class L3ServiceProvider(object):
|
||||
"""Base class for L3 service providers.
|
||||
|
||||
On __init__ this will be given a handle to the l3 plugin. It is then the
|
||||
responsibility of the driver to subscribe to the events it is interested
|
||||
in (e.g. router_create, router_update, router_delete, etc).
|
||||
|
||||
The 'ha' and 'distributed' attributes below are used to determine if a
|
||||
router request with the 'ha' or 'distributed' attribute can be supported
|
||||
by this particular driver. These attributes must be present.
|
||||
"""
|
||||
|
||||
ha_support = UNSUPPORTED
|
||||
distributed_support = UNSUPPORTED
|
||||
|
||||
def __init__(self, l3plugin):
|
||||
self.l3plugin = l3plugin
|
|
@ -0,0 +1,249 @@
|
|||
# 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_lib import constants as lib_const
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.callbacks import events
|
||||
from neutron.callbacks import registry
|
||||
from neutron.callbacks import resources
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services import provider_configuration
|
||||
from neutron.services import service_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DriverController(object):
|
||||
"""Driver controller for the L3 service plugin.
|
||||
|
||||
This component is responsible for dispatching router requests to L3
|
||||
service providers and for performing the bookkeeping about which
|
||||
driver is associated with a given router.
|
||||
|
||||
This is not intended to be accessed by the drivers or the l3 plugin.
|
||||
All of the methods are marked as private to reflect this.
|
||||
"""
|
||||
|
||||
def __init__(self, l3_plugin):
|
||||
self.l3_plugin = l3_plugin
|
||||
self._stm = st_db.ServiceTypeManager.get_instance()
|
||||
self._stm.add_provider_configuration(
|
||||
constants.L3_ROUTER_NAT, _LegacyPlusProviderConfiguration())
|
||||
self._load_drivers()
|
||||
registry.subscribe(self._set_router_provider,
|
||||
resources.ROUTER, events.PRECOMMIT_CREATE)
|
||||
registry.subscribe(self._update_router_provider,
|
||||
resources.ROUTER, events.PRECOMMIT_UPDATE)
|
||||
registry.subscribe(self._clear_router_provider,
|
||||
resources.ROUTER, events.PRECOMMIT_DELETE)
|
||||
|
||||
def _load_drivers(self):
|
||||
self.drivers, self.default_provider = (
|
||||
service_base.load_drivers(constants.L3_ROUTER_NAT, self.l3_plugin))
|
||||
# store the provider name on each driver to make finding inverse easy
|
||||
for provider_name, driver in self.drivers.items():
|
||||
setattr(driver, 'name', provider_name)
|
||||
|
||||
@property
|
||||
def _flavor_plugin(self):
|
||||
if not hasattr(self, '_flavor_plugin_ref'):
|
||||
self._flavor_plugin = manager.NeutronManager.get_service_plugins()[
|
||||
constants.FLAVORS]
|
||||
return self._flavor_plugin_ref
|
||||
|
||||
def _set_router_provider(self, resource, event, trigger, context, router,
|
||||
router_db, **kwargs):
|
||||
"""Associates a router with a service provider.
|
||||
|
||||
Association is done by flavor_id if it's specified, otherwise it will
|
||||
fallback to determining which loaded driver supports the ha/distributed
|
||||
attributes associated with the router.
|
||||
"""
|
||||
if _flavor_specified(router):
|
||||
router_db.flavor_id = router['flavor_id']
|
||||
drv = self._get_provider_for_create(context, router)
|
||||
_ensure_driver_supports_request(drv, router)
|
||||
self._stm.add_resource_association(context, 'L3_ROUTER_NAT',
|
||||
drv.name, router['id'])
|
||||
|
||||
def _clear_router_provider(self, resource, event, trigger, context,
|
||||
router_id, **kwargs):
|
||||
"""Remove the association between a router and a service provider."""
|
||||
self._stm.del_resource_associations(context, [router_id])
|
||||
|
||||
def _update_router_provider(self, resource, event, trigger, context,
|
||||
router_id, router, old_router, router_db,
|
||||
**kwargs):
|
||||
"""Handle transition between providers.
|
||||
|
||||
The provider can currently be changed only by the caller updating
|
||||
'ha' and/or 'distributed' attributes. If we allow updates of flavor_id
|
||||
directly in the future those requests will also land here.
|
||||
"""
|
||||
drv = self._get_provider_for_router(context, router_id)
|
||||
new_drv = None
|
||||
if _flavor_specified(router):
|
||||
if router['flavor_id'] != old_router['flavor_id']:
|
||||
# TODO(kevinbenton): this is currently disallowed by the API
|
||||
# so we shouldn't hit it but this is a placeholder to add
|
||||
# support later.
|
||||
raise NotImplementedError()
|
||||
|
||||
# the following is to support updating the 'ha' and 'distributed'
|
||||
# attributes via the API.
|
||||
try:
|
||||
_ensure_driver_supports_request(drv, router)
|
||||
except n_exc.Invalid:
|
||||
# the current driver does not support this request, we need to
|
||||
# migrate to a new provider. populate the distributed and ha
|
||||
# flags from the previous state if not in the update so we can
|
||||
# determine the target provider appropriately.
|
||||
# NOTE(kevinbenton): if the router is associated with a flavor
|
||||
# we bail because changing the provider without changing
|
||||
# the flavor will make things inconsistent. We can probably
|
||||
# update the flavor automatically in the future.
|
||||
if old_router['flavor_id']:
|
||||
raise n_exc.Invalid(_(
|
||||
"Changing the 'ha' and 'distributed' attributes on a "
|
||||
"router associated with a flavor is not supported."))
|
||||
if 'distributed' not in router:
|
||||
router['distributed'] = old_router['distributed']
|
||||
if 'ha' not in router:
|
||||
router['ha'] = old_router['distributed']
|
||||
new_drv = self._attrs_to_driver(router)
|
||||
if new_drv:
|
||||
LOG.debug("Router %(id)s migrating from %(old)s provider to "
|
||||
"%(new)s provider.", {'id': router_id, 'old': drv,
|
||||
'new': new_drv})
|
||||
_ensure_driver_supports_request(new_drv, router)
|
||||
# TODO(kevinbenton): notify old driver explicity of driver change
|
||||
with context.session.begin(subtransactions=True):
|
||||
self._stm.del_resource_associations(context, [router_id])
|
||||
self._stm.add_resource_association(
|
||||
context, 'L3_ROUTER_NAT', new_drv.name, router_id)
|
||||
|
||||
def _get_provider_for_router(self, context, router_id):
|
||||
"""Return the provider driver handle for a router id."""
|
||||
driver_name = self._stm.get_provider_names_by_resource_ids(
|
||||
context, [router_id]).get(router_id)
|
||||
if not driver_name:
|
||||
# this is an old router that hasn't been mapped to a provider
|
||||
# yet so we do this now
|
||||
router = self.l3_plugin.get_router(context, router_id)
|
||||
driver = self._attrs_to_driver(router)
|
||||
driver_name = driver.name
|
||||
self._stm.add_resource_association(context, 'L3_ROUTER_NAT',
|
||||
driver_name, router_id)
|
||||
return self.drivers[driver_name]
|
||||
|
||||
def _get_provider_for_create(self, context, router):
|
||||
"""Get provider based on flavor or ha/distributed flags."""
|
||||
if not _flavor_specified(router):
|
||||
return self._attrs_to_driver(router)
|
||||
return self._get_l3_driver_by_flavor(context, router['flavor_id'])
|
||||
|
||||
def _get_l3_driver_by_flavor(self, context, flavor_id):
|
||||
"""Get a provider driver handle for a given flavor_id."""
|
||||
flavor = self._flavor_plugin.get_flavor(context, flavor_id)
|
||||
provider = self._flavor_plugin.get_flavor_next_provider(
|
||||
context, flavor['id'])[0]
|
||||
# TODO(kevinbenton): the callback framework suppresses the nice errors
|
||||
# these generate when they fail to lookup. carry them through
|
||||
driver = self.drivers[provider['provider']]
|
||||
return driver
|
||||
|
||||
def _attrs_to_driver(self, router):
|
||||
"""Get a provider driver handle based on the ha/distributed flags."""
|
||||
distributed = _is_distributed(router['distributed'])
|
||||
ha = _is_ha(router['ha'])
|
||||
drivers = self.drivers.values()
|
||||
# make sure default is tried before the rest if defined
|
||||
if self.default_provider:
|
||||
drivers.insert(0, self.drivers[self.default_provider])
|
||||
for driver in drivers:
|
||||
if _is_driver_compatible(distributed, ha, driver):
|
||||
return driver
|
||||
raise NotImplementedError(
|
||||
_("Could not find a service provider that supports "
|
||||
"distributed=%(d)s and ha=%(h)s") % {'d': distributed, 'h': ha}
|
||||
)
|
||||
|
||||
|
||||
class _LegacyPlusProviderConfiguration(
|
||||
provider_configuration.ProviderConfiguration):
|
||||
|
||||
def __init__(self):
|
||||
# loads up ha, dvr, and single_node service providers automatically.
|
||||
# If an operator has setup explicit values that conflict with these,
|
||||
# the operator defined values will take priority.
|
||||
super(_LegacyPlusProviderConfiguration, self).__init__()
|
||||
for name, driver in (('dvrha', 'dvrha.DvrHaDriver'),
|
||||
('dvr', 'dvr.DvrDriver'), ('ha', 'ha.HaDriver'),
|
||||
('single_node', 'single_node.SingleNodeDriver')):
|
||||
path = 'neutron.services.l3_router.service_providers.%s' % driver
|
||||
try:
|
||||
self.add_provider({'service_type': constants.L3_ROUTER_NAT,
|
||||
'name': name, 'driver': path,
|
||||
'default': False})
|
||||
except n_exc.Invalid:
|
||||
LOG.debug("Could not add L3 provider '%s', it may have "
|
||||
"already been explicitly defined.", name)
|
||||
|
||||
|
||||
def _is_driver_compatible(distributed, ha, driver):
|
||||
if not driver.distributed_support.is_compatible(distributed):
|
||||
return False
|
||||
if not driver.ha_support.is_compatible(ha):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _is_distributed(distributed_attr):
|
||||
if distributed_attr is False:
|
||||
return False
|
||||
if distributed_attr == lib_const.ATTR_NOT_SPECIFIED:
|
||||
return cfg.CONF.router_distributed
|
||||
return True
|
||||
|
||||
|
||||
def _is_ha(ha_attr):
|
||||
if ha_attr is False:
|
||||
return False
|
||||
if ha_attr == lib_const.ATTR_NOT_SPECIFIED:
|
||||
return cfg.CONF.l3_ha
|
||||
return True
|
||||
|
||||
|
||||
def _flavor_specified(router):
|
||||
return ('flavor_id' in router and
|
||||
router['flavor_id'] != lib_const.ATTR_NOT_SPECIFIED)
|
||||
|
||||
|
||||
def _ensure_driver_supports_request(drv, router_body):
|
||||
r = router_body
|
||||
for key, attr in (('distributed', 'distributed_support'),
|
||||
('ha', 'ha_support')):
|
||||
flag = r.get(key)
|
||||
if flag not in [True, False]:
|
||||
continue # not specified in body
|
||||
if not getattr(drv, attr).is_compatible(flag):
|
||||
raise n_exc.Invalid(
|
||||
_("Provider %(name)s does not support %(key)s=%(flag)s")
|
||||
% dict(name=drv.name, key=key, flag=flag))
|
|
@ -0,0 +1,19 @@
|
|||
# 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.services.l3_router.service_providers import base
|
||||
|
||||
|
||||
class DvrDriver(base.L3ServiceProvider):
|
||||
distributed_support = base.MANDATORY
|
|
@ -0,0 +1,22 @@
|
|||
# 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.services.l3_router.service_providers import base
|
||||
from neutron.services.l3_router.service_providers import dvr
|
||||
from neutron.services.l3_router.service_providers import ha
|
||||
|
||||
|
||||
class DvrHaDriver(dvr.DvrDriver, ha.HaDriver):
|
||||
ha_support = base.MANDATORY
|
||||
dvr_support = base.MANDATORY
|
|
@ -0,0 +1,19 @@
|
|||
# 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.services.l3_router.service_providers import base
|
||||
|
||||
|
||||
class HaDriver(base.L3ServiceProvider):
|
||||
ha_support = base.MANDATORY
|
|
@ -0,0 +1,19 @@
|
|||
# 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.services.l3_router.service_providers import base
|
||||
|
||||
|
||||
class SingleNodeDriver(base.L3ServiceProvider):
|
||||
"""Provider for single L3 agent routers."""
|
|
@ -601,7 +601,8 @@ class TestRouterController(TestResourceController):
|
|||
def setUp(self):
|
||||
cfg.CONF.set_override(
|
||||
'service_plugins',
|
||||
['neutron.services.l3_router.l3_router_plugin.L3RouterPlugin'])
|
||||
['neutron.services.l3_router.l3_router_plugin.L3RouterPlugin',
|
||||
'neutron.services.flavors.flavors_plugin.FlavorsPlugin'])
|
||||
super(TestRouterController, self).setUp()
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
ctx = context.get_admin_context()
|
||||
|
@ -697,7 +698,8 @@ class TestL3AgentShimControllers(test_functional.PecanFunctionalTest):
|
|||
def setUp(self):
|
||||
cfg.CONF.set_override(
|
||||
'service_plugins',
|
||||
['neutron.services.l3_router.l3_router_plugin.L3RouterPlugin'])
|
||||
['neutron.services.l3_router.l3_router_plugin.L3RouterPlugin',
|
||||
'neutron.services.flavors.flavors_plugin.FlavorsPlugin'])
|
||||
super(TestL3AgentShimControllers, self).setUp()
|
||||
policy.init()
|
||||
policy._ENFORCER.set_rules(
|
||||
|
|
|
@ -232,7 +232,11 @@ class OvsAgentSchedulerTestCaseBase(test_l3.L3NatTestCaseMixin,
|
|||
def setUp(self):
|
||||
self.useFixture(tools.AttributeMapMemento())
|
||||
if self.l3_plugin:
|
||||
service_plugins = {'l3_plugin_name': self.l3_plugin}
|
||||
service_plugins = {
|
||||
'l3_plugin_name': self.l3_plugin,
|
||||
'flavors_plugin_name': 'neutron.services.flavors.'
|
||||
'flavors_plugin.FlavorsPlugin'
|
||||
}
|
||||
else:
|
||||
service_plugins = None
|
||||
# NOTE(ivasilevskaya) mocking this way allows some control over mocked
|
||||
|
@ -1452,7 +1456,11 @@ class OvsL3AgentNotifierTestCase(test_l3.L3NatTestCaseMixin,
|
|||
self.useFixture(tools.AttributeMapMemento())
|
||||
|
||||
if self.l3_plugin:
|
||||
service_plugins = {'l3_plugin_name': self.l3_plugin}
|
||||
service_plugins = {
|
||||
'l3_plugin_name': self.l3_plugin,
|
||||
'flavors_plugin_name': 'neutron.services.flavors.'
|
||||
'flavors_plugin.FlavorsPlugin'
|
||||
}
|
||||
else:
|
||||
service_plugins = None
|
||||
super(OvsL3AgentNotifierTestCase, self).setUp(
|
||||
|
|
|
@ -22,6 +22,12 @@ class ML2TestFramework(test_plugin.Ml2PluginV2TestCase):
|
|||
'L3RouterPlugin')
|
||||
_mechanism_drivers = ['openvswitch']
|
||||
|
||||
def get_additional_service_plugins(self):
|
||||
p = super(ML2TestFramework, self).get_additional_service_plugins()
|
||||
p.update({'flavors_plugin_name': 'neutron.services.flavors.'
|
||||
'flavors_plugin.FlavorsPlugin'})
|
||||
return p
|
||||
|
||||
def setUp(self):
|
||||
super(ML2TestFramework, self).setUp()
|
||||
self.core_plugin = manager.NeutronManager.get_instance().get_plugin()
|
||||
|
|
|
@ -109,9 +109,14 @@ class Ml2PluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
|
|||
l3_plugin = ('neutron.tests.unit.extensions.test_l3.'
|
||||
'TestL3NatServicePlugin')
|
||||
|
||||
def get_additional_service_plugins(self):
|
||||
"""Subclasses can return a dictionary of service plugins to load."""
|
||||
return {}
|
||||
|
||||
def setup_parent(self):
|
||||
"""Perform parent setup with the common plugin configuration class."""
|
||||
service_plugins = {'l3_plugin_name': self.l3_plugin}
|
||||
service_plugins.update(self.get_additional_service_plugins())
|
||||
# Ensure that the parent setup can be called without arguments
|
||||
# by the common configuration setUp.
|
||||
parent_setup = functools.partial(
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# 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
|
||||
from neutron_lib import constants
|
||||
import testtools
|
||||
|
||||
from neutron import context
|
||||
from neutron.services.l3_router.service_providers import driver_controller
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
class TestDriverController(testlib_api.SqlTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDriverController, self).setUp()
|
||||
self.fake_l3 = mock.Mock()
|
||||
self.dc = driver_controller.DriverController(self.fake_l3)
|
||||
self.ctx = context.get_admin_context()
|
||||
|
||||
def _return_provider_for_flavor(self, provider):
|
||||
self.dc._flavor_plugin_ref = mock.Mock()
|
||||
self.dc._flavor_plugin_ref.get_flavor.return_value = {'id': 'abc'}
|
||||
provider = {'provider': provider}
|
||||
self.dc._flavor_plugin_ref.get_flavor_next_provider.return_value = [
|
||||
provider]
|
||||
|
||||
def test__set_router_provider_flavor_specified(self):
|
||||
self._return_provider_for_flavor('dvrha')
|
||||
router_db = mock.Mock()
|
||||
router = dict(id='router_id', flavor_id='abc123')
|
||||
self.dc._set_router_provider('router', 'PRECOMMIT_CREATE', self,
|
||||
self.ctx, router, router_db)
|
||||
self.assertEqual('abc123', router_db.flavor_id)
|
||||
self.assertEqual(self.dc.drivers['dvrha'],
|
||||
self.dc._get_provider_for_router(self.ctx,
|
||||
'router_id'))
|
||||
|
||||
def test__set_router_provider_attr_lookups(self):
|
||||
# ensure correct drivers are looked up based on attrs
|
||||
cases = [
|
||||
('dvrha', dict(id='router_id1', distributed=True, ha=True)),
|
||||
('dvr', dict(id='router_id2', distributed=True, ha=False)),
|
||||
('ha', dict(id='router_id3', distributed=False, ha=True)),
|
||||
('single_node', dict(id='router_id4', distributed=False,
|
||||
ha=False)),
|
||||
('ha', dict(id='router_id5', ha=True,
|
||||
distributed=constants.ATTR_NOT_SPECIFIED)),
|
||||
('dvr', dict(id='router_id6', distributed=True,
|
||||
ha=constants.ATTR_NOT_SPECIFIED)),
|
||||
('single_node', dict(id='router_id7', ha=False,
|
||||
distributed=constants.ATTR_NOT_SPECIFIED)),
|
||||
('single_node', dict(id='router_id8', distributed=False,
|
||||
ha=constants.ATTR_NOT_SPECIFIED)),
|
||||
('single_node', dict(id='router_id9',
|
||||
distributed=constants.ATTR_NOT_SPECIFIED,
|
||||
ha=constants.ATTR_NOT_SPECIFIED)),
|
||||
]
|
||||
for driver, body in cases:
|
||||
self.dc._set_router_provider('router', 'PRECOMMIT_CREATE', self,
|
||||
self.ctx, body, mock.Mock())
|
||||
self.assertEqual(self.dc.drivers[driver],
|
||||
self.dc._get_provider_for_router(self.ctx,
|
||||
body['id']),
|
||||
'Expecting %s for body %s' % (driver, body))
|
||||
|
||||
def test__clear_router_provider(self):
|
||||
# ensure correct drivers are looked up based on attrs
|
||||
body = dict(id='router_id1', distributed=True, ha=True)
|
||||
self.dc._set_router_provider('router', 'PRECOMMIT_CREATE', self,
|
||||
self.ctx, body, mock.Mock())
|
||||
self.assertEqual(self.dc.drivers['dvrha'],
|
||||
self.dc._get_provider_for_router(self.ctx,
|
||||
body['id']))
|
||||
self.dc._clear_router_provider('router', 'PRECOMMIT_DELETE', self,
|
||||
self.ctx, body['id'])
|
||||
with testtools.ExpectedException(ValueError):
|
||||
# if association was cleared, get_router will be called
|
||||
self.fake_l3.get_router.side_effect = ValueError
|
||||
self.dc._get_provider_for_router(self.ctx, body['id'])
|
Loading…
Reference in New Issue