Merge "Adding/fixing docstring to _create_creds function"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 7e4503d..c2df0b6 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -135,7 +135,7 @@
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+html_use_smartypants = False
 
 # Custom sidebar templates, maps document names to template names.
 #html_sidebars = {}
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 9a7ce15..e4b104f 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -26,6 +26,11 @@
 - Run tests for admin APIs
 - Generate test credentials on the fly (see `Dynamic Credentials`_)
 
+When keystone uses a policy that requires domain scoped tokens for admin
+actions, the flag ``admin_domain_scope`` must be set to ``True``.
+The admin user configured, if any, must have a role assigned to the domain to
+be usable.
+
 Tempest allows for configuring pre-provisioned test credentials as well.
 This can be done using the accounts.yaml file (see
 `Pre-Provisioned Credentials`_). This file is used to specify an arbitrary
@@ -87,6 +92,14 @@
 by dynamic credentials. This option will not have any effect when Tempest is not
 configured to use dynamic credentials.
 
+When the ``admin_domain_scope`` option is set to ``True``, provisioned admin
+accounts will be assigned a role on domain configured in
+``default_credentials_domain_name``. This will make the accounts provisioned
+usable in a cloud where domain scoped tokens are required by keystone for
+admin operations. Note that the the initial pre-provision admin accounts,
+configured in tempest.conf, must have a role on the same domain as well, for
+Dynamic Credentials to work.
+
 
 Pre-Provisioned Credentials
 """""""""""""""""""""""""""
@@ -124,6 +137,18 @@
 to the tests using the credentials, and failure to do this will likely cause
 unexpected failures in some tests.
 
+When the keystone in the target cloud requires domain scoped tokens to
+perform admin actions, all pre-provisioned admin users must have a role
+assigned on the domain where test accounts a provisioned.
+The option ``admin_domain_scope`` is used to tell tempest that domain scoped
+tokens shall be used. ``default_credentials_domain_name`` is the domain where
+test accounts are expected to be provisioned if no domain is specified.
+
+Note that if credentials are pre-provisioned via ``tempest account-generator``
+the role on the domain will be assigned automatically for you, as long as
+``admin_domain_scope`` as ``default_credentials_domain_name`` are configured
+properly in tempest.conf.
+
 Pre-Provisioned Credentials are also know as accounts.yaml or accounts file.
 
 Compute
@@ -205,6 +230,8 @@
 
 Enabling Remote Access to Created Servers
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Network Creation/Usage for Servers
+""""""""""""""""""""""""""""""""""
 When Tempest creates servers for testing, some tests require being able to
 connect those servers. Depending on the configuration of the cloud, the methods
 for doing this can be different. In certain configurations it is required to
@@ -214,23 +241,8 @@
 run. This section covers the different methods of configuring Tempest to provide
 a network when creating servers.
 
-The ``validation`` group gathers all the connection options to remotely access the
-created servers.
-
-To enable remote access to servers, at least the three following options need to be
-set:
-
-* The ``run_validation`` option needs be set to ``true``.
-
-* The ``connect_method`` option. Two connect methods are available: ``fixed`` and
-  ``floating``, the later being set by default.
-
-* The ``auth_method`` option. Currently, only authentication by keypair is
-  available.
-
-
 Fixed Network Name
-""""""""""""""""""
+''''''''''''''''''
 This is the simplest method of specifying how networks should be used. You can
 just specify a single network name/label to use for all server creations. The
 limitation with this is that all projects and users must be able to see
@@ -252,7 +264,7 @@
 
 
 Accounts File
-"""""""""""""
+'''''''''''''
 If you are using an accounts file to provide credentials for running Tempest
 then you can leverage it to also specify which network should be used with
 server creations on a per project and user pair basis. This provides
@@ -277,7 +289,7 @@
 
 
 With Dynamic Credentials
-""""""""""""""""""""""""
+''''''''''''''''''''''''
 With dynamic credentials enabled and using nova-network, your only option for
 configuration is to either set a fixed network name or not. However, in most
 cases it shouldn't matter because nova-network should have no problem booting a
@@ -302,6 +314,34 @@
 network available for the server creation, or use ``fixed_network_name`` to
 inform Tempest which network to use.
 
