Merge "Move Cinder admin tests into unversioned path"
diff --git a/releasenotes/notes/add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml b/releasenotes/notes/add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml
new file mode 100644
index 0000000..6b45666
--- /dev/null
+++ b/releasenotes/notes/add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Add a new client to handle the OAUTH consumers feature from the identity API.
diff --git a/requirements.txt b/requirements.txt
index 92825a7..7c934c2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,7 +10,7 @@
 testrepository>=0.0.18 # Apache-2.0/BSD
 oslo.concurrency>=3.8.0 # Apache-2.0
 oslo.config>=3.22.0 # Apache-2.0
-oslo.log>=3.11.0 # Apache-2.0
+oslo.log>=3.22.0 # Apache-2.0
 oslo.serialization>=1.10.0 # Apache-2.0
 oslo.utils>=3.20.0 # Apache-2.0
 six>=1.9.0 # MIT
diff --git a/tempest/api/identity/admin/v3/test_oauth_consumers.py b/tempest/api/identity/admin/v3/test_oauth_consumers.py
new file mode 100644
index 0000000..f06fb8f
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_oauth_consumers.py
@@ -0,0 +1,91 @@
+# 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 tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as exceptions
+
+
+class OAUTHConsumersV3Test(base.BaseIdentityV3AdminTest):
+
+    def _create_consumer(self):
+        """Creates a consumer with a random description."""
+        description = data_utils.rand_name('test_create_consumer')
+        consumer = self.oauth_consumers_client.create_consumer(
+            description)['consumer']
+        # cleans up created consumers after tests
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.oauth_consumers_client.delete_consumer,
+                        consumer['id'])
+        return consumer
+
+    @decorators.idempotent_id('c8307ea6-a86c-47fd-ae7b-5b3b2caca76d')
+    def test_create_and_show_consumer(self):
+        """Tests to make sure that a consumer with parameters is made"""
+        consumer = self._create_consumer()
+        # fetch created consumer from client
+        fetched_consumer = self.oauth_consumers_client.show_consumer(
+            consumer['id'])['consumer']
+        # assert that the fetched consumer matches the created one and
+        # has all parameters
+        for key in ['description', 'id', 'links']:
+            self.assertEqual(consumer[key], fetched_consumer[key])
+
+    @decorators.idempotent_id('fdfa1b7f-2a31-4354-b2c7-f6ae20554f93')
+    def test_delete_consumer(self):
+        """Tests the delete function."""
+        consumer = self._create_consumer()
+        # fetch consumer from client to confirm it exists
+        fetched_consumer = self.oauth_consumers_client.show_consumer(
+            consumer['id'])['consumer']
+        self.assertEqual(consumer['id'], fetched_consumer['id'])
+        # delete existing consumer
+        self.oauth_consumers_client.delete_consumer(consumer['id'])
+        # check that consumer no longer exists
+        self.assertRaises(exceptions.NotFound,
+                          self.oauth_consumers_client.show_consumer,
+                          consumer['id'])
+
+    @decorators.idempotent_id('080a9b1a-c009-47c0-9979-5305bf72e3dc')
+    def test_update_consumer(self):
+        """Tests the update functionality"""
+        # create a new consumer to update
+        consumer = self._create_consumer()
+        # create new description
+        new_description = data_utils.rand_name('test_update_consumer')
+        # update consumer
+        self.oauth_consumers_client.update_consumer(consumer['id'],
+                                                    new_description)
+        # check that the same consumer now has the new description
+        updated_consumer = self.oauth_consumers_client.show_consumer(
+            consumer['id'])['consumer']
+        self.assertEqual(new_description, updated_consumer['description'])
+
+    @decorators.idempotent_id('09ca50de-78f2-4ffb-ac71-f2254036b2b8')
+    def test_list_consumers(self):
+        """Test for listing consumers"""
+        # create two consumers to populate list
+        new_consumer_one = self._create_consumer()
+        new_consumer_two = self._create_consumer()
+        # fetch the list of consumers
+        consumer_list = self.oauth_consumers_client \
+                            .list_consumers()['consumers']
+        # add fetched consumer ids to a list
+        id_list = [consumer['id'] for consumer in consumer_list]
+        # check if created consumers are in the list
+        self.assertIn(new_consumer_one['id'], id_list)
+        self.assertIn(new_consumer_two['id'], id_list)
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 9339d3c..8317535 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -212,6 +212,7 @@
         cls.groups_client = cls.os_adm.groups_client
         cls.projects_client = cls.os_adm.projects_client
         cls.role_assignments = cls.os_admin.role_assignments_client
