Merge "Don't create network resources for identity tests"
diff --git a/releasenotes/notes/add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml b/releasenotes/notes/add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml
index b8a8491..6673616 100644
--- a/releasenotes/notes/add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml
+++ b/releasenotes/notes/add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml
@@ -6,3 +6,5 @@
     any maintenance changes.
 
       * identity_client(v2)
+      * groups_client(v3)
+      * users_client(v3)
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
index 052c17d..ee8ed14 100644
--- a/tempest/api/compute/admin/test_auto_allocate_network.py
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -16,6 +16,7 @@
 
 from tempest.api.compute import base
 from tempest.common import compute
+from tempest.common import credentials_factory as credentials
 from tempest.common import waiters
 from tempest import config
 from tempest import exceptions
@@ -45,6 +46,11 @@
     @classmethod
     def skip_checks(cls):
         super(AutoAllocateNetworkTest, cls).skip_checks()
+        identity_version = cls.get_identity_version()
+        if not credentials.is_admin_available(
+                identity_version=identity_version):
+            msg = "Missing Identity Admin API credentials in configuration."
+            raise cls.skipException(msg)
         if not CONF.service_available.neutron:
             raise cls.skipException('Neutron is required')
         if not test.is_extension_enabled('auto-allocated-topology', 'network'):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index a5c303c..80d2c78 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -64,18 +64,19 @@
             self.volumes_client.wait_for_resource_deletion(self.volume['id'])
             self.volume = None
 
-    def _create_and_attach(self, shelve_server=False):
+    def _create_server(self):
         # Start a server and wait for it to become ready
-        self.admin_pass = self.image_ssh_password
-        self.server = self.create_test_server(
+        server = self.create_test_server(
             validatable=True,
             wait_until='ACTIVE',
-            adminPass=self.admin_pass)
+            adminPass=self.image_ssh_password)
 
         # Record addresses so that we can ssh later
-        self.server['addresses'] = self.servers_client.list_addresses(
-            self.server['id'])['addresses']
+        server['addresses'] = self.servers_client.list_addresses(
+            server['id'])['addresses']
+        return server
 
+    def _create_and_attach_volume(self, server):
         # Create a volume and wait for it to become ready
         self.volume = self.volumes_client.create_volume(
             size=CONF.volume.volume_size, display_name='test')['volume']
@@ -83,77 +84,60 @@
         waiters.wait_for_volume_status(self.volumes_client,
                                        self.volume['id'], 'available')
 
-        if shelve_server:
-            if CONF.validation.run_validation:
-                # NOTE(andreaf) If we are going to shelve a server, we should
-                # check first whether the server is ssh-able. Otherwise we
-                # won't be able to distinguish failures introduced by shelve
-                # from pre-existing ones. Also it's good to wait for cloud-init
-                # to be done and sshd server to be running before shelving to
-                # avoid breaking the VM
-                linux_client = remote_client.RemoteClient(
-                    self.get_server_ip(self.server),
-                    self.image_ssh_user,
-                    self.admin_pass,
-                    self.validation_resources['keypair']['private_key'])
-                linux_client.validate_authentication()
-
-            # If validation went ok, or it was skipped, shelve the server
-            compute.shelve_server(self.servers_client, self.server['id'])
-
         # Attach the volume to the server
         self.attachment = self.servers_client.attach_volume(
-            self.server['id'],
+            server['id'],
             volumeId=self.volume['id'],
             device='/dev/%s' % self.device)['volumeAttachment']
         waiters.wait_for_volume_status(self.volumes_client,
                                        self.volume['id'], 'in-use')
 
-        self.addCleanup(self._detach, self.server['id'], self.volume['id'])
+        self.addCleanup(self._detach, server['id'], self.volume['id'])
 
     @test.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
     def test_attach_detach_volume(self):
         # Stop and Start a server with an attached volume, ensuring that
         # the volume remains attached.
-        self._create_and_attach()
+        server = self._create_server()
+        self._create_and_attach_volume(server)
 
-        self.servers_client.stop_server(self.server['id'])
-        waiters.wait_for_server_status(self.servers_client, self.server['id'],
+        self.servers_client.stop_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'SHUTOFF')
 
-        self.servers_client.start_server(self.server['id'])
-        waiters.wait_for_server_status(self.servers_client, self.server['id'],
+        self.servers_client.start_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'ACTIVE')
 
         if CONF.validation.run_validation:
             linux_client = remote_client.RemoteClient(
-                self.get_server_ip(self.server),
+                self.get_server_ip(server),
                 self.image_ssh_user,
-                self.admin_pass,
+                self.image_ssh_password,
                 self.validation_resources['keypair']['private_key'],
-                server=self.server,
+                server=server,
                 servers_client=self.servers_client)
 
             partitions = linux_client.get_partitions()
             self.assertIn(self.device, partitions)
 
-        self._detach(self.server['id'], self.volume['id'])
+        self._detach(server['id'], self.volume['id'])
         self.attachment = None
-        self.servers_client.stop_server(self.server['id'])
-        waiters.wait_for_server_status(self.servers_client, self.server['id'],
+        self.servers_client.stop_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'SHUTOFF')
 
-        self.servers_client.start_server(self.server['id'])
-        waiters.wait_for_server_status(self.servers_client, self.server['id'],
+        self.servers_client.start_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'ACTIVE')
 
         if CONF.validation.run_validation:
             linux_client = remote_client.RemoteClient(
-                self.get_server_ip(self.server),
+                self.get_server_ip(server),
                 self.image_ssh_user,
-                self.admin_pass,
+                self.image_ssh_password,
                 self.validation_resources['keypair']['private_key'],
-                server=self.server,
+                server=server,
                 servers_client=self.servers_client)
 
             partitions = linux_client.get_partitions()
@@ -162,18 +146,20 @@
     @test.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513')
     def test_list_get_volume_attachments(self):
         # Create Server, Volume and attach that Volume to Server
-        self._create_and_attach()
+        server = self._create_server()
+        self._create_and_attach_volume(server)
+
         # List Volume attachment of the server
         body = self.servers_client.list_volume_attachments(
-            self.server['id'])['volumeAttachments']
+            server['id'])['volumeAttachments']
         self.assertEqual(1, len(body))
         self.assertIn(self.attachment, body)
 
         # Get Volume attachment of the server
         body = self.servers_client.show_volume_attachment(
-            self.server['id'],
+            server['id'],
             self.attachment['id'])['volumeAttachment']