+SSH Connection Configuration
+""""""""""""""""""""""""""""
+There are also several different ways to actually establish a connection and
+authenticate/login on the server. After a server is booted with a provided
+network there are still details needed to know how to actually connect to
+the server. The ``validation`` group gathers all the options regarding
+connecting to and remotely accessing the created servers.
+
+To enable remote access to servers, there are 3 options at a minimum that are used:
+
+ #. ``run_validation``
+ #. ``connect_method``
+ #. ``auth_method``
+
+The ``run_validation`` is used to enable or disable ssh connectivity for
+all tests (with the exception of scenario tests which do not have a flag for
+enabling or disabling ssh) To enable ssh connectivity this needs be set to ``true``.
+
+The ``connect_method`` option is used to tell tempest what kind of IP to use for
+establishing a connection to the server. Two methods are available: ``fixed``
+and ``floating``, the later being set by default. If this is set to floating
+tempest will create a floating ip for the server before attempted to connect
+to it. The IP for the floating ip is what is used for the connection.
+
+For the ``auth_method`` option there is currently, only one valid option,
+``keypair``. With this set to ``keypair`` tempest will create an ssh keypair
+and use that for authenticating against the created server.
+
 Configuring Available Services
 ------------------------------
 OpenStack is really a constellation of several different projects which
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 10364db..c73fac3 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -51,6 +51,8 @@
    account_generator
    cleanup
    javelin
+   workspace
+   run
 
 ==================
 Indices and tables
diff --git a/doc/source/library.rst b/doc/source/library.rst
index a89512c..6a2fb83 100644
--- a/doc/source/library.rst
+++ b/doc/source/library.rst
@@ -66,3 +66,4 @@
    library/rest_client
    library/utils
    library/api_microversion_testing
+   library/auth
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 3568470..17059e4 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -19,6 +19,7 @@
   multiple Microversion tests in a single Tempest operation, configuration
   options should represent the range of test target Microversions.
   New configuration options are:
+
   * min_microversion
   * max_microversion
 
@@ -130,8 +131,9 @@
 If that range is out of configured Microversion range then, test
 will be skipped.
 
-*NOTE: Microversion testing is supported at test class level not at individual
-test case level.*
+.. note:: Microversion testing is supported at test class level not at
+      individual test case level.
+
 For example:
 
 Below test is applicable for Microversion from 2.2 till 2.9::
@@ -211,3 +213,7 @@
  * `2.10`_
 
  .. _2.10: http://docs.openstack.org/developer/nova/api_microversion_history.html#id9
+
+ * `2.20`_
+
+ .. _2.20: http://docs.openstack.org/developer/nova/api_microversion_history.html#id18
diff --git a/doc/source/run.rst b/doc/source/run.rst
new file mode 100644
index 0000000..07fa5f7
--- /dev/null
+++ b/doc/source/run.rst
@@ -0,0 +1,5 @@
+-----------
+Tempest Run
+-----------
+
+.. automodule:: tempest.cmd.run
diff --git a/doc/source/workspace.rst b/doc/source/workspace.rst
new file mode 100644
index 0000000..41325b2
--- /dev/null
+++ b/doc/source/workspace.rst
@@ -0,0 +1,5 @@
+-----------------
+Tempest Workspace
+-----------------
+
+.. automodule:: tempest.cmd.workspace
diff --git a/releasenotes/notes/add-tempest-run-3d0aaf69c2ca4115.yaml b/releasenotes/notes/add-tempest-run-3d0aaf69c2ca4115.yaml
new file mode 100644
index 0000000..429bf52
--- /dev/null
+++ b/releasenotes/notes/add-tempest-run-3d0aaf69c2ca4115.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - Adds the tempest run command to the unified tempest CLI. This new command
+    is used for running tempest tests.
diff --git a/releasenotes/notes/add-tempest-workspaces-228a2ba4690b5589.yaml b/releasenotes/notes/add-tempest-workspaces-228a2ba4690b5589.yaml
new file mode 100644
index 0000000..9a1cef6
--- /dev/null
+++ b/releasenotes/notes/add-tempest-workspaces-228a2ba4690b5589.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - Adds tempest workspaces command and WorkspaceManager.
+    This is used to have a centralized repository for managing
+    different tempest configurations.
diff --git a/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml b/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml
new file mode 100644
index 0000000..b2190f3
--- /dev/null
+++ b/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Tempest library auth interface now supports
+    filtering with catalog name.  Note that filtering by
+    name is only successful if a known service type is
+    provided.
diff --git a/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml b/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
new file mode 100644
index 0000000..b7850d0
--- /dev/null
+++ b/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    Define identity service clients as libraries
+    The following identity service clients are defined as library interface,
+    so the other projects can use these modules as stable libraries without
+    any maintenance changes.
+
+      * endpoints_client(v2)
diff --git a/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
new file mode 100644
index 0000000..faae7d0
--- /dev/null
+++ b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
@@ -0,0 +1,13 @@
+---
+features:
+  - |
+    Define image service clients as libraries
+    The following image service clients are defined as library interface,
+    so the other projects can use these modules as stable libraries
+    without any maintenance changes.
+
+      * image_members_client(v2)
+      * images_client(v2)
+      * namespaces_client(v2)
+      * resource_types_client(v2)
+      * schemas_client(v2)
diff --git a/setup.cfg b/setup.cfg
index 24e0214..66a8743 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -41,6 +41,8 @@
     run-stress = tempest.cmd.run_stress:TempestRunStress
     list-plugins = tempest.cmd.list_plugins:TempestListPlugins
     verify-config = tempest.cmd.verify_tempest_config:TempestVerifyConfig
+    workspace = tempest.cmd.workspace:TempestWorkspace
+    run = tempest.cmd.run:TempestRun
 oslo.config.opts =
     tempest.config = tempest.config:list_opts
 
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 25efd2e..84b00a7 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.api.compute import base
 from tempest.common import tempest_fixtures as fixtures
 from tempest.common.utils import data_utils
@@ -36,10 +38,17 @@
         cls.aggregate_name_prefix = 'test_aggregate'
         cls.az_name_prefix = 'test_az'
 
-        hosts_all = cls.os_adm.hosts_client.list_hosts()['hosts']
-        hosts = map(lambda x: x['host_name'],
-                    filter(lambda y: y['service'] == 'compute', hosts_all))
-        cls.host = hosts[0]
+        cls.host = None
+        hypers = cls.os_adm.hypervisor_client.list_hypervisors(
+            detail=True)['hypervisors']
+        hosts_available = [hyper['service']['host'] for hyper in hypers
+                           if (hyper['state'] == 'up' and
+                               hyper['status'] == 'enabled')]
+        if hosts_available:
+            cls.host = hosts_available[0]
+        else:
+            raise testtools.TestCase.failureException(
+                "no available compute node found")
 
     @test.idempotent_id('0d148aa3-d54c-4317-aa8d-42040a475e20')
     def test_aggregate_create_delete(self):
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 0724566..427a748 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -19,6 +19,7 @@
 from tempest.common.utils import data_utils
 from tempest.common import waiters
 from tempest import config
+from tempest import exceptions
 from tempest import test
 
 CONF = config.CONF
@@ -36,7 +37,17 @@
     @classmethod
     def setup_clients(cls):
         super(ImagesMetadataTestJSON, cls).setup_clients()
-        cls.glance_client = cls.os.image_client
+        # Check if glance v1 is available to determine which client to use. We
+        # prefer glance v1 for the compute API tests since the compute image
+        # API proxy was written for glance v1.
+        if CONF.image_feature_enabled.api_v1:
+            cls.glance_client = cls.os.image_client
+        elif CONF.image_feature_enabled.api_v2:
+            cls.glance_client = cls.os.image_client_v2
+        else:
+            raise exceptions.InvalidConfiguration(
+                'Either api_v1 or api_v2 must be True in '
+                '[image-feature-enabled].')
         cls.client = cls.compute_images_client
 
     @classmethod
@@ -45,14 +56,22 @@
         cls.image_id = None
 
         name = data_utils.rand_name('image')
+        if CONF.image_feature_enabled.api_v1:
+            kwargs = dict(is_public=False)
+        else:
+            kwargs = dict(visibility='private')
         body = cls.glance_client.create_image(name=name,
                                               container_format='bare',
                                               disk_format='raw',
-                                              is_public=False)['image']
+                                              **kwargs)
+        body = body['image'] if 'image' in body else body
         cls.image_id = body['id']
         cls.images.append(cls.image_id)
         image_file = six.StringIO(('*' * 1024))
-        cls.glance_client.update_image(cls.image_id, data=image_file)
+        if CONF.image_feature_enabled.api_v1:
+            cls.glance_client.update_image(cls.image_id, data=image_file)
+        else:
+            cls.glance_client.store_image_file(cls.image_id, data=image_file)
         waiters.wait_for_image_status(cls.client, cls.image_id, 'ACTIVE')
 
     def setUp(self):
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index af840cc..e74ca67 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -22,6 +22,7 @@
 from tempest.common.utils import data_utils
 from tempest.common import waiters
 from tempest import config
+from tempest import exceptions
 from tempest import test
 
 CONF = config.CONF
@@ -40,7 +41,17 @@
     def setup_clients(cls):
         super(ListImageFiltersTestJSON, cls).setup_clients()
         cls.client = cls.compute_images_client
-        cls.glance_client = cls.os.image_client
+        # Check if glance v1 is available to determine which client to use. We
+        # prefer glance v1 for the compute API tests since the compute image
+        # API proxy was written for glance v1.
+        if CONF.image_feature_enabled.api_v1:
+            cls.glance_client = cls.os.image_client
+        elif CONF.image_feature_enabled.api_v2:
+            cls.glance_client = cls.os.image_client_v2
+        else:
+            raise exceptions.InvalidConfiguration(
+                'Either api_v1 or api_v2 must be True in '
+                '[image-feature-enabled].')
 
     @classmethod
     def resource_setup(cls):
@@ -48,17 +59,25 @@
 
         def _create_image():
             name = data_utils.rand_name('image')
+            if CONF.image_feature_enabled.api_v1:
+                kwargs = dict(is_public=False)
+            else:
+                kwargs = dict(visibility='private')
             body = cls.glance_client.create_image(name=name,
                                                   container_format='bare',
                                                   disk_format='raw',
-                                                  is_public=False)['image']
+                                                  **kwargs)
+            body = body['image'] if 'image' in body else body
             image_id = body['id']
             cls.images.append(image_id)
             # Wait 1 second between creation and upload to ensure a delta
             # between created_at and updated_at.
             time.sleep(1)
             image_file = six.StringIO(('*' * 1024))
-            cls.glance_client.update_image(image_id, data=image_file)
+            if CONF.image_feature_enabled.api_v1:
+                cls.glance_client.update_image(image_id, data=image_file)
+            else:
+                cls.glance_client.store_image_file(image_id, data=image_file)
             waiters.wait_for_image_status(cls.client, image_id, 'ACTIVE')
             body = cls.client.show_image(image_id)['image']
             return body
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index b18789e..357c907 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -109,9 +109,12 @@
     @test.attr(type=['negative'])
     @test.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
     def test_list_servers_by_limits_greater_than_actual_count(self):
+        # Gather the complete list of servers in the project for reference
+        full_list = self.client.list_servers()['servers']
         # List servers by specifying a greater value for limit
-        body = self.client.list_servers(limit=100)
-        self.assertEqual(len(self.existing_fixtures), len(body['servers']))
+        limit = len(full_list) + 100
+        body = self.client.list_servers(limit=limit)
+        self.assertEqual(len(full_list), len(body['servers']))
 
     @test.attr(type=['negative'])
     @test.idempotent_id('679bc053-5e70-4514-9800-3dfab1a380a6')
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 66aec84..3a4428a 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -24,6 +24,7 @@
 from tempest.common.utils.linux import remote_client
 from tempest.common import waiters
 from tempest import config
+from tempest import exceptions
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 from tempest import test
@@ -320,6 +321,19 @@
     def test_create_backup(self):
         # Positive test:create backup successfully and rotate backups correctly
         # create the first and the second backup
+
+        # Check if glance v1 is available to determine which client to use. We
+        # prefer glance v1 for the compute API tests since the compute image
+        # API proxy was written for glance v1.
+        if CONF.image_feature_enabled.api_v1:
+            glance_client = self.os.image_client
+        elif CONF.image_feature_enabled.api_v2:
+            glance_client = self.os.image_client_v2
+        else:
+            raise exceptions.InvalidConfiguration(
+                'Either api_v1 or api_v2 must be True in '
+                '[image-feature-enabled].')
+
         backup1 = data_utils.rand_name('backup-1')
         resp = self.client.create_backup(self.server_id,
                                          backup_type='daily',
@@ -331,7 +345,7 @@
         def _clean_oldest_backup(oldest_backup):
             if oldest_backup_exist:
                 try:
-                    self.os.image_client.delete_image(oldest_backup)
+                    glance_client.delete_image(oldest_backup)
                 except lib_exc.NotFound:
                     pass
                 else:
@@ -341,7 +355,7 @@
 
         image1_id = data_utils.parse_image_id(resp['location'])
         self.addCleanup(_clean_oldest_backup, image1_id)
-        waiters.wait_for_image_status(self.os.image_client,
+        waiters.wait_for_image_status(glance_client,
                                       image1_id, 'active')
 
         backup2 = data_utils.rand_name('backup-2')
@@ -351,8 +365,8 @@
                                          rotation=2,
                                          name=backup2).response
         image2_id = data_utils.parse_image_id(resp['location'])
-        self.addCleanup(self.os.image_client.delete_image, image2_id)
-        waiters.wait_for_image_status(self.os.image_client,
+        self.addCleanup(glance_client.delete_image, image2_id)
+        waiters.wait_for_image_status(glance_client,
                                       image2_id, 'active')
 
         # verify they have been created
@@ -361,12 +375,20 @@
             'backup_type': "daily",
             'instance_uuid': self.server_id,
         }
-        image_list = self.os.image_client.list_images(
-            detail=True,
-            properties=properties,
-            status='active',
-            sort_key='created_at',
-            sort_dir='asc')['images']
+        if CONF.image_feature_enabled.api_v1:
+            params = dict(
+                properties=properties, status='active',
+                sort_key='created_at', sort_dir='asc',)
+            image_list = glance_client.list_images(
+                detail=True,
+                **params)['images']
+        else:
+            # Additional properties are flattened in glance v2.
+            params = dict(
+                status='active', sort_key='created_at', sort_dir='asc')
+            params.update(properties)
+            image_list = glance_client.list_images(params)['images']
+
         self.assertEqual(2, len(image_list))
         self.assertEqual((backup1, backup2),
                          (image_list[0]['name'], image_list[1]['name']))
@@ -380,17 +402,16 @@
                                          rotation=2,
                                          name=backup3).response
         image3_id = data_utils.parse_image_id(resp['location'])
-        self.addCleanup(self.os.image_client.delete_image, image3_id)
+        self.addCleanup(glance_client.delete_image, image3_id)
         # the first back up should be deleted
         waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
-        self.os.image_client.wait_for_resource_deletion(image1_id)
+        glance_client.wait_for_resource_deletion(image1_id)
         oldest_backup_exist = False
-        image_list = self.os.image_client.list_images(
-            detail=True,
-            properties=properties,
-            status='active',
-            sort_key='created_at',
-            sort_dir='asc')['images']
+        if CONF.image_feature_enabled.api_v1:
+            image_list = glance_client.list_images(
+                detail=True, **params)['images']
+        else:
+            image_list = glance_client.list_images(params)['images']
         self.assertEqual(2, len(image_list),
                          'Unexpected number of images for '
                          'v2:test_create_backup; was the oldest backup not '
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index fa3fdfe..05c23ee 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -200,7 +200,7 @@
             server=self.server,
             servers_client=self.servers_client)
 
-        command = 'grep vd /proc/partitions | wc -l'
+        command = 'grep [vs]d /proc/partitions | wc -l'
         nb_partitions = linux_client.exec_command(command).strip()
         self.assertEqual(number_of_partition, nb_partitions)
 
diff --git a/tempest/api/identity/admin/v2/test_endpoints.py b/tempest/api/identity/admin/v2/test_endpoints.py
index df75d0a..651a316 100644
--- a/tempest/api/identity/admin/v2/test_endpoints.py
+++ b/tempest/api/identity/admin/v2/test_endpoints.py
@@ -28,7 +28,8 @@
         s_type = data_utils.rand_name('type')
         s_description = data_utils.rand_name('description')
         cls.service_data = cls.services_client.create_service(
-            s_name, s_type, description=s_description)['OS-KSADM:service']
+            name=s_name, type=s_type,
+            description=s_description)['OS-KSADM:service']
         cls.service_id = cls.service_data['id']
         cls.service_ids.append(cls.service_id)
         # Create endpoints so as to use for LIST and GET test cases
@@ -37,8 +38,8 @@
             region = data_utils.rand_name('region')
             url = data_utils.rand_url()
             endpoint = cls.endpoints_client.create_endpoint(
-                cls.service_id,
-                region,
+                service_id=cls.service_id,
+                region=region,
                 publicurl=url,
                 adminurl=url,
                 internalurl=url)['endpoint']
@@ -70,8 +71,8 @@
         region = data_utils.rand_name('region')
         url = data_utils.rand_url()
         endpoint = self.endpoints_client.create_endpoint(
-            self.service_id,
-            region,
+            service_id=self.service_id,
+            region=region,
             publicurl=url,
             adminurl=url,
             internalurl=url)['endpoint']
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index fe83759..6c9b564 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -35,10 +35,11 @@
         # GET Service
         # Creating a Service
         name = data_utils.rand_name('service')
-        type = data_utils.rand_name('type')
+        s_type = data_utils.rand_name('type')
         description = data_utils.rand_name('description')
         service_data = self.services_client.create_service(
-            name, type, description=description)['OS-KSADM:service']
+            name=name, type=s_type,
+            description=description)['OS-KSADM:service']
         self.assertFalse(service_data['id'] is None)
         self.addCleanup(self._del_service, service_data['id'])
         # Verifying response body of create service
@@ -46,7 +47,7 @@
         self.assertIn('name', service_data)
         self.assertEqual(name, service_data['name'])
         self.assertIn('type', service_data)
-        self.assertEqual(type, service_data['type'])
+        self.assertEqual(s_type, service_data['type'])
         self.assertIn('description', service_data)
         self.assertEqual(description, service_data['description'])
         # Get service
@@ -68,15 +69,15 @@
     def test_create_service_without_description(self):
         # Create a service only with name and type
         name = data_utils.rand_name('service')
-        type = data_utils.rand_name('type')
-        service = self.services_client.create_service(name,
-                                                      type)['OS-KSADM:service']
+        s_type = data_utils.rand_name('type')
+        service = self.services_client.create_service(
+            name=name, type=s_type)['OS-KSADM:service']
         self.assertIn('id', service)
         self.addCleanup(self._del_service, service['id'])
         self.assertIn('name', service)
         self.assertEqual(name, service['name'])
         self.assertIn('type', service)
-        self.assertEqual(type, service['type'])
+        self.assertEqual(s_type, service['type'])
 
     @test.attr(type='smoke')
     @test.idempotent_id('34ea6489-012d-4a86-9038-1287cadd5eca')
@@ -85,10 +86,12 @@
         services = []
         for _ in moves.xrange(3):
             name = data_utils.rand_name('service')
-            type = data_utils.rand_name('type')
+            s_type = data_utils.rand_name('type')
             description = data_utils.rand_name('description')
+
             service = self.services_client.create_service(
-                name, type, description=description)['OS-KSADM:service']
+                name=name, type=s_type,
+                description=description)['OS-KSADM:service']
             services.append(service)
         service_ids = map(lambda x: x['id'], services)
 
diff --git a/tempest/api/identity/admin/v2/test_tokens.py b/tempest/api/identity/admin/v2/test_tokens.py
index ee04420..2297a9d 100644
--- a/tempest/api/identity/admin/v2/test_tokens.py
+++ b/tempest/api/identity/admin/v2/test_tokens.py
@@ -30,8 +30,10 @@
         tenant = self.tenants_client.create_tenant(tenant_name)['tenant']
         self.data.tenants.append(tenant)
         # second:create a user
-        user = self.users_client.create_user(user_name, user_password,
-                                             tenant['id'], '')['user']
+        user = self.users_client.create_user(name=user_name,
+                                             password=user_password,
+                                             tenantId=tenant['id'],
+                                             email='')['user']
         self.data.users.append(user)
         # then get a token for the user
         body = self.token_client.auth(user_name,
@@ -62,8 +64,10 @@
         user_password = data_utils.rand_password()
         tenant_id = None  # No default tenant so will get unscoped token.
         email = ''
-        user = self.users_client.create_user(user_name, user_password,
-                                             tenant_id, email)['user']
+        user = self.users_client.create_user(name=user_name,
+                                             password=user_password,
+                                             tenantId=tenant_id,
+                                             email=email)['user']
         self.data.users.append(user)
 
         # Create a couple tenants.
diff --git a/tempest/api/identity/admin/v2/test_users.py b/tempest/api/identity/admin/v2/test_users.py
index d860d2f..0f783b3 100644
--- a/tempest/api/identity/admin/v2/test_users.py
+++ b/tempest/api/identity/admin/v2/test_users.py
@@ -36,9 +36,10 @@
     def test_create_user(self):
         # Create a user
         self.data.setup_test_tenant()
-        user = self.users_client.create_user(self.alt_user, self.alt_password,
-                                             self.data.tenant['id'],
-                                             self.alt_email)['user']
+        user = self.users_client.create_user(name=self.alt_user,
+                                             password=self.alt_password,
+                                             tenantId=self.data.tenant['id'],
+                                             email=self.alt_email)['user']
         self.data.users.append(user)
         self.assertEqual(self.alt_user, user['name'])
 
@@ -47,9 +48,10 @@
         # Create a user with enabled : False
         self.data.setup_test_tenant()
         name = data_utils.rand_name('test_user')
-        user = self.users_client.create_user(name, self.alt_password,
-                                             self.data.tenant['id'],
-                                             self.alt_email,
+        user = self.users_client.create_user(name=name,
+                                             password=self.alt_password,
+                                             tenantId=self.data.tenant['id'],
+                                             email=self.alt_email,
                                              enabled=False)['user']
         self.data.users.append(user)
         self.assertEqual(name, user['name'])
@@ -61,9 +63,10 @@
         # Test case to check if updating of user attributes is successful.
         test_user = data_utils.rand_name('test_user')
         self.data.setup_test_tenant()
-        user = self.users_client.create_user(test_user, self.alt_password,
-                                             self.data.tenant['id'],
-                                             self.alt_email)['user']
+        user = self.users_client.create_user(name=test_user,
+                                             password=self.alt_password,
+                                             tenantId=self.data.tenant['id'],
+                                             email=self.alt_email)['user']
         # Delete the User at the end of this method
         self.addCleanup(self.users_client.delete_user, user['id'])
         # Updating user details with new values
@@ -87,9 +90,10 @@
         # Delete a user
         test_user = data_utils.rand_name('test_user')
         self.data.setup_test_tenant()
-        user = self.users_client.create_user(test_user, self.alt_password,
-                                             self.data.tenant['id'],
-                                             self.alt_email)['user']
+        user = self.users_client.create_user(name=test_user,
+                                             password=self.alt_password,
+                                             tenantId=self.data.tenant['id'],
+                                             email=self.alt_email)['user']
         self.users_client.delete_user(user['id'])
 
     @test.idempotent_id('aca696c3-d645-4f45-b728-63646045beb1')
@@ -139,16 +143,18 @@
         fetched_user_ids = list()
         password1 = data_utils.rand_password()
         alt_tenant_user1 = data_utils.rand_name('tenant_user1')
-        user1 = self.users_client.create_user(alt_tenant_user1, password1,
-                                              self.data.tenant['id'],
-                                              'user1@123')['user']
+        user1 = self.users_client.create_user(name=alt_tenant_user1,
+                                              password=password1,
+                                              tenantId=self.data.tenant['id'],
+                                              email='user1@123')['user']
         user_ids.append(user1['id'])
         self.data.users.append(user1)
         password2 = data_utils.rand_password()
         alt_tenant_user2 = data_utils.rand_name('tenant_user2')
-        user2 = self.users_client.create_user(alt_tenant_user2, password2,
-                                              self.data.tenant['id'],
-                                              'user2@123')['user']
+        user2 = self.users_client.create_user(name=alt_tenant_user2,
+                                              password=password2,
+                                              tenantId=self.data.tenant['id'],
+                                              email='user2@123')['user']
         user_ids.append(user2['id'])
         self.data.users.append(user2)
         # List of users for the respective tenant ID
@@ -180,9 +186,11 @@
 
         alt_user2 = data_utils.rand_name('second_user')
         alt_password2 = data_utils.rand_password()
-        second_user = self.users_client.create_user(alt_user2, alt_password2,
-                                                    self.data.tenant['id'],
-                                                    'user2@123')['user']
+        second_user = self.users_client.create_user(
+            name=alt_user2,
+            password=alt_password2,
+            tenantId=self.data.tenant['id'],
+            email='user2@123')['user']
         user_ids.append(second_user['id'])
         self.data.users.append(second_user)
         role = self.roles_client.assign_user_role(tenant['id'],
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 5fda4c14..78b89fa 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -35,9 +35,9 @@
         self.data.setup_test_tenant()
         self.assertRaises(lib_exc.Forbidden,
                           self.non_admin_users_client.create_user,
-                          self.alt_user, self.alt_password,
-                          self.data.tenant['id'],
-                          self.alt_email)
+                          name=self.alt_user, password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('d80d0c2f-4514-4d1e-806d-0930dfc5a187')
@@ -45,8 +45,9 @@
         # User with an empty name should not be created
         self.data.setup_test_tenant()
         self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
-                          '', self.alt_password, self.data.tenant['id'],
-                          self.alt_email)
+                          name='', password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('7704b4f3-3b75-4b82-87cc-931d41c8f780')
@@ -54,8 +55,9 @@
         # Length of user name filed should be restricted to 255 characters
         self.data.setup_test_tenant()
         self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
-                          'a' * 256, self.alt_password,
-                          self.data.tenant['id'], self.alt_email)
+                          name='a' * 256, password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('57ae8558-120c-4723-9308-3751474e7ecf')
@@ -63,16 +65,20 @@
         # Duplicate user should not be created
         self.data.setup_test_user()
         self.assertRaises(lib_exc.Conflict, self.users_client.create_user,
-                          self.data.user['name'], self.data.user_password,
-                          self.data.tenant['id'], self.data.user['email'])
+                          name=self.data.user['name'],
+                          password=self.data.user_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.data.user['email'])
 
     @test.attr(type=['negative'])
     @test.idempotent_id('0132cc22-7c4f-42e1-9e50-ac6aad31d59a')
     def test_create_user_for_non_existent_tenant(self):
         # Attempt to create a user in a non-existent tenant should fail
         self.assertRaises(lib_exc.NotFound, self.users_client.create_user,
-                          self.alt_user, self.alt_password, '49ffgg99999',
-                          self.alt_email)
+                          name=self.alt_user,
+                          password=self.alt_password,
+                          tenantId='49ffgg99999',
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('55bbb103-d1ae-437b-989b-bcdf8175c1f4')
@@ -88,8 +94,9 @@
         self.addCleanup(self.client.auth_provider.clear_auth)
 
         self.assertRaises(lib_exc.Unauthorized, self.users_client.create_user,
-                          self.alt_user, self.alt_password,
-                          self.data.tenant['id'], self.alt_email)
+                          name=self.alt_user, password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('23a2f3da-4a1a-41da-abdd-632328a861ad')
@@ -98,9 +105,9 @@
         self.data.setup_test_tenant()
         name = data_utils.rand_name('test_user')
         self.assertRaises(lib_exc.BadRequest, self.users_client.create_user,
-                          name, self.alt_password,
-                          self.data.tenant['id'],
-                          self.alt_email, enabled=3)
+                          name=name, password=self.alt_password,
+                          tenantId=self.data.tenant['id'],
+                          email=self.alt_email, enabled=3)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('3d07e294-27a0-4144-b780-a2a1bf6fee19')
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 27ff15d..24a7a4e 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -16,6 +16,7 @@
 from tempest.api.identity import base
 from tempest.common.utils import data_utils
 from tempest import config
+from tempest.lib.common.utils import test_utils
 from tempest import test
 
 CONF = config.CONF
@@ -23,44 +24,78 @@
 
 class DomainsTestJSON(base.BaseIdentityV3AdminTest):
 
+    @classmethod
+    def resource_setup(cls):
+        super(DomainsTestJSON, cls).resource_setup()
+        # Create some test domains to be used during tests
+        # One of those domains will be disabled
+        cls.setup_domains = list()
+        for i in range(3):
+            domain = cls.domains_client.create_domain(
+                data_utils.rand_name('domain'),
+                description=data_utils.rand_name('domain-desc'),
+                enabled=i < 2)['domain']
+            cls.setup_domains.append(domain)
+
+    @classmethod
+    def resource_cleanup(cls):
+        for domain in cls.setup_domains:
+            cls._delete_domain(domain['id'])
+        super(DomainsTestJSON, cls).resource_cleanup()
+
+    @classmethod
     def _delete_domain(self, domain_id):
         # It is necessary to disable the domain before deleting,
         # or else it would result in unauthorized error
         self.domains_client.update_domain(domain_id, enabled=False)
         self.domains_client.delete_domain(domain_id)
-        # Asserting that the domain is not found in the list
-        # after deletion
-        body = self.domains_client.list_domains()['domains']
-        domains_list = [d['id'] for d in body]
-        self.assertNotIn(domain_id, domains_list)
 
     @test.idempotent_id('8cf516ef-2114-48f1-907b-d32726c734d4')
     def test_list_domains(self):
         # Test to list domains
-        domain_ids = list()
         fetched_ids = list()
-        for _ in range(3):
-            domain = self.domains_client.create_domain(
-                data_utils.rand_name('domain'),
-                description=data_utils.rand_name('domain-desc'))['domain']
-            # Delete the domain at the end of this method
-            self.addCleanup(self._delete_domain, domain['id'])
-            domain_ids.append(domain['id'])
         # List and Verify Domains
         body = self.domains_client.list_domains()['domains']
         for d in body:
             fetched_ids.append(d['id'])
-        missing_doms = [d for d in domain_ids if d not in fetched_ids]
+        missing_doms = [d for d in self.setup_domains
+                        if d['id'] not in fetched_ids]
         self.assertEqual(0, len(missing_doms))
 
+    @test.idempotent_id('c6aee07b-4981-440c-bb0b-eb598f58ffe9')
+    def test_list_domains_filter_by_name(self):
+        # List domains filtering by name
+        params = {'name': self.setup_domains[0]['name']}
+        fetched_domains = self.domains_client.list_domains(
+            params=params)['domains']
+        # Verify the filtered list is correct, domain names are unique
+        # so exactly one domain should be found with the provided name
+        self.assertEqual(1, len(fetched_domains))
+        self.assertEqual(self.setup_domains[0]['name'],
+                         fetched_domains[0]['name'])
+
+    @test.idempotent_id('3fd19840-65c1-43f8-b48c-51bdd066dff9')
+    def test_list_domains_filter_by_enabled(self):
+        # List domains filtering by enabled domains
+        params = {'enabled': True}
+        fetched_domains = self.domains_client.list_domains(
+            params=params)['domains']
+        # Verify the filtered list is correct
+        self.assertIn(self.setup_domains[0], fetched_domains)
+        self.assertIn(self.setup_domains[1], fetched_domains)
+        for domain in fetched_domains:
+            self.assertEqual(True, domain['enabled'])
+
     @test.attr(type='smoke')
     @test.idempotent_id('f2f5b44a-82e8-4dad-8084-0661ea3b18cf')
     def test_create_update_delete_domain(self):
+        # Create domain
         d_name = data_utils.rand_name('domain')
         d_desc = data_utils.rand_name('domain-desc')
         domain = self.domains_client.create_domain(
             d_name, description=d_desc)['domain']
-        self.addCleanup(self._delete_domain, domain['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self._delete_domain, domain['id'])
         self.assertIn('id', domain)
         self.assertIn('description', domain)
         self.assertIn('name', domain)
@@ -70,11 +105,12 @@
         self.assertEqual(d_name, domain['name'])
         self.assertEqual(d_desc, domain['description'])
         self.assertEqual(True, domain['enabled'])
+        # Update domain
         new_desc = data_utils.rand_name('new-desc')
         new_name = data_utils.rand_name('new-name')
-
         updated_domain = self.domains_client.update_domain(
-            domain['id'], name=new_name, description=new_desc)['domain']
+            domain['id'], name=new_name, description=new_desc,
+            enabled=False)['domain']
         self.assertIn('id', updated_domain)
         self.assertIn('description', updated_domain)
         self.assertIn('name', updated_domain)
@@ -83,13 +119,18 @@
         self.assertIsNotNone(updated_domain['id'])
         self.assertEqual(new_name, updated_domain['name'])
         self.assertEqual(new_desc, updated_domain['description'])
-        self.assertEqual(True, updated_domain['enabled'])
-
+        self.assertEqual(False, updated_domain['enabled'])
+        # Show domain
         fetched_domain = self.domains_client.show_domain(
             domain['id'])['domain']
         self.assertEqual(new_name, fetched_domain['name'])
         self.assertEqual(new_desc, fetched_domain['description'])
-        self.assertEqual(True, fetched_domain['enabled'])
+        self.assertEqual(False, fetched_domain['enabled'])
+        # Delete domain
+        self.domains_client.delete_domain(domain['id'])
+        body = self.domains_client.list_domains()['domains']
+        domains_list = [d['id'] for d in body]
+        self.assertNotIn(domain['id'], domains_list)
 
     @test.idempotent_id('036df86e-bb5d-42c0-a7c2-66b9db3a6046')
     def test_create_domain_with_disabled_status(self):
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 928437c..86f6b12 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -37,9 +37,15 @@
         cls.p2 = cls.projects_client.create_project(p2_name)['project']
         cls.data.projects.append(cls.p2)
         cls.project_ids.append(cls.p2['id'])
+        # Create a new project (p3) using p2 as parent project
+        p3_name = data_utils.rand_name('project')
+        cls.p3 = cls.projects_client.create_project(
+            p3_name, parent_id=cls.p2['id'])['project']
+        cls.data.projects.append(cls.p3)
+        cls.project_ids.append(cls.p3['id'])
 
     @test.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
-    def test_projects_list(self):
+    def test_list_projects(self):
         # List projects
         list_projects = self.projects_client.list_projects()['projects']
 
@@ -63,6 +69,16 @@
         # List projects with name
         self._list_projects_with_params({'name': self.p1_name}, 'name')
 
+    @test.idempotent_id('6edc66f5-2941-4a17-9526-4073311c1fac')
+    def test_list_projects_with_parent(self):
+        # List projects with parent
+        params = {'parent_id': self.p3['parent_id']}
+        fetched_projects = self.projects_client.list_projects(
+            params)['projects']
+        self.assertNotEmpty(fetched_projects)
+        for project in fetched_projects:
+            self.assertEqual(self.p3['parent_id'], project['parent_id'])
+
     def _list_projects_with_params(self, params, key):
         body = self.projects_client.list_projects(params)['projects']
         self.assertIn(self.p1[key], map(lambda x: x[key], body))
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index ece36b9..95894a6 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -15,7 +15,7 @@
 
 from tempest.api.identity import base
 from tempest.common.utils import data_utils
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
 from tempest import test
 
 
@@ -42,18 +42,19 @@
             cls.client.delete_region(r['id'])
         super(RegionsTestJSON, cls).resource_cleanup()
 
-    def _delete_region(self, region_id):
-        self.client.delete_region(region_id)
-        self.assertRaises(lib_exc.NotFound,
-                          self.client.show_region, region_id)
-
     @test.idempotent_id('56186092-82e4-43f2-b954-91013218ba42')
     def test_create_update_get_delete_region(self):
+        # Create region
         r_description = data_utils.rand_name('description')
         region = self.client.create_region(
             description=r_description,
             parent_region_id=self.setup_regions[0]['id'])['region']
-        self.addCleanup(self._delete_region, region['id'])
+        # This test will delete the region as part of the validation
+        # procedure, so it needs a different cleanup method that
+        # would be useful in case the tests fails at any point before
+        # reaching the deletion part.
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.client.delete_region, region['id'])
         self.assertEqual(r_description, region['description'])
         self.assertEqual(self.setup_regions[0]['id'],
                          region['parent_region_id'])
@@ -71,6 +72,11 @@
         self.assertEqual(r_alt_description, region['description'])
         self.assertEqual(self.setup_regions[1]['id'],
                          region['parent_region_id'])
+        # Delete the region
+        self.client.delete_region(region['id'])
+        body = self.client.list_regions()['regions']
+        regions_list = [r['id'] for r in body]
+        self.assertNotIn(region['id'], regions_list)
 
     @test.attr(type='smoke')
     @test.idempotent_id('2c12c5b5-efcf-4aa5-90c5-bff1ab0cdbe2')
@@ -80,7 +86,7 @@
         r_description = data_utils.rand_name('description')
         region = self.client.create_region(
             region_id=r_region_id, description=r_description)['region']
-        self.addCleanup(self._delete_region, region['id'])
+        self.addCleanup(self.client.delete_region, region['id'])
         # Asserting Create Region with specific id response body
         self.assertEqual(r_region_id, region['id'])
         self.assertEqual(r_description, region['description'])
@@ -95,3 +101,20 @@
         self.assertEqual(0, len(missing_regions),
                          "Failed to find region %s in fetched list" %
                          ', '.join(str(e) for e in missing_regions))
+
+    @test.idempotent_id('2d1057cb-bbde-413a-acdf-e2d265284542')
+    def test_list_regions_filter_by_parent_region_id(self):
+        # Add a sub-region to one of the existing test regions
+        r_description = data_utils.rand_name('description')
+        region = self.client.create_region(
+            description=r_description,
+            parent_region_id=self.setup_regions[0]['id'])['region']
+        self.addCleanup(self.client.delete_region, region['id'])
+        # Get the list of regions filtering with the parent_region_id
+        params = {'parent_region_id': self.setup_regions[0]['id']}
+        fetched_regions = self.client.list_regions(params=params)['regions']
+        # Asserting list regions response
+        self.assertIn(region, fetched_regions)
+        for r in fetched_regions:
+            self.assertEqual(self.setup_regions[0]['id'],
+                             r['parent_region_id'])
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 09ae468..9c8f1f6 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -98,7 +98,8 @@
             password=self.trustor_password,
             user_domain_id='default',
             tenant_name=self.trustor_project_name,
-            project_domain_id='default')
+            project_domain_id='default',
+            domain_id='default')
         os = clients.Manager(credentials=creds)
         self.trustor_client = os.trusts_client
 
@@ -266,7 +267,18 @@
     @test.attr(type='smoke')
     @test.idempotent_id('4773ebd5-ecbf-4255-b8d8-b63e6f72b65d')
     def test_get_trusts_all(self):
+
+        # Simple function that can be used for cleanup
+        def set_scope(auth_provider, scope):
+            auth_provider.scope = scope
+
         self.create_trust()
+        # Listing trusts can be done by trustor, by trustee, or without
+        # any filter if scoped to a project, so we must ensure token scope is
+        # project for this test.
+        original_scope = self.os_adm.auth_provider.scope
+        set_scope(self.os_adm.auth_provider, 'project')
+        self.addCleanup(set_scope, self.os_adm.auth_provider, original_scope)
         trusts_get = self.trusts_client.list_trusts()['trusts']
         trusts = [t for t in trusts_get
                   if t['id'] == self.trust_id]
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 2d4ff17..bc1b158 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -162,6 +162,12 @@
         cls.creds_client = cls.os_adm.credentials_client
         cls.groups_client = cls.os_adm.groups_client
         cls.projects_client = cls.os_adm.projects_client
+        if CONF.identity.admin_domain_scope:
+            # NOTE(andreaf) When keystone policy requires it, the identity
+            # admin clients for these tests shall use 'domain' scoped tokens.
+            # As the client manager is already created by the base class,
+            # we set the scope for the inner auth provider.
+            cls.os_adm.auth_provider.scope = 'domain'
 
     @classmethod
     def resource_setup(cls):
@@ -209,11 +215,10 @@
         self.domains = []
 
     def _create_test_user(self, **kwargs):
-        username = data_utils.rand_name('test_user')
         self.user_password = data_utils.rand_password()
         self.user = self.users_client.create_user(
-            username, password=self.user_password,
-            email=username + '@testmail.tm', **kwargs)['user']
+            password=self.user_password,
+            **kwargs)['user']
         self.users.append(self.user)
 
     def setup_test_role(self):
@@ -250,7 +255,10 @@
     def setup_test_user(self):
         """Set up a test user."""
         self.setup_test_tenant()
-        self._create_test_user(tenant_id=self.tenant['id'])
+        username = data_utils.rand_name('test_user')
+        email = username + '@testmail.tm'
+        self._create_test_user(name=username, email=email,
+                               tenantId=self.tenant['id'])
 
     def setup_test_tenant(self):
         """Set up a test tenant."""
@@ -265,7 +273,10 @@
     def setup_test_user(self):
         """Set up a test user."""
         self.setup_test_project()
-        self._create_test_user(project_id=self.project['id'])
+        username = data_utils.rand_name('test_user')
+        email = username + '@testmail.tm'
+        self._create_test_user(user_name=username, email=email,
+                               project_id=self.project['id'])
 
     def setup_test_project(self):
         """Set up a test project."""
diff --git a/tempest/api/identity/v2/test_ec2_credentials.py b/tempest/api/identity/v2/test_ec2_credentials.py
index 8600980..5902196 100644
--- a/tempest/api/identity/v2/test_ec2_credentials.py
+++ b/tempest/api/identity/v2/test_ec2_credentials.py
@@ -36,16 +36,16 @@
     def test_create_ec2_credentials(self):
         """Create user ec2 credentials."""
         resp = self.non_admin_users_client.create_user_ec2_credentials(
-            self.creds.credentials.user_id,
-            tenant_id=self.creds.credentials.tenant_id)["credential"]
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
         access = resp['access']
         self.addCleanup(
             self.non_admin_users_client.delete_user_ec2_credentials,
-            self.creds.credentials.user_id, access)
+            self.creds.user_id, access)
         self.assertNotEmpty(resp['access'])
         self.assertNotEmpty(resp['secret'])
-        self.assertEqual(self.creds.credentials.user_id, resp['user_id'])
-        self.assertEqual(self.creds.credentials.tenant_id, resp['tenant_id'])
+        self.assertEqual(self.creds.user_id, resp['user_id'])
+        self.assertEqual(self.creds.tenant_id, resp['tenant_id'])
 
     @test.idempotent_id('9e2ea42f-0a4f-468c-a768-51859ce492e0')
     def test_list_ec2_credentials(self):
@@ -54,24 +54,24 @@
         fetched_creds = []
         # create first ec2 credentials
         creds1 = self.non_admin_users_client.create_user_ec2_credentials(
-            self.creds.credentials.user_id,
-            tenant_id=self.creds.credentials.tenant_id)["credential"]
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
         created_creds.append(creds1['access'])
         # create second ec2 credentials
         creds2 = self.non_admin_users_client.create_user_ec2_credentials(
-            self.creds.credentials.user_id,
-            tenant_id=self.creds.credentials.tenant_id)["credential"]
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
         created_creds.append(creds2['access'])
         # add credentials to be cleaned up
         self.addCleanup(
             self.non_admin_users_client.delete_user_ec2_credentials,
-            self.creds.credentials.user_id, creds1['access'])
+            self.creds.user_id, creds1['access'])
         self.addCleanup(
             self.non_admin_users_client.delete_user_ec2_credentials,
-            self.creds.credentials.user_id, creds2['access'])
+            self.creds.user_id, creds2['access'])
         # get the list of user ec2 credentials
         resp = self.non_admin_users_client.list_user_ec2_credentials(
-            self.creds.credentials.user_id)["credentials"]
+            self.creds.user_id)["credentials"]
         fetched_creds = [cred['access'] for cred in resp]
         # created credentials should be in a fetched list
         missing = [cred for cred in created_creds
@@ -84,14 +84,14 @@
     def test_show_ec2_credentials(self):
         """Get the definite user ec2 credentials."""
         resp = self.non_admin_users_client.create_user_ec2_credentials(
-            self.creds.credentials.user_id,
-            tenant_id=self.creds.credentials.tenant_id)["credential"]
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
         self.addCleanup(
             self.non_admin_users_client.delete_user_ec2_credentials,
-            self.creds.credentials.user_id, resp['access'])
+            self.creds.user_id, resp['access'])
 
         ec2_creds = self.non_admin_users_client.show_user_ec2_credentials(
-            self.creds.credentials.user_id, resp['access']
+            self.creds.user_id, resp['access']
         )["credential"]
         for key in ['access', 'secret', 'user_id', 'tenant_id']:
             self.assertEqual(ec2_creds[key], resp[key])
@@ -100,13 +100,13 @@
     def test_delete_ec2_credentials(self):
         """Delete user ec2 credentials."""
         resp = self.non_admin_users_client.create_user_ec2_credentials(
-            self.creds.credentials.user_id,
-            tenant_id=self.creds.credentials.tenant_id)["credential"]
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
         access = resp['access']
         self.non_admin_users_client.delete_user_ec2_credentials(
-            self.creds.credentials.user_id, access)
+            self.creds.user_id, access)
         self.assertRaises(
             lib_exc.NotFound,
             self.non_admin_users_client.show_user_ec2_credentials,
-            self.creds.credentials.user_id,
+            self.creds.user_id,
             access)
diff --git a/tempest/api/identity/v2/test_tenants.py b/tempest/api/identity/v2/test_tenants.py
index b742e69..cc6de47 100644
--- a/tempest/api/identity/v2/test_tenants.py
+++ b/tempest/api/identity/v2/test_tenants.py
@@ -24,7 +24,7 @@
 
     @test.idempotent_id('ecae2459-243d-4ba1-ad02-65f15dc82b78')
     def test_list_tenants_returns_only_authorized_tenants(self):
-        alt_tenant_name = self.alt_manager.credentials.credentials.tenant_name
+        alt_tenant_name = self.alt_manager.credentials.tenant_name
         resp = self.non_admin_tenants_client.list_tenants()
 
         # check that user can see only that tenants that he presents in so user
diff --git a/tempest/api/identity/v2/test_tokens.py b/tempest/api/identity/v2/test_tokens.py
index 3b508f4..bdca1e0 100644
--- a/tempest/api/identity/v2/test_tokens.py
+++ b/tempest/api/identity/v2/test_tokens.py
@@ -43,8 +43,8 @@
         self.assertGreater(expires_at, now)
 
         self.assertEqual(body['token']['tenant']['id'],
-                         creds.credentials.tenant_id)
+                         creds.tenant_id)
         self.assertEqual(body['token']['tenant']['name'],
                          tenant_name)
 
-        self.assertEqual(body['user']['id'], creds.credentials.user_id)
+        self.assertEqual(body['user']['id'], creds.user_id)
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 79f2576..4833f9e 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -46,9 +46,9 @@
             client.auth_provider.clear_auth()
             client.auth_provider.set_auth()
 
-        old_pass = self.creds.credentials.password
+        old_pass = self.creds.password
         new_pass = data_utils.rand_password()
-        user_id = self.creds.credentials.user_id
+        user_id = self.creds.user_id
         # to change password back. important for allow_tenant_isolation = false
         self.addCleanup(_restore_password, self.non_admin_users_client,
                         user_id, old_pass=old_pass, new_pass=new_pass)
diff --git a/tempest/api/identity/v3/test_projects.py b/tempest/api/identity/v3/test_projects.py
index 1574ab7..26cb90b 100644
--- a/tempest/api/identity/v3/test_projects.py
+++ b/tempest/api/identity/v3/test_projects.py
@@ -25,7 +25,7 @@
     @test.idempotent_id('86128d46-e170-4644-866a-cc487f699e1d')
     def test_list_projects_returns_only_authorized_projects(self):
         alt_project_name =\
-            self.alt_manager.credentials.credentials.project_name
+            self.alt_manager.credentials.project_name
         resp = self.non_admin_users_client.list_user_projects(
             self.os.credentials.user_id)
 
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 76b46c0..c92e750 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -46,9 +46,9 @@
             client.auth_provider.clear_auth()
             client.auth_provider.set_auth()
 
-        old_pass = self.creds.credentials.password
+        old_pass = self.creds.password
         new_pass = data_utils.rand_password()
-        user_id = self.creds.credentials.user_id
+        user_id = self.creds.user_id
         # to change password back. important for allow_tenant_isolation = false
         self.addCleanup(_restore_password, self.non_admin_users_client,
                         user_id, old_pass=old_pass, new_pass=new_pass)
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index bd04c0d..3fefc81 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -93,12 +93,14 @@
     @classmethod
     def setup_clients(cls):
         super(BaseV1ImageMembersTest, cls).setup_clients()
+        cls.image_member_client = cls.os.image_member_client
+        cls.alt_image_member_client = cls.os_alt.image_member_client
         cls.alt_img_cli = cls.os_alt.image_client
 
     @classmethod
     def resource_setup(cls):
         super(BaseV1ImageMembersTest, cls).resource_setup()
-        cls.alt_tenant_id = cls.alt_img_cli.tenant_id
+        cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
 
     def _create_image(self):
         image_file = moves.cStringIO(data_utils.random_bytes())
@@ -123,6 +125,9 @@
     def setup_clients(cls):
         super(BaseV2ImageTest, cls).setup_clients()
         cls.client = cls.os.image_client_v2
+        cls.namespaces_client = cls.os.namespaces_client
+        cls.resource_types_client = cls.os.resource_types_client
+        cls.schemas_client = cls.os.schemas_client
 
 
 class BaseV2MemberImageTest(BaseV2ImageTest):
@@ -132,13 +137,14 @@
     @classmethod
     def setup_clients(cls):
         super(BaseV2MemberImageTest, cls).setup_clients()
-        cls.os_img_client = cls.os.image_client_v2
+        cls.image_member_client = cls.os.image_member_client_v2
+        cls.alt_image_member_client = cls.os_alt.image_member_client_v2
         cls.alt_img_client = cls.os_alt.image_client_v2
 
     @classmethod
     def resource_setup(cls):
         super(BaseV2MemberImageTest, cls).resource_setup()
-        cls.alt_tenant_id = cls.alt_img_client.tenant_id
+        cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
 
     def _list_image_ids_as_alt(self):
         image_list = self.alt_img_client.list_images()['images']
@@ -147,11 +153,11 @@
 
     def _create_image(self):
         name = data_utils.rand_name('image')
-        image = self.os_img_client.create_image(name=name,
-                                                container_format='bare',
-                                                disk_format='raw')
+        image = self.client.create_image(name=name,
+                                         container_format='bare',
+                                         disk_format='raw')
         image_id = image['id']
-        self.addCleanup(self.os_img_client.delete_image, image_id)
+        self.addCleanup(self.client.delete_image, image_id)
         return image_id
 
 
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index eb6969b..94edb6c 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -22,8 +22,8 @@
     @test.idempotent_id('1d6ef640-3a20-4c84-8710-d95828fdb6ad')
     def test_add_image_member(self):
         image = self._create_image()
-        self.client.add_member(self.alt_tenant_id, image)
-        body = self.client.list_image_members(image)
+        self.image_member_client.create_image_member(image, self.alt_tenant_id)
+        body = self.image_member_client.list_image_members(image)
         members = body['members']
         members = map(lambda x: x['member_id'], members)
         self.assertIn(self.alt_tenant_id, members)
@@ -33,10 +33,12 @@
     @test.idempotent_id('6a5328a5-80e8-4b82-bd32-6c061f128da9')
     def test_get_shared_images(self):
         image = self._create_image()
-        self.client.add_member(self.alt_tenant_id, image)
+        self.image_member_client.create_image_member(image, self.alt_tenant_id)
         share_image = self._create_image()
-        self.client.add_member(self.alt_tenant_id, share_image)
-        body = self.client.list_shared_images(self.alt_tenant_id)
+        self.image_member_client.create_image_member(share_image,
+                                                     self.alt_tenant_id)
+        body = self.image_member_client.list_shared_images(
+            self.alt_tenant_id)
         images = body['shared_images']
         images = map(lambda x: x['image_id'], images)
         self.assertIn(share_image, images)
@@ -45,8 +47,10 @@
     @test.idempotent_id('a76a3191-8948-4b44-a9d6-4053e5f2b138')
     def test_remove_member(self):
         image_id = self._create_image()
-        self.client.add_member(self.alt_tenant_id, image_id)
-        self.client.delete_member(self.alt_tenant_id, image_id)
-        body = self.client.list_image_members(image_id)
+        self.image_member_client.create_image_member(image_id,
+                                                     self.alt_tenant_id)
+        self.image_member_client.delete_image_member(image_id,
+                                                     self.alt_tenant_id)
+        body = self.image_member_client.list_image_members(image_id)
         members = body['members']
         self.assertEqual(0, len(members), str(members))
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
index 16a4ba6..2538781 100644
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -25,16 +25,18 @@
     def test_add_member_with_non_existing_image(self):
         # Add member with non existing image.
         non_exist_image = data_utils.rand_uuid()
-        self.assertRaises(lib_exc.NotFound, self.client.add_member,
-                          self.alt_tenant_id, non_exist_image)
+        self.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.create_image_member,
+                          non_exist_image, self.alt_tenant_id)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('e1559f05-b667-4f1b-a7af-518b52dc0c0f')
     def test_delete_member_with_non_existing_image(self):
         # Delete member with non existing image.
         non_exist_image = data_utils.rand_uuid()
-        self.assertRaises(lib_exc.NotFound, self.client.delete_member,
-                          self.alt_tenant_id, non_exist_image)
+        self.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.delete_image_member,
+                          non_exist_image, self.alt_tenant_id)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('f5720333-dd69-4194-bb76-d2f048addd56')
@@ -42,8 +44,9 @@
         # Delete member with non existing tenant.
         image_id = self._create_image()
         non_exist_tenant = data_utils.rand_uuid_hex()
-        self.assertRaises(lib_exc.NotFound, self.client.delete_member,
-                          non_exist_tenant, image_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.delete_image_member,
+                          image_id, non_exist_tenant)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('f25f89e4-0b6c-453b-a853-1f80b9d7ef26')
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 6d5559d..59ac646 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -16,6 +16,7 @@
 from six import moves
 
 from tempest.api.image import base
+from tempest.common import image as common_image
 from tempest.common.utils import data_utils
 from tempest.common import waiters
 from tempest import config
@@ -306,7 +307,8 @@
     @test.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
     def test_list_image_metadata(self):
         # All metadata key/value pairs for an image should be returned
-        resp_metadata = self.client.check_image(self.image_id)
+        resp = self.client.check_image(self.image_id)
+        resp_metadata = common_image.get_image_meta_from_headers(resp)
         expected = {'key1': 'value1'}
         self.assertEqual(expected, resp_metadata['properties'])
 
@@ -314,12 +316,14 @@
     def test_update_image_metadata(self):
         # The metadata for the image should match the updated values
         req_metadata = {'key1': 'alt1', 'key2': 'value2'}
-        metadata = self.client.check_image(self.image_id)
+        resp = self.client.check_image(self.image_id)
+        metadata = common_image.get_image_meta_from_headers(resp)
         self.assertEqual(metadata['properties'], {'key1': 'value1'})
         metadata['properties'].update(req_metadata)
         metadata = self.client.update_image(
             self.image_id, properties=metadata['properties'])['image']
 
-        resp_metadata = self.client.check_image(self.image_id)
+        resp = self.client.check_image(self.image_id)
+        resp_metadata = common_image.get_image_meta_from_headers(resp)
         expected = {'key1': 'alt1', 'key2': 'value2'}
         self.assertEqual(expected, resp_metadata['properties'])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 04582c6..1fb9c52 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -254,12 +254,12 @@
     def test_get_image_schema(self):
         # Test to get image schema
         schema = "image"
-        body = self.client.show_schema(schema)
+        body = self.schemas_client.show_schema(schema)
         self.assertEqual("image", body['name'])
 
     @test.idempotent_id('25c8d7b2-df21-460f-87ac-93130bcdc684')
     def test_get_images_schema(self):
         # Test to get images schema
         schema = "images"
-        body = self.client.show_schema(schema)
+        body = self.schemas_client.show_schema(schema)
         self.assertEqual("images", body['name'])
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index bb73318..fe8dd65 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -19,17 +19,17 @@
     @test.idempotent_id('5934c6ea-27dc-4d6e-9421-eeb5e045494a')
     def test_image_share_accept(self):
         image_id = self._create_image()
-        member = self.os_img_client.create_image_member(
+        member = self.image_member_client.create_image_member(
             image_id, member=self.alt_tenant_id)
         self.assertEqual(member['member_id'], self.alt_tenant_id)
         self.assertEqual(member['image_id'], image_id)
         self.assertEqual(member['status'], 'pending')
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
-        self.alt_img_client.update_image_member(image_id,
-                                                self.alt_tenant_id,
-                                                status='accepted')
+        self.alt_image_member_client.update_image_member(image_id,
+                                                         self.alt_tenant_id,
+                                                         status='accepted')
         self.assertIn(image_id, self._list_image_ids_as_alt())
-        body = self.os_img_client.list_image_members(image_id)
+        body = self.image_member_client.list_image_members(image_id)
         members = body['members']
         member = members[0]
         self.assertEqual(len(members), 1, str(members))
@@ -40,29 +40,29 @@
     @test.idempotent_id('d9e83e5f-3524-4b38-a900-22abcb26e90e')
     def test_image_share_reject(self):
         image_id = self._create_image()
-        member = self.os_img_client.create_image_member(
+        member = self.image_member_client.create_image_member(
             image_id, member=self.alt_tenant_id)
         self.assertEqual(member['member_id'], self.alt_tenant_id)
         self.assertEqual(member['image_id'], image_id)
         self.assertEqual(member['status'], 'pending')
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
-        self.alt_img_client.update_image_member(image_id,
-                                                self.alt_tenant_id,
-                                                status='rejected')
+        self.alt_image_member_client.update_image_member(image_id,
+                                                         self.alt_tenant_id,
+                                                         status='rejected')
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
 
     @test.idempotent_id('a6ee18b9-4378-465e-9ad9-9a6de58a3287')
     def test_get_image_member(self):
         image_id = self._create_image()
-        self.os_img_client.create_image_member(
+        self.image_member_client.create_image_member(
             image_id, member=self.alt_tenant_id)
-        self.alt_img_client.update_image_member(image_id,
-                                                self.alt_tenant_id,
-                                                status='accepted')
+        self.alt_image_member_client.update_image_member(image_id,
+                                                         self.alt_tenant_id,
+                                                         status='accepted')
 
         self.assertIn(image_id, self._list_image_ids_as_alt())
-        member = self.os_img_client.show_image_member(image_id,
-                                                      self.alt_tenant_id)
+        member = self.image_member_client.show_image_member(
+            image_id, self.alt_tenant_id)
         self.assertEqual(self.alt_tenant_id, member['member_id'])
         self.assertEqual(image_id, member['image_id'])
         self.assertEqual('accepted', member['status'])
@@ -70,38 +70,40 @@
     @test.idempotent_id('72989bc7-2268-48ed-af22-8821e835c914')
     def test_remove_image_member(self):
         image_id = self._create_image()
-        self.os_img_client.create_image_member(
+        self.image_member_client.create_image_member(
             image_id, member=self.alt_tenant_id)
-        self.alt_img_client.update_image_member(image_id,
-                                                self.alt_tenant_id,
-                                                status='accepted')
+        self.alt_image_member_client.update_image_member(image_id,
+                                                         self.alt_tenant_id,
+                                                         status='accepted')
 
         self.assertIn(image_id, self._list_image_ids_as_alt())
-        self.os_img_client.delete_image_member(image_id, self.alt_tenant_id)
+        self.image_member_client.delete_image_member(image_id,
+                                                     self.alt_tenant_id)
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
 
     @test.idempotent_id('634dcc3f-f6e2-4409-b8fd-354a0bb25d83')
     def test_get_image_member_schema(self):
-        body = self.os_img_client.show_schema("member")
+        body = self.schemas_client.show_schema("member")
         self.assertEqual("member", body['name'])
 
     @test.idempotent_id('6ae916ef-1052-4e11-8d36-b3ae14853cbb')
     def test_get_image_members_schema(self):
-        body = self.os_img_client.show_schema("members")
+        body = self.schemas_client.show_schema("members")
         self.assertEqual("members", body['name'])
 
     @test.idempotent_id('cb961424-3f68-4d21-8e36-30ad66fb6bfb')
     def test_get_private_image(self):
         image_id = self._create_image()
-        member = self.os_img_client.create_image_member(
+        member = self.image_member_client.create_image_member(
             image_id, member=self.alt_tenant_id)
         self.assertEqual(member['member_id'], self.alt_tenant_id)
         self.assertEqual(member['image_id'], image_id)
         self.assertEqual(member['status'], 'pending')
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
-        self.alt_img_client.update_image_member(image_id,
-                                                self.alt_tenant_id,
-                                                status='accepted')
+        self.alt_image_member_client.update_image_member(image_id,
+                                                         self.alt_tenant_id,
+                                                         status='accepted')
         self.assertIn(image_id, self._list_image_ids_as_alt())
-        self.os_img_client.delete_image_member(image_id, self.alt_tenant_id)
+        self.image_member_client.delete_image_member(image_id,
+                                                     self.alt_tenant_id)
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
index 388eb08..fa29a92 100644
--- a/tempest/api/image/v2/test_images_member_negative.py
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -21,11 +21,11 @@
     @test.idempotent_id('b79efb37-820d-4cf0-b54c-308b00cf842c')
     def test_image_share_invalid_status(self):
         image_id = self._create_image()
-        member = self.os_img_client.create_image_member(
+        member = self.image_member_client.create_image_member(
             image_id, member=self.alt_tenant_id)
         self.assertEqual(member['status'], 'pending')
         self.assertRaises(lib_exc.BadRequest,
-                          self.alt_img_client.update_image_member,
+                          self.alt_image_member_client.update_image_member,
                           image_id, self.alt_tenant_id,
                           status='notavalidstatus')
 
@@ -33,11 +33,11 @@
     @test.idempotent_id('27002f74-109e-4a37-acd0-f91cd4597967')
     def test_image_share_owner_cannot_accept(self):
         image_id = self._create_image()
-        member = self.os_img_client.create_image_member(
+        member = self.image_member_client.create_image_member(
             image_id, member=self.alt_tenant_id)
         self.assertEqual(member['status'], 'pending')
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
         self.assertRaises(lib_exc.Forbidden,
-                          self.os_img_client.update_image_member,
+                          self.image_member_client.update_image_member,
                           image_id, self.alt_tenant_id, status='accepted')
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/image/v2/test_images_metadefs_namespaces.py b/tempest/api/image/v2/test_images_metadefs_namespaces.py
index da0f4c1..6fced00 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespaces.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespaces.py
@@ -26,43 +26,47 @@
     @test.idempotent_id('319b765e-7f3d-4b3d-8b37-3ca3876ee768')
     def test_basic_metadata_definition_namespaces(self):
         # get the available resource types and use one resource_type
-        body = self.client.list_resource_types()
+        body = self.resource_types_client.list_resource_types()
         resource_name = body['resource_types'][0]['name']
         name = [{'name': resource_name}]
         namespace_name = data_utils.rand_name('namespace')
         # create the metadef namespace
-        body = self.client.create_namespace(namespace=namespace_name,
-                                            visibility='public',
-                                            description='Tempest',
-                                            display_name=namespace_name,
-                                            resource_type_associations=name,
-                                            protected=True)
+        body = self.namespaces_client.create_namespace(
+            namespace=namespace_name,
+            visibility='public',
+            description='Tempest',
+            display_name=namespace_name,
+            resource_type_associations=name,
+            protected=True)
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self._cleanup_namespace, namespace_name)
         # get namespace details
-        body = self.client.show_namespace(namespace_name)
+        body = self.namespaces_client.show_namespace(namespace_name)
         self.assertEqual(namespace_name, body['namespace'])
         self.assertEqual('public', body['visibility'])
         # unable to delete protected namespace
-        self.assertRaises(lib_exc.Forbidden, self.client.delete_namespace,
+        self.assertRaises(lib_exc.Forbidden,
+                          self.namespaces_client.delete_namespace,
                           namespace_name)
         # update the visibility to private and protected to False
-        body = self.client.update_namespace(namespace=namespace_name,
-                                            description='Tempest',
-                                            visibility='private',
-                                            display_name=namespace_name,
-                                            protected=False)
+        body = self.namespaces_client.update_namespace(
+            namespace=namespace_name,
+            description='Tempest',
+            visibility='private',
+            display_name=namespace_name,
+            protected=False)
         self.assertEqual('private', body['visibility'])
         self.assertEqual(False, body['protected'])
         # now able to delete the non-protected namespace
-        self.client.delete_namespace(namespace_name)
+        self.namespaces_client.delete_namespace(namespace_name)
 
     def _cleanup_namespace(self, namespace_name):
-        body = self.client.show_namespace(namespace_name)
+        body = self.namespaces_client.show_namespace(namespace_name)
         self.assertEqual(namespace_name, body['namespace'])
-        body = self.client.update_namespace(namespace=namespace_name,
-                                            description='Tempest',
-                                            visibility='private',
-                                            display_name=namespace_name,
-                                            protected=False)
-        self.client.delete_namespace(namespace_name)
+        body = self.namespaces_client.update_namespace(
+            namespace=namespace_name,
+            description='Tempest',
+            visibility='private',
+            display_name=namespace_name,
+            protected=False)
+        self.namespaces_client.delete_namespace(namespace_name)
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 00acc7d..5e1c20b 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -63,7 +63,7 @@
             extra_specs = {spec_key_with_prefix: backend_name_key}
         else:
             extra_specs = {spec_key_without_prefix: backend_name_key}
-        self.type = self.volume_types_client.create_volume_type(
+        self.type = self.admin_volume_types_client.create_volume_type(
             name=type_name, extra_specs=extra_specs)['volume_type']
         self.volume_type_id_list.append(self.type['id'])
 
@@ -95,7 +95,7 @@
         # volume types deletion
         volume_type_id_list = getattr(cls, 'volume_type_id_list', [])
         for volume_type_id in volume_type_id_list:
-            cls.volume_types_client.delete_volume_type(volume_type_id)
+            cls.admin_volume_types_client.delete_volume_type(volume_type_id)
 
         super(VolumeMultiBackendV2Test, cls).resource_cleanup()
 
diff --git a/tempest/api/volume/admin/test_qos.py b/tempest/api/volume/admin/test_qos.py
index 722a39a..68e57f5 100644
--- a/tempest/api/volume/admin/test_qos.py
+++ b/tempest/api/volume/admin/test_qos.py
@@ -43,27 +43,27 @@
         for key in ['name', 'consumer']:
             self.assertEqual(qos[key], body[key])
 
-        self.volume_qos_client.delete_qos(body['id'])
-        self.volume_qos_client.wait_for_resource_deletion(body['id'])
+        self.admin_volume_qos_client.delete_qos(body['id'])
+        self.admin_volume_qos_client.wait_for_resource_deletion(body['id'])
 
         # validate the deletion
-        list_qos = self.volume_qos_client.list_qos()['qos_specs']
+        list_qos = self.admin_volume_qos_client.list_qos()['qos_specs']
         self.assertNotIn(body, list_qos)
 
     def _create_test_volume_type(self):
         vol_type_name = utils.rand_name("volume-type")
-        vol_type = self.volume_types_client.create_volume_type(
+        vol_type = self.admin_volume_types_client.create_volume_type(
             name=vol_type_name)['volume_type']
-        self.addCleanup(self.volume_types_client.delete_volume_type,
+        self.addCleanup(self.admin_volume_types_client.delete_volume_type,
                         vol_type['id'])
         return vol_type
 
     def _test_associate_qos(self, vol_type_id):
-        self.volume_qos_client.associate_qos(
+        self.admin_volume_qos_client.associate_qos(
             self.created_qos['id'], vol_type_id)
 
     def _test_get_association_qos(self):
-        body = self.volume_qos_client.show_association_qos(
+        body = self.admin_volume_qos_client.show_association_qos(
             self.created_qos['id'])['qos_associations']
 
         associations = []
@@ -99,7 +99,7 @@
     @test.idempotent_id('7aa214cc-ac1a-4397-931f-3bb2e83bb0fd')
     def test_get_qos(self):
         """Tests the detail of a given qos-specs"""
-        body = self.volume_qos_client.show_qos(
+        body = self.admin_volume_qos_client.show_qos(
             self.created_qos['id'])['qos_specs']
         self.assertEqual(self.qos_name, body['name'])
         self.assertEqual(self.qos_consumer, body['consumer'])
@@ -107,28 +107,29 @@
     @test.idempotent_id('75e04226-bcf7-4595-a34b-fdf0736f38fc')
     def test_list_qos(self):
         """Tests the list of all qos-specs"""
-        body = self.volume_qos_client.list_qos()['qos_specs']
+        body = self.admin_volume_qos_client.list_qos()['qos_specs']
         self.assertIn(self.created_qos, body)
 
     @test.idempotent_id('ed00fd85-4494-45f2-8ceb-9e2048919aed')
     def test_set_unset_qos_key(self):
         """Test the addition of a specs key to qos-specs"""
         args = {'iops_bytes': '500'}
-        body = self.volume_qos_client.set_qos_key(
+        body = self.admin_volume_qos_client.set_qos_key(
             self.created_qos['id'],
             iops_bytes='500')['qos_specs']
         self.assertEqual(args, body)
-        body = self.volume_qos_client.show_qos(
+        body = self.admin_volume_qos_client.show_qos(
             self.created_qos['id'])['qos_specs']
         self.assertEqual(args['iops_bytes'], body['specs']['iops_bytes'])
 
         # test the deletion of a specs key from qos-specs
         keys = ['iops_bytes']
-        self.volume_qos_client.unset_qos_key(self.created_qos['id'], keys)
+        self.admin_volume_qos_client.unset_qos_key(self.created_qos['id'],
+                                                   keys)
         operation = 'qos-key-unset'
-        self.volume_qos_client.wait_for_qos_operations(self.created_qos['id'],
-                                                       operation, keys)
-        body = self.volume_qos_client.show_qos(
+        self.admin_volume_qos_client.wait_for_qos_operations(
+            self.created_qos['id'], operation, keys)
+        body = self.admin_volume_qos_client.show_qos(
             self.created_qos['id'])['qos_specs']
         self.assertNotIn(keys[0], body['specs'])
 
@@ -158,21 +159,20 @@
             self.assertIn(vol_type[i]['id'], associations)
 
         # disassociate a volume-type with qos-specs
-        self.volume_qos_client.disassociate_qos(
+        self.admin_volume_qos_client.disassociate_qos(
             self.created_qos['id'], vol_type[0]['id'])
         operation = 'disassociate'
-        self.volume_qos_client.wait_for_qos_operations(self.created_qos['id'],
-                                                       operation,
-                                                       vol_type[0]['id'])
+        self.admin_volume_qos_client.wait_for_qos_operations(
+            self.created_qos['id'], operation, vol_type[0]['id'])
         associations = self._test_get_association_qos()
         self.assertNotIn(vol_type[0]['id'], associations)
 
         # disassociate all volume-types from qos-specs
-        self.volume_qos_client.disassociate_all_qos(
+        self.admin_volume_qos_client.disassociate_all_qos(
             self.created_qos['id'])
         operation = 'disassociate-all'
-        self.volume_qos_client.wait_for_qos_operations(self.created_qos['id'],
-                                                       operation)
+        self.admin_volume_qos_client.wait_for_qos_operations(
+            self.created_qos['id'], operation)
         associations = self._test_get_association_qos()
         self.assertEmpty(associations)
 
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index b28488a..b58c525 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -21,7 +21,7 @@
 
     @test.idempotent_id('d5f3efa2-6684-4190-9ced-1c2f526352ad')
     def test_list_hosts(self):
-        hosts = self.hosts_client.list_hosts()['hosts']
+        hosts = self.admin_hosts_client.list_hosts()['hosts']
         self.assertTrue(len(hosts) >= 2, "No. of hosts are < 2,"
                         "response of list hosts is: % s" % hosts)
 
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index cf05f5f..cd24d17 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -13,9 +13,9 @@
 #    under the License.
 
 import six
-
 from tempest.api.volume import base
 from tempest.common.utils import data_utils
+from tempest.common import waiters
 from tempest import test
 
 QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes']
@@ -25,10 +25,13 @@
 class BaseVolumeQuotasAdminV2TestJSON(base.BaseVolumeAdminTest):
     force_tenant_isolation = True
 
+    credentials = ['primary', 'alt', 'admin']
+
     @classmethod
     def setup_credentials(cls):
         super(BaseVolumeQuotasAdminV2TestJSON, cls).setup_credentials()
         cls.demo_tenant_id = cls.os.credentials.tenant_id
+        cls.alt_client = cls.os_alt.volumes_client
 
     def _delete_volume(self, volume_id):
         # Delete the specified volume using admin credentials
@@ -37,14 +40,14 @@
 
     @test.idempotent_id('59eada70-403c-4cef-a2a3-a8ce2f1b07a0')
     def test_list_quotas(self):
-        quotas = (self.quotas_client.show_quota_set(self.demo_tenant_id)
+        quotas = (self.admin_quotas_client.show_quota_set(self.demo_tenant_id)
                   ['quota_set'])
         for key in QUOTA_KEYS:
             self.assertIn(key, quotas)
 
     @test.idempotent_id('2be020a2-5fdd-423d-8d35-a7ffbc36e9f7')
     def test_list_default_quotas(self):
-        quotas = self.quotas_client.show_default_quota_set(
+        quotas = self.admin_quotas_client.show_default_quota_set(
             self.demo_tenant_id)['quota_set']
         for key in QUOTA_KEYS:
             self.assertIn(key, quotas)
@@ -52,21 +55,21 @@
     @test.idempotent_id('3d45c99e-cc42-4424-a56e-5cbd212b63a6')
     def test_update_all_quota_resources_for_tenant(self):
         # Admin can update all the resource quota limits for a tenant
-        default_quota_set = self.quotas_client.show_default_quota_set(
+        default_quota_set = self.admin_quotas_client.show_default_quota_set(
             self.demo_tenant_id)['quota_set']
         new_quota_set = {'gigabytes': 1009,
                          'volumes': 11,
                          'snapshots': 11}
 
         # Update limits for all quota resources
-        quota_set = self.quotas_client.update_quota_set(
+        quota_set = self.admin_quotas_client.update_quota_set(
             self.demo_tenant_id,
             **new_quota_set)['quota_set']
 
         cleanup_quota_set = dict(
             (k, v) for k, v in six.iteritems(default_quota_set)
             if k in QUOTA_KEYS)
-        self.addCleanup(self.quotas_client.update_quota_set,
+        self.addCleanup(self.admin_quotas_client.update_quota_set,
                         self.demo_tenant_id, **cleanup_quota_set)
         # test that the specific values we set are actually in
         # the final result. There is nothing here that ensures there
@@ -75,7 +78,7 @@
 
     @test.idempotent_id('18c51ae9-cb03-48fc-b234-14a19374dbed')
     def test_show_quota_usage(self):
-        quota_usage = self.quotas_client.show_quota_usage(
+        quota_usage = self.admin_quotas_client.show_quota_usage(
             self.os_adm.credentials.tenant_id)['quota_set']
         for key in QUOTA_KEYS:
             self.assertIn(key, quota_usage)
@@ -84,13 +87,13 @@
 
     @test.idempotent_id('ae8b6091-48ad-4bfa-a188-bbf5cc02115f')
     def test_quota_usage(self):
-        quota_usage = self.quotas_client.show_quota_usage(
+        quota_usage = self.admin_quotas_client.show_quota_usage(
             self.demo_tenant_id)['quota_set']
 
         volume = self.create_volume()
         self.addCleanup(self._delete_volume, volume['id'])
 
-        new_quota_usage = self.quotas_client.show_quota_usage(
+        new_quota_usage = self.admin_quotas_client.show_quota_usage(
             self.demo_tenant_id)['quota_set']
 
         self.assertEqual(quota_usage['volumes']['in_use'] + 1,
@@ -109,18 +112,66 @@
                                                      description=description)
         project_id = project['id']
         self.addCleanup(self.identity_utils.delete_project, project_id)
-        quota_set_default = self.quotas_client.show_default_quota_set(
+        quota_set_default = self.admin_quotas_client.show_default_quota_set(
             project_id)['quota_set']
         volume_default = quota_set_default['volumes']
 
-        self.quotas_client.update_quota_set(project_id,
-                                            volumes=(int(volume_default) + 5))
+        self.admin_quotas_client.update_quota_set(
+            project_id, volumes=(int(volume_default) + 5))
 
-        self.quotas_client.delete_quota_set(project_id)
-        quota_set_new = (self.quotas_client.show_quota_set(project_id)
+        self.admin_quotas_client.delete_quota_set(project_id)
+        quota_set_new = (self.admin_quotas_client.show_quota_set(project_id)
                          ['quota_set'])
         self.assertEqual(volume_default, quota_set_new['volumes'])
 
+    @test.idempotent_id('8911036f-9d54-4720-80cc-a1c9796a8805')
+    def test_quota_usage_after_volume_transfer(self):
+        # Create a volume for transfer
+        volume = self.create_volume()
+        self.addCleanup(self._delete_volume, volume['id'])
+
+        # List of tenants quota usage pre-transfer
+        primary_quota = self.admin_quotas_client.show_quota_usage(
+            self.demo_tenant_id)['quota_set']
+
+        alt_quota = self.admin_quotas_client.show_quota_usage(
+            self.alt_client.tenant_id)['quota_set']
+
+        # Creates a volume transfer
+        transfer = self.volumes_client.create_volume_transfer(
+            volume_id=volume['id'])['transfer']
+        transfer_id = transfer['id']
+        auth_key = transfer['auth_key']
+
+        # Accepts a volume transfer
+        self.alt_client.accept_volume_transfer(
+            transfer_id, auth_key=auth_key)['transfer']
+
+        # Verify volume transferred is available
+        waiters.wait_for_volume_status(
+            self.alt_client, volume['id'], 'available')
+
+        # List of tenants quota usage post transfer
+        new_primary_quota = self.admin_quotas_client.show_quota_usage(
+            self.demo_tenant_id)['quota_set']
+
+        new_alt_quota = self.admin_quotas_client.show_quota_usage(
+            self.alt_client.tenant_id)['quota_set']
+
+        # Verify tenants quota usage was updated
+        self.assertEqual(primary_quota['volumes']['in_use'] -
+                         new_primary_quota['volumes']['in_use'],
+                         new_alt_quota['volumes']['in_use'] -
+                         alt_quota['volumes']['in_use'])
+
+        self.assertEqual(alt_quota['gigabytes']['in_use'] +
+                         volume['size'],
+                         new_alt_quota['gigabytes']['in_use'])
+
+        self.assertEqual(primary_quota['gigabytes']['in_use'] -
+                         volume['size'],
+                         new_primary_quota['gigabytes']['in_use'])
+
 
 class VolumeQuotasAdminV1TestJSON(BaseVolumeQuotasAdminV2TestJSON):
     _api_version = 1
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index a43ee8e..dde8915 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -38,7 +38,7 @@
 
         # NOTE(gfidente): no need to restore original quota set
         # after the tests as they only work with dynamic credentials.
-        cls.quotas_client.update_quota_set(
+        cls.admin_quotas_client.update_quota_set(
             cls.demo_tenant_id,
             **cls.shared_quota_set)
 
@@ -58,12 +58,12 @@
         # NOTE(gfidente): quota set needs to be changed for this test
         # or we may be limited by the volumes or snaps quota number, not by
         # actual gigs usage; next line ensures shared set is restored.
-        self.addCleanup(self.quotas_client.update_quota_set,
+        self.addCleanup(self.admin_quotas_client.update_quota_set,
                         self.demo_tenant_id,
                         **self.shared_quota_set)
         new_quota_set = {'gigabytes': self.default_volume_size,
                          'volumes': 2, 'snapshots': 1}
-        self.quotas_client.update_quota_set(
+        self.admin_quotas_client.update_quota_set(
             self.demo_tenant_id,
             **new_quota_set)
         self.assertRaises(lib_exc.OverLimit,
diff --git a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
index b7f70ba..1565a8c 100644
--- a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
@@ -44,7 +44,7 @@
 
         # NOTE(gfidente): no need to restore original quota set
         # after the tests as they only work with tenant isolation.
-        cls.quotas_client.update_quota_set(
+        cls.admin_quotas_client.update_quota_set(
             cls.demo_tenant_id,
             **cls.shared_quota_set)
 
@@ -63,12 +63,12 @@
     @test.attr(type='negative')
     @test.idempotent_id('c99a1ca9-6cdf-498d-9fdf-25832babef27')
     def test_quota_volume_gigabytes_snapshots(self):
-        self.addCleanup(self.quotas_client.update_quota_set,
+        self.addCleanup(self.admin_quotas_client.update_quota_set,
                         self.demo_tenant_id,
                         **self.shared_quota_set)
         new_quota_set = {'gigabytes': 2 * self.default_volume_size,
                          'volumes': 1, 'snapshots': 2}
-        self.quotas_client.update_quota_set(
+        self.admin_quotas_client.update_quota_set(
             self.demo_tenant_id,
             **new_quota_set)
         self.assertRaises(lib_exc.OverLimit,
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 6fc6f1c..587dbd2 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -31,7 +31,8 @@
     @test.idempotent_id('9d9b28e3-1b2e-4483-a2cc-24aa0ea1de54')
     def test_volume_type_list(self):
         # List volume types.
-        body = self.volume_types_client.list_volume_types()['volume_types']
+        body = \
+            self.admin_volume_types_client.list_volume_types()['volume_types']
         self.assertIsInstance(body, list)
 
     @test.idempotent_id('c03cc62c-f4e9-4623-91ec-64ce2f9c1260')
@@ -47,11 +48,11 @@
         # Create two volume_types
         for i in range(2):
             vol_type_name = data_utils.rand_name("volume-type")
-            vol_type = self.volume_types_client.create_volume_type(
+            vol_type = self.admin_volume_types_client.create_volume_type(
                 name=vol_type_name,
                 extra_specs=extra_specs)['volume_type']
             volume_types.append(vol_type)
-            self.addCleanup(self.volume_types_client.delete_volume_type,
+            self.addCleanup(self.admin_volume_types_client.delete_volume_type,
                             vol_type['id'])
         params = {self.name_field: vol_name,
                   'volume_type': volume_types[0]['id']}
@@ -97,11 +98,11 @@
         vendor = CONF.volume.vendor_name
         extra_specs = {"storage_protocol": proto,
                        "vendor_name": vendor}
-        body = self.volume_types_client.create_volume_type(
+        body = self.admin_volume_types_client.create_volume_type(
             name=name,
             extra_specs=extra_specs)['volume_type']
         self.assertIn('id', body)
-        self.addCleanup(self.volume_types_client.delete_volume_type,
+        self.addCleanup(self.admin_volume_types_client.delete_volume_type,
                         body['id'])
         self.assertIn('name', body)
         self.assertEqual(body['name'], name,
@@ -109,7 +110,7 @@
                          "to the requested name")
         self.assertTrue(body['id'] is not None,
                         "Field volume_type id is empty or not found.")
-        fetched_volume_type = self.volume_types_client.show_volume_type(
+        fetched_volume_type = self.admin_volume_types_client.show_volume_type(
             body['id'])['volume_type']
         self.assertEqual(name, fetched_volume_type['name'],
                          'The fetched Volume_type is different '
@@ -127,15 +128,16 @@
         provider = "LuksEncryptor"
         control_location = "front-end"
         name = data_utils.rand_name("volume-type")
-        body = self.volume_types_client.create_volume_type(
+        body = self.admin_volume_types_client.create_volume_type(
             name=name)['volume_type']
-        self.addCleanup(self.volume_types_client.delete_volume_type,
+        self.addCleanup(self.admin_volume_types_client.delete_volume_type,
                         body['id'])
 
         # Create encryption type
-        encryption_type = self.volume_types_client.create_encryption_type(
-            body['id'], provider=provider,
-            control_location=control_location)['encryption']
+        encryption_type = \
+            self.admin_volume_types_client.create_encryption_type(
+                body['id'], provider=provider,
+                control_location=control_location)['encryption']
         self.assertIn('volume_type_id', encryption_type)
         self.assertEqual(provider, encryption_type['provider'],
                          "The created encryption_type provider is not equal "
@@ -146,7 +148,7 @@
 
         # Get encryption type
         fetched_encryption_type = (
-            self.volume_types_client.show_encryption_type(
+            self.admin_volume_types_client.show_encryption_type(
                 encryption_type['volume_type_id']))
         self.assertEqual(provider,
                          fetched_encryption_type['provider'],
@@ -158,13 +160,13 @@
                          'different from the created encryption_type')
 
         # Delete encryption type
-        self.volume_types_client.delete_encryption_type(
+        self.admin_volume_types_client.delete_encryption_type(
             encryption_type['volume_type_id'])
         resource = {"id": encryption_type['volume_type_id'],
                     "type": "encryption-type"}
-        self.volume_types_client.wait_for_resource_deletion(resource)
+        self.admin_volume_types_client.wait_for_resource_deletion(resource)
         deleted_encryption_type = (
-            self.volume_types_client.show_encryption_type(
+            self.admin_volume_types_client.show_encryption_type(
                 encryption_type['volume_type_id']))
         self.assertEmpty(deleted_encryption_type)
 
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 502cd86..a45bee0 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -24,23 +24,24 @@
     def resource_setup(cls):
         super(VolumeTypesExtraSpecsV2Test, cls).resource_setup()
         vol_type_name = data_utils.rand_name('Volume-type')
-        cls.volume_type = cls.volume_types_client.create_volume_type(
-            name=vol_type_name)['volume_type']
+        cls.volume_type = \
+            cls.admin_volume_types_client.create_volume_type(
+                name=vol_type_name)['volume_type']
 
     @classmethod
     def resource_cleanup(cls):
-        cls.volume_types_client.delete_volume_type(cls.volume_type['id'])
+        cls.admin_volume_types_client.delete_volume_type(cls.volume_type['id'])
         super(VolumeTypesExtraSpecsV2Test, cls).resource_cleanup()
 
     @test.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60')
     def test_volume_type_extra_specs_list(self):
         # List Volume types extra specs.
         extra_specs = {"spec1": "val1"}
-        body = self.volume_types_client.create_volume_type_extra_specs(
+        body = self.admin_volume_types_client.create_volume_type_extra_specs(
             self.volume_type['id'], extra_specs)['extra_specs']
         self.assertEqual(extra_specs, body,
                          "Volume type extra spec incorrectly created")
-        body = self.volume_types_client.list_volume_types_extra_specs(
+        body = self.admin_volume_types_client.list_volume_types_extra_specs(
             self.volume_type['id'])['extra_specs']
         self.assertIsInstance(body, dict)
         self.assertIn('spec1', body)
@@ -49,13 +50,13 @@
     def test_volume_type_extra_specs_update(self):
         # Update volume type extra specs
         extra_specs = {"spec2": "val1"}
-        body = self.volume_types_client.create_volume_type_extra_specs(
+        body = self.admin_volume_types_client.create_volume_type_extra_specs(
             self.volume_type['id'], extra_specs)['extra_specs']
         self.assertEqual(extra_specs, body,
                          "Volume type extra spec incorrectly created")
 
         extra_spec = {"spec2": "val2"}
-        body = self.volume_types_client.update_volume_type_extra_specs(
+        body = self.admin_volume_types_client.update_volume_type_extra_specs(
             self.volume_type['id'],
             extra_spec.keys()[0],
             extra_spec)
@@ -67,19 +68,19 @@
     def test_volume_type_extra_spec_create_get_delete(self):
         # Create/Get/Delete volume type extra spec.
         extra_specs = {"spec3": "val1"}
-        body = self.volume_types_client.create_volume_type_extra_specs(
+        body = self.admin_volume_types_client.create_volume_type_extra_specs(
             self.volume_type['id'],
             extra_specs)['extra_specs']
         self.assertEqual(extra_specs, body,
                          "Volume type extra spec incorrectly created")
 
-        self.volume_types_client.show_volume_type_extra_specs(
+        self.admin_volume_types_client.show_volume_type_extra_specs(
             self.volume_type['id'],
             extra_specs.keys()[0])
         self.assertEqual(extra_specs, body,
                          "Volume type extra spec incorrectly fetched")
 
-        self.volume_types_client.delete_volume_type_extra_specs(
+        self.admin_volume_types_client.delete_volume_type_extra_specs(
             self.volume_type['id'],
             extra_specs.keys()[0])
 
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index f3e52e9..6983974 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -26,13 +26,13 @@
         super(ExtraSpecsNegativeV2Test, cls).resource_setup()
         vol_type_name = data_utils.rand_name('Volume-type')
         cls.extra_specs = {"spec1": "val1"}
-        cls.volume_type = cls.volume_types_client.create_volume_type(
+        cls.volume_type = cls.admin_volume_types_client.create_volume_type(
             name=vol_type_name,
             extra_specs=cls.extra_specs)['volume_type']
 
     @classmethod
     def resource_cleanup(cls):
-        cls.volume_types_client.delete_volume_type(cls.volume_type['id'])
+        cls.admin_volume_types_client.delete_volume_type(cls.volume_type['id'])
         super(ExtraSpecsNegativeV2Test, cls).resource_cleanup()
 
     @test.idempotent_id('08961d20-5cbb-4910-ac0f-89ad6dbb2da1')
@@ -41,7 +41,7 @@
         extra_spec = {"spec1": "val2"}
         self.assertRaises(
             lib_exc.BadRequest,
-            self.volume_types_client.update_volume_type_extra_specs,
+            self.admin_volume_types_client.update_volume_type_extra_specs,
             self.volume_type['id'], extra_spec.keys()[0], None)
 
     @test.idempotent_id('25e5a0ee-89b3-4c53-8310-236f76c75365')
@@ -50,7 +50,7 @@
         extra_spec = {"spec1": "val2"}
         self.assertRaises(
             lib_exc.BadRequest,
-            self.volume_types_client.update_volume_type_extra_specs,
+            self.admin_volume_types_client.update_volume_type_extra_specs,
             self.volume_type['id'], data_utils.rand_uuid(),
             extra_spec)
 
@@ -60,7 +60,7 @@
         extra_spec = {"spec1": "val2"}
         self.assertRaises(
             lib_exc.BadRequest,
-            self.volume_types_client.update_volume_type_extra_specs,
+            self.admin_volume_types_client.update_volume_type_extra_specs,
             self.volume_type['id'], None, extra_spec)
 
     @test.idempotent_id('a77dfda2-9100-448e-9076-ed1711f4bdfc')
@@ -70,7 +70,7 @@
         extra_spec = {"spec1": "val2", "spec2": "val1"}
         self.assertRaises(
             lib_exc.BadRequest,
-            self.volume_types_client.update_volume_type_extra_specs,
+            self.admin_volume_types_client.update_volume_type_extra_specs,
             self.volume_type['id'], extra_spec.keys()[0],
             extra_spec)
 
@@ -81,7 +81,7 @@
         extra_specs = {"spec2": "val1"}
         self.assertRaises(
             lib_exc.NotFound,
-            self.volume_types_client.create_volume_type_extra_specs,
+            self.admin_volume_types_client.create_volume_type_extra_specs,
             data_utils.rand_uuid(), extra_specs)
 
     @test.idempotent_id('c821bdc8-43a4-4bf4-86c8-82f3858d5f7d')
@@ -89,7 +89,7 @@
         # Should not create volume type extra spec for none POST body.
         self.assertRaises(
             lib_exc.BadRequest,
-            self.volume_types_client.create_volume_type_extra_specs,
+            self.admin_volume_types_client.create_volume_type_extra_specs,
             self.volume_type['id'], None)
 
     @test.idempotent_id('bc772c71-1ed4-4716-b945-8b5ed0f15e87')
@@ -97,7 +97,7 @@
         # Should not create volume type extra spec for invalid POST body.
         self.assertRaises(
             lib_exc.BadRequest,
-            self.volume_types_client.create_volume_type_extra_specs,
+            self.admin_volume_types_client.create_volume_type_extra_specs,
             self.volume_type['id'], extra_specs=['invalid'])
 
     @test.idempotent_id('031cda8b-7d23-4246-8bf6-bbe73fd67074')
@@ -107,7 +107,7 @@
         extra_specs = {"spec1": "val1"}
         self.assertRaises(
             lib_exc.NotFound,
-            self.volume_types_client.delete_volume_type_extra_specs,
+            self.admin_volume_types_client.delete_volume_type_extra_specs,
             data_utils.rand_uuid(), extra_specs.keys()[0])
 
     @test.idempotent_id('dee5cf0c-cdd6-4353-b70c-e847050d71fb')
@@ -115,7 +115,7 @@
         # Should not list volume type extra spec for nonexistent type id.
         self.assertRaises(
             lib_exc.NotFound,
-            self.volume_types_client.list_volume_types_extra_specs,
+            self.admin_volume_types_client.list_volume_types_extra_specs,
             data_utils.rand_uuid())
 
     @test.idempotent_id('9f402cbd-1838-4eb4-9554-126a6b1908c9')
@@ -124,7 +124,7 @@
         extra_specs = {"spec1": "val1"}
         self.assertRaises(
             lib_exc.NotFound,
-            self.volume_types_client.show_volume_type_extra_specs,
+            self.admin_volume_types_client.show_volume_type_extra_specs,
             data_utils.rand_uuid(), extra_specs.keys()[0])
 
     @test.idempotent_id('c881797d-12ff-4f1a-b09d-9f6212159753')
@@ -133,7 +133,7 @@
             # id.
         self.assertRaises(
             lib_exc.NotFound,
-            self.volume_types_client.show_volume_type_extra_specs,
+            self.admin_volume_types_client.show_volume_type_extra_specs,
             self.volume_type['id'], data_utils.rand_uuid())
 
 
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index aff5466..857e7d2 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -33,21 +33,22 @@
     @test.idempotent_id('878b4e57-faa2-4659-b0d1-ce740a06ae81')
     def test_create_with_empty_name(self):
         # Should not be able to create volume type with an empty name.
-        self.assertRaises(lib_exc.BadRequest,
-                          self.volume_types_client.create_volume_type, name='')
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.admin_volume_types_client.create_volume_type, name='')
 
     @test.idempotent_id('994610d6-0476-4018-a644-a2602ef5d4aa')
     def test_get_nonexistent_type_id(self):
         # Should not be able to get volume type with nonexistent type id.
         self.assertRaises(lib_exc.NotFound,
-                          self.volume_types_client.show_volume_type,
+                          self.admin_volume_types_client.show_volume_type,
                           data_utils.rand_uuid())
 
     @test.idempotent_id('6b3926d2-7d73-4896-bc3d-e42dfd11a9f6')
     def test_delete_nonexistent_type_id(self):
         # Should not be able to delete volume type with nonexistent type id.
         self.assertRaises(lib_exc.NotFound,
-                          self.volume_types_client.delete_volume_type,
+                          self.admin_volume_types_client.delete_volume_type,
                           data_utils.rand_uuid())
 
 
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 1289297..66bab51 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -42,8 +42,8 @@
         cls.volume = cls.create_volume()
 
     def _delete_backup(self, backup_id):
-        self.backups_adm_client.delete_backup(backup_id)
-        self.backups_adm_client.wait_for_backup_deletion(backup_id)
+        self.admin_backups_client.delete_backup(backup_id)
+        self.admin_backups_client.wait_for_backup_deletion(backup_id)
 
     def _decode_url(self, backup_url):
         return json.loads(base64.decodestring(backup_url))
@@ -63,36 +63,37 @@
     def test_volume_backup_create_get_detailed_list_restore_delete(self):
         # Create backup
         backup_name = data_utils.rand_name('Backup')
-        create_backup = self.backups_adm_client.create_backup
+        create_backup = self.admin_backups_client.create_backup
         backup = create_backup(volume_id=self.volume['id'],
                                name=backup_name)['backup']
-        self.addCleanup(self.backups_adm_client.delete_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.backups_adm_client.wait_for_backup_status(backup['id'],
-                                                       'available')
+        self.admin_backups_client.wait_for_backup_status(backup['id'],
+                                                         'available')
 
         # Get a given backup
-        backup = self.backups_adm_client.show_backup(backup['id'])['backup']
+        backup = self.admin_backups_client.show_backup(backup['id'])['backup']
         self.assertEqual(backup_name, backup['name'])
 
         # Get all backups with detail
-        backups = self.backups_adm_client.list_backups(detail=True)['backups']
+        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.backups_adm_client.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.backups_adm_client.wait_for_backup_status(backup['id'],
-                                                       'available')
+        self.admin_backups_client.wait_for_backup_status(backup['id'],
+                                                         'available')
         waiters.wait_for_volume_status(self.admin_volume_client,
                                        restore['volume_id'], 'available')
 
@@ -105,15 +106,15 @@
         """
         # Create backup
         backup_name = data_utils.rand_name('Backup')
