Merge "Using fixtures instead of deprecated mockpatch module"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index cbfcc09..f11d96a 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -27,6 +27,8 @@
 import subprocess
 import warnings
 
+import openstackdocstheme
+
 # Build the plugin registry
 def build_plugin_registry(app):
     root_dir = os.path.dirname(
@@ -117,7 +119,7 @@
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'nature'
+html_theme = 'openstackdocs'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
@@ -125,7 +127,7 @@
 #html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+html_theme_path = [openstackdocstheme.get_html_theme_path()]
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 1264ecc..c4affd2 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -2,18 +2,16 @@
 Tempest Testing Project
 =======================
 
---------
 Overview
---------
+========
 
 .. toctree::
    :maxdepth: 2
 
    overview
 
-------------
 Field Guides
-------------
+============
 Tempest contains tests of many different types, the field guides
 attempt to explain these in a way that makes it easy to understand
 where your test contributions should go.
@@ -26,11 +24,9 @@
    field_guide/scenario
    field_guide/unit_tests
 
-=========
 For users
 =========
 
----------------------------
 Tempest Configuration Guide
 ---------------------------
 
@@ -40,7 +36,6 @@
    configuration
    sampleconf
 
----------------------
 Command Documentation
 ---------------------
 
@@ -53,11 +48,9 @@
    workspace
    run
 
-==============
 For developers
 ==============
 
------------
 Development
 -----------
 
@@ -70,7 +63,6 @@
    test_removal
    write_tests
 
--------
 Plugins
 -------
 
@@ -80,7 +72,6 @@
    plugin
    plugin-registry
 
--------
 Library
 -------
 
@@ -89,7 +80,6 @@
 
    library
 
-==================
 Indices and tables
 ==================
 
diff --git a/releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yaml b/releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yaml
new file mode 100644
index 0000000..361e387
--- /dev/null
+++ b/releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Define v3 volumes_client for the volume service as a library interface,
+    allowing other projects to use this module as a stable library without
+    maintenance changes.
+    Add show volume summary API to v3 volumes_client library, min_microversion
+    of this API is 3.12.
+
+    * volumes_client(v3)
diff --git a/releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml b/releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml
new file mode 100644
index 0000000..c88522e
--- /dev/null
+++ b/releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+  - |
+    Deprecate default value for configuration parameter v3_endpoint_type
+    of identity section in OpenStack Pike and modify the default value to
+    publicURL in OpenStack Q release.
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 97e3a4d..d240467 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -111,7 +111,7 @@
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'default'
+html_theme = 'openstackdocs'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
@@ -119,7 +119,9 @@
 # html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-# html_theme_path = []
+import openstackdocstheme
+
+html_theme_path = [openstackdocstheme.get_html_theme_path()]
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index adec7a7..d2be814 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -1,5 +1,5 @@
 ===========================
- tempest Release Notes
+ Tempest Release Notes
 ===========================
 
  .. toctree::
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 4d7abb6..747cb42 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -83,8 +83,8 @@
 
         # GET extra specs and verify the value of the key2
         # is the same as before
-        get_body = (self.admin_flavors_client.list_flavor_extra_specs(
-            self.flavor['id'])['extra_specs'])
+        get_body = self.admin_flavors_client.list_flavor_extra_specs(
+            self.flavor['id'])['extra_specs']
         self.assertEqual(get_body, {"key1": "value", "key2": "value2"})
 
         # UNSET extra specs that were set in this test
@@ -92,6 +92,9 @@
                                                           "key1")
         self.admin_flavors_client.unset_flavor_extra_spec(self.flavor['id'],
                                                           "key2")
+        get_body = self.admin_flavors_client.list_flavor_extra_specs(
+            self.flavor['id'])['extra_specs']
+        self.assertEmpty(get_body)
 
     @decorators.idempotent_id('a99dad88-ae1c-4fba-aeb4-32f898218bd0')
     def test_flavor_non_admin_get_all_keys(self):
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 98bf4bf..a492b43 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -65,7 +65,7 @@
         params = {'status': 'invalid_status'}
         body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
