Decentralize the managemement of service providers
After the service split, some of the configuration, parsing and validation was kept in the neutron core; ultimately this needs to get closer to the services where it belongs. This patch starts from ProviderConfiguration and ServiceTypeManager classes, and aims at removing the hard-coded elements, like the list of known advanced services, so that in the long run we can make Neutron easier to plug with external services. Partial-bug: #1473110 Depends-on: I44edcceba37ac58efcc0a53c9d1f835d9530344a Depends-on: I8924234aadf786801ffc100d7daa27acc145a195 Change-Id: Ia4cad678e6c722ca05821dbdbf05d61523246a86
This commit is contained in:
parent
43c00a9f65
commit
61121c5f2a
|
@ -27,10 +27,10 @@ import webob.dec
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
from neutron.common import exceptions
|
from neutron.common import exceptions
|
||||||
from neutron.common import repos
|
|
||||||
import neutron.extensions
|
import neutron.extensions
|
||||||
from neutron.i18n import _LE, _LI, _LW
|
from neutron.i18n import _LE, _LI, _LW
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
|
from neutron.services import provider_configuration
|
||||||
from neutron import wsgi
|
from neutron import wsgi
|
||||||
|
|
||||||
|
|
||||||
|
@ -579,8 +579,9 @@ class PluginAwareExtensionManager(ExtensionManager):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = cls(get_extensions_path(),
|
service_plugins = manager.NeutronManager.get_service_plugins()
|
||||||
manager.NeutronManager.get_service_plugins())
|
cls._instance = cls(get_extensions_path(service_plugins),
|
||||||
|
service_plugins)
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def get_supported_extension_aliases(self):
|
def get_supported_extension_aliases(self):
|
||||||
|
@ -647,31 +648,30 @@ class ResourceExtension(object):
|
||||||
|
|
||||||
# Returns the extension paths from a config entry and the __path__
|
# Returns the extension paths from a config entry and the __path__
|
||||||
# of neutron.extensions
|
# of neutron.extensions
|
||||||
def get_extensions_path():
|
def get_extensions_path(service_plugins=None):
|
||||||
paths = neutron.extensions.__path__
|
paths = collections.OrderedDict()
|
||||||
|
|
||||||
neutron_mods = repos.NeutronModules()
|
# Add Neutron core extensions
|
||||||
for x in neutron_mods.installed_list():
|
paths[neutron.extensions.__path__[0]] = 1
|
||||||
|
if service_plugins:
|
||||||
|
# Add Neutron *-aas extensions
|
||||||
|
for plugin in service_plugins.values():
|
||||||
|
neutron_mod = provider_configuration.NeutronModule(
|
||||||
|
plugin.__module__.split('.')[0])
|
||||||
try:
|
try:
|
||||||
paths += neutron_mods.module(x).extensions.__path__
|
paths[neutron_mod.module().extensions.__path__[0]] = 1
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Occurs normally if module has no extensions sub-module
|
# Occurs normally if module has no extensions sub-module
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Add external/other plugins extensions
|
||||||
if cfg.CONF.api_extensions_path:
|
if cfg.CONF.api_extensions_path:
|
||||||
paths.append(cfg.CONF.api_extensions_path)
|
for path in cfg.CONF.api_extensions_path.split(":"):
|
||||||
|
paths[path] = 1
|
||||||
# If the path has dups in it, from discovery + conf file, the duplicate
|
|
||||||
# import of the same module and super() do not play nicely, so weed
|
|
||||||
# out the duplicates, preserving search order.
|
|
||||||
|
|
||||||
z = collections.OrderedDict()
|
|
||||||
for x in paths:
|
|
||||||
z[x] = 1
|
|
||||||
paths = z.keys()
|
|
||||||
|
|
||||||
LOG.debug("get_extension_paths = %s", paths)
|
LOG.debug("get_extension_paths = %s", paths)
|
||||||
|
|
||||||
|
# Re-build the extension string
|
||||||
path = ':'.join(paths)
|
path = ':'.join(paths)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
# Copyright (c) 2015, A10 Networks
|
|
||||||
#
|
|
||||||
# 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 importlib
|
|
||||||
import os
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from six.moves import configparser as ConfigParser
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class NeutronModules(object):
|
|
||||||
|
|
||||||
MODULES = {
|
|
||||||
'neutron_fwaas': {
|
|
||||||
'alembic-name': 'fwaas',
|
|
||||||
},
|
|
||||||
'neutron_lbaas': {
|
|
||||||
'alembic-name': 'lbaas',
|
|
||||||
},
|
|
||||||
'neutron_vpnaas': {
|
|
||||||
'alembic-name': 'vpnaas',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.repos = {}
|
|
||||||
for repo in self.MODULES:
|
|
||||||
self.repos[repo] = {}
|
|
||||||
self.repos[repo]['mod'] = self._import_or_none(repo)
|
|
||||||
self.repos[repo]['ini'] = None
|
|
||||||
|
|
||||||
def _import_or_none(self, module):
|
|
||||||
try:
|
|
||||||
return importlib.import_module(module)
|
|
||||||
except ImportError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def installed_list(self):
|
|
||||||
z = filter(lambda k: self.repos[k]['mod'] is not None, self.repos)
|
|
||||||
LOG.debug("NeutronModules related repos installed = %s", z)
|
|
||||||
return z
|
|
||||||
|
|
||||||
def module(self, module):
|
|
||||||
return self.repos[module]['mod']
|
|
||||||
|
|
||||||
def alembic_name(self, module):
|
|
||||||
return self.MODULES[module]['alembic-name']
|
|
||||||
|
|
||||||
# Return an INI parser for the child module. oslo.config is a bit too
|
|
||||||
# magical in its INI loading, and in one notable case, we need to merge
|
|
||||||
# together the [service_providers] section across at least four
|
|
||||||
# repositories.
|
|
||||||
def ini(self, module):
|
|
||||||
if self.repos[module]['ini'] is None:
|
|
||||||
neutron_dir = None
|
|
||||||
try:
|
|
||||||
neutron_dir = cfg.CONF.config_dir
|
|
||||||
except cfg.NoSuchOptError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if neutron_dir is None:
|
|
||||||
neutron_dir = '/etc/neutron'
|
|
||||||
|
|
||||||
ini = ConfigParser.SafeConfigParser()
|
|
||||||
ini_path = os.path.join(neutron_dir, '%s.conf' % module)
|
|
||||||
if os.path.exists(ini_path):
|
|
||||||
ini.read(ini_path)
|
|
||||||
|
|
||||||
self.repos[module]['ini'] = ini
|
|
||||||
|
|
||||||
return self.repos[module]['ini']
|
|
||||||
|
|
||||||
def service_providers(self, module):
|
|
||||||
ini = self.ini(module)
|
|
||||||
|
|
||||||
sp = []
|
|
||||||
try:
|
|
||||||
for name, value in ini.items('service_providers'):
|
|
||||||
if name == 'service_provider':
|
|
||||||
sp.append(value)
|
|
||||||
except ConfigParser.NoSectionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return sp
|
|
|
@ -13,6 +13,8 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
@ -42,14 +44,36 @@ class ServiceTypeManager(object):
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._load_conf()
|
self.config = {}
|
||||||
|
# TODO(armax): remove these as soon as *-aaS start using
|
||||||
|
# the newly introduced add_provider_configuration API
|
||||||
|
self.config['LOADBALANCER'] = (
|
||||||
|
pconf.ProviderConfiguration('neutron_lbaas'))
|
||||||
|
self.config['LOADBALANCERV2'] = (
|
||||||
|
pconf.ProviderConfiguration('neutron_lbaas'))
|
||||||
|
self.config['FIREWALL'] = (
|
||||||
|
pconf.ProviderConfiguration('neutron_fwaas'))
|
||||||
|
self.config['VPN'] = (
|
||||||
|
pconf.ProviderConfiguration('neutron_vpnaas'))
|
||||||
|
|
||||||
def _load_conf(self):
|
def add_provider_configuration(self, service_type, configuration):
|
||||||
self.conf = pconf.ProviderConfiguration(
|
"""Add or update the provider configuration for the service type."""
|
||||||
pconf.parse_service_provider_opt())
|
LOG.debug('Adding provider configuration for service %s', service_type)
|
||||||
|
self.config.update({service_type: configuration})
|
||||||
|
|
||||||
def get_service_providers(self, context, filters=None, fields=None):
|
def get_service_providers(self, context, filters=None, fields=None):
|
||||||
return self.conf.get_service_providers(filters, fields)
|
if filters and 'service_type' in filters:
|
||||||
|
return list(
|
||||||
|
chain.from_iterable(self.config[svc_type].
|
||||||
|
get_service_providers(filters, fields)
|
||||||
|
for svc_type in filters['service_type']
|
||||||
|
if svc_type in self.config)
|
||||||
|
)
|
||||||
|
return list(
|
||||||
|
chain.from_iterable(
|
||||||
|
self.config[p].get_service_providers(filters, fields)
|
||||||
|
for p in self.config)
|
||||||
|
)
|
||||||
|
|
||||||
def get_default_service_provider(self, context, service_type):
|
def get_default_service_provider(self, context, service_type):
|
||||||
"""Return the default provider for a given service type."""
|
"""Return the default provider for a given service type."""
|
||||||
|
@ -65,7 +89,7 @@ class ServiceTypeManager(object):
|
||||||
|
|
||||||
def add_resource_association(self, context, service_type, provider_name,
|
def add_resource_association(self, context, service_type, provider_name,
|
||||||
resource_id):
|
resource_id):
|
||||||
r = self.conf.get_service_providers(
|
r = self.get_service_providers(context,
|
||||||
filters={'service_type': [service_type], 'name': [provider_name]})
|
filters={'service_type': [service_type], 'name': [provider_name]})
|
||||||
if not r:
|
if not r:
|
||||||
raise pconf.ServiceProviderNotFound(provider=provider_name,
|
raise pconf.ServiceProviderNotFound(provider=provider_name,
|
||||||
|
|
|
@ -13,14 +13,16 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from six.moves import configparser as ConfigParser
|
||||||
import stevedore
|
import stevedore
|
||||||
|
|
||||||
from neutron.common import exceptions as n_exc
|
from neutron.common import exceptions as n_exc
|
||||||
from neutron.common import repos
|
|
||||||
from neutron.i18n import _LW
|
from neutron.i18n import _LW
|
||||||
from neutron.plugins.common import constants
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -36,6 +38,67 @@ serviceprovider_opts = [
|
||||||
cfg.CONF.register_opts(serviceprovider_opts, 'service_providers')
|
cfg.CONF.register_opts(serviceprovider_opts, 'service_providers')
|
||||||
|
|
||||||
|
|
||||||
|
class NeutronModule(object):
|
||||||
|
"""A Neutron extension module."""
|
||||||
|
|
||||||
|
def __init__(self, service_module):
|
||||||
|
self.module_name = service_module
|
||||||
|
self.repo = {
|
||||||
|
'mod': self._import_or_none(),
|
||||||
|
'ini': None
|
||||||
|
}
|
||||||
|
|
||||||
|
def _import_or_none(self):
|
||||||
|
try:
|
||||||
|
return importlib.import_module(self.module_name)
|
||||||
|
except ImportError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def installed(self):
|
||||||
|
LOG.debug("NeutronModule installed = %s", self.module_name)
|
||||||
|
return self.module_name
|
||||||
|
|
||||||
|
def module(self):
|
||||||
|
return self.repo['mod']
|
||||||
|
|
||||||
|
# Return an INI parser for the child module. oslo.config is a bit too
|
||||||
|
# magical in its INI loading, and in one notable case, we need to merge
|
||||||
|
# together the [service_providers] section across at least four
|
||||||
|
# repositories.
|
||||||
|
def ini(self):
|
||||||
|
if self.repo['ini'] is None:
|
||||||
|
neutron_dir = None
|
||||||
|
try:
|
||||||
|
neutron_dir = cfg.CONF.config_dir
|
||||||
|
except cfg.NoSuchOptError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if neutron_dir is None:
|
||||||
|
neutron_dir = '/etc/neutron'
|
||||||
|
|
||||||
|
ini = ConfigParser.SafeConfigParser()
|
||||||
|
ini_path = os.path.join(neutron_dir, '%s.conf' % self.module_name)
|
||||||
|
if os.path.exists(ini_path):
|
||||||
|
ini.read(ini_path)
|
||||||
|
|
||||||
|
self.repo['ini'] = ini
|
||||||
|
|
||||||
|
return self.repo['ini']
|
||||||
|
|
||||||
|
def service_providers(self):
|
||||||
|
ini = self.ini()
|
||||||
|
|
||||||
|
sp = []
|
||||||
|
try:
|
||||||
|
for name, value in ini.items('service_providers'):
|
||||||
|
if name == 'service_provider':
|
||||||
|
sp.append(value)
|
||||||
|
except ConfigParser.NoSectionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return sp
|
||||||
|
|
||||||
|
|
||||||
#global scope function that should be used in service APIs
|
#global scope function that should be used in service APIs
|
||||||
def normalize_provider_name(name):
|
def normalize_provider_name(name):
|
||||||
return name.lower()
|
return name.lower()
|
||||||
|
@ -65,32 +128,16 @@ def get_provider_driver_class(driver, namespace=SERVICE_PROVIDERS):
|
||||||
return new_driver
|
return new_driver
|
||||||
|
|
||||||
|
|
||||||
def parse_service_provider_opt():
|
def parse_service_provider_opt(service_module='neutron'):
|
||||||
|
|
||||||
"""Parse service definition opts and returns result."""
|
"""Parse service definition opts and returns result."""
|
||||||
def validate_name(name):
|
def validate_name(name):
|
||||||
if len(name) > 255:
|
if len(name) > 255:
|
||||||
raise n_exc.Invalid(
|
raise n_exc.Invalid(
|
||||||
_("Provider name is limited by 255 characters: %s") % name)
|
_("Provider name is limited by 255 characters: %s") % name)
|
||||||
|
|
||||||
# TODO(dougwig) - phase out the neutron.conf location for service
|
neutron_mod = NeutronModule(service_module)
|
||||||
# providers a cycle or two after Kilo.
|
svc_providers_opt = neutron_mod.service_providers()
|
||||||
|
|
||||||
# Look in neutron.conf for service providers first (legacy mode)
|
|
||||||
try:
|
|
||||||
svc_providers_opt = cfg.CONF.service_providers.service_provider
|
|
||||||
except cfg.NoSuchOptError:
|
|
||||||
svc_providers_opt = []
|
|
||||||
|
|
||||||
# Look in neutron-*aas.conf files for service provider configs
|
|
||||||
if svc_providers_opt:
|
|
||||||
LOG.warning(_LW("Reading service_providers from legacy location in "
|
|
||||||
"neutron.conf, and ignoring values in "
|
|
||||||
"neutron_*aas.conf files; this override will be "
|
|
||||||
"going away soon."))
|
|
||||||
else:
|
|
||||||
neutron_mods = repos.NeutronModules()
|
|
||||||
for x in neutron_mods.installed_list():
|
|
||||||
svc_providers_opt += neutron_mods.service_providers(x)
|
|
||||||
|
|
||||||
LOG.debug("Service providers = %s", svc_providers_opt)
|
LOG.debug("Service providers = %s", svc_providers_opt)
|
||||||
|
|
||||||
|
@ -113,14 +160,7 @@ def parse_service_provider_opt():
|
||||||
prov_def)
|
prov_def)
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise n_exc.Invalid(msg)
|
raise n_exc.Invalid(msg)
|
||||||
ALLOWED_SERVICES = constants.EXT_TO_SERVICE_MAPPING.values()
|
|
||||||
if svc_type not in ALLOWED_SERVICES:
|
|
||||||
msg = (_("Service type '%(svc_type)s' is not allowed, "
|
|
||||||
"allowed types: %(allowed)s") %
|
|
||||||
{'svc_type': svc_type,
|
|
||||||
'allowed': ALLOWED_SERVICES})
|
|
||||||
LOG.error(msg)
|
|
||||||
raise n_exc.Invalid(msg)
|
|
||||||
driver = get_provider_driver_class(driver)
|
driver = get_provider_driver_class(driver)
|
||||||
res.append({'service_type': svc_type,
|
res.append({'service_type': svc_type,
|
||||||
'name': name,
|
'name': name,
|
||||||
|
@ -145,9 +185,10 @@ class ServiceProviderAlreadyAssociated(n_exc.Conflict):
|
||||||
|
|
||||||
|
|
||||||
class ProviderConfiguration(object):
|
class ProviderConfiguration(object):
|
||||||
def __init__(self, prov_data):
|
|
||||||
|
def __init__(self, svc_module='neutron'):
|
||||||
self.providers = {}
|
self.providers = {}
|
||||||
for prov in prov_data:
|
for prov in parse_service_provider_opt(svc_module):
|
||||||
self.add_provider(prov)
|
self.add_provider(prov)
|
||||||
|
|
||||||
def _ensure_driver_unique(self, driver):
|
def _ensure_driver_unique(self, driver):
|
||||||
|
|
|
@ -67,6 +67,40 @@ class FakePluginWithExtension(object):
|
||||||
self._log("method_to_support_foxnsox_extension", context)
|
self._log("method_to_support_foxnsox_extension", context)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionPathTest(base.BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.base_path = extensions.get_extensions_path()
|
||||||
|
super(ExtensionPathTest, self).setUp()
|
||||||
|
|
||||||
|
def test_get_extensions_path_with_plugins(self):
|
||||||
|
path = extensions.get_extensions_path(
|
||||||
|
{constants.CORE: FakePluginWithExtension()})
|
||||||
|
self.assertEqual(path,
|
||||||
|
'%s:neutron/tests/unit/extensions' % self.base_path)
|
||||||
|
|
||||||
|
def test_get_extensions_path_no_extensions(self):
|
||||||
|
# Reset to default value, as it's overriden by base class
|
||||||
|
cfg.CONF.set_override('api_extensions_path', '')
|
||||||
|
path = extensions.get_extensions_path()
|
||||||
|
self.assertEqual(path, self.base_path)
|
||||||
|
|
||||||
|
def test_get_extensions_path_single_extension(self):
|
||||||
|
cfg.CONF.set_override('api_extensions_path', 'path1')
|
||||||
|
path = extensions.get_extensions_path()
|
||||||
|
self.assertEqual(path, '%s:path1' % self.base_path)
|
||||||
|
|
||||||
|
def test_get_extensions_path_multiple_extensions(self):
|
||||||
|
cfg.CONF.set_override('api_extensions_path', 'path1:path2')
|
||||||
|
path = extensions.get_extensions_path()
|
||||||
|
self.assertEqual(path, '%s:path1:path2' % self.base_path)
|
||||||
|
|
||||||
|
def test_get_extensions_path_duplicate_extensions(self):
|
||||||
|
cfg.CONF.set_override('api_extensions_path', 'path1:path1')
|
||||||
|
path = extensions.get_extensions_path()
|
||||||
|
self.assertEqual(path, '%s:path1' % self.base_path)
|
||||||
|
|
||||||
|
|
||||||
class PluginInterfaceTest(base.BaseTestCase):
|
class PluginInterfaceTest(base.BaseTestCase):
|
||||||
def test_issubclass_hook(self):
|
def test_issubclass_hook(self):
|
||||||
class A(object):
|
class A(object):
|
||||||
|
|
|
@ -38,65 +38,63 @@ _get_path = test_base._get_path
|
||||||
|
|
||||||
class ServiceTypeManagerTestCase(testlib_api.SqlTestCase):
|
class ServiceTypeManagerTestCase(testlib_api.SqlTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.service_providers = mock.patch.object(
|
||||||
|
provconf.NeutronModule, 'service_providers').start()
|
||||||
super(ServiceTypeManagerTestCase, self).setUp()
|
super(ServiceTypeManagerTestCase, self).setUp()
|
||||||
st_db.ServiceTypeManager._instance = None
|
|
||||||
self.manager = st_db.ServiceTypeManager.get_instance()
|
|
||||||
self.ctx = context.get_admin_context()
|
self.ctx = context.get_admin_context()
|
||||||
|
|
||||||
|
def _set_override(self, service_providers):
|
||||||
|
self.service_providers.return_value = service_providers
|
||||||
|
st_db.ServiceTypeManager._instance = None
|
||||||
|
self.manager = st_db.ServiceTypeManager.get_instance()
|
||||||
|
for provider in service_providers:
|
||||||
|
self.manager.add_provider_configuration(
|
||||||
|
provider.split(':')[0], provconf.ProviderConfiguration())
|
||||||
|
|
||||||
def test_service_provider_driver_not_unique(self):
|
def test_service_provider_driver_not_unique(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER + ':lbaas:driver'])
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':lbaas:driver'],
|
|
||||||
'service_providers')
|
|
||||||
prov = {'service_type': constants.LOADBALANCER,
|
prov = {'service_type': constants.LOADBALANCER,
|
||||||
'name': 'name2',
|
'name': 'name2',
|
||||||
'driver': 'driver',
|
'driver': 'driver',
|
||||||
'default': False}
|
'default': False}
|
||||||
self.manager._load_conf()
|
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
n_exc.Invalid, self.manager.conf.add_provider, prov)
|
n_exc.Invalid,
|
||||||
|
self.manager.config['LOADBALANCER'].add_provider, prov)
|
||||||
|
|
||||||
def test_get_service_providers(self):
|
def test_get_service_providers(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
"""Test that get_service_providers filters correctly."""
|
||||||
|
self._set_override(
|
||||||
[constants.LOADBALANCER +
|
[constants.LOADBALANCER +
|
||||||
':lbaas:driver_path',
|
':lbaas:driver_path1',
|
||||||
constants.DUMMY + ':dummy:dummy_dr'],
|
constants.FIREWALL +
|
||||||
'service_providers')
|
':fwaas:driver_path2'])
|
||||||
ctx = context.get_admin_context()
|
ctx = context.get_admin_context()
|
||||||
provconf.parse_service_provider_opt()
|
|
||||||
self.manager._load_conf()
|
|
||||||
res = self.manager.get_service_providers(ctx)
|
|
||||||
self.assertEqual(len(res), 2)
|
|
||||||
|
|
||||||
res = self.manager.get_service_providers(
|
|
||||||
ctx,
|
|
||||||
filters=dict(service_type=[constants.DUMMY])
|
|
||||||
)
|
|
||||||
self.assertEqual(len(res), 1)
|
|
||||||
|
|
||||||
res = self.manager.get_service_providers(
|
res = self.manager.get_service_providers(
|
||||||
ctx,
|
ctx,
|
||||||
filters=dict(service_type=[constants.LOADBALANCER])
|
filters=dict(service_type=[constants.LOADBALANCER])
|
||||||
)
|
)
|
||||||
self.assertEqual(len(res), 1)
|
self.assertEqual(len(res), 1)
|
||||||
|
|
||||||
|
res = self.manager.get_service_providers(
|
||||||
|
ctx,
|
||||||
|
filters=dict(service_type=[constants.FIREWALL])
|
||||||
|
)
|
||||||
|
self.assertEqual(len(res), 1)
|
||||||
|
|
||||||
def test_multiple_default_providers_specified_for_service(self):
|
def test_multiple_default_providers_specified_for_service(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self.assertRaises(
|
||||||
|
n_exc.Invalid,
|
||||||
|
self._set_override,
|
||||||
[constants.LOADBALANCER +
|
[constants.LOADBALANCER +
|
||||||
':lbaas1:driver_path:default',
|
':lbaas1:driver_path:default',
|
||||||
constants.LOADBALANCER +
|
constants.LOADBALANCER +
|
||||||
':lbaas2:driver_path:default'],
|
':lbaas2:driver_path:default'])
|
||||||
'service_providers')
|
|
||||||
self.assertRaises(n_exc.Invalid, self.manager._load_conf)
|
|
||||||
|
|
||||||
def test_get_default_provider(self):
|
def test_get_default_provider(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':lbaas1:driver_path:default',
|
':lbaas1:driver_path:default',
|
||||||
constants.DUMMY +
|
constants.DUMMY +
|
||||||
':lbaas2:driver_path2'],
|
':lbaas2:driver_path2'])
|
||||||
'service_providers')
|
|
||||||
self.manager._load_conf()
|
|
||||||
# can pass None as a context
|
# can pass None as a context
|
||||||
p = self.manager.get_default_service_provider(None,
|
p = self.manager.get_default_service_provider(None,
|
||||||
constants.LOADBALANCER)
|
constants.LOADBALANCER)
|
||||||
|
@ -112,13 +110,10 @@ class ServiceTypeManagerTestCase(testlib_api.SqlTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_add_resource_association(self):
|
def test_add_resource_association(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':lbaas1:driver_path:default',
|
':lbaas1:driver_path:default',
|
||||||
constants.DUMMY +
|
constants.DUMMY +
|
||||||
':lbaas2:driver_path2'],
|
':lbaas2:driver_path2'])
|
||||||
'service_providers')
|
|
||||||
self.manager._load_conf()
|
|
||||||
ctx = context.get_admin_context()
|
ctx = context.get_admin_context()
|
||||||
self.manager.add_resource_association(ctx,
|
self.manager.add_resource_association(ctx,
|
||||||
constants.LOADBALANCER,
|
constants.LOADBALANCER,
|
||||||
|
@ -130,13 +125,10 @@ class ServiceTypeManagerTestCase(testlib_api.SqlTestCase):
|
||||||
ctx.session.delete(assoc)
|
ctx.session.delete(assoc)
|
||||||
|
|
||||||
def test_invalid_resource_association(self):
|
def test_invalid_resource_association(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':lbaas1:driver_path:default',
|
':lbaas1:driver_path:default',
|
||||||
constants.DUMMY +
|
constants.DUMMY +
|
||||||
':lbaas2:driver_path2'],
|
':lbaas2:driver_path2'])
|
||||||
'service_providers')
|
|
||||||
self.manager._load_conf()
|
|
||||||
ctx = context.get_admin_context()
|
ctx = context.get_admin_context()
|
||||||
self.assertRaises(provconf.ServiceProviderNotFound,
|
self.assertRaises(provconf.ServiceProviderNotFound,
|
||||||
self.manager.add_resource_association,
|
self.manager.add_resource_association,
|
||||||
|
@ -200,13 +192,19 @@ class ServiceTypeExtensionTestCase(ServiceTypeExtensionTestCaseBase):
|
||||||
class ServiceTypeManagerExtTestCase(ServiceTypeExtensionTestCaseBase):
|
class ServiceTypeManagerExtTestCase(ServiceTypeExtensionTestCaseBase):
|
||||||
"""Tests ServiceTypemanager as a public API."""
|
"""Tests ServiceTypemanager as a public API."""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.service_providers = mock.patch.object(
|
||||||
|
provconf.NeutronModule, 'service_providers').start()
|
||||||
|
service_providers = [
|
||||||
|
constants.LOADBALANCER + ':lbaas:driver_path',
|
||||||
|
constants.DUMMY + ':dummy:dummy_dr'
|
||||||
|
]
|
||||||
|
self.service_providers.return_value = service_providers
|
||||||
# Blank out service type manager instance
|
# Blank out service type manager instance
|
||||||
st_db.ServiceTypeManager._instance = None
|
st_db.ServiceTypeManager._instance = None
|
||||||
cfg.CONF.set_override('service_provider',
|
self.manager = st_db.ServiceTypeManager.get_instance()
|
||||||
[constants.LOADBALANCER +
|
for provider in service_providers:
|
||||||
':lbaas:driver_path',
|
self.manager.add_provider_configuration(
|
||||||
constants.DUMMY + ':dummy:dummy_dr'],
|
provider.split(':')[0], provconf.ProviderConfiguration())
|
||||||
'service_providers')
|
|
||||||
super(ServiceTypeManagerExtTestCase, self).setUp()
|
super(ServiceTypeManagerExtTestCase, self).setUp()
|
||||||
|
|
||||||
def _list_service_providers(self):
|
def _list_service_providers(self):
|
||||||
|
@ -217,4 +215,4 @@ class ServiceTypeManagerExtTestCase(ServiceTypeExtensionTestCaseBase):
|
||||||
self.assertEqual(res.status_int, webexc.HTTPOk.code)
|
self.assertEqual(res.status_int, webexc.HTTPOk.code)
|
||||||
data = self.deserialize(res)
|
data = self.deserialize(res)
|
||||||
self.assertIn('service_providers', data)
|
self.assertIn('service_providers', data)
|
||||||
self.assertEqual(len(data['service_providers']), 2)
|
self.assertGreaterEqual(len(data['service_providers']), 2)
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from neutron.common import exceptions as n_exc
|
from neutron.common import exceptions as n_exc
|
||||||
|
@ -22,15 +24,22 @@ from neutron.tests import base
|
||||||
|
|
||||||
|
|
||||||
class ParseServiceProviderConfigurationTestCase(base.BaseTestCase):
|
class ParseServiceProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ParseServiceProviderConfigurationTestCase, self).setUp()
|
||||||
|
self.service_providers = mock.patch.object(
|
||||||
|
provconf.NeutronModule, 'service_providers').start()
|
||||||
|
|
||||||
|
def _set_override(self, service_providers):
|
||||||
|
self.service_providers.return_value = service_providers
|
||||||
|
|
||||||
def test_default_service_provider_configuration(self):
|
def test_default_service_provider_configuration(self):
|
||||||
providers = cfg.CONF.service_providers.service_provider
|
providers = cfg.CONF.service_providers.service_provider
|
||||||
self.assertEqual(providers, [])
|
self.assertEqual(providers, [])
|
||||||
|
|
||||||
def test_parse_single_service_provider_opt(self):
|
def test_parse_single_service_provider_opt(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
':lbaas:driver_path'])
|
||||||
':lbaas:driver_path'],
|
|
||||||
'service_providers')
|
|
||||||
expected = {'service_type': constants.LOADBALANCER,
|
expected = {'service_type': constants.LOADBALANCER,
|
||||||
'name': 'lbaas',
|
'name': 'lbaas',
|
||||||
'driver': 'driver_path',
|
'driver': 'driver_path',
|
||||||
|
@ -40,10 +49,8 @@ class ParseServiceProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
self.assertEqual(res, [expected])
|
self.assertEqual(res, [expected])
|
||||||
|
|
||||||
def test_parse_single_default_service_provider_opt(self):
|
def test_parse_single_default_service_provider_opt(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
':lbaas:driver_path:default'])
|
||||||
':lbaas:driver_path:default'],
|
|
||||||
'service_providers')
|
|
||||||
expected = {'service_type': constants.LOADBALANCER,
|
expected = {'service_type': constants.LOADBALANCER,
|
||||||
'name': 'lbaas',
|
'name': 'lbaas',
|
||||||
'driver': 'driver_path',
|
'driver': 'driver_path',
|
||||||
|
@ -53,56 +60,46 @@ class ParseServiceProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
self.assertEqual(res, [expected])
|
self.assertEqual(res, [expected])
|
||||||
|
|
||||||
def test_parse_multi_service_provider_opt(self):
|
def test_parse_multi_service_provider_opt(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':lbaas:driver_path',
|
':lbaas:driver_path',
|
||||||
constants.LOADBALANCER + ':name1:path1',
|
constants.LOADBALANCER + ':name1:path1',
|
||||||
constants.LOADBALANCER +
|
constants.LOADBALANCER +
|
||||||
':name2:path2:default'],
|
':name2:path2:default'])
|
||||||
'service_providers')
|
|
||||||
res = provconf.parse_service_provider_opt()
|
res = provconf.parse_service_provider_opt()
|
||||||
# This parsing crosses repos if additional projects are installed,
|
# This parsing crosses repos if additional projects are installed,
|
||||||
# so check that at least what we expect is there; there may be more.
|
# so check that at least what we expect is there; there may be more.
|
||||||
self.assertTrue(len(res) >= 3)
|
self.assertTrue(len(res) >= 3)
|
||||||
|
|
||||||
def test_parse_service_provider_opt_not_allowed_raises(self):
|
|
||||||
cfg.CONF.set_override('service_provider',
|
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':lbaas:driver_path',
|
|
||||||
'svc_type:name1:path1'],
|
|
||||||
'service_providers')
|
|
||||||
self.assertRaises(n_exc.Invalid, provconf.parse_service_provider_opt)
|
|
||||||
|
|
||||||
def test_parse_service_provider_invalid_format(self):
|
def test_parse_service_provider_invalid_format(self):
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':lbaas:driver_path',
|
':lbaas:driver_path',
|
||||||
'svc_type:name1:path1:def'],
|
'svc_type:name1:path1:def'])
|
||||||
'service_providers')
|
|
||||||
self.assertRaises(n_exc.Invalid, provconf.parse_service_provider_opt)
|
self.assertRaises(n_exc.Invalid, provconf.parse_service_provider_opt)
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':',
|
':',
|
||||||
'svc_type:name1:path1:def'],
|
'svc_type:name1:path1:def'])
|
||||||
'service_providers')
|
|
||||||
self.assertRaises(n_exc.Invalid, provconf.parse_service_provider_opt)
|
self.assertRaises(n_exc.Invalid, provconf.parse_service_provider_opt)
|
||||||
|
|
||||||
def test_parse_service_provider_name_too_long(self):
|
def test_parse_service_provider_name_too_long(self):
|
||||||
name = 'a' * 256
|
name = 'a' * 256
|
||||||
cfg.CONF.set_override('service_provider',
|
self._set_override([constants.LOADBALANCER +
|
||||||
[constants.LOADBALANCER +
|
|
||||||
':' + name + ':driver_path',
|
':' + name + ':driver_path',
|
||||||
'svc_type:name1:path1:def'],
|
'svc_type:name1:path1:def'])
|
||||||
'service_providers')
|
|
||||||
self.assertRaises(n_exc.Invalid, provconf.parse_service_provider_opt)
|
self.assertRaises(n_exc.Invalid, provconf.parse_service_provider_opt)
|
||||||
|
|
||||||
|
|
||||||
class ProviderConfigurationTestCase(base.BaseTestCase):
|
class ProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ProviderConfigurationTestCase, self).setUp()
|
super(ProviderConfigurationTestCase, self).setUp()
|
||||||
|
self.service_providers = mock.patch.object(
|
||||||
|
provconf.NeutronModule, 'service_providers').start()
|
||||||
|
|
||||||
|
def _set_override(self, service_providers):
|
||||||
|
self.service_providers.return_value = service_providers
|
||||||
|
|
||||||
def test_ensure_driver_unique(self):
|
def test_ensure_driver_unique(self):
|
||||||
pconf = provconf.ProviderConfiguration([])
|
pconf = provconf.ProviderConfiguration()
|
||||||
pconf.providers[('svctype', 'name')] = {'driver': 'driver',
|
pconf.providers[('svctype', 'name')] = {'driver': 'driver',
|
||||||
'default': True}
|
'default': True}
|
||||||
self.assertRaises(n_exc.Invalid,
|
self.assertRaises(n_exc.Invalid,
|
||||||
|
@ -110,7 +107,7 @@ class ProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
self.assertIsNone(pconf._ensure_driver_unique('another_driver1'))
|
self.assertIsNone(pconf._ensure_driver_unique('another_driver1'))
|
||||||
|
|
||||||
def test_ensure_default_unique(self):
|
def test_ensure_default_unique(self):
|
||||||
pconf = provconf.ProviderConfiguration([])
|
pconf = provconf.ProviderConfiguration()
|
||||||
pconf.providers[('svctype', 'name')] = {'driver': 'driver',
|
pconf.providers[('svctype', 'name')] = {'driver': 'driver',
|
||||||
'default': True}
|
'default': True}
|
||||||
self.assertRaises(n_exc.Invalid,
|
self.assertRaises(n_exc.Invalid,
|
||||||
|
@ -121,7 +118,7 @@ class ProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
self.assertIsNone(pconf._ensure_default_unique('svctype1', False))
|
self.assertIsNone(pconf._ensure_default_unique('svctype1', False))
|
||||||
|
|
||||||
def test_add_provider(self):
|
def test_add_provider(self):
|
||||||
pconf = provconf.ProviderConfiguration([])
|
pconf = provconf.ProviderConfiguration()
|
||||||
prov = {'service_type': constants.LOADBALANCER,
|
prov = {'service_type': constants.LOADBALANCER,
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
'driver': 'path',
|
'driver': 'path',
|
||||||
|
@ -134,7 +131,7 @@ class ProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
[{'driver': 'path', 'default': False}])
|
[{'driver': 'path', 'default': False}])
|
||||||
|
|
||||||
def test_add_duplicate_provider(self):
|
def test_add_duplicate_provider(self):
|
||||||
pconf = provconf.ProviderConfiguration([])
|
pconf = provconf.ProviderConfiguration()
|
||||||
prov = {'service_type': constants.LOADBALANCER,
|
prov = {'service_type': constants.LOADBALANCER,
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
'driver': 'path',
|
'driver': 'path',
|
||||||
|
@ -144,6 +141,10 @@ class ProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
self.assertEqual(len(pconf.providers), 1)
|
self.assertEqual(len(pconf.providers), 1)
|
||||||
|
|
||||||
def test_get_service_providers(self):
|
def test_get_service_providers(self):
|
||||||
|
self._set_override([constants.LOADBALANCER + ':name:path',
|
||||||
|
constants.LOADBALANCER + ':name2:path2',
|
||||||
|
'st2:name:driver:default',
|
||||||
|
'st3:name2:driver2:default'])
|
||||||
provs = [{'service_type': constants.LOADBALANCER,
|
provs = [{'service_type': constants.LOADBALANCER,
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
'driver': 'path',
|
'driver': 'path',
|
||||||
|
@ -161,7 +162,7 @@ class ProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
'name': 'name2',
|
'name': 'name2',
|
||||||
'driver': 'driver2',
|
'driver': 'driver2',
|
||||||
'default': True}]
|
'default': True}]
|
||||||
pconf = provconf.ProviderConfiguration(provs)
|
pconf = provconf.ProviderConfiguration()
|
||||||
for prov in provs:
|
for prov in provs:
|
||||||
p = pconf.get_service_providers(
|
p = pconf.get_service_providers(
|
||||||
filters={'name': [prov['name']],
|
filters={'name': [prov['name']],
|
||||||
|
@ -170,6 +171,8 @@ class ProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
self.assertEqual(p, [prov])
|
self.assertEqual(p, [prov])
|
||||||
|
|
||||||
def test_get_service_providers_with_fields(self):
|
def test_get_service_providers_with_fields(self):
|
||||||
|
self._set_override([constants.LOADBALANCER + ":name:path",
|
||||||
|
constants.LOADBALANCER + ":name2:path2"])
|
||||||
provs = [{'service_type': constants.LOADBALANCER,
|
provs = [{'service_type': constants.LOADBALANCER,
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
'driver': 'path',
|
'driver': 'path',
|
||||||
|
@ -178,7 +181,7 @@ class ProviderConfigurationTestCase(base.BaseTestCase):
|
||||||
'name': 'name2',
|
'name': 'name2',
|
||||||
'driver': 'path2',
|
'driver': 'path2',
|
||||||
'default': False}]
|
'default': False}]
|
||||||
pconf = provconf.ProviderConfiguration(provs)
|
pconf = provconf.ProviderConfiguration()
|
||||||
for prov in provs:
|
for prov in provs:
|
||||||
p = pconf.get_service_providers(
|
p = pconf.get_service_providers(
|
||||||
filters={'name': [prov['name']],
|
filters={'name': [prov['name']],
|
||||||
|
|
Loading…
Reference in New Issue