-        backup = (self.backups_adm_client.create_backup(
+        backup = (self.admin_backups_client.create_backup(
             volume_id=self.volume['id'], name=backup_name)['backup'])
         self.addCleanup(self._delete_backup, backup['id'])
         self.assertEqual(backup_name, backup['name'])
-        self.backups_adm_client.wait_for_backup_status(backup['id'],
-                                                       'available')
+        self.admin_backups_client.wait_for_backup_status(backup['id'],
+                                                         'available')
 
         # Export Backup
-        export_backup = (self.backups_adm_client.export_backup(backup['id'])
+        export_backup = (self.admin_backups_client.export_backup(backup['id'])
                          ['backup-record'])
         self.assertIn('backup_service', export_backup)
         self.assertIn('backup_url', export_backup)
@@ -132,7 +133,7 @@
             export_backup['backup_url'], {'id': new_id})
 
         # Import Backup
-        import_backup = self.backups_adm_client.import_backup(
+        import_backup = self.admin_backups_client.import_backup(
             backup_service=export_backup['backup_service'],
             backup_url=new_url)['backup']
 
@@ -142,15 +143,16 @@
         self.addCleanup(self._delete_backup, new_id)
         self.assertIn("id", import_backup)
         self.assertEqual(new_id, import_backup['id'])
-        self.backups_adm_client.wait_for_backup_status(import_backup['id'],
-                                                       'available')
+        self.admin_backups_client.wait_for_backup_status(import_backup['id'],
+                                                         'available')
 
         # Verify Import Backup
-        backups = self.backups_adm_client.list_backups(detail=True)['backups']
+        backups = self.admin_backups_client.list_backups(
+            detail=True)['backups']
         self.assertIn(new_id, [b['id'] for b in backups])
 
         # Restore backup
-        restore = self.backups_adm_client.restore_backup(
+        restore = self.admin_backups_client.restore_backup(
             backup['id'])['restore']
         self.addCleanup(self.admin_volume_client.delete_volume,
                         restore['volume_id'])
@@ -161,8 +163,8 @@
         # Verify if restored volume is there in volume list
         volumes = self.admin_volume_client.list_volumes()['volumes']
         self.assertIn(restore['volume_id'], [v['id'] for v in volumes])
-        self.backups_adm_client.wait_for_backup_status(import_backup['id'],
-                                                       'available')
+        self.admin_backups_client.wait_for_backup_status(import_backup['id'],
+                                                         'available')
 
 
 class VolumesBackupsV1Test(VolumesBackupsV2Test):
diff --git a/tempest/api/volume/api_microversion_fixture.py b/tempest/api/volume/api_microversion_fixture.py
new file mode 100644
index 0000000..6817eaa
--- /dev/null
+++ b/tempest/api/volume/api_microversion_fixture.py
@@ -0,0 +1,30 @@
+#
+# 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 fixtures
+
+from tempest.services.volume.base import base_v3_client
+
+
+class APIMicroversionFixture(fixtures.Fixture):
+
+    def __init__(self, volume_microversion):
+        self.volume_microversion = volume_microversion
+
+    def _setUp(self):
+        super(APIMicroversionFixture, self)._setUp()
+        base_v3_client.VOLUME_MICROVERSION = self.volume_microversion
+        self.addCleanup(self._reset_volume_microversion)
+
+    def _reset_volume_microversion(self):
+        base_v3_client.VOLUME_MICROVERSION = None
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 6116d72..9010c89 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -45,6 +45,10 @@
             if not CONF.volume_feature_enabled.api_v2:
                 msg = "Volume API v2 is disabled"
                 raise cls.skipException(msg)
+        elif cls._api_version == 3:
+            if not CONF.volume_feature_enabled.api_v3:
+                msg = "Volume API v3 is disabled"
+                raise cls.skipException(msg)
         else:
             msg = ("Invalid Cinder API version (%s)" % cls._api_version)
             raise exceptions.InvalidConfiguration(message=msg)
@@ -179,25 +183,25 @@
         super(BaseVolumeAdminTest, cls).setup_clients()
 
         if cls._api_version == 1:
-            cls.volume_qos_client = cls.os_adm.volume_qos_client
+            cls.admin_volume_qos_client = cls.os_adm.volume_qos_client
             cls.admin_volume_services_client = \
                 cls.os_adm.volume_services_client
-            cls.volume_types_client = cls.os_adm.volume_types_client
+            cls.admin_volume_types_client = cls.os_adm.volume_types_client
             cls.admin_volume_client = cls.os_adm.volumes_client
-            cls.hosts_client = cls.os_adm.volume_hosts_client
+            cls.admin_hosts_client = cls.os_adm.volume_hosts_client
             cls.admin_snapshots_client = cls.os_adm.snapshots_client
-            cls.backups_adm_client = cls.os_adm.backups_client
-            cls.quotas_client = cls.os_adm.volume_quotas_client
+            cls.admin_backups_client = cls.os_adm.backups_client
+            cls.admin_quotas_client = cls.os_adm.volume_quotas_client
         elif cls._api_version == 2:
-            cls.volume_qos_client = cls.os_adm.volume_qos_v2_client
+            cls.admin_volume_qos_client = cls.os_adm.volume_qos_v2_client
             cls.admin_volume_services_client = \
                 cls.os_adm.volume_services_v2_client
-            cls.volume_types_client = cls.os_adm.volume_types_v2_client
+            cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
             cls.admin_volume_client = cls.os_adm.volumes_v2_client
-            cls.hosts_client = cls.os_adm.volume_hosts_v2_client
+            cls.admin_hosts_client = cls.os_adm.volume_hosts_v2_client
             cls.admin_snapshots_client = cls.os_adm.snapshots_v2_client
-            cls.backups_adm_client = cls.os_adm.backups_v2_client
-            cls.quotas_client = cls.os_adm.volume_quotas_v2_client
+            cls.admin_backups_client = cls.os_adm.backups_v2_client
+            cls.admin_quotas_client = cls.os_adm.volume_quotas_v2_client
 
     @classmethod
     def resource_setup(cls):
@@ -215,7 +219,7 @@
         """create a test Qos-Specs."""
         name = name or data_utils.rand_name(cls.__name__ + '-QoS')
         consumer = consumer or 'front-end'
-        qos_specs = cls.volume_qos_client.create_qos(
+        qos_specs = cls.admin_volume_qos_client.create_qos(
             name=name, consumer=consumer, **kwargs)['qos_specs']
         cls.qos_specs.append(qos_specs['id'])
         return qos_specs
@@ -224,8 +228,8 @@
     def clear_qos_specs(cls):
         for qos_id in cls.qos_specs:
             test_utils.call_and_ignore_notfound_exc(
-                cls.volume_qos_client.delete_qos, qos_id)
+                cls.admin_volume_qos_client.delete_qos, qos_id)
 
         for qos_id in cls.qos_specs:
             test_utils.call_and_ignore_notfound_exc(
-                cls.volume_qos_client.wait_for_resource_deletion, qos_id)
+                cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 38913ce..76cd36c 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -18,6 +18,7 @@
 from tempest.common.utils import data_utils
 from tempest.common import waiters
 from tempest import config
+from tempest import exceptions
 from tempest.lib.common.utils import test_utils
 from tempest import test
 
@@ -30,7 +31,16 @@
     def setup_clients(cls):
         super(VolumesV2ActionsTest, cls).setup_clients()
         cls.client = cls.volumes_client
-        cls.image_client = cls.os.image_client
+        if CONF.service_available.glance:
+            # Check if glance v1 is available to determine which client to use.
+            if CONF.image_feature_enabled.api_v1:
+                cls.image_client = cls.os.image_client
+            elif CONF.image_feature_enabled.api_v2:
+                cls.image_client = cls.os.image_client_v2
+            else:
+                raise exceptions.InvalidConfiguration(
+                    'Either api_v1 or api_v2 must be True in '
+                    '[image-feature-enabled].')
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/services/image/v2/__init__.py b/tempest/api/volume/v3/__init__.py
similarity index 100%
copy from tempest/services/image/v2/__init__.py
copy to tempest/api/volume/v3/__init__.py
diff --git a/tempest/services/image/v2/__init__.py b/tempest/api/volume/v3/admin/__init__.py
similarity index 100%
copy from tempest/services/image/v2/__init__.py
copy to tempest/api/volume/v3/admin/__init__.py
diff --git a/tempest/api/volume/v3/admin/test_user_messages.py b/tempest/api/volume/v3/admin/test_user_messages.py
new file mode 100644
index 0000000..19c37be
--- /dev/null
+++ b/tempest/api/volume/v3/admin/test_user_messages.py
@@ -0,0 +1,98 @@
+# Copyright 2016 Andrew Kerr
+# 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.api.volume.v3 import base
+from tempest.common.utils import data_utils
+from tempest.common import waiters
+from tempest import exceptions
+from tempest import test
+
+MESSAGE_KEYS = [
+    'created_at',
+    'event_id',
+    'guaranteed_until',
+    'id',
+    'message_level',
+    'request_id',
+    'resource_type',
+    'resource_uuid',
+    'user_message',
+    'links']
+
+
+class UserMessagesTest(base.VolumesV3AdminTest):
+    min_microversion = '3.3'
+    max_microversion = 'latest'
+
+    def _delete_volume(self, volume_id):
+        self.volumes_client.delete_volume(volume_id)
+        self.volumes_client.wait_for_resource_deletion(volume_id)
+
+    def _create_user_message(self):
+        """Trigger a 'no valid host' situation to generate a message."""
+        bad_protocol = data_utils.rand_name('storage_protocol')
+        bad_vendor = data_utils.rand_name('vendor_name')
+        extra_specs = {'storage_protocol': bad_protocol,
+                       'vendor_name': bad_vendor}
+        vol_type_name = data_utils.rand_name('volume-type')
+        bogus_type = self.admin_volume_types_client.create_volume_type(
+            name=vol_type_name,
+            extra_specs=extra_specs)['volume_type']
+        self.addCleanup(self.admin_volume_types_client.delete_volume_type,
+                        bogus_type['id'])
+        params = {'volume_type': bogus_type['id']}
+        volume = self.volumes_client.create_volume(**params)['volume']
+        self.addCleanup(self._delete_volume, volume['id'])
+        try:
+            waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+                                           'error')
+        except exceptions.VolumeBuildErrorException:
+            # Error state is expected and desired
+            pass
+        messages = self.messages_client.list_messages()['messages']
+        message_id = None
+        for message in messages:
+            if message['resource_uuid'] == volume['id']:
+                message_id = message['id']
+                break
+        self.assertIsNotNone(message_id, 'No user message generated for '
+                                         'volume %s' % volume['id'])
+        return message_id
+
+    @test.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a')
+    def test_list_messages(self):
+        self._create_user_message()
+        messages = self.messages_client.list_messages()['messages']
+        self.assertIsInstance(messages, list)
+        for message in messages:
+            for key in MESSAGE_KEYS:
+                self.assertIn(key, message.keys(),
+                              'Missing expected key %s' % key)
+
+    @test.idempotent_id('55a4a61e-c7b2-4ba0-a05d-b914bdef3070')
+    def test_show_message(self):
+        message_id = self._create_user_message()
+        self.addCleanup(self.messages_client.delete_message, message_id)
+
+        message = self.messages_client.show_message(message_id)['message']
+
+        for key in MESSAGE_KEYS:
+            self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
+
+    @test.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
+    def test_delete_message(self):
+        message_id = self._create_user_message()
+        self.messages_client.delete_message(message_id)
+        self.messages_client.wait_for_resource_deletion(message_id)
diff --git a/tempest/api/volume/v3/base.py b/tempest/api/volume/v3/base.py
new file mode 100644
index 0000000..c31c83c
--- /dev/null
+++ b/tempest/api/volume/v3/base.py
@@ -0,0 +1,64 @@
+# Copyright 2016 Andrew Kerr
+# 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.api.volume import api_microversion_fixture
+from tempest.api.volume import base
+from tempest import config
+from tempest.lib.common import api_version_utils
+
+CONF = config.CONF
+
+
+class VolumesV3Test(api_version_utils.BaseMicroversionTest,
+                    base.BaseVolumeTest):
+    """Base test case class for all v3 Cinder API tests."""
+
+    _api_version = 3
+
+    @classmethod
+    def skip_checks(cls):
+        super(VolumesV3Test, cls).skip_checks()
+        api_version_utils.check_skip_with_microversion(
+            cls.min_microversion, cls.max_microversion,
+            CONF.volume.min_microversion, CONF.volume.max_microversion)
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumesV3Test, cls).resource_setup()
+        cls.request_microversion = (
+            api_version_utils.select_request_microversion(
+                cls.min_microversion,
+                CONF.volume.min_microversion))
+
+    @classmethod
+    def setup_clients(cls):
+        super(VolumesV3Test, cls).setup_clients()
+        cls.messages_client = cls.os.volume_messages_client
+
+    def setUp(self):
+        super(VolumesV3Test, self).setUp()
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            self.request_microversion))
+
+
+class VolumesV3AdminTest(VolumesV3Test):
+    """Base test case class for all v3 Volume Admin API tests."""
+
+    credentials = ['primary', 'admin']
+
+    @classmethod
+    def setup_clients(cls):
+        super(VolumesV3AdminTest, cls).setup_clients()
+        cls.admin_messages_client = cls.os_adm.volume_messages_client
+        cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
diff --git a/tempest/clients.py b/tempest/clients.py
index bc56710..93ab74b 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -73,8 +73,17 @@
 from tempest.lib.services.compute.versions_client import VersionsClient
 from tempest.lib.services.compute.volumes_client import \
     VolumesClient as ComputeVolumesClient