-        self.assertEqual([], servers)
+        self.assertEmpty(servers)
 
     @decorators.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
     def test_list_servers_by_admin(self):
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 527f4bd..3e32c2d 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -50,7 +50,7 @@
         servers = body['servers']
         actual = [srv for srv in servers
                   if srv['id'] == self.deleted_id]
-        self.assertEqual([], actual)
+        self.assertEmpty(actual)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('ff01387d-c7ad-47b4-ae9e-64fa214638fe')
@@ -58,7 +58,7 @@
         # Listing servers for a non existing image returns empty list
         body = self.client.list_servers(image='non_existing_image')
         servers = body['servers']
-        self.assertEqual([], servers)
+        self.assertEmpty(servers)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('5913660b-223b-44d4-a651-a0fbfd44ca75')
@@ -66,7 +66,7 @@
         # Listing servers by non existing flavor returns empty list
         body = self.client.list_servers(flavor='non_existing_flavor')
         servers = body['servers']
-        self.assertEqual([], servers)
+        self.assertEmpty(servers)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('e2c77c4a-000a-4af3-a0bd-629a328bde7c')
@@ -74,7 +74,7 @@
         # Listing servers for a non existent server name returns empty list
         body = self.client.list_servers(name='non_existing_server_name')
         servers = body['servers']
-        self.assertEqual([], servers)
+        self.assertEmpty(servers)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
@@ -82,7 +82,7 @@
         # Return an empty list when invalid status is specified
         body = self.client.list_servers(status='non_existing_status')
         servers = body['servers']
-        self.assertEqual([], servers)
+        self.assertEmpty(servers)
 
     @decorators.idempotent_id('12c80a9f-2dec-480e-882b-98ba15757659')
     def test_list_servers_by_limits(self):
@@ -138,4 +138,4 @@
         servers = body['servers']
         actual = [srv for srv in servers
                   if srv['id'] == self.deleted_id]
-        self.assertEqual([], actual)
+        self.assertEmpty(actual)
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 8760af6..83151b3 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -42,81 +42,50 @@
     def resource_setup(cls):
         super(ServerRescueTestJSON, cls).resource_setup()
 
-        # Floating IP creation
-        body = cls.floating_ips_client.create_floating_ip(
-            pool=CONF.network.floating_network_name)['floating_ip']
-        cls.floating_ip_id = str(body['id']).strip()
-        cls.floating_ip = str(body['ip']).strip()
-
-        # Security group creation
-        cls.sg_name = data_utils.rand_name('sg')
-        sg_desc = data_utils.rand_name('sg-desc')
-        cls.sg = cls.security_groups_client.create_security_group(
-            name=cls.sg_name, description=sg_desc)['security_group']
-        cls.sg_id = cls.sg['id']
-
-        cls.password = data_utils.rand_password()
-        # Server for positive tests
-        server = cls.create_test_server(adminPass=cls.password,
+        password = data_utils.rand_password()
+        server = cls.create_test_server(adminPass=password,
                                         wait_until='ACTIVE')
-        cls.server_id = server['id']
-
-    @classmethod
-    def resource_cleanup(cls):
-        # Deleting the floating IP which is created in this method
-        cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id)
-        cls.sg = cls.security_groups_client.delete_security_group(
-            cls.sg_id)
-        super(ServerRescueTestJSON, cls).resource_cleanup()
-
-    def _unrescue(self, server_id):
-        self.servers_client.unrescue_server(server_id)
-        waiters.wait_for_server_status(self.servers_client, server_id,
-                                       'ACTIVE')
+        cls.servers_client.rescue_server(server['id'], adminPass=password)
+        waiters.wait_for_server_status(cls.servers_client, server['id'],
+                                       'RESCUE')
+        cls.rescued_server_id = server['id']
 
     @decorators.idempotent_id('fd032140-714c-42e4-a8fd-adcd8df06be6')
     def test_rescue_unrescue_instance(self):