-        self.assertEqual(self.server['id'], body['serverId'])
+        self.assertEqual(server['id'], body['serverId'])
         self.assertEqual(self.volume['id'], body['volumeId'])
         self.assertEqual(self.attachment['id'], body['id'])
 
@@ -188,39 +174,71 @@
     min_microversion = '2.20'
     max_microversion = 'latest'
 
-    def _unshelve_server_and_check_volumes(self, number_of_partition):
-        # Unshelve the instance and check that there are expected volumes
-        self.servers_client.unshelve_server(self.server['id'])
-        waiters.wait_for_server_status(self.servers_client,
-                                       self.server['id'],
-                                       'ACTIVE')
+    def _count_volumes(self, server):
+        # Count number of volumes on an instance
+        volumes = 0
         if CONF.validation.run_validation:
             linux_client = remote_client.RemoteClient(
-                self.get_server_ip(self.server['id']),
+                self.get_server_ip(server),
                 self.image_ssh_user,
-                self.admin_pass,
+                self.image_ssh_password,
                 self.validation_resources['keypair']['private_key'],
-                server=self.server,
+                server=server,
                 servers_client=self.servers_client)
 
-            command = 'grep [vs]d /proc/partitions | wc -l'
-            nb_partitions = linux_client.exec_command(command).strip()
-            self.assertEqual(number_of_partition, nb_partitions)
+            command = 'grep -c -E [vs]d.$ /proc/partitions'
+            volumes = int(linux_client.exec_command(command).strip())
+        return volumes
+
+    def _shelve_server(self, server):
+        # NOTE(andreaf) If we are going to shelve a server, we should
+        # check first whether the server is ssh-able. Otherwise we
+        # won't be able to distinguish failures introduced by shelve
+        # from pre-existing ones. Also it's good to wait for cloud-init
+        # to be done and sshd server to be running before shelving to
+        # avoid breaking the VM
+        if CONF.validation.run_validation:
+            linux_client = remote_client.RemoteClient(
+                self.get_server_ip(server),
+                self.image_ssh_user,
+                self.image_ssh_password,
+                self.validation_resources['keypair']['private_key'],
+                server=server,
+                servers_client=self.servers_client)
+            linux_client.validate_authentication()
+
+        # If validation went ok, or it was skipped, shelve the server
+        compute.shelve_server(self.servers_client, server['id'])
+
+    def _unshelve_server_and_check_volumes(self, server, number_of_volumes):
+        # Unshelve the instance and check that there are expected volumes
+        self.servers_client.unshelve_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'],
+                                       'ACTIVE')
+        if CONF.validation.run_validation:
+            counted_volumes = self._count_volumes(server)
+            self.assertEqual(number_of_volumes, counted_volumes)
 
     @test.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee')
     @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
                           'Shelve is not available.')
     def test_attach_volume_shelved_or_offload_server(self):
-        self._create_and_attach(shelve_server=True)
+        # Create server, count number of volumes on it, shelve
+        # server and attach pre-created volume to shelved server
+        server = self._create_server()
+        num_vol = self._count_volumes(server)
+        self._shelve_server(server)
+        self._create_and_attach_volume(server)
 
-        # Unshelve the instance and check that there are two volumes
-        self._unshelve_server_and_check_volumes('2')
+        # Unshelve the instance and check that attached volume exists
+        self._unshelve_server_and_check_volumes(server, num_vol + 1)
 
         # Get Volume attachment of the server
         volume_attachment = self.servers_client.show_volume_attachment(
-            self.server['id'],
+            server['id'],
             self.attachment['id'])['volumeAttachment']
-        self.assertEqual(self.server['id'], volume_attachment['serverId'])
+        self.assertEqual(server['id'], volume_attachment['serverId'])
         self.assertEqual(self.attachment['id'], volume_attachment['id'])
         # Check the mountpoint is not None after unshelve server even in
         # case of shelved_offloaded.
@@ -230,11 +248,17 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
                           'Shelve is not available.')
     def test_detach_volume_shelved_or_offload_server(self):
-        self._create_and_attach(shelve_server=True)
+        # Create server, count number of volumes on it, shelve
+        # server and attach pre-created volume to shelved server
+        server = self._create_server()
+        num_vol = self._count_volumes(server)
+        self._shelve_server(server)
+        self._create_and_attach_volume(server)
 
         # Detach the volume
-        self._detach(self.server['id'], self.volume['id'])
+        self._detach(server['id'], self.volume['id'])
         self.attachment = None
 
-        # Unshelve the instance and check that there is only one volume
-        self._unshelve_server_and_check_volumes('1')
+        # Unshelve the instance and check that we have the expected number of
+        # volume(s)
+        self._unshelve_server_and_check_volumes(server, num_vol)
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index 3616e77..59ffc19 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -69,7 +69,7 @@
         admin_role_id = admin_role['id']
 
         # grant the admin role to the user on his project