+from tempest.lib.services.identity.v2.endpoints_client import EndpointsClient
 from tempest.lib.services.identity.v2.token_client import TokenClient
 from tempest.lib.services.identity.v3.token_client import V3TokenClient
+from tempest.lib.services.image.v2.image_members_client import \
+    ImageMembersClient as ImageMembersClientV2
+from tempest.lib.services.image.v2.images_client import \
+    ImagesClient as ImagesV2Client
+from tempest.lib.services.image.v2.namespaces_client import NamespacesClient
+from tempest.lib.services.image.v2.resource_types_client import \
+    ResourceTypesClient
+from tempest.lib.services.image.v2.schemas_client import SchemasClient
 from tempest.lib.services.network.agents_client import AgentsClient \
     as NetworkAgentsClient
 from tempest.lib.services.network.extensions_client import \
@@ -106,7 +115,6 @@
     DatabaseLimitsClient
 from tempest.services.database.json.versions_client import \
     DatabaseVersionsClient
-from tempest.services.identity.v2.json.endpoints_client import EndpointsClient
 from tempest.services.identity.v2.json.identity_client import IdentityClient
 from tempest.services.identity.v2.json.roles_client import RolesClient
 from tempest.services.identity.v2.json.services_client import \
@@ -131,9 +139,9 @@
 from tempest.services.identity.v3.json.trusts_client import TrustsClient
 from tempest.services.identity.v3.json.users_clients import \
     UsersClient as UsersV3Client
+from tempest.services.image.v1.json.image_members_client import \
+    ImageMembersClient
 from tempest.services.image.v1.json.images_client import ImagesClient
-from tempest.services.image.v2.json.images_client import \
-    ImagesClient as ImagesV2Client
 from tempest.services.object_storage.account_client import AccountClient
 from tempest.services.object_storage.container_client import ContainerClient
 from tempest.services.object_storage.object_client import ObjectClient
@@ -175,6 +183,7 @@
     SnapshotsClient as SnapshotsV2Client
 from tempest.services.volume.v2.json.volumes_client import \
     VolumesClient as VolumesV2Client
+from tempest.services.volume.v3.json.messages_client import MessagesClient
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
@@ -212,6 +221,8 @@
         self._set_identity_clients()
         self._set_volume_clients()
         self._set_object_storage_clients()
+        self._set_image_clients()
+        self._set_network_clients()
 
         self.baremetal_client = BaremetalClient(
             self.auth_provider,
@@ -219,127 +230,6 @@
             CONF.identity.region,
             endpoint_type=CONF.baremetal.endpoint_type,
             **self.default_params_with_timeout_values)
-        self.network_agents_client = NetworkAgentsClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.network_extensions_client = NetworkExtensionsClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.networks_client = NetworksClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.subnetpools_client = SubnetpoolsClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.subnets_client = SubnetsClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.ports_client = PortsClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.network_quotas_client = NetworkQuotasClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.floating_ips_client = FloatingIPsClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.metering_labels_client = MeteringLabelsClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.metering_label_rules_client = MeteringLabelRulesClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.routers_client = RoutersClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.security_group_rules_client = SecurityGroupRulesClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        self.security_groups_client = SecurityGroupsClient(
-            self.auth_provider,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **self.default_params)
-        if CONF.service_available.glance:
-            self.image_client = ImagesClient(
-                self.auth_provider,
-                CONF.image.catalog_type,
-                CONF.image.region or CONF.identity.region,
-                endpoint_type=CONF.image.endpoint_type,
-                build_interval=CONF.image.build_interval,
-                build_timeout=CONF.image.build_timeout,
-                **self.default_params)
-            self.image_client_v2 = ImagesV2Client(
-                self.auth_provider,
-                CONF.image.catalog_type,
-                CONF.image.region or CONF.identity.region,
-                endpoint_type=CONF.image.endpoint_type,
-                build_interval=CONF.image.build_interval,
-                build_timeout=CONF.image.build_timeout,
-                **self.default_params)
         self.orchestration_client = OrchestrationClient(
             self.auth_provider,
             CONF.orchestration.catalog_type,
@@ -357,6 +247,68 @@
         self.negative_client = negative_rest_client.NegativeRestClient(
             self.auth_provider, service, **self.default_params)
 
+    def _set_network_clients(self):
+        params = {
+            'service': CONF.network.catalog_type,
+            'region': CONF.network.region or CONF.identity.region,
+            'endpoint_type': CONF.network.endpoint_type,
+            'build_interval': CONF.network.build_interval,
+            'build_timeout': CONF.network.build_timeout
+        }
+        params.update(self.default_params)
+        self.network_agents_client = NetworkAgentsClient(
+            self.auth_provider, **params)
+        self.network_extensions_client = NetworkExtensionsClient(
+            self.auth_provider, **params)
+        self.networks_client = NetworksClient(
+            self.auth_provider, **params)
+        self.subnetpools_client = SubnetpoolsClient(
+            self.auth_provider, **params)
+        self.subnets_client = SubnetsClient(
+            self.auth_provider, **params)
+        self.ports_client = PortsClient(
+            self.auth_provider, **params)
+        self.network_quotas_client = NetworkQuotasClient(
+            self.auth_provider, **params)
+        self.floating_ips_client = FloatingIPsClient(
+            self.auth_provider, **params)
+        self.metering_labels_client = MeteringLabelsClient(
+            self.auth_provider, **params)
+        self.metering_label_rules_client = MeteringLabelRulesClient(
+            self.auth_provider, **params)
+        self.routers_client = RoutersClient(
+            self.auth_provider, **params)
+        self.security_group_rules_client = SecurityGroupRulesClient(
+            self.auth_provider, **params)
+        self.security_groups_client = SecurityGroupsClient(
+            self.auth_provider, **params)
+
+    def _set_image_clients(self):
+        params = {
+            'service': CONF.image.catalog_type,
+            'region': CONF.image.region or CONF.identity.region,
+            'endpoint_type': CONF.image.endpoint_type,
+            'build_interval': CONF.image.build_interval,
+            'build_timeout': CONF.image.build_timeout
+        }
+        params.update(self.default_params)
+
+        if CONF.service_available.glance:
+            self.image_client = ImagesClient(
+                self.auth_provider, **params)
+            self.image_member_client = ImageMembersClient(
+                self.auth_provider, **params)
+            self.image_client_v2 = ImagesV2Client(
+                self.auth_provider, **params)
+            self.image_member_client_v2 = ImageMembersClientV2(
+                self.auth_provider, **params)
+            self.namespaces_client = NamespacesClient(
+                self.auth_provider, **params)
+            self.resource_types_client = ResourceTypesClient(
+                self.auth_provider, **params)
+            self.schemas_client = SchemasClient(
+                self.auth_provider, **params)
+
     def _set_compute_clients(self):
         params = {
             'service': CONF.compute.catalog_type,
@@ -557,6 +509,8 @@
         self.volumes_v2_client = VolumesV2Client(
             self.auth_provider, default_volume_size=CONF.volume.volume_size,
             **params)
+        self.volume_messages_client = MessagesClient(self.auth_provider,
+                                                     **params)
         self.volume_types_client = VolumeTypesClient(self.auth_provider,
                                                      **params)
         self.volume_types_v2_client = VolumeTypesV2Client(
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 94a646a..f9d7a9b 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -38,15 +38,17 @@
 of your cloud to operate properly. The corresponding info can be given either
 through CLI options or environment variables.
 
-You're probably familiar with these, but just to remind::
+You're probably familiar with these, but just to remind:
 
-    +----------+------------------+----------------------+
-    | Param    | CLI              | Environment Variable |
-    +----------+------------------+----------------------+
-    | Username | --os-username    | OS_USERNAME          |
-    | Password | --os-password    | OS_PASSWORD          |
-    | Tenant   | --os-tenant-name | OS_TENANT_NAME       |
-    +----------+------------------+----------------------+
+======== ======================== ====================
+Param    CLI                      Environment Variable
+======== ======================== ====================
+Username --os-username            OS_USERNAME
+Password --os-password            OS_PASSWORD
+Project  --os-project-name        OS_PROJECT_NAME
+Tenant   --os-tenant-name (depr.) OS_TENANT_NAME
+Domain   --os-domain-name         OS_DOMAIN_NAME
+======== ======================== ====================
 
 Optional Arguments
 ------------------
@@ -63,8 +65,14 @@
 **--os-password <auth-password>** (Optional) Password used for authentication
 with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
 
-**--os-tenant-name <auth-tenant-name>** (Optional) Tenant to request
-authorization on. Defaults to env[OS_TENANT_NAME].
+**--os-project-name <auth-project-name>** (Optional) Project to request
+authorization on. Defaults to env[OS_PROJECT_NAME].
+
+**--os-tenant-name <auth-tenant-name>** (Optional, deprecated) Tenant to
+request authorization on. Defaults to env[OS_TENANT_NAME].
+
+**--os-domain-name <auth-domain-name>** (Optional) Domain the user and project
+belong to. Defaults to env[OS_DOMAIN_NAME].
 
 **--tag TAG** (Optional) Resources tag. Each created resource (user, project)
 will have the prefix with the given TAG in its name. Using tag is recommended
@@ -79,11 +87,13 @@
 **--with-admin** (Optional) Creates admin for each concurrent group
 (default: False).
 
+**-i VERSION**, **--identity-version VERSION** (Optional) Provisions accounts
+using the specified version of the identity API. (default: '3').
+
 To see help on specific argument, please do: ``tempest-account-generator
 [OPTIONS] <accounts_file.yaml> -h``.
 """
 import argparse
-import netaddr
 import os
 import traceback
 
@@ -91,19 +101,10 @@
 from oslo_log import log as logging
 import yaml
 
-from tempest.common import identity
+from tempest.common import credentials_factory
+from tempest.common import dynamic_creds
 from tempest import config
-from tempest import exceptions as exc
-import tempest.lib.auth
-from tempest.lib.common.utils import data_utils
-import tempest.lib.exceptions
-from tempest.lib.services.network import networks_client
-from tempest.lib.services.network import routers_client
-from tempest.lib.services.network import subnets_client
-from tempest.services.identity.v2.json import identity_client
-from tempest.services.identity.v2.json import roles_client
-from tempest.services.identity.v2.json import tenants_client
-from tempest.services.identity.v2.json import users_client
+
 
 LOG = None
 CONF = config.CONF
@@ -120,290 +121,84 @@
     LOG = logging.getLogger(__name__)
 
 
-def get_admin_clients(opts):
-    _creds = tempest.lib.auth.KeystoneV2Credentials(
-        username=opts.os_username,
-        password=opts.os_password,
-        tenant_name=opts.os_tenant_name)
-    auth_params = {
-        'disable_ssl_certificate_validation':
-            CONF.identity.disable_ssl_certificate_validation,
-        'ca_certs': CONF.identity.ca_certificates_file,
-        'trace_requests': CONF.debug.trace_requests
-    }
-    _auth = tempest.lib.auth.KeystoneV2AuthProvider(
-        _creds, CONF.identity.uri, **auth_params)
-    params = {
-        'disable_ssl_certificate_validation':
-            CONF.identity.disable_ssl_certificate_validation,
-        'ca_certs': CONF.identity.ca_certificates_file,
-        'trace_requests': CONF.debug.trace_requests,
-        'build_interval': CONF.compute.build_interval,
-        'build_timeout': CONF.compute.build_timeout
-    }
-    identity_admin = identity_client.IdentityClient(
-        _auth,
-        CONF.identity.catalog_type,
-        CONF.identity.region,
-        endpoint_type='adminURL',
-        **params
-    )
-    tenants_admin = tenants_client.TenantsClient(
-        _auth,
-        CONF.identity.catalog_type,
-        CONF.identity.region,
-        endpoint_type='adminURL',
-        **params
-    )
-    roles_admin = roles_client.RolesClient(
-        _auth,
-        CONF.identity.catalog_type,
-        CONF.identity.region,
-        endpoint_type='adminURL',
-        **params
-    )
-    users_admin = users_client.UsersClient(
-        _auth,
-        CONF.identity.catalog_type,
-        CONF.identity.region,
-        endpoint_type='adminURL',
-        **params
-    )
-    networks_admin = None
-    routers_admin = None
-    subnets_admin = None
-    neutron_iso_networks = False
-    if (CONF.service_available.neutron and
-        CONF.auth.create_isolated_networks):
-        neutron_iso_networks = True
-        networks_admin = networks_client.NetworksClient(
-            _auth,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type='adminURL',
-            **params)
-        routers_admin = routers_client.RoutersClient(
-            _auth,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type='adminURL',
-            **params)
-        subnets_admin = subnets_client.SubnetsClient(
-            _auth,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type='adminURL',
-            **params)
-    return (identity_admin, tenants_admin, roles_admin, users_admin,
-            neutron_iso_networks, networks_admin, routers_admin,
-            subnets_admin)
+def get_credential_provider(opts):
+    identity_version = "".join(['v', str(opts.identity_version)])
+    # NOTE(andreaf) For now tempest.conf controls whether resources will
+    # actually be created. Once we remove the dependency from tempest.conf
+    # we will need extra CLI option(s) to control this.
+    network_resources = {'router': True,
+                         'network': True,
+                         'subnet': True,
+                         'dhcp': True}
+    admin_creds_dict = {'username': opts.os_username,
+                        'password': opts.os_password}
+    _project_name = opts.os_project_name or opts.os_tenant_name
+    if opts.identity_version == 3:
+        admin_creds_dict['project_name'] = _project_name
+        admin_creds_dict['domain_name'] = opts.os_domain_name or 'Default'
+    elif opts.identity_version == 2:
+        admin_creds_dict['tenant_name'] = _project_name
+    admin_creds = credentials_factory.get_credentials(
+        fill_in=False, identity_version=identity_version, **admin_creds_dict)
+    return dynamic_creds.DynamicCredentialProvider(
+        identity_version=identity_version,
+        name=opts.tag,
+        network_resources=network_resources,
+        admin_creds=admin_creds,
+        **credentials_factory.get_dynamic_provider_params())
 
 
-def create_resources(opts, resources):
-    (identity_admin, tenants_admin, roles_admin, users_admin,
-     neutron_iso_networks, networks_admin, routers_admin,
-     subnets_admin) = get_admin_clients(opts)
-    roles = roles_admin.list_roles()['roles']
-    for u in resources['users']:
-        u['role_ids'] = []
-        for r in u.get('roles', ()):
-            try:
-                role = filter(lambda r_: r_['name'] == r, roles)[0]
-            except IndexError:
-                msg = "Role: %s doesn't exist" % r
-                raise exc.InvalidConfiguration(msg)
-            u['role_ids'] += [role['id']]
-    existing = [x['name'] for x in tenants_admin.list_tenants()['tenants']]
-    for tenant in resources['tenants']:
-        if tenant not in existing:
-            tenants_admin.create_tenant(tenant)
-        else:
-            LOG.warning("Tenant '%s' already exists in this environment"
-                        % tenant)
-    LOG.info('Tenants created')
-    for u in resources['users']:
-        try:
-            tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
-        except tempest.lib.exceptions.NotFound:
-            LOG.error("Tenant: %s - not found" % u['tenant'])
-            continue
-        while True:
-            try:
-                identity.get_user_by_username(tenants_admin,
-                                              tenant['id'], u['name'])
-            except tempest.lib.exceptions.NotFound:
-                users_admin.create_user(
-                    u['name'], u['pass'], tenant['id'],
-                    "%s@%s" % (u['name'], tenant['id']),
-                    enabled=True)
-                break
-            else:
-                LOG.warning("User '%s' already exists in this environment. "
-                            "New name generated" % u['name'])
-                u['name'] = random_user_name(opts.tag, u['prefix'])
-
-    LOG.info('Users created')
-    if neutron_iso_networks:
-        for u in resources['users']:
-            tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
-            network_name, router_name = create_network_resources(
-                networks_admin, routers_admin, subnets_admin,
-                tenant['id'], u['name'])
-            u['network'] = network_name
-            u['router'] = router_name
-        LOG.info('Networks created')
-    for u in resources['users']:
-        try:
-            tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
-        except tempest.lib.exceptions.NotFound:
-            LOG.error("Tenant: %s - not found" % u['tenant'])
-            continue
-        try:
-            user = identity.get_user_by_username(tenants_admin,
-                                                 tenant['id'], u['name'])
-        except tempest.lib.exceptions.NotFound:
-            LOG.error("User: %s - not found" % u['name'])
-            continue
-        for r in u['role_ids']:
-            try:
-                roles_admin.assign_user_role(tenant['id'], user['id'], r)
-            except tempest.lib.exceptions.Conflict:
-                # don't care if it's already assigned
-                pass
-    LOG.info('Roles assigned')
-    LOG.info('Resources deployed successfully!')
-
-
-def create_network_resources(networks_admin_client,
-                             routers_admin_client, subnets_admin_client,
-                             tenant_id, name):
-
-    def _create_network(name):
-        resp_body = networks_admin_client.create_network(
-            name=name, tenant_id=tenant_id)
-        return resp_body['network']
-
-    def _create_subnet(subnet_name, network_id):
-        base_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
-        mask_bits = CONF.network.project_network_mask_bits
-        for subnet_cidr in base_cidr.subnet(mask_bits):
-            try:
-                resp_body = subnets_admin_client.\
-                    create_subnet(
-                        network_id=network_id, cidr=str(subnet_cidr),
-                        name=subnet_name,
-                        tenant_id=tenant_id,
-                        enable_dhcp=True,
-                        ip_version=4)
-                break
-            except tempest.lib.exceptions.BadRequest as e:
-                if 'overlaps with another subnet' not in str(e):
-                    raise
-        else:
-            message = 'Available CIDR for subnet creation could not be found'
-            raise Exception(message)
-        return resp_body['subnet']
-
-    def _create_router(router_name):
-        external_net_id = dict(
-            network_id=CONF.network.public_network_id)
-        resp_body = routers_admin_client.create_router(
-            name=router_name,
-            external_gateway_info=external_net_id,
-            tenant_id=tenant_id)
-        return resp_body['router']
-
-    def _add_router_interface(router_id, subnet_id):
-        routers_admin_client.add_router_interface(router_id,
-                                                  subnet_id=subnet_id)
-
-    network_name = name + "-network"
-    network = _create_network(network_name)
-    subnet_name = name + "-subnet"
-    subnet = _create_subnet(subnet_name, network['id'])
-    router_name = name + "-router"
-    router = _create_router(router_name)
-    _add_router_interface(router['id'], subnet['id'])
-    return network_name, router_name
-
-
-def random_user_name(tag, prefix):
-    if tag:
-        return data_utils.rand_name('-'.join((tag, prefix)))
-    else:
-        return data_utils.rand_name(prefix)
-
-
-def generate_resources(opts):
-    spec = [{'number': 1,
-             'prefix': 'primary',
-             'roles': (CONF.auth.tempest_roles +
-                       [CONF.object_storage.operator_role])},
-            {'number': 1,
-             'prefix': 'alt',
-             'roles': (CONF.auth.tempest_roles +
-                       [CONF.object_storage.operator_role])}]
+def generate_resources(cred_provider, admin):
+    # Create the list of resources to be provisioned for each process
+    # NOTE(andreaf) get_credentials expects a string for types or a list for
+    # roles. Adding all required inputs to the spec list.
+    spec = ['primary', 'alt']
     if CONF.service_available.swift:
-        spec.append({'number': 1,
-                     'prefix': 'swift_operator',
-                     'roles': (CONF.auth.tempest_roles +
-                               [CONF.object_storage.operator_role])})
-        spec.append({'number': 1,
-                     'prefix': 'swift_reseller_admin',
-                     'roles': (CONF.auth.tempest_roles +
-                               [CONF.object_storage.reseller_admin_role])})
+        spec.append([CONF.object_storage.operator_role])
+        spec.append([CONF.object_storage.reseller_admin_role])
     if CONF.service_available.heat:
-        spec.append({'number': 1,
-                     'prefix': 'stack_owner',
-                     'roles': (CONF.auth.tempest_roles +
-                               [CONF.orchestration.stack_owner_role])})
-    if opts.admin:
-        spec.append({
-            'number': 1,
-            'prefix': 'admin',
-            'roles': (CONF.auth.tempest_roles +
-                      [CONF.identity.admin_role])
-        })
-    resources = {'tenants': [],
-                 'users': []}
-    for count in range(opts.concurrency):
-        for user_group in spec:
-            users = [random_user_name(opts.tag, user_group['prefix'])
-                     for _ in range(user_group['number'])]
-            for user in users:
-                tenant = '-'.join((user, 'tenant'))
-                resources['tenants'].append(tenant)
-                resources['users'].append({
-                    'tenant': tenant,
-                    'name': user,
-                    'pass': data_utils.rand_password(),
-                    'prefix': user_group['prefix'],
-                    'roles': user_group['roles']
-                })
+        spec.append([CONF.orchestration.stack_owner_role,
+                     CONF.object_storage.operator_role])
+    if admin:
+        spec.append('admin')
+    resources = []
+    for cred_type in spec:
+        resources.append((cred_type, cred_provider.get_credentials(
+            credential_type=cred_type)))
     return resources
 
 
-def dump_accounts(opts, resources):
+def dump_accounts(resources, identity_version, account_file):
     accounts = []
-    for user in resources['users']:
+    for resource in resources:
+        cred_type, test_resource = resource
         account = {
-            'username': user['name'],
-            'tenant_name': user['tenant'],
-            'password': user['pass'],
-            'roles': user['roles']
+            'username': test_resource.username,
+            'password': test_resource.password
         }
-        if 'network' in user or 'router' in user:
+        if identity_version == 3:
+            account['project_name'] = test_resource.project_name
+            account['domain_name'] = test_resource.domain_name
+        else:
+            account['project_name'] = test_resource.tenant_name
+
+        # If the spec includes 'admin' credentials are defined via type,
+        # else they are defined via list of roles.
+        if cred_type == 'admin':
+            account['types'] = [cred_type]
+        elif cred_type not in ['primary', 'alt']:
+            account['roles'] = cred_type
+
+        if test_resource.network:
             account['resources'] = {}
-        if 'network' in user:
-            account['resources']['network'] = user['network']
-        if 'router' in user:
-            account['resources']['router'] = user['router']
+        if test_resource.network:
+            account['resources']['network'] = test_resource.network['name']
         accounts.append(account)
-    if os.path.exists(opts.accounts):
-        os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
-    with open(opts.accounts, 'w') as f:
-        yaml.dump(accounts, f, default_flow_style=False)
-    LOG.info('%s generated successfully!' % opts.accounts)
+    if os.path.exists(account_file):
+        os.rename(account_file, '.'.join((account_file, 'bak')))
+    with open(account_file, 'w') as f:
+        yaml.safe_dump(accounts, f, default_flow_style=False)
+    LOG.info('%s generated successfully!' % account_file)
 
 
 def _parser_add_args(parser):
@@ -420,10 +215,18 @@
                         metavar='<auth-password>',
                         default=os.environ.get('OS_PASSWORD'),
                         help='Defaults to env[OS_PASSWORD].')
+    parser.add_argument('--os-project-name',
+                        metavar='<auth-project-name>',
+                        default=os.environ.get('OS_PROJECT_NAME'),
+                        help='Defaults to env[OS_PROJECT_NAME].')
     parser.add_argument('--os-tenant-name',
                         metavar='<auth-tenant-name>',
                         default=os.environ.get('OS_TENANT_NAME'),
                         help='Defaults to env[OS_TENANT_NAME].')
+    parser.add_argument('--os-domain-name',
+                        metavar='<auth-domain-name>',
+                        default=os.environ.get('OS_DOMAIN_NAME'),
+                        help='Defaults to env[OS_DOMAIN_NAME].')
     parser.add_argument('--tag',
                         default='',
                         required=False,
@@ -432,13 +235,20 @@
     parser.add_argument('-r', '--concurrency',
                         default=1,
                         type=int,
-                        required=True,
+                        required=False,
                         dest='concurrency',
                         help='Concurrency count')
     parser.add_argument('--with-admin',
                         action='store_true',
                         dest='admin',
                         help='Creates admin for each concurrent group')
+    parser.add_argument('-i', '--identity-version',
+                        default=3,
+                        choices=[2, 3],
+                        type=int,
+                        required=False,
+                        dest='identity_version',
+                        help='Version of the Identity API to use')
     parser.add_argument('accounts',
                         metavar='accounts_file.yaml',
                         help='Output accounts yaml file')
@@ -468,12 +278,11 @@
 
     def take_action(self, parsed_args):
         try:
-            return main(parsed_args)
+            main(parsed_args)
         except Exception:
             LOG.exception("Failure generating test accounts.")
             traceback.print_exc()
             raise
-        return 0
 
     def get_description(self):
         return DESCRIPTION
@@ -487,9 +296,16 @@
         opts = get_options()
     if opts.config_file:
         config.CONF.set_config_path(opts.config_file)
-    resources = generate_resources(opts)
-    create_resources(opts, resources)
-    dump_accounts(opts, resources)
+    if opts.os_tenant_name:
+        LOG.warning("'os-tenant-name' and 'OS_TENANT_NAME' are both "
+                    "deprecated, please use 'os-project-name' or "
+                    "'OS_PROJECT_NAME' instead")
+    resources = []
+    for count in range(opts.concurrency):
+        # Use N different cred_providers to obtain different sets of creds
+        cred_provider = get_credential_provider(opts)
+        resources.extend(generate_resources(cred_provider, opts.admin))
+    dump_accounts(resources, opts.identity_version, opts.accounts)
 
 if __name__ == "__main__":
     main()
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index caba4b5..e8e691e 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -83,7 +83,6 @@
             LOG.exception("Failure during cleanup")
             traceback.print_exc()
             raise
-        return 0
 
     def init(self, parsed_args):
         cleanup_service.init_conf()
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 633b9e9..77d62d3 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -21,6 +21,8 @@
 from oslo_log import log as logging
 from six import moves
 
+from tempest.cmd.workspace import WorkspaceManager
+
 LOG = logging.getLogger(__name__)
 
 TESTR_CONF = """[DEFAULT]
@@ -89,6 +91,10 @@
                             action='store_true', dest='show_global_dir',
                             help="Print the global config dir location, "
                                  "then exit")
+        parser.add_argument('--name', help="The workspace name", default=None)
+        parser.add_argument('--workspace-path', default=None,
+                            help="The path to the workspace file, the default "
+                                 "is ~/.tempest/workspace")
         return parser
 
     def generate_testr_conf(self, local_path):
@@ -114,15 +120,22 @@
             config_parse.write(conf_file)
 
     def copy_config(self, etc_dir, config_dir):
-        shutil.copytree(config_dir, etc_dir)
+        if os.path.isdir(config_dir):
+            shutil.copytree(config_dir, etc_dir)
+        else:
+            LOG.warning("Global config dir %s can't be found" % config_dir)
 
     def generate_sample_config(self, local_dir, config_dir):
-        conf_generator = os.path.join(config_dir,
-                                      'config-generator.tempest.conf')
+        if os.path.isdir(config_dir):
+            conf_generator = os.path.join(config_dir,
+                                          'config-generator.tempest.conf')
 
-        subprocess.call(['oslo-config-generator', '--config-file',
-                         conf_generator],
-                        cwd=local_dir)
+            subprocess.call(['oslo-config-generator', '--config-file',
+                             conf_generator],
+                            cwd=local_dir)
+        else:
+            LOG.warning("Skipping sample config generation because global "
+                        "config dir %s can't be found" % config_dir)
 
     def create_working_dir(self, local_dir, config_dir):
         # Create local dir if missing
@@ -159,6 +172,10 @@
             subprocess.call(['testr', 'init'], cwd=local_dir)
 
     def take_action(self, parsed_args):
+        workspace_manager = WorkspaceManager(parsed_args.workspace_path)
+        name = parsed_args.name or parsed_args.dir.split(os.path.sep)[-1]
+        workspace_manager.register_new_workspace(
+            name, parsed_args.dir, init=True)
         config_dir = parsed_args.config_dir or get_tempest_default_config_dir()
         if parsed_args.show_global_dir:
             print("Global config dir is located at: %s" % config_dir)
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 6a65fcb..7ce6225 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -126,6 +126,7 @@
 from tempest.lib.services.compute import security_group_rules_client
 from tempest.lib.services.compute import security_groups_client
 from tempest.lib.services.compute import servers_client
+from tempest.lib.services.image.v2 import images_client
 from tempest.lib.services.network import networks_client
 from tempest.lib.services.network import ports_client
 from tempest.lib.services.network import routers_client
@@ -134,7 +135,6 @@
 from tempest.services.identity.v2.json import roles_client
 from tempest.services.identity.v2.json import tenants_client
 from tempest.services.identity.v2.json import users_client
-from tempest.services.image.v2.json import images_client
 from tempest.services.object_storage import container_client
 from tempest.services.object_storage import object_client
 from tempest.services.volume.v1.json import volumes_client
@@ -391,8 +391,9 @@
                         % u['name'])
         except lib_exc.NotFound:
             admin.users.create_user(
-                u['name'], u['pass'], tenant['id'],
-                "%s@%s" % (u['name'], tenant['id']),
+                name=u['name'], password=u['pass'],
+                tenantId=tenant['id'],
+                email="%s@%s" % (u['name'], tenant['id']),
                 enabled=True)
 
 
diff --git a/tempest/cmd/list_plugins.py b/tempest/cmd/list_plugins.py
index 1f1ff1a..5f6b3e6 100644
--- a/tempest/cmd/list_plugins.py
+++ b/tempest/cmd/list_plugins.py
@@ -30,7 +30,6 @@
 class TempestListPlugins(command.Command):
     def take_action(self, parsed_args):
         self._list_plugins()
-        return 0
 
     def get_description(self):
         return 'List all tempest plugins'
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
new file mode 100644
index 0000000..b4b7ebb
--- /dev/null
+++ b/tempest/cmd/run.py
@@ -0,0 +1,181 @@
+# 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.
+
+"""
+Runs tempest tests
+
+This command is used for running the tempest tests
+
+Test Selection
+==============
+Tempest run has several options:
+
+ * **--regex/-r**: This is a selection regex like what testr uses. It will run
+                   any tests that match on re.match() with the regex
+ * **--smoke**: Run all the tests tagged as smoke
+
+You can also use the **--list-tests** option in conjunction with selection
+arguments to list which tests will be run.
+
+Test Execution
+==============
+There are several options to control how the tests are executed. By default
+tempest will run in parallel with a worker for each CPU present on the machine.
+If you want to adjust the number of workers use the **--concurrency** option
+and if you want to run tests serially use **--serial**
+
+Test Output
+===========
+By default tempest run's output to STDOUT will be generated using the
+subunit-trace output filter. But, if you would prefer a subunit v2 stream be
+output to STDOUT use the **--subunit** flag
+
+"""
+
+import io
+import os
+import sys
+import threading
+
+from cliff import command
+from os_testr import subunit_trace
+from oslo_log import log as logging
+from testrepository.commands import run_argv
+
+from tempest import config
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class TempestRun(command.Command):
+
+    def _set_env(self):
+        # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
+        # stacktraces on failure.
+        if 'TESTR_PDB' in os.environ:
+            return
+        else:
+            os.environ["TESTR_PDB"] = ""
+
+    def take_action(self, parsed_args):
+        self._set_env()
+        # Local exceution mode
+        if os.path.isfile('.testr.conf'):
+            # If you're running in local execution mode and there is not a
+            # testrepository dir create one
+            if not os.path.isdir('.testrepository'):
+                returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
+                                      sys.stderr)
+            if returncode:
+                sys.exit(returncode)
+        else:
+            print("No .testr.conf file was found for local exceution")
+            sys.exit(2)
+
+        regex = self._build_regex(parsed_args)
+        if parsed_args.list_tests:
+            argv = ['tempest', 'list-tests', regex]
+            returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
+        else:
+            options = self._build_options(parsed_args)
+            returncode = self._run(regex, options)
+        sys.exit(returncode)
+
+    def get_description(self):
+        return 'Run tempest'
+
+    def get_parser(self, prog_name):
+        parser = super(TempestRun, self).get_parser(prog_name)
+        parser = self._add_args(parser)
+        return parser
+
+    def _add_args(self, parser):
+        # test selection args
+        regex = parser.add_mutually_exclusive_group()
+        regex.add_argument('--smoke', action='store_true',
+                           help="Run the smoke tests only")
+        regex.add_argument('--regex', '-r', default='',
+                           help='A normal testr selection regex used to '
+                                'specify a subset of tests to run')
+        # list only args
+        parser.add_argument('--list-tests', '-l', action='store_true',
+                            help='List tests',
+                            default=False)
+        # exectution args
+        parser.add_argument('--concurrency', '-w',
+                            help="The number of workers to use, defaults to "
+                                 "the number of cpus")
+        parallel = parser.add_mutually_exclusive_group()
+        parallel.add_argument('--parallel', dest='parallel',
+                              action='store_true',
+                              help='Run tests in parallel (this is the'
+                                   ' default)')
+        parallel.add_argument('--serial', dest='parallel',
+                              action='store_false',
+                              help='Run tests serially')
+        # output args
+        parser.add_argument("--subunit", action='store_true',
+                            help='Enable subunit v2 output')
+
+        parser.set_defaults(parallel=True)
+        return parser
+
+    def _build_regex(self, parsed_args):
+        regex = ''
+        if parsed_args.smoke:
+            regex = 'smoke'
+        elif parsed_args.regex:
+            regex = parsed_args.regex
+        return regex
+
+    def _build_options(self, parsed_args):
+        options = []
+        if parsed_args.subunit:
+            options.append("--subunit")
+        if parsed_args.parallel:
+            options.append("--parallel")
+        if parsed_args.concurrency:
+            options.append("--concurrency=%s" % parsed_args.concurrency)
+        return options
+
+    def _run(self, regex, options):
+        returncode = 0
+        argv = ['tempest', 'run', regex] + options
+        if '--subunit' in options:
+            returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
+        else:
+            argv.append('--subunit')
+            stdin = io.StringIO()
+            stdout_r, stdout_w = os.pipe()
+            subunit_w = os.fdopen(stdout_w, 'wt')
+            subunit_r = os.fdopen(stdout_r)
+            returncodes = {}
+
+            def run_argv_thread():
+                returncodes['testr'] = run_argv(argv, stdin, subunit_w,
+                                                sys.stderr)
+                subunit_w.close()
+
+            run_thread = threading.Thread(target=run_argv_thread)
+            run_thread.start()
+            returncodes['subunit-trace'] = subunit_trace.trace(subunit_r,
+                                                               sys.stdout)
+            run_thread.join()
+            subunit_r.close()
+            # python version of pipefail
+            if returncodes['testr']:
+                returncode = returncodes['testr']
+            elif returncodes['subunit-trace']:
+                returncode = returncodes['subunit-trace']
+        return returncode
diff --git a/tempest/cmd/run_stress.py b/tempest/cmd/run_stress.py
index 9c8552f..7502c23 100755
--- a/tempest/cmd/run_stress.py
+++ b/tempest/cmd/run_stress.py
@@ -97,7 +97,6 @@
             LOG.exception("Failure in the stress test framework")
             traceback.print_exc()
             raise