-        self.servers_client.rescue_server(
-            self.server_id, adminPass=self.password)
-        waiters.wait_for_server_status(self.servers_client, self.server_id,
+        password = data_utils.rand_password()
+        server = self.create_test_server(adminPass=password,
+                                         wait_until='ACTIVE')
+        self.servers_client.rescue_server(server['id'], adminPass=password)
+        waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'RESCUE')
-        self.servers_client.unrescue_server(self.server_id)
-        waiters.wait_for_server_status(self.servers_client, self.server_id,
+        self.servers_client.unrescue_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'ACTIVE')
 
     @decorators.idempotent_id('4842e0cf-e87d-4d9d-b61f-f4791da3cacc')
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
     def test_rescued_vm_associate_dissociate_floating_ip(self):
-        # Rescue the server
-        self.servers_client.rescue_server(
-            self.server_id, adminPass=self.password)
-        waiters.wait_for_server_status(self.servers_client, self.server_id,
-                                       'RESCUE')
-        self.addCleanup(self._unrescue, self.server_id)
-
         # Association of floating IP to a rescued vm
-        client = self.floating_ips_client
-        client.associate_floating_ip_to_server(self.floating_ip,
-                                               self.server_id)
+        floating_ip_body = self.floating_ips_client.create_floating_ip(
+            pool=CONF.network.floating_network_name)['floating_ip']
+        self.addCleanup(self.floating_ips_client.delete_floating_ip,
+                        floating_ip_body['id'])
+
+        self.floating_ips_client.associate_floating_ip_to_server(
+            str(floating_ip_body['ip']).strip(), self.rescued_server_id)
 
         # Disassociation of floating IP that was associated in this method
-        client.disassociate_floating_ip_from_server(self.floating_ip,
-                                                    self.server_id)
+        self.floating_ips_client.disassociate_floating_ip_from_server(
+            str(floating_ip_body['ip']).strip(), self.rescued_server_id)
 
     @decorators.idempotent_id('affca41f-7195-492d-8065-e09eee245404')
     def test_rescued_vm_add_remove_security_group(self):
-        # Rescue the server
-        self.servers_client.rescue_server(
-            self.server_id, adminPass=self.password)
-        waiters.wait_for_server_status(self.servers_client, self.server_id,
-                                       'RESCUE')
-        self.addCleanup(self._unrescue, self.server_id)
-
         # Add Security group
-        self.servers_client.add_security_group(self.server_id,
-                                               name=self.sg_name)
+        sg = self.create_security_group()
+        self.servers_client.add_security_group(self.rescued_server_id,
+                                               name=sg['name'])
 
         # Delete Security group
-        self.servers_client.remove_security_group(self.server_id,
-                                                  name=self.sg_name)
+        self.servers_client.remove_security_group(self.rescued_server_id,
+                                                  name=sg['name'])
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index a480a15..764767b 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -54,6 +54,11 @@
         server = cls.create_test_server(wait_until='ACTIVE')
         cls.server_id = server['id']
 
+        server = cls.create_test_server()
+        cls.client.delete_server(server['id'])
+        waiters.wait_for_server_termination(cls.client, server['id'])
+        cls.deleted_server_id = server['id']
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('dbbfd247-c40c-449e-8f6c-d2aa7c7da7cf')
     def test_server_name_blank(self):
@@ -170,25 +175,17 @@
     @decorators.idempotent_id('98fa0458-1485-440f-873b-fe7f0d714930')
     def test_rebuild_deleted_server(self):
         # Rebuild a deleted server
-        server = self.create_test_server()
-        self.client.delete_server(server['id'])
-        waiters.wait_for_server_termination(self.client, server['id'])
-
         self.assertRaises(lib_exc.NotFound,
                           self.client.rebuild_server,
-                          server['id'], self.image_ref)
+                          self.deleted_server_id, self.image_ref)
 
     @decorators.related_bug('1660878', status_code=409)
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984')
     def test_reboot_deleted_server(self):
         # Reboot a deleted server
-        server = self.create_test_server()
-        self.client.delete_server(server['id'])
-        waiters.wait_for_server_termination(self.client, server['id'])
-
         self.assertRaises(lib_exc.NotFound, self.client.reboot_server,
-                          server['id'], type='SOFT')
+                          self.deleted_server_id, type='SOFT')
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('d86141a7-906e-4731-b187-d64a2ea61422')
@@ -551,7 +548,7 @@
     def setUp(self):
         super(ServersNegativeTestMultiTenantJSON, self).setUp()
         try:
-            waiters.wait_for_server_status(self.client, self.server_id,
+            waiters.wait_for_server_status(self.servers_client, self.server_id,
                                            'ACTIVE')
         except Exception:
             self.__class__.server_id = self.rebuild_server(self.server_id)
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 11517cc..ed5f9a6 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -52,6 +52,7 @@
             validatable=True,
             wait_until='ACTIVE',
             adminPass=self.image_ssh_password)
+        self.addCleanup(self.delete_server, server['id'])
         # Record addresses so that we can ssh later
         server['addresses'] = self.servers_client.list_addresses(
             server['id'])['addresses']
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 2694402..4bc987f 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -134,4 +134,4 @@
         for g in body:
             fetched_ids.append(g['id'])
         missing_groups = [g for g in group_ids if g not in fetched_ids]
-        self.assertEqual([], missing_groups)
+        self.assertEmpty(missing_groups)
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index adb467c..6d42b2a 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -215,7 +215,7 @@
                 implies_role_id)
 
     @decorators.idempotent_id('c90c316c-d706-4728-bcba-eb1912081b69')
-    def test_implied_roles_create_delete(self):
+    def test_implied_roles_create_check_show_delete(self):
         prior_role_id = self.roles[0]['id']
         implies_role_id = self.roles[1]['id']
 
@@ -224,9 +224,19 @@
                                   ignore_not_found=True)
 
         # Check if the inference rule exists
-        self.roles_client.show_role_inference_rule(
+        self.roles_client.check_role_inference_rule(
             prior_role_id, implies_role_id)
 
+        # Show the inference rule and check its elements
+        resp_body = self.roles_client.show_role_inference_rule(
+            prior_role_id, implies_role_id)
+        self.assertIn('role_inference', resp_body)
+        role_inference = resp_body['role_inference']
+        for key1 in ['prior_role', 'implies']:
+            self.assertIn(key1, role_inference)
+            for key2 in ['id', 'links', 'name']:
+                self.assertIn(key2, role_inference[key1])
+
         # Delete the inference rule
         self.roles_client.delete_role_inference_rule(
             prior_role_id, implies_role_id)
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
index e2a3617..69bebfe 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
@@ -58,7 +58,7 @@
             namespace['namespace'])
         body = self.namespace_tags_client.list_namespace_tags(
             namespace['namespace'])
-        self.assertEqual([], body['tags'])
+        self.assertEmpty(body['tags'])
 
     @decorators.idempotent_id('a2a3765e-1a2c-3f6d-a3a7-3cc3466ab875')
     def test_create_update_delete_tag(self):
diff --git a/tempest/api/network/base_routers.py b/tempest/api/network/base_routers.py
deleted file mode 100644
index 62062ba..0000000
--- a/tempest/api/network/base_routers.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from tempest.api.network import base
-
-
-class BaseRouterTest(base.BaseAdminNetworkTest):
-    # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
-    # as some router operations, such as enabling or disabling SNAT
-    # require admin credentials by default
-
-    def _cleanup_router(self, router):
-        self.delete_router(router)
-        self.routers.remove(router)
-
-    def _create_router(self, name=None, admin_state_up=False,
-                       external_network_id=None, enable_snat=None):
-        # associate a cleanup with created routers to avoid quota limits
-        router = self.create_router(name, admin_state_up,
-                                    external_network_id, enable_snat)
-        self.addCleanup(self._cleanup_router, router)
-        return router
-
-    def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
-        interface = self.routers_client.add_router_interface(
-            router_id, subnet_id=subnet_id)
-        self.addCleanup(self._remove_router_interface_with_subnet_id,
-                        router_id, subnet_id)
-        self.assertEqual(subnet_id, interface['subnet_id'])
-        return interface
-
-    def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
-        body = self.routers_client.remove_router_interface(router_id,
-                                                           subnet_id=subnet_id)
-        self.assertEqual(subnet_id, body['subnet_id'])
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index ee0e395..0466d3a 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -16,7 +16,7 @@
 import netaddr
 import testtools
 