+        cls.oauth_consumers_client = cls.os_adm.oauth_consumers_client
         if CONF.identity.admin_domain_scope:
             # NOTE(andreaf) When keystone policy requires it, the identity
             # admin clients for these tests shall use 'domain' scoped tokens.
diff --git a/tempest/clients.py b/tempest/clients.py
index 49a046a..71c3d41 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -229,6 +229,8 @@
         self.groups_client = self.identity_v3.GroupsClient(**params_v3)
         self.identity_versions_v3_client = self.identity_v3.VersionsClient(
             **params_v3)
+        self.oauth_consumers_client = self.identity_v3.OAUTHConsumerClient(
+            **params_v3)
 
         # Token clients do not use the catalog. They only need default_params.
         # They read auth_url, so they should only be set if the corresponding
diff --git a/tempest/config.py b/tempest/config.py
index ef67e7d..35eb187 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -505,7 +505,7 @@
                      "users can specify."),
     cfg.ListOpt('disk_formats',
                 default=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2',
-                         'vdi', 'iso'],
+                         'vdi', 'iso', 'vhdx'],
                 help="A list of image's disk formats "
                      "users can specify.")
 ]
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index eafde44..e911776 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -29,7 +29,7 @@
 DECORATOR_MODULE = 'decorators'
 DECORATOR_NAME = 'idempotent_id'
 DECORATOR_IMPORT = 'tempest.%s' % DECORATOR_MODULE
-IMPORT_LINE = 'from tempest import %s' % DECORATOR_MODULE
+IMPORT_LINE = 'from tempest.lib import %s' % DECORATOR_MODULE
 DECORATOR_TEMPLATE = "@%s.%s('%%s')" % (DECORATOR_MODULE,
                                         DECORATOR_NAME)
 UNIT_TESTS_EXCLUDE = 'tempest.tests'
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index 88801e7..1489b50 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -20,6 +20,8 @@
 from tempest.lib.services.identity.v3.identity_client import IdentityClient
 from tempest.lib.services.identity.v3.inherited_roles_client import \
     InheritedRolesClient
+from tempest.lib.services.identity.v3.oauth_consumers_client import \
+    OAUTHConsumerClient
 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
@@ -36,4 +38,5 @@
            'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
            'PoliciesClient', 'ProjectsClient', 'RegionsClient',
            'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
-           'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient']
+           'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient',
+           'OAUTHConsumerClient']
diff --git a/tempest/lib/services/identity/v3/oauth_consumers_client.py b/tempest/lib/services/identity/v3/oauth_consumers_client.py
new file mode 100644
index 0000000..582c9e0
--- /dev/null
+++ b/tempest/lib/services/identity/v3/oauth_consumers_client.py
@@ -0,0 +1,95 @@
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class OAUTHConsumerClient(rest_client.RestClient):
+    api_version = "v3"
+
+    def create_consumer(self, description=None):
+        """Creates a consumer.
+
+        :param str description: Optional field to add notes about the consumer
+
+        For more information, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#create-consumer
+        """
+        post_body = {"description": description}
+        post_body = json.dumps({'consumer': post_body})
+        resp, body = self.post('OS-OAUTH1/consumers', post_body)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_consumer(self, consumer_id):
+        """Deletes a consumer.
+
+        :param str consumer_id: The ID of the consumer that will be deleted
+
+        For more information, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#delete-consumer
+        """
+        resp, body = self.delete('OS-OAUTH1/consumers/%s' % consumer_id)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_consumer(self, consumer_id, description=None):
+        """Updates a consumer.
+
+        :param str consumer_id: The ID of the consumer that will be updated
+        :param str description: Optional field to add notes about the consumer
+
+        For more information, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#update-consumers
+        """
+        post_body = {"description": description}
+        post_body = json.dumps({'consumer': post_body})
+        resp, body = self.patch('OS-OAUTH1/consumers/%s' % consumer_id,
+                                post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_consumer(self, consumer_id):
+        """Show consumer details.
+
+        :param str consumer_id: The ID of the consumer that will be shown
+
+        For more information, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#show-consumer-details
+        """
+        resp, body = self.get('OS-OAUTH1/consumers/%s' % consumer_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_consumers(self):
+        """List all consumers.
+
+        For more information, please refer to the official
+        API reference:
+        http://developer.openstack.org/api-ref/identity/v3-ext/#list-consumers
+        """
+        resp, body = self.get('OS-OAUTH1/consumers')
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/lib/services/identity/v3/test_oauth_consumers_client.py b/tempest/tests/lib/services/identity/v3/test_oauth_consumers_client.py
new file mode 100644
index 0000000..8d53792
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_oauth_consumers_client.py
@@ -0,0 +1,160 @@
+# 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 tempest.lib.services.identity.v3 import oauth_consumers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestOAUTHConsumerClient(base.BaseServiceTest):
+    FAKE_CREATE_CONSUMER = {
+        "consumer": {
+            'description': 'A fake description 1'
+        }
+
+    }
+
+    FAKE_CONSUMER_INFO = {
+        "consumer": {
+            'id': '6392c7d3b7a2062e09a07aa377',
+            'links': {
+                'self': 'http://example.com/identity/v3/' +
+                        'OS-OAUTH1/consumers/g6f2l9'
+            },
+            'description': 'A description that is fake'
+        }
+
+    }
+
+    FAKE_LIST_CONSUMERS = {
+        'links': {
+            'self': 'http://example.com/identity/v3/OS-OAUTH1/consumers/',
+            'next': None,
+            'previous': None
+        },
+        'consumers': [
+            {
+                'id': '6392c7d3b7a2062e09a07aa377',
+                'links': {
+                    'self': 'http://example.com/identity/v3/' +
+                            'OS-OAUTH1/consumers/6b9f2g5'
+                },
+                'description': 'A description that is fake'
+            },
+            {
+                'id': '677a855c9e3eb3a3954b36aca6',
+                'links': {
+                    'self': 'http://example.com/identity/v3/' +
+                            'OS-OAUTH1/consumers/6a9f2366'
+                },
+                'description': 'A very fake description 2'
+            },
+            {
+                'id': '9d3ac57b08d65e07826b5e506',
+                'links': {
+                    'self': 'http://example.com/identity/v3/' +
+                            'OS-OAUTH1/consumers/626b5e506'
+                },
+                'description': 'A very fake description 3'
+            },
+            {
+                'id': 'b522d163b1a18e928aca9y426',
+                'links': {
+                    'self': 'http://example.com/identity/v3/' +
+                            'OS-OAUTH1/consumers/g7ca9426'
+                },
+                'description': 'A very fake description 4'
+            },
+            {
+                'id': 'b7e47321b5ef9051f93c2049e',
+                'links': {
+                    'self': 'http://example.com/identity/v3/' +
+                            'OS-OAUTH1/consumers/23d82049e'
+                },
+                'description': 'A very fake description 5'
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestOAUTHConsumerClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = oauth_consumers_client.OAUTHConsumerClient(fake_auth,
+                                                                 'identity',
+                                                                 'regionOne')
+
+    def _test_create_consumer(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_consumer,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_CONSUMER,
+            bytes_body,
+            description=self.FAKE_CREATE_CONSUMER["consumer"]["description"],
+            status=201)
+
+    def _test_show_consumer(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_consumer,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_CONSUMER_INFO,
+            bytes_body,
+            consumer_id="6392c7d3b7a2062e09a07aa377")
+
+    def _test_list_consumers(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_consumers,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_CONSUMERS,
+            bytes_body)
+
+    def _test_update_consumer(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_consumer,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_CONSUMER_INFO,
+            bytes_body,
+            consumer_id="6392c7d3b7a2062e09a07aa377")
+
+    def test_create_consumer_with_str_body(self):
+        self._test_create_consumer()
+
+    def test_create_consumer_with_bytes_body(self):
+        self._test_create_consumer(bytes_body=True)
+
+    def test_show_consumer_with_str_body(self):
+        self._test_show_consumer()
+
+    def test_show_consumer_with_bytes_body(self):
+        self._test_show_consumer(bytes_body=True)
+
+    def test_list_consumers_with_str_body(self):
+        self._test_list_consumers()
+
+    def test_list_consumers_with_bytes_body(self):
+        self._test_list_consumers(bytes_body=True)
+
+    def test_update_consumer_with_str_body(self):
+        self._test_update_consumer()
+
+    def test_update_consumer_with_bytes_body(self):
+        self._test_update_consumer(bytes_body=True)
+
+    def test_delete_consumer(self):
+        self.check_service_client_function(
+            self.client.delete_consumer,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            consumer_id="6392c7d3b7a2062e09a07aa377",
+            status=204)