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)
|
admin_state_up = sa.Column(sa.Boolean)
|
||||||
gw_port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id'))
|
gw_port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id'))
|
||||||
gw_port = orm.relationship(models_v2.Port, lazy='joined')
|
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(
|
attached_ports = orm.relationship(
|
||||||
RouterPort,
|
RouterPort,
|
||||||
backref='router',
|
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_dvrscheduler_db
|
||||||
from neutron.db import l3_gwmode_db
|
from neutron.db import l3_gwmode_db
|
||||||
from neutron.db import l3_hamode_db
|
from neutron.db import l3_hamode_db
|
||||||
|
from neutron.extensions import l3
|
||||||
from neutron.plugins.common import constants
|
from neutron.plugins.common import constants
|
||||||
from neutron.quota import resource_registry
|
from neutron.quota import resource_registry
|
||||||
from neutron import service
|
from neutron import service
|
||||||
|
from neutron.services.l3_router.service_providers import driver_controller
|
||||||
from neutron.services import service_base
|
from neutron.services import service_base
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +57,8 @@ class L3RouterPlugin(service_base.ServicePluginBase,
|
||||||
"""
|
"""
|
||||||
supported_extension_aliases = ["dvr", "router", "ext-gw-mode",
|
supported_extension_aliases = ["dvr", "router", "ext-gw-mode",
|
||||||
"extraroute", "l3_agent_scheduler",
|
"extraroute", "l3_agent_scheduler",
|
||||||
"l3-ha", "router_availability_zone"]
|
"l3-ha", "router_availability_zone",
|
||||||
|
"l3-flavors"]
|
||||||
|
|
||||||
__native_pagination_support = True
|
__native_pagination_support = True
|
||||||
__native_sorting_support = True
|
__native_sorting_support = True
|
||||||
|
@ -76,6 +79,7 @@ class L3RouterPlugin(service_base.ServicePluginBase,
|
||||||
rpc_worker = service.RpcWorker([self], worker_process_count=0)
|
rpc_worker = service.RpcWorker([self], worker_process_count=0)
|
||||||
|
|
||||||
self.add_worker(rpc_worker)
|
self.add_worker(rpc_worker)
|
||||||
|
self.l3_driver_controller = driver_controller.DriverController(self)
|
||||||
|
|
||||||
@log_helpers.log_method_call
|
@log_helpers.log_method_call
|
||||||
def start_rpc_listeners(self):
|
def start_rpc_listeners(self):
|
||||||
|
@ -111,3 +115,11 @@ class L3RouterPlugin(service_base.ServicePluginBase,
|
||||||
return super(L3RouterPlugin, self).create_floatingip(
|
return super(L3RouterPlugin, self).create_floatingip(
|
||||||
context, floatingip,
|
context, floatingip,
|
||||||
initial_status=n_const.FLOATINGIP_STATUS_DOWN)
|
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):
|
def setUp(self):
|
||||||
cfg.CONF.set_override(
|
cfg.CONF.set_override(
|
||||||
'service_plugins',
|
'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()
|
super(TestRouterController, self).setUp()
|
||||||
plugin = manager.NeutronManager.get_plugin()
|
plugin = manager.NeutronManager.get_plugin()
|
||||||
ctx = context.get_admin_context()
|
ctx = context.get_admin_context()
|
||||||
|
@ -697,7 +698,8 @@ class TestL3AgentShimControllers(test_functional.PecanFunctionalTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
cfg.CONF.set_override(
|
cfg.CONF.set_override(
|
||||||
'service_plugins',
|
'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()
|
super(TestL3AgentShimControllers, self).setUp()
|
||||||
policy.init()
|
policy.init()
|
||||||
policy._ENFORCER.set_rules(
|
policy._ENFORCER.set_rules(
|
||||||
|
|
|
@ -232,7 +232,11 @@ class OvsAgentSchedulerTestCaseBase(test_l3.L3NatTestCaseMixin,
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.useFixture(tools.AttributeMapMemento())
|
self.useFixture(tools.AttributeMapMemento())
|
||||||
if self.l3_plugin:
|
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:
|
else:
|
||||||
service_plugins = None
|
service_plugins = None
|
||||||
# NOTE(ivasilevskaya) mocking this way allows some control over mocked
|
# NOTE(ivasilevskaya) mocking this way allows some control over mocked
|
||||||
|
@ -1452,7 +1456,11 @@ class OvsL3AgentNotifierTestCase(test_l3.L3NatTestCaseMixin,
|
||||||
self.useFixture(tools.AttributeMapMemento())
|
self.useFixture(tools.AttributeMapMemento())
|
||||||
|
|
||||||
if self.l3_plugin:
|
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:
|
else:
|
||||||
service_plugins = None
|
service_plugins = None
|
||||||
super(OvsL3AgentNotifierTestCase, self).setUp(
|
super(OvsL3AgentNotifierTestCase, self).setUp(
|
||||||
|
|
|
@ -22,6 +22,12 @@ class ML2TestFramework(test_plugin.Ml2PluginV2TestCase):
|
||||||
'L3RouterPlugin')
|
'L3RouterPlugin')
|
||||||
_mechanism_drivers = ['openvswitch']
|
_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):
|
def setUp(self):
|
||||||
super(ML2TestFramework, self).setUp()
|
super(ML2TestFramework, self).setUp()
|
||||||
self.core_plugin = manager.NeutronManager.get_instance().get_plugin()
|
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.'
|
l3_plugin = ('neutron.tests.unit.extensions.test_l3.'
|
||||||
'TestL3NatServicePlugin')
|
'TestL3NatServicePlugin')
|
||||||
|
|
||||||
|
def get_additional_service_plugins(self):
|
||||||
|
"""Subclasses can return a dictionary of service plugins to load."""
|
||||||
|
return {}
|
||||||
|
|
||||||
def setup_parent(self):
|
def setup_parent(self):
|
||||||
"""Perform parent setup with the common plugin configuration class."""
|
"""Perform parent setup with the common plugin configuration class."""
|
||||||
service_plugins = {'l3_plugin_name': self.l3_plugin}
|
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
|
# Ensure that the parent setup can be called without arguments
|
||||||
# by the common configuration setUp.
|
# by the common configuration setUp.
|
||||||
parent_setup = functools.partial(
|
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