Merge "Add response assertions and remove unused variables in volume tests"
diff --git a/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml b/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml
new file mode 100644
index 0000000..9115f03
--- /dev/null
+++ b/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Add a new client to handle the OAUTH token feature from the identity API.
diff --git a/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml b/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
new file mode 100644
index 0000000..ec21098
--- /dev/null
+++ b/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Added tempest workspace remove --name <workspace_name> --rmdir
+    feature to delete the workspace directory as well as entry.
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 6f072b2..cd09177 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -517,23 +517,33 @@
     @decorators.idempotent_id('77eba8e0-036e-4635-944b-f7a8f3b78dc9')
     @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
                           'Shelve is not available.')
+    @test.services('image')
     def test_shelve_unshelve_server(self):
+        if CONF.image_feature_enabled.api_v2:
+            glance_client = self.os_primary.image_client_v2
+        elif CONF.image_feature_enabled.api_v1:
+            glance_client = self.os_primary.image_client
+        else:
+            raise lib_exc.InvalidConfiguration(
+                'Either api_v1 or api_v2 must be True in '
+                '[image-feature-enabled].')
         compute.shelve_server(self.client, self.server_id,
                               force_shelve_offload=True)
 
         server = self.client.show_server(self.server_id)['server']
         image_name = server['name'] + '-shelved'
         params = {'name': image_name}
-        images = self.compute_images_client.list_images(**params)['images']
+        if CONF.image_feature_enabled.api_v2:
+            images = glance_client.list_images(params)['images']
+        elif CONF.image_feature_enabled.api_v1:
+            images = glance_client.list_images(
+                detail=True, **params)['images']
         self.assertEqual(1, len(images))
         self.assertEqual(image_name, images[0]['name'])
 
         self.client.unshelve_server(self.server_id)
         waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
-
-        images = self.compute_images_client.list_images(**params)['images']
-        msg = ('After unshelve, shelved image is not deleted.')
-        self.assertEmpty(images, msg)
+        glance_client.wait_for_resource_deletion(images[0]['id'])
 
     @decorators.idempotent_id('af8eafd4-38a7-4a4b-bdbc-75145a580560')
     def test_stop_start_server(self):
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 3bc6ce1..4495cbf 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -223,6 +223,7 @@
         cls.projects_client = cls.os_admin.projects_client
         cls.role_assignments = cls.os_admin.role_assignments_client
         cls.oauth_consumers_client = cls.os_admin.oauth_consumers_client
+        cls.oauth_token_client = cls.os_admin.oauth_token_client
         cls.domain_config_client = cls.os_admin.domain_config_client
         cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client
         cls.endpoint_groups_client = cls.os_admin.endpoint_groups_client
diff --git a/tempest/api/network/test_tags.py b/tempest/api/network/test_tags.py
index 1f3a7c4..567a462 100644
--- a/tempest/api/network/test_tags.py
+++ b/tempest/api/network/test_tags.py
@@ -14,11 +14,14 @@
 # under the License.
 
 from tempest.api.network import base
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 from tempest import test
 
