Merge "Add unit test for volume extensions client"
diff --git a/doc/source/library.rst b/doc/source/library.rst
index 29248d1..a461a0f 100644
--- a/doc/source/library.rst
+++ b/doc/source/library.rst
@@ -68,3 +68,4 @@
    library/api_microversion_testing
    library/auth
    library/clients
+   library/credential_providers
diff --git a/doc/source/library/credential_providers.rst b/doc/source/library/credential_providers.rst
new file mode 100644
index 0000000..7e831cc
--- /dev/null
+++ b/doc/source/library/credential_providers.rst
@@ -0,0 +1,148 @@
+.. _cred_providers:
+
+Credential Providers
+====================
+
+These library interfaces are used to deal with allocating credentials on demand
+either dynamically by calling keystone to allocate new credentials, or from
+a list of preprovisioned credentials. These 2 modules are implementations of
+the same abstract credential providers class and can be used interchangably.
+However, each implementation has some additional parameters that are used to
+influence the behavior of the modules. The API reference at the bottom of this
+doc shows the interface definitions for both modules, however that may be a bit
+opaque. You can see some examples of how to leverage this interface below.
+
+Initialization Example
+----------------------
+This example is from Tempest itself (from tempest/common/credentials_factory.py
+just modified slightly) and is how it initializes the credential provider based
+on config::
+
+  from tempest import config
+  from tempest.lib.common import dynamic_creds
+  from tempest.lib.common import preprov_creds
+
+  CONF = config.CONF
+
+  def get_credentials_provider(name, network_resources=None,
+                               force_tenant_isolation=False,
+                               identity_version=None):
+      # If a test requires a new account to work, it can have it via forcing
+      # dynamic credentials. A new account will be produced only for that test.
+      # In case admin credentials are not available for the account creation,
+      # the test should be skipped else it would fail.
+      identity_version = identity_version or CONF.identity.auth_version
+      if CONF.auth.use_dynamic_credentials or force_tenant_isolation:
+          admin_creds = get_configured_admin_credentials(
+              fill_in=True, identity_version=identity_version)
+          return dynamic_creds.DynamicCredentialProvider(
+              name=name,
+              network_resources=network_resources,
+              identity_version=identity_version,
+              admin_creds=admin_creds,
+              identity_admin_domain_scope=CONF.identity.admin_domain_scope,
+              identity_admin_role=CONF.identity.admin_role,
+              extra_roles=CONF.auth.tempest_roles,
+              neutron_available=CONF.service_available.neutron,
+              project_network_cidr=CONF.network.project_network_cidr,
+              project_network_mask_bits=CONF.network.project_network_mask_bits,
+              public_network_id=CONF.network.public_network_id,
+              create_networks=(CONF.auth.create_isolated_networks and not
+                               CONF.network.shared_physical_network),
+              resource_prefix=CONF.resources_prefix,
+              credentials_domain=CONF.auth.default_credentials_domain_name,
+              admin_role=CONF.identity.admin_role,
+              identity_uri=CONF.identity.uri_v3,
+              identity_admin_endpoint_type=CONF.identity.v3_endpoint_type)
+      else:
+          if CONF.auth.test_accounts_file:
+              # Most params are not relevant for pre-created accounts
+              return preprov_creds.PreProvisionedCredentialProvider(
+                  name=name, identity_version=identity_version,
+                  accounts_lock_dir=lockutils.get_lock_path(CONF),
+                  test_accounts_file=CONF.auth.test_accounts_file,
+                  object_storage_operator_role=CONF.object_storage.operator_role,
+                  object_storage_reseller_admin_role=reseller_admin_role,
+                  credentials_domain=CONF.auth.default_credentials_domain_name,
+                  admin_role=CONF.identity.admin_role,
+                  identity_uri=CONF.identity.uri_v3,
+                  identity_admin_endpoint_type=CONF.identity.v3_endpoint_type)
+          else:
+              raise exceptions.InvalidConfiguration(
+                  'A valid credential provider is needed')
+
+This function just returns an initialized credential provider class based on the
+config file. The consumer of this function treats the output as the same
+regardless of whether it's a dynamic or preprovisioned provider object.
+
+Dealing with Credentials
+------------------------
+
+Once you have a credential provider object created the access patterns for
+allocating and removing credentials are the same across both the dynamic
+and preprovisioned credentials. These are defined in the abstract
+CredentialProvider class. At a high level the credentials provider enables
+you to get 3 basic types of credentials at once (per object): a primary, alt,
+and admin. You're also able to allocate a credential by role. These credentials
+are tracked by the provider object and delete must manually be called otherwise
+the created resources will not be deleted (or returned to the pool in the case
+of preprovisioned creds)
+
+Examples
+''''''''
+
+Continuing from the example above, to allocate credentials by the 3 basic types
+you can do the following::
+
+  provider = get_credentials_provider('my_tests')
+  primary_creds = provider.get_primary_creds()
+  alt_creds = provider.get_alt_creds()
+  admin_creds = provider.get_admin_creds()
+  # Make sure to delete the credentials when you're finished
+  provider.clear_creds()
+
+To create and interact with credentials by role you can do the following::
+
+  provider = get_credentials_provider('my_tests')
+  my_role_creds = provider.get_creds_by_role({'roles': ['my_role']})
+  # provider.clear_creds() will clear all creds including those allocated by
+  # role
+  provider.clear_creds()
+
+When multiple roles are specified a set of creds with all the roles assigned
+will be allocated::
+
+  provider = get_credentials_provider('my_tests')
+  my_role_creds = provider.get_creds_by_role({'roles': ['my_role',
+                                                        'my_other_role']})
+  # provider.clear_creds() will clear all creds including those allocated by
+  # role
+  provider.clear_creds()
+
+If you need multiple sets of credentials with the same roles you can also do
+this by leveraging the ``force_new`` kwarg::
+
+  provider = get_credentials_provider('my_tests')
+  my_role_creds = provider.get_creds_by_role({'roles': ['my_role']})
+  my_role_other_creds = provider.get_creds_by_role({'roles': ['my_role']},
+                                                   force_new=True)
+  # provider.clear_creds() will clear all creds including those allocated by
+  # role
+  provider.clear_creds()
+
+API Reference
+=============
+
+------------------------------
+The dynamic credentials module
+------------------------------
+
+.. automodule:: tempest.lib.common.dynamic_creds
+   :members:
+
+--------------------------------------
+The pre-provisioned credentials module
+--------------------------------------
+
+.. automodule:: tempest.lib.common.preprov_creds
+   :members:
diff --git a/releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml b/releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml
new file mode 100644
index 0000000..c20cbc6
--- /dev/null
+++ b/releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    The tempest module tempest.common.dynamic creds which is used for
+    dynamically allocating credentials has been migrated into tempest lib.
diff --git a/releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml b/releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml
new file mode 100644
index 0000000..aa5f71a
--- /dev/null
+++ b/releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - The tempest module tempest.common.preprov_creds which is used to provide
+    credentials from a list of preprovisioned resources has been migrated into
+    tempest lib at tempest.lib.common.preprov_creds.
+  - The InvalidTestResource exception class from tempest.exceptions has been
+    migrated into tempest.lib.exceptions
+  - The tempest module tempest.common.fixed_network which provided utilities for
+    finding fixed networks by and helpers for picking the network to use when
+    multiple tenant networks are available has been migrated into tempest lib
+    at tempest.lib.common.fixed_network.
diff --git a/requirements.txt b/requirements.txt
index b7ebf8a..a74f5c2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
-cliff>=2.6.0 # Apache-2.0
+cliff>=2.8.0 # Apache-2.0
 jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
 testtools>=1.4.0 # MIT
 paramiko>=2.0 # LGPLv2.1+
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 0521cca..d9a7800 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -14,8 +14,8 @@
 
 from tempest.api.compute import base
 from tempest.common import compute
