Merge "Fix cinder volume nameing for admin"
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 10364db..98b006d 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -51,6 +51,7 @@
    account_generator
    cleanup
    javelin
+   workspace
 
 ==================
 Indices and tables
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/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-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/image-clients-as-library-86d17caa26ce3961.yaml b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
new file mode 100644
index 0000000..630f8ed
--- /dev/null
+++ b/releasenotes/notes/image-clients-as-library-86d17caa26ce3961.yaml
@@ -0,0 +1,10 @@
+---
+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..0bf493c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -41,6 +41,7 @@
     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
 oslo.config.opts =
     tempest.config = tempest.config:list_opts
 
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/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/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/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/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/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 63fb59d..4ffaf3b 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -188,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 = '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 = '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):
-        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/v2/json/images_client.py b/tempest/services/image/v2/json/images_client.py
index f175dce..71e7c6b 100644
--- a/tempest/services/image/v2/json/images_client.py
+++ b/tempest/services/image/v2/json/images_client.py
@@ -131,104 +131,3 @@
         resp, _ = self.delete(url)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
-
-    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)
-
-    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)
-
-    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)
-
-    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/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/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/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