+CONF = config.CONF
+
 
 class TagsTest(base.BaseNetworkTest):
     """Tests the following operations in the tags API:
@@ -88,3 +91,112 @@
             self.assertRaises(lib_exc.NotFound,
                               self.tags_client.check_tag_existence, 'networks',
                               self.network['id'], replace_tags[i])
+
+
+class TagsExtTest(base.BaseNetworkTest):
+    """Tests the following operations in the tags API:
+
+        Update all tags.
+        Delete all tags.
+        Check tag existence.
+        Create a tag.
+        List tags.
+        Remove a tag.
+
+    v2.0 of the Neutron API is assumed. The tag-ext extension allows users to
+    set tags on the following resources: subnets, ports, routers and
+    subnetpools.
+    """
+
+    # NOTE(felipemonteiro): The supported resource names are plural. Use
+    # the singular case for the corresponding class resource object.
+    SUPPORTED_RESOURCES = ['subnets', 'ports', 'routers', 'subnetpools']
+
+    @classmethod
+    def skip_checks(cls):
+        super(TagsExtTest, cls).skip_checks()
+        if not test.is_extension_enabled('tag-ext', 'network'):
+            msg = "tag-ext extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(TagsExtTest, cls).resource_setup()
+        cls.network = cls.create_network()
+        cls.subnet = cls.create_subnet(cls.network)
+        cls.port = cls.create_port(cls.network)
+        cls.router = cls.create_router()
+
+        subnetpool_name = data_utils.rand_name(cls.__name__ + '-Subnetpool')
+        prefix = CONF.network.default_network
+        cls.subnetpool = cls.subnetpools_client.create_subnetpool(
+            name=subnetpool_name, prefixes=prefix)['subnetpool']
+
+    @classmethod
+    def resource_cleanup(cls):
+        cls.subnetpools_client.delete_subnetpool(cls.subnetpool['id'])
+        super(TagsExtTest, cls).resource_cleanup()
+
+    def _create_tags_for_each_resource(self):
+        # Create a tag for each resource in `SUPPORTED_RESOURCES` and return
+        # the list of tag names.
+        tag_names = []
+
+        for resource in self.SUPPORTED_RESOURCES:
+            tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag')
+            tag_names.append(tag_name)
+            resource_object = getattr(self, resource[:-1])
+
+            self.tags_client.create_tag(resource, resource_object['id'],
+                                        tag_name)
+            self.addCleanup(self.tags_client.delete_all_tags, resource,
+                            resource_object['id'])
+
+        return tag_names
+
+    @decorators.idempotent_id('c6231efa-9a89-4adf-b050-2a3156b8a1d9')
+    def test_create_check_list_and_delete_tags(self):
+        tag_names = self._create_tags_for_each_resource()
+
+        for i, resource in enumerate(self.SUPPORTED_RESOURCES):
+            # Ensure that a tag was created for each resource.
+            resource_object = getattr(self, resource[:-1])
+            retrieved_tags = self.tags_client.list_tags(
+                resource, resource_object['id'])['tags']
+            self.assertEqual(1, len(retrieved_tags))
+            self.assertEqual(tag_names[i], retrieved_tags[0])
+
+            # Check that the expected tag exists for each resource.
+            self.tags_client.check_tag_existence(
+                resource, resource_object['id'], tag_names[i])
+
+            # Delete the tag and ensure it was deleted.
+            self.tags_client.delete_tag(
+                resource, resource_object['id'], tag_names[i])
+            retrieved_tags = self.tags_client.list_tags(
+                resource, resource_object['id'])['tags']
+            self.assertEmpty(retrieved_tags)
+
+    @decorators.idempotent_id('663a90f5-f334-4b44-afe0-c5fc1d408791')
+    def test_update_and_delete_all_tags(self):
+        self._create_tags_for_each_resource()
+
+        for resource in self.SUPPORTED_RESOURCES:
+            # Generate 3 new tag names.
+            replace_tags = [data_utils.rand_name(
+                self.__class__.__name__ + '-Tag') for _ in range(3)]
+
+            # Replace the current tag with the 3 new tags and validate that the
+            # current resource has the 3 new tags.
+            resource_object = getattr(self, resource[:-1])
+            updated_tags = self.tags_client.update_all_tags(
+                resource, resource_object['id'], replace_tags)['tags']
+            self.assertEqual(3, len(updated_tags))
+            self.assertEqual(set(replace_tags), set(updated_tags))
+
+            # Delete all the tags and check that they have been removed.
+            self.tags_client.delete_all_tags(resource, resource_object['id'])
+            for i in range(3):
+                self.assertRaises(
+                    lib_exc.NotFound, self.tags_client.check_tag_existence,
+                    resource, resource_object['id'], replace_tags[i])
diff --git a/tempest/clients.py b/tempest/clients.py
index d29bef9..c3357bb 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -200,6 +200,8 @@
             **params_v3)
         self.oauth_consumers_client = self.identity_v3.OAUTHConsumerClient(
             **params_v3)
+        self.oauth_token_client = self.identity_v3.OAUTHTokenClient(
+            **params_v3)
         self.domain_config_client = self.identity_v3.DomainConfigurationClient(
             **params_v3)
         self.endpoint_filter_client = \
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index 96d2300..8166b4f 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -40,6 +40,8 @@
 ------
 Deletes the entry for a given tempest workspace --name
 
+--rmdir Deletes the given tempest workspace directory
+
 General Options
 ===============
 
@@ -49,6 +51,7 @@
 """
 
 import os
+import shutil
 import sys
 
 from cliff import command
@@ -102,11 +105,16 @@
             sys.exit(1)
 
     @lockutils.synchronized('workspaces', external=True)