-from tempest.common import fixed_network
 from tempest.common import waiters
+from tempest.lib.common import fixed_network
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 921b7da..a4ed8e1 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -15,9 +15,9 @@
 import testtools
 
 from tempest.api.compute import base
-from tempest.common import fixed_network
 from tempest.common import waiters
 from tempest import config
+from tempest.lib.common import fixed_network
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 13614cb..11273e4 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -98,7 +98,7 @@
         cls.policies = None
 
         if CONF.object_storage_feature_enabled.discoverability:
-            _, body = cls.capabilities_client.list_capabilities()
+            body = cls.capabilities_client.list_capabilities()
 
             if 'swift' in body and 'policies' in body['swift']:
                 cls.policies = body['swift']['policies']
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index e765414..7c538e8 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -58,9 +58,9 @@
         # upload an archived file
         with open(filepath) as fh:
             mydata = fh.read()
-            resp, body = self.bulk_client.upload_archive(
+            resp = self.bulk_client.upload_archive(
                 upload_path='', data=mydata, archive_file_format='tar')
-        return resp, body
+        return resp
 
     def _check_contents_deleted(self, container_name):
         param = {'format': 'txt'}
@@ -73,21 +73,20 @@
     def test_extract_archive(self):
         # Test bulk operation of file upload with an archived file
         filepath, container_name, object_name = self._create_archive()
-        resp, _ = self._upload_archive(filepath)
-
+        resp = self._upload_archive(filepath)
         self.containers.append(container_name)
 
         # When uploading an archived file with the bulk operation, the response
         # does not contain 'content-length' header. This is the special case,
         # therefore the existence of response headers is checked without
         # custom matcher.
-        self.assertIn('transfer-encoding', resp)
-        self.assertIn('content-type', resp)
-        self.assertIn('x-trans-id', resp)
-        self.assertIn('date', resp)
+        self.assertIn('transfer-encoding', resp.response)
+        self.assertIn('content-type', resp.response)
+        self.assertIn('x-trans-id', resp.response)
+        self.assertIn('date', resp.response)
 
         # Check only the format of common headers with custom matcher
-        self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+        self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
 
         param = {'format': 'json'}
         resp, body = self.account_client.list_account_containers(param)
@@ -112,19 +111,19 @@
         self._upload_archive(filepath)
 
         data = '%s/%s\n%s' % (container_name, object_name, container_name)
-        resp, _ = self.bulk_client.delete_bulk_data(data=data)
+        resp = self.bulk_client.delete_bulk_data(data=data)
 
         # When deleting multiple files using the bulk operation, the response
         # does not contain 'content-length' header. This is the special case,
         # therefore the existence of response headers is checked without
         # custom matcher.
-        self.assertIn('transfer-encoding', resp)
-        self.assertIn('content-type', resp)
-        self.assertIn('x-trans-id', resp)
-        self.assertIn('date', resp)
+        self.assertIn('transfer-encoding', resp.response)
+        self.assertIn('content-type', resp.response)
+        self.assertIn('x-trans-id', resp.response)
+        self.assertIn('date', resp.response)
 
         # Check only the format of common headers with custom matcher
-        self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+        self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
 
         # Check if uploaded contents are completely deleted
         self._check_contents_deleted(container_name)
@@ -138,19 +137,19 @@
 
         data = '%s/%s\n%s' % (container_name, object_name, container_name)
 
-        resp, _ = self.bulk_client.delete_bulk_data_with_post(data=data)
+        resp = self.bulk_client.delete_bulk_data_with_post(data=data)
 
         # When deleting multiple files using the bulk operation, the response
         # does not contain 'content-length' header. This is the special case,
         # therefore the existence of response headers is checked without
         # custom matcher.
-        self.assertIn('transfer-encoding', resp)
-        self.assertIn('content-type', resp)
-        self.assertIn('x-trans-id', resp)
-        self.assertIn('date', resp)
+        self.assertIn('transfer-encoding', resp.response)
+        self.assertIn('content-type', resp.response)
+        self.assertIn('x-trans-id', resp.response)
+        self.assertIn('date', resp.response)
 
         # Check only the format of common headers with custom matcher
-        self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+        self.assertThat(resp.response, custom_matchers.AreAllWellFormatted())
 
         # Check if uploaded contents are completely deleted
         self._check_contents_deleted(container_name)
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 9e62046..2fb676f 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -135,7 +135,7 @@
         not CONF.object_storage_feature_enabled.discoverability,
         'Discoverability function is disabled')
     def test_list_extensions(self):
