Merge "Define volume services_client as library"
diff --git a/doc/source/library.rst b/doc/source/library.rst
index 6a2fb83..29248d1 100644
--- a/doc/source/library.rst
+++ b/doc/source/library.rst
@@ -67,3 +67,4 @@
library/utils
library/api_microversion_testing
library/auth
+ library/clients
diff --git a/doc/source/library/clients.rst b/doc/source/library/clients.rst
new file mode 100644
index 0000000..086cfc9
--- /dev/null
+++ b/doc/source/library/clients.rst
@@ -0,0 +1,24 @@
+.. _clients:
+
+Service Clients Usage
+=====================
+
+Tests make requests against APIs using service clients. Service clients are
+specializations of the ``RestClient`` class. The service clients that cover the
+APIs exposed by a service should be grouped in a service clients module.
+A service clients module is python module where all service clients are
+defined. If major API versions are available, submodules should be defined,
+one for each version.
+
+The ``ClientsFactory`` class helps initializing all clients of a specific
+service client module from a set of shared parameters.
+
+The ``ServiceClients`` class provides a convenient way to get access to all
+available service clients initialized with a provided set of credentials.
+
+------------------
+The clients module
+------------------
+
+.. automodule:: tempest.lib.services.clients
+ :members:
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 9640469..d34023f 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -111,8 +111,9 @@
class MyPlugin(plugins.TempestPlugin):
-Then you need to ensure you locally define all of the methods in the abstract
-class, you can refer to the api doc below for a reference of what that entails.
+Then you need to ensure you locally define all of the mandatory methods in the
+abstract class, you can refer to the api doc below for a reference of what that
+entails.
Abstract Plugin Class
---------------------
@@ -164,6 +165,142 @@
CONF object from tempest. This enables the plugin to add options to both
existing sections and also create new configuration sections for new options.
+Service Clients
+---------------
+
+If a plugin defines a service client, it is beneficial for it to implement the
+``get_service_clients`` method in the plugin class. All service clients which
+are exposed via this interface will be automatically configured and be
+available in any instance of the service clients class, defined in
+``tempest.lib.services.clients.ServiceClients``. In case multiple plugins are
+installed, all service clients from all plugins will be registered, making it
+easy to write tests which rely on multiple APIs whose service clients are in
+different plugins.
+
+Example implementation of ``get_service_clients``::
+
+ def get_service_clients(self):
+ # Example implementation with two service clients
+ my_service1_config = config.service_client_config('my_service')
+ params_my_service1 = {
+ 'name': 'my_service_v1',
+ 'service_version': 'my_service.v1',
+ 'module_path': 'plugin_tempest_tests.services.my_service.v1',
+ 'client_names': ['API1Client', 'API2Client'],
+ }
+ params_my_service1.update(my_service_config)
+ my_service2_config = config.service_client_config('my_service')
+ params_my_service2 = {
+ 'name': 'my_service_v2',
+ 'service_version': 'my_service.v2',
+ 'module_path': 'plugin_tempest_tests.services.my_service.v2',
+ 'client_names': ['API1Client', 'API2Client'],
+ }
+ params_my_service2.update(my_service2_config)
+ return [params_my_service1, params_my_service2]
+
+Parameters:
+
+* **name**: Name of the attribute used to access the ``ClientsFactory`` from
+ the ``ServiceClients`` instance. See example below.
+* **service_version**: Tempest enforces a single implementation for each
+ service client. Available service clients are held in a ``ClientsRegistry``
+ singleton, and registered with ``service_version``, which means that
+ ``service_version`` must be unique and it should represent the service API
+ and version implemented by the service client.
+* **module_path**: Relative to the service client module, from the root of the
+ plugin.
+* **client_names**: Name of the classes that implement service clients in the
+ service clients module.
+
+Example usage of the the service clients in tests::
+
+ # my_creds is instance of tempest.lib.auth.Credentials
+ # identity_uri is v2 or v3 depending on the configuration
+ from tempest.lib.services import clients
+
+ my_clients = clients.ServiceClients(my_creds, identity_uri)
+ my_service1_api1_client = my_clients.my_service_v1.API1Client()
+ my_service2_api1_client = my_clients.my_service_v2.API1Client(my_args='any')
+
+Automatic configuration and registration of service clients imposes some extra
+constraints on the structure of the configuration options exposed by the
+plugin.
+
+First ``service_version`` should be in the format `service_config[.version]`.
+The `.version` part is optional, and should only be used if there are multiple
+versions of the same API available. The `service_config` must match the name of
+a configuration options group defined by the plugin. Different versions of one
+API must share the same configuration group.
+
+Second the configuration options group `service_config` must contain the
+following options:
+
+* `catalog_type`: corresponds to `service` in the catalog
+* `endpoint_type`
+
+The following options will be honoured if defined, but they are not mandatory,
+as they do not necessarily apply to all service clients.
+
+* `region`: default to identity.region
+* `build_timeout` : default to compute.build_timeout
+* `build_interval`: default to compute.build_interval
+
+Third the service client classes should inherit from ``RestClient``, should
+accept generic keyword arguments, and should pass those arguments to the
+``__init__`` method of ``RestClient``. Extra arguments can be added. For
+instance::
+
+ class MyAPIClient(rest_client.RestClient):
+
+ def __init__(self, auth_provider, service, region,
+ my_arg, my_arg2=True, **kwargs):
+ super(MyAPIClient, self).__init__(
+ auth_provider, service, region, **kwargs)
+ self.my_arg = my_arg
+ self.my_args2 = my_arg
+
+Finally the service client should be structured in a python module, so that all
+service client classes are importable from it. Each major API version should
+have its own module.
+
+The following folder and module structure is recommended for a single major
+API version::
+
+ plugin_dir/
+ services/
+ __init__.py
+ client_api_1.py
+ client_api_2.py
+
+The content of __init__.py module should be::
+
+ from client_api_1.py import API1Client
+ from client_api_2.py import API2Client
+
+ __all__ = ['API1Client', 'API2Client']
+
+The following folder and module structure is recommended for multiple major
+API version::
+
+ plugin_dir/
+ services/
+ v1/
+ __init__.py
+ client_api_1.py
+ client_api_2.py
+ v2/
+ __init__.py
+ client_api_1.py
+ client_api_2.py
+
+The content each of __init__.py module under vN should be::
+
+ from client_api_1.py import API1Client
+ from client_api_2.py import API2Client
+
+ __all__ = ['API1Client', 'API2Client']
+
Using Plugins
=============
diff --git a/releasenotes/notes/clients_module-16f3025f515bf9ec.yaml b/releasenotes/notes/clients_module-16f3025f515bf9ec.yaml
new file mode 100644
index 0000000..53741da
--- /dev/null
+++ b/releasenotes/notes/clients_module-16f3025f515bf9ec.yaml
@@ -0,0 +1,18 @@
+---
+features:
+ - The Tempest plugin interface contains a new optional method, which allows
+ plugins to declare and automatically register any service client defined
+ in the plugin.
+ - tempest.lib exposes a new stable interface, the clients module and
+ ServiceClients class, which provides a convinient way for plugin tests to
+ access service clients defined in Tempest as well as service clients
+ defined in all loaded plugins.
+ The new ServiceClients class only exposes for now the service clients
+ which are in tempest.lib, i.e. compute, network and image. The remaing
+ service clients (identity, volume and object-storage) will be added in
+ future updates.
+deprecations:
+ - The new clients module provides a stable alternative to tempest classes
+ manager.Manager and clients.Manager. manager.Manager only exists now
+ to smoothen the transition of plugins to the new interface, but it will
+ be removed shortly without further notice.
diff --git a/releasenotes/notes/nova_cert_default-90eb7c1e3cde624a.yaml b/releasenotes/notes/nova_cert_default-90eb7c1e3cde624a.yaml
new file mode 100644
index 0000000..cfe97c5
--- /dev/null
+++ b/releasenotes/notes/nova_cert_default-90eb7c1e3cde624a.yaml
@@ -0,0 +1,8 @@
+---
+upgrade:
+
+ - The ``nova_cert`` option default is changed to ``False``. The nova
+ certification management APIs were a hold over from ec2, and are
+ not used by any other parts of nova. They are deprecated for
+ removal in nova after the newton release. This makes false a more
+ sensible default going forward.
\ No newline at end of file
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 11350e6..ff28b50 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -59,44 +59,6 @@
backup.update(changes)
return self._encode_backup(backup)
- @test.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
- def test_volume_backup_create_get_detailed_list_restore_delete(self):
- # Create backup
- backup_name = data_utils.rand_name('Backup')
- create_backup = self.admin_backups_client.create_backup
- backup = create_backup(volume_id=self.volume['id'],
- name=backup_name)['backup']
- self.addCleanup(self.admin_backups_client.delete_backup,
- backup['id'])
- self.assertEqual(backup_name, backup['name'])
- waiters.wait_for_volume_status(self.admin_volume_client,
- self.volume['id'], 'available')
- self.admin_backups_client.wait_for_backup_status(backup['id'],
- 'available')
-
- # Get a given backup
- backup = self.admin_backups_client.show_backup(backup['id'])['backup']
- self.assertEqual(backup_name, backup['name'])
-
- # Get all backups with detail
- backups = self.admin_backups_client.list_backups(
- detail=True)['backups']
- self.assertIn((backup['name'], backup['id']),
- [(m['name'], m['id']) for m in backups])
-
- # Restore backup
- restore = self.admin_backups_client.restore_backup(
- backup['id'])['restore']
-
- # Delete backup
- self.addCleanup(self.admin_volume_client.delete_volume,
- restore['volume_id'])
- self.assertEqual(backup['id'], restore['backup_id'])
- self.admin_backups_client.wait_for_backup_status(backup['id'],
- 'available')
- waiters.wait_for_volume_status(self.admin_volume_client,
- restore['volume_id'], 'available')
-
@test.idempotent_id('a99c54a1-dd80-4724-8a13-13bf58d4068d')
def test_volume_backup_export_import(self):
"""Test backup export import functionality.
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 87146db..d83c308 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -36,6 +36,44 @@
cls.volume = cls.create_volume()
+ @test.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
+ def test_volume_backup_create_get_detailed_list_restore_delete(self):
+ # Create backup
+ backup_name = data_utils.rand_name('Backup')
+ create_backup = self.backups_client.create_backup
+ backup = create_backup(volume_id=self.volume['id'],
+ name=backup_name)['backup']
+ self.addCleanup(self.backups_client.delete_backup,
+ backup['id'])
+ self.assertEqual(backup_name, backup['name'])
+ waiters.wait_for_volume_status(self.volumes_client,
+ self.volume['id'], 'available')
+ self.backups_client.wait_for_backup_status(backup['id'],
+ 'available')
+
+ # Get a given backup
+ backup = self.backups_client.show_backup(backup['id'])['backup']
+ self.assertEqual(backup_name, backup['name'])
+
+ # Get all backups with detail
+ backups = self.backups_client.list_backups(
+ detail=True)['backups']
+ self.assertIn((backup['name'], backup['id']),
+ [(m['name'], m['id']) for m in backups])
+
+ # Restore backup
+ restore = self.backups_client.restore_backup(
+ backup['id'])['restore']
+
+ # Delete backup
+ self.addCleanup(self.volumes_client.delete_volume,
+ restore['volume_id'])
+ self.assertEqual(backup['id'], restore['backup_id'])
+ self.backups_client.wait_for_backup_status(backup['id'],
+ 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ restore['volume_id'], 'available')
+
@test.idempotent_id('07af8f6d-80af-44c9-a5dc-c8427b1b62e6')
@test.services('compute')
def test_backup_create_attached_volume(self):
diff --git a/tempest/clients.py b/tempest/clients.py
index e070637..406f9d5 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -14,15 +14,13 @@
# under the License.
import copy
-
from oslo_log import log as logging
-
from tempest.common import negative_rest_client
from tempest import config
from tempest import exceptions
from tempest.lib import auth
from tempest.lib import exceptions as lib_exc
-from tempest import service_clients
+from tempest.lib.services import clients
from tempest.services import baremetal
from tempest.services import data_processing
from tempest.services import identity
@@ -34,7 +32,7 @@
LOG = logging.getLogger(__name__)
-class Manager(service_clients.ServiceClients):
+class Manager(clients.ServiceClients):
"""Top level manager for OpenStack tempest clients"""
default_params = config.service_client_config()
@@ -109,7 +107,9 @@
# Setup the parameters for all Tempest services.
# NOTE(andreaf) Since client.py is an internal module of Tempest,
# it doesn't have to consider plugin configuration.
- for service in service_clients.tempest_modules():
+ all_tempest_modules = (set(clients.tempest_modules()) |
+ clients._tempest_internal_modules())
+ for service in all_tempest_modules:
try:
# NOTE(andreaf) Use the unversioned service name to fetch
# the configuration since configuration is not versioned.
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 073481c..9d307ee 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -53,9 +53,7 @@
return
# NOTE(afazekas): The instance is in "ready for action state"
# when no task in progress
- # NOTE(afazekas): Converted to string because of the XML
- # responses
- if str(task_state) == "None":
+ if task_state is None:
# without state api extension 3 sec usually enough
time.sleep(CONF.compute.ready_wait)
return
diff --git a/tempest/config.py b/tempest/config.py
index 6bae021..daff494 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -379,8 +379,9 @@
help='Does the test environment support creating snapshot '
'images of running instances?'),
cfg.BoolOpt('nova_cert',
- default=True,
- help='Does the test environment have the nova cert running?'),
+ default=False,
+ help='Does the test environment have the nova cert running?',
+ deprecated_for_removal=True),
cfg.BoolOpt('personality',
default=False,
help='Does the test environment support server personality'),
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 8054e62..e782321 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -1,3 +1,4 @@
+# Copyright 2012 OpenStack Foundation
# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
# All Rights Reserved.
#
@@ -13,8 +14,92 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+import importlib
+import inspect
+import logging
+import six
+
+from tempest.lib import auth
from tempest.lib.common.utils import misc
from tempest.lib import exceptions
+from tempest.lib.services import compute
+from tempest.lib.services import image
+from tempest.lib.services import network
+
+
+LOG = logging.getLogger(__name__)
+
+
+def tempest_modules():
+ """Dict of service client modules available in Tempest.
+
+ Provides a dict of stable service modules available in Tempest, with
+ ``service_version`` as key, and the module object as value.
+ """
+ return {
+ 'compute': compute,
+ 'image.v1': image.v1,
+ 'image.v2': image.v2,
+ 'network': network
+ }
+
+
+def _tempest_internal_modules():
+ # Set of unstable service clients available in Tempest
+ # NOTE(andreaf) This list will exists only as long the remain clients
+ # are migrated to tempest.lib, and it will then be deleted without
+ # deprecation or advance notice
+ return set(['identity.v2', 'identity.v3', 'object-storage', 'volume.v1',
+ 'volume.v2', 'volume.v3'])
+
+
+def available_modules():
+ """Set of service client modules available in Tempest and plugins
+
+ Set of stable service clients from Tempest and service clients exposed
+ by plugins. This set of available modules can be used for automatic
+ configuration.
+
+ :raise PluginRegistrationException: if a plugin exposes a service_version
+ already defined by Tempest or another plugin.
+
+ Examples:
+
+ >>> from tempest import config
+ >>> params = {}
+ >>> for service_version in available_modules():
+ >>> service = service_version.split('.')[0]
+ >>> params[service] = config.service_client_config(service)
+ >>> service_clients = ServiceClients(creds, identity_uri,
+ >>> client_parameters=params)
+ """
+ extra_service_versions = set([])
+ _tempest_modules = set(tempest_modules())
+ plugin_services = ClientsRegistry().get_service_clients()
+ for plugin_name in plugin_services:
+ plug_service_versions = set([x['service_version'] for x in
+ plugin_services[plugin_name]])
+ # If a plugin exposes a duplicate service_version raise an exception
+ if plug_service_versions:
+ if not plug_service_versions.isdisjoint(extra_service_versions):
+ detailed_error = (
+ 'Plugin %s is trying to register a service %s already '
+ 'claimed by another one' % (plugin_name,
+ extra_service_versions &
+ plug_service_versions))
+ raise exceptions.PluginRegistrationException(
+ name=plugin_name, detailed_error=detailed_error)
+ if not plug_service_versions.isdisjoint(_tempest_modules):
+ detailed_error = (
+ 'Plugin %s is trying to register a service %s already '
+ 'claimed by a Tempest one' % (plugin_name,
+ _tempest_modules &
+ plug_service_versions))
+ raise exceptions.PluginRegistrationException(
+ name=plugin_name, detailed_error=detailed_error)
+ extra_service_versions |= plug_service_versions
+ return _tempest_modules | extra_service_versions
@misc.singleton
@@ -34,3 +119,333 @@
def get_service_clients(self):
return self._service_clients
+
+
+class ClientsFactory(object):
+ """Builds service clients for a service client module
+
+ This class implements the logic of feeding service client parameters
+ to service clients from a specific module. It allows setting the
+ parameters once and obtaining new instances of the clients without the
+ need of passing any parameter.
+
+ ClientsFactory can be used directly, or consumed via the `ServiceClients`
+ class, which manages the authorization part.
+ """
+
+ def __init__(self, module_path, client_names, auth_provider, **kwargs):
+ """Initialises the client factory
+
+ :param module_path: Path to module that includes all service clients.
+ All service client classes must be exposed by a single module.
+ If they are separated in different modules, defining __all__
+ in the root module can help, similar to what is done by service
+ clients in tempest.
+ :param client_names: List or set of names of the service client
+ classes.
+ :param auth_provider: The auth provider used to initialise client.
+ :param kwargs: Parameters to be passed to all clients. Parameters
+ values can be overwritten when clients are initialised, but
+ parameters cannot be deleted.
+ :raise ImportError if the specified module_path cannot be imported
+
+ Example:
+
+ >>> # Get credentials and an auth_provider
+ >>> clients = ClientsFactory(
+ >>> module_path='my_service.my_service_clients',
+ >>> client_names=['ServiceClient1', 'ServiceClient2'],
+ >>> auth_provider=auth_provider,
+ >>> service='my_service',
+ >>> region='region1')
+ >>> my_api_client = clients.MyApiClient()
+ >>> my_api_client_region2 = clients.MyApiClient(region='region2')
+
+ """
+ # Import the module. If it's not importable, the raised exception
+ # provides good enough information about what happened
+ _module = importlib.import_module(module_path)
+ # If any of the classes is not in the module we fail
+ for class_name in client_names:
+ # TODO(andreaf) This always passes all parameters to all clients.
+ # In future to allow clients to specify the list of parameters
+ # that they accept based out of a list of standard ones.
+
+ # Obtain the class
+ klass = self._get_class(_module, class_name)
+ final_kwargs = copy.copy(kwargs)
+
+ # Set the function as an attribute of the factory
+ setattr(self, class_name, self._get_partial_class(
+ klass, auth_provider, final_kwargs))
+
+ def _get_partial_class(self, klass, auth_provider, kwargs):
+
+ # Define a function that returns a new class instance by
+ # combining default kwargs with extra ones
+ def partial_class(alias=None, **later_kwargs):
+ """Returns a callable the initialises a service client
+
+ Builds a callable that accepts kwargs, which are passed through
+ to the __init__ of the service client, along with a set of defaults
+ set in factory at factory __init__ time.
+ Original args in the service client can only be passed as kwargs.
+
+ It accepts one extra parameter 'alias' compared to the original
+ service client. When alias is provided, the returned callable will
+ also set an attribute called with a name defined in 'alias', which
+ contains the instance of the service client.
+
+ :param alias: str Name of the attribute set on the factory once
+ the callable is invoked which contains the initialised
+ service client. If None, no attribute is set.
+ :param later_kwargs: kwargs passed through to the service client
+ __init__ on top of defaults set at factory level.
+ """
+ kwargs.update(later_kwargs)
+ _client = klass(auth_provider=auth_provider, **kwargs)
+ if alias:
+ setattr(self, alias, _client)
+ return _client
+
+ return partial_class
+
+ @classmethod
+ def _get_class(cls, module, class_name):
+ klass = getattr(module, class_name, None)
+ if not klass:
+ msg = 'Invalid class name, %s is not found in %s'
+ raise AttributeError(msg % (class_name, module))
+ if not inspect.isclass(klass):
+ msg = 'Expected a class, got %s of type %s instead'
+ raise TypeError(msg % (klass, type(klass)))
+ return klass
+
+
+class ServiceClients(object):
+ """Service client provider class
+
+ The ServiceClients object provides a useful means for tests to access
+ service clients configured for a specified set of credentials.
+ It hides some of the complexity from the authorization and configuration
+ layers.
+
+ Examples:
+
+ >>> from tempest.lib.services import clients
+ >>> johndoe = cred_provider.get_creds_by_role(['johndoe'])
+ >>> johndoe_clients = clients.ServiceClients(johndoe,
+ >>> identity_uri)
+ >>> johndoe_servers = johndoe_clients.servers_client.list_servers()
+
+ """
+ # NOTE(andreaf) This class does not depend on tempest configuration
+ # and its meant for direct consumption by external clients such as tempest
+ # plugins. Tempest provides a wrapper class, `clients.Manager`, that
+ # initialises this class using values from tempest CONF object. The wrapper
+ # class should only be used by tests hosted in Tempest.
+
+ def __init__(self, credentials, identity_uri, region=None, scope='project',
+ disable_ssl_certificate_validation=True, ca_certs=None,
+ trace_requests='', client_parameters=None):
+ """Service Clients provider
+
+ Instantiate a `ServiceClients` object, from a set of credentials and an
+ identity URI. The identity version is inferred from the credentials
+ object. Optionally auth scope can be provided.
+
+ A few parameters can be given a value which is applied as default
+ for all service clients: region, dscv, ca_certs, trace_requests.
+
+ Parameters dscv, ca_certs and trace_requests all apply to the auth
+ provider as well as any service clients provided by this manager.
+
+ Any other client parameter must be set via client_parameters.
+ The list of available parameters is defined in the service clients
+ interfaces. For reference, most clients will accept 'region',
+ 'service', 'endpoint_type', 'build_timeout' and 'build_interval', which
+ are all inherited from RestClient.
+
+ The `config` module in Tempest exposes an helper function
+ `service_client_config` that can be used to extract from configuration
+ a dictionary ready to be injected in kwargs.
+
+ Exceptions are:
+ - Token clients for 'identity' have a very different interface
+ - Volume client for 'volume' accepts 'default_volume_size'
+ - Servers client from 'compute' accepts 'enable_instance_password'
+
+ Examples:
+
+ >>> identity_params = config.service_client_config('identity')
+ >>> params = {
+ >>> 'identity': identity_params,
+ >>> 'compute': {'region': 'region2'}}
+ >>> manager = lib_manager.Manager(
+ >>> my_creds, identity_uri, client_parameters=params)
+
+ :param credentials: An instance of `auth.Credentials`
+ :param identity_uri: URI of the identity API. This should be a
+ mandatory parameter, and it will so soon.
+ :param region: Default value of region for service clients.
+ :param scope: default scope for tokens produced by the auth provider
+ :param disable_ssl_certificate_validation: Applies to auth and to all
+ service clients.
+ :param ca_certs: Applies to auth and to all service clients.
+ :param trace_requests: Applies to auth and to all service clients.
+ :param client_parameters: Dictionary with parameters for service
+ clients. Keys of the dictionary are the service client service
+ name, as declared in `service_clients.available_modules()` except
+ for the version. Values are dictionaries of parameters that are
+ going to be passed to all clients in the service client module.
+
+ Examples:
+
+ >>> params_service_x = {'param_name': 'param_value'}
+ >>> client_parameters = { 'service_x': params_service_x }
+
+ >>> params_service_y = config.service_client_config('service_y')
+ >>> client_parameters['service_y'] = params_service_y
+
+ """
+ self._registered_services = set([])
+ self.credentials = credentials
+ self.identity_uri = identity_uri
+ if not identity_uri:
+ raise exceptions.InvalidCredentials(
+ 'ServiceClients requires a non-empty identity_uri.')
+ self.region = region
+ # Check if passed or default credentials are valid
+ if not self.credentials.is_valid():
+ raise exceptions.InvalidCredentials()
+ # Get the identity classes matching the provided credentials
+ # TODO(andreaf) Define a new interface in Credentials to get
+ # the API version from an instance
+ identity = [(k, auth.IDENTITY_VERSION[k][1]) for k in
+ auth.IDENTITY_VERSION.keys() if
+ isinstance(self.credentials, auth.IDENTITY_VERSION[k][0])]
+ # Zero matches or more than one are both not valid.
+ if len(identity) != 1:
+ raise exceptions.InvalidCredentials()
+ self.auth_version, auth_provider_class = identity[0]
+ self.dscv = disable_ssl_certificate_validation
+ self.ca_certs = ca_certs
+ self.trace_requests = trace_requests
+ # Creates an auth provider for the credentials
+ self.auth_provider = auth_provider_class(
+ self.credentials, self.identity_uri, scope=scope,
+ disable_ssl_certificate_validation=self.dscv,
+ ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+ # Setup some defaults for client parameters of registered services
+ client_parameters = client_parameters or {}
+ self.parameters = {}
+ # Parameters are provided for unversioned services
+ all_modules = available_modules() | _tempest_internal_modules()
+ unversioned_services = set(
+ [x.split('.')[0] for x in all_modules])
+ for service in unversioned_services:
+ self.parameters[service] = self._setup_parameters(
+ client_parameters.pop(service, {}))
+ # Check that no client parameters was supplied for unregistered clients
+ if client_parameters:
+ raise exceptions.UnknownServiceClient(
+ services=list(client_parameters.keys()))
+
+ # Register service clients owned by tempest
+ for service, module in six.iteritems(tempest_modules()):
+ attribute = service.replace('.', '_')
+ configs = service.split('.')[0]
+ self.register_service_client_module(
+ attribute, service, module.__name__,
+ module.__all__, **self.parameters[configs])
+
+ # Register service clients from plugins
+ clients_registry = ClientsRegistry()
+ plugin_service_clients = clients_registry.get_service_clients()
+ for plugin in plugin_service_clients:
+ service_clients = plugin_service_clients[plugin]
+ # Each plugin returns a list of service client parameters
+ for service_client in service_clients:
+ # NOTE(andreaf) If a plugin cannot register, stop the
+ # registration process, log some details to help
+ # troubleshooting, and re-raise
+ try:
+ self.register_service_client_module(**service_client)
+ except Exception:
+ LOG.exception(
+ 'Failed to register service client from plugin %s '
+ 'with parameters %s' % (plugin, service_client))
+ raise
+
+ def register_service_client_module(self, name, service_version,
+ module_path, client_names, **kwargs):
+ """Register a service client module
+
+ Initiates a client factory for the specified module, using this
+ class auth_provider, and accessible via a `name` attribute in the
+ service client.
+
+ :param name: Name used to access the client
+ :param service_version: Name of the service complete with version.
+ Used to track registered services. When a plugin implements it,
+ it can be used by other plugins to obtain their configuration.
+ :param module_path: Path to module that includes all service clients.
+ All service client classes must be exposed by a single module.
+ If they are separated in different modules, defining __all__
+ in the root module can help, similar to what is done by service
+ clients in tempest.
+ :param client_names: List or set of names of service client classes.
+ :param kwargs: Extra optional parameters to be passed to all clients.
+ ServiceClient provides defaults for region, dscv, ca_certs and
+ trace_requests.
+ :raise ServiceClientRegistrationException: if the provided name is
+ already in use or if service_version is already registered.
+ :raise ImportError: if module_path cannot be imported.
+ """
+ if hasattr(self, name):
+ using_name = getattr(self, name)
+ detailed_error = 'Module name already in use: %s' % using_name
+ raise exceptions.ServiceClientRegistrationException(
+ name=name, service_version=service_version,
+ module_path=module_path, client_names=client_names,
+ detailed_error=detailed_error)
+ if service_version in self.registered_services:
+ detailed_error = 'Service %s already registered.' % service_version
+ raise exceptions.ServiceClientRegistrationException(
+ name=name, service_version=service_version,
+ module_path=module_path, client_names=client_names,
+ detailed_error=detailed_error)
+ params = dict(region=self.region,
+ disable_ssl_certificate_validation=self.dscv,
+ ca_certs=self.ca_certs,
+ trace_requests=self.trace_requests)
+ params.update(kwargs)
+ # Instantiate the client factory
+ _factory = ClientsFactory(module_path=module_path,
+ client_names=client_names,
+ auth_provider=self.auth_provider,
+ **params)
+ # Adds the client factory to the service_client
+ setattr(self, name, _factory)
+ # Add the name of the new service in self.SERVICES for discovery
+ self._registered_services.add(service_version)
+
+ @property
+ def registered_services(self):
+ return self._registered_services | _tempest_internal_modules()
+
+ def _setup_parameters(self, parameters):
+ """Setup default values for client parameters
+
+ Region by default is the region passed as an __init__ parameter.
+ Checks that no parameter for an unknown service is provided.
+ """
+ _parameters = {}
+ # Use region from __init__
+ if self.region:
+ _parameters['region'] = self.region
+ # Update defaults with specified parameters
+ _parameters.update(parameters)
+ # If any parameter is left, parameters for an unknown service were
+ # provided as input. Fail rather than ignore silently.
+ return _parameters
diff --git a/tempest/lib/services/network/base.py b/tempest/lib/services/network/base.py
index a6ada04..620e0f1 100644
--- a/tempest/lib/services/network/base.py
+++ b/tempest/lib/services/network/base.py
@@ -54,18 +54,26 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def create_resource(self, uri, post_data):
+ def create_resource(self, uri, post_data, expect_empty_body=False):
req_uri = self.uri_prefix + uri
req_post_data = json.dumps(post_data)
resp, body = self.post(req_uri, req_post_data)
- body = json.loads(body)
+ # NOTE: RFC allows both a valid non-empty body and an empty body for
+ # response of POST API. If a body is expected not empty, we decode the
+ # body. Otherwise we returns the body as it is.
+ if not expect_empty_body:
+ body = json.loads(body)
self.expected_success(201, resp.status)
return rest_client.ResponseBody(resp, body)
- def update_resource(self, uri, post_data):
+ def update_resource(self, uri, post_data, expect_empty_body=False):
req_uri = self.uri_prefix + uri
req_post_data = json.dumps(post_data)
resp, body = self.put(req_uri, req_post_data)
- body = json.loads(body)
+ # NOTE: RFC allows both a valid non-empty body and an empty body for
+ # response of PUT API. If a body is expected not empty, we decode the
+ # body. Otherwise we returns the body as it is.
+ if not expect_empty_body:
+ body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/manager.py b/tempest/manager.py
index 3d495b6..e3174d4 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -15,15 +15,15 @@
from oslo_log import log as logging
-from tempest import clients
+from tempest import clients as tempest_clients
from tempest import config
-from tempest import service_clients
+from tempest.lib.services import clients
CONF = config.CONF
LOG = logging.getLogger(__name__)
-class Manager(service_clients.ServiceClients):
+class Manager(clients.ServiceClients):
"""Service client manager class for backward compatibility
The former manager.Manager is not a stable interface in Tempest,
@@ -37,7 +37,7 @@
"soon as the client manager becomes available in tempest.lib.")
LOG.warning(msg)
dscv = CONF.identity.disable_ssl_certificate_validation
- _, uri = clients.get_auth_provider_class(credentials)
+ _, uri = tempest_clients.get_auth_provider_class(credentials)
super(Manager, self).__init__(
credentials=credentials, scope=scope,
identity_uri=uri,
@@ -58,5 +58,5 @@
"as such it should not imported directly. It will be removed as "
"the client manager becomes available in tempest.lib.")
LOG.warning(msg)
- return clients.get_auth_provider(credentials=credentials,
- pre_auth=pre_auth, scope=scope)
+ return tempest_clients.get_auth_provider(credentials=credentials,
+ pre_auth=pre_auth, scope=scope)
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 9c48080..e0e1204 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -24,6 +24,7 @@
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
from tempest.scenario import manager
from tempest import test
@@ -410,6 +411,7 @@
@test.idempotent_id('1546850e-fbaa-42f5-8b5f-03d8a6a95f15')
@testtools.skipIf(CONF.baremetal.driver_enabled,
'Baremetal relies on a shared physical network.')
+ @decorators.skip_because(bug="1610994")
@test.services('compute', 'network')
def test_connectivity_between_vms_on_different_networks(self):
"""Test connectivity between VMs on different networks
diff --git a/tempest/service_clients.py b/tempest/service_clients.py
deleted file mode 100644
index 136ad65..0000000
--- a/tempest/service_clients.py
+++ /dev/null
@@ -1,425 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-# 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 copy
-import importlib
-import inspect
-import logging
-
-from tempest.lib import auth
-from tempest.lib import exceptions
-from tempest.lib.services import clients
-from tempest.lib.services import compute
-from tempest.lib.services import image
-from tempest.lib.services import network
-
-LOG = logging.getLogger(__name__)
-
-client_modules_by_service_name = {
- 'compute': compute,
- 'image.v1': image.v1,
- 'image.v2': image.v2,
- 'network': network
-}
-
-
-def tempest_modules():
- """List of service client modules available in Tempest.
-
- Provides a list of service modules available Tempest.
- """
- return set(['compute', 'identity.v2', 'identity.v3', 'image.v1',
- 'image.v2', 'network', 'object-storage', 'volume.v1',
- 'volume.v2', 'volume.v3'])
-
-
-def available_modules():
- """List of service client modules available in Tempest and plugins
-
- The list of available modules can be used for automatic configuration.
-
- :raise PluginRegistrationException: if a plugin exposes a service_version
- already defined by Tempest or another plugin.
-
- Examples:
-
- >>> from tempest import config
- >>> params = {}
- >>> for service_version in available_modules():
- >>> service = service_version.split('.')[0]
- >>> params[service] = config.service_client_config(service)
- >>> service_clients = ServiceClients(creds, identity_uri,
- >>> client_parameters=params)
- """
- extra_service_versions = set([])
- plugin_services = clients.ClientsRegistry().get_service_clients()
- for plugin_name in plugin_services:
- plug_service_versions = set([x['service_version'] for x in
- plugin_services[plugin_name]])
- # If a plugin exposes a duplicate service_version raise an exception
- if plug_service_versions:
- if not plug_service_versions.isdisjoint(extra_service_versions):
- detailed_error = (
- 'Plugin %s is trying to register a service %s already '
- 'claimed by another one' % (plugin_name,
- extra_service_versions &
- plug_service_versions))
- raise exceptions.PluginRegistrationException(
- name=plugin_name, detailed_error=detailed_error)
- if not plug_service_versions.isdisjoint(tempest_modules()):
- detailed_error = (
- 'Plugin %s is trying to register a service %s already '
- 'claimed by a Tempest one' % (plugin_name,
- tempest_modules() &
- plug_service_versions))
- raise exceptions.PluginRegistrationException(
- name=plugin_name, detailed_error=detailed_error)
- extra_service_versions |= plug_service_versions
- return tempest_modules() | extra_service_versions
-
-
-class ClientsFactory(object):
- """Builds service clients for a service client module
-
- This class implements the logic of feeding service client parameters
- to service clients from a specific module. It allows setting the
- parameters once and obtaining new instances of the clients without the
- need of passing any parameter.
-
- ClientsFactory can be used directly, or consumed via the `ServiceClients`
- class, which manages the authorization part.
- """
-
- def __init__(self, module_path, client_names, auth_provider, **kwargs):
- """Initialises the client factory
-
- :param module_path: Path to module that includes all service clients.
- All service client classes must be exposed by a single module.
- If they are separated in different modules, defining __all__
- in the root module can help, similar to what is done by service
- clients in tempest.
- :param client_names: List or set of names of the service client
- classes.
- :param auth_provider: The auth provider used to initialise client.
- :param kwargs: Parameters to be passed to all clients. Parameters
- values can be overwritten when clients are initialised, but
- parameters cannot be deleted.
- :raise ImportError if the specified module_path cannot be imported
-
- Example:
-
- >>> # Get credentials and an auth_provider
- >>> clients = ClientsFactory(
- >>> module_path='my_service.my_service_clients',
- >>> client_names=['ServiceClient1', 'ServiceClient2'],
- >>> auth_provider=auth_provider,
- >>> service='my_service',
- >>> region='region1')
- >>> my_api_client = clients.MyApiClient()
- >>> my_api_client_region2 = clients.MyApiClient(region='region2')
-
- """
- # Import the module. If it's not importable, the raised exception
- # provides good enough information about what happened
- _module = importlib.import_module(module_path)
- # If any of the classes is not in the module we fail
- for class_name in client_names:
- # TODO(andreaf) This always passes all parameters to all clients.
- # In future to allow clients to specify the list of parameters
- # that they accept based out of a list of standard ones.
-
- # Obtain the class
- klass = self._get_class(_module, class_name)
- final_kwargs = copy.copy(kwargs)
-
- # Set the function as an attribute of the factory
- setattr(self, class_name, self._get_partial_class(
- klass, auth_provider, final_kwargs))
-
- def _get_partial_class(self, klass, auth_provider, kwargs):
-
- # Define a function that returns a new class instance by
- # combining default kwargs with extra ones
- def partial_class(alias=None, **later_kwargs):
- """Returns a callable the initialises a service client
-
- Builds a callable that accepts kwargs, which are passed through
- to the __init__ of the service client, along with a set of defaults
- set in factory at factory __init__ time.
- Original args in the service client can only be passed as kwargs.
-
- It accepts one extra parameter 'alias' compared to the original
- service client. When alias is provided, the returned callable will
- also set an attribute called with a name defined in 'alias', which
- contains the instance of the service client.
-
- :param alias: str Name of the attribute set on the factory once
- the callable is invoked which contains the initialised
- service client. If None, no attribute is set.
- :param later_kwargs: kwargs passed through to the service client
- __init__ on top of defaults set at factory level.
- """
- kwargs.update(later_kwargs)
- _client = klass(auth_provider=auth_provider, **kwargs)
- if alias:
- setattr(self, alias, _client)
- return _client
-
- return partial_class
-
- @classmethod
- def _get_class(cls, module, class_name):
- klass = getattr(module, class_name, None)
- if not klass:
- msg = 'Invalid class name, %s is not found in %s'
- raise AttributeError(msg % (class_name, module))
- if not inspect.isclass(klass):
- msg = 'Expected a class, got %s of type %s instead'
- raise TypeError(msg % (klass, type(klass)))
- return klass
-
-
-class ServiceClients(object):
- """Service client provider class
-
- The ServiceClients object provides a useful means for tests to access
- service clients configured for a specified set of credentials.
- It hides some of the complexity from the authorization and configuration
- layers.
-
- Examples:
-
- >>> from tempest import service_clients
- >>> johndoe = cred_provider.get_creds_by_role(['johndoe'])
- >>> johndoe_clients = service_clients.ServiceClients(johndoe,
- >>> identity_uri)
- >>> johndoe_servers = johndoe_clients.servers_client.list_servers()
-
- """
- # NOTE(andreaf) This class does not depend on tempest configuration
- # and its meant for direct consumption by external clients such as tempest
- # plugins. Tempest provides a wrapper class, `clients.Manager`, that
- # initialises this class using values from tempest CONF object. The wrapper
- # class should only be used by tests hosted in Tempest.
-
- def __init__(self, credentials, identity_uri, region=None, scope='project',
- disable_ssl_certificate_validation=True, ca_certs=None,
- trace_requests='', client_parameters=None):
- """Service Clients provider
-
- Instantiate a `ServiceClients` object, from a set of credentials and an
- identity URI. The identity version is inferred from the credentials
- object. Optionally auth scope can be provided.
-
- A few parameters can be given a value which is applied as default
- for all service clients: region, dscv, ca_certs, trace_requests.
-
- Parameters dscv, ca_certs and trace_requests all apply to the auth
- provider as well as any service clients provided by this manager.
-
- Any other client parameter must be set via client_parameters.
- The list of available parameters is defined in the service clients
- interfaces. For reference, most clients will accept 'region',
- 'service', 'endpoint_type', 'build_timeout' and 'build_interval', which
- are all inherited from RestClient.
-
- The `config` module in Tempest exposes an helper function
- `service_client_config` that can be used to extract from configuration
- a dictionary ready to be injected in kwargs.
-
- Exceptions are:
- - Token clients for 'identity' have a very different interface
- - Volume client for 'volume' accepts 'default_volume_size'
- - Servers client from 'compute' accepts 'enable_instance_password'
-
- Examples:
-
- >>> identity_params = config.service_client_config('identity')
- >>> params = {
- >>> 'identity': identity_params,
- >>> 'compute': {'region': 'region2'}}
- >>> manager = lib_manager.Manager(
- >>> my_creds, identity_uri, client_parameters=params)
-
- :param credentials: An instance of `auth.Credentials`
- :param identity_uri: URI of the identity API. This should be a
- mandatory parameter, and it will so soon.
- :param region: Default value of region for service clients.
- :param scope: default scope for tokens produced by the auth provider
- :param disable_ssl_certificate_validation: Applies to auth and to all
- service clients.
- :param ca_certs: Applies to auth and to all service clients.
- :param trace_requests: Applies to auth and to all service clients.
- :param client_parameters: Dictionary with parameters for service
- clients. Keys of the dictionary are the service client service
- name, as declared in `service_clients.available_modules()` except
- for the version. Values are dictionaries of parameters that are
- going to be passed to all clients in the service client module.
-
- Examples:
-
- >>> params_service_x = {'param_name': 'param_value'}
- >>> client_parameters = { 'service_x': params_service_x }
-
- >>> params_service_y = config.service_client_config('service_y')
- >>> client_parameters['service_y'] = params_service_y
-
- """
- self._registered_services = set([])
- self.credentials = credentials
- self.identity_uri = identity_uri
- if not identity_uri:
- raise exceptions.InvalidCredentials(
- 'ServiceClients requires a non-empty identity_uri.')
- self.region = region
- # Check if passed or default credentials are valid
- if not self.credentials.is_valid():
- raise exceptions.InvalidCredentials()
- # Get the identity classes matching the provided credentials
- # TODO(andreaf) Define a new interface in Credentials to get
- # the API version from an instance
- identity = [(k, auth.IDENTITY_VERSION[k][1]) for k in
- auth.IDENTITY_VERSION.keys() if
- isinstance(self.credentials, auth.IDENTITY_VERSION[k][0])]
- # Zero matches or more than one are both not valid.
- if len(identity) != 1:
- raise exceptions.InvalidCredentials()
- self.auth_version, auth_provider_class = identity[0]
- self.dscv = disable_ssl_certificate_validation
- self.ca_certs = ca_certs
- self.trace_requests = trace_requests
- # Creates an auth provider for the credentials
- self.auth_provider = auth_provider_class(
- self.credentials, self.identity_uri, scope=scope,
- disable_ssl_certificate_validation=self.dscv,
- ca_certs=self.ca_certs, trace_requests=self.trace_requests)
- # Setup some defaults for client parameters of registered services
- client_parameters = client_parameters or {}
- self.parameters = {}
- # Parameters are provided for unversioned services
- unversioned_services = set(
- [x.split('.')[0] for x in available_modules()])
- for service in unversioned_services:
- self.parameters[service] = self._setup_parameters(
- client_parameters.pop(service, {}))
- # Check that no client parameters was supplied for unregistered clients
- if client_parameters:
- raise exceptions.UnknownServiceClient(
- services=list(client_parameters.keys()))
-
- # Register service clients owned by tempest
- for service in tempest_modules():
- if service in list(client_modules_by_service_name):
- attribute = service.replace('.', '_')
- configs = service.split('.')[0]
- module = client_modules_by_service_name[service]
- self.register_service_client_module(
- attribute, service, module.__name__,
- module.__all__, **self.parameters[configs])
-
- # Register service clients from plugins
- clients_registry = clients.ClientsRegistry()
- plugin_service_clients = clients_registry.get_service_clients()
- for plugin in plugin_service_clients:
- service_clients = plugin_service_clients[plugin]
- # Each plugin returns a list of service client parameters
- for service_client in service_clients:
- # NOTE(andreaf) If a plugin cannot register, stop the
- # registration process, log some details to help
- # troubleshooting, and re-raise
- try:
- self.register_service_client_module(**service_client)
- except Exception:
- LOG.exception(
- 'Failed to register service client from plugin %s '
- 'with parameters %s' % (plugin, service_client))
- raise
-
- def register_service_client_module(self, name, service_version,
- module_path, client_names, **kwargs):
- """Register a service client module
-
- Initiates a client factory for the specified module, using this
- class auth_provider, and accessible via a `name` attribute in the
- service client.
-
- :param name: Name used to access the client
- :param service_version: Name of the service complete with version.
- Used to track registered services. When a plugin implements it,
- it can be used by other plugins to obtain their configuration.
- :param module_path: Path to module that includes all service clients.
- All service client classes must be exposed by a single module.
- If they are separated in different modules, defining __all__
- in the root module can help, similar to what is done by service
- clients in tempest.
- :param client_names: List or set of names of service client classes.
- :param kwargs: Extra optional parameters to be passed to all clients.
- ServiceClient provides defaults for region, dscv, ca_certs and
- trace_requests.
- :raise ServiceClientRegistrationException: if the provided name is
- already in use or if service_version is already registered.
- :raise ImportError: if module_path cannot be imported.
- """
- if hasattr(self, name):
- using_name = getattr(self, name)
- detailed_error = 'Module name already in use: %s' % using_name
- raise exceptions.ServiceClientRegistrationException(
- name=name, service_version=service_version,
- module_path=module_path, client_names=client_names,
- detailed_error=detailed_error)
- if service_version in self.registered_services:
- detailed_error = 'Service %s already registered.' % service_version
- raise exceptions.ServiceClientRegistrationException(
- name=name, service_version=service_version,
- module_path=module_path, client_names=client_names,
- detailed_error=detailed_error)
- params = dict(region=self.region,
- disable_ssl_certificate_validation=self.dscv,
- ca_certs=self.ca_certs,
- trace_requests=self.trace_requests)
- params.update(kwargs)
- # Instantiate the client factory
- _factory = ClientsFactory(module_path=module_path,
- client_names=client_names,
- auth_provider=self.auth_provider,
- **params)
- # Adds the client factory to the service_client
- setattr(self, name, _factory)
- # Add the name of the new service in self.SERVICES for discovery
- self._registered_services.add(service_version)
-
- @property
- def registered_services(self):
- # TODO(andreaf) Temporary set needed until all services are migrated
- _non_migrated_services = tempest_modules() - set(
- client_modules_by_service_name)
- return self._registered_services | _non_migrated_services
-
- def _setup_parameters(self, parameters):
- """Setup default values for client parameters
-
- Region by default is the region passed as an __init__ parameter.
- Checks that no parameter for an unknown service is provided.
- """
- _parameters = {}
- # Use region from __init__
- if self.region:
- _parameters['region'] = self.region
- # Update defaults with specified parameters
- _parameters.update(parameters)
- # If any parameter is left, parameters for an unknown service were
- # provided as input. Fail rather than ignore silently.
- return _parameters
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index cfb0c7f..eb50126 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -88,7 +88,7 @@
>>> 'client_names': ['API1Client', 'API2Client'],
>>> }
>>> params.update(myservice_config)
- >>> return [params]
+ >>> return [params]
>>> # Example implementation with two service clients
>>> foo1_config = config.service_client_config('foo')
@@ -107,7 +107,7 @@
>>> 'client_names': ['API1Client', 'API2Client'],
>>> }
>>> params_foo2.update(foo2_config)
- >>> return [params_foo1, params_foo2]
+ >>> return [params_foo1, params_foo2]
"""
return []
diff --git a/tempest/tests/test_service_clients.py b/tempest/tests/lib/services/test_clients.py
similarity index 87%
rename from tempest/tests/test_service_clients.py
rename to tempest/tests/lib/services/test_clients.py
index b0aa456..5db932c 100644
--- a/tempest/tests/test_service_clients.py
+++ b/tempest/tests/lib/services/test_clients.py
@@ -19,7 +19,7 @@
from tempest.lib import auth
from tempest.lib import exceptions
-from tempest import service_clients
+from tempest.lib.services import clients
from tempest.tests import base
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib import fake_credentials
@@ -54,14 +54,14 @@
def test___init___one_class(self):
fake_partial = 'fake_partial'
partial_mock = self.useFixture(fixtures.MockPatch(
- 'tempest.service_clients.ClientsFactory._get_partial_class',
+ 'tempest.lib.services.clients.ClientsFactory._get_partial_class',
return_value=fake_partial)).mock
class_names = ['FakeServiceClient1']
mock_importlib = self._setup_fake_module(class_names=class_names)
auth_provider = fake_auth_provider.FakeAuthProvider()
params = {'k1': 'v1', 'k2': 'v2'}
- factory = service_clients.ClientsFactory('fake_path', class_names,
- auth_provider, **params)
+ factory = clients.ClientsFactory('fake_path', class_names,
+ auth_provider, **params)
# Assert module has been imported
mock_importlib.assert_called_once_with('fake_path')
# All attributes have been created
@@ -77,14 +77,14 @@
def test___init___two_classes(self):
fake_partial = 'fake_partial'
partial_mock = self.useFixture(fixtures.MockPatch(
- 'tempest.service_clients.ClientsFactory._get_partial_class',
+ 'tempest.lib.services.clients.ClientsFactory._get_partial_class',
return_value=fake_partial)).mock
class_names = ['FakeServiceClient1', 'FakeServiceClient2']
mock_importlib = self._setup_fake_module(class_names=class_names)
auth_provider = fake_auth_provider.FakeAuthProvider()
params = {'k1': 'v1', 'k2': 'v2'}
- factory = service_clients.ClientsFactory('fake_path', class_names,
- auth_provider, **params)
+ factory = clients.ClientsFactory('fake_path', class_names,
+ auth_provider, **params)
# Assert module has been imported
mock_importlib.assert_called_once_with('fake_path')
# All attributes have been created
@@ -100,8 +100,8 @@
auth_provider = fake_auth_provider.FakeAuthProvider()
class_names = ['FakeServiceClient1', 'FakeServiceClient2']
with testtools.ExpectedException(ImportError, '.*fake_module.*'):
- service_clients.ClientsFactory('fake_module', class_names,
- auth_provider)
+ clients.ClientsFactory('fake_module', class_names,
+ auth_provider)
def test___init___not_a_class(self):
class_names = ['FakeServiceClient1', 'FakeServiceClient2']
@@ -111,8 +111,8 @@
auth_provider = fake_auth_provider.FakeAuthProvider()
expected_msg = '.*not_really_a_class.*str.*'
with testtools.ExpectedException(TypeError, expected_msg):
- service_clients.ClientsFactory('fake_module', extended_class_names,
- auth_provider)
+ clients.ClientsFactory('fake_module', extended_class_names,
+ auth_provider)
def test___init___class_not_found(self):
class_names = ['FakeServiceClient1', 'FakeServiceClient2']
@@ -121,15 +121,15 @@
auth_provider = fake_auth_provider.FakeAuthProvider()
expected_msg = '.*not_really_a_class.*fake_service_client.*'
with testtools.ExpectedException(AttributeError, expected_msg):
- service_clients.ClientsFactory('fake_module', extended_class_names,
- auth_provider)
+ clients.ClientsFactory('fake_module', extended_class_names,
+ auth_provider)
def test__get_partial_class_no_later_kwargs(self):
expected_fake_client = 'not_really_a_client'
self._setup_fake_module(class_names=[])
auth_provider = fake_auth_provider.FakeAuthProvider()
params = {'k1': 'v1', 'k2': 'v2'}
- factory = service_clients.ClientsFactory(
+ factory = clients.ClientsFactory(
'fake_path', [], auth_provider, **params)
klass_mock = mock.Mock(return_value=expected_fake_client)
partial = factory._get_partial_class(klass_mock, auth_provider, params)
@@ -147,7 +147,7 @@
auth_provider = fake_auth_provider.FakeAuthProvider()
params = {'k1': 'v1', 'k2': 'v2'}
later_params = {'k2': 'v4', 'k3': 'v3'}
- factory = service_clients.ClientsFactory(
+ factory = clients.ClientsFactory(
'fake_path', [], auth_provider, **params)
klass_mock = mock.Mock(return_value=expected_fake_client)
partial = factory._get_partial_class(klass_mock, auth_provider, params)
@@ -167,7 +167,7 @@
auth_provider = fake_auth_provider.FakeAuthProvider()
params = {'k1': 'v1', 'k2': 'v2'}
later_params = {'k2': 'v4', 'k3': 'v3'}
- factory = service_clients.ClientsFactory(
+ factory = clients.ClientsFactory(
'fake_path', [], auth_provider, **params)
klass_mock = mock.Mock(return_value=expected_fake_client)
partial = factory._get_partial_class(klass_mock, auth_provider, params)
@@ -188,15 +188,17 @@
def setUp(self):
super(TestServiceClients, self).setUp()
self.useFixture(fixtures.MockPatch(
- 'tempest.service_clients.tempest_modules',
- return_value=set(['fake_service1', 'fake_service2'])))
+ 'tempest.lib.services.clients.tempest_modules', return_value={}))
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.services.clients._tempest_internal_modules',
+ return_value=set(['fake_service1'])))
def test___init___creds_v2_uri(self):
# Verify that no API request is made, since no mock
# is required to run the test successfully
creds = fake_credentials.FakeKeystoneV2Credentials()
uri = 'fake_uri'
- _manager = service_clients.ServiceClients(creds, identity_uri=uri)
+ _manager = clients.ServiceClients(creds, identity_uri=uri)
self.assertIsInstance(_manager.auth_provider,
auth.KeystoneV2AuthProvider)
@@ -205,7 +207,7 @@
# is required to run the test successfully
creds = fake_credentials.FakeKeystoneV3Credentials()
uri = 'fake_uri'
- _manager = service_clients.ServiceClients(creds, identity_uri=uri)
+ _manager = clients.ServiceClients(creds, identity_uri=uri)
self.assertIsInstance(_manager.auth_provider,
auth.KeystoneV3AuthProvider)
@@ -213,14 +215,14 @@
creds = fake_credentials.FakeCredentials()
uri = 'fake_uri'
with testtools.ExpectedException(exceptions.InvalidCredentials):
- service_clients.ServiceClients(creds, identity_uri=uri)
+ clients.ServiceClients(creds, identity_uri=uri)
def test___init___invalid_creds_uri(self):
creds = fake_credentials.FakeKeystoneV2Credentials()
delattr(creds, 'username')
uri = 'fake_uri'
with testtools.ExpectedException(exceptions.InvalidCredentials):
- service_clients.ServiceClients(creds, identity_uri=uri)
+ clients.ServiceClients(creds, identity_uri=uri)
def test___init___creds_uri_none(self):
creds = fake_credentials.FakeKeystoneV2Credentials()
@@ -228,7 +230,7 @@
"non-empty")
with testtools.ExpectedException(exceptions.InvalidCredentials,
value_re=msg):
- service_clients.ServiceClients(creds, None)
+ clients.ServiceClients(creds, None)
def test___init___creds_uri_params(self):
creds = fake_credentials.FakeKeystoneV2Credentials()
@@ -236,8 +238,8 @@
'fake_param2': 'fake_value2'}
params = {'fake_service1': expeted_params}
uri = 'fake_uri'
- _manager = service_clients.ServiceClients(creds, identity_uri=uri,
- client_parameters=params)
+ _manager = clients.ServiceClients(creds, identity_uri=uri,
+ client_parameters=params)
self.assertIn('fake_service1', _manager.parameters)
for _key in expeted_params:
self.assertIn(_key, _manager.parameters['fake_service1'].keys())
@@ -253,14 +255,14 @@
msg = "(?=.*{0})(?=.*{1})".format(*list(params.keys()))
with testtools.ExpectedException(
exceptions.UnknownServiceClient, value_re=msg):
- service_clients.ServiceClients(creds, identity_uri=uri,
- client_parameters=params)
+ clients.ServiceClients(creds, identity_uri=uri,
+ client_parameters=params)
def _get_manager(self, init_region='fake_region'):
# Get a manager to invoke _setup_parameters on
creds = fake_credentials.FakeKeystoneV2Credentials()
- return service_clients.ServiceClients(creds, identity_uri='fake_uri',
- region=init_region)
+ return clients.ServiceClients(creds, identity_uri='fake_uri',
+ region=init_region)
def test__setup_parameters_none_no_region(self):
kwargs = {}
@@ -292,7 +294,7 @@
_manager = self._get_manager(init_region='fake_region_default')
# Mock after the _manager is setup to preserve the call count
factory_mock = self.useFixture(fixtures.MockPatch(
- 'tempest.service_clients.ClientsFactory')).mock
+ 'tempest.lib.services.clients.ClientsFactory')).mock
_manager.register_service_client_module(
name='fake_module',
service_version='fake_service',
@@ -321,7 +323,7 @@
_manager = self._get_manager(init_region='fake_region_default')
# Mock after the _manager is setup to preserve the call count
factory_mock = self.useFixture(fixtures.MockPatch(
- 'tempest.service_clients.ClientsFactory')).mock
+ 'tempest.lib.services.clients.ClientsFactory')).mock
_manager.register_service_client_module(
name='fake_module',
service_version='fake_service',
@@ -344,7 +346,7 @@
def test_register_service_client_module_duplicate_name(self):
self.useFixture(fixtures.MockPatch(
- 'tempest.service_clients.ClientsFactory'))
+ 'tempest.lib.services.clients.ClientsFactory')).mock
_manager = self._get_manager()
name_owner = 'this_is_a_string'
setattr(_manager, 'fake_module', name_owner)
@@ -357,7 +359,7 @@
def test_register_service_client_module_duplicate_service(self):
self.useFixture(fixtures.MockPatch(
- 'tempest.service_clients.ClientsFactory'))
+ 'tempest.lib.services.clients.ClientsFactory')).mock
_manager = self._get_manager()
duplicate_service = 'fake_service1'
expected_error = '.*' + duplicate_service