Merge "Prepare the Manager class for tempest.lib"
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index 18a50d0..a540da7 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -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 @@
         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)
 
diff --git a/tempest/clients.py b/tempest/clients.py
index 3a1a3c0..ef03e80 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -20,10 +20,11 @@
 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 @@
 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 @@
         :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 @@
             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
diff --git a/tempest/manager.py b/tempest/manager.py
index f2659a8..3d495b6 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -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)
diff --git a/tempest/service_clients.py b/tempest/service_clients.py
new file mode 100644
index 0000000..3208c8d
--- /dev/null
+++ b/tempest/service_clients.py
@@ -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)
diff --git a/tempest/tests/test_service_clients.py b/tempest/tests/test_service_clients.py
new file mode 100644
index 0000000..f67781c
--- /dev/null
+++ b/tempest/tests/test_service_clients.py
@@ -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)