-        resp, _ = self.capabilities_client.list_capabilities()
+        resp = self.capabilities_client.list_capabilities()
 
         self.assertThat(resp, custom_matchers.AreAllWellFormatted())
 
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
index a8d70c5..387b7b6 100644
--- a/tempest/api/object_storage/test_container_services_negative.py
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -32,7 +32,7 @@
 
         if CONF.object_storage_feature_enabled.discoverability:
             # use /info to get default constraints
-            _, body = cls.capabilities_client.list_capabilities()
+            body = cls.capabilities_client.list_capabilities()
             cls.constraints = body['swift']
 
     @decorators.attr(type=["negative"])
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index a76123c..8636405 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -102,8 +102,8 @@
 import yaml
 
 from tempest.common import credentials_factory
-from tempest.common import dynamic_creds
 from tempest import config
+from tempest.lib.common import dynamic_creds
 
 
 LOG = None
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 2f4d120..8e71ecc 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -200,7 +200,7 @@
     if service != 'swift':
         resp = extensions_client.list_extensions()
     else:
-        __, resp = extensions_client.list_capabilities()
+        resp = extensions_client.list_capabilities()
     # For Nova, Cinder and Neutron we use the alias name rather than the
     # 'name' field because the alias is considered to be the canonical
     # name.
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 9f467fe..e3fbfb8 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -25,9 +25,9 @@
 from oslo_log import log as logging
 from oslo_utils import excutils
 
-from tempest.common import fixed_network
 from tempest.common import waiters
 from tempest import config
+from tempest.lib.common import fixed_network
 from tempest.lib.common import rest_client
 from tempest.lib.common.utils import data_utils
 
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 449c343..fd875be 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -14,10 +14,10 @@
 from oslo_concurrency import lockutils
 
 from tempest import clients
