Prepare the Manager class for tempest.lib

Remove CONF dependencies from the client manager base class,
to make it useful to external consumers (such as plugins).

The ultimate target is to have a manager which can be used with
as little as possible setup, which only instantiates the clients
which are actually needed by the test, and which allows to register
new service clients defined in plugins.

Since plugins already import both manager.Manager and
clients.Manager, we maintain for now both classes with their names.
The plan is to migrate the 6 core service client groups to
manager.Manager, so that those clients are available to all tests
along with plugin clients. That requires a few steps. I'm doing
changes in clients.Manager for now so that it's easier to review.

The result of this first step is:
- manager.Manager is moved to manager_lib.Manager and does not
  depend on CONF anymore, nor on any tempest unstable class.
  It does not provide any client yet.
  Add unit test coverage for this class.
- manager.Manager is still provided with backward compatible
  interface for plugins benefit.

Change-Id: Ic9ccc7037d15cdd4c6f1749eaeda13d4e7ee0114
Partially-implements: bp client-manager-refactor
This commit is contained in:
Andrea Frittoli (andreaf) 2016-06-13 12:39:29 +01:00
parent f11e225b0c
commit 2395014351
5 changed files with 222 additions and 52 deletions

View File

@ -9,13 +9,11 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tempest.api.identity import base
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
from tempest.lib import auth
from tempest import manager
from tempest import test
CONF = config.CONF
@ -78,7 +76,7 @@ class TestDefaultProjectId (base.BaseIdentityV3AdminTest):
creds = auth.KeystoneV3Credentials(username=user_name,
password=user_name,
user_domain_name=dom_name)
auth_provider = manager.get_auth_provider(creds)
auth_provider = clients.get_auth_provider(creds)
creds = auth_provider.fill_credentials()
admin_client = clients.Manager(credentials=creds)

View File