-from tempest.api.network import base_routers as base
+from tempest.api.network import base
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -25,7 +25,35 @@
 CONF = config.CONF
 
 
-class RoutersTest(base.BaseRouterTest):
+class RoutersTest(base.BaseAdminNetworkTest):
+    # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
+    # as some router operations, such as enabling or disabling SNAT
+    # require admin credentials by default
+
+    def _cleanup_router(self, router):
+        self.delete_router(router)
+        self.routers.remove(router)
+
+    def _create_router(self, name=None, admin_state_up=False,
+                       external_network_id=None, enable_snat=None):
+        # associate a cleanup with created routers to avoid quota limits
+        router = self.create_router(name, admin_state_up,
+                                    external_network_id, enable_snat)
+        self.addCleanup(self._cleanup_router, router)
+        return router
+
+    def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
+        interface = self.routers_client.add_router_interface(
+            router_id, subnet_id=subnet_id)
+        self.addCleanup(self._remove_router_interface_with_subnet_id,
+                        router_id, subnet_id)
+        self.assertEqual(subnet_id, interface['subnet_id'])
+        return interface
+
+    def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
+        body = self.routers_client.remove_router_interface(router_id,
+                                                           subnet_id=subnet_id)
+        self.assertEqual(subnet_id, body['subnet_id'])
 
     @classmethod
     def skip_checks(cls):
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index cc1e087..3f33c7b 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -80,6 +80,9 @@
         cls.messages_client = cls.os_primary.volume_v3_messages_client
         cls.versions_client = cls.os_primary.volume_v3_versions_client
 