-from tempest.common import dynamic_creds
-from tempest.common import preprov_creds
 from tempest import config
 from tempest.lib import auth
+from tempest.lib.common import dynamic_creds
+from tempest.lib.common import preprov_creds
 from tempest.lib import exceptions
 
 CONF = config.CONF
@@ -147,12 +147,16 @@
                 'A valid credential provider is needed')
 
 
-# We want a helper function here to check and see if admin credentials
-# are available so we can do a single call from skip_checks if admin
-# creds area available.
-# This depends on identity_version as there may be admin credentials
-# available for v2 but not for v3.
 def is_admin_available(identity_version):
+    """Helper to check for admin credentials
+
+    Helper function to check if a set of admin credentials is available so we
+    can do a single call from skip_checks.
+    This helper depends on identity_version as there may be admin credentials
+    available for v2 but not for v3.
+
+    :param identity_version: 'v2' or 'v3'
+    """
     is_admin = True
     # If dynamic credentials is enabled admin will be available
     if CONF.auth.use_dynamic_credentials:
@@ -173,12 +177,16 @@
     return is_admin
 
 
-# We want a helper function here to check and see if alt credentials
-# are available so we can do a single call from skip_checks if alt
-# creds area available.
-# This depends on identity_version as there may be alt credentials
-# available for v2 but not for v3.
 def is_alt_available(identity_version):
+    """Helper to check for alt credentials
+
+    Helper function to check if a second set of credentials is available (aka
+    alt credentials) so we can do a single call from skip_checks.
+    This helper depends on identity_version as there may be alt credentials
+    available for v2 but not for v3.
+
+    :param identity_version: 'v2' or 'v3'
+    """
     # If dynamic credentials is enabled alt will be available
     if CONF.auth.use_dynamic_credentials:
         return True
@@ -216,9 +224,19 @@
 }
 
 
-# Read credentials from configuration, builds a Credentials object
-# based on the specified or configured version
 def get_configured_admin_credentials(fill_in=True, identity_version=None):
+    """Get admin credentials from the config file
+
+    Read credentials from configuration, builds a Credentials object based on
+    the specified or configured version
+
+    :param fill_in: If True, a request to the Token API is submitted, and the
+                    credential object is filled in with all names and IDs from
+                    the token API response.
+    :param identity_version: The identity version to talk to and the type of
+                             credentials object to be created. 'v2' or 'v3'.
+    :returns: An object of a sub-type of `auth.Credentials`
+    """
     identity_version = identity_version or CONF.identity.auth_version
 
     if identity_version not in ('v2', 'v3'):
@@ -250,6 +268,19 @@
 # Wrapper around auth.get_credentials to use the configured identity version
 # if none is specified
 def get_credentials(fill_in=True, identity_version=None, **kwargs):
+    """Get credentials from dict based on config
+
+    Wrapper around auth.get_credentials to use the configured identity version
+    if none is specified.
+
+    :param fill_in: If True, a request to the Token API is submitted, and the
+                    credential object is filled in with all names and IDs from
+                    the token API response.
+    :param identity_version: The identity version to talk to and the type of
+                             credentials object to be created. 'v2' or 'v3'.
+    :param kwargs: Attributes to be used to build the Credentials object.
+    :returns: An object of a sub-type of `auth.Credentials`
+    """
     params = dict(DEFAULT_PARAMS, **kwargs)
     identity_version = identity_version or CONF.identity.auth_version
     # In case of "v3" add the domain from config if not specified
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 84e31d0..e69de29 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -1,38 +0,0 @@
-# Copyright (c) 2015 Hewlett-Packard 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.
-
-from functools import partial
-
-from tempest import config
-from tempest.lib.common.utils import data_utils as lib_data_utils
-
-CONF = config.CONF
-
-
-class DataUtils(object):
-    def __getattr__(self, attr):
-
-        if attr == 'rand_name':
-            # NOTE(flwang): This is a proxy to generate a random name that
-            # includes a random number and a prefix if one is configured in
-            # CONF.resources_prefix
-            attr_obj = partial(lib_data_utils.rand_name,
-                               prefix=CONF.resources_prefix)
-        else:
-            attr_obj = getattr(lib_data_utils, attr)
-
-        self.__dict__[attr] = attr_obj
-        return attr_obj
-
-data_utils = DataUtils()
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index a437761..b5b2d71 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -52,12 +52,5 @@
                "the configured network")
 
 
-# NOTE(andreaf) This exception is added here to facilitate the migration
-# of get_network_from_name and preprov_creds to tempest.lib, and it should
-# be migrated along with them
-class InvalidTestResource(exceptions.TempestException):
-    message = "%(name)s is not a valid %(type)s, or the name is ambiguous"
-
-
 class RFCViolation(exceptions.RestClientException):
     message = "RFC Violation"