@ -20,10 +20,11 @@ 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.services import compute
from tempest.lib.services import image
from tempest.lib.services import network
from tempest import manager
from tempest import service_clients
from tempest.services import baremetal
from tempest.services import data_processing
from tempest.services import identity
@ -35,7 +36,7 @@ CONF = config.CONF
LOG = logging.getLogger(__name__)
class Manager(manager.Manager):
class Manager(service_clients.ServiceClients):
"""Top level manager for OpenStack tempest clients"""
default_params = {
@ -61,7 +62,10 @@ class Manager(manager.Manager):
:param service: Service name
:param scope: default scope for tokens produced by the auth provider
"""
super(Manager, self).__init__(credentials=credentials, scope=scope)
_, identity_uri = get_auth_provider_class(credentials)
super(Manager, self).__init__(
credentials=credentials, identity_uri=identity_uri, scope=scope,
region=CONF.identity.region, **self.default_params)
self._set_compute_clients()
self._set_identity_clients()
self._set_volume_clients()
@ -390,3 +394,30 @@ class Manager(manager.Manager):
self.auth_provider, **params)
self.object_client = object_storage.ObjectClient(self.auth_provider,
**params)
def get_auth_provider_class(credentials):
if isinstance(credentials, auth.KeystoneV3Credentials):
return auth.KeystoneV3AuthProvider, CONF.identity.uri_v3
else:
return auth.KeystoneV2AuthProvider, CONF.identity.uri
def get_auth_provider(credentials, pre_auth=False, scope='project'):
default_params = {
'disable_ssl_certificate_validation':
CONF.identity.disable_ssl_certificate_validation,
'ca_certs': CONF.identity.ca_certificates_file,
'trace_requests': CONF.debug.trace_requests
}
if credentials is None:
raise exceptions.InvalidCredentials(
'Credentials must be specified')
auth_provider_class, auth_url = get_auth_provider_class(
credentials)
_auth_provider = auth_provider_class(credentials, auth_url,
scope=scope,
**default_params)
if pre_auth:
_auth_provider.set_auth()
return _auth_provider

View File

@ -13,61 +13,50 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from tempest import clients
from tempest import config
from tempest.lib import auth
from tempest.lib import exceptions
from tempest import service_clients
CONF = config.CONF
LOG = logging.getLogger(__name__)
class Manager(object):
"""Base manager class
class Manager(service_clients.ServiceClients):
"""Service client manager class for backward compatibility
Manager objects are responsible for providing a configuration object
and a client object for a test case to use in performing actions.
The former manager.Manager is not a stable interface in Tempest,
nonetheless it is consumed by a number of plugins already. This class
exists to provide some grace time for the move to tempest.lib.
"""
def __init__(self, credentials, scope='project'):
"""Initialization of base manager class
Credentials to be used within the various client classes managed by the
Manager object must be defined.
:param credentials: An instance of `auth.Credentials`
:param scope: default scope for tokens produced by the auth provider
"""
self.credentials = credentials
# Check if passed or default credentials are valid
if not self.credentials.is_valid():
raise exceptions.InvalidCredentials()
self.auth_version = CONF.identity.auth_version
# Creates an auth provider for the credentials
self.auth_provider = get_auth_provider(
self.credentials, pre_auth=True, scope=scope)
def get_auth_provider_class(credentials):
if isinstance(credentials, auth.KeystoneV3Credentials):
return auth.KeystoneV3AuthProvider, CONF.identity.uri_v3
else:
return auth.KeystoneV2AuthProvider, CONF.identity.uri
msg = ("tempest.manager.Manager is not a stable interface and as such "
"it should not imported directly. It will be removed as "
"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)
super(Manager, self).__init__(
credentials=credentials, scope=scope,
identity_uri=uri,
disable_ssl_certificate_validation=dscv,
ca_certs=CONF.identity.ca_certificates_file,
trace_requests=CONF.debug.trace_requests)
def get_auth_provider(credentials, pre_auth=False, scope='project'):
default_params = {
'disable_ssl_certificate_validation':
CONF.identity.disable_ssl_certificate_validation,
'ca_certs': CONF.identity.ca_certificates_file,
'trace_requests': CONF.debug.trace_requests
}
if credentials is None:
raise exceptions.InvalidCredentials(
'Credentials must be specified')
auth_provider_class, auth_url = get_auth_provider_class(
credentials)
_auth_provider = auth_provider_class(credentials, auth_url,
scope=scope,
**default_params)
if pre_auth:
_auth_provider.set_auth()
return _auth_provider
"""Shim to get_auth_provider in clients.py
get_auth_provider used to be hosted in this module, but it has been
moved to clients.py now as a more permanent location.
This module will be removed eventually, and this shim is only
maintained for the benefit of plugins already consuming this interface.
"""
msg = ("tempest.manager.get_auth_provider is not a stable interface and "
"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)

View File

@ -0,0 +1,90 @@
# 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.
from tempest.lib import auth
from tempest.lib import exceptions
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)
>>> 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=''):
"""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.
Parameters dscv, ca_certs and trace_requests all apply to the auth
provider as well as any service clients provided by this manager.
: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.
"""
self.credentials = credentials
self.identity_uri = identity_uri
if not identity_uri:
raise exceptions.InvalidCredentials(
'Manager 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)

View File

@ -0,0 +1,62 @@
# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
#
# 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 testtools
from tempest.lib import auth
from tempest.lib import exceptions
from tempest import service_clients
from tempest.tests import base
from tempest.tests.lib import fake_credentials
class TestServiceClients(base.TestCase):
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)
self.assertIsInstance(_manager.auth_provider,
auth.KeystoneV2AuthProvider)
def test__init__creds_v3_uri(self):
# Verify that no API request is made, since no mock
# is required to run the test successfully
creds = fake_credentials.FakeKeystoneV3Credentials()
uri = 'fake_uri'
_manager = service_clients.ServiceClients(creds, identity_uri=uri)
self.assertIsInstance(_manager.auth_provider,
auth.KeystoneV3AuthProvider)
def test__init__base_creds_uri(self):
creds = fake_credentials.FakeCredentials()
uri = 'fake_uri'
with testtools.ExpectedException(exceptions.InvalidCredentials):
service_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)
def test__init__creds_uri_none(self):
creds = fake_credentials.FakeKeystoneV2Credentials()
msg = "Invalid Credentials\nDetails: Manager requires a non-empty"
with testtools.ExpectedException(exceptions.InvalidCredentials,
value_re=msg):
service_clients.ServiceClients(creds, None)