-        return 0
 
     def get_description(self):
         return 'Run tempest stress tests'
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 72ea894..4b12ecb 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -384,7 +384,7 @@
     icreds = credentials.get_credentials_provider(
         'verify_tempest_config', network_resources=net_resources)
     try:
-        os = clients.Manager(icreds.get_primary_creds())
+        os = clients.Manager(icreds.get_primary_creds().credentials)
         services = check_service_availability(os, update)
         results = {}
         for service in ['nova', 'cinder', 'neutron', 'swift']:
@@ -418,12 +418,11 @@
 
     def take_action(self, parsed_args):
         try:
-            return main(parsed_args)
+            main(parsed_args)
         except Exception:
             LOG.exception("Failure verifying configuration.")
             traceback.print_exc()
             raise
-        return 0
 
 if __name__ == "__main__":
     main()
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
new file mode 100644
index 0000000..cc82284
--- /dev/null
+++ b/tempest/cmd/workspace.py
@@ -0,0 +1,218 @@
+# Copyright 2016 Rackspace
+#
+# 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.
+
+"""
+Manages Tempest workspaces
+
+This command is used for managing tempest workspaces
+
+Commands
+========
+
+list
+----
+Outputs the name and path of all known tempest workspaces
+
+register
+--------
+Registers a new tempest workspace via a given --name and --path
+
+rename
+------
+Renames a tempest workspace from --old-name to --new-name
+
+move
+----
+Changes the path of a given tempest workspace --name to --path
+
+remove
+------
+Deletes the entry for a given tempest workspace --name
+
+General Options
+===============
+
+ **--workspace_path**: Allows the user to specify a different location for the
+                       workspace.yaml file containing the workspace definitions
+                       instead of ~/.tempest/workspace.yaml
+"""
+
+import os
+import sys
+
+from cliff import command
+from oslo_concurrency import lockutils
+from oslo_log import log as logging
+import prettytable
+import yaml
+
+from tempest import config
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class WorkspaceManager(object):
+    def __init__(self, path=None):
+        lockutils.get_lock_path(CONF)
+        self.path = path or os.path.join(
+            os.path.expanduser("~"), ".tempest", "workspace.yaml")
+        if not os.path.isdir(os.path.dirname(self.path)):
+            os.makedirs(self.path.rsplit(os.path.sep, 1)[0])
+        self.workspaces = {}
+
+    @lockutils.synchronized('workspaces', external=True)
+    def get_workspace(self, name):
+        """Returns the workspace that has the given name"""
+        self._populate()
+        return self.workspaces.get(name)
+
+    @lockutils.synchronized('workspaces', external=True)
+    def rename_workspace(self, old_name, new_name):
+        self._populate()
+        self._name_exists(old_name)
+        self._workspace_name_exists(new_name)
+        self.workspaces[new_name] = self.workspaces.pop(old_name)
+        self._write_file()
+
+    @lockutils.synchronized('workspaces', external=True)
+    def move_workspace(self, name, path):
+        self._populate()
+        path = os.path.abspath(os.path.expanduser(path))
+        self._name_exists(name)
+        self._validate_path(path)
+        self.workspaces[name] = path
+        self._write_file()
+
+    def _name_exists(self, name):
+        if name not in self.workspaces:
+            print("A workspace was not found with name: {0}".format(name))
+            sys.exit(1)
+
+    @lockutils.synchronized('workspaces', external=True)
+    def remove_workspace(self, name):
+        self._populate()
+        self._name_exists(name)
+        self.workspaces.pop(name)
+        self._write_file()
+
+    @lockutils.synchronized('workspaces', external=True)
+    def list_workspaces(self):
+        self._populate()
+        self._validate_workspaces()
+        return self.workspaces
+
+    def _workspace_name_exists(self, name):
+        if name in self.workspaces:
+            print("A workspace already exists with name: {0}.".format(
+                name))
+            sys.exit(1)
+
+    def _validate_path(self, path):
+        if not os.path.exists(path):
+            print("Path does not exist.")
+            sys.exit(1)
+
+    @lockutils.synchronized('workspaces', external=True)
+    def register_new_workspace(self, name, path, init=False):
+        """Adds the new workspace and writes out the new workspace config"""
+        self._populate()
+        path = os.path.abspath(os.path.expanduser(path))
+        # This only happens when register is called from outside of init
+        if not init:
+            self._validate_path(path)
+        self._workspace_name_exists(name)
+        self.workspaces[name] = path
+        self._write_file()
+
+    def _validate_workspaces(self):
+        if self.workspaces is not None:
+            self.workspaces = {n: p for n, p in self.workspaces.items()
+                               if os.path.exists(p)}
+            self._write_file()
+
+    def _write_file(self):
+        with open(self.path, 'w') as f:
+            f.write(yaml.dump(self.workspaces))
+
+    def _populate(self):
+        if not os.path.isfile(self.path):
+            return
+        with open(self.path, 'r') as f:
+            self.workspaces = yaml.load(f) or {}
+
+
+class TempestWorkspace(command.Command):
+    def take_action(self, parsed_args):
+        self.manager = WorkspaceManager(parsed_args.workspace_path)
+        if getattr(parsed_args, 'register', None):
+            self.manager.register_new_workspace(
+                parsed_args.name, parsed_args.path)
+        elif getattr(parsed_args, 'rename', None):
+            self.manager.rename_workspace(
+                parsed_args.old_name, parsed_args.new_name)
+        elif getattr(parsed_args, 'move', None):
+            self.manager.move_workspace(
+                parsed_args.name, parsed_args.path)
+        elif getattr(parsed_args, 'remove', None):
+            self.manager.remove_workspace(
+                parsed_args.name)
+        else:
+            self._print_workspaces()
+        sys.exit(0)
+
+    def get_description(self):
+        return 'Tempest workspace actions'
+
+    def get_parser(self, prog_name):
+        parser = super(TempestWorkspace, self).get_parser(prog_name)
+
+        parser.add_argument(
+            '--workspace-path', required=False, default=None,
+            help="The path to the workspace file, the default is "
+                 "~/.tempest/workspace.yaml")
+
+        subparsers = parser.add_subparsers()
+
+        list_parser = subparsers.add_parser('list')
+        list_parser.set_defaults(list=True)
+
+        register_parser = subparsers.add_parser('register')
+        register_parser.add_argument('--name', required=True)
+        register_parser.add_argument('--path', required=True)
+        register_parser.set_defaults(register=True)
+
+        update_parser = subparsers.add_parser('rename')
+        update_parser.add_argument('--old-name', required=True)
+        update_parser.add_argument('--new-name', required=True)
+        update_parser.set_defaults(rename=True)
+
+        move_parser = subparsers.add_parser('move')
+        move_parser.add_argument('--name', required=True)
+        move_parser.add_argument('--path', required=True)
+        move_parser.set_defaults(move=True)
+
+        remove_parser = subparsers.add_parser('remove')
+        remove_parser.add_argument('--name', required=True)
+        remove_parser.set_defaults(remove=True)
+
+        return parser
+
+    def _print_workspaces(self):
+        output = prettytable.PrettyTable(["Name", "Path"])
+        if self.manager.list_workspaces() is not None:
+            for name, path in self.manager.list_workspaces().items():
+                output.add_row([name, path])
+
+        print(output)
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index b23bc6f..48d81ca 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -40,8 +40,9 @@
         self.roles_client = roles_client
 
     def create_user(self, username, password, project, email):
-        user = self.users_client.create_user(
-            username, password, project['id'], email)
+        params = self._create_user_params(username, password,
+                                          project['id'], email)
+        user = self.users_client.create_user(**params)
         if 'user' in user:
             user = user['user']
         return user
@@ -101,6 +102,13 @@
                                             users_client,
                                             roles_client)
 
+    def _create_user_params(self, username, password, project_id, email):
+        params = {'name': username,
+                  'password': password,
+                  'tenantId': project_id,
+                  'email': email}
+        return params
+
     def create_project(self, name, description):
         tenant = self.projects_client.create_tenant(
             name=name, description=description)['tenant']
@@ -143,6 +151,13 @@
             msg = "Requested domain %s could not be found" % domain_name
             raise lib_exc.InvalidCredentials(msg)
 
+    def _create_user_params(self, username, password, project_id, email):
+        params = {'user_name': username,
+                  'password': password,
+                  'project_id': project_id,
+                  'email': email}
+        return params
+
     def create_project(self, name, description):
         project = self.projects_client.create_project(
             name=name, description=description,
@@ -155,6 +170,9 @@
     def get_credentials(self, user, project, password):
         # User, project and domain already include both ID and name here,
         # so there's no need to use the fill_in mode.
+        # NOTE(andreaf) We need to set all fields in the returned credentials.
+        # Scope is then used to pick only those relevant for the type of
+        # token needed by each service client.
         return auth.get_credentials(
             auth_url=None,
             fill_in=False,
@@ -163,7 +181,9 @@
             project_name=project['name'], project_id=project['id'],
             password=password,
             project_domain_id=self.creds_domain['id'],
-            project_domain_name=self.creds_domain['name'])
+            project_domain_name=self.creds_domain['name'],
+            domain_id=self.creds_domain['id'],
+            domain_name=self.creds_domain['name'])
 
     def _assign_user_role(self, project, user, role):
         self.roles_client.assign_user_role_on_project(project['id'],
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index 5cce0bb..bf6c537 100644
--- a/tempest/common/cred_provider.py
+++ b/tempest/common/cred_provider.py
@@ -16,8 +16,8 @@
 
 import six
 
-from tempest import exceptions
 from tempest.lib import auth
+from tempest.lib import exceptions
 
 
 @six.add_metaclass(abc.ABCMeta)
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 286e01f..c22afc1 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -39,19 +39,19 @@
 
 
 # Subset of the parameters of credential providers that depend on configuration
-def _get_common_provider_params():
+def get_common_provider_params():
     return {
         'credentials_domain': CONF.auth.default_credentials_domain_name,
         'admin_role': CONF.identity.admin_role
     }
 
 
-def _get_dynamic_provider_params():
-    return _get_common_provider_params()
+def get_dynamic_provider_params():
+    return get_common_provider_params()
 
 
-def _get_preprov_provider_params():
-    _common_params = _get_common_provider_params()
+def get_preprov_provider_params():
+    _common_params = get_common_provider_params()
     reseller_admin_role = CONF.object_storage.reseller_admin_role
     return dict(_common_params, **dict([
         ('accounts_lock_dir', lockutils.get_lock_path(CONF)),
@@ -80,13 +80,13 @@
             network_resources=network_resources,
             identity_version=identity_version,
             admin_creds=admin_creds,
-            **_get_dynamic_provider_params())
+            **get_dynamic_provider_params())
     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,
-                **_get_preprov_provider_params())
+                **get_preprov_provider_params())
         else:
             raise exceptions.InvalidConfiguration(
                 'A valid credential provider is needed')
@@ -106,7 +106,7 @@
     elif CONF.auth.test_accounts_file:
         check_accounts = preprov_creds.PreProvisionedCredentialProvider(
             identity_version=identity_version, name='check_admin',
-            **_get_preprov_provider_params())
+            **get_preprov_provider_params())
         if not check_accounts.admin_available():
             is_admin = False
     else:
@@ -131,7 +131,7 @@
     if CONF.auth.test_accounts_file:
         check_accounts = preprov_creds.PreProvisionedCredentialProvider(
             identity_version=identity_version, name='check_alt',
-            **_get_preprov_provider_params())
+            **get_preprov_provider_params())
     else:
         raise exceptions.InvalidConfiguration(
             'A valid credential provider is needed')
@@ -193,17 +193,21 @@
 
 
 # Wrapper around auth.get_credentials to use the configured identity version
-# is none is specified
+# if none is specified
 def get_credentials(fill_in=True, identity_version=None, **kwargs):
     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
+    # To honour the "default_credentials_domain_name", if not domain
+    # field is specified at all, add it the credential dict.
     if identity_version == 'v3':
         domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
                             if 'domain' in x)
         if not domain_fields.intersection(kwargs.keys()):
             domain_name = CONF.auth.default_credentials_domain_name
-            params['user_domain_name'] = domain_name
+            # NOTE(andreaf) Setting domain_name implicitly sets user and
+            # project domain names, if they are None
+            params['domain_name'] = domain_name
 
         auth_url = CONF.identity.uri_v3
     else:
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index 3569183..b0c01f5 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -96,8 +96,15 @@
                     os.networks_client, os.routers_client, os.subnets_client,
                     os.ports_client, os.security_groups_client)
         else:
-            return (os.identity_v3_client, os.projects_client,
-                    os.users_v3_client, os.roles_v3_client, os.domains_client,
+            # We use a dedicated client manager for identity client in case we
+            # need a different token scope for them.
+            scope = 'domain' if CONF.identity.admin_domain_scope else 'project'
+            identity_os = clients.Manager(self.default_admin_creds,
+                                          scope=scope)
+            return (identity_os.identity_v3_client,
+                    identity_os.projects_client,
+                    identity_os.users_v3_client, identity_os.roles_v3_client,
+                    identity_os.domains_client,
                     os.networks_client, os.routers_client,
                     os.subnets_client, os.ports_client,
                     os.security_groups_client)
@@ -135,7 +142,8 @@
         if admin:
             self.creds_client.assign_user_role(user, project, self.admin_role)
             role_assigned = True
-            if self.identity_version == 'v3':
+            if (self.identity_version == 'v3' and
+                    CONF.identity.admin_domain_scope):
                 self.creds_client.assign_user_role_on_domain(
                     user, CONF.identity.admin_role)
         # Add roles specified in config file
diff --git a/tempest/common/image.py b/tempest/common/image.py
new file mode 100644
index 0000000..42ce5ac
--- /dev/null
+++ b/tempest/common/image.py
@@ -0,0 +1,38 @@
+# Copyright 2016 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.
+
+
+def get_image_meta_from_headers(resp):
+    meta = {'properties': {}}
+    for key in resp.response:
+        value = resp.response[key]
+        if key.startswith('x-image-meta-property-'):
+            _key = key[22:]
+            meta['properties'][_key] = value
+        elif key.startswith('x-image-meta-'):
+            _key = key[13:]
+            meta[_key] = value
+
+    for key in ['is_public', 'protected', 'deleted']:
+        if key in meta:
+            meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes', '1')
+
+    for key in ['size', 'min_ram', 'min_disk']:
+        if key in meta:
+            try:
+                meta[key] = int(meta[key])
+            except ValueError:
+                pass
+    return meta
diff --git a/tempest/common/preprov_creds.py b/tempest/common/preprov_creds.py
index 42c69d5..5992d24 100644
--- a/tempest/common/preprov_creds.py
+++ b/tempest/common/preprov_creds.py
@@ -86,10 +86,8 @@
         self.test_accounts_file = test_accounts_file
         if test_accounts_file:
             accounts = read_accounts_yaml(self.test_accounts_file)
-            self.use_default_creds = False
         else:
-            accounts = {}
-            self.use_default_creds = True
+            raise lib_exc.InvalidCredentials("No accounts file specified")
         self.hash_dict = self.get_hash_dict(
             accounts, admin_role, object_storage_operator_role,
             object_storage_reseller_admin_role)
@@ -165,12 +163,7 @@
         return hash_dict
 
     def is_multi_user(self):
-        # Default credentials is not a valid option with locking Account
-        if self.use_default_creds:
-            raise lib_exc.InvalidCredentials(
-                "Account file %s doesn't exist" % self.test_accounts_file)
-        else:
-            return len(self.hash_dict['creds']) > 1
+        return len(self.hash_dict['creds']) > 1
 
     def is_multi_tenant(self):
         return self.is_multi_user()
@@ -245,9 +238,6 @@
         return temp_creds
 
     def _get_creds(self, roles=None):
-        if self.use_default_creds:
-            raise lib_exc.InvalidCredentials(
-                "Account file %s doesn't exist" % self.test_accounts_file)
         useable_hashes = self._get_match_hash_list(roles)
         if len(useable_hashes) == 0:
             msg = 'No users configured for type/roles %s' % roles
@@ -329,12 +319,9 @@
         return self.get_creds_by_roles([self.admin_role])
 
     def is_role_available(self, role):
-        if self.use_default_creds:
-            return False
-        else:
-            if self.hash_dict['roles'].get(role):
-                return True
-            return False
+        if self.hash_dict['roles'].get(role):
+            return True
+        return False
 
     def admin_available(self):
         return self.is_role_available(self.admin_role)
diff --git a/tempest/common/validation_resources.py b/tempest/common/validation_resources.py
index c3c9a41..a55ee32 100644
--- a/tempest/common/validation_resources.py
+++ b/tempest/common/validation_resources.py
@@ -22,6 +22,26 @@
 LOG = logging.getLogger(__name__)
 
 
+def _create_neutron_sec_group_rules(os, sec_group):
+    sec_group_rules_client = os.security_group_rules_client
+    ethertype = 'IPv4'
+    if CONF.validation.ip_version_for_ssh == 6:
+        ethertype = 'IPv6'
+
+    sec_group_rules_client.create_security_group_rule(
+        security_group_id=sec_group['id'],
+        protocol='tcp',
+        ethertype=ethertype,
+        port_range_min=22,
+        port_range_max=22,
+        direction='ingress')
+    sec_group_rules_client.create_security_group_rule(
+        security_group_id=sec_group['id'],
+        protocol='icmp',
+        ethertype=ethertype,
+        direction='ingress')
+
+
 def create_ssh_security_group(os, add_rule=False):
     security_groups_client = os.compute_security_groups_client
     security_group_rules_client = os.compute_security_group_rules_client
@@ -30,12 +50,15 @@
     security_group = security_groups_client.create_security_group(
         name=sg_name, description=sg_description)['security_group']
     if add_rule:
-        security_group_rules_client.create_security_group_rule(
-            parent_group_id=security_group['id'], ip_protocol='tcp',
-            from_port=22, to_port=22)
-        security_group_rules_client.create_security_group_rule(
-            parent_group_id=security_group['id'], ip_protocol='icmp',
-            from_port=-1, to_port=-1)
+        if CONF.service_available.neutron:
+            _create_neutron_sec_group_rules(os, security_group)
+        else:
+            security_group_rules_client.create_security_group_rule(
+                parent_group_id=security_group['id'], ip_protocol='tcp',
+                from_port=22, to_port=22)
+            security_group_rules_client.create_security_group_rule(
+                parent_group_id=security_group['id'], ip_protocol='icmp',
+                from_port=-1, to_port=-1)
     LOG.debug("SSH Validation resource security group with tcp and icmp "
               "rules %s created"
               % sg_name)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 23d7f88..d8dad69 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -15,6 +15,7 @@
 
 from oslo_log import log as logging
 
+from tempest.common import image as common_image
 from tempest import config
 from tempest import exceptions
 from tempest.lib.common.utils import misc as misc_utils
@@ -127,7 +128,11 @@
         # The 'check_image' method is used here because the show_image method
         # returns image details plus the image itself which is very expensive.
         # The 'check_image' method returns just image details.
-        show_image = client.check_image
+        def _show_image_v1(image_id):
+            resp = client.check_image(image_id)
+            return common_image.get_image_meta_from_headers(resp)
+
+        show_image = _show_image_v1
     else:
         show_image = client.show_image
 
diff --git a/tempest/config.py b/tempest/config.py
index 3810ceb..a9cf537 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -166,6 +166,10 @@
     cfg.StrOpt('default_domain_id',
                default='default',
                help="ID of the default domain"),
+    cfg.BoolOpt('admin_domain_scope',
+                default=False,
+                help="Whether keystone identity v3 policy required "
+                     "a domain scoped token to use admin APIs")
 ]
 
 identity_feature_group = cfg.OptGroup(name='identity-feature-enabled',
@@ -678,6 +682,24 @@
     cfg.IntOpt('volume_size',
                default=1,
                help='Default size in GB for volumes created by volumes tests'),
+    cfg.StrOpt('min_microversion',
+               default=None,
+               help="Lower version of the test target microversion range. "
+                    "The format is 'X.Y', where 'X' and 'Y' are int values. "
+                    "Tempest selects tests based on the range between "
+                    "min_microversion and max_microversion. "
+                    "If both values are not specified, Tempest avoids tests "
+                    "which require a microversion. Valid values are string "
+                    "with format 'X.Y' or string 'latest'",),
+    cfg.StrOpt('max_microversion',
+               default=None,
+               help="Upper version of the test target microversion range. "
+                    "The format is 'X.Y', where 'X' and 'Y' are int values. "
+                    "Tempest selects tests based on the range between "
+                    "min_microversion and max_microversion. "
+                    "If both values are not specified, Tempest avoids tests "
+                    "which require a microversion. Valid values are string "
+                    "with format 'X.Y' or string 'latest'",),
 ]
 
 volume_feature_group = cfg.OptGroup(name='volume-feature-enabled',
@@ -707,6 +729,9 @@
     cfg.BoolOpt('api_v2',
                 default=True,
                 help="Is the v2 volume API enabled"),
+    cfg.BoolOpt('api_v3',
+                default=False,
+                help="Is the v3 volume API enabled"),
     cfg.BoolOpt('bootable',
                 default=True,
                 help='Update bootable status of a volume '
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 92f335f..f534f30 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -13,157 +13,65 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
+
+from tempest.lib import exceptions
 
 
-class TempestException(Exception):
-    """Base Tempest Exception
-
-    To correctly use this class, inherit from it and define
-    a 'message' property. That message will get printf'd
-    with the keyword arguments provided to the constructor.
-    """
-    message = "An unknown exception occurred"
-
-    def __init__(self, *args, **kwargs):
-        super(TempestException, self).__init__()
-        try:
-            self._error_string = self.message % kwargs
-        except Exception:
-            # at least get the core message out if something happened
-            self._error_string = self.message
-        if len(args) > 0:
-            # If there is a non-kwarg parameter, assume it's the error
-            # message or reason description and tack it on to the end
-            # of the exception message
-            # Convert all arguments into their string representations...
-            args = ["%s" % arg for arg in args]
-            self._error_string = (self._error_string +
-                                  "\nDetails: %s" % '\n'.join(args))
-
-    def __str__(self):
-        return self._error_string
-
-
-class RestClientException(TempestException,
-                          testtools.TestCase.failureException):
-    pass
-
-
-class InvalidConfiguration(TempestException):
+class InvalidConfiguration(exceptions.TempestException):
     message = "Invalid Configuration"
 
 
-class InvalidCredentials(TempestException):
-    message = "Invalid Credentials"
-
-
-class InvalidServiceTag(TempestException):
+class InvalidServiceTag(exceptions.TempestException):
     message = "Invalid service tag"
 
 
-class InvalidIdentityVersion(TempestException):
-    message = "Invalid version %(identity_version)s of the identity service"
-
-
-class TimeoutException(TempestException):
+class TimeoutException(exceptions.TempestException):
     message = "Request timed out"
 
 
-class BuildErrorException(TempestException):
+class BuildErrorException(exceptions.TempestException):
     message = "Server %(server_id)s failed to build and is in ERROR status"
 
 
-class ImageKilledException(TempestException):
+class ImageKilledException(exceptions.TempestException):
     message = "Image %(image_id)s 'killed' while waiting for '%(status)s'"
 
 
-class AddImageException(TempestException):
+class AddImageException(exceptions.TempestException):
     message = "Image %(image_id)s failed to become ACTIVE in the allotted time"
 
 
-class VolumeBuildErrorException(TempestException):
+class VolumeBuildErrorException(exceptions.TempestException):
     message = "Volume %(volume_id)s failed to build and is in ERROR status"
 
 
-class VolumeRestoreErrorException(TempestException):
+class VolumeRestoreErrorException(exceptions.TempestException):
     message = "Volume %(volume_id)s failed to restore and is in ERROR status"
 
 
-class SnapshotBuildErrorException(TempestException):
+class SnapshotBuildErrorException(exceptions.TempestException):
     message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
 
 
-class VolumeBackupException(TempestException):
+class VolumeBackupException(exceptions.TempestException):
     message = "Volume backup %(backup_id)s failed and is in ERROR status"
 
 
-class StackBuildErrorException(TempestException):
+class StackBuildErrorException(exceptions.TempestException):
     message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
                "due to '%(stack_status_reason)s'")
 
 
-class EndpointNotFound(TempestException):
-    message = "Endpoint not found"
-
-
-class IdentityError(TempestException):
-    message = "Got identity error"
-
-
-class ServerUnreachable(TempestException):
+class ServerUnreachable(exceptions.TempestException):
     message = "The server is not reachable via 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(TempestException):
+class InvalidTestResource(exceptions.TempestException):
     message = "%(name) is not a valid %(type), or the name is ambiguous"
 
 
-class RFCViolation(RestClientException):
+class RFCViolation(exceptions.RestClientException):
     message = "RFC Violation"
-
-
-class InvalidHttpSuccessCode(RestClientException):
-    message = "The success code is different than the expected one"
-
-
-class BadRequest(RestClientException):
-    message = "Bad request"
-
-
-class ResponseWithNonEmptyBody(RFCViolation):
-    message = ("RFC Violation! Response with %(status)d HTTP Status Code "
-               "MUST NOT have a body")
-
-
-class ResponseWithEntity(RFCViolation):
-    message = ("RFC Violation! Response with 205 HTTP Status Code "
-               "MUST NOT have an entity")
-
-
-class InvalidHTTPResponseHeader(RestClientException):
-    message = "HTTP response header is invalid"
-
-
-class InvalidStructure(TempestException):
-    message = "Invalid structure of table with details"
-
-
-class CommandFailed(Exception):
-    def __init__(self, returncode, cmd, output, stderr):
-        super(CommandFailed, self).__init__()
-        self.returncode = returncode
-        self.cmd = cmd
-        self.stdout = output
-        self.stderr = stderr
-
-    def __str__(self):
-        return ("Command '%s' returned non-zero exit status %d.\n"
-                "stdout:\n%s\n"
-                "stderr:\n%s" % (self.cmd,
-                                 self.returncode,
-                                 self.stdout,
-                                 self.stderr))
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 974ba82..425758f 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -1,4 +1,5 @@
 # Copyright 2014 Hewlett-Packard Development Company, L.P.
+# Copyright 2016 Rackspace Inc.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -179,7 +180,7 @@
         :param headers: HTTP headers of the request
         :param body: HTTP body in case of POST / PUT
         :param filters: select a base URL out of the catalog
-        :returns a Tuple (url, headers, body)
+        :return: a Tuple (url, headers, body)
         """
         orig_req = dict(url=url, headers=headers, body=body)
 
@@ -223,6 +224,7 @@
         Configure auth provider to provide alt authentication data
         on a part of the *next* auth_request. If credentials are None,
         set invalid data.
+
         :param request_part: request part to contain invalid auth: url,
                              headers, body
         :param auth_data: alternative auth_data from which to get the
@@ -260,7 +262,7 @@
                  disable_ssl_certificate_validation=None,
                  ca_certs=None, trace_requests=None, scope='project'):
         super(KeystoneAuthProvider, self).__init__(credentials, scope)
-        self.dsvm = disable_ssl_certificate_validation
+        self.dscv = disable_ssl_certificate_validation
         self.ca_certs = ca_certs
         self.trace_requests = trace_requests
         self.auth_url = auth_url
@@ -339,7 +341,7 @@
 
     def _auth_client(self, auth_url):
         return json_v2id.TokenClient(
-            auth_url, disable_ssl_certificate_validation=self.dsvm,
+            auth_url, disable_ssl_certificate_validation=self.dscv,
             ca_certs=self.ca_certs, trace_requests=self.trace_requests)
 
     def _auth_params(self):
@@ -368,18 +370,27 @@
     def base_url(self, filters, auth_data=None):
         """Base URL from catalog
 
+        :param filters: Used to filter results
+
         Filters can be:
-        - service: compute, image, etc
-        - region: the service region
-        - endpoint_type: adminURL, publicURL, internalURL
-        - api_version: replace catalog version with this
-        - skip_path: take just the base URL
+
+        - service: service type name such as compute, image, etc.
+        - region: service region name
+        - name: service name, only if service exists
+        - endpoint_type: type of endpoint such as
+            adminURL, publicURL, internalURL
+        - api_version: the version of api used to replace catalog version
+        - skip_path: skips the suffix path of the url and uses base URL
+
+        :rtype: string
+        :return: url with filters applied
         """
         if auth_data is None:
             auth_data = self.get_auth()
         token, _auth_data = auth_data
         service = filters.get('service')
         region = filters.get('region')
+        name = filters.get('name')
         endpoint_type = filters.get('endpoint_type', 'publicURL')
 
         if service is None:
@@ -388,17 +399,19 @@
         _base_url = None
         for ep in _auth_data['serviceCatalog']:
             if ep["type"] == service:
+                if name is not None and ep["name"] != name:
+                    continue
                 for _ep in ep['endpoints']:
                     if region is not None and _ep['region'] == region:
                         _base_url = _ep.get(endpoint_type)
                 if not _base_url:
-                    # No region matching, use the first
+                    # No region or name matching, use the first
                     _base_url = ep['endpoints'][0].get(endpoint_type)
                 break
         if _base_url is None:
             raise exceptions.EndpointNotFound(
-                "service: %s, region: %s, endpoint_type: %s" %
-                (service, region, endpoint_type))
+                "service: %s, region: %s, endpoint_type: %s, name: %s" %
+                (service, region, endpoint_type, name))
         return apply_url_filters(_base_url, filters)
 
     def is_expired(self, auth_data):
@@ -415,7 +428,7 @@
 
     def _auth_client(self, auth_url):
         return json_v3id.V3TokenClient(
-            auth_url, disable_ssl_certificate_validation=self.dsvm,
+            auth_url, disable_ssl_certificate_validation=self.dscv,
             ca_certs=self.ca_certs, trace_requests=self.trace_requests)
 
     def _auth_params(self):
@@ -489,18 +502,27 @@
         the auth_data. In such case, as long as the requested service is
         'identity', we can use the original auth URL to build the base_url.
 
+        :param filters: Used to filter results
+
         Filters can be:
-        - service: compute, image, etc
-        - region: the service region
-        - endpoint_type: adminURL, publicURL, internalURL
-        - api_version: replace catalog version with this
-        - skip_path: take just the base URL
+
+        - service: service type name such as compute, image, etc.
+        - region: service region name
+        - name: service name, only if service exists
+        - endpoint_type: type of endpoint such as
+            adminURL, publicURL, internalURL
+        - api_version: the version of api used to replace catalog version
+        - skip_path: skips the suffix path of the url and uses base URL
+
+        :rtype: string
+        :return: url with filters applied
         """
         if auth_data is None:
             auth_data = self.get_auth()
         token, _auth_data = auth_data
         service = filters.get('service')
         region = filters.get('region')
+        name = filters.get('name')
         endpoint_type = filters.get('endpoint_type', 'public')
 
         if service is None:
@@ -510,19 +532,38 @@
             endpoint_type = endpoint_type.replace('URL', '')
         _base_url = None
         catalog = _auth_data.get('catalog', [])
+
         # Select entries with matching service type
         service_catalog = [ep for ep in catalog if ep['type'] == service]
         if len(service_catalog) > 0:
-            service_catalog = service_catalog[0]['endpoints']
+            if name is not None:
+                service_catalog = (
+                    [ep for ep in service_catalog if ep['name'] == name])
+                if len(service_catalog) > 0:
+                    service_catalog = service_catalog[0]['endpoints']
+                else:
+                    raise exceptions.EndpointNotFound(name)
+            else:
+                service_catalog = service_catalog[0]['endpoints']
         else:
             if len(catalog) == 0 and service == 'identity':
                 # NOTE(andreaf) If there's no catalog at all and the service
                 # is identity, it's a valid use case. Having a non-empty
                 # catalog with no identity in it is not valid instead.
+                msg = ('Got an empty catalog. Scope: %s. '
+                       'Falling back to configured URL for %s: %s')
+                LOG.debug(msg, self.scope, service, self.auth_url)
                 return apply_url_filters(self.auth_url, filters)
             else:
                 # No matching service
-                raise exceptions.EndpointNotFound(service)
+                msg = ('No matching service found in the catalog.\n'
+                       'Scope: %s, Credentials: %s\n'
+                       'Auth data: %s\n'
+                       'Service: %s, Region: %s, endpoint_type: %s\n'
+                       'Catalog: %s')
+                raise exceptions.EndpointNotFound(msg % (
+                    self.scope, self.credentials, _auth_data, service, region,
+                    endpoint_type, catalog))
         # Filter by endpoint type (interface)
         filtered_catalog = [ep for ep in service_catalog if
                             ep['interface'] == endpoint_type]
@@ -533,7 +574,7 @@
         filtered_catalog = [ep for ep in filtered_catalog if
                             ep['region'] == region]
         if len(filtered_catalog) == 0:
-            # No matching region, take the first endpoint
+            # No matching region (or name), take the first endpoint
             filtered_catalog = [service_catalog[0]]
         # There should be only one match. If not take the first.
         _base_url = filtered_catalog[0].get('url', None)
@@ -590,9 +631,9 @@
     creds = credential_class(**kwargs)
     # Fill in the credentials fields that were not specified
     if fill_in:
-        dsvm = disable_ssl_certificate_validation
+        dscv = disable_ssl_certificate_validation
         auth_provider = auth_provider_class(
-            creds, auth_url, disable_ssl_certificate_validation=dsvm,
+            creds, auth_url, disable_ssl_certificate_validation=dscv,
             ca_certs=ca_certs, trace_requests=trace_requests)
         creds = auth_provider.fill_credentials()
     return creds
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index bf107d2..1b0f53a 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -54,6 +54,8 @@
     :param auth_provider: an auth provider object used to wrap requests in auth
     :param str service: The service name to use for the catalog lookup
     :param str region: The region to use for the catalog lookup
+    :param str name: The endpoint name to use for the catalog lookup; this
+                     returns only if the service exists
     :param str endpoint_type: The endpoint type to use for the catalog lookup
     :param int build_interval: Time in seconds between to status checks in
                                wait loops
@@ -76,10 +78,11 @@
                  endpoint_type='publicURL',
                  build_interval=1, build_timeout=60,
                  disable_ssl_certificate_validation=False, ca_certs=None,
-                 trace_requests=''):
+                 trace_requests='', name=None):
         self.auth_provider = auth_provider
         self.service = service
         self.region = region
+        self.name = name
         self.endpoint_type = endpoint_type
         self.build_interval = build_interval
         self.build_timeout = build_timeout
@@ -191,7 +194,8 @@
         _filters = dict(
             service=self.service,
             endpoint_type=self.endpoint_type,
-            region=self.region
+            region=self.region,
+            name=self.name
         )
         if self.api_version is not None:
             _filters['api_version'] = self.api_version
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 45e5067..f9f0c83 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -19,6 +19,7 @@
 import string
 import uuid
 
+from oslo_utils import netutils
 import six.moves
 
 
