Merge "Use scope in v3 identity client"
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 743b575..e4b104f 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -230,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
@@ -239,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
@@ -277,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
@@ -302,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
@@ -327,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/image-clients-as-library-86d17caa26ce3961.yaml b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
new file mode 100644
index 0000000..b50ed38
--- /dev/null
+++ b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
@@ -0,0 +1,12 @@
+---
+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
+      * namespaces_client
+      * resource_types_client
+      * schemas_client
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..dabc45e 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,16 @@
         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()['hypervisors']
+        hypers_available = [hyper['hypervisor_hostname'] for hyper in hypers
+                            if (hyper['state'] == 'up' and
+                                hyper['status'] == 'enabled')]
+        if hypers_available:
+            cls.host = hypers_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_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/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..0bad96a 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.add_member(self.alt_tenant_id, image)
+        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,11 @@
     @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.add_member(self.alt_tenant_id, image)
         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.add_member(self.alt_tenant_id, share_image)
+        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 +46,8 @@
     @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.add_member(self.alt_tenant_id, image_id)
+        self.image_member_client.delete_member(self.alt_tenant_id, image_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..d46a836 100644
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -25,7 +25,8 @@
     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.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.add_member,
                           self.alt_tenant_id, non_exist_image)
 
     @test.attr(type=['negative'])
@@ -33,7 +34,8 @@
     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.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.delete_member,
                           self.alt_tenant_id, non_exist_image)
 
     @test.attr(type=['negative'])
@@ -42,7 +44,8 @@
         # 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,
+        self.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.delete_member,
                           non_exist_tenant, image_id)
 
     @test.attr(type=['negative'])
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..27ccae5 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -37,14 +37,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 +52,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 +75,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 +84,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,15 +109,15 @@
                                                      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'])
 
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/base.py b/tempest/api/volume/base.py
index 6116d72..cd21424 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -179,25 +179,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 +215,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 +224,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/clients.py b/tempest/clients.py
index bc56710..7ec5c7e 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -75,6 +75,12 @@
     VolumesClient as ComputeVolumesClient
 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.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 \
@@ -131,6 +137,8 @@
 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
@@ -212,6 +220,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 +229,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 +246,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,
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 249261b..0a5a41b 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -38,17 +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          |
-    | Project  | --os-project-name         | OS_PROJECT_NAME      |
-    | Tenant   | --os-tenant-name (depr.)  | OS_TENANT_NAME       |
-    | Domain   | --os-domain-name          | OS_DOMAIN_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
 ------------------
@@ -277,12 +277,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
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/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..10f753c 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -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/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/lib/auth.py b/tempest/lib/auth.py
index 213ab8a..80af41d 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
@@ -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):
@@ -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:
@@ -514,7 +536,15 @@
         # 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
@@ -544,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)
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/services/image/__init__.py b/tempest/lib/services/image/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/image/__init__.py
diff --git a/tempest/lib/services/image/v2/__init__.py b/tempest/lib/services/image/v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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/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/scenario/manager.py b/tempest/scenario/manager.py
index 8d0f2f6..1dfa86d 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -50,8 +50,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
@@ -376,14 +383,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 +410,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 +466,16 @@
             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.
+            snapshot_image = _image_client.check_image(image_id)
+            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]:
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 4c2d31b..bfdb0c2 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -52,7 +52,10 @@
 
     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')
@@ -60,7 +63,7 @@
             name=server_name,
             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
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..df16d2fd
--- /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 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 = '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):
+        """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..4ffaf3b 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,6 +29,7 @@
 
 
 class ImagesClient(rest_client.RestClient):
+    api_version = "v1"
 
     def _image_meta_from_headers(self, headers):
         meta = {'properties': {}}
@@ -68,43 +67,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 +84,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,
@@ -136,7 +106,7 @@
         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)
@@ -149,14 +119,14 @@
         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 +141,7 @@
         any changes.
         :param changes_since: The name is changed to changes-since
         """
-        url = 'v1/images'
+        url = 'images'
 
         if detail:
             url += '/detail'
@@ -193,7 +163,7 @@
 
     def check_image(self, image_id):
         """Check image metadata."""
-        url = 'v1/images/%s' % image_id
+        url = 'images/%s' % image_id
         resp, __ = self.head(url)
         self.expected_success(200, resp.status)
         body = self._image_meta_from_headers(resp)
@@ -201,7 +171,7 @@
 
     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)
@@ -218,36 +188,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
index 83f56cc..71e7c6b 100644
--- a/tempest/services/image/v2/json/images_client.py
+++ b/tempest/services/image/v2/json/images_client.py
@@ -25,6 +25,7 @@
 
 
 class ImagesClient(rest_client.RestClient):
+    api_version = "v2"
 
     def update_image(self, image_id, patch):
         """Update an image.
@@ -35,7 +36,7 @@
         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)
+        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)
@@ -47,31 +48,31 @@
                               api-ref-image-v2.html#createImage-v2
         """
         data = json.dumps(kwargs)
-        resp, body = self.post('v2/images', data)
+        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 = 'v2/images/%s/actions/deactivate' % 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 = 'v2/images/%s/actions/reactivate' % 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 = 'v2/images/%s' % 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 = 'v2/images'
+        url = 'images'
 
         if params:
             url += '?%s' % urllib.urlencode(params)
@@ -82,7 +83,7 @@
         return rest_client.ResponseBody(resp, body)
 
     def show_image(self, image_id):
-        url = 'v2/images/%s' % image_id
+        url = 'images/%s' % image_id
         resp, body = self.get(url)
         self.expected_success(200, resp.status)
         body = json.loads(body)
@@ -101,7 +102,7 @@
         return 'image'
 
     def store_image_file(self, image_id, data):
-        url = 'v2/images/%s/file' % image_id
+        url = 'images/%s/file' % image_id
 
         # We are going to do chunked transfert, so split the input data
         # info fixed-sized chunks.
@@ -114,120 +115,19 @@
         return rest_client.ResponseBody(resp, body)
 
     def show_image_file(self, image_id):
-        url = 'v2/images/%s/file' % 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 = 'v2/images/%s/tags/%s' % (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 = '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
+        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/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/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/image/__init__.py b/tempest/tests/lib/services/image/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/services/image/__init__.py
diff --git a/tempest/tests/lib/services/image/v2/__init__.py b/tempest/tests/lib/services/image/v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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_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..c08bf6a 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',
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/services/image/__init__.py b/tempest/tests/services/image/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/services/image/__init__.py