-    def remove_workspace(self, name):
+    def remove_workspace_entry(self, name):
         self._populate()
         self._name_exists(name)
-        self.workspaces.pop(name)
+        workspace_path = self.workspaces.pop(name)
         self._write_file()
+        return workspace_path
+
+    @lockutils.synchronized('workspaces', external=True)
+    def remove_workspace_directory(self, workspace_path):
+        shutil.rmtree(workspace_path)
 
     @lockutils.synchronized('workspaces', external=True)
     def list_workspaces(self):
@@ -226,12 +234,16 @@
         parser = super(TempestWorkspaceRemove, self).get_parser(prog_name)
         add_global_arguments(parser)
         parser.add_argument('--name', required=True)
+        parser.add_argument('--rmdir', action='store_true',
+                            help='Deletes the given workspace directory')
 
         return parser
 
     def take_action(self, parsed_args):
         self.manager = WorkspaceManager(parsed_args.workspace_path)
-        self.manager.remove_workspace(parsed_args.name)
+        workspace_path = self.manager.remove_workspace_entry(parsed_args.name)
+        if parsed_args.rmdir:
+            self.manager.remove_workspace_directory(workspace_path)
         sys.exit(0)
 
 
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index ce607ff..e271a58 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -28,6 +28,8 @@
     InheritedRolesClient
 from tempest.lib.services.identity.v3.oauth_consumers_client import \
     OAUTHConsumerClient
+from tempest.lib.services.identity.v3.oauth_token_client import \
+    OAUTHTokenClient
 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