@@ -183,7 +184,7 @@
     :rtype: netaddr.IPAddress
     """
     # Check if the prefix is IPv4 address
-    is_ipv4 = netaddr.valid_ipv4(cidr)
+    is_ipv4 = netutils.is_valid_ipv4(cidr)
     if is_ipv4:
         msg = "Unable to generate IP address by EUI64 for IPv4 prefix"
         raise TypeError(msg)
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 259bbbb..2a6a788 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -149,6 +149,10 @@
     message = "Unexpected response code received"
 
 
+class InvalidIdentityVersion(TempestException):
+    message = "Invalid version %(identity_version)s of the identity service"
+
+
 class InvalidStructure(TempestException):
     message = "Invalid structure of table with details"
 
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 8e4eca1..0d31ac7 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -315,7 +315,11 @@
         return self.action(server_id, 'os-start', **kwargs)
 
     def attach_volume(self, server_id, **kwargs):
-        """Attaches a volume to a server instance."""
+        """Attaches a volume to a server instance.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-compute-v2.1.html#attachVolume
+        """
         post_body = json.dumps({'volumeAttachment': kwargs})
         resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
                                post_body)
diff --git a/tempest/services/identity/v2/json/endpoints_client.py b/tempest/lib/services/identity/v2/endpoints_client.py
similarity index 77%
rename from tempest/services/identity/v2/json/endpoints_client.py
rename to tempest/lib/services/identity/v2/endpoints_client.py
index ba9f867..f7b265d 100644
--- a/tempest/services/identity/v2/json/endpoints_client.py
+++ b/tempest/lib/services/identity/v2/endpoints_client.py
@@ -20,16 +20,14 @@
 class EndpointsClient(rest_client.RestClient):
     api_version = "v2.0"
 
-    def create_endpoint(self, service_id, region_id, **kwargs):
-        """Create an endpoint for service."""
-        post_body = {
-            'service_id': service_id,
-            'region': region_id,
-            'publicurl': kwargs.get('publicurl'),
-            'adminurl': kwargs.get('adminurl'),
-            'internalurl': kwargs.get('internalurl')
-        }
-        post_body = json.dumps({'endpoint': post_body})
+    def create_endpoint(self, **kwargs):
+        """Create an endpoint for service.
+
+        Available params: http://developer.openstack.org/
+                          api-ref-identity-v2-ext.html#createEndpoint
+        """
+
+        post_body = json.dumps({'endpoint': kwargs})
         resp, body = self.post('/endpoints', post_body)
         self.expected_success(200, resp.status)
         body = json.loads(body)
diff --git a/tempest/services/image/v2/__init__.py b/tempest/lib/services/image/__init__.py
similarity index 100%
copy from tempest/services/image/v2/__init__.py
copy to tempest/lib/services/image/__init__.py
diff --git a/tempest/services/image/v2/__init__.py b/tempest/lib/services/image/v2/__init__.py
similarity index 100%
rename from tempest/services/image/v2/__init__.py
rename to tempest/lib/services/image/v2/__init__.py
diff --git a/tempest/lib/services/image/v2/image_members_client.py b/tempest/lib/services/image/v2/image_members_client.py
new file mode 100644
index 0000000..2ae7516
--- /dev/null
+++ b/tempest/lib/services/image/v2/image_members_client.py
@@ -0,0 +1,64 @@
+#    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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ImageMembersClient(rest_client.RestClient):
+    api_version = "v2"
+
+    def list_image_members(self, image_id):
+        url = 'images/%s/members' % image_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_image_member(self, image_id, **kwargs):
+        """Create an image member.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v2.html#createImageMember-v2
+        """
+        url = 'images/%s/members' % image_id
+        data = json.dumps(kwargs)
+        resp, body = self.post(url, data)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_image_member(self, image_id, member_id, **kwargs):
+        """Update an image member.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v2.html#updateImageMember-v2
+        """
+        url = 'images/%s/members/%s' % (image_id, member_id)
+        data = json.dumps(kwargs)
+        resp, body = self.put(url, data)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_image_member(self, image_id, member_id):
+        url = 'images/%s/members/%s' % (image_id, member_id)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, json.loads(body))
+
+    def delete_image_member(self, image_id, member_id):
+        url = 'images/%s/members/%s' % (image_id, member_id)
+        resp, _ = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
new file mode 100644
index 0000000..71e7c6b
--- /dev/null
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -0,0 +1,133 @@
+# Copyright 2013 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.
+
+import functools
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+CHUNKSIZE = 1024 * 64  # 64kB
+
+
+class ImagesClient(rest_client.RestClient):
+    api_version = "v2"
+
+    def update_image(self, image_id, patch):
+        """Update an image.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v2.html#updateImage-v2
+        """
+        data = json.dumps(patch)
+        headers = {"Content-Type": "application/openstack-images-v2.0"
+                                   "-json-patch"}
+        resp, body = self.patch('images/%s' % image_id, data, headers)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_image(self, **kwargs):
+        """Create an image.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v2.html#createImage-v2
+        """
+        data = json.dumps(kwargs)
+        resp, body = self.post('images', data)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def deactivate_image(self, image_id):
+        url = 'images/%s/actions/deactivate' % image_id
+        resp, body = self.post(url, None)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def reactivate_image(self, image_id):
+        url = 'images/%s/actions/reactivate' % image_id
+        resp, body = self.post(url, None)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_image(self, image_id):
+        url = 'images/%s' % image_id
+        resp, _ = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
+
+    def list_images(self, params=None):
+        url = 'images'
+
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_image(self, image_id):
+        url = 'images/%s' % image_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.show_image(id)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'image'
+
+    def store_image_file(self, image_id, data):
+        url = 'images/%s/file' % image_id
+
+        # We are going to do chunked transfert, so split the input data
+        # info fixed-sized chunks.
+        headers = {'Content-Type': 'application/octet-stream'}
+        data = iter(functools.partial(data.read, CHUNKSIZE), b'')
+
+        resp, body = self.request('PUT', url, headers=headers,
+                                  body=data, chunked=True)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_image_file(self, image_id):
+        url = 'images/%s/file' % image_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBodyData(resp, body)
+
+    def add_image_tag(self, image_id, tag):
+        url = 'images/%s/tags/%s' % (image_id, tag)
+        resp, body = self.put(url, body=None)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_image_tag(self, image_id, tag):
+        url = 'images/%s/tags/%s' % (image_id, tag)
+        resp, _ = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/image/v2/namespaces_client.py b/tempest/lib/services/image/v2/namespaces_client.py
new file mode 100644
index 0000000..97400e1
--- /dev/null
+++ b/tempest/lib/services/image/v2/namespaces_client.py
@@ -0,0 +1,64 @@
+# Copyright 2013 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class NamespacesClient(rest_client.RestClient):
+    api_version = "v2"
+
+    def create_namespace(self, **kwargs):
+        """Create a namespace.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v2.html#createNamespace-v2
+        """
+        data = json.dumps(kwargs)
+        resp, body = self.post('metadefs/namespaces', data)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_namespace(self, namespace):
+        url = 'metadefs/namespaces/%s' % namespace
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_namespace(self, namespace, **kwargs):
+        """Update a namespace.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v2.html#updateNamespace-v2
+        """
+        # NOTE: On Glance API, we need to pass namespace on both URI
+        # and a request body.
+        params = {'namespace': namespace}
+        params.update(kwargs)
+        data = json.dumps(params)
+        url = 'metadefs/namespaces/%s' % namespace
+        resp, body = self.put(url, body=data)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_namespace(self, namespace):
+        url = 'metadefs/namespaces/%s' % namespace
+        resp, _ = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/image/v2/resource_types_client.py b/tempest/lib/services/image/v2/resource_types_client.py
new file mode 100644
index 0000000..1349c63
--- /dev/null
+++ b/tempest/lib/services/image/v2/resource_types_client.py
@@ -0,0 +1,29 @@
+# Copyright 2013 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ResourceTypesClient(rest_client.RestClient):
+    api_version = "v2"
+
+    def list_resource_types(self):
+        url = 'metadefs/resource_types'
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/image/v2/schemas_client.py b/tempest/lib/services/image/v2/schemas_client.py
new file mode 100644
index 0000000..0c9db40
--- /dev/null
+++ b/tempest/lib/services/image/v2/schemas_client.py
@@ -0,0 +1,29 @@
+# Copyright 2013 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class SchemasClient(rest_client.RestClient):
+    api_version = "v2"
+
+    def show_schema(self, schema):
+        url = 'schemas/%s' % schema
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/manager.py b/tempest/manager.py
index cafa5b9..f2659a8 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -13,10 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.common import cred_provider
 from tempest import config
-from tempest import exceptions
 from tempest.lib import auth
+from tempest.lib import exceptions
 
 CONF = config.CONF
 
@@ -34,7 +33,7 @@
         Credentials to be used within the various client classes managed by the
         Manager object must be defined.
 
-        :param credentials: type Credentials or TestResources
+        :param credentials: An instance of `auth.Credentials`
         :param scope: default scope for tokens produced by the auth provider
         """
         self.credentials = credentials
@@ -42,15 +41,9 @@
         if not self.credentials.is_valid():
             raise exceptions.InvalidCredentials()
         self.auth_version = CONF.identity.auth_version
-        # Tenant isolation creates TestResources, but
-        # PreProvisionedCredentialProvider and some tests create Credentials
-        if isinstance(credentials, cred_provider.TestResources):
-            creds = self.credentials.credentials
-        else:
-            creds = self.credentials
         # Creates an auth provider for the credentials
-        self.auth_provider = get_auth_provider(creds, pre_auth=True,
-                                               scope=scope)
+        self.auth_provider = get_auth_provider(
+            self.credentials, pre_auth=True, scope=scope)
 
 
 def get_auth_provider_class(credentials):
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 8d0f2f6..dd6e0e5 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -19,9 +19,11 @@
 import netaddr
 from oslo_log import log
 from oslo_serialization import jsonutils as json
+from oslo_utils import netutils
 import six
 
 from tempest.common import compute
+from tempest.common import image as common_image
 from tempest.common.utils import data_utils
 from tempest.common.utils.linux import remote_client
 from tempest.common import waiters
@@ -29,7 +31,6 @@
 from tempest import exceptions
 from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions as lib_exc
-from tempest.scenario import network_resources
 import tempest.test
 
 CONF = config.CONF
@@ -50,8 +51,15 @@
         cls.compute_floating_ips_client = (
             cls.manager.compute_floating_ips_client)
         if CONF.service_available.glance:
-            # Glance image client v1
-            cls.image_client = cls.manager.image_client
+            # Check if glance v1 is available to determine which client to use.
+            if CONF.image_feature_enabled.api_v1:
+                cls.image_client = cls.manager.image_client
+            elif CONF.image_feature_enabled.api_v2:
+                cls.image_client = cls.manager.image_client_v2
+            else:
+                raise exceptions.InvalidConfiguration(
+                    'Either api_v1 or api_v2 must be True in '
+                    '[image-feature-enabled].')
         # Compute image client
         cls.compute_images_client = cls.manager.compute_images_client
         cls.keypairs_client = cls.manager.keypairs_client
@@ -216,7 +224,7 @@
                 port = self._create_port(network_id=net_id,
                                          client=clients.ports_client,
                                          **create_port_body)
-                ports.append({'port': port.id})
+                ports.append({'port': port['id']})
             if ports:
                 kwargs['networks'] = ports
             self.ports = ports
@@ -376,14 +384,23 @@
             'name': name,
             'container_format': fmt,
             'disk_format': disk_format or fmt,
-            'is_public': 'False',
         }
-        params['properties'] = properties
-        image = self.image_client.create_image(**params)['image']
+        if CONF.image_feature_enabled.api_v1:
+            params['is_public'] = 'False'
+            params['properties'] = properties
+        else:
+            params['visibility'] = 'private'
+            # Additional properties are flattened out in the v2 API.
+            params.update(properties)
+        body = self.image_client.create_image(**params)
+        image = body['image'] if 'image' in body else body
         self.addCleanup(self.image_client.delete_image, image['id'])
         self.assertEqual("queued", image['status'])
         with open(path, 'rb') as image_file:
-            self.image_client.update_image(image['id'], data=image_file)
+            if CONF.image_feature_enabled.api_v1:
+                self.image_client.update_image(image['id'], data=image_file)
+            else:
+                self.image_client.store_image_file(image['id'], image_file)
         return image['id']
 
     def glance_image_create(self):
@@ -394,7 +411,7 @@
         img_container_format = CONF.scenario.img_container_format
         img_disk_format = CONF.scenario.img_disk_format
         img_properties = CONF.scenario.img_properties
-        LOG.debug("paths: img: %s, container_fomat: %s, disk_format: %s, "
+        LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
                   "properties: %s, ami: %s, ari: %s, aki: %s" %
                   (img_path, img_container_format, img_disk_format,
                    img_properties, ami_img_path, ari_img_path, aki_img_path))
@@ -450,9 +467,17 @@
             thing_id=image_id, thing_id_param='id',
             cleanup_callable=test_utils.call_and_ignore_notfound_exc,
             cleanup_args=[_image_client.delete_image, image_id])
-        snapshot_image = _image_client.check_image(image_id)
+        if CONF.image_feature_enabled.api_v1:
+            # In glance v1 the additional properties are stored in the headers.
+            resp = _image_client.check_image(image_id)
+            snapshot_image = common_image.get_image_meta_from_headers(resp)
+            image_props = snapshot_image.get('properties', {})
+        else:
+            # In glance v2 the additional properties are flattened.
+            snapshot_image = _image_client.show_image(image_id)
+            image_props = snapshot_image
 
-        bdm = snapshot_image.get('properties', {}).get('block_device_mapping')
+        bdm = image_props.get('block_device_mapping')
         if bdm:
             bdm = json.loads(bdm)
             if bdm and 'snapshot_id' in bdm[0]:
@@ -681,12 +706,12 @@
             tenant_id = networks_client.tenant_id
         name = data_utils.rand_name(namestart)
         result = networks_client.create_network(name=name, tenant_id=tenant_id)
-        network = network_resources.DeletableNetwork(
-            networks_client=networks_client, routers_client=routers_client,
-            **result['network'])
-        self.assertEqual(network.name, name)
+        network = result['network']
+
+        self.assertEqual(network['name'], name)
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        network.delete)
+                        self.networks_client.delete_network,
+                        network['id'])
         return network
 
     def _list_networks(self, *args, **kwargs):
@@ -756,13 +781,13 @@
         # blocks until an unallocated block is found.
         for subnet_cidr in tenant_cidr.subnet(num_bits):
             str_cidr = str(subnet_cidr)
-            if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
+            if cidr_in_use(str_cidr, tenant_id=network['tenant_id']):
                 continue
 
             subnet = dict(
                 name=data_utils.rand_name(namestart),
-                network_id=network.id,
-                tenant_id=network.tenant_id,
+                network_id=network['id'],
+                tenant_id=network['tenant_id'],
                 cidr=str_cidr,
                 ip_version=ip_version,
                 **kwargs
@@ -775,11 +800,13 @@
                 if not is_overlapping_cidr:
                     raise
         self.assertIsNotNone(result, 'Unable to allocate tenant network')
-        subnet = network_resources.DeletableSubnet(
-            subnets_client=subnets_client,
-            routers_client=routers_client, **result['subnet'])
-        self.assertEqual(subnet.cidr, str_cidr)
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc, subnet.delete)
+
+        subnet = result['subnet']
+        self.assertEqual(subnet['cidr'], str_cidr)
+
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        subnets_client.delete_subnet, subnet['id'])
+
         return subnet
 
     def _create_port(self, network_id, client=None, namestart='port-quotatest',
@@ -792,9 +819,9 @@
             network_id=network_id,
             **kwargs)
         self.assertIsNotNone(result, 'Unable to allocate port')
-        port = network_resources.DeletablePort(ports_client=client,
-                                               **result['port'])
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc, port.delete)
+        port = result['port']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        client.delete_port, port['id'])
         return port
 
     def _get_server_port_id_and_ip4(self, server, ip_addr=None):
@@ -811,7 +838,7 @@
         port_map = [(p["id"], fxip["ip_address"])
                     for p in ports
                     for fxip in p["fixed_ips"]
-                    if netaddr.valid_ipv4(fxip["ip_address"])
+                    if netutils.is_valid_ipv4(fxip["ip_address"])
                     and p['status'] in p_status]
         inactive = [p for p in ports if p['status'] != 'ACTIVE']
         if inactive:
@@ -829,7 +856,7 @@
         net = self._list_networks(name=network_name)
         self.assertNotEqual(len(net), 0,
                             "Unable to get network by name: %s" % network_name)
-        return network_resources.AttributeDict(net[0])
+        return net[0]
 
     def create_floating_ip(self, thing, external_network_id=None,
                            port_id=None, client=None):
@@ -848,44 +875,51 @@
             tenant_id=thing['tenant_id'],
             fixed_ip_address=ip4
         )
-        floating_ip = network_resources.DeletableFloatingIp(
-            client=client,
-            **result['floatingip'])
+        floating_ip = result['floatingip']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        floating_ip.delete)
+                        self.floating_ips_client.delete_floatingip,
+                        floating_ip['id'])
         return floating_ip
 
     def _associate_floating_ip(self, floating_ip, server):
         port_id, _ = self._get_server_port_id_and_ip4(server)
-        floating_ip.update(port_id=port_id)
-        self.assertEqual(port_id, floating_ip.port_id)
+        kwargs = dict(port_id=port_id)
+        floating_ip = self.floating_ips_client.update_floatingip(
+            floating_ip['id'], **kwargs)['floatingip']
+        self.assertEqual(port_id, floating_ip['port_id'])
         return floating_ip
 
     def _disassociate_floating_ip(self, floating_ip):
-        """:param floating_ip: type DeletableFloatingIp"""
-        floating_ip.update(port_id=None)
-        self.assertIsNone(floating_ip.port_id)
+        """:param floating_ip: floating_ips_client.create_floatingip"""
+        kwargs = dict(port_id=None)
+        floating_ip = self.floating_ips_client.update_floatingip(
+            floating_ip['id'], **kwargs)['floatingip']
+        self.assertIsNone(floating_ip['port_id'])
         return floating_ip
 
     def check_floating_ip_status(self, floating_ip, status):
         """Verifies floatingip reaches the given status
 
-        :param floating_ip: network_resources.DeletableFloatingIp floating
-        IP to check status
+        :param dict floating_ip: floating IP dict to check status
         :param status: target status
         :raises: AssertionError if status doesn't match
         """
+        floatingip_id = floating_ip['id']
+
         def refresh():
-            floating_ip.refresh()
-            return status == floating_ip.status
+            result = (self.floating_ips_client.
+                      show_floatingip(floatingip_id)['floatingip'])
+            return status == result['status']
 
         tempest.test.call_until_true(refresh,
                                      CONF.network.build_timeout,
                                      CONF.network.build_interval)
-        self.assertEqual(status, floating_ip.status,
+        floating_ip = self.floating_ips_client.show_floatingip(
+            floatingip_id)['floatingip']
+        self.assertEqual(status, floating_ip['status'],
                          message="FloatingIP: {fp} is at status: {cst}. "
                                  "failed  to reach status: {st}"
-                         .format(fp=floating_ip, cst=floating_ip.status,
+                         .format(fp=floating_ip, cst=floating_ip['status'],
                                  st=status))
         LOG.info("FloatingIP: {fp} is at status: {st}"
                  .format(fp=floating_ip, st=status))
@@ -958,8 +992,8 @@
             secgroup=secgroup,
             security_groups_client=security_groups_client)
         for rule in rules:
-            self.assertEqual(tenant_id, rule.tenant_id)
-            self.assertEqual(secgroup.id, rule.security_group_id)
+            self.assertEqual(tenant_id, rule['tenant_id'])
+            self.assertEqual(secgroup['id'], rule['security_group_id'])
         return secgroup
 
     def _create_empty_security_group(self, client=None, tenant_id=None,
@@ -971,7 +1005,7 @@
          - IPv6 egress to any
 
         :param tenant_id: secgroup will be created in this tenant
-        :returns: DeletableSecurityGroup -- containing the secgroup created
+        :returns: the created security group
         """
         if client is None:
             client = self.security_groups_client
@@ -983,15 +1017,14 @@
                        description=sg_desc)
         sg_dict['tenant_id'] = tenant_id
         result = client.create_security_group(**sg_dict)
-        secgroup = network_resources.DeletableSecurityGroup(
-            client=client, routers_client=self.routers_client,
-            **result['security_group']
-        )
-        self.assertEqual(secgroup.name, sg_name)
-        self.assertEqual(tenant_id, secgroup.tenant_id)
-        self.assertEqual(secgroup.description, sg_desc)
+
+        secgroup = result['security_group']
+        self.assertEqual(secgroup['name'], sg_name)
+        self.assertEqual(tenant_id, secgroup['tenant_id'])
+        self.assertEqual(secgroup['description'], sg_desc)
+
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        secgroup.delete)
+                        client.delete_security_group, secgroup['id'])
         return secgroup
 
     def _default_security_group(self, client=None, tenant_id=None):
@@ -1009,8 +1042,7 @@
         ]
         msg = "No default security group for tenant %s." % (tenant_id)
         self.assertTrue(len(sgs) > 0, msg)
-        return network_resources.DeletableSecurityGroup(client=client,
-                                                        **sgs[0])
+        return sgs[0]
 
     def _create_security_group_rule(self, secgroup=None,
                                     sec_group_rules_client=None,
@@ -1021,7 +1053,7 @@
         Create a rule in a secgroup. if secgroup not defined will search for
         default secgroup in tenant_id.
 
-        :param secgroup: type DeletableSecurityGroup.
+        :param secgroup: the security group.
         :param tenant_id: if secgroup not passed -- the tenant in which to
             search for default secgroup
         :param kwargs: a dictionary containing rule parameters:
@@ -1043,17 +1075,15 @@
             secgroup = self._default_security_group(
                 client=security_groups_client, tenant_id=tenant_id)
 
-        ruleset = dict(security_group_id=secgroup.id,
-                       tenant_id=secgroup.tenant_id)
+        ruleset = dict(security_group_id=secgroup['id'],
+                       tenant_id=secgroup['tenant_id'])
         ruleset.update(kwargs)
 
         sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
-        sg_rule = network_resources.DeletableSecurityGroupRule(
-            client=sec_group_rules_client,
-            **sg_rule['security_group_rule']
-        )
-        self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
-        self.assertEqual(secgroup.id, sg_rule.security_group_id)
+        sg_rule = sg_rule['security_group_rule']
+
+        self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
+        self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
 
         return sg_rule
 
@@ -1107,7 +1137,7 @@
                     if msg not in ex._error_string:
                         raise ex
                 else:
-                    self.assertEqual(r_direction, sg_rule.direction)
+                    self.assertEqual(r_direction, sg_rule['direction'])
                     rules.append(sg_rule)
 
         return rules
@@ -1129,10 +1159,11 @@
         network_id = CONF.network.public_network_id
         if router_id:
             body = client.show_router(router_id)
-            return network_resources.AttributeDict(**body['router'])
+            return body['router']
         elif network_id:
             router = self._create_router(client, tenant_id)
-            router.set_gateway(network_id)
+            kwargs = {'external_gateway_info': dict(network_id=network_id)}
+            router = client.update_router(router['id'], **kwargs)['router']
             return router
         else:
             raise Exception("Neither of 'public_router_id' or "
@@ -1148,15 +1179,18 @@
         result = client.create_router(name=name,
                                       admin_state_up=True,
                                       tenant_id=tenant_id)
-        router = network_resources.DeletableRouter(routers_client=client,
-                                                   **result['router'])
-        self.assertEqual(router.name, name)
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc, router.delete)
+        router = result['router']
+        self.assertEqual(router['name'], name)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        client.delete_router,
+                        router['id'])
         return router
 
     def _update_router_admin_state(self, router, admin_state_up):
-        router.update(admin_state_up=admin_state_up)
-        self.assertEqual(admin_state_up, router.admin_state_up)
+        kwargs = dict(admin_state_up=admin_state_up)
+        router = self.routers_client.update_router(
+            router['id'], **kwargs)['router']
+        self.assertEqual(admin_state_up, router['admin_state_up'])
 
     def create_networks(self, networks_client=None,
                         routers_client=None, subnets_client=None,
@@ -1189,7 +1223,6 @@
                 tenant_id=tenant_id)
             router = self._get_router(client=routers_client,
                                       tenant_id=tenant_id)
-
             subnet_kwargs = dict(network=network,
                                  subnets_client=subnets_client,
                                  routers_client=routers_client)
@@ -1197,7 +1230,17 @@
             if dns_nameservers is not None:
                 subnet_kwargs['dns_nameservers'] = dns_nameservers
             subnet = self._create_subnet(**subnet_kwargs)
-            subnet.add_to_router(router.id)
+            if not routers_client:
+                routers_client = self.routers_client
+            router_id = router['id']
+            routers_client.add_router_interface(router_id,
+                                                subnet_id=subnet['id'])
+
+            # save a cleanup job to remove this association between
+            # router and subnet
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            routers_client.remove_router_interface, router_id,
+                            subnet_id=subnet['id'])
         return network, subnet, router
 
 
diff --git a/tempest/scenario/network_resources.py b/tempest/scenario/network_resources.py
deleted file mode 100644
index 667476f..0000000
--- a/tempest/scenario/network_resources.py
+++ /dev/null
@@ -1,220 +0,0 @@
-# Copyright 2013 Hewlett-Packard 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 abc
-import time
-
-import six
-
-from tempest import exceptions
-from tempest.lib.common.utils import misc
-
-
-class AttributeDict(dict):
-    """Provide attribute access (dict.key) to dictionary values."""
-
-    def __getattr__(self, name):
-        """Allow attribute access for all keys in the dict."""
-        if name in self:
-            return self[name]
-        return super(AttributeDict, self).__getattribute__(name)
-
-
-@six.add_metaclass(abc.ABCMeta)
-class DeletableResource(AttributeDict):
-    """Support deletion of neutron resources (networks, subnets)
-
-    via a delete() method, as is supported by keystone and nova resources.
-    """
-
-    def __init__(self, *args, **kwargs):
-        self.client = kwargs.pop('client', None)
-        self.networks_client = kwargs.pop('networks_client', None)
-        self.routers_client = kwargs.pop('routers_client', None)
-        self.subnets_client = kwargs.pop('subnets_client', None)
-        self.ports_client = kwargs.pop('ports_client', None)
-        super(DeletableResource, self).__init__(*args, **kwargs)
-
-    def __str__(self):
-        return '<%s id="%s" name="%s">' % (self.__class__.__name__,
-                                           self.id, self.name)
-
-    @abc.abstractmethod
-    def delete(self):
-        return
-
-    @abc.abstractmethod
-    def refresh(self):
-        return
-
-    def __hash__(self):
-        return hash(self.id)
-
-    def wait_for_status(self, status):
-        if not hasattr(self, 'status'):
-            return
-
-        def helper_get():
-            self.refresh()
-            return self
-
-        return self.wait_for_resource_status(helper_get, status)
-
-    def wait_for_resource_status(self, fetch, status):
-        """Waits for a network resource to reach a status
-
-        @param fetch: the callable to be used to query the resource status
-        @type fetch: callable that takes no parameters and returns the resource
-        @param status: the status that the resource has to reach
-        @type status: String
-        """
-        interval = self.build_interval
-        timeout = self.build_timeout
-        start_time = time.time()
-
-        while time.time() - start_time <= timeout:
-            resource = fetch()
-            if resource['status'] == status:
-                return
-            time.sleep(interval)
-
-        # At this point, the wait has timed out
-        message = 'Resource %s' % (str(resource))
-        message += ' failed to reach status %s' % status
-        message += ' (current: %s)' % resource['status']
-        message += ' within the required time %s' % timeout
-        caller = misc.find_test_caller()
-        if caller:
-            message = '(%s) %s' % (caller, message)
-        raise exceptions.TimeoutException(message)
-
-
-class DeletableNetwork(DeletableResource):
-
-    def delete(self):
-        self.networks_client.delete_network(self.id)
-
-
-class DeletableSubnet(DeletableResource):
-
-    def __init__(self, *args, **kwargs):
-        super(DeletableSubnet, self).__init__(*args, **kwargs)
-        self._router_ids = set()
-
-    def update(self, *args, **kwargs):
-        result = self.subnets_client.update_subnet(self.id,
-                                                   *args,
-                                                   **kwargs)
-        return super(DeletableSubnet, self).update(**result['subnet'])
-
-    def add_to_router(self, router_id):
-        self._router_ids.add(router_id)
-        self.routers_client.add_router_interface(router_id,
-                                                 subnet_id=self.id)
-
-    def delete(self):
-        for router_id in self._router_ids.copy():
-            self.routers_client.remove_router_interface(router_id,
-                                                        subnet_id=self.id)
-            self._router_ids.remove(router_id)
-        self.subnets_client.delete_subnet(self.id)
-
-
-class DeletableRouter(DeletableResource):
-
-    def set_gateway(self, network_id):
-        return self.update(external_gateway_info=dict(network_id=network_id))
-
-    def unset_gateway(self):
-        return self.update(external_gateway_info=dict())
-
-    def update(self, *args, **kwargs):
-        result = self.routers_client.update_router(self.id,
-                                                   *args,
-                                                   **kwargs)
-        return super(DeletableRouter, self).update(**result['router'])
-
-    def delete(self):
-        self.unset_gateway()
-        self.routers_client.delete_router(self.id)
-
-
-class DeletableFloatingIp(DeletableResource):
-
-    def refresh(self, *args, **kwargs):
-        result = self.client.show_floatingip(self.id,
-                                             *args,
-                                             **kwargs)
-        super(DeletableFloatingIp, self).update(**result['floatingip'])
-
-    def update(self, *args, **kwargs):
-        result = self.client.update_floatingip(self.id,
-                                               *args,
-                                               **kwargs)
-        super(DeletableFloatingIp, self).update(**result['floatingip'])
-
-    def __repr__(self):
-        return '<%s addr="%s">' % (self.__class__.__name__,
-                                   self.floating_ip_address)
-
-    def __str__(self):
-        return '<"FloatingIP" addr="%s" id="%s">' % (self.floating_ip_address,
-                                                     self.id)
-
-    def delete(self):
-        self.client.delete_floatingip(self.id)
-
-
-class DeletablePort(DeletableResource):
-
-    def delete(self):
-        self.ports_client.delete_port(self.id)
-
-
-class DeletableSecurityGroup(DeletableResource):
-
-    def delete(self):
-        self.client.delete_security_group(self.id)
-
-
-class DeletableSecurityGroupRule(DeletableResource):
-
-    def __repr__(self):
-        return '<%s id="%s">' % (self.__class__.__name__, self.id)
-
-    def delete(self):
-        self.client.delete_security_group_rule(self.id)
-
-
-class DeletablePool(DeletableResource):
-
-    def delete(self):
-        self.client.delete_pool(self.id)
-
-
-class DeletableMember(DeletableResource):
-
-    def delete(self):
-        self.client.delete_member(self.id)
-
-
-class DeletableVip(DeletableResource):
-
-    def delete(self):
-        self.client.delete_vip(self.id)
-
-    def refresh(self):
-        result = self.client.show_vip(self.id)
-        super(DeletableVip, self).update(**result['vip'])
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 4c2d31b..e4b699e 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -52,15 +52,18 @@
 
     def _setup_network_and_servers(self):
         keypair = self.create_keypair()
-        security_group = self._create_security_group()
+        security_groups = []
+        if test.is_extension_enabled('security-group', 'network'):
+            security_group = self._create_security_group()
+            security_groups = [{'name': security_group['name']}]
         network, subnet, router = self.create_networks()
         public_network_id = CONF.network.public_network_id
         server_name = data_utils.rand_name('server-smoke')
         server = self.create_server(
             name=server_name,
-            networks=[{'uuid': network.id}],
+            networks=[{'uuid': network['id']}],
             key_name=keypair['name'],
-            security_groups=[{'name': security_group['name']}],
+            security_groups=security_groups,
             wait_until='ACTIVE')
         floating_ip = self.create_floating_ip(server, public_network_id)
         # Verify that we can indeed connect to the server before we mess with
@@ -78,7 +81,7 @@
             server, username, private_key,
             should_connect=should_connect,
             servers_for_debug=[server])
-        floating_ip_addr = floating_ip.floating_ip_address
+        floating_ip_addr = floating_ip['floating_ip_address']
         # Check FloatingIP status before checking the connectivity
         self.check_floating_ip_status(floating_ip, 'ACTIVE')
         self.check_public_network_connectivity(floating_ip_addr, username,
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index f5134e5..402a70c 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -25,7 +25,6 @@
 from tempest import exceptions
 from tempest.lib.common.utils import test_utils
 from tempest.scenario import manager
-from tempest.scenario import network_resources
 from tempest import test
 
 CONF = config.CONF
@@ -114,7 +113,7 @@
         self.port_id = None
         if boot_with_port:
             # create a port on the network and boot with that
-            self.port_id = self._create_port(self.network['id']).id
+            self.port_id = self._create_port(self.network['id'])['id']
             self.ports.append({'port': self.port_id})
 
         name = data_utils.rand_name('server-smoke')
@@ -133,30 +132,30 @@
         seen_nets = self._list_networks()
         seen_names = [n['name'] for n in seen_nets]
         seen_ids = [n['id'] for n in seen_nets]
-        self.assertIn(self.network.name, seen_names)
-        self.assertIn(self.network.id, seen_ids)
+        self.assertIn(self.network['name'], seen_names)
+        self.assertIn(self.network['id'], seen_ids)
 
         if self.subnet:
             seen_subnets = self._list_subnets()
             seen_net_ids = [n['network_id'] for n in seen_subnets]
             seen_subnet_ids = [n['id'] for n in seen_subnets]
-            self.assertIn(self.network.id, seen_net_ids)
-            self.assertIn(self.subnet.id, seen_subnet_ids)
+            self.assertIn(self.network['id'], seen_net_ids)
+            self.assertIn(self.subnet['id'], seen_subnet_ids)
 
         if self.router:
             seen_routers = self._list_routers()
             seen_router_ids = [n['id'] for n in seen_routers]
             seen_router_names = [n['name'] for n in seen_routers]
-            self.assertIn(self.router.name,
+            self.assertIn(self.router['name'],
                           seen_router_names)
-            self.assertIn(self.router.id,
+            self.assertIn(self.router['id'],
                           seen_router_ids)
 
     def _create_server(self, name, network, port_id=None):
         keypair = self.create_keypair()
         self.keypairs[keypair['name']] = keypair
         security_groups = [{'name': self.security_group['name']}]
-        network = {'uuid': network.id}
+        network = {'uuid': network['id']}
         if port_id is not None:
             network['port'] = port_id
 
@@ -198,7 +197,7 @@
         """
         ssh_login = CONF.validation.image_ssh_user
         floating_ip, server = self.floating_ip_tuple
-        ip_address = floating_ip.floating_ip_address
+        ip_address = floating_ip['floating_ip_address']
         private_key = None
         floatingip_status = 'DOWN'
         if should_connect:
@@ -239,7 +238,7 @@
 
     def _hotplug_server(self):
         old_floating_ip, server = self.floating_ip_tuple
-        ip_address = old_floating_ip.floating_ip_address
+        ip_address = old_floating_ip['floating_ip_address']
         private_key = self._get_server_key(server)
         ssh_client = self.get_remote_client(
             ip_address, private_key=private_key)
@@ -250,7 +249,7 @@
         old_port = port_list[0]
         interface = self.interface_client.create_interface(
             server_id=server['id'],
-            net_id=self.new_net.id)['interfaceAttachment']
+            net_id=self.new_net['id'])['interfaceAttachment']
         self.addCleanup(self.ports_client.wait_for_resource_deletion,
                         interface['port_id'])
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
@@ -270,9 +269,7 @@
                 "Old port: %s. Number of new ports: %d" % (
                     CONF.network.build_timeout, old_port,
                     len(self.new_port_list)))
-        new_port = network_resources.DeletablePort(
-            ports_client=self.ports_client,
-            **self.new_port_list[0])
+        new_port = self.new_port_list[0]
 
         def check_new_nic():
             new_nic_list = self._get_server_nics(ssh_client)
@@ -287,7 +284,8 @@
 
         num, new_nic = self.diff_list[0]
         ssh_client.assign_static_ip(nic=new_nic,
-                                    addr=new_port.fixed_ips[0]['ip_address'])
+                                    addr=new_port['fixed_ips'][0][
+                                        'ip_address'])
         ssh_client.set_nic_state(nic=new_nic)
 
     def _get_server_nics(self, ssh_client):
@@ -307,7 +305,7 @@
         # get all network ports in the new network
         internal_ips = (p['fixed_ips'][0]['ip_address'] for p in
                         self._list_ports(tenant_id=server['tenant_id'],
-                                         network_id=network.id)
+                                         network_id=network['id'])
                         if p['device_owner'].startswith('network'))
 
         self._check_server_connectivity(floating_ip,
@@ -335,7 +333,7 @@
 
     def _check_server_connectivity(self, floating_ip, address_list,
                                    should_connect=True):
-        ip_address = floating_ip.floating_ip_address
+        ip_address = floating_ip['floating_ip_address']
         private_key = self._get_server_key(self.floating_ip_tuple.server)
         ssh_source = self.get_remote_client(
             ip_address, private_key=private_key)
@@ -451,7 +449,13 @@
         self._create_server(name, self.new_net)
         self._check_network_internal_connectivity(network=self.new_net,
                                                   should_connect=False)
-        self.new_subnet.add_to_router(self.router.id)
+        router_id = self.router['id']
+        self.routers_client.add_router_interface(
+            router_id, subnet_id=self.new_subnet['id'])
+
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.routers_client.remove_router_interface,
+                        router_id, subnet_id=self.new_subnet['id'])
         self._check_network_internal_connectivity(network=self.new_net,
                                                   should_connect=True)
 
@@ -553,7 +557,7 @@
         self.check_public_network_connectivity(should_connect=True)
 
         floating_ip, server = self.floating_ip_tuple