+        if cls._api_version == 3:
+            cls.volumes_client = cls.os_primary.volumes_v3_client
+
     def setUp(self):
         super(BaseVolumeTest, self).setUp()
         self.useFixture(api_microversion_fixture.APIMicroversionFixture(
@@ -266,6 +269,9 @@
             cls.os_admin.volume_scheduler_stats_v2_client
         cls.admin_messages_client = cls.os_admin.volume_v3_messages_client
 
+        if cls._api_version == 3:
+            cls.admin_volume_client = cls.os_admin.volumes_v3_client
+
     @classmethod
     def resource_setup(cls):
         super(BaseVolumeAdminTest, cls).resource_setup()
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 5fd1904..5a192ac 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -63,6 +63,12 @@
             transfer_id, auth_key=auth_key)['transfer']
         waiters.wait_for_volume_resource_status(self.alt_volumes_client,
                                                 volume['id'], 'available')
+        accepted_volume = self.alt_volumes_client.show_volume(
+            volume['id'])['volume']
+        self.assertEqual(self.os_alt.credentials.user_id,
+                         accepted_volume['user_id'])
+        self.assertEqual(self.os_alt.credentials.project_id,
+                         accepted_volume['os-vol-tenant-attr:tenant_id'])
 
     @decorators.idempotent_id('ab526943-b725-4c07-b875-8e8ef87a2c30')
     def test_create_list_delete_volume_transfer(self):
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 712254e..ec9a0dd 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -137,3 +137,17 @@
         origin = self.create_volume()
         self._volume_create_get_update_delete(source_volid=origin['id'],
                                               size=CONF.volume.volume_size)
+
+
+class VolumesSummaryTest(base.BaseVolumeTest):
+
+    _api_version = 3
+    min_microversion = '3.12'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('c4f2431e-4920-4736-9e00-4040386b6feb')
+    def test_show_volume_summary(self):
+        volume_summary = \
+            self.volumes_client.show_volume_summary()['volume-summary']
+        for key in ['total_size', 'total_count']:
+            self.assertIn(key, volume_summary)
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 9787160..8593d3a 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -343,7 +343,7 @@
 
             # If cannot follow make sure it's because we have finished
             else:
-                self.assertEqual([], remaining or [],
+                self.assertEmpty(remaining or [],
                                  'No more pages reported, but still '
                                  'missing ids %s' % remaining)
                 break
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index be3f1f2..99918eb 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -35,10 +35,9 @@
         super(VolumesSnapshotTestJSON, cls).resource_setup()
         cls.volume_origin = cls.create_volume()
 
-    @decorators.idempotent_id('b467b54c-07a4-446d-a1cf-651dedcc3ff1')
+    @decorators.idempotent_id('8567b54c-4455-446d-a1cf-651ddeaa3ff2')
     @test.services('compute')
-    def test_snapshot_create_with_volume_in_use(self):
-        # Create a snapshot when volume status is in-use
+    def test_snapshot_create_delete_with_volume_in_use(self):
         # Create a test instance
         server = self.create_server(wait_until='ACTIVE')
         self.attach_volume(server['id'], self.volume_origin['id'])
@@ -47,19 +46,6 @@
         self.assertRaises(lib_exc.BadRequest, self.create_snapshot,
                           self.volume_origin['id'], force=False)
 
-        # Snapshot a volume even if it's attached to an instance
-        snapshot = self.create_snapshot(self.volume_origin['id'],
-                                        force=True)
-        # Delete the snapshot
-        self.delete_snapshot(snapshot['id'])
-
-    @decorators.idempotent_id('8567b54c-4455-446d-a1cf-651ddeaa3ff2')
-    @test.services('compute')
-    def test_snapshot_delete_with_volume_in_use(self):
-        # Create a test instance
-        server = self.create_server(wait_until='ACTIVE')
-        self.attach_volume(server['id'], self.volume_origin['id'])
-
         # Snapshot a volume attached to an instance
         snapshot1 = self.create_snapshot(self.volume_origin['id'], force=True)
         snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True)
diff --git a/tempest/clients.py b/tempest/clients.py
index 73a4b20..4baa31d 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -269,6 +269,7 @@
         self.volume_manage_v2_client = self.volume_v2.VolumeManageClient()
         self.volumes_client = self.volume_v1.VolumesClient()
         self.volumes_v2_client = self.volume_v2.VolumesClient()
+        self.volumes_v3_client = self.volume_v3.VolumesClient()
         self.volume_v3_messages_client = self.volume_v3.MessagesClient()
         self.volume_v3_versions_client = self.volume_v3.VersionsClient()
         self.volume_types_client = self.volume_v1.TypesClient()
diff --git a/tempest/config.py b/tempest/config.py
index f5b2f0d..a2e0877 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -161,7 +161,9 @@
                choices=['public', 'admin', 'internal',
                         'publicURL', 'adminURL', 'internalURL'],
                help="The endpoint type to use for OpenStack Identity "
-                    "(Keystone) API v3"),
+                    "(Keystone) API v3. The default value adminURL is "
+                    "deprecated and will be modified to publicURL in "
+                    "the next release."),
     cfg.StrOpt('admin_role',
                default='admin',
                help="Role required to administrate keystone."),
@@ -899,38 +901,58 @@
 OrchestrationGroup = [
     cfg.StrOpt('catalog_type',
                default='orchestration',
-               help="Catalog type of the Orchestration service."),
+               help="Catalog type of the Orchestration service.",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.StrOpt('region',
                default='',
                help="The orchestration region name to use. If empty, the "
                     "value of identity.region is used instead. If no such "
                     "region is found in the service catalog, the first found "
-                    "one is used."),
+                    "one is used.",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.StrOpt('endpoint_type',
                default='publicURL',
                choices=['public', 'admin', 'internal',
                         'publicURL', 'adminURL', 'internalURL'],
-               help="The endpoint type to use for the orchestration service."),
+               help="The endpoint type to use for the orchestration service.",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.StrOpt('stack_owner_role', default='heat_stack_owner',
-               help='Role required for users to be able to manage stacks'),
+               help='Role required for users to be able to manage stacks',
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.IntOpt('build_interval',
                default=1,
-               help="Time in seconds between build status checks."),
+               help="Time in seconds between build status checks.",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.IntOpt('build_timeout',
                default=1200,
-               help="Timeout in seconds to wait for a stack to build."),
+               help="Timeout in seconds to wait for a stack to build.",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.StrOpt('instance_type',
                default='m1.micro',
                help="Instance type for tests. Needs to be big enough for a "
-                    "full OS plus the test workload"),
+                    "full OS plus the test workload",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.StrOpt('keypair_name',
-               help="Name of existing keypair to launch servers with."),
+               help="Name of existing keypair to launch servers with.",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.IntOpt('max_template_size',
                default=524288,
-               help="Value must match heat configuration of the same name."),
+               help="Value must match heat configuration of the same name.",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
     cfg.IntOpt('max_resources_per_stack',
                default=1000,
-               help="Value must match heat configuration of the same name."),
+               help="Value must match heat configuration of the same name.",
+               deprecated_for_removal=True,
+               deprecated_reason='Heat support will be removed from Tempest'),
 ]
 
 