diff --git a/tempest/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
similarity index 100%
rename from tempest/common/dynamic_creds.py
rename to tempest/lib/common/dynamic_creds.py
diff --git a/tempest/common/fixed_network.py b/tempest/lib/common/fixed_network.py
similarity index 99%
rename from tempest/common/fixed_network.py
rename to tempest/lib/common/fixed_network.py
index 4032c90..e2054a4 100644
--- a/tempest/common/fixed_network.py
+++ b/tempest/lib/common/fixed_network.py
@@ -14,8 +14,8 @@
 
 from oslo_log import log as logging
 
-from tempest import exceptions
 from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions
 
 LOG = logging.getLogger(__name__)
 
diff --git a/tempest/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
similarity index 98%
rename from tempest/common/preprov_creds.py
rename to tempest/lib/common/preprov_creds.py
index 64cabb7..cd3a10e 100644
--- a/tempest/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -20,10 +20,9 @@
 import six
 import yaml
 
-from tempest.common import fixed_network
-from tempest import exceptions
 from tempest.lib import auth
 from tempest.lib.common import cred_provider
+from tempest.lib.common import fixed_network
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services import clients
 
@@ -350,7 +349,7 @@
         try:
             network = fixed_network.get_network_from_name(
                 net_name, compute_network_client)
-        except exceptions.InvalidTestResource:
+        except lib_exc.InvalidTestResource:
             network = {}
         net_creds.set_resources(network=network)
         return net_creds
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 68ce57a..cdb8be9 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -269,3 +269,7 @@
 class DeleteErrorException(TempestException):
     message = ("Resource %(resource_id)s failed to delete "
                "and is in ERROR status")
+
+
+class InvalidTestResource(TempestException):
+    message = "%(name)s is not a valid %(type)s, or the name is ambiguous"
diff --git a/tempest/services/object_storage/bulk_middleware_client.py b/tempest/services/object_storage/bulk_middleware_client.py
index 83d2d80..c11a105 100644
--- a/tempest/services/object_storage/bulk_middleware_client.py
+++ b/tempest/services/object_storage/bulk_middleware_client.py
@@ -31,7 +31,7 @@
             headers = {}
         resp, body = self.put(url, data, headers)
         self.expected_success(200, resp.status)
-        return resp, body
+        return rest_client.ResponseBodyData(resp, body)
 
     def delete_bulk_data(self, data=None, headers=None):
         """Delete multiple objects or containers from their account.
@@ -43,9 +43,9 @@
 
         if headers is None:
             headers = {}
-        resp, body = self.delete(url, headers=headers, body=data)
+        resp, body = self.delete(url, headers, data)
         self.expected_success(200, resp.status)
-        return resp, body
+        return rest_client.ResponseBodyData(resp, body)
 
     def delete_bulk_data_with_post(self, data=None, headers=None):
         """Delete multiple objects or containers with POST request.
@@ -57,6 +57,6 @@
 
         if headers is None:
             headers = {}
-        resp, body = self.post(url, headers=headers, body=data)
+        resp, body = self.post(url, data, headers)
         self.expected_success([200, 204], resp.status)
-        return resp, body
+        return rest_client.ResponseBodyData(resp, body)
diff --git a/tempest/services/object_storage/capabilities_client.py b/tempest/services/object_storage/capabilities_client.py
index 0fe437f..d31bbc2 100644
--- a/tempest/services/object_storage/capabilities_client.py
+++ b/tempest/services/object_storage/capabilities_client.py
@@ -28,4 +28,4 @@
             self.reset_path()
         body = json.loads(body)
         self.expected_success(200, resp.status)
-        return resp, body
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/test.py b/tempest/test.py
index fc846ff..317c0a7 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -26,10 +26,10 @@
 
 from tempest import clients
 from tempest.common import credentials_factory as credentials
-from tempest.common import fixed_network
 import tempest.common.validation_resources as vresources
 from tempest import config
 from tempest.lib.common import cred_client
+from tempest.lib.common import fixed_network
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index 248cfb0..f907bd0 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -140,7 +140,8 @@
 
     identity_version = 2
     cred_client = 'tempest.lib.common.cred_client.V2CredsClient'
-    dynamic_creds = 'tempest.common.dynamic_creds.DynamicCredentialProvider'
+    dynamic_creds = ('tempest.lib.common.dynamic_creds.'
+                     'DynamicCredentialProvider')
 
     def setUp(self):
         super(TestGenerateResourcesV2, self).setUp()
@@ -245,7 +246,8 @@
 
     identity_version = 2
     cred_client = 'tempest.lib.common.cred_client.V2CredsClient'