-        ip_address = floating_ip.floating_ip_address
+        ip_address = floating_ip['floating_ip_address']
         private_key = self._get_server_key(server)
         ssh_client = self.get_remote_client(
             ip_address, private_key=private_key)
@@ -568,9 +572,11 @@
                                  act_serv=servers,
                                  trgt_serv=dns_servers))
 
-        self.subnet.update(dns_nameservers=[alt_dns_server])
+        self.subnet = self.subnets_client.update_subnet(
+            self.subnet['id'], dns_nameservers=[alt_dns_server])['subnet']
+
         # asserts that Neutron DB has updated the nameservers
-        self.assertEqual([alt_dns_server], self.subnet.dns_nameservers,
+        self.assertEqual([alt_dns_server], self.subnet['dns_nameservers'],
                          "Failed to update subnet's nameservers")
 
         def check_new_dns_server():
@@ -695,7 +701,8 @@
 
         # NOTE(kevinbenton): we have to use the admin credentials to check
         # for the distributed flag because self.router only has a project view.
-        admin = self.admin_manager.routers_client.show_router(self.router.id)
+        admin = self.admin_manager.routers_client.show_router(
+            self.router['id'])
         if admin['router'].get('distributed', False):
             msg = "Rescheduling test does not apply to distributed routers."
             raise self.skipException(msg)
@@ -704,16 +711,16 @@
 
         # remove resource from agents
         hosting_agents = set(a["id"] for a in
-                             list_hosts(self.router.id)['agents'])
+                             list_hosts(self.router['id'])['agents'])
         no_migration = agent_list_alive == hosting_agents
         LOG.info("Router will be assigned to {mig} hosting agent".
                  format(mig="the same" if no_migration else "a new"))
 
         for hosting_agent in hosting_agents:
-            unschedule_router(hosting_agent, self.router.id)
+            unschedule_router(hosting_agent, self.router['id'])
             self.assertNotIn(hosting_agent,
                              [a["id"] for a in
-                              list_hosts(self.router.id)['agents']],
+                              list_hosts(self.router['id'])['agents']],
                              'unscheduling router failed')
 
         # verify resource is un-functional
@@ -730,7 +737,7 @@
                         router_id=self.router['id'])
         self.assertEqual(
             target_agent,
-            list_hosts(self.router.id)['agents'][0]['id'],
+            list_hosts(self.router['id'])['agents'][0]['id'],
             "Router failed to reschedule. Hosting agent doesn't match "
             "target agent")
 
@@ -776,12 +783,12 @@
                                      network_id=self.new_net["id"])
         spoof_port = new_ports[0]
         private_key = self._get_server_key(server)
-        ssh_client = self.get_remote_client(fip.floating_ip_address,
+        ssh_client = self.get_remote_client(fip['floating_ip_address'],
                                             private_key=private_key)
         spoof_nic = ssh_client.get_nic_name_by_mac(spoof_port["mac_address"])
         name = data_utils.rand_name('peer')
         peer = self._create_server(name, self.new_net)
-        peer_address = peer['addresses'][self.new_net.name][0]['addr']
+        peer_address = peer['addresses'][self.new_net['name']][0]['addr']
         self._check_remote_connectivity(ssh_client, dest=peer_address,
                                         nic=spoof_nic, should_succeed=True)
         ssh_client.set_mac_address(spoof_nic, spoof_mac)
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index a52d8f9..59ebb7a 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -17,6 +17,7 @@
 import six
 
 from tempest import config
+from tempest.lib.common.utils import test_utils
 from tempest.scenario import manager
 from tempest import test
 
@@ -82,8 +83,12 @@
                                    ip_version=4)
 
         router = self._get_router(tenant_id=self.tenant_id)
-        sub4.add_to_router(router_id=router['id'])
-        self.addCleanup(sub4.delete)
+        self.routers_client.add_router_interface(router['id'],
+                                                 subnet_id=sub4['id'])
+
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.routers_client.remove_router_interface,
+                        router['id'], subnet_id=sub4['id'])
 
         self.subnets_v6 = []
         for _ in range(n_subnets6):
@@ -94,10 +99,14 @@
                                        ipv6_ra_mode=address6_mode,
                                        ipv6_address_mode=address6_mode)
 
-            sub6.add_to_router(router_id=router['id'])
-            self.addCleanup(sub6.delete)
-            self.subnets_v6.append(sub6)
+            self.routers_client.add_router_interface(router['id'],
+                                                     subnet_id=sub6['id'])
 
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self.routers_client.remove_router_interface,
+                            router['id'], subnet_id=sub6['id'])
+
+            self.subnets_v6.append(sub6)
         return [self.network, self.network_v6] if dualnet else [self.network]
 
     @staticmethod
@@ -119,12 +128,12 @@
         srv = self.create_server(
             key_name=self.keypair['name'],
             security_groups=[{'name': self.sec_grp['name']}],
-            networks=[{'uuid': n.id} for n in networks],
+            networks=[{'uuid': n['id']} for n in networks],
             wait_until='ACTIVE')
         fip = self.create_floating_ip(thing=srv)
         ips = self.define_server_ips(srv=srv)
         ssh = self.get_remote_client(
-            ip_address=fip.floating_ip_address,
+            ip_address=fip['floating_ip_address'],
             username=username)
         return ssh, ips, srv["id"]
 
@@ -139,7 +148,7 @@
         """
         ports = [p["mac_address"] for p in
                  self._list_ports(device_id=sid,
-                                  network_id=self.network_v6.id)]
+                                  network_id=self.network_v6['id'])]
         self.assertEqual(1, len(ports),
                          message=("Multiple IPv6 ports found on network %s. "
                                   "ports: %s")
@@ -190,11 +199,11 @@
             self._check_connectivity(sshv4_1,
                                      ips_from_api_2['6'][i])
             self._check_connectivity(sshv4_1,
-                                     self.subnets_v6[i].gateway_ip)
+                                     self.subnets_v6[i]['gateway_ip'])
             self._check_connectivity(sshv4_2,
                                      ips_from_api_1['6'][i])
             self._check_connectivity(sshv4_2,
-                                     self.subnets_v6[i].gateway_ip)
+                                     self.subnets_v6[i]['gateway_ip'])
 
     def _check_connectivity(self, source, dest):
         self.assertTrue(
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index adc9008..86185c8 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -227,22 +227,23 @@
         seen_names = [n['name'] for n in seen_nets]
         seen_ids = [n['id'] for n in seen_nets]
 
-        self.assertIn(tenant.network.name, seen_names)
-        self.assertIn(tenant.network.id, seen_ids)
+        self.assertIn(tenant.network['name'], seen_names)
+        self.assertIn(tenant.network['id'], seen_ids)
 
         seen_subnets = [(n['id'], n['cidr'], n['network_id'])
                         for n in self._list_subnets()]
-        mysubnet = (tenant.subnet.id, tenant.subnet.cidr, tenant.network.id)
+        mysubnet = (tenant.subnet['id'], tenant.subnet['cidr'],
+                    tenant.network['id'])
         self.assertIn(mysubnet, seen_subnets)
 
         seen_routers = self._list_routers()
         seen_router_ids = [n['id'] for n in seen_routers]
         seen_router_names = [n['name'] for n in seen_routers]
 
-        self.assertIn(tenant.router.name, seen_router_names)
-        self.assertIn(tenant.router.id, seen_router_ids)
+        self.assertIn(tenant.router['name'], seen_router_names)
+        self.assertIn(tenant.router['id'], seen_router_ids)
 
-        myport = (tenant.router.id, tenant.subnet.id)
+        myport = (tenant.router['id'], tenant.subnet['id'])
         router_ports = [(i['device_id'], i['fixed_ips'][0]['subnet_id']) for i
                         in self._list_ports()
                         if self._is_router_port(i)]
@@ -270,7 +271,7 @@
             kwargs["scheduler_hints"] = {'different_host': self.servers}
         server = self.create_server(
             name=name,
-            networks=[{'uuid': tenant.network.id}],
+            networks=[{'uuid': tenant.network["id"]}],
             key_name=tenant.keypair['name'],
             security_groups=security_groups_names,
             wait_until='ACTIVE',
@@ -353,10 +354,10 @@
     def _get_server_ip(self, server, floating=False):
         """returns the ip (floating/internal) of a server"""
         if floating:
-            server_ip = self.floating_ips[server['id']].floating_ip_address
+            server_ip = self.floating_ips[server['id']]['floating_ip_address']
         else:
             server_ip = None
-            network_name = self.tenants[server['tenant_id']].network.name
+            network_name = self.tenants[server['tenant_id']].network['name']
             if network_name in server['addresses']:
                 server_ip = server['addresses'][network_name][0]['addr']
         return server_ip
@@ -364,7 +365,7 @@
     def _connect_to_access_point(self, tenant):
         """create ssh connection to tenant access point"""
         access_point_ssh = \
-            self.floating_ips[tenant.access_point['id']].floating_ip_address
+            self.floating_ips[tenant.access_point['id']]['floating_ip_address']
         private_key = tenant.keypair['private_key']
         access_point_ssh = self.get_remote_client(
             access_point_ssh, private_key=private_key)
@@ -388,7 +389,7 @@
     def _test_in_tenant_allow(self, tenant):
         ruleset = dict(
             protocol='icmp',
-            remote_group_id=tenant.security_groups['default'].id,
+            remote_group_id=tenant.security_groups['default']['id'],
             direction='ingress'
         )
         self._create_security_group_rule(
@@ -464,7 +465,7 @@
             for port in port_list if port['fixed_ips']
         ]
         server_ip = self._get_server_ip(tenant.access_point)
-        subnet_id = tenant.subnet.id
+        subnet_id = tenant.subnet['id']
         self.assertIn((subnet_id, server_ip, mac_addr), port_detail_list)
 
     @test.idempotent_id('e79f879e-debb-440c-a7e4-efeda05b6848')
@@ -545,7 +546,7 @@
 
             # update port with new security group and check connectivity
             self.ports_client.update_port(port_id, security_groups=[
-                new_tenant.security_groups['new_sg'].id])
+                new_tenant.security_groups['new_sg']['id']])
             self._check_connectivity(
                 access_point=access_point_ssh,
                 ip=self._get_server_ip(server))
diff --git a/tempest/scenario/utils.py b/tempest/scenario/utils.py
index 75fd000..c7ba659 100644
--- a/tempest/scenario/utils.py
+++ b/tempest/scenario/utils.py
@@ -107,7 +107,8 @@
             name='InputScenarioUtils',
             identity_version=CONF.identity.auth_version,
             network_resources=network_resources)
-        os = clients.Manager(self.cred_provider.get_primary_creds())
+        os = clients.Manager(
+            self.cred_provider.get_primary_creds().credentials)
         self.compute_images_client = os.compute_images_client
         self.flavors_client = os.flavors_client
         self.image_pattern = CONF.input_scenario.image_regex
diff --git a/tempest/services/identity/v2/json/services_client.py b/tempest/services/identity/v2/json/services_client.py
index d8be6c6..4a63d56 100644
--- a/tempest/services/identity/v2/json/services_client.py
+++ b/tempest/services/identity/v2/json/services_client.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
@@ -20,14 +21,9 @@
 class ServicesClient(rest_client.RestClient):
     api_version = "v2.0"
 
-    def create_service(self, name, type, **kwargs):
+    def create_service(self, **kwargs):
         """Create a service."""
-        post_body = {
-            'name': name,
-            'type': type,
-            'description': kwargs.get('description')
-        }
-        post_body = json.dumps({'OS-KSADM:service': post_body})
+        post_body = json.dumps({'OS-KSADM:service': kwargs})
         resp, body = self.post('/OS-KSADM/services', post_body)
         self.expected_success(200, resp.status)
         body = json.loads(body)
@@ -41,9 +37,12 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
-    def list_services(self):
+    def list_services(self, **params):
         """List Service - Returns Services."""
-        resp, body = self.get('/OS-KSADM/services')
+        url = '/OS-KSADM/services'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/tenants_client.py b/tempest/services/identity/v2/json/tenants_client.py
index 034938e..97e5c11 100644
--- a/tempest/services/identity/v2/json/tenants_client.py
+++ b/tempest/services/identity/v2/json/tenants_client.py
@@ -59,7 +59,11 @@
         return rest_client.ResponseBody(resp, body)
 
     def update_tenant(self, tenant_id, **kwargs):
-        """Updates a tenant."""
+        """Updates a tenant.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#updateTenant
+        """
         body = self.show_tenant(tenant_id)['tenant']
         name = kwargs.get('name', body['name'])
         desc = kwargs.get('description', body['description'])
diff --git a/tempest/services/identity/v2/json/users_client.py b/tempest/services/identity/v2/json/users_client.py
index 5f8127f..1048840 100644
--- a/tempest/services/identity/v2/json/users_client.py
+++ b/tempest/services/identity/v2/json/users_client.py
@@ -11,6 +11,7 @@
 #    under the License.
 
 from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
@@ -18,25 +19,24 @@
 class UsersClient(rest_client.RestClient):
     api_version = "v2.0"
 
-    def create_user(self, name, password, tenant_id, email, **kwargs):
-        """Create a user."""
-        post_body = {
-            'name': name,
-            'password': password,
-            'email': email
-        }
-        if tenant_id is not None:
-            post_body['tenantId'] = tenant_id
-        if kwargs.get('enabled') is not None:
-            post_body['enabled'] = kwargs.get('enabled')
-        post_body = json.dumps({'user': post_body})
+    def create_user(self, **kwargs):
+        """Create a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-createUser
+        """
+        post_body = json.dumps({'user': kwargs})
         resp, body = self.post('users', post_body)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
     def update_user(self, user_id, **kwargs):
-        """Updates a user."""
+        """Updates a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-updateUser
+        """
         put_body = json.dumps({'user': kwargs})
         resp, body = self.put('users/%s' % user_id, put_body)
         self.expected_success(200, resp.status)
@@ -44,21 +44,36 @@
         return rest_client.ResponseBody(resp, body)
 
     def show_user(self, user_id):
-        """GET a user."""
+        """GET a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-showUser
+        """
         resp, body = self.get("users/%s" % user_id)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_user(self, user_id):
-        """Delete a user."""
+        """Delete a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-deleteUser
+        """
         resp, body = self.delete("users/%s" % user_id)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
-    def list_users(self):
-        """Get the list of users."""
-        resp, body = self.get("users")
+    def list_users(self, **params):
+        """Get the list of users.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-listUsers
+        """
+        url = "users"
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/users_clients.py b/tempest/services/identity/v3/json/users_clients.py
index 3ab8eab..73bd343 100644
--- a/tempest/services/identity/v3/json/users_clients.py
+++ b/tempest/services/identity/v3/json/users_clients.py
@@ -44,7 +44,11 @@
         return rest_client.ResponseBody(resp, body)
 
     def update_user(self, user_id, name, **kwargs):
-        """Updates a user."""
+        """Updates a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v3.html#updateUser
+        """
         body = self.show_user(user_id)['user']
         email = kwargs.get('email', body['email'])
         en = kwargs.get('enabled', body['enabled'])
diff --git a/tempest/services/image/v1/json/image_members_client.py b/tempest/services/image/v1/json/image_members_client.py
new file mode 100644
index 0000000..e7fa0c9
--- /dev/null
+++ b/tempest/services/image/v1/json/image_members_client.py
@@ -0,0 +1,63 @@
+#    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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ImageMembersClient(rest_client.RestClient):
+    api_version = "v1"
+
+    def list_image_members(self, image_id):
+        """List all members of an image."""
+        url = 'images/%s/members' % image_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_shared_images(self, tenant_id):
+        """List image memberships for the given tenant.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v1.html#listSharedImages-v1
+        """
+
+        url = 'shared-images/%s' % tenant_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_image_member(self, image_id, member_id, **kwargs):
+        """Add a member to an image.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v1.html#addMember-v1
+        """
+        url = 'images/%s/members/%s' % (image_id, member_id)
+        body = json.dumps({'member': kwargs})
+        resp, __ = self.put(url, body)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
+
+    def delete_image_member(self, image_id, member_id):
+        """Removes a membership from the image.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v1.html#removeMember-v1
+        """
+        url = 'images/%s/members/%s' % (image_id, member_id)
+        resp, __ = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/services/image/v1/json/images_client.py
index b45ca22..30325c0 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -14,9 +14,7 @@
 #    under the License.
 
 import copy
-import errno
 import functools
-import os
 
 from oslo_log import log as logging
 from oslo_serialization import jsonutils as json
@@ -31,28 +29,7 @@
 
 
 class ImagesClient(rest_client.RestClient):
-
-    def _image_meta_from_headers(self, headers):
-        meta = {'properties': {}}
-        for key, value in six.iteritems(headers):
-            if key.startswith('x-image-meta-property-'):
-                _key = key[22:]
-                meta['properties'][_key] = value
-            elif key.startswith('x-image-meta-'):
-                _key = key[13:]
-                meta[_key] = value
-
-        for key in ['is_public', 'protected', 'deleted']:
-            if key in meta:
-                meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes',
-                                                          '1')
-        for key in ['size', 'min_ram', 'min_disk']:
-            if key in meta:
-                try:
-                    meta[key] = int(meta[key])
-                except ValueError:
-                    pass
-        return meta
+    api_version = "v1"
 
     def _image_meta_to_headers(self, fields):
         headers = {}
@@ -68,43 +45,14 @@
             headers['x-image-meta-%s' % key] = str(value)
         return headers
 
-    def _get_file_size(self, obj):
-        """Analyze file-like object and attempt to determine its size.
-
-        :param obj: file-like object, typically redirected from stdin.
-        :retval The file's size or None if it cannot be determined.
-        """
-        # For large images, we need to supply the size of the
-        # image file. See LP Bugs #827660 and #845788.
-        if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
-            try:
-                obj.seek(0, os.SEEK_END)
-                obj_size = obj.tell()
-                obj.seek(0)
-                return obj_size
-            except IOError as e:
-                if e.errno == errno.ESPIPE:
-                    # Illegal seek. This means the user is trying
-                    # to pipe image data to the client, e.g.
-                    # echo testdata | bin/glance add blah..., or
-                    # that stdin is empty, or that a file-like
-                    # object which doesn't support 'seek/tell' has
-                    # been supplied.
-                    return None
-                else:
-                    raise
-        else:
-            # Cannot determine size of input image
-            return None
-
     def _create_with_data(self, headers, data):
         # We are going to do chunked transfert, so split the input data
         # info fixed-sized chunks.
         headers['Content-Type'] = 'application/octet-stream'
         data = iter(functools.partial(data.read, CHUNKSIZE), b'')
-        resp, body = self.request('POST', '/v1/images',
+        resp, body = self.request('POST', 'images',
                                   headers=headers, body=data, chunked=True)
-        self._error_checker('POST', '/v1/images', headers, data, resp,
+        self._error_checker('POST', 'images', headers, data, resp,
                             body)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
@@ -114,7 +62,7 @@
         # info fixed-sized chunks.
         headers['Content-Type'] = 'application/octet-stream'
         data = iter(functools.partial(data.read, CHUNKSIZE), b'')
-        url = '/v1/images/%s' % image_id
+        url = 'images/%s' % image_id
         resp, body = self.request('PUT', url, headers=headers,
                                   body=data, chunked=True)
         self._error_checker('PUT', url, headers, data,
@@ -128,35 +76,43 @@
             self._http = self._get_http()
         return self._http
 
-    def create_image(self, **kwargs):
+    def create_image(self, data=None, **kwargs):
+        """Create an image.
+
+        Available params: http://developer.openstack.org/
+                          api-ref-image-v1.html#createImage-v1
+        """
         headers = {}
-        data = kwargs.pop('data', None)
         headers.update(self._image_meta_to_headers(kwargs))
 
         if data is not None:
             return self._create_with_data(headers, data)
 
-        resp, body = self.post('v1/images', None, headers)
+        resp, body = self.post('images', None, headers)
         self.expected_success(201, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
-    def update_image(self, image_id, **kwargs):
+    def update_image(self, image_id, data=None, **kwargs):
+        """Update an image.
+
+        Available params: http://developer.openstack.org/
+                          api-ref-image-v1.html#updateImage-v1
+        """
         headers = {}
-        data = kwargs.pop('data', None)
         headers.update(self._image_meta_to_headers(kwargs))
 
         if data is not None:
             return self._update_with_data(image_id, headers, data)
 
-        url = 'v1/images/%s' % image_id
+        url = 'images/%s' % image_id
         resp, body = self.put(url, None, headers)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_image(self, image_id):
-        url = 'v1/images/%s' % image_id
+        url = 'images/%s' % image_id
         resp, body = self.delete(url)
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
@@ -171,7 +127,7 @@
         any changes.
         :param changes_since: The name is changed to changes-since
         """
-        url = 'v1/images'
+        url = 'images'
 
         if detail:
             url += '/detail'
@@ -193,22 +149,22 @@
 
     def check_image(self, image_id):
         """Check image metadata."""
-        url = 'v1/images/%s' % image_id
-        resp, __ = self.head(url)
+        url = 'images/%s' % image_id
+        resp, body = self.head(url)
         self.expected_success(200, resp.status)
-        body = self._image_meta_from_headers(resp)
         return rest_client.ResponseBody(resp, body)
 
     def show_image(self, image_id):
         """Get image details plus the image itself."""
-        url = 'v1/images/%s' % image_id
+        url = 'images/%s' % image_id
         resp, body = self.get(url)
         self.expected_success(200, resp.status)
         return rest_client.ResponseBodyData(resp, body)
 
     def is_resource_deleted(self, id):
         try:
-            if self.check_image(id)['status'] == 'deleted':
+            resp = self.check_image(id)
+            if resp.response["x-image-meta-status"] == 'deleted':
                 return True
         except lib_exc.NotFound:
             return True
@@ -218,36 +174,3 @@
     def resource_type(self):
         """Returns the primary type of resource this client works with."""
         return 'image_meta'
-
-    def list_image_members(self, image_id):
-        url = 'v1/images/%s/members' % image_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_shared_images(self, tenant_id):
-        """List shared images with the specified tenant"""
-        url = 'v1/shared-images/%s' % tenant_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def add_member(self, member_id, image_id, **kwargs):
-        """Add a member to an image.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-image-v1.html#addMember-v1
-        """
-        url = 'v1/images/%s/members/%s' % (image_id, member_id)
-        body = json.dumps({'member': kwargs})
-        resp, __ = self.put(url, body)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def delete_member(self, member_id, image_id):
-        url = 'v1/images/%s/members/%s' % (image_id, member_id)
-        resp, __ = self.delete(url)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
diff --git a/tempest/services/image/v2/json/images_client.py b/tempest/services/image/v2/json/images_client.py
deleted file mode 100644
index 83f56cc..0000000
--- a/tempest/services/image/v2/json/images_client.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# Copyright 2013 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.
-
-import functools
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-
-CHUNKSIZE = 1024 * 64  # 64kB
-
-
-class ImagesClient(rest_client.RestClient):
-
-    def update_image(self, image_id, patch):
-        """Update an image.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-image-v2.html#updateImage-v2
-        """
-        data = json.dumps(patch)
-        headers = {"Content-Type": "application/openstack-images-v2.0"
-                                   "-json-patch"}
-        resp, body = self.patch('v2/images/%s' % image_id, data, headers)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_image(self, **kwargs):
-        """Create an image.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-image-v2.html#createImage-v2
-        """
-        data = json.dumps(kwargs)
-        resp, body = self.post('v2/images', data)
-        self.expected_success(201, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def deactivate_image(self, image_id):
-        url = 'v2/images/%s/actions/deactivate' % image_id
-        resp, body = self.post(url, None)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def reactivate_image(self, image_id):
-        url = 'v2/images/%s/actions/reactivate' % image_id
-        resp, body = self.post(url, None)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_image(self, image_id):
-        url = 'v2/images/%s' % image_id
-        resp, _ = self.delete(url)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def list_images(self, params=None):
-        url = 'v2/images'
-
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_image(self, image_id):
-        url = 'v2/images/%s' % image_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def is_resource_deleted(self, id):
-        try:
-            self.show_image(id)
-        except lib_exc.NotFound:
-            return True
-        return False
-
-    @property
-    def resource_type(self):
-        """Returns the primary type of resource this client works with."""
-        return 'image'
-
-    def store_image_file(self, image_id, data):
-        url = 'v2/images/%s/file' % image_id
-
-        # We are going to do chunked transfert, so split the input data
-        # info fixed-sized chunks.
-        headers = {'Content-Type': 'application/octet-stream'}
-        data = iter(functools.partial(data.read, CHUNKSIZE), b'')
-
-        resp, body = self.request('PUT', url, headers=headers,
-                                  body=data, chunked=True)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_image_file(self, image_id):
-        url = 'v2/images/%s/file' % image_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBodyData(resp, body)
-
-    def add_image_tag(self, image_id, tag):
-        url = 'v2/images/%s/tags/%s' % (image_id, tag)
-        resp, body = self.put(url, body=None)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_image_tag(self, image_id, tag):
-        url = 'v2/images/%s/tags/%s' % (image_id, tag)
-        resp, _ = self.delete(url)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def list_image_members(self, image_id):
-        url = 'v2/images/%s/members' % image_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_image_member(self, image_id, **kwargs):
-        """Create an image member.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-image-v2.html#createImageMember-v2
-        """
-        url = 'v2/images/%s/members' % image_id
-        data = json.dumps(kwargs)
-        resp, body = self.post(url, data)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_image_member(self, image_id, member_id, **kwargs):
-        """Update an image member.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-image-v2.html#updateImageMember-v2
-        """
-        url = 'v2/images/%s/members/%s' % (image_id, member_id)
-        data = json.dumps(kwargs)
-        resp, body = self.put(url, data)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_image_member(self, image_id, member_id):
-        url = 'v2/images/%s/members/%s' % (image_id, member_id)
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, json.loads(body))
-
-    def delete_image_member(self, image_id, member_id):
-        url = 'v2/images/%s/members/%s' % (image_id, member_id)
-        resp, _ = self.delete(url)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def show_schema(self, schema):
-        url = 'v2/schemas/%s' % schema
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_resource_types(self):
-        url = '/v2/metadefs/resource_types'
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_namespace(self, **kwargs):
-        """Create a namespace.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-image-v2.html#createNamespace-v2
-        """
-        data = json.dumps(kwargs)
-        resp, body = self.post('/v2/metadefs/namespaces', data)
-        self.expected_success(201, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_namespace(self, namespace):
-        url = '/v2/metadefs/namespaces/%s' % namespace
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_namespace(self, namespace, **kwargs):
-        """Update a namespace.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-image-v2.html#updateNamespace-v2
-        """
-        # NOTE: On Glance API, we need to pass namespace on both URI
-        # and a request body.
-        params = {'namespace': namespace}
-        params.update(kwargs)
-        data = json.dumps(params)
-        url = '/v2/metadefs/namespaces/%s' % namespace
-        resp, body = self.put(url, body=data)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_namespace(self, namespace):
-        url = '/v2/metadefs/namespaces/%s' % namespace
-        resp, _ = self.delete(url)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
diff --git a/tempest/services/volume/base/base_v3_client.py b/tempest/services/volume/base/base_v3_client.py
new file mode 100644
index 0000000..ad6f760
--- /dev/null
+++ b/tempest/services/volume/base/base_v3_client.py
@@ -0,0 +1,46 @@
+# Copyright 2016 Andrew Kerr
+# 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.common import api_version_utils
+from tempest.lib.common import rest_client
+
+VOLUME_MICROVERSION = None
+
+
+class BaseV3Client(rest_client.RestClient):
+    """Base class to handle Cinder v3 client microversion support."""
+    api_version = 'v3'
+    api_microversion_header_name = 'Openstack-Api-Version'
+
+    def get_headers(self, accept_type=None, send_type=None):
+        headers = super(BaseV3Client, self).get_headers(
+            accept_type=accept_type, send_type=send_type)
+        if VOLUME_MICROVERSION:
+            headers[self.api_microversion_header_name] = ('volume %s' %
+                                                          VOLUME_MICROVERSION)
+        return headers
+
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None, chunked=False):
+
+        resp, resp_body = super(BaseV3Client, self).request(
+            method, url, extra_headers, headers, body, chunked)
+        if (VOLUME_MICROVERSION and
+            VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
+            api_version_utils.assert_version_header_matches_request(
+                self.api_microversion_header_name,
+                'volume %s' % VOLUME_MICROVERSION,
+                resp)
+        return resp, resp_body
diff --git a/tempest/services/image/v2/__init__.py b/tempest/services/volume/v3/__init__.py
similarity index 100%
copy from tempest/services/image/v2/__init__.py
copy to tempest/services/volume/v3/__init__.py
diff --git a/tempest/services/image/v2/json/__init__.py b/tempest/services/volume/v3/json/__init__.py
similarity index 100%
rename from tempest/services/image/v2/json/__init__.py
rename to tempest/services/volume/v3/json/__init__.py
diff --git a/tempest/services/volume/v3/json/messages_client.py b/tempest/services/volume/v3/json/messages_client.py
new file mode 100644
index 0000000..6be6d59
--- /dev/null
+++ b/tempest/services/volume/v3/json/messages_client.py
@@ -0,0 +1,59 @@
+# Copyright 2016 Andrew Kerr
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+from tempest.services.volume.base import base_v3_client
+
+
+class MessagesClient(base_v3_client.BaseV3Client):
+    """Client class to send user messages API requests."""
+
+    def show_message(self, message_id):
+        """Show details for a single message."""
+        url = 'messages/%s' % str(message_id)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_messages(self):
+        """List all messages."""
+        url = 'messages'
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_message(self, message_id):
+        """Delete a single message."""
+        url = 'messages/%s' % str(message_id)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.show_message(id)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'message'
diff --git a/tempest/test.py b/tempest/test.py
index d31c509..4e06db8 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -39,6 +39,7 @@
 from tempest import exceptions
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
 
 LOG = logging.getLogger(__name__)
 
@@ -539,9 +540,10 @@
             if hasattr(cred_provider, credentials_method):
                 creds = getattr(cred_provider, credentials_method)()
             else:
-                raise exceptions.InvalidCredentials(
+                raise lib_exc.InvalidCredentials(
                     "Invalid credentials type %s" % credential_type)
-        return cls.client_manager(credentials=creds, service=cls._service)
+        return cls.client_manager(credentials=creds.credentials,
+                                  service=cls._service)
 
     @classmethod
     def clear_credentials(cls):
@@ -640,7 +642,7 @@
                 credentials.is_admin_available(
                     identity_version=cls.get_identity_version())):
             admin_creds = cred_provider.get_admin_creds()
-            admin_manager = clients.Manager(admin_creds)
+            admin_manager = clients.Manager(admin_creds.credentials)
             networks_client = admin_manager.compute_networks_client
         return fixed_network.get_tenant_network(
             cred_provider, networks_client, CONF.compute.fixed_network_name)
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
new file mode 100755
index 0000000..b3931d1
--- /dev/null
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -0,0 +1,346 @@
+# 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 fixtures
+import mock
+from oslo_config import cfg
+
+from tempest.cmd import account_generator
+from tempest import config
+from tempest.tests import base
+from tempest.tests import fake_config
+from tempest.tests.lib import fake_identity
+
+
+class FakeOpts(object):
+
+    def __init__(self, version=3):
+        self.os_username = 'fake_user'
+        self.os_password = 'fake_password'
+        self.os_project_name = 'fake_project_name'
+        self.os_tenant_name = None
+        self.os_domain_name = 'fake_domain'
+        self.tag = 'fake'
+        self.concurrency = 2
+        self.with_admin = True
+        self.identity_version = version
+        self.accounts = 'fake_accounts.yml'
+
+
+class MockHelpersMixin(object):
+
+    def mock_config_and_opts(self, identity_version):
+        self.useFixture(fake_config.ConfigFixture())
+        self.patchobject(config, 'TempestConfigPrivate',
+                         fake_config.FakePrivate)
+        self.opts = FakeOpts(version=identity_version)
+
+    def mock_resource_creation(self):
+        fake_resource = dict(id='id', name='name')
+        self.user_create_fixture = self.useFixture(fixtures.MockPatch(
+            self.cred_client + '.create_user', return_value=fake_resource))
+        self.useFixture(fixtures.MockPatch(
+            self.cred_client + '.create_project',
+            return_value=fake_resource))
+        self.useFixture(fixtures.MockPatch(
+            self.cred_client + '.assign_user_role'))
+        self.useFixture(fixtures.MockPatch(
+            self.cred_client + '._check_role_exists',
+            return_value=fake_resource))
+        self.useFixture(fixtures.MockPatch(
+            self.dynamic_creds + '._create_network',
+            return_value=fake_resource))
+        self.useFixture(fixtures.MockPatch(
+            self.dynamic_creds + '._create_subnet',
+            return_value=fake_resource))
+        self.useFixture(fixtures.MockPatch(
+            self.dynamic_creds + '._create_router',
+            return_value=fake_resource))
+        self.useFixture(fixtures.MockPatch(
+            self.dynamic_creds + '._add_router_interface',
+            return_value=fake_resource))
+
+    def mock_domains(self):
+        fake_domain_list = {'domains': [{'id': 'fake_domain',
+                                         'name': 'Fake_Domain'}]}
+        self.useFixture(fixtures.MockPatch(''.join([
+            'tempest.services.identity.v3.json.domains_client.'
+            'DomainsClient.list_domains']),
+            return_value=fake_domain_list))
+        self.useFixture(fixtures.MockPatch(
+            self.cred_client + '.assign_user_role_on_domain'))
+
+
+class TestAccountGeneratorV2(base.TestCase, MockHelpersMixin):
+
+    identity_version = 2
+    identity_response = fake_identity._fake_v2_response
+
+    def setUp(self):
+        super(TestAccountGeneratorV2, self).setUp()
+        self.mock_config_and_opts(self.identity_version)
+        self.useFixture(fixtures.MockPatch(
+            'tempest.lib.auth.AuthProvider.set_auth',
+            return_value=self.identity_response))
+
+    def test_get_credential_provider(self):
+        cp = account_generator.get_credential_provider(self.opts)
+        admin_creds = cp.default_admin_creds
+        self.assertEqual(self.opts.tag, cp.name)
+        self.assertIn(str(self.opts.identity_version), cp.identity_version)
+        self.assertEqual(self.opts.os_username, admin_creds.username)
+        self.assertEqual(self.opts.os_project_name, admin_creds.tenant_name)
+        self.assertEqual(self.opts.os_password, admin_creds.password)
+        self.assertFalse(hasattr(admin_creds, 'domain_name'))
+
+    def test_get_credential_provider_with_tenant(self):
+        self.opts.os_project_name = None
+        self.opts.os_tenant_name = 'fake_tenant'
+        cp = account_generator.get_credential_provider(self.opts)
+        admin_creds = cp.default_admin_creds
+        self.assertEqual(self.opts.os_tenant_name, admin_creds.tenant_name)
+
+
+class TestAccountGeneratorV3(TestAccountGeneratorV2):
+
+    identity_version = 3
+    identity_response = fake_identity._fake_v3_response
+
+    def setUp(self):
+        super(TestAccountGeneratorV3, self).setUp()
+        fake_domain_list = {'domains': [{'id': 'fake_domain'}]}
+        self.useFixture(fixtures.MockPatch(''.join([
+            'tempest.services.identity.v3.json.domains_client.'
+            'DomainsClient.list_domains']),
+            return_value=fake_domain_list))
+
+    def test_get_credential_provider(self):
+        cp = account_generator.get_credential_provider(self.opts)
+        admin_creds = cp.default_admin_creds
+        self.assertEqual(self.opts.tag, cp.name)
+        self.assertIn(str(self.opts.identity_version), cp.identity_version)
+        self.assertEqual(self.opts.os_username, admin_creds.username)
+        self.assertEqual(self.opts.os_project_name, admin_creds.tenant_name)
+        self.assertEqual(self.opts.os_password, admin_creds.password)
+        self.assertEqual(self.opts.os_domain_name, admin_creds.domain_name)
+
+    def test_get_credential_provider_without_domain(self):
+        self.opts.os_domain_name = None
+        cp = account_generator.get_credential_provider(self.opts)
+        admin_creds = cp.default_admin_creds
+        self.assertIsNotNone(admin_creds.domain_name)
+
+
+class TestGenerateResourcesV2(base.TestCase, MockHelpersMixin):
+
+    identity_version = 2
+    identity_response = fake_identity._fake_v2_response
+    cred_client = 'tempest.common.cred_client.V2CredsClient'
+    dynamic_creds = 'tempest.common.dynamic_creds.DynamicCredentialProvider'
+
+    def setUp(self):
+        super(TestGenerateResourcesV2, self).setUp()
+        self.mock_config_and_opts(self.identity_version)
+        self.useFixture(fixtures.MockPatch(
+            'tempest.lib.auth.AuthProvider.set_auth',
+            return_value=self.identity_response))
+        self.cred_provider = account_generator.get_credential_provider(
+            self.opts)
+        self.mock_resource_creation()
+
+    def test_generate_resources_no_admin(self):
+        cfg.CONF.set_default('swift', False, group='service_available')
+        cfg.CONF.set_default('heat', False, group='service_available')
+        cfg.CONF.set_default('operator_role', 'fake_operator',
+                             group='object-storage')
+        cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
+                             group='object-storage')
+        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
+                             group='orchestration')
+        resources = account_generator.generate_resources(
+            self.cred_provider, admin=False)
+        resource_types = [k for k, _ in resources]
+        # No admin, no heat, no swift, expect two credentials only
+        self.assertEqual(2, len(resources))
+        # Ensure create_user was invoked twice (two distinct users)
+        self.assertEqual(2, self.user_create_fixture.mock.call_count)
+        self.assertIn('primary', resource_types)
+        self.assertIn('alt', resource_types)
+        self.assertNotIn('admin', resource_types)
+        self.assertNotIn(['fake_operator'], resource_types)
+        self.assertNotIn(['fake_reseller'], resource_types)
+        self.assertNotIn(['fake_owner'], resource_types)
+        for resource in resources:
+            self.assertIsNotNone(resource[1].network)
+            self.assertIsNotNone(resource[1].router)
+            self.assertIsNotNone(resource[1].subnet)
+
+    def test_generate_resources_admin(self):
+        cfg.CONF.set_default('swift', False, group='service_available')
+        cfg.CONF.set_default('heat', False, group='service_available')
+        cfg.CONF.set_default('operator_role', 'fake_operator',
+                             group='object-storage')
+        cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
+                             group='object-storage')
+        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
+                             group='orchestration')
+        resources = account_generator.generate_resources(
+            self.cred_provider, admin=True)
+        resource_types = [k for k, _ in resources]
+        # Admin, no heat, no swift, expect three credentials only
+        self.assertEqual(3, len(resources))
+        # Ensure create_user was invoked 3 times (3 distinct users)
+        self.assertEqual(3, self.user_create_fixture.mock.call_count)
+        self.assertIn('primary', resource_types)
+        self.assertIn('alt', resource_types)
+        self.assertIn('admin', resource_types)
+        self.assertNotIn(['fake_operator'], resource_types)
+        self.assertNotIn(['fake_reseller'], resource_types)
+        self.assertNotIn(['fake_owner'], resource_types)
+        for resource in resources:
+            self.assertIsNotNone(resource[1].network)
+            self.assertIsNotNone(resource[1].router)
+            self.assertIsNotNone(resource[1].subnet)
+
+    def test_generate_resources_swift_heat_admin(self):
+        cfg.CONF.set_default('swift', True, group='service_available')
+        cfg.CONF.set_default('heat', True, group='service_available')
+        cfg.CONF.set_default('operator_role', 'fake_operator',
+                             group='object-storage')
+        cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
+                             group='object-storage')
+        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
+                             group='orchestration')
+        resources = account_generator.generate_resources(
+            self.cred_provider, admin=True)
+        resource_types = [k for k, _ in resources]
+        # all options on, expect six credentials
+        self.assertEqual(6, len(resources))
+        # Ensure create_user was invoked 6 times (6 distinct users)
+        self.assertEqual(6, self.user_create_fixture.mock.call_count)
+        self.assertIn('primary', resource_types)
+        self.assertIn('alt', resource_types)
+        self.assertIn('admin', resource_types)
+        self.assertIn(['fake_operator'], resource_types)
+        self.assertIn(['fake_reseller'], resource_types)
+        self.assertIn(['fake_owner', 'fake_operator'], resource_types)
+        for resource in resources:
+            self.assertIsNotNone(resource[1].network)
+            self.assertIsNotNone(resource[1].router)
+            self.assertIsNotNone(resource[1].subnet)
+
+
+class TestGenerateResourcesV3(TestGenerateResourcesV2):
+
+    identity_version = 3
+    identity_response = fake_identity._fake_v3_response
+    cred_client = 'tempest.common.cred_client.V3CredsClient'
+
+    def setUp(self):
+        self.mock_domains()
+        super(TestGenerateResourcesV3, self).setUp()
+
+
+class TestDumpAccountsV2(base.TestCase, MockHelpersMixin):
+
+    identity_version = 2
+    identity_response = fake_identity._fake_v2_response
+    cred_client = 'tempest.common.cred_client.V2CredsClient'
+    dynamic_creds = 'tempest.common.dynamic_creds.DynamicCredentialProvider'
+    domain_is_in = False
+
+    def setUp(self):
+        super(TestDumpAccountsV2, self).setUp()
+        self.mock_config_and_opts(self.identity_version)
+        self.useFixture(fixtures.MockPatch(
+            'tempest.lib.auth.AuthProvider.set_auth',
+            return_value=self.identity_response))
+        self.cred_provider = account_generator.get_credential_provider(
+            self.opts)
+        self.mock_resource_creation()
+        cfg.CONF.set_default('swift', True, group='service_available')
+        cfg.CONF.set_default('heat', True, group='service_available')
+        self.resources = account_generator.generate_resources(
+            self.cred_provider, admin=True)
+
+    def test_dump_accounts(self):
+        self.useFixture(fixtures.MockPatch('os.path.exists',
+                                           return_value=False))
+        mocked_open = mock.mock_open()
+        with mock.patch('{}.open'.format(account_generator.__name__),
+                        mocked_open, create=True):
+            with mock.patch('yaml.safe_dump') as yaml_dump_mock:
+                account_generator.setup_logging()
+                account_generator.dump_accounts(self.resources,
+                                                self.opts.identity_version,
+                                                self.opts.accounts)
+        mocked_open.assert_called_once_with(self.opts.accounts, 'w')
+        handle = mocked_open()
+        # Ordered args in [0], keyword args in [1]
+        accounts, f = yaml_dump_mock.call_args[0]
+        self.assertEqual(handle, f)
+        self.assertEqual(6, len(accounts))
+        if self.domain_is_in:
+            self.assertIn('domain_name', accounts[0].keys())
+        else:
+            self.assertNotIn('domain_name', accounts[0].keys())
+        self.assertEqual(1, len([x for x in accounts if
+                                 x.get('types') == ['admin']]))
+        self.assertEqual(3, len([x for x in accounts if 'roles' in x]))
+        for account in accounts:
+            self.assertIn('resources', account)
+            self.assertIn('network', account.get('resources'))
+
+    def test_dump_accounts_existing_file(self):
+        self.useFixture(fixtures.MockPatch('os.path.exists',
+                                           return_value=True))
+        rename_mock = self.useFixture(fixtures.MockPatch('os.rename')).mock
+        backup_file = '.'.join((self.opts.accounts, 'bak'))
+        mocked_open = mock.mock_open()
+        with mock.patch('{}.open'.format(account_generator.__name__),
+                        mocked_open, create=True):
+            with mock.patch('yaml.safe_dump') as yaml_dump_mock:
+                account_generator.setup_logging()
+                account_generator.dump_accounts(self.resources,
+                                                self.opts.identity_version,
+                                                self.opts.accounts)
+        rename_mock.assert_called_once_with(self.opts.accounts, backup_file)
+        mocked_open.assert_called_once_with(self.opts.accounts, 'w')
+        handle = mocked_open()
+        # Ordered args in [0], keyword args in [1]
+        accounts, f = yaml_dump_mock.call_args[0]
+        self.assertEqual(handle, f)
+        self.assertEqual(6, len(accounts))
+        if self.domain_is_in:
+            self.assertIn('domain_name', accounts[0].keys())
+        else:
+            self.assertNotIn('domain_name', accounts[0].keys())
+        self.assertEqual(1, len([x for x in accounts if
+                                 x.get('types') == ['admin']]))
+        self.assertEqual(3, len([x for x in accounts if 'roles' in x]))
+        for account in accounts:
+            self.assertIn('resources', account)
+            self.assertIn('network', account.get('resources'))
+
+
+class TestDumpAccountsV3(TestDumpAccountsV2):
+
+    identity_version = 3
+    identity_response = fake_identity._fake_v3_response
+    cred_client = 'tempest.common.cred_client.V3CredsClient'
+    domain_is_in = True
+
+    def setUp(self):
+        self.mock_domains()
+        super(TestDumpAccountsV3, self).setUp()
diff --git a/tempest/tests/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
index 2d0256a..50660ff 100644
--- a/tempest/tests/cmd/test_javelin.py
+++ b/tempest/tests/cmd/test_javelin.py
@@ -120,11 +120,12 @@
         fake_tenant_id = self.fake_object['tenant']['id']
         fake_email = "%s@%s" % (self.fake_object['user'], fake_tenant_id)
         mocked_function = self.fake_client.users.create_user