-        self.roles_client.assign_user_role_on_project(proj_id, user_id,
+        self.roles_client.create_user_role_on_project(proj_id, user_id,
                                                       admin_role_id)
 
         # create a new client with user's credentials (NOTE: unscoped token!)
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 4ecf0a5..f5bf923 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -94,7 +94,7 @@
 
     @test.idempotent_id('c6b80012-fe4a-498b-9ce8-eb391c05169f')
     def test_grant_list_revoke_role_to_user_on_project(self):
-        self.roles_client.assign_user_role_on_project(self.project['id'],
+        self.roles_client.create_user_role_on_project(self.project['id'],
                                                       self.user_body['id'],
                                                       self.role['id'])
 
@@ -115,7 +115,7 @@
 
     @test.idempotent_id('6c9a2940-3625-43a3-ac02-5dcec62ef3bd')
     def test_grant_list_revoke_role_to_user_on_domain(self):
-        self.roles_client.assign_user_role_on_domain(
+        self.roles_client.create_user_role_on_domain(
             self.domain['id'], self.user_body['id'], self.role['id'])
 
         roles = self.roles_client.list_user_roles_on_domain(
@@ -136,7 +136,7 @@
     @test.idempotent_id('cbf11737-1904-4690-9613-97bcbb3df1c4')
     def test_grant_list_revoke_role_to_group_on_project(self):
         # Grant role to group on project
-        self.roles_client.assign_group_role_on_project(
+        self.roles_client.create_group_role_on_project(
             self.project['id'], self.group_body['id'], self.role['id'])
         # List group roles on project
         roles = self.roles_client.list_group_roles_on_project(
@@ -170,7 +170,7 @@
 
     @test.idempotent_id('4bf8a70b-e785-413a-ad53-9f91ce02faa7')
     def test_grant_list_revoke_role_to_group_on_domain(self):
-        self.roles_client.assign_group_role_on_domain(
+        self.roles_client.create_group_role_on_domain(
             self.domain['id'], self.group_body['id'], self.role['id'])
 
         roles = self.roles_client.list_group_roles_on_domain(
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index fcf4772..8706cf7 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -83,11 +83,11 @@
         self.addCleanup(self.roles_client.delete_role, role['id'])
 
         # Grant the user the role on both projects.
-        self.roles_client.assign_user_role_on_project(project1['id'],
+        self.roles_client.create_user_role_on_project(project1['id'],
                                                       user['id'],
                                                       role['id'])
 
-        self.roles_client.assign_user_role_on_project(project2['id'],
+        self.roles_client.create_user_role_on_project(project2['id'],
                                                       user['id'],
                                                       role['id'])
 
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 58e2ab8..4e69de8 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -77,11 +77,11 @@
         self.not_delegated_role_id = role['id']
 
         # Assign roles to trustor
-        self.roles_client.assign_user_role_on_project(
+        self.roles_client.create_user_role_on_project(
             self.trustor_project_id,
             self.trustor_user_id,
             self.delegated_role_id)
-        self.roles_client.assign_user_role_on_project(
+        self.roles_client.create_user_role_on_project(
             self.trustor_project_id,
             self.trustor_user_id,
             self.not_delegated_role_id)
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 1bcec62..fd2683e 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -130,7 +130,7 @@
             self.addCleanup(
                 self.projects_client.delete_project, project_body['id'])
             # Assigning roles to user on project
-            self.roles_client.assign_user_role_on_project(project['id'],
+            self.roles_client.create_user_role_on_project(project['id'],
                                                           user['id'],
                                                           role['id'])
             assigned_project_ids.append(project['id'])
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 723b870..9e9f08b 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -78,7 +78,16 @@
         # container request, the response does not contain 'accept-ranges'
         # header. This is a special case, therefore the existence of response
         # headers is checked without custom matcher.
-        self.assertIn('content-length', resp)
+        #
+        # As the expected response is 204 No Content, Content-Length presence
+        # is not checked here intensionally. According to RFC 7230 a server
+        # MUST NOT send the header in such responses. Thus, clients should not
+        # depend on this header. However, the standard does not require them
+        # to validate the server's behavior. We leverage that to not refuse
+        # any implementation violating it like Swift [1] or some versions of
+        # Ceph RadosGW [2].
+        # [1] https://bugs.launchpad.net/swift/+bug/1537811
+        # [2] http://tracker.ceph.com/issues/13582
         self.assertIn('x-timestamp', resp)
         self.assertIn('x-account-bytes-used', resp)
         self.assertIn('x-account-container-count', resp)
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 00da5be..646bc68 100755
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -128,7 +128,7 @@
         body = self.create_volume_type(name=name)
         # Create encryption type
         encryption_type = \
-            self.admin_volume_types_client.create_encryption_type(
+            self.admin_encryption_types_client.create_encryption_type(
                 body['id'], provider=provider,
                 control_location=control_location)['encryption']
         self.assertIn('volume_type_id', encryption_type)
@@ -141,7 +141,7 @@
 
         # Get encryption type
         fetched_encryption_type = (
-            self.admin_volume_types_client.show_encryption_type(
+            self.admin_encryption_types_client.show_encryption_type(
                 encryption_type['volume_type_id']))
         self.assertEqual(provider,
                          fetched_encryption_type['provider'],
@@ -153,14 +153,11 @@
                          'different from the created encryption_type')
 
         # 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.admin_volume_types_client.wait_for_resource_deletion(resource)
+        type_id = encryption_type['volume_type_id']
+        self.admin_encryption_types_client.delete_encryption_type(type_id)
+        self.admin_encryption_types_client.wait_for_resource_deletion(type_id)
         deleted_encryption_type = (
-            self.admin_volume_types_client.show_encryption_type(
-                encryption_type['volume_type_id']))
+            self.admin_encryption_types_client.show_encryption_type(type_id))
         self.assertEmpty(deleted_encryption_type)
 
     @test.idempotent_id('cf9f07c6-db9e-4462-a243-5933ad65e9c8')
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 4f37c82..183452c 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -204,6 +204,8 @@
             cls.admin_hosts_client = cls.os_adm.volume_hosts_client
             cls.admin_snapshots_client = cls.os_adm.snapshots_client
             cls.admin_backups_client = cls.os_adm.backups_client
+            cls.admin_encryption_types_client = \
+                cls.os_adm.encryption_types_client
             cls.admin_quotas_client = cls.os_adm.volume_quotas_client
         elif cls._api_version == 2:
             cls.admin_volume_qos_client = cls.os_adm.volume_qos_v2_client
@@ -214,6 +216,8 @@
             cls.admin_hosts_client = cls.os_adm.volume_hosts_v2_client
             cls.admin_snapshots_client = cls.os_adm.snapshots_v2_client
             cls.admin_backups_client = cls.os_adm.backups_v2_client
+            cls.admin_encryption_types_client = \
+                cls.os_adm.encryption_types_v2_client
             cls.admin_quotas_client = cls.os_adm.volume_quotas_v2_client
 
     @classmethod
@@ -265,12 +269,9 @@
                 cls.admin_volume_types_client.delete_volume_type, vol_type)
 
         for vol_type in cls.volume_types:
-            # Resource dictionary uses for is_resource_deleted method,
-            # to distinguish between volume-type to encryption-type.
-            resource = {'id': vol_type, 'type': 'volume-type'}
             test_utils.call_and_ignore_notfound_exc(
                 cls.admin_volume_types_client.wait_for_resource_deletion,
-                resource)
+                vol_type)
 
     def wait_for_qos_operations(self, qos_id, operation, args=None):
         """Waits for a qos operations to be completed.
diff --git a/tempest/clients.py b/tempest/clients.py
index 5406d73..f8c276a 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -293,6 +293,10 @@
                                                       **params)
         self.backups_v2_client = volume.v2.BackupsClient(self.auth_provider,
                                                          **params)
+        self.encryption_types_client = volume.v1.EncryptionTypesClient(
+            self.auth_provider, **params)
+        self.encryption_types_v2_client = volume.v2.EncryptionTypesClient(
+            self.auth_provider, **params)
         self.snapshots_client = volume.v1.SnapshotsClient(self.auth_provider,
                                                           **params)
         self.snapshots_v2_client = volume.v2.SnapshotsClient(
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index d8b808b..ad968f1 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -74,7 +74,9 @@
             msg = 'No "%s" role found' % role_name
             raise lib_exc.NotFound(msg)
         try:
-            self._assign_user_role(project, user, role)
+            self.roles_client.create_user_role_on_project(project['id'],
+                                                          user['id'],
+                                                          role['id'])
         except lib_exc.Conflict:
             LOG.debug("Role %s already assigned on project %s for user %s" % (
                 role['id'], project['id'], user['id']))
@@ -124,11 +126,6 @@
             tenant_name=project['name'], tenant_id=project['id'],
             password=password)
 
-    def _assign_user_role(self, project, user, role):
-        self.roles_client.create_user_role_on_project(project['id'],
-                                                      user['id'],
-                                                      role['id'])
-
 
 class V3CredsClient(CredsClient):
     project_id_param = 'project_id'
@@ -176,11 +173,6 @@
             domain_id=self.creds_domain['id'],
             domain_name=self.creds_domain['name'])
 
-    def _assign_user_role(self, project, user, role):
-        self.roles_client.assign_user_role_on_project(project['id'],
-                                                      user['id'],
-                                                      role['id'])
-
     def assign_user_role_on_domain(self, user, role_name, domain=None):
         """Assign the specified role on a domain
 
@@ -198,7 +190,7 @@
             msg = 'No "%s" role found' % role_name
             raise lib_exc.NotFound(msg)
         try:
-            self.roles_client.assign_user_role_on_domain(
+            self.roles_client.create_user_role_on_domain(
                 domain['id'], user['id'], role['id'])
         except lib_exc.Conflict:
             LOG.debug("Role %s already assigned on domain %s for user %s",
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index 8ba33ed..998612b 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -37,13 +37,32 @@
         self.target = target
         self.method = method
 
+    def _content_length_required(self, resp):
+        # Verify whether given HTTP response must contain content-length.
+        # Take into account the exceptions defined in RFC 7230.
+        if resp.status in range(100, 200) or resp.status == 204:
+            return False
+
+        return True
+
     def match(self, actual):
         """Check headers
 
-        param: actual HTTP response headers
+        param: actual HTTP response object containing headers and status
         """
-        # Check common headers for all HTTP methods
-        if 'content-length' not in actual:
+        # Check common headers for all HTTP methods.
+        #
+        # Please note that for 1xx and 204 responses Content-Length presence
+        # is not checked intensionally. According to RFC 7230 a server MUST
+        # NOT send the header in such responses. Thus, clients should not
+        # depend on this header. However, the standard does not require them
+        # to validate the server's behavior. We leverage that to not refuse
+        # any implementation violating it like Swift [1] or some versions of
+        # Ceph RadosGW [2].
+        # [1] https://bugs.launchpad.net/swift/+bug/1537811
+        # [2] http://tracker.ceph.com/issues/13582
+        if ('content-length' not in actual and
+                self._content_length_required(actual)):
             return NonExistentHeader('content-length')
         if 'content-type' not in actual:
             return NonExistentHeader('content-type')
diff --git a/tempest/services/identity/v3/json/groups_client.py b/tempest/lib/services/identity/v3/groups_client.py
similarity index 100%
rename from tempest/services/identity/v3/json/groups_client.py
rename to tempest/lib/services/identity/v3/groups_client.py
diff --git a/tempest/services/identity/v3/json/users_clients.py b/tempest/lib/services/identity/v3/users_client.py
similarity index 100%
rename from tempest/services/identity/v3/json/users_clients.py
rename to tempest/lib/services/identity/v3/users_client.py
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a3f832a..233d747 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -1346,8 +1346,12 @@
         super(EncryptionScenarioTest, cls).setup_clients()
         if CONF.volume_feature_enabled.api_v1:
             cls.admin_volume_types_client = cls.os_adm.volume_types_client
+            cls.admin_encryption_types_client =\
+                cls.os_adm.encryption_types_client
         else:
             cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
+            cls.admin_encryption_types_client =\
+                cls.os_adm.encryption_types_v2_client
 
     def create_volume_type(self, client=None, name=None):
         if not client:
@@ -1366,7 +1370,7 @@
                                key_size=None, cipher=None,
                                control_location=None):
         if not client:
-            client = self.admin_volume_types_client
+            client = self.admin_encryption_types_client
         if not type_id:
             volume_type = self.create_volume_type()
             type_id = volume_type['id']
diff --git a/tempest/services/identity/v3/__init__.py b/tempest/services/identity/v3/__init__.py
index c77c78d..b4a1bb8 100644
--- a/tempest/services/identity/v3/__init__.py
+++ b/tempest/services/identity/v3/__init__.py
@@ -13,21 +13,22 @@
 # the License.
 
 from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
+from tempest.lib.services.identity.v3.groups_client import GroupsClient
 from tempest.lib.services.identity.v3.policies_client import PoliciesClient
 from tempest.lib.services.identity.v3.projects_client import ProjectsClient
 from tempest.lib.services.identity.v3.regions_client import RegionsClient
 from tempest.lib.services.identity.v3.services_client import ServicesClient
 from tempest.lib.services.identity.v3.token_client import V3TokenClient
+from tempest.lib.services.identity.v3.users_client import UsersClient
 from tempest.services.identity.v3.json.credentials_client import \
     CredentialsClient
 from tempest.services.identity.v3.json.domains_client import DomainsClient
-from tempest.services.identity.v3.json.groups_client import GroupsClient
 from tempest.services.identity.v3.json.identity_client import IdentityClient
 from tempest.services.identity.v3.json.roles_client import RolesClient
 from tempest.services.identity.v3.json.trusts_client import TrustsClient
-from tempest.services.identity.v3.json.users_clients import UsersClient
 
-__all__ = ['EndPointsClient', 'PoliciesClient', 'ProjectsClient',
-           'RegionsClient', 'ServicesClient', 'V3TokenClient',
-           'CredentialsClient', 'DomainsClient', 'GroupsClient',
-           'IdentityClient', 'RolesClient', 'TrustsClient', 'UsersClient', ]
+
+__all__ = ['EndPointsClient', 'GroupsClient', 'PoliciesClient',
+           'ProjectsClient', 'RegionsClient', 'ServicesClient',
+           'V3TokenClient', 'UsersClient', 'CredentialsClient',
+           'DomainsClient', 'IdentityClient', 'RolesClient', 'TrustsClient', ]
diff --git a/tempest/services/identity/v3/json/roles_client.py b/tempest/services/identity/v3/json/roles_client.py
index aab203f..3f165fa 100644
--- a/tempest/services/identity/v3/json/roles_client.py
+++ b/tempest/services/identity/v3/json/roles_client.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
@@ -39,9 +40,13 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
-    def list_roles(self):
+    def list_roles(self, **params):
         """Get the list of Roles."""
-        resp, body = self.get("roles")
+
+        url = 'roles'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
@@ -64,14 +69,14 @@
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
-    def assign_user_role_on_project(self, project_id, user_id, role_id):
+    def create_user_role_on_project(self, project_id, user_id, role_id):
         """Add roles to a user on a project."""
         resp, body = self.put('projects/%s/users/%s/roles/%s' %
                               (project_id, user_id, role_id), None)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
-    def assign_user_role_on_domain(self, domain_id, user_id, role_id):
+    def create_user_role_on_domain(self, domain_id, user_id, role_id):
         """Add roles to a user on a domain."""
         resp, body = self.put('domains/%s/users/%s/roles/%s' %
                               (domain_id, user_id, role_id), None)
@@ -124,22 +129,22 @@
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
 
-    def assign_group_role_on_project(self, project_id, group_id, role_id):
-        """Add roles to a user on a project."""
+    def create_group_role_on_project(self, project_id, group_id, role_id):
+        """Add roles to a group on a project."""
         resp, body = self.put('projects/%s/groups/%s/roles/%s' %
                               (project_id, group_id, role_id), None)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
-    def assign_group_role_on_domain(self, domain_id, group_id, role_id):
-        """Add roles to a user on a domain."""
+    def create_group_role_on_domain(self, domain_id, group_id, role_id):
+        """Add roles to a group on a domain."""
         resp, body = self.put('domains/%s/groups/%s/roles/%s' %
                               (domain_id, group_id, role_id), None)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
     def list_group_roles_on_project(self, project_id, group_id):
-        """list roles of a user on a project."""
+        """list roles of a group on a project."""
         resp, body = self.get('projects/%s/groups/%s/roles' %
                               (project_id, group_id))
         self.expected_success(200, resp.status)
@@ -147,7 +152,7 @@
         return rest_client.ResponseBody(resp, body)
 
     def list_group_roles_on_domain(self, domain_id, group_id):
-        """list roles of a user on a domain."""
+        """list roles of a group on a domain."""
         resp, body = self.get('domains/%s/groups/%s/roles' %
                               (domain_id, group_id))
         self.expected_success(200, resp.status)
@@ -155,14 +160,14 @@
         return rest_client.ResponseBody(resp, body)
 
     def delete_role_from_group_on_project(self, project_id, group_id, role_id):
-        """Delete role of a user on a project."""
+        """Delete role of a group on a project."""
         resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
                                  (project_id, group_id, role_id))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
     def delete_role_from_group_on_domain(self, domain_id, group_id, role_id):
-        """Delete role of a user on a domain."""
+        """Delete role of a group on a domain."""
         resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
                                  (domain_id, group_id, role_id))
         self.expected_success(204, resp.status)
@@ -170,7 +175,7 @@
 
     def check_role_from_group_on_project_existence(self, project_id,
                                                    group_id, role_id):
-        """Check role of a user on a project."""
+        """Check role of a group on a project."""
         resp, body = self.head('projects/%s/groups/%s/roles/%s' %
                                (project_id, group_id, role_id))
         self.expected_success(204, resp.status)
@@ -178,7 +183,7 @@
 
     def check_role_from_group_on_domain_existence(self, domain_id,
                                                   group_id, role_id):
-        """Check role of a user on a domain."""
+        """Check role of a group on a domain."""
         resp, body = self.head('domains/%s/groups/%s/roles/%s' %
                                (domain_id, group_id, role_id))
         self.expected_success(204, resp.status)
diff --git a/tempest/services/volume/base/admin/base_types_client.py b/tempest/services/volume/base/admin/base_types_client.py
index 83870ae..fe70c1a 100755
--- a/tempest/services/volume/base/admin/base_types_client.py
+++ b/tempest/services/volume/base/admin/base_types_client.py
@@ -23,21 +23,9 @@
 class BaseTypesClient(rest_client.RestClient):
     """Client class to send CRUD Volume Types API requests"""
 
-    def is_resource_deleted(self, resource):
-        # to use this method self.resource must be defined to respective value
-        # Resource is a dictionary containing resource id and type
-        # Resource : {"id" : resource_id
-        #             "type": resource_type}
+    def is_resource_deleted(self, id):
         try:
-            if resource['type'] == "volume-type":
-                self.show_volume_type(resource['id'])
-            elif resource['type'] == "encryption-type":
-                body = self.show_encryption_type(resource['id'])
-                if not body:
-                    return True
-            else:
-                msg = (" resource value is either not defined or incorrect.")
-                raise lib_exc.UnprocessableEntity(msg)
+            self.show_volume_type(id)
         except lib_exc.NotFound:
             return True
         return False
@@ -45,7 +33,7 @@
     @property
     def resource_type(self):
         """Returns the primary type of resource this client works with."""
-        return 'volume-type/encryption-type'
+        return 'volume-type'
 
     def list_volume_types(self, **params):
         """List all the volume_types created.
@@ -170,35 +158,3 @@
         body = json.loads(body)
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
-
-    def show_encryption_type(self, volume_type_id):
-        """Get the volume encryption type for the specified volume type.
-
-        volume_type_id: Id of volume_type.
-        """
-        url = "/types/%s/encryption" % volume_type_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_encryption_type(self, volume_type_id, **kwargs):
-        """Create encryption type.
-
-        TODO: Current api-site doesn't contain this API description.
-        After fixing the api-site, we need to fix here also for putting
-        the link to api-site.
-        """
-        url = "/types/%s/encryption" % volume_type_id
-        post_body = json.dumps({'encryption': kwargs})
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_encryption_type(self, volume_type_id):
-        """Delete the encryption type for the specified volume-type."""
-        resp, body = self.delete(
-            "/types/%s/encryption/provider" % volume_type_id)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v1/__init__.py b/tempest/services/volume/v1/__init__.py
index 945d2f2..72868bc 100644
--- a/tempest/services/volume/v1/__init__.py
+++ b/tempest/services/volume/v1/__init__.py
@@ -20,10 +20,13 @@
 from tempest.lib.services.volume.v1.services_client import ServicesClient
 from tempest.services.volume.v1.json.admin.types_client import TypesClient
 from tempest.services.volume.v1.json.backups_client import BackupsClient
+from tempest.services.volume.v1.json.encryption_types_client import \
+    EncryptionTypesClient
 from tempest.services.volume.v1.json.qos_client import QosSpecsClient
 from tempest.services.volume.v1.json.snapshots_client import SnapshotsClient
 from tempest.services.volume.v1.json.volumes_client import VolumesClient
 
 __all__ = ['HostsClient', 'QuotasClient', 'ServicesClient', 'TypesClient',
            'AvailabilityZoneClient', 'BackupsClient', 'ExtensionsClient',
-           'QosSpecsClient', 'SnapshotsClient', 'VolumesClient']
+           'QosSpecsClient', 'SnapshotsClient', 'VolumesClient',
+           'EncryptionTypesClient']
diff --git a/tempest/services/volume/v1/json/encryption_types_client.py b/tempest/services/volume/v1/json/encryption_types_client.py
new file mode 100755
index 0000000..067b4e8
--- /dev/null
+++ b/tempest/services/volume/v1/json/encryption_types_client.py
@@ -0,0 +1,68 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class EncryptionTypesClient(rest_client.RestClient):
+
+    def is_resource_deleted(self, id):
+        try:
+            body = self.show_encryption_type(id)
+            if not body:
+                return True
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'encryption-type'
+
+    def show_encryption_type(self, volume_type_id):
+        """Get the volume encryption type for the specified volume type.
+
+        volume_type_id: Id of volume_type.
+        """
+        url = "/types/%s/encryption" % volume_type_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_encryption_type(self, volume_type_id, **kwargs):
+        """Create encryption type.
+
+        TODO: Current api-site doesn't contain this API description.
+        After fixing the api-site, we need to fix here also for putting
+        the link to api-site.
+        """
+        url = "/types/%s/encryption" % volume_type_id
+        post_body = json.dumps({'encryption': kwargs})
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_encryption_type(self, volume_type_id):
+        """Delete the encryption type for the specified volume-type."""
+        resp, body = self.delete(
+            "/types/%s/encryption/provider" % volume_type_id)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v2/__init__.py b/tempest/services/volume/v2/__init__.py
index 80f7a94..4afcc29 100644
--- a/tempest/services/volume/v2/__init__.py
+++ b/tempest/services/volume/v2/__init__.py
@@ -20,10 +20,13 @@
 from tempest.lib.services.volume.v2.services_client import ServicesClient
 from tempest.services.volume.v2.json.admin.types_client import TypesClient
 from tempest.services.volume.v2.json.backups_client import BackupsClient
+from tempest.services.volume.v2.json.encryption_types_client import \
+    EncryptionTypesClient
 from tempest.services.volume.v2.json.qos_client import QosSpecsClient
 from tempest.services.volume.v2.json.snapshots_client import SnapshotsClient
 from tempest.services.volume.v2.json.volumes_client import VolumesClient
 
 __all__ = ['HostsClient', 'QuotasClient', 'ServicesClient', 'TypesClient',
            'AvailabilityZoneClient', 'BackupsClient', 'ExtensionsClient',
-           'QosSpecsClient', 'SnapshotsClient', 'VolumesClient']
+           'QosSpecsClient', 'SnapshotsClient', 'VolumesClient',
+           'EncryptionTypesClient']
diff --git a/tempest/services/volume/v2/json/encryption_types_client.py b/tempest/services/volume/v2/json/encryption_types_client.py
new file mode 100755
index 0000000..8b01f11
--- /dev/null
+++ b/tempest/services/volume/v2/json/encryption_types_client.py
@@ -0,0 +1,69 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class EncryptionTypesClient(rest_client.RestClient):
+    api_version = "v2"
+
+    def is_resource_deleted(self, id):
+        try:
+            body = self.show_encryption_type(id)
+            if not body:
+                return True
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'encryption-type'
+
+    def show_encryption_type(self, volume_type_id):
+        """Get the volume encryption type for the specified volume type.
+
+        volume_type_id: Id of volume_type.
+        """
+        url = "/types/%s/encryption" % volume_type_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_encryption_type(self, volume_type_id, **kwargs):
+        """Create encryption type.
+
+        TODO: Current api-site doesn't contain this API description.
+        After fixing the api-site, we need to fix here also for putting
+        the link to api-site.
+        """
+        url = "/types/%s/encryption" % volume_type_id
+        post_body = json.dumps({'encryption': kwargs})
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_encryption_type(self, volume_type_id):
+        """Delete the encryption type for the specified volume-type."""
+        resp, body = self.delete(
+            "/types/%s/encryption/provider" % volume_type_id)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index 8c74d9c..f9f4100 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -31,11 +31,12 @@
 from tempest.lib.services.identity.v3 import projects_client as \
     v3_projects_client
 from tempest.lib.services.identity.v3 import token_client as v3_token_client
+from tempest.lib.services.identity.v3 import users_client as \
+    v3_users_client
 from tempest.lib.services.network import routers_client
 from tempest.services.identity.v3.json import domains_client
 from tempest.services.identity.v3.json import identity_client as v3_iden_client
 from tempest.services.identity.v3.json import roles_client as v3_roles_client
-from tempest.services.identity.v3.json import users_clients as v3_users_client
 from tempest.tests import base
 from tempest.tests import fake_config
 from tempest.tests.lib import fake_http
@@ -55,7 +56,6 @@
     users_client = v2_users_client
     token_client_class = token_client.TokenClient
     fake_response = fake_identity._fake_v2_response
-    assign_role_on_project = 'create_user_role_on_project'
     tenants_client_class = tenants_client.TenantsClient
     delete_tenant = 'delete_tenant'
 
@@ -125,7 +125,7 @@
     def _mock_assign_user_role(self):
         tenant_fix = self.useFixture(mockpatch.PatchObject(
             self.roles_client.RolesClient,
-            self.assign_role_on_project,
+            'create_user_role_on_project',
             return_value=(rest_client.ResponseBody
                           (200, {}))))
         return tenant_fix
@@ -198,11 +198,11 @@
         self._mock_tenant_create('1234', 'fake_admin_tenant')
 
         user_mock = mock.patch.object(self.roles_client.RolesClient,
-                                      self.assign_role_on_project)
+                                      'create_user_role_on_project')
         user_mock.start()
         self.addCleanup(user_mock.stop)
         with mock.patch.object(self.roles_client.RolesClient,
-                               self.assign_role_on_project) as user_mock:
+                               'create_user_role_on_project') as user_mock:
             admin_creds = creds.get_admin_creds()
         user_mock.assert_has_calls([
             mock.call('1234', '1234', '1234')])
@@ -221,11 +221,11 @@
         self._mock_tenant_create('1234', 'fake_role_tenant')
 
         user_mock = mock.patch.object(self.roles_client.RolesClient,
-                                      self.assign_role_on_project)
+                                      'create_user_role_on_project')
         user_mock.start()
         self.addCleanup(user_mock.stop)
         with mock.patch.object(self.roles_client.RolesClient,
-                               self.assign_role_on_project) as user_mock:
+                               'create_user_role_on_project') as user_mock:
             role_creds = creds.get_creds_by_roles(
                 roles=['role1', 'role2'])
         calls = user_mock.mock_calls
@@ -612,7 +612,6 @@
     users_client = v3_users_client
     token_client_class = token_client.V3TokenClient
     fake_response = fake_identity._fake_v3_response
-    assign_role_on_project = 'assign_user_role_on_project'
     tenants_client_class = tenants_client.ProjectsClient
     delete_tenant = 'delete_project'
 
@@ -624,7 +623,7 @@
             return_value=dict(domains=[dict(id='default',
                                             name='Default')])))
         self.patchobject(self.roles_client.RolesClient,
-                         'assign_user_role_on_domain')
+                         'create_user_role_on_domain')
 
     def _mock_list_ec2_credentials(self, user_id, tenant_id):
         pass
diff --git a/tempest/tests/lib/services/identity/v3/test_groups_client.py b/tempest/tests/lib/services/identity/v3/test_groups_client.py
new file mode 100644
index 0000000..38cf3ae
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_groups_client.py
@@ -0,0 +1,213 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import groups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestGroupsClient(base.BaseServiceTest):
+    FAKE_CREATE_GROUP = {
+        'group': {
+            'description': 'Tempest Group Description',
+            'domain_id': 'TempestDomain',
+            'name': 'Tempest Group',
+        }
+    }
+
+    FAKE_GROUP_INFO = {
+        'group': {
+            'description': 'Tempest Group Description',
+            'domain_id': 'TempestDomain',
+            'id': '6e13e2068cf9466e98950595baf6bb35',
+            'links': {
+                'self': 'http://example.com/identity/v3/groups/' +
+                        '6e13e2068cf9466e98950595baf6bb35'
+            },
+            'name': 'Tempest Group',
+        }
+    }
+
+    FAKE_GROUP_LIST = {
+        'links': {
+            'self': 'http://example.com/identity/v3/groups',
+            'previous': None,
+            'next': None,
+        },
+        'groups': [
+            {
+                'description': 'Tempest Group One Description',
+                'domain_id': 'TempestDomain',
+                'id': '1c92f3453ed34291a074b87493455b8f',
+                'links': {
+                    'self': 'http://example.com/identity/v3/groups/' +
+                            '1c92f3453ed34291a074b87493455b8f'
+                },
+                'name': 'Tempest Group One',
+            },
+            {
+                'description': 'Tempest Group Two Description',
+                'domain_id': 'TempestDomain',
+                'id': 'ce9e7dafed3b4877a7d4466ed730a9ee',
+                'links': {
+                    'self': 'http://example.com/identity/v3/groups/' +
+                            'ce9e7dafed3b4877a7d4466ed730a9ee'
+                },
+                'name': 'Tempest Group Two',
+            },
+        ]
+    }
+
+    FAKE_USER_LIST = {
+        'links': {
+            'self': 'http://example.com/identity/v3/groups/' +
+                    '6e13e2068cf9466e98950595baf6bb35/users',
+            'previous': None,
+            'next': None,
+        },
+        'users': [
+            {
+                'domain_id': 'TempestDomain',
+                'description': 'Tempest Test User One Description',
+                'enabled': True,
+                'id': '642688fa65a84217b86cef3c063de2b9',
+                'name': 'TempestUserOne',
+                'links': {
+                    'self': 'http://example.com/identity/v3/users/' +
+                            '642688fa65a84217b86cef3c063de2b9'
+                }
+            },
+            {
+                'domain_id': 'TempestDomain',
+                'description': 'Tempest Test User Two Description',
+                'enabled': True,
+                'id': '1048ead6f8ef4a859b44ffbce3ac0b52',
+                'name': 'TempestUserTwo',
+                'links': {
+                    'self': 'http://example.com/identity/v3/users/' +
+                            '1048ead6f8ef4a859b44ffbce3ac0b52'
+                }
+            },
+        ]
+    }
+
+    def setUp(self):
+        super(TestGroupsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = groups_client.GroupsClient(fake_auth, 'identity',
+                                                 'regionOne')
+
+    def _test_create_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_group,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_GROUP,
+            bytes_body,
+            status=201,
+        )
+
+    def _test_show_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_group,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_GROUP_INFO,
+            bytes_body,
+            group_id='6e13e2068cf9466e98950595baf6bb35',
+        )
+
+    def _test_list_groups(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_groups,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_GROUP_LIST,
+            bytes_body,
+        )
+
+    def _test_update_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_group,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_GROUP_INFO,
+            bytes_body,
+            group_id='6e13e2068cf9466e98950595baf6bb35',
+            name='NewName',
+        )
+
+    def _test_list_users_in_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_group_users,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_USER_LIST,
+            bytes_body,
+            group_id='6e13e2068cf9466e98950595baf6bb35',
+        )
+
+    def test_create_group_with_string_body(self):
+        self._test_create_group()
+
+    def test_create_group_with_bytes_body(self):
+        self._test_create_group(bytes_body=True)
+
+    def test_show_group_with_string_body(self):
+        self._test_show_group()
+
+    def test_show_group_with_bytes_body(self):
+        self._test_show_group(bytes_body=True)
+
+    def test_list_groups_with_string_body(self):
+        self._test_list_groups()
+
+    def test_list_groups_with_bytes_body(self):
+        self._test_list_groups(bytes_body=True)
+
+    def test_update_group_with_string_body(self):
+        self._test_update_group()
+
+    def test_update_group_with_bytes_body(self):
+        self._test_update_group(bytes_body=True)
+
+    def test_list_users_in_group_with_string_body(self):
+        self._test_list_users_in_group()
+
+    def test_list_users_in_group_with_bytes_body(self):
+        self._test_list_users_in_group(bytes_body=True)
+
+    def test_delete_group(self):
+        self.check_service_client_function(
+            self.client.delete_group,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            group_id='6e13e2068cf9466e98950595baf6bb35',
+            status=204,
+        )
+
+    def test_add_user_to_group(self):
+        self.check_service_client_function(
+            self.client.add_group_user,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            status=204,
+            group_id='6e13e2068cf9466e98950595baf6bb35',
+            user_id='642688fa65a84217b86cef3c063de2b9',
+        )
+
+    def test_check_user_in_group(self):
+        self.check_service_client_function(
+            self.client.check_group_user_existence,
+            'tempest.lib.common.rest_client.RestClient.head',
+            {},
+            status=204,
+            group_id='6e13e2068cf9466e98950595baf6bb35',
+            user_id='642688fa65a84217b86cef3c063de2b9',
+        )
diff --git a/tempest/tests/lib/services/identity/v3/test_users_client.py b/tempest/tests/lib/services/identity/v3/test_users_client.py
new file mode 100644
index 0000000..5b572f5
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_users_client.py
@@ -0,0 +1,205 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import users_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestUsersClient(base.BaseServiceTest):
+    FAKE_CREATE_USER = {
+        'user': {
+            'default_project_id': '95f8c3f8e7b54409a418fc30717f9ae0',
+            'domain_id': '8347b31afc3545c4b311cb4cce788a08',
+            'enabled': True,
+            'name': 'Tempest User',
+            'password': 'TempestPassword',
+        }
+    }
+
+    FAKE_USER_INFO = {
+        'user': {
+            'default_project_id': '95f8c3f8e7b54409a418fc30717f9ae0',
+            'domain_id': '8347b31afc3545c4b311cb4cce788a08',
+            'enabled': True,
+            'id': '817fb3c23fd7465ba6d7fe1b1320121d',
+            'links': {
+                'self': 'http://example.com/identity',
+            },
+            'name': 'Tempest User',
+            'password_expires_at': '2016-11-06T15:32:17.000000',
+        }
+    }
+
+    FAKE_USER_LIST = {
+        'links': {
+            'next': None,
+            'previous': None,
+            'self': 'http://example.com/identity/v3/users',
+        },
+        'users': [
+            {
+                'domain_id': 'TempestDomain',
+                'enabled': True,
+                'id': '817fb3c23fd7465ba6d7fe1b1320121d',
+                'links': {
+                    'self': 'http://example.com/identity/v3/users/' +
+                            '817fb3c23fd7465ba6d7fe1b1320121d',
+                },
+                'name': 'Tempest User',
+                'password_expires_at': '2016-11-06T15:32:17.000000',
+            },
+            {
+                'domain_id': 'TempestDomain',
+                'enabled': True,
+                'id': 'bdbfb1e2f1344be197e90a778379cca1',
+                'links': {
+                    'self': 'http://example.com/identity/v3/users/' +
+                            'bdbfb1e2f1344be197e90a778379cca1',
+                },
+                'name': 'Tempest User',
+                'password_expires_at': None,
+            },
+        ]
+    }
+
+    FAKE_GROUP_LIST = {
+        'links': {
+            'self': 'http://example.com/identity/v3/groups',
+            'previous': None,
+            'next': None,
+        },
+        'groups': [
+            {
+                'description': 'Tempest Group One Description',
+                'domain_id': 'TempestDomain',
+                'id': '1c92f3453ed34291a074b87493455b8f',
+                'links': {
+                    'self': 'http://example.com/identity/v3/groups/' +
+                            '1c92f3453ed34291a074b87493455b8f'
+                },
+                'name': 'Tempest Group One',
+            },
+            {
+                'description': 'Tempest Group Two Description',
+                'domain_id': 'TempestDomain',
+                'id': 'ce9e7dafed3b4877a7d4466ed730a9ee',
+                'links': {
+                    'self': 'http://example.com/identity/v3/groups/' +
+                            'ce9e7dafed3b4877a7d4466ed730a9ee'
+                },
+                'name': 'Tempest Group Two',
+            },
+        ]
+    }
+
+    def setUp(self):
+        super(TestUsersClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = users_client.UsersClient(fake_auth, 'identity',
+                                               'regionOne')
+
+    def _test_create_user(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_user,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_USER,
+            bytes_body,
+            status=201,
+        )
+
+    def _test_show_user(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_user,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_USER_INFO,
+            bytes_body,
+            user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+        )
+
+    def _test_list_users(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_users,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_USER_LIST,
+            bytes_body,
+        )
+
+    def _test_update_user(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_user,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_USER_INFO,
+            bytes_body,
+            user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+            name='NewName',
+        )
+
+    def _test_list_user_groups(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_user_groups,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_GROUP_LIST,
+            bytes_body,
+            user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+        )
+
+    def test_create_user_with_string_body(self):
+        self._test_create_user()
+
+    def test_create_user_with_bytes_body(self):
+        self._test_create_user(bytes_body=True)
+
+    def test_show_user_with_string_body(self):
+        self._test_show_user()
+
+    def test_show_user_with_bytes_body(self):
+        self._test_show_user(bytes_body=True)
+
+    def test_list_users_with_string_body(self):
+        self._test_list_users()
+
+    def test_list_users_with_bytes_body(self):
+        self._test_list_users(bytes_body=True)
+
+    def test_update_user_with_string_body(self):
+        self._test_update_user()
+
+    def test_update_user_with_bytes_body(self):
+        self._test_update_user(bytes_body=True)
+
+    def test_list_user_groups_with_string_body(self):
+        self._test_list_user_groups()
+
+    def test_list_user_groups_with_bytes_body(self):
+        self._test_list_user_groups(bytes_body=True)
+
+    def test_delete_user(self):
+        self.check_service_client_function(
+            self.client.delete_user,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+            status=204,
+        )
+
+    def test_change_user_password(self):
+        self.check_service_client_function(
+            self.client.update_user_password,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {},
+            status=204,
+            user_id='817fb3c23fd7465ba6d7fe1b1320121d',
+            password='NewTempestPassword',
+            original_password='OldTempestPassword')