-    dynamic_creds = 'tempest.common.dynamic_creds.DynamicCredentialProvider'
+    dynamic_creds = ('tempest.lib.common.dynamic_creds.'
+                     'DynamicCredentialProvider')
     domain_is_in = False
 
     def setUp(self):
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index b0e74fb..1415111 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -392,10 +392,10 @@
 
     def test_verify_extensions_swift(self):
         def fake_list_extensions():
-            return (None, {'fake1': 'metadata',
-                           'fake2': 'metadata',
-                           'not_fake': 'metadata',
-                           'swift': 'metadata'})
+            return {'fake1': 'metadata',
+                    'fake2': 'metadata',
+                    'not_fake': 'metadata',
+                    'swift': 'metadata'}
         fake_os = mock.MagicMock()
         fake_os.capabilities_client.list_capabilities = fake_list_extensions
         self.useFixture(fixtures.MockPatchObject(
@@ -414,10 +414,10 @@
 
     def test_verify_extensions_swift_all(self):
         def fake_list_extensions():
-            return (None, {'fake1': 'metadata',
-                           'fake2': 'metadata',
-                           'not_fake': 'metadata',
-                           'swift': 'metadata'})
+            return {'fake1': 'metadata',
+                    'fake2': 'metadata',
+                    'not_fake': 'metadata',
+                    'swift': 'metadata'}
         fake_os = mock.MagicMock()
         fake_os.capabilities_client.list_capabilities = fake_list_extensions
         self.useFixture(fixtures.MockPatchObject(
diff --git a/tempest/tests/common/test_admin_available.py b/tempest/tests/common/test_admin_available.py
index c3d248c..7b3b1b0 100644
--- a/tempest/tests/common/test_admin_available.py
+++ b/tempest/tests/common/test_admin_available.py
@@ -53,7 +53,7 @@
                                  'password': 'p',
                                  'types': ['admin']})
             self.useFixture(fixtures.MockPatch(
-                'tempest.common.preprov_creds.read_accounts_yaml',
+                'tempest.lib.common.preprov_creds.read_accounts_yaml',
                 return_value=accounts))
             cfg.CONF.set_default('test_accounts_file',
                                  use_accounts_file, group='auth')
diff --git a/tempest/tests/common/test_alt_available.py b/tempest/tests/common/test_alt_available.py
index b9a8967..a425bb8 100644
--- a/tempest/tests/common/test_alt_available.py
+++ b/tempest/tests/common/test_alt_available.py
@@ -40,7 +40,7 @@
                              project_name="t%s" % ii,
                              password="p") for ii in creds]
             self.useFixture(fixtures.MockPatch(
-                'tempest.common.preprov_creds.read_accounts_yaml',
+                'tempest.lib.common.preprov_creds.read_accounts_yaml',
                 return_value=accounts))
             cfg.CONF.set_default('test_accounts_file',
                                  use_accounts_file, group='auth')
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
similarity index 99%
rename from tempest/tests/common/test_dynamic_creds.py
rename to tempest/tests/lib/common/test_dynamic_creds.py
index cf131eb..6aa7a42 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -17,8 +17,8 @@
 from oslo_config import cfg
 
 from tempest.common import credentials_factory as credentials
-from tempest.common import dynamic_creds
 from tempest import config
+from tempest.lib.common import dynamic_creds
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services.identity.v2 import identity_client as v2_iden_client
@@ -659,7 +659,7 @@
         creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
         creds.creds_client = mock.MagicMock()
         creds.creds_client.create_user_role.side_effect = lib_exc.Conflict
-        with mock.patch('tempest.common.dynamic_creds.LOG') as log_mock:
+        with mock.patch('tempest.lib.common.dynamic_creds.LOG') as log_mock:
             creds._create_creds()
             log_mock.warning.assert_called_once_with(
                 "Member role already exists, ignoring conflict.")
diff --git a/tempest/tests/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
similarity index 97%
rename from tempest/tests/common/test_preprov_creds.py
rename to tempest/tests/lib/common/test_preprov_creds.py
index d894c5e..5402e47 100644
--- a/tempest/tests/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -24,10 +24,10 @@
 from oslo_concurrency.fixture import lockutils as lockutils_fixtures
 from oslo_config import cfg
 
-from tempest.common import preprov_creds
 from tempest import config
 from tempest.lib import auth
 from tempest.lib.common import cred_provider
+from tempest.lib.common import preprov_creds
 from tempest.lib import exceptions as lib_exc
 from tempest.tests import base
 from tempest.tests import fake_config