-        mocked_function.assert_called_once_with(self.fake_object['name'],
-                                                self.fake_object['password'],
-                                                fake_tenant_id,
-                                                fake_email,
-                                                enabled=True)
+        mocked_function.assert_called_once_with(
+            name=self.fake_object['name'],
+            password=self.fake_object['password'],
+            tenantId=fake_tenant_id,
+            email=fake_email,
+            enabled=True)
 
     def test_create_user_missing_tenant(self):
         self.useFixture(mockpatch.Patch(
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
new file mode 100644
index 0000000..9aa06e5
--- /dev/null
+++ b/tempest/tests/cmd/test_run.py
@@ -0,0 +1,110 @@
+# Copyright 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.
+
+import argparse
+import os
+import shutil
+import subprocess
+import tempfile
+
+import mock
+
+from tempest.cmd import run
+from tempest.tests import base
+
+DEVNULL = open(os.devnull, 'wb')
+
+
+class TestTempestRun(base.TestCase):
+
+    def setUp(self):
+        super(TestTempestRun, self).setUp()
+        self.run_cmd = run.TempestRun(None, None)
+
+    def test_build_options(self):
+        args = mock.Mock(spec=argparse.Namespace)
+        setattr(args, "subunit", True)
+        setattr(args, "parallel", False)
+        setattr(args, "concurrency", 10)
+        options = self.run_cmd._build_options(args)
+        self.assertEqual(['--subunit',
+                          '--concurrency=10'],
+                         options)
+
+    def test__build_regex_default(self):
+        args = mock.Mock(spec=argparse.Namespace)
+        setattr(args, 'smoke', False)
+        setattr(args, 'regex', '')
+        self.assertEqual('', self.run_cmd._build_regex(args))
+
+    def test__build_regex_smoke(self):
+        args = mock.Mock(spec=argparse.Namespace)
+        setattr(args, "smoke", True)
+        setattr(args, 'regex', '')
+        self.assertEqual('smoke', self.run_cmd._build_regex(args))
+
+    def test__build_regex_regex(self):
+        args = mock.Mock(spec=argparse.Namespace)
+        setattr(args, 'smoke', False)
+        setattr(args, "regex", 'i_am_a_fun_little_regex')
+        self.assertEqual('i_am_a_fun_little_regex',
+                         self.run_cmd._build_regex(args))
+
+
+class TestRunReturnCode(base.TestCase):
+    def setUp(self):
+        super(TestRunReturnCode, self).setUp()
+        # Setup test dirs
+        self.directory = tempfile.mkdtemp(prefix='tempest-unit')
+        self.addCleanup(shutil.rmtree, self.directory)
+        self.test_dir = os.path.join(self.directory, 'tests')
+        os.mkdir(self.test_dir)
+        # Setup Test files
+        self.testr_conf_file = os.path.join(self.directory, '.testr.conf')
+        self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg')
+        self.passing_file = os.path.join(self.test_dir, 'test_passing.py')
+        self.failing_file = os.path.join(self.test_dir, 'test_failing.py')
+        self.init_file = os.path.join(self.test_dir, '__init__.py')
+        self.setup_py = os.path.join(self.directory, 'setup.py')
+        shutil.copy('tempest/tests/files/testr-conf', self.testr_conf_file)
+        shutil.copy('tempest/tests/files/passing-tests', self.passing_file)
+        shutil.copy('tempest/tests/files/failing-tests', self.failing_file)
+        shutil.copy('setup.py', self.setup_py)
+        shutil.copy('tempest/tests/files/setup.cfg', self.setup_cfg_file)
+        shutil.copy('tempest/tests/files/__init__.py', self.init_file)
+        # Change directory, run wrapper and check result
+        self.addCleanup(os.chdir, os.path.abspath(os.curdir))
+        os.chdir(self.directory)
+
+    def assertRunExit(self, cmd, expected):
+        p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        out, err = p.communicate()
+        msg = ("Running %s got an unexpected returncode\n"
+               "Stdout: %s\nStderr: %s" % (' '.join(cmd), out, err))
+        self.assertEqual(p.returncode, expected, msg)
+
+    def test_tempest_run_passes(self):
+        # Git init is required for the pbr testr command. pbr requires a git
+        # version or an sdist to work. so make the test directory a git repo
+        # too.
+        subprocess.call(['git', 'init'], stderr=DEVNULL)
+        self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0)
+
+    def test_tempest_run_fails(self):
+        # Git init is required for the pbr testr command. pbr requires a git
+        # version or an sdist to work. so make the test directory a git repo
+        # too.
+        subprocess.call(['git', 'init'], stderr=DEVNULL)
+        self.assertRunExit(['tempest', 'run'], 1)
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
new file mode 100644
index 0000000..c4bd7b2
--- /dev/null
+++ b/tempest/tests/cmd/test_workspace.py
@@ -0,0 +1,124 @@
+# Copyright 2016 Rackspace
+#
+# 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 os
+import shutil
+import subprocess
+import tempfile
+
+from tempest.cmd.workspace import WorkspaceManager
+from tempest.lib.common.utils import data_utils
+from tempest.tests import base
+
+
+class TestTempestWorkspaceBase(base.TestCase):
+    def setUp(self):
+        super(TestTempestWorkspaceBase, self).setUp()
+        self.name = data_utils.rand_uuid()
+        self.path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.path, ignore_errors=True)
+        store_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, store_dir, ignore_errors=True)
+        self.store_file = os.path.join(store_dir, 'workspace.yaml')
+        self.workspace_manager = WorkspaceManager(path=self.store_file)
+        self.workspace_manager.register_new_workspace(self.name, self.path)
+
+
+class TestTempestWorkspace(TestTempestWorkspaceBase):
+    def _run_cmd_gets_return_code(self, cmd, expected):
+        process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE)
+        stdout, stderr = process.communicate()
+        return_code = process.returncode
+        msg = ("%s failled with:\nstdout: %s\nstderr: %s" % (' '.join(cmd),
+                                                             stdout, stderr))
+        self.assertEqual(return_code, expected, msg)
+
+    def test_run_workspace_list(self):
+        cmd = ['tempest', 'workspace', '--workspace-path',
+               self.store_file, 'list']
+        self._run_cmd_gets_return_code(cmd, 0)
+
+    def test_run_workspace_register(self):
+        name = data_utils.rand_uuid()
+        path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, path, ignore_errors=True)
+        cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
+               'register', '--name', name, '--path', path]
+        self._run_cmd_gets_return_code(cmd, 0)
+        self.assertIsNotNone(self.workspace_manager.get_workspace(name))
+
+    def test_run_workspace_rename(self):
+        new_name = data_utils.rand_uuid()
+        cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
+               'rename', "--old-name", self.name, '--new-name', new_name]
+        self._run_cmd_gets_return_code(cmd, 0)
+        self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+        self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
+
+    def test_run_workspace_move(self):
+        new_path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
+        cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
+               'move', '--name', self.name, '--path', new_path]
+        self._run_cmd_gets_return_code(cmd, 0)
+        self.assertEqual(
+            self.workspace_manager.get_workspace(self.name), new_path)
+
+    def test_run_workspace_remove(self):
+        cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
+               'remove', '--name', self.name]
+        self._run_cmd_gets_return_code(cmd, 0)
+        self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
+
+class TestTempestWorkspaceManager(TestTempestWorkspaceBase):
+    def setUp(self):
+        super(TestTempestWorkspaceManager, self).setUp()
+        self.name = data_utils.rand_uuid()
+        self.path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.path, ignore_errors=True)
+        store_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, store_dir, ignore_errors=True)
+        self.store_file = os.path.join(store_dir, 'workspace.yaml')
+        self.workspace_manager = WorkspaceManager(path=self.store_file)
+        self.workspace_manager.register_new_workspace(self.name, self.path)
+
+    def test_workspace_manager_get(self):
+        self.assertIsNotNone(self.workspace_manager.get_workspace(self.name))
+
+    def test_workspace_manager_rename(self):
+        new_name = data_utils.rand_uuid()
+        self.workspace_manager.rename_workspace(self.name, new_name)
+        self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+        self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
+
+    def test_workspace_manager_move(self):
+        new_path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
+        self.workspace_manager.move_workspace(self.name, new_path)
+        self.assertEqual(
+            self.workspace_manager.get_workspace(self.name), new_path)
+
+    def test_workspace_manager_remove(self):
+        self.workspace_manager.remove_workspace(self.name)
+        self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
+    def test_path_expansion(self):
+        name = data_utils.rand_uuid()
+        path = os.path.join("~", name)
+        os.makedirs(os.path.expanduser(path))
+        self.addCleanup(shutil.rmtree, path, ignore_errors=True)
+        self.workspace_manager.register_new_workspace(name, path)
+        self.assertIsNotNone(self.workspace_manager.get_workspace(name))
diff --git a/tempest/tests/common/test_image.py b/tempest/tests/common/test_image.py
new file mode 100644
index 0000000..fdd0ae8
--- /dev/null
+++ b/tempest/tests/common/test_image.py
@@ -0,0 +1,40 @@
+# Copyright 2016 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.common import image
+from tempest.lib.common import rest_client
+from tempest.tests import base
+
+
+class TestImage(base.TestCase):
+
+    def test_get_image_meta_from_headers(self):
+        resp = {
+            'x-image-meta-id': 'ea30c926-0629-4400-bb6e-f8a8da6a4e56',
+            'x-image-meta-owner': '8f421f9470e645b1b10f5d2db7804924',
+            'x-image-meta-status': 'queued',
+            'x-image-meta-name': 'New Http Image'
+        }
+        respbody = rest_client.ResponseBody(resp)
+        observed = image.get_image_meta_from_headers(respbody)
+
+        expected = {
+            'properties': {},
+            'id': 'ea30c926-0629-4400-bb6e-f8a8da6a4e56',
+            'owner': '8f421f9470e645b1b10f5d2db7804924',
+            'status': 'queued',
+            'name': 'New Http Image'
+        }
+        self.assertEqual(expected, observed)
diff --git a/tempest/tests/lib/fake_identity.py b/tempest/tests/lib/fake_identity.py
index c903e47..831f8b5 100644
--- a/tempest/tests/lib/fake_identity.py
+++ b/tempest/tests/lib/fake_identity.py
@@ -100,7 +100,8 @@
 
     ],
     "type": "compute",
-    "id": "fake_compute_endpoint"
+    "id": "fake_compute_endpoint",
+    "name": "nova"
 }
 
 CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ]
diff --git a/tempest/tests/lib/services/identity/v2/test_endpoints_client.py b/tempest/tests/lib/services/identity/v2/test_endpoints_client.py
new file mode 100644
index 0000000..7d2cac2
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_endpoints_client.py
@@ -0,0 +1,99 @@
+# Copyright 2016 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.lib.services.identity.v2 import endpoints_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestEndpointsClient(base.BaseServiceTest):
+    FAKE_CREATE_ENDPOINT = {
+        "endpoint": {
+            "id": 1,
+            "tenantId": 1,
+            "region": "North",
+            "type": "compute",
+            "publicURL": "https://compute.north.public.com/v1",
+            "internalURL": "https://compute.north.internal.com/v1",
+            "adminURL": "https://compute.north.internal.com/v1"
+        }
+    }
+
+    FAKE_LIST_ENDPOINTS = {
+        "endpoints": [
+            {
+                "id": 1,
+                "tenantId": "1",
+                "region": "North",
+                "type": "compute",
+                "publicURL": "https://compute.north.public.com/v1",
+                "internalURL": "https://compute.north.internal.com/v1",
+                "adminURL": "https://compute.north.internal.com/v1"
+            },
+            {
+                "id": 2,
+                "tenantId": "1",
+                "region": "South",
+                "type": "compute",
+                "publicURL": "https://compute.north.public.com/v1",
+                "internalURL": "https://compute.north.internal.com/v1",
+                "adminURL": "https://compute.north.internal.com/v1"
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestEndpointsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = endpoints_client.EndpointsClient(fake_auth,
+                                                       'identity', 'regionOne')
+
+    def _test_create_endpoint(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_endpoint,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_ENDPOINT,
+            bytes_body,
+            service_id="b344506af7644f6794d9cb316600b020",
+            region="region-demo",
+            publicurl="https://compute.north.public.com/v1",
+            adminurl="https://compute.north.internal.com/v1",
+            internalurl="https://compute.north.internal.com/v1")
+
+    def _test_list_endpoints(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_endpoints,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ENDPOINTS,
+            bytes_body)
+
+    def test_create_endpoint_with_str_body(self):
+        self._test_create_endpoint()
+
+    def test_create_endpoint_with_bytes_body(self):
+        self._test_create_endpoint(bytes_body=True)
+
+    def test_list_endpoints_with_str_body(self):
+        self._test_list_endpoints()
+
+    def test_list_endpoints_with_bytes_body(self):
+        self._test_list_endpoints(bytes_body=True)
+
+    def test_delete_endpoint(self):
+        self.check_service_client_function(
+            self.client.delete_endpoint,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            endpoint_id="b344506af7644f6794d9cb316600b020",
+            status=204)
diff --git a/tempest/tests/lib/services/identity/v2/test_token_client.py b/tempest/tests/lib/services/identity/v2/test_token_client.py
index 7925152..dfce9b3 100644
--- a/tempest/tests/lib/services/identity/v2/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v2/test_token_client.py
@@ -25,9 +25,6 @@
 
 class TestTokenClientV2(base.TestCase):
 
-    def setUp(self):
-        super(TestTokenClientV2, self).setUp()
-
     def test_init_without_authurl(self):
         self.assertRaises(exceptions.IdentityError,
                           token_client.TokenClient, None)
diff --git a/tempest/services/image/v2/__init__.py b/tempest/tests/lib/services/image/__init__.py
similarity index 100%
copy from tempest/services/image/v2/__init__.py
copy to tempest/tests/lib/services/image/__init__.py
diff --git a/tempest/services/image/v2/__init__.py b/tempest/tests/lib/services/image/v2/__init__.py
similarity index 100%
copy from tempest/services/image/v2/__init__.py
copy to tempest/tests/lib/services/image/v2/__init__.py
diff --git a/tempest/tests/lib/services/image/v2/test_image_members_client.py b/tempest/tests/lib/services/image/v2/test_image_members_client.py
new file mode 100644
index 0000000..703b6e1
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_image_members_client.py
@@ -0,0 +1,90 @@
+# Copyright 2016 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.lib.services.image.v2 import image_members_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestImageMembersClient(base.BaseServiceTest):
+    FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER = {
+        "status": "pending",
+        "created_at": "2013-11-26T07:21:21Z",
+        "updated_at": "2013-11-26T07:21:21Z",
+        "image_id": "0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+        "member_id": "8989447062e04a818baf9e073fd04fa7",
+        "schema": "/v2/schemas/member"
+    }
+
+    def setUp(self):
+        super(TestImageMembersClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = image_members_client.ImageMembersClient(fake_auth,
+                                                              'image',
+                                                              'regionOne')
+
+    def _test_show_image_member(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_image_member,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER,
+            bytes_body,
+            image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+            member_id="8989447062e04a818baf9e073fd04fa7")
+
+    def _test_create_image_member(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_image_member,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER,
+            bytes_body,
+            image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+            member_id="8989447062e04a818baf9e073fd04fa7")
+
+    def _test_update_image_member(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_image_member,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER,
+            bytes_body,
+            image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+            member_id="8989447062e04a818baf9e073fd04fa7",
+            schema="/v2/schemas/member2")
+
+    def test_show_image_member_with_str_body(self):
+        self._test_show_image_member()
+
+    def test_show_image_member_with_bytes_body(self):
+        self._test_show_image_member(bytes_body=True)
+
+    def test_create_image_member_with_str_body(self):
+        self._test_create_image_member()
+
+    def test_create_image_member_with_bytes_body(self):
+        self._test_create_image_member(bytes_body=True)
+
+    def test_delete_image_member(self):
+        self.check_service_client_function(
+            self.client.delete_image_member,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
+            member_id="8989447062e04a818baf9e073fd04fa7",
+            status=204)
+
+    def test_update_image_member_with_str_body(self):
+        self._test_update_image_member()
+
+    def test_update_image_member_with_bytes_body(self):
+        self._test_update_image_member(bytes_body=True)
diff --git a/tempest/tests/lib/services/image/v2/test_images_client.py b/tempest/tests/lib/services/image/v2/test_images_client.py
new file mode 100644
index 0000000..9648985
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_images_client.py
@@ -0,0 +1,111 @@
+# Copyright 2016 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.lib.services.image.v2 import images_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestImagesClient(base.BaseServiceTest):
+    FAKE_CREATE_UPDATE_SHOW_IMAGE = {
+        "id": "e485aab9-0907-4973-921c-bb6da8a8fcf8",
+        "name": u"\u2740(*\xb4\u25e2`*)\u2740",
+        "status": "active",
+        "visibility": "public",
+        "size": 2254249,
+        "checksum": "2cec138d7dae2aa59038ef8c9aec2390",
+        "tags": [
+            "fedora",
+            "beefy"
+        ],
+        "created_at": "2012-08-10T19:23:50Z",
+        "updated_at": "2012-08-12T11:11:33Z",
+        "self": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea",
+        "file": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file",
+        "schema": "/v2/schemas/image",
+        "owner": None,
+        "min_ram": None,
+        "min_disk": None,
+        "disk_format": None,
+        "virtual_size": None,
+        "container_format": None
+    }
+
+    def setUp(self):
+        super(TestImagesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = images_client.ImagesClient(fake_auth,
+                                                 'image', 'regionOne')
+
+    def _test_update_image(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_image,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_CREATE_UPDATE_SHOW_IMAGE,
+            bytes_body,
+            image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8",
+            patch=[{"op": "add", "path": "/a/b/c", "value": ["foo", "bar"]}])
+
+    def _test_create_image(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_image,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_UPDATE_SHOW_IMAGE,
+            bytes_body,
+            name="virtual machine image",
+            status=201)
+
+    def _test_show_image(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_image,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_CREATE_UPDATE_SHOW_IMAGE,
+            bytes_body,
+            image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8")
+
+    def test_create_image_with_str_body(self):
+        self._test_create_image()
+
+    def test_create_image_with_bytes_body(self):
+        self._test_create_image(bytes_body=True)
+
+    def test_update_image_with_str_body(self):
+        self._test_update_image()
+
+    def test_update_image_with_bytes_body(self):
+        self._test_update_image(bytes_body=True)
+
+    def test_deactivate_image(self):
+        self.check_service_client_function(
+            self.client.deactivate_image,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204)
+
+    def test_reactivate_image(self):
+        self.check_service_client_function(
+            self.client.reactivate_image,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204)
+
+    def test_delete_image(self):
+        self.check_service_client_function(
+            self.client.delete_image,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204)
+
+    def test_show_image_with_str_body(self):
+        self._test_show_image()
+
+    def test_show_image_with_bytes_body(self):
+        self._test_show_image(bytes_body=True)
diff --git a/tempest/tests/lib/services/image/v2/test_namespaces_client.py b/tempest/tests/lib/services/image/v2/test_namespaces_client.py
new file mode 100644
index 0000000..4cb9d01
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_namespaces_client.py
@@ -0,0 +1,93 @@
+# Copyright 2016 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.lib.services.image.v2 import namespaces_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestNamespacesClient(base.BaseServiceTest):
+    FAKE_CREATE_SHOW_NAMESPACE = {
+        "namespace": "OS::Compute::Hypervisor",
+        "visibility": "public",
+        "description": "Tempest",
+        "display_name": u"\u2740(*\xb4\u25e1`*)\u2740",
+        "protected": True
+    }
+
+    FAKE_UPDATE_NAMESPACE = {
+        "namespace": "OS::Compute::Hypervisor",
+        "visibility": "public",
+        "description": "Tempest",
+        "display_name": u"\u2740(*\xb4\u25e2`*)\u2740",
+        "protected": True
+    }
+
+    def setUp(self):
+        super(TestNamespacesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = namespaces_client.NamespacesClient(fake_auth,
+                                                         'image', 'regionOne')
+
+    def _test_show_namespace(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_namespace,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_CREATE_SHOW_NAMESPACE,
+            bytes_body,
+            namespace="OS::Compute::Hypervisor")
+
+    def _test_create_namespace(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_namespace,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_SHOW_NAMESPACE,
+            bytes_body,
+            namespace="OS::Compute::Hypervisor",
+            visibility="public", description="Tempest",
+            display_name=u"\u2740(*\xb4\u25e1`*)\u2740", protected=True,
+            status=201)
+
+    def _test_update_namespace(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_namespace,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_UPDATE_NAMESPACE,
+            bytes_body,
+            namespace="OS::Compute::Hypervisor",
+            display_name=u"\u2740(*\xb4\u25e2`*)\u2740", protected=True)
+
+    def test_show_namespace_with_str_body(self):
+        self._test_show_namespace()
+
+    def test_show_namespace_with_bytes_body(self):
+        self._test_show_namespace(bytes_body=True)
+
+    def test_create_namespace_with_str_body(self):
+        self._test_create_namespace()
+
+    def test_create_namespace_with_bytes_body(self):
+        self._test_create_namespace(bytes_body=True)
+
+    def test_delete_namespace(self):
+        self.check_service_client_function(
+            self.client.delete_namespace,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {}, namespace="OS::Compute::Hypervisor", status=204)
+
+    def test_update_namespace_with_str_body(self):
+        self._test_update_namespace()
+
+    def test_update_namespace_with_bytes_body(self):
+        self._test_update_namespace(bytes_body=True)
diff --git a/tempest/tests/lib/services/image/v2/test_resource_types_client.py b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
new file mode 100644
index 0000000..2e3b117
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
@@ -0,0 +1,69 @@
+# Copyright 2016 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.lib.services.image.v2 import resource_types_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestResouceTypesClient(base.BaseServiceTest):
+    FAKE_LIST_RESOURCETYPES = {
+        "resource_types": [
+            {
+                "created_at": "2014-08-28T18:13:04Z",
+                "name": "OS::Glance::Image",
+                "updated_at": "2014-08-28T18:13:04Z"
+            },
+            {
+                "created_at": "2014-08-28T18:13:04Z",
+                "name": "OS::Cinder::Volume",
+                "updated_at": "2014-08-28T18:13:04Z"
+            },
+            {
+                "created_at": "2014-08-28T18:13:04Z",
+                "name": "OS::Nova::Flavor",
+                "updated_at": "2014-08-28T18:13:04Z"
+            },
+            {
+                "created_at": "2014-08-28T18:13:04Z",
+                "name": "OS::Nova::Aggregate",
+                "updated_at": "2014-08-28T18:13:04Z"
+            },
+            {
+                "created_at": "2014-08-28T18:13:04Z",
+                "name": u"\u2740(*\xb4\u25e1`*)\u2740",
+                "updated_at": "2014-08-28T18:13:04Z"
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestResouceTypesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = resource_types_client.ResourceTypesClient(fake_auth,
+                                                                'image',
+                                                                'regionOne')
+
+    def _test_list_resouce_types(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_resource_types,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_RESOURCETYPES,
+            bytes_body)
+
+    def test_list_resouce_types_with_str_body(self):
+        self._test_list_resouce_types()
+
+    def test_list_resouce_types_with_bytes_body(self):
+        self._test_list_resouce_types(bytes_body=True)
diff --git a/tempest/tests/lib/services/image/v2/test_schemas_client.py b/tempest/tests/lib/services/image/v2/test_schemas_client.py
new file mode 100644
index 0000000..4c4b86a
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_schemas_client.py
@@ -0,0 +1,96 @@
+# Copyright 2016 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.lib.services.image.v2 import schemas_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSchemasClient(base.BaseServiceTest):
+    FAKE_SHOW_SCHEMA = {
+        "links": [
+            {
+                "href": "{schema}",
+                "rel": "describedby"
+            }
+        ],
+        "name": "members",
+        "properties": {
+            "members": {
+                "items": {
+                    "name": "member",
+                    "properties": {
+                        "created_at": {
+                            "description": ("Date and time of image member"
+                                            " creation"),
+                            "type": "string"
+                        },
+                        "image_id": {
+                            "description": "An identifier for the image",
+                            "pattern": ("^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}"
+                                        "-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}"
+                                        "-([0-9a-fA-F]){12}$"),
+                            "type": "string"
+                        },
+                        "member_id": {
+                            "description": ("An identifier for the image"
+                                            " member (tenantId)"),
+                            "type": "string"
+                        },
+                        "schema": {
+                            "type": "string"
+                        },
+                        "status": {
+                            "description": "The status of this image member",
+                            "enum": [
+                                "pending",
+                                "accepted",
+                                "rejected"
+                            ],
+                            "type": "string"
+                        },
+                        "updated_at": {
+                            "description": ("Date and time of last"
+                                            " modification of image member"),
+                            "type": "string"
+                        }
+                    }
+                },
+                "type": "array"
+            },
+            "schema": {
+                "type": "string"
+            }
+        }
+    }
+
+    def setUp(self):
+        super(TestSchemasClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = schemas_client.SchemasClient(fake_auth,
+                                                   'image', 'regionOne')
+
+    def _test_show_schema(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_schema,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_SCHEMA,
+            bytes_body,
+            schema="member")
+
+    def test_show_schema_with_str_body(self):
+        self._test_show_schema()
+
+    def test_show_schema_with_bytes_body(self):
+        self._test_show_schema(bytes_body=True)
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index c253187..12590a3 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -360,6 +360,58 @@
         self.assertRaises(exceptions.EndpointNotFound,
                           self._test_base_url_helper, None, self.filters)
 
+    def test_base_url_with_known_name(self):
+        """If name and service is known, return the endpoint."""
+        self.filters = {
+            'service': 'compute',
+            'endpoint_type': 'publicURL',
+            'region': 'FakeRegion',
+            'name': 'nova'
+        }
+        expected = self._get_result_url_from_endpoint(
+            self._endpoints[0]['endpoints'][1])
+        self._test_base_url_helper(expected, self.filters)
+
+    def test_base_url_with_known_name_and_unknown_servce(self):
+        """Test with Known Name and Unknown service
+
+        If the name is known but the service is unknown, raise an exception.
+        """
+        self.filters = {
+            'service': 'AintNoBodyKnowThatService',
+            'endpoint_type': 'publicURL',
+            'region': 'FakeRegion',
+            'name': 'AintNoBodyKnowThatName'
+        }
+        self.assertRaises(exceptions.EndpointNotFound,
+                          self._test_base_url_helper, None, self.filters)
+
+    def test_base_url_with_unknown_name_and_known_service(self):
+        """Test with Unknown Name and Known Service
+
+        If the name is unknown, raise an exception.  Note that filtering by
+        name is only successful service exists.
+        """
+
+        self.filters = {
+            'service': 'compute',
+            'endpoint_type': 'publicURL',
+            'region': 'FakeRegion',
+            'name': 'AintNoBodyKnowThatName'
+        }
+        self.assertRaises(exceptions.EndpointNotFound,
+                          self._test_base_url_helper, None, self.filters)
+
+    def test_base_url_without_name(self):
+        self.filters = {
+            'service': 'compute',
+            'endpoint_type': 'publicURL',
+            'region': 'FakeRegion',
+        }
+        expected = self._get_result_url_from_endpoint(
+            self._endpoints[0]['endpoints'][1])
+        self._test_base_url_helper(expected, self.filters)
+
     def test_base_url_with_api_version_filter(self):
         self.filters = {
             'service': 'compute',
@@ -750,3 +802,12 @@
         _, auth_data = self.auth_provider.get_auth()
         self.assertIn('domain', auth_data)
         self.assertNotIn('project', auth_data)
+
+
+class TestGetCredentials(base.TestCase):
+
+    def test_invalid_identity_version(self):
+        with testtools.ExpectedException(exceptions.InvalidIdentityVersion,
+                                         '.* v1 .*'):
+            auth.get_credentials('http://localhost/identity/v3',
+                                 identity_version='v1')
diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/test_rest_client.py
index 106a1e5..057f57b 100644
--- a/tempest/tests/lib/test_rest_client.py
+++ b/tempest/tests/lib/test_rest_client.py
@@ -633,6 +633,7 @@
         expected = {'api_version': 'v1',
                     'endpoint_type': 'publicURL',
                     'region': None,
+                    'name': None,
                     'service': None,
                     'skip_path': True}
         self.rest_client.skip_path()
@@ -643,6 +644,7 @@
         expected = {'api_version': 'v1',
                     'endpoint_type': 'publicURL',
                     'region': None,
+                    'name': None,
                     'service': None}
         self.assertEqual(expected, self.rest_client.filters)
 
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
index dc355b4..01b8a72 100644
--- a/tempest/tests/test_base_test.py
+++ b/tempest/tests/test_base_test.py
@@ -66,7 +66,7 @@
         test.BaseTestCase.get_tenant_network()
 
         mock_man.assert_called_once_with(
-            mock_prov.get_admin_creds.return_value)
+            mock_prov.get_admin_creds.return_value.credentials)
         mock_iaa.assert_called_once_with(
             identity_version=mock_giv.return_value)
         mock_gcp.assert_called_once_with()