@@ -996,7 +1018,9 @@
                 help="Whether or not nova is expected to be available"),
     cfg.BoolOpt('heat',
                 default=False,
-                help="Whether or not Heat is expected to be available"),
+                help="Whether or not Heat is expected to be available",
+                deprecated_for_removal=True,
+                deprecated_reason='Heat support will be removed from Tempest'),
 ]
 
 debug_group = cfg.OptGroup(name="debug",
diff --git a/tempest/lib/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
index 72ab785..07ae917 100644
--- a/tempest/lib/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -15,5 +15,6 @@
 from tempest.lib.services.volume.v3.base_client import BaseClient
 from tempest.lib.services.volume.v3.messages_client import MessagesClient
 from tempest.lib.services.volume.v3.versions_client import VersionsClient
+from tempest.lib.services.volume.v3.volumes_client import VolumesClient
 
-__all__ = ['MessagesClient', 'BaseClient', 'VersionsClient']
+__all__ = ['MessagesClient', 'BaseClient', 'VersionsClient', 'VolumesClient']
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
new file mode 100644
index 0000000..aec0205
--- /dev/null
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -0,0 +1,42 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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 six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v2 import volumes_client
+from tempest.lib.services.volume.v3 import base_client
+
+
+class VolumesClient(base_client.BaseClient,
+                    volumes_client.VolumesClient):
+    """Client class to send CRUD Volume V3 API requests"""
+    api_version = "v3"
+
+    def show_volume_summary(self, **params):
+        """Get volumes summary.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#get-volumes-summary
+        """
+        url = 'volumes/summary'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/lib/services/volume/v3/test_volumes_client.py b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
new file mode 100644
index 0000000..a515fd3
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
@@ -0,0 +1,48 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.volume.v3 import volumes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestVolumesClient(base.BaseServiceTest):
+
+    FAKE_VOLUME_SUMMARY = {
+        "volume-summary": {
+            "total_size": 20,
+            "total_count": 5
+        }
+    }
+
+    def setUp(self):
+        super(TestVolumesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = volumes_client.VolumesClient(fake_auth,
+                                                   'volume',
+                                                   'regionOne')
+
+    def _test_show_volume_summary(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_volume_summary,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VOLUME_SUMMARY,
+            bytes_body)
+
+    def test_show_volume_summary_with_str_body(self):
+        self._test_show_volume_summary()
+
+    def test_show_volume_summary_with_bytes_body(self):
+        self._test_show_volume_summary(bytes_body=True)
diff --git a/test-requirements.txt b/test-requirements.txt
index 13950bd..fbdad44 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,10 +3,11 @@
 # process, which may cause wedges in the gate later.
 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
 # needed for doc build
-sphinx>=1.5.1 # BSD
+sphinx!=1.6.1,>=1.5.1 # BSD
 oslosphinx>=4.7.0 # Apache-2.0
 reno>=1.8.0 # Apache-2.0
 mock>=2.0 # BSD
-coverage>=4.0 # Apache-2.0
+coverage!=4.4,>=4.0 # Apache-2.0
 oslotest>=1.10.0 # Apache-2.0
 flake8-import-order==0.11 # LGPLv3
+openstackdocstheme>=1.5.0 # Apache-2.0