@@ -88,7 +88,7 @@
         self.useFixture(lockutils_fixtures.ExternalLockFixture())
         self.test_accounts = self._fake_accounts(cfg.CONF.identity.admin_role)
         self.accounts_mock = self.useFixture(fixtures.MockPatch(
-            'tempest.common.preprov_creds.read_accounts_yaml',
+            'tempest.lib.common.preprov_creds.read_accounts_yaml',
             return_value=self.test_accounts))
         self.useFixture(fixtures.MockPatch(
             'os.path.isfile', return_value=True))
@@ -271,7 +271,7 @@
     def test_is_not_multi_user(self):
         self.test_accounts = [self.test_accounts[0]]
         self.useFixture(fixtures.MockPatch(
-            'tempest.common.preprov_creds.read_accounts_yaml',
+            'tempest.lib.common.preprov_creds.read_accounts_yaml',
             return_value=self.test_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
@@ -335,7 +335,7 @@
              'password': 'p', 'roles': ['role-7', 'role-11'],
              'resources': {'network': 'network-2'}}]
         self.useFixture(fixtures.MockPatch(
-            'tempest.common.preprov_creds.read_accounts_yaml',
+            'tempest.lib.common.preprov_creds.read_accounts_yaml',
             return_value=test_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
@@ -363,7 +363,7 @@
         admin_accounts = [x for x in self.test_accounts if 'test_admin'
                           in x['username']]
         self.useFixture(fixtures.MockPatch(
-            'tempest.common.preprov_creds.read_accounts_yaml',
+            'tempest.lib.common.preprov_creds.read_accounts_yaml',
             return_value=admin_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
@@ -381,7 +381,7 @@
         admin_accounts = [x for x in self.test_accounts if 'test_admin'
                           in x['username']]
         self.useFixture(fixtures.MockPatch(
-            'tempest.common.preprov_creds.read_accounts_yaml',
+            'tempest.lib.common.preprov_creds.read_accounts_yaml',
             return_value=admin_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
@@ -402,7 +402,7 @@
             {'username': 'test_admin1', 'tenant_name': 'test_tenant11',
              'password': 'p', 'types': ['admin']}]
         self.useFixture(fixtures.MockPatch(
-            'tempest.common.preprov_creds.read_accounts_yaml',
+            'tempest.lib.common.preprov_creds.read_accounts_yaml',
             return_value=test_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
@@ -416,7 +416,7 @@
             {'username': 'test_admin1', 'tenant_name': 'test_tenant11',
              'password': 'p', 'roles': [cfg.CONF.identity.admin_role]}]
         self.useFixture(fixtures.MockPatch(
-            'tempest.common.preprov_creds.read_accounts_yaml',
+            'tempest.lib.common.preprov_creds.read_accounts_yaml',
             return_value=test_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
@@ -427,7 +427,7 @@
         non_admin_accounts = [x for x in self.test_accounts if 'test_admin'
                               not in x['username']]
         self.useFixture(fixtures.MockPatch(
-            'tempest.common.preprov_creds.read_accounts_yaml',
+            'tempest.lib.common.preprov_creds.read_accounts_yaml',
             return_value=non_admin_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
diff --git a/tempest/tests/lib/services/base.py b/tempest/tests/lib/services/base.py
index 778c966..924f9f2 100644
--- a/tempest/tests/lib/services/base.py
+++ b/tempest/tests/lib/services/base.py
@@ -32,6 +32,7 @@
     def check_service_client_function(self, function, function2mock,
                                       body, to_utf=False, status=200,
                                       headers=None, mock_args=None,
+                                      resp_as_string=False,
                                       **kwargs):
         """Mock a service client function for unit testing.
 
@@ -53,6 +54,9 @@
                  ``assert_called_once_with(foo='bar')`` is called.
                * If mock_args='foo' then ``assert_called_once_with('foo')``
                  is called.
+        :param resp_as_string: Whether response body is retruned as string.
+               This is for service client methods which return ResponseBodyData
+               object.
         :param kwargs: kwargs that are passed to function.
         """
         mocked_response = self.create_response(body, to_utf, status, headers)
@@ -62,8 +66,9 @@
             resp = function(**kwargs)
         else:
             resp = function()
+        if resp_as_string:
+            resp = resp.data
         self.assertEqual(body, resp)
-
         if isinstance(mock_args, list):
             fixture.mock.assert_called_once_with(*mock_args)
         elif isinstance(mock_args, dict):
diff --git a/tempest/tests/lib/services/volume/v2/test_limits_client.py b/tempest/tests/lib/services/volume/v2/test_limits_client.py
new file mode 100644
index 0000000..202054c
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_limits_client.py
@@ -0,0 +1,59 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.services.volume.v2 import limits_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestLimitsClient(base.BaseServiceTest):
+
+    FAKE_LIMIT_INFO = {
+        "limits": {
+            "rate": [],
+            "absolute": {
+                "totalSnapshotsUsed": 0,
+                "maxTotalBackups": 10,
+                "maxTotalVolumeGigabytes": 1000,
+                "maxTotalSnapshots": 10,
+                "maxTotalBackupGigabytes": 1000,
+                "totalBackupGigabytesUsed": 0,
+                "maxTotalVolumes": 10,
+                "totalVolumesUsed": 0,
+                "totalBackupsUsed": 0,
+                "totalGigabytesUsed": 0
+            }
+        }
+    }
+
+    def setUp(self):
+        super(TestLimitsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = limits_client.LimitsClient(fake_auth,
+                                                 'volume',
+                                                 'regionOne')
+
+    def _test_show_limits(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_limits,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIMIT_INFO,
+            bytes_body)
+
+    def test_show_limits_with_str_body(self):
+        self._test_show_limits()
+
+    def test_show_limits_with_bytes_body(self):
+        self._test_show_limits(bytes_body=True)
diff --git a/tempest/tests/services/object_storage/test_bulk_middleware_client.py b/tempest/tests/services/object_storage/test_bulk_middleware_client.py
new file mode 100644
index 0000000..163b48e
--- /dev/null
+++ b/tempest/tests/services/object_storage/test_bulk_middleware_client.py
@@ -0,0 +1,66 @@
+# Copyright 2017 NEC Corporation.  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.services.object_storage import bulk_middleware_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestBulkMiddlewareClient(base.BaseServiceTest):
+
+    def setUp(self):
+        super(TestBulkMiddlewareClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = bulk_middleware_client.BulkMiddlewareClient(
+            fake_auth, 'object-storage', 'regionOne')
+
+    def test_upload_archive(self):
+        url = 'test_path?extract-archive=tar'
+        data = 'test_data'
+        self.check_service_client_function(
+            self.client.upload_archive,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            mock_args=[url, data, {}],
+            resp_as_string=True,
+            upload_path='test_path', data=data, archive_file_format='tar')
+
+    def test_delete_bulk_data(self):
+        url = '?bulk-delete'
+        data = 'test_data'
+        self.check_service_client_function(
+            self.client.delete_bulk_data,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            mock_args=[url, {}, data],
+            resp_as_string=True,
+            data=data)
+
+    def _test_delete_bulk_data_with_post(self, status):
+        url = '?bulk-delete'
+        data = 'test_data'
+        self.check_service_client_function(
+            self.client.delete_bulk_data_with_post,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {},
+            mock_args=[url, data, {}],
+            resp_as_string=True,
+            status=status,
+            data=data)
+
+    def test_delete_bulk_data_with_post_200(self):
+        self._test_delete_bulk_data_with_post(200)
+
+    def test_delete_bulk_data_with_post_204(self):
+        self._test_delete_bulk_data_with_post(204)
diff --git a/tempest/tests/services/object_storage/test_capabilities_client.py b/tempest/tests/services/object_storage/test_capabilities_client.py
new file mode 100644
index 0000000..5279bf4
--- /dev/null
+++ b/tempest/tests/services/object_storage/test_capabilities_client.py
@@ -0,0 +1,54 @@
+# Copyright 2016 IBM Corp.
+# 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.services.object_storage import capabilities_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestCapabilitiesClient(base.BaseServiceTest):
+
+    def setUp(self):
+        super(TestCapabilitiesClient, self).setUp()
+        self.fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.url = self.fake_auth.base_url(None)
+        self.client = capabilities_client.CapabilitiesClient(
+            self.fake_auth, 'swift', 'region1')
+
+    def _test_list_capabilities(self, bytes_body=False):
+        resp = {
+            "swift": {
+                "version": "1.11.0"
+            },
+            "slo": {
+                "max_manifest_segments": 1000,
+                "max_manifest_size": 2097152,
+                "min_segment_size": 1
+            },
+            "staticweb": {},
+            "tempurl": {}
+        }
+        self.check_service_client_function(
+            self.client.list_capabilities,
+            'tempest.lib.common.rest_client.RestClient.get',
+            resp,
+            bytes_body)
+
+    def test_list_capabilities_with_str_body(self):
+        self._test_list_capabilities()
+
+    def test_list_capabilities_with_bytes_body(self):
+        self._test_list_capabilities(True)
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
index 01b8a72..6c6f612 100644
--- a/tempest/tests/test_base_test.py
+++ b/tempest/tests/test_base_test.py
@@ -16,8 +16,8 @@
 
 from tempest import clients
 from tempest.common import credentials_factory as credentials
-from tempest.common import fixed_network
 from tempest import config
+from tempest.lib.common import fixed_network
 from tempest import test
 from tempest.tests import base
 from tempest.tests import fake_config