@@ -43,7 +45,7 @@
 __all__ = ['CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
            'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient',
            'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
-           'OAUTHConsumerClient', 'PoliciesClient', 'ProjectsClient',
-           'RegionsClient', 'RoleAssignmentsClient', 'RolesClient',
-           'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient',
-           'VersionsClient']
+           'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient',
+           'ProjectsClient', 'RegionsClient', 'RoleAssignmentsClient',
+           'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient',
+           'UsersClient', 'VersionsClient']
diff --git a/tempest/lib/services/identity/v3/oauth_token_client.py b/tempest/lib/services/identity/v3/oauth_token_client.py
new file mode 100644
index 0000000..b1d298b
--- /dev/null
+++ b/tempest/lib/services/identity/v3/oauth_token_client.py
@@ -0,0 +1,236 @@
+# Copyright 2017 AT&T 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.
+
+import binascii
+import hashlib
+import hmac
+import random
+import time
+
+import six
+from six.moves.urllib import parse as urlparse
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class OAUTHTokenClient(rest_client.RestClient):
+    api_version = "v3"
+
+    def _escape(self, s):
+        """Escape a unicode string in an OAuth-compatible fashion."""
+        safe = b'~'
+        s = s.encode('utf-8') if isinstance(s, six.text_type) else s
+        s = urlparse.quote(s, safe)
+        if isinstance(s, six.binary_type):
+            s = s.decode('utf-8')
+        return s
+
+    def _generate_params_with_signature(self, client_key, uri,
+                                        client_secret=None,
+                                        resource_owner_key=None,
+                                        resource_owner_secret=None,
+                                        callback_uri=None,
+                                        verifier=None,
+                                        http_method='GET'):
+        """Generate OAUTH params along with signature."""
+        timestamp = six.text_type(int(time.time()))
+        nonce = six.text_type(random.getrandbits(64)) + timestamp
+        oauth_params = [
+            ('oauth_nonce', nonce),
+            ('oauth_timestamp', timestamp),
+            ('oauth_version', '1.0'),
+            ('oauth_signature_method', 'HMAC-SHA1'),
+            ('oauth_consumer_key', client_key),
+        ]
+        if resource_owner_key:
+            oauth_params.append(('oauth_token', resource_owner_key))
+        if callback_uri:
+            oauth_params.append(('oauth_callback', callback_uri))
+        if verifier:
+            oauth_params.append(('oauth_verifier', verifier))
+
+        # normalize_params
+        key_values = [(self._escape(k), self._escape(v))
+                      for k, v in oauth_params]
+        key_values.sort()
+        parameter_parts = ['{0}={1}'.format(k, v) for k, v in key_values]
+        normalized_params = '&'.join(parameter_parts)
+
+        # normalize_uri
+        scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
+        scheme = scheme.lower()
+        netloc = netloc.lower()
+        normalized_uri = urlparse.urlunparse((scheme, netloc, path,
+                                              params, '', ''))
+
+        # construct base string
+        base_string = self._escape(http_method.upper())
+        base_string += '&'
+        base_string += self._escape(normalized_uri)
+        base_string += '&'
+        base_string += self._escape(normalized_params)
+
+        # sign using hmac-sha1
+        key = self._escape(client_secret or '')
+        key += '&'
+        key += self._escape(resource_owner_secret or '')
+        key_utf8 = key.encode('utf-8')
+        text_utf8 = base_string.encode('utf-8')
+        signature = hmac.new(key_utf8, text_utf8, hashlib.sha1)
+        sig = binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
+
+        oauth_params.append(('oauth_signature', sig))
+        return oauth_params
+
+    def _generate_oauth_header(self, oauth_params):
+        authorization_header = {}
+        authorization_header_parameters_parts = []
+        for oauth_parameter_name, value in oauth_params:
+            escaped_name = self._escape(oauth_parameter_name)
+            escaped_value = self._escape(value)
+            part = '{0}="{1}"'.format(escaped_name, escaped_value)
+            authorization_header_parameters_parts.append(part)
+
+        authorization_header_parameters = ', '.join(
+            authorization_header_parameters_parts)
+        oauth_string = 'OAuth %s' % authorization_header_parameters
+        authorization_header['Authorization'] = oauth_string
+
+        return authorization_header
+
+    def create_request_token(self, consumer_key, consumer_secret, project_id):
+        """Create request token.
+
+        For more information, please refer to the official API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#create-request-token
+        """
+        endpoint = 'OS-OAUTH1/request_token'
+        headers = {'Requested-Project-Id': project_id}
+        oauth_params = self._generate_params_with_signature(
+            consumer_key,
+            self.base_url + '/' + endpoint,
+            client_secret=consumer_secret,
+            callback_uri='oob',
+            http_method='POST')
+        oauth_header = self._generate_oauth_header(oauth_params)
+        headers.update(oauth_header)
+        resp, body = self.post(endpoint,
+                               body=None,
+                               headers=headers)
+        self.expected_success(201, resp.status)
+        if not isinstance(body, str):
+            body = body.decode('utf-8')
+        body = dict(item.split("=") for item in body.split("&"))
+        return rest_client.ResponseBody(resp, body)
+
+    def authorize_request_token(self, request_token_id, role_ids):
+        """Authorize request token.
+
+        For more information, please refer to the official API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#authorize-request-token
+        """
+        roles = [{'id': role_id} for role_id in role_ids]
+        body = {'roles': roles}
+        post_body = json.dumps(body)
+        resp, body = self.put("OS-OAUTH1/authorize/%s" % request_token_id,
+                              post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_access_token(self, consumer_key, consumer_secret, request_key,
+                            request_secret, oauth_verifier):
+        """Create access token.
+
+        For more information, please refer to the official API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#create-access-token
+        """
+        endpoint = 'OS-OAUTH1/access_token'
+        oauth_params = self._generate_params_with_signature(
+            consumer_key,
+            self.base_url + '/' + endpoint,
+            client_secret=consumer_secret,
+            resource_owner_key=request_key,
+            resource_owner_secret=request_secret,
+            verifier=oauth_verifier,
+            http_method='POST')
+        headers = self._generate_oauth_header(oauth_params)
+        resp, body = self.post(endpoint, body=None, headers=headers)
+        self.expected_success(201, resp.status)
+        if not isinstance(body, str):
+            body = body.decode('utf-8')
+        body = dict(item.split("=") for item in body.split("&"))
+        return rest_client.ResponseBody(resp, body)
+
+    def get_access_token(self, user_id, access_token_id):
+        """Get access token.
+
+        For more information, please refer to the official API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#get-access-token
+        """
+        resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s"
+                              % (user_id, access_token_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def revoke_access_token(self, user_id, access_token_id):
+        """Revoke access token.
+
+        For more information, please refer to the official API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#revoke-access-token
+        """
+        resp, body = self.delete("users/%s/OS-OAUTH1/access_tokens/%s"
+                                 % (user_id, access_token_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_access_tokens(self, user_id):
+        """List access tokens.
+
+        For more information, please refer to the official API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#list-access-tokens
+        """
+        resp, body = self.get("users/%s/OS-OAUTH1/access_tokens"
+                              % (user_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_access_token_roles(self, user_id, access_token_id):
+        """List roles for an access token.
+
+        For more information, please refer to the official API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#list-roles-for-an-access-token
+        """
+        resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles"
+                              % (user_id, access_token_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_access_token_role(self, user_id, access_token_id, role_id):
+        """Show role details for an access token.
+
+        For more information, please refer to the official API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#show-role-details-for-an-access-token
+        """
+        resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles/%s"
+                              % (user_id, access_token_id, role_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index dc6c0c8..a1c8c53 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -80,13 +80,20 @@
         self.assertEqual(
             self.workspace_manager.get_workspace(self.name), new_path)
 
-    def test_run_workspace_remove(self):
+    def test_run_workspace_remove_entry(self):
         cmd = ['tempest', 'workspace', 'remove',
                '--workspace-path', self.store_file,
                '--name', self.name]
         self._run_cmd_gets_return_code(cmd, 0)
         self.assertIsNone(self.workspace_manager.get_workspace(self.name))
 
+    def test_run_workspace_remove_directory(self):
+        cmd = ['tempest', 'workspace', 'remove',
+               '--workspace-path', self.store_file,
+               '--name', self.name, '--rmdir']
+        self._run_cmd_gets_return_code(cmd, 0)
+        self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
 
 class TestTempestWorkspaceManager(TestTempestWorkspaceBase):
     def setUp(self):
@@ -117,8 +124,13 @@
         self.assertEqual(
             self.workspace_manager.get_workspace(self.name), new_path)
 
-    def test_workspace_manager_remove(self):
-        self.workspace_manager.remove_workspace(self.name)
+    def test_workspace_manager_remove_entry(self):
+        self.workspace_manager.remove_workspace_entry(self.name)
+        self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
+    def test_workspace_manager_remove_directory(self):
+        path = self.workspace_manager.remove_workspace_entry(self.name)
+        self.workspace_manager.remove_workspace_directory(path)
         self.assertIsNone(self.workspace_manager.get_workspace(self.name))
 
     def test_path_expansion(self):
diff --git a/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py b/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py
new file mode 100644
index 0000000..b9b9b15
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py
@@ -0,0 +1,215 @@
+# Copyright 2017 AT&T 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 oslotest import mockpatch
+
+from tempest.lib.services.identity.v3 import oauth_token_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_http
+from tempest.tests.lib.services import base
+
+
+class TestOAUTHTokenClient(base.BaseServiceTest):
+    FAKE_CREATE_REQUEST_TOKEN = {
+        'oauth_token': '29971f',
+        'oauth_token_secret': '238eb8',
+        'oauth_expires_at': '2013-09-11T06:07:51.501805Z'
+    }
+
+    FAKE_AUTHORIZE_REQUEST_TOKEN = {
+        'token': {
+            'oauth_verifier': '8171'
+        }
+    }
+
+    FAKE_CREATE_ACCESS_TOKEN = {
+        'oauth_token': 'accd36',
+        'oauth_token_secret': 'aa47da',
+        'oauth_expires_at': '2013-09-11T06:07:51.501805Z'
+    }
+
+    FAKE_ACCESS_TOKEN_INFO = {
+        'access_token': {
+            'consumer_id': '7fea2d',
+            'id': '6be26a',
+            'expires_at': '2013-09-11T06:07:51.501805Z',
+            'links': {
+                'roles': 'http://example.com/identity/v3/' +
+                         'users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles',
+                'self': 'http://example.com/identity/v3/' +
+                        'users/ce9e07/OS-OAUTH1/access_tokens/6be26a'
+            },
+            'project_id': 'b9fca3',
+            'authorizing_user_id': 'ce9e07'
+        }
+    }
+
+    FAKE_LIST_ACCESS_TOKENS = {
+        'access_tokens': [
+            {
+                'consumer_id': '7fea2d',
+                'id': '6be26a',
+                'expires_at': '2013-09-11T06:07:51.501805Z',
+                'links': {
+                    'roles': 'http://example.com/identity/v3/' +
+                             'users/ce9e07/OS-OAUTH1/access_tokens/' +
+                             '6be26a/roles',
+                    'self': 'http://example.com/identity/v3/' +
+                            'users/ce9e07/OS-OAUTH1/access_tokens/6be26a'
+                },
+                'project_id': 'b9fca3',
+                'authorizing_user_id': 'ce9e07'
+            }
+        ],
+        'links': {
+            'next': None,
+            'previous': None,
+            'self': 'http://example.com/identity/v3/' +
+                    'users/ce9e07/OS-OAUTH1/access_tokens'
+        }
+    }
+
+    FAKE_LIST_ACCESS_TOKEN_ROLES = {
+        'roles': [
+            {
+                'id': '26b860',
+                'domain_id': 'fake_domain',
+                'links': {
+                    'self': 'http://example.com/identity/v3/' +
+                            'roles/26b860'
+                },
+                'name': 'fake_role'
+            }
+        ],
+        'links': {
+            'next': None,
+            'previous': None,
+            'self': 'http://example.com/identity/v3/' +
+                    'users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles'
+        }
+    }
+
+    FAKE_ACCESS_TOKEN_ROLE_INFO = {
+        'role': {
+            'id': '26b860',
+            'domain_id': 'fake_domain',
+            'links': {
+                'self': 'http://example.com/identity/v3/' +
+                        'roles/26b860'
+            },
+            'name': 'fake_role'
+        }
+    }
+
+    def setUp(self):
+        super(TestOAUTHTokenClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = oauth_token_client.OAUTHTokenClient(fake_auth,
+                                                          'identity',
+                                                          'regionOne')
+
+    def _mock_token_response(self, body):
+        temp_response = [key + '=' + value for key, value in body.items()]
+        return '&'.join(temp_response)
+
+    def _test_authorize_request_token(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.authorize_request_token,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_AUTHORIZE_REQUEST_TOKEN,
+            bytes_body,
+            request_token_id=self.FAKE_CREATE_REQUEST_TOKEN['oauth_token'],
+            role_ids=['26b860'],
+            status=200)
+
+    def test_create_request_token(self):
+        mock_resp = self._mock_token_response(self.FAKE_CREATE_REQUEST_TOKEN)
+        resp = fake_http.fake_http_response(None, status=201), mock_resp
+        self.useFixture(mockpatch.Patch(
+            'tempest.lib.common.rest_client.RestClient.post',
+            return_value=resp))
+
+        resp = self.client.create_request_token(
+            consumer_key='12345',
+            consumer_secret='23456',
+            project_id='c8f58432c6f00162f04d3250f')
+        self.assertEqual(self.FAKE_CREATE_REQUEST_TOKEN, resp)
+
+    def test_authorize_token_request_with_str_body(self):
+        self._test_authorize_request_token()
+
+    def test_authorize_token_request_with_bytes_body(self):
+        self._test_authorize_request_token(bytes_body=True)
+
+    def test_create_access_token(self):
+        mock_resp = self._mock_token_response(self.FAKE_CREATE_ACCESS_TOKEN)
+        req_secret = self.FAKE_CREATE_REQUEST_TOKEN['oauth_token_secret']
+        resp = fake_http.fake_http_response(None, status=201), mock_resp
+        self.useFixture(mockpatch.Patch(
+            'tempest.lib.common.rest_client.RestClient.post',
+            return_value=resp))
+
+        resp = self.client.create_access_token(
+            consumer_key='12345',
+            consumer_secret='23456',
+            request_key=self.FAKE_CREATE_REQUEST_TOKEN['oauth_token'],
+            request_secret=req_secret,
+            oauth_verifier='8171')
+        self.assertEqual(self.FAKE_CREATE_ACCESS_TOKEN, resp)
+
+    def test_get_access_token(self):
+        self.check_service_client_function(
+            self.client.get_access_token,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ACCESS_TOKEN_INFO,
+            user_id='ce9e07',
+            access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+            status=200)
+
+    def test_list_access_tokens(self):
+        self.check_service_client_function(
+            self.client.list_access_tokens,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ACCESS_TOKENS,
+            user_id='ce9e07',
+            status=200)
+
+    def test_revoke_access_token(self):
+        self.check_service_client_function(
+            self.client.revoke_access_token,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            user_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['consumer_id'],
+            access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+            status=204)
+
+    def test_list_access_token_roles(self):
+        self.check_service_client_function(
+            self.client.list_access_token_roles,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ACCESS_TOKEN_ROLES,
+            user_id='ce9e07',
+            access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+            status=200)
+
+    def test_get_access_token_role(self):
+        self.check_service_client_function(
+            self.client.get_access_token_role,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ACCESS_TOKEN_ROLE_INFO,
+            user_id='ce9e07',
+            access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+            role_id=self.FAKE_ACCESS_TOKEN_ROLE_INFO['role']['id'],
+            status=200)