Merge "Add additional roles method to v3 roles client."
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 18269bf..2314222 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -9,6 +9,8 @@
 config file which explains the purpose of each individual option. You can see
 the sample config file here: :ref:`tempest-sampleconf`
 
+.. _tempest_cred_provider_conf:
+
 Test Credentials
 ----------------
 
@@ -232,6 +234,9 @@
 
 Enabling Remote Access to Created Servers
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. _tempest_conf_network_allocation:
+
 Network Creation/Usage for Servers
 """"""""""""""""""""""""""""""""""
 When Tempest creates servers for testing, some tests require being able to
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 896cd98..1264ecc 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -67,7 +67,8 @@
    HACKING
    REVIEWING
    microversion_testing
-   test-removal
+   test_removal
+   write_tests
 
 -------
 Plugins
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 6b30825..b3af92f 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -1,3 +1,5 @@
+.. _tempest_plugin:
+
 =============================
 Tempest Test Plugin Interface
 =============================
diff --git a/doc/source/test-removal.rst b/doc/source/test_removal.rst
similarity index 100%
rename from doc/source/test-removal.rst
rename to doc/source/test_removal.rst
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
new file mode 100644
index 0000000..768bf0f
--- /dev/null
+++ b/doc/source/write_tests.rst
@@ -0,0 +1,243 @@
+.. _tempest_test_writing:
+
+Tempest Test Writing Guide
+==========================
+
+This guide serves as a starting point for developers working on writing new
+Tempest tests. At a high level tests in Tempest are just tests that conform to
+the standard python `unit test`_ framework. But there are several aspects of
+that are unique to tempest and it's role as an integration test suite running
+against a real cloud.
+
+.. _unit test: https://docs.python.org/3.6/library/unittest.html
+
+.. note:: This guide is for writing tests in the tempest repository. While many
+          parts of this guide are also applicable to tempest plugins, not all
+          the APIs mentioned are considered stable or recommended for use in
+          plugins. Please refer to :ref:`tempest_plugin` for details about
+          writing plugins
+
+
+Adding a New TestCase
+=====================
+
+The base unit of testing in Tempest is the `TestCase`_ (also called the test
+class). Each TestCase contains test methods which are the individual tests that
+will be executed by the test runner. But, the TestCase is the smallest self
+contained unit for tests from the tempest perspective. It's also the level at
+which tempest is parallel safe. In other words, multiple TestCases can be
+executed in parallel, but individual test methods in the same TestCase can not.
+Also, all test methods within a TestCase are assumed to be executed serially. As
+such you can use the test case to store variables that are shared between
+methods.
+
+.. _TestCase: https://docs.python.org/3.6/library/unittest.html#unittest.TestCase
+
+In standard unittest the lifecycle of a TestCase can be described in the
+following phases:
+
+ #. setUpClass
+ #. setUp
+ #. Test Execution
+ #. tearDown
+ #. doCleanups
+ #. tearDownClass
+
+setUpClass
+----------
+
+The setUpClass phase is the first phase executed by the test runner and is used
+to perform any setup required for all the test methods to be executed. In
+Tempest this is a very important step and will automatically do the necessary
+setup for interacting with the configured cloud.
+
+To accomplish this you do **not** define a setUpClass function, instead there
+are a number of predefined phases to setUpClass that are used. The phases are:
+
+ * skip_checks
+ * setup_credentials
+ * setup_clients
+ * resource_setup
+
+which is executed in that order. An example of a TestCase which defines all
+of these would be::
+
+  from tempest import config
+  from tempest import test
+
+  CONF = config.CONF
+
+
+  class TestExampleCase(test.BaseTestCase):
+
+    @classmethod
+    def skip_checks(cls):
+        """This section is used to evaluate config early and skip all test
+           methods based on these checks
+        """
+        super(TestExampleCase, cls).skip_checks()
+        if not CONF.section.foo
+            cls.skip('A helpful message')
+
+    @classmethod
+    def setup_credentials(cls):
+        """This section is used to do any manual credential allocation and also
+           in the case of dynamic credentials to override the default network
+           resource creation/auto allocation
+        """
+        # This call is used to tell the credential allocator to not create any
+        # network resources for this test case. It also enables selective
+        # creation of other neutron resources. NOTE: it must go before the
+        # super call
+        cls.set_network_resources()
+        super(TestExampleCase, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        """This section is used to setup client aliases from the manager object
+           or to initialize any additional clients. Except in a few very
+           specific situations you should not need to use this.
+        """
+        super(TestExampleCase, cls).setup_clients()
+        cls.servers_client = cls.os.servers_client
+
+    @classmethod
+    def resource_setup(cls):
+        """This section is used to create any resources or objects which are
+           going to be used and shared by **all** test methods in the
+           TestCase. Note then anything created in this section must also be
+           destroyed in the corresponding resource_cleanup() method (which will
+           be run during tearDownClass())
+        """
+        super(TestExampleCase, cls).resource_setup()
+        cls.shared_server = cls.servers_client.create_server(...)
+
+
+Allocating Credentials
+''''''''''''''''''''''
+
+Since Tempest tests are all about testing a running cloud, every test will need
+credentials to be able to make API requests against the cloud. Since this is
+critical to operation and, when running in parallel, easy to make a mistake,
+the base TestCase class will automatically allocate a regular user for each
+TestCase during the setup_credentials() phase. During this process it will also
+initialize a client manager object using those credentials, which will be your
+entry point into interacting with the cloud. For more details on how credentials
+are allocated the :ref:`tempest_cred_provider_conf` section of the Tempest
+Configuration Guide provides more details on the operation of this.
+
+There are some cases when you need more than a single set of credentials, or
+credentials with a more specialized set of roles. To accomplish this you have
+to set a class variable ``credentials`` on the TestCase directly. For example::
+
+    from tempest import test
+
+    class TestExampleAdmin(test.BaseTestCase):
+
+        credentials = ['primary', 'admin']
+
+    @classmethod
+    def skip_checks(cls):
+    ...
+
+In this example the ``TestExampleAdmin`` TestCase will allocate 2 sets of
+credentials, one regular user and one admin user. The corresponding manager
+objects will be set as class variables cls.os and cls.os_adm respectively. You
+can also allocate a second user by putting **'alt'** in the list too. A set of
+alt credentials are the same as primary but can be used for tests cases that
+need a second user/project.
+
+You can also specify credentials with specific roles assigned. This is useful
+for cases where there are specific RBAC requirements hard coded into an API.
+The canonical example of this are swift tests which often want to test swift's
+concepts of operator and reseller_admin. An actual example from tempest on how
+to do this is::
+
+    class PublicObjectTest(base.BaseObjectTest):
+
+        credentials = [['operator', CONF.object_storage.operator_role],
+                       ['operator_alt', CONF.object_storage.operator_role]]
+
+        @classmethod
+        def setup_credentials(cls):
+            super(PublicObjectTest, cls).setup_credentials()
+            ...
+
+In this case the manager objects will be set to ``cls.os_roles_operator`` and
+``cls.os_roles_operator_alt`` respectively.
+
+
+There is no limit to how many credentials you can allocate in this manner,
+however in almost every case you should **not** need more than 3 sets of
+credentials per test case.
+
+To figure out the mapping of manager objects set on the TestCase and the
+requested credentials you can reference:
+
++-------------------+---------------------+
+| Credentials Entry | Manager Variable    |
++===================+=====================+
+| primary           | cls.os              |
++-------------------+---------------------+
+| admin             | cls.os_adm          |
++-------------------+---------------------+
+| alt               | cls.os_alt          |
++-------------------+---------------------+
+| [$label, $role]   | cls.os_roles_$label |
++-------------------+---------------------+
+
+By default cls.os is available since it is allocated in the base tempest test
+class. (located in tempest/test.py) If your TestCase inherits from a different
+direct parent class (it'll still inherit from the BaseTestCase, just not
+directly) be sure to check if that class overrides allocated credentials.
+
+Dealing with Network Allocation
+'''''''''''''''''''''''''''''''
+
+When neutron is enabled and a testing requires networking this isn't normally
+automatically setup when a tenant is created. Since tempest needs isolated
+tenants to function properly it also needs to handle network allocation. By
+default the base test class will allocate a network, subnet, and router
+automatically. (this depends on the configured credential provider, for more
+details see: :ref:`tempest_conf_network_allocation`) However, there are
+situations where you do no need all of these resources allocated. (or your
+TestCase inherits from a class that overrides the default in tempest/test.py)
+There is a class level mechanism to override this allocation and specify which
+resources you need. To do this you need to call `cls.set_network_resources()`
+in the `setup_credentials()` method before the `super()`. For example::
+
+  from tempest import test
+
+
+  class TestExampleCase(test.BaseTestCase):
+
+  @classmethod
+  def setup_credentials(cls):
+      cls.set_network_resources(network=True, subnet=True, router=False)
+      super(TestExampleCase, cls).setup_credentials()
+
+There are 2 quirks with the usage here. First for the set_network_resources
+function to work properly it **must be called before super()**. This is so
+that children classes' settings are always used instead of a parent classes'.
+The other quirk here is that if you do not want to allocate any network
+resources for your test class simply call `set_network_resources()` without
+any arguments. For example::
+
+  from tempest import test
+
+
+  class TestExampleCase(test.BaseTestCase):
+
+  @classmethod
+  def setup_credentials(cls):
+      cls.set_network_resources()
+      super(TestExampleCase, cls).setup_credentials()
+
+This will not allocate any networking resources. This is because by default all
+the arguments default to False.
+
+It's also worth pointing out that it is common for base test classes for
+different services (and scenario tests) to override this setting. When
+inheriting from classes other than the base TestCase in tempest/test.py it is
+worth checking the immediate parent for what is set to determine if your
+class needs to override that setting.
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/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 12eb5e1..1ad153a 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -48,23 +48,25 @@
             cls.fixed_network_name = None
         network_kwargs = fixed_network.set_networks_kwarg(network)
         cls.s1_name = data_utils.rand_name(cls.__name__ + '-instance')
-        cls.s1 = cls.create_test_server(name=cls.s1_name,
-                                        wait_until='ACTIVE',
-                                        **network_kwargs)
+        cls.s1 = cls.create_test_server(name=cls.s1_name, **network_kwargs)
 
         cls.s2_name = data_utils.rand_name(cls.__name__ + '-instance')
         # If image_ref_alt is "" or None then we still want to boot a server
         # but we rely on `testtools.skipUnless` decorator to actually skip
         # the irrelevant tests.
         cls.s2 = cls.create_test_server(
-            name=cls.s2_name, image_id=cls.image_ref_alt or cls.image_ref,
-            wait_until='ACTIVE')
+            name=cls.s2_name, image_id=cls.image_ref_alt or cls.image_ref)
 
         cls.s3_name = data_utils.rand_name(cls.__name__ + '-instance')
         cls.s3 = cls.create_test_server(name=cls.s3_name,
                                         flavor=cls.flavor_ref_alt,
                                         wait_until='ACTIVE')
 
+        waiters.wait_for_server_status(cls.client, cls.s1['id'],
+                                       'ACTIVE')
+        waiters.wait_for_server_status(cls.client, cls.s2['id'],
+                                       'ACTIVE')
+
     @decorators.idempotent_id('05e8a8e7-9659-459a-989d-92c2f501f4ba')
     @testtools.skipUnless(CONF.compute.image_ref != CONF.compute.image_ref_alt,
                           "Need distinct images to run this test")
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/api/volume/admin/v2/test_backends_capabilities.py b/tempest/api/volume/admin/test_backends_capabilities.py
similarity index 100%
rename from tempest/api/volume/admin/v2/test_backends_capabilities.py
rename to tempest/api/volume/admin/test_backends_capabilities.py
diff --git a/tempest/api/volume/admin/v2/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
similarity index 100%
rename from tempest/api/volume/admin/v2/test_snapshot_manage.py
rename to tempest/api/volume/admin/test_snapshot_manage.py
diff --git a/tempest/api/volume/admin/v3/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
similarity index 96%
rename from tempest/api/volume/admin/v3/test_user_messages.py
rename to tempest/api/volume/admin/test_user_messages.py
index 991397a..20c3538 100755
--- a/tempest/api/volume/admin/v3/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.volume.v3 import base
+from tempest.api.volume import base
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -33,7 +33,8 @@
     'links']
 
 
-class UserMessagesTest(base.VolumesV3AdminTest):
+class UserMessagesTest(base.BaseVolumeAdminTest):
+    _api_version = 3
     min_microversion = '3.3'
     max_microversion = 'latest'
 
diff --git a/tempest/api/volume/admin/v2/test_volume_manage.py b/tempest/api/volume/admin/test_volume_manage.py
similarity index 96%
rename from tempest/api/volume/admin/v2/test_volume_manage.py
rename to tempest/api/volume/admin/test_volume_manage.py
index f983490..a039085 100644
--- a/tempest/api/volume/admin/v2/test_volume_manage.py
+++ b/tempest/api/volume/admin/test_volume_manage.py
@@ -22,11 +22,11 @@
 CONF = config.CONF
 
 
-class VolumeManageAdminV2Test(base.BaseVolumeAdminTest):
+class VolumeManageAdminTest(base.BaseVolumeAdminTest):
 
     @classmethod
     def skip_checks(cls):
-        super(VolumeManageAdminV2Test, cls).skip_checks()
+        super(VolumeManageAdminTest, cls).skip_checks()
 
         if not CONF.volume_feature_enabled.manage_volume:
             raise cls.skipException("Manage volume tests are disabled")
diff --git a/tempest/api/volume/admin/v2/test_volume_pools.py b/tempest/api/volume/admin/test_volume_pools.py
similarity index 100%
rename from tempest/api/volume/admin/v2/test_volume_pools.py
rename to tempest/api/volume/admin/test_volume_pools.py
diff --git a/tempest/api/volume/admin/v2/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
similarity index 100%
rename from tempest/api/volume/admin/v2/test_volume_type_access.py
rename to tempest/api/volume/admin/test_volume_type_access.py
diff --git a/tempest/api/volume/admin/v2/test_volumes_list.py b/tempest/api/volume/admin/test_volumes_list.py
similarity index 100%
rename from tempest/api/volume/admin/v2/test_volumes_list.py
rename to tempest/api/volume/admin/test_volumes_list.py
diff --git a/tempest/api/volume/admin/v2/__init__.py b/tempest/api/volume/admin/v2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/volume/admin/v2/__init__.py
+++ /dev/null
diff --git a/tempest/api/volume/admin/v3/__init__.py b/tempest/api/volume/admin/v3/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/volume/admin/v3/__init__.py
+++ /dev/null
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 2f719c8..0cd5ec7 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -13,9 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.api.volume import api_microversion_fixture
 from tempest.common import compute
 from tempest.common import waiters
 from tempest import config
+from tempest.lib.common import api_version_utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions
@@ -24,7 +26,8 @@
 CONF = config.CONF
 
 
-class BaseVolumeTest(tempest.test.BaseTestCase):
+class BaseVolumeTest(api_version_utils.BaseMicroversionTest,
+                     tempest.test.BaseTestCase):
     """Base test case class for all Cinder API tests."""
 
     _api_version = 2
@@ -49,6 +52,10 @@
             msg = ("Invalid Cinder API version (%s)" % cls._api_version)
             raise exceptions.InvalidConfiguration(msg)
 
+        api_version_utils.check_skip_with_microversion(
+            cls.min_microversion, cls.max_microversion,
+            CONF.volume.min_microversion, CONF.volume.max_microversion)
+
     @classmethod
     def setup_credentials(cls):
         cls.set_network_resources()
@@ -68,10 +75,21 @@
         cls.availability_zone_client = (
             cls.os.volume_v2_availability_zone_client)
         cls.volume_limits_client = cls.os.volume_v2_limits_client
+        cls.messages_client = cls.os.volume_v3_messages_client
+        cls.versions_client = cls.os.volume_v3_versions_client
+
+    def setUp(self):
+        super(BaseVolumeTest, self).setUp()
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            self.request_microversion))
 
     @classmethod
     def resource_setup(cls):
         super(BaseVolumeTest, cls).resource_setup()
+        cls.request_microversion = (
+            api_version_utils.select_request_microversion(
+                cls.min_microversion,
+                CONF.volume.min_microversion))
 
         cls.snapshots = []
         cls.volumes = []
@@ -240,6 +258,8 @@
             cls.os_adm.volume_capabilities_v2_client
         cls.admin_scheduler_stats_client = \
             cls.os_adm.volume_scheduler_stats_v2_client
+        cls.admin_messages_client = cls.os_adm.volume_v3_messages_client
+        cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/api/volume/v2/test_image_metadata.py b/tempest/api/volume/test_image_metadata.py
similarity index 100%
rename from tempest/api/volume/v2/test_image_metadata.py
rename to tempest/api/volume/test_image_metadata.py
diff --git a/tempest/api/volume/v3/test_versions.py b/tempest/api/volume/test_versions.py
similarity index 90%
rename from tempest/api/volume/v3/test_versions.py
rename to tempest/api/volume/test_versions.py
index 20f1657..76f2a99 100644
--- a/tempest/api/volume/v3/test_versions.py
+++ b/tempest/api/volume/test_versions.py
@@ -12,12 +12,14 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.api.volume.v3 import base
+from tempest.api.volume import base
 from tempest.lib import decorators
 from tempest import test
 
 
-class VersionsTest(base.VolumesV3Test):
+class VersionsTest(base.BaseVolumeTest):
+
+    _api_version = 3
 
     @decorators.idempotent_id('77838fc4-b49b-4c64-9533-166762517369')
     @test.attr(type='smoke')
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index f6fd86e..925beee 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -66,7 +66,8 @@
         description = data_utils.rand_name("volume-backup-description")
         backup = self.create_backup(volume_id=volume['id'],
                                     name=backup_name,
-                                    description=description)
+                                    description=description,
+                                    container='container')
         self.assertEqual(backup_name, backup['name'])
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume['id'], 'available')
@@ -75,6 +76,7 @@
         backup = self.backups_client.show_backup(backup['id'])['backup']
         self.assertEqual(backup_name, backup['name'])
         self.assertEqual(description, backup['description'])
+        self.assertEqual('container', backup['container'])
 
         # Get all backups with detail
         backups = self.backups_client.list_backups(
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index 6e6cd35..2e30d04 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -68,3 +68,17 @@
         self.assertRaises(lib_exc.BadRequest,
                           self.snapshots_client.list_snapshots,
                           limit='invalid')
+
+    @test.attr(type=['negative'])
+    @decorators.idempotent_id('27b5f37f-bf69-4e8c-986e-c44f3d6819b8')
+    def test_list_snapshots_invalid_param_sort(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.snapshots_client.list_snapshots,
+                          sort_key='invalid')
+
+    @test.attr(type=['negative'])
+    @decorators.idempotent_id('b68deeda-ca79-4a32-81af-5c51179e553a')
+    def test_list_snapshots_invalid_param_marker(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.snapshots_client.list_snapshots,
+                          marker=data_utils.rand_uuid())
diff --git a/tempest/api/volume/v2/test_volumes_snapshots_negative.py b/tempest/api/volume/v2/test_volumes_snapshots_negative.py
deleted file mode 100644
index 42ae859..0000000
--- a/tempest/api/volume/v2/test_volumes_snapshots_negative.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2017 Red Hat, Inc.
-# 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.volume 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 VolumesSnapshotNegativeTest(base.BaseVolumeTest):
-
-    @classmethod
-    def skip_checks(cls):
-        super(VolumesSnapshotNegativeTest, cls).skip_checks()
-        if not CONF.volume_feature_enabled.snapshot:
-            raise cls.skipException("Cinder volume snapshots are disabled")
-
-    @test.attr(type=['negative'])
-    @decorators.idempotent_id('27b5f37f-bf69-4e8c-986e-c44f3d6819b8')
-    def test_list_snapshots_invalid_param_sort(self):
-        self.assertRaises(lib_exc.BadRequest,
-                          self.snapshots_client.list_snapshots,
-                          sort_key='invalid')
-
-    @test.attr(type=['negative'])
-    @decorators.idempotent_id('b68deeda-ca79-4a32-81af-5c51179e553a')
-    def test_list_snapshots_invalid_param_marker(self):
-        self.assertRaises(lib_exc.NotFound,
-                          self.snapshots_client.list_snapshots,
-                          marker=data_utils.rand_uuid())
diff --git a/tempest/api/volume/v3/__init__.py b/tempest/api/volume/v3/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/volume/v3/__init__.py
+++ /dev/null
diff --git a/tempest/api/volume/v3/base.py b/tempest/api/volume/v3/base.py
deleted file mode 100644
index 7f76e6f..0000000
--- a/tempest/api/volume/v3/base.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2016 Andrew Kerr
-# 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.volume import api_microversion_fixture
-from tempest.api.volume import base
-from tempest import config
-from tempest.lib.common import api_version_utils
-
-CONF = config.CONF
-
-
-class VolumesV3Test(api_version_utils.BaseMicroversionTest,
-                    base.BaseVolumeTest):
-    """Base test case class for all v3 Cinder API tests."""
-
-    _api_version = 3
-
-    @classmethod
-    def skip_checks(cls):
-        super(VolumesV3Test, cls).skip_checks()
-        api_version_utils.check_skip_with_microversion(
-            cls.min_microversion, cls.max_microversion,
-            CONF.volume.min_microversion, CONF.volume.max_microversion)
-
-    @classmethod
-    def resource_setup(cls):
-        super(VolumesV3Test, cls).resource_setup()
-        cls.request_microversion = (
-            api_version_utils.select_request_microversion(
-                cls.min_microversion,
-                CONF.volume.min_microversion))
-
-    @classmethod
-    def setup_clients(cls):
-        super(VolumesV3Test, cls).setup_clients()
-        cls.messages_client = cls.os.volume_v3_messages_client
-        cls.versions_client = cls.os.volume_v3_versions_client
-
-    def setUp(self):
-        super(VolumesV3Test, self).setUp()
-        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
-            self.request_microversion))
-
-
-class VolumesV3AdminTest(VolumesV3Test,
-                         base.BaseVolumeAdminTest):
-    """Base test case class for all v3 Volume Admin API tests."""
-
-    credentials = ['primary', 'admin']
-
-    @classmethod
-    def setup_clients(cls):
-        super(VolumesV3AdminTest, cls).setup_clients()
-        cls.admin_messages_client = cls.os_adm.volume_v3_messages_client
-        cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
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/common/waiters.py b/tempest/common/waiters.py
index 3e5600c..9c83c99 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -261,16 +261,16 @@
         time.sleep(client.build_interval)
 
 
-def wait_for_interface_status(client, server, port_id, status):
+def wait_for_interface_status(client, server_id, port_id, status):
     """Waits for an interface to reach a given status."""
-    body = (client.show_interface(server, port_id)
+    body = (client.show_interface(server_id, port_id)
             ['interfaceAttachment'])
     interface_status = body['port_state']
     start = int(time.time())
 
     while(interface_status != status):
         time.sleep(client.build_interval)
-        body = (client.show_interface(server, port_id)
+        body = (client.show_interface(server_id, port_id)
                 ['interfaceAttachment'])
         interface_status = body['port_state']
 
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/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 4c4b5eb..33a7757 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -100,8 +100,10 @@
                 'id': {'type': 'string'},
                 'links': parameter_types.links
             },
-            'additionalProperties': False,
-            'required': ['id', 'links']
+            # NOTE(gmann): This will be empty object if there is no
+            # flavor info present in DB. This can happen when flavor info is
+            # deleted after server creation.
+            'additionalProperties': False
         },
         'fault': {
             'type': 'object',
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/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index 2bdf1b1..983ed89 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -27,7 +27,8 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-with-details
+        http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots
         """
         url = 'snapshots'
         if detail:
@@ -45,7 +46,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-details
         """
         url = "snapshots/%s" % snapshot_id
         resp, body = self.get(url)
@@ -71,7 +72,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot
         """
         put_body = json.dumps({'snapshot': kwargs})
         resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
@@ -84,7 +85,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot
         """
         resp, body = self.delete("snapshots/%s" % snapshot_id)
         self.expected_success(202, resp.status)
@@ -136,7 +137,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata
         """
         url = "snapshots/%s/metadata" % snapshot_id
         resp, body = self.get(url)
@@ -149,7 +150,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata
         """
         put_body = json.dumps(kwargs)
         url = "snapshots/%s/metadata" % snapshot_id
diff --git a/tempest/lib/services/volume/v2/types_client.py b/tempest/lib/services/volume/v2/types_client.py
index 31597d7..5d30615 100644
--- a/tempest/lib/services/volume/v2/types_client.py
+++ b/tempest/lib/services/volume/v2/types_client.py
@@ -41,7 +41,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-volume-types-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#list-volume-types
         """
         url = 'types'
         if params:
@@ -57,7 +57,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details
         """
         url = "types/%s" % volume_type_id
         resp, body = self.get(url)
@@ -70,7 +70,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type
         """
         post_body = json.dumps({'volume_type': kwargs})
         resp, body = self.post('types', post_body)
@@ -83,7 +83,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type
         """
         resp, body = self.delete("types/%s" % volume_type_id)
         self.expected_success(202, resp.status)
@@ -138,7 +138,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type
         """
         put_body = json.dumps({'volume_type': kwargs})
         resp, body = self.put('types/%s' % volume_type_id, put_body)
@@ -156,7 +156,7 @@
                      updated value.
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type-extra-specs-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#update-extra-specs-for-a-volume-type
         """
         url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
         put_body = json.dumps(extra_specs)
@@ -170,7 +170,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#add-type-access-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#add-private-volume-type-access
         """
         post_body = json.dumps({'addProjectAccess': kwargs})
         url = 'types/%s/action' % volume_type_id
@@ -183,7 +183,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#remove-type-access-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#remove-private-volume-type-access
         """
         post_body = json.dumps({'removeProjectAccess': kwargs})
         url = 'types/%s/action' % volume_type_id
@@ -196,7 +196,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-type-access-v2
+        http://developer.openstack.org/api-ref/block-storage/v2/#list-private-volume-type-access-details
         """
         url = 'types/%s/os-volume-type-access' % volume_type_id
         resp, body = self.get(url)
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index f59abb7..72823c0 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -106,7 +106,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#attach-volume
+        http://developer.openstack.org/api-ref/block-storage/v2/#attach-volume-to-server
         """
         post_body = json.dumps({'os-attach': kwargs})
         url = 'volumes/%s/action' % (volume_id)
@@ -163,7 +163,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#extend-volume
+        http://developer.openstack.org/api-ref/block-storage/v2/#extend-volume-size
         """
         post_body = json.dumps({'os-extend': kwargs})
         url = 'volumes/%s/action' % (volume_id)
@@ -176,7 +176,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-status
+        http://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-statuses
         """
         post_body = json.dumps({'os-reset_status': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
@@ -307,7 +307,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-image-metadata
+        http://developer.openstack.org/api-ref/block-storage/v2/#set-image-metadata-for-volume
         """
         post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
         url = "volumes/%s/action" % (volume_id)
@@ -344,7 +344,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#show_backend_capabilities
+        http://developer.openstack.org/api-ref/block-storage/v2/#show-back-end-capabilities
         """
         url = 'capabilities/%s' % host
         resp, body = self.get(url)
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)
diff --git a/tox.ini b/tox.ini
index b3052eb..892f834 100644
--- a/tox.ini
+++ b/tox.ini
@@ -36,7 +36,6 @@
 commands = oslo-config-generator --config-file tempest/cmd/config-generator.tempest.conf
 
 [testenv:cover]
-setenv = OS_TEST_PATH=./tempest/tests
 commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}'
 
 [testenv:all]