Merge "Make volume client alias with _latest suffix"
diff --git a/.zuul.yaml b/.zuul.yaml
index c794d26..52c11c9 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -9,10 +9,10 @@
       test setup. To run a multi-node test inherit from devstack-tempest and
       set the nodeset to a multi-node one.
     required-projects:
-      - openstack/tempest
+      - git.openstack.org/openstack/tempest
     timeout: 7200
     roles:
-      - zuul: openstack-dev/devstack
+      - zuul: git.openstack.org/openstack-dev/devstack
     vars:
       devstack_services:
         tempest: true
@@ -108,6 +108,7 @@
       This job includes two nodes, controller / tempest plus a subnode, but
       it can be used with different topologies, as long as a controller node
       and a tempest one exist.
+    timeout: 10800
     vars:
       tox_envlist: full
       devstack_localrc:
@@ -195,70 +196,70 @@
       - ^tempest/hacking/.*$
       - ^tempest/tests/.*$
     required-projects:
-      - openstack/almanach
-      - openstack/aodh
-      - openstack/barbican-tempest-plugin
-      - openstack/ceilometer
-      - openstack/cinder
-      - openstack/congress
-      - openstack/designate-tempest-plugin
-      - openstack/ec2-api
-      - openstack/freezer
-      - openstack/freezer-api
-      - openstack/freezer-tempest-plugin
-      - openstack/gce-api
-      - openstack/glare
-      - openstack/heat
-      - openstack/intel-nfv-ci-tests
-      - openstack/ironic
-      - openstack/ironic-inspector
-      - openstack/keystone-tempest-plugin
-      - openstack/kingbird
-      - openstack/kuryr-tempest-plugin
-      - openstack/magnum
-      - openstack/magnum-tempest-plugin
-      - openstack/manila
-      - openstack/manila-tempest-plugin
-      - openstack/mistral
-      - openstack/mogan
-      - openstack/monasca-api
-      - openstack/monasca-log-api
-      - openstack/murano
-      - openstack/networking-bgpvpn
-      - openstack/networking-cisco
-      - openstack/networking-fortinet
-      - openstack/networking-generic-switch
-      - openstack/networking-l2gw
-      - openstack/networking-midonet
-      - openstack/networking-plumgrid
-      - openstack/networking-sfc
-      - openstack/neutron
-      - openstack/neutron-dynamic-routing
-      - openstack/neutron-fwaas
-      - openstack/neutron-lbaas
-      - openstack/neutron-tempest-plugin
-      - openstack/neutron-vpnaas
-      - openstack/nova-lxd
-      - openstack/novajoin-tempest-plugin
-      - openstack/octavia-tempest-plugin
-      - openstack/oswin-tempest-plugin
-      - openstack/panko
-      - openstack/patrole
-      - openstack/qinling
-      - openstack/requirements
-      - openstack/sahara-tests
-      - openstack/senlin
-      - openstack/senlin-tempest-plugin
-      - openstack/tap-as-a-service
-      - openstack/tempest-horizon
-      - openstack/trio2o
-      - openstack/trove
-      - openstack/valet
-      - openstack/vitrage
-      - openstack/vmware-nsx-tempest-plugin
-      - openstack/watcher-tempest-plugin
-      - openstack/zaqar-tempest-plugin
-      - openstack/zun-tempest-plugin
+      - git.openstack.org/openstack/almanach
+      - git.openstack.org/openstack/aodh
+      - git.openstack.org/openstack/barbican-tempest-plugin
+      - git.openstack.org/openstack/ceilometer
+      - git.openstack.org/openstack/cinder
+      - git.openstack.org/openstack/congress
+      - git.openstack.org/openstack/designate-tempest-plugin
+      - git.openstack.org/openstack/ec2-api
+      - git.openstack.org/openstack/freezer
+      - git.openstack.org/openstack/freezer-api
+      - git.openstack.org/openstack/freezer-tempest-plugin
+      - git.openstack.org/openstack/gce-api
+      - git.openstack.org/openstack/glare
+      - git.openstack.org/openstack/heat
+      - git.openstack.org/openstack/intel-nfv-ci-tests
+      - git.openstack.org/openstack/ironic
+      - git.openstack.org/openstack/ironic-inspector
+      - git.openstack.org/openstack/keystone-tempest-plugin
+      - git.openstack.org/openstack/kingbird
+      - git.openstack.org/openstack/kuryr-tempest-plugin
+      - git.openstack.org/openstack/magnum
+      - git.openstack.org/openstack/magnum-tempest-plugin
+      - git.openstack.org/openstack/manila
+      - git.openstack.org/openstack/manila-tempest-plugin
+      - git.openstack.org/openstack/mistral
+      - git.openstack.org/openstack/mogan
+      - git.openstack.org/openstack/monasca-api
+      - git.openstack.org/openstack/monasca-log-api
+      - git.openstack.org/openstack/murano
+      - git.openstack.org/openstack/networking-bgpvpn
+      - git.openstack.org/openstack/networking-cisco
+      - git.openstack.org/openstack/networking-fortinet
+      - git.openstack.org/openstack/networking-generic-switch
+      - git.openstack.org/openstack/networking-l2gw
+      - git.openstack.org/openstack/networking-midonet
+      - git.openstack.org/openstack/networking-plumgrid
+      - git.openstack.org/openstack/networking-sfc
+      - git.openstack.org/openstack/neutron
+      - git.openstack.org/openstack/neutron-dynamic-routing
+      - git.openstack.org/openstack/neutron-fwaas
+      - git.openstack.org/openstack/neutron-lbaas
+      - git.openstack.org/openstack/neutron-tempest-plugin
+      - git.openstack.org/openstack/neutron-vpnaas
+      - git.openstack.org/openstack/nova-lxd
+      - git.openstack.org/openstack/novajoin-tempest-plugin
+      - git.openstack.org/openstack/octavia-tempest-plugin
+      - git.openstack.org/openstack/oswin-tempest-plugin
+      - git.openstack.org/openstack/panko
+      - git.openstack.org/openstack/patrole
+      - git.openstack.org/openstack/qinling
+      - git.openstack.org/openstack/requirements
+      - git.openstack.org/openstack/sahara-tests
+      - git.openstack.org/openstack/senlin
+      - git.openstack.org/openstack/senlin-tempest-plugin
+      - git.openstack.org/openstack/tap-as-a-service
+      - git.openstack.org/openstack/tempest-horizon
+      - git.openstack.org/openstack/trio2o
+      - git.openstack.org/openstack/trove
+      - git.openstack.org/openstack/valet
+      - git.openstack.org/openstack/vitrage
+      - git.openstack.org/openstack/vmware-nsx-tempest-plugin
+      - git.openstack.org/openstack/watcher-tempest-plugin
+      - git.openstack.org/openstack/zaqar-tempest-plugin
+      - git.openstack.org/openstack/zun-tempest-plugin
 
 - job:
     name: tempest-cinder-v2-api
diff --git a/HACKING.rst b/HACKING.rst
index 2a7ae1d..5b9c0f1 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -25,6 +25,8 @@
 - [T115] Check that admin tests should exist under admin path
 - [N322] Method's default argument shouldn't be mutable
 - [T116] Unsupported 'message' Exception attribute in PY3
+- [T117] Check negative tests have ``@decorators.attr(type=['negative'])``
+  applied.
 
 Test Data/Configuration
 -----------------------
@@ -146,11 +148,6 @@
 This attribute must be applied to each test that belongs to a negative test
 class, i.e. a test class name ending with "Negative.*" substring.
 
-.. todo::
-
-  Add a hacking check for ensuring that all classes that contain substring
-  "Negative" have the negative attribute decorator applied above each test.
-
 Slow Attribute
 ^^^^^^^^^^^^^^
 The ``type='slow'`` attribute is used to signify that a test takes a long time
diff --git a/doc/source/data/tempest-plugins-registry.header b/doc/source/data/tempest-plugins-registry.header
index 9821e8e..0de12b7 100644
--- a/doc/source/data/tempest-plugins-registry.header
+++ b/doc/source/data/tempest-plugins-registry.header
@@ -17,7 +17,3 @@
 The following are plugins that a script has found in the openstack/
 namespace, which includes but is not limited to official OpenStack
 projects.
-
-+----------------------------+-------------------------------------------------------------------------+
-|Plugin Name                 |URL                                                                      |
-+----------------------------+-------------------------------------------------------------------------+
diff --git a/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml b/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml
new file mode 100644
index 0000000..1dda4e1
--- /dev/null
+++ b/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    Cinder CLI calls have now been updated to use the ``--os-endpoint-type``
+    option instead of ``--endpoint-type``. The latter had been deprecated in
+    Cinder and has been removed in the Rocky release.
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 384ca38..71b8e4f 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -39,3 +39,20 @@
    :default: smoke
 
    The Tempest tox environment to run.
+
+.. zuul:rolevar:: tempest_black_regex
+   :default: ''
+
+   A regular expression used to skip the tests.
+
+   It works only when used with some specific tox environments
+   ('all', 'all-plugin'.)
+
+   Multi-line and commented regexs can be achieved by doing this:
+
+       ::
+           vars:
+             tempest_black_regex: |
+               (?x)    # Ignore comments and whitespaces
+               # Line with only a comment.
+               (tempest.api.identity).*$
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index 85e94f2..c89eb93 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -1,3 +1,4 @@
 devstack_base_dir: /opt/stack
 tempest_test_regex: ''
 tox_envlist: smoke
+tempest_black_regex: ''
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index b68507a..54ddc71 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -35,7 +35,9 @@
       when: blacklist_stat.stat.exists
 
 - name: Run Tempest
-  command: tox -e {{tox_envlist}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} --concurrency={{tempest_concurrency|default(default_concurrency)}}
+  command: tox -e {{tox_envlist}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} \
+            --concurrency={{tempest_concurrency|default(default_concurrency)}} \
+            --black-regex={{tempest_black_regex|quote}}
   args:
     chdir: "{{devstack_base_dir}}/tempest"
   become: true
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index a57a360..a72675e 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -34,6 +34,9 @@
 METHOD_DELETE_RESOURCE = re.compile(r"^\s*def delete_.+")
 CLASS = re.compile(r"^class .+")
 EX_ATTRIBUTE = re.compile(r'(\s+|\()(e|ex|exc|exception).message(\s+|\))')
+NEGATIVE_TEST_DECORATOR = re.compile(
+    r'\s*@decorators\.attr\(type=.*negative.*\)')
+_HAVE_NEGATIVE_DECORATOR = False
 
 
 def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
@@ -306,6 +309,29 @@
         yield(0, msg)
 
 
+def negative_test_attribute_always_applied_to_negative_tests(physical_line,
+                                                             filename):
+    """Check ``@decorators.attr(type=['negative'])`` applied to negative tests.
+
+    T117
+    """
+    global _HAVE_NEGATIVE_DECORATOR
+
+    if re.match(r'.\/tempest\/api\/.*_negative.*', filename):
+
+        if NEGATIVE_TEST_DECORATOR.match(physical_line):
+            _HAVE_NEGATIVE_DECORATOR = True
+            return
+
+        if TEST_DEFINITION.match(physical_line):
+            if not _HAVE_NEGATIVE_DECORATOR:
+                return (
+                    0, "T117: Must apply `@decorators.attr(type=['negative'])`"
+                       " to all negative API tests"
+                )
+            _HAVE_NEGATIVE_DECORATOR = False
+
+
 def factory(register):
     register(import_no_clients_in_api_and_scenario_tests)
     register(scenario_tests_need_service_tags)
@@ -322,3 +348,4 @@
     register(use_rand_uuid_instead_of_uuid4)
     register(dont_put_admin_tests_on_nonadmin_path)
     register(unsupported_exception_attribute_PY3)
+    register(negative_test_attribute_always_applied_to_negative_tests)
diff --git a/tempest/lib/api_schema/response/compute/v2_16/servers.py b/tempest/lib/api_schema/response/compute/v2_16/servers.py
index 3eb658f..fb1a2fc 100644
--- a/tempest/lib/api_schema/response/compute/v2_16/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_16/servers.py
@@ -157,4 +157,14 @@
     }
 }
 
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.9 ******
 list_servers = copy.deepcopy(servers.list_servers)
+update_server = copy.deepcopy(servers.update_server)
+rebuild_server = copy.deepcopy(servers.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers.rebuild_server_with_admin_pass)
+show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers.get_remote_consoles)
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
index fd9e933..e3e8ad1 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -16,10 +16,10 @@
 
 from tempest.lib.api_schema.response.compute.v2_16 import servers \
     as serversv216
-from tempest.lib.api_schema.response.compute.v2_9 import servers as serversv29
 
-list_servers = copy.deepcopy(serversv216.list_servers)
-
+# Compute microversion 2.19:
+# 1. New attributes in 'server' dict.
+#      'description'
 get_server = copy.deepcopy(serversv216.get_server)
 get_server['response_body']['properties']['server'][
     'properties'].update({'description': {'type': ['string', 'null']}})
@@ -32,21 +32,29 @@
 list_servers_detail['response_body']['properties']['servers']['items'][
     'required'].append('description')
 
-update_server = copy.deepcopy(serversv29.update_server)
+update_server = copy.deepcopy(serversv216.update_server)
 update_server['response_body']['properties']['server'][
     'properties'].update({'description': {'type': ['string', 'null']}})
 update_server['response_body']['properties']['server'][
     'required'].append('description')
 
-rebuild_server = copy.deepcopy(serversv29.rebuild_server)
+rebuild_server = copy.deepcopy(serversv216.rebuild_server)
 rebuild_server['response_body']['properties']['server'][
     'properties'].update({'description': {'type': ['string', 'null']}})
 rebuild_server['response_body']['properties']['server'][
     'required'].append('description')
 
 rebuild_server_with_admin_pass = copy.deepcopy(
-    serversv29.rebuild_server_with_admin_pass)
+    serversv216.rebuild_server_with_admin_pass)
 rebuild_server_with_admin_pass['response_body']['properties']['server'][
     'properties'].update({'description': {'type': ['string', 'null']}})
 rebuild_server_with_admin_pass['response_body']['properties']['server'][
     'required'].append('description')
+
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.16 ******
+list_servers = copy.deepcopy(serversv216.list_servers)
+show_server_diagnostics = copy.deepcopy(serversv216.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(serversv216.get_remote_consoles)
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
index 5c35eab..8e62dc3 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -15,7 +15,6 @@
 
 import copy
 
-from tempest.lib.api_schema.response.compute.v2_1 import servers as servers21
 from tempest.lib.api_schema.response.compute.v2_19 import servers as servers219
 
 # The 2.26 microversion changes the server GET and (detailed) LIST responses to
@@ -62,10 +61,6 @@
 rebuild_server_with_admin_pass['response_body']['properties']['server'][
     'required'].append('tags')
 
-# list response schema wasn't changed for v2.26 so use v2.1
-
-list_servers = copy.deepcopy(servers21.list_servers)
-
 list_tags = {
     'status_code': [200],
     'response_body': {
@@ -98,3 +93,11 @@
 }
 
 delete_tag = {'status_code': [204]}
+
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.19 ******
+list_servers = copy.deepcopy(servers219.list_servers)
+show_server_diagnostics = copy.deepcopy(servers219.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers219.get_remote_consoles)
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
index 3fb56ec..d8c776b 100644
--- a/tempest/lib/cli/base.py
+++ b/tempest/lib/cli/base.py
@@ -269,7 +269,7 @@
         :param merge_stderr: if True the stderr buffer is merged into stdout
         :type merge_stderr: boolean
         """
-        flags += ' --endpoint-type %s' % endpoint_type
+        flags += ' --os-endpoint-type %s' % endpoint_type
         return self.cmd_with_auth(
             'cinder', action, flags, params, fail_ok, merge_stderr)
 
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 8918a8c..833cfd6 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -331,7 +331,7 @@
         self.region = region
         # Check if passed or default credentials are valid
         if not self.credentials.is_valid():
-            raise exceptions.InvalidCredentials()
+            raise exceptions.InvalidCredentials(credentials)
         # Get the identity classes matching the provided credentials
         # TODO(andreaf) Define a new interface in Credentials to get
         # the API version from an instance
@@ -340,7 +340,9 @@
                     isinstance(self.credentials, auth.IDENTITY_VERSION[k][0])]
         # Zero matches or more than one are both not valid.
         if len(identity) != 1:
-            raise exceptions.InvalidCredentials()
+            msg = "Zero or %d ambiguous auth provider found. identity: %s, " \
+                "credentials: %s" % (len(identity), identity, credentials)
+            raise exceptions.InvalidCredentials(msg)
         self.auth_version, auth_provider_class = identity[0]
         self.dscv = disable_ssl_certificate_validation
         self.ca_certs = ca_certs
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index e39afe0..f5805ef 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -369,7 +369,8 @@
             self.floating_ips[tenant.access_point['id']]['floating_ip_address']
         private_key = tenant.keypair['private_key']
         access_point_ssh = self.get_remote_client(
-            access_point_ssh, private_key=private_key)
+            access_point_ssh, private_key=private_key,
+            server=tenant.access_point)
         return access_point_ssh
 
     def _test_in_tenant_block(self, tenant):
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/tests/lib/services/volume/v2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/tests/lib/services/volume/v2/__init__.py
+++ /dev/null
diff --git a/tempest/tests/lib/services/volume/v2/test_backups_client.py b/tempest/tests/lib/services/volume/v2/test_backups_client.py
deleted file mode 100644
index 14e5fb0..0000000
--- a/tempest/tests/lib/services/volume/v2/test_backups_client.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# 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.v2 import backups_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestBackupsClient(base.BaseServiceTest):
-
-    FAKE_BACKUP_LIST = {
-        "backups": [
-            {
-                "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
-                "links": [
-                    {
-                        "href": "fake-url-1",
-                        "rel": "self"
-                    },
-                    {
-                        "href": "fake-url-2",
-                        "rel": "bookmark"
-                    }
-                ],
-                "name": "backup001"
-            }
-        ]
-    }
-
-    FAKE_BACKUP_LIST_WITH_DETAIL = {
-        "backups": [
-            {
-                "availability_zone": "az1",
-                "container": "volumebackups",
-                "created_at": "2013-04-02T10:35:27.000000",
-                "description": None,
-                "fail_reason": None,
-                "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
-                "links": [
-                    {
-                        "href": "fake-url-1",
-                        "rel": "self"
-                    },
-                    {
-                        "href": "fake-url-2",
-                        "rel": "bookmark"
-                    }
-                ],
-                "name": "backup001",
-                "object_count": 22,
-                "size": 1,
-                "status": "available",
-                "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
-                "is_incremental": True,
-                "has_dependent_backups": False
-            }
-        ]
-    }
-
-    def setUp(self):
-        super(TestBackupsClient, self).setUp()
-        fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.client = backups_client.BackupsClient(fake_auth,
-                                                   'volume',
-                                                   'regionOne')
-
-    def _test_list_backups(self, detail=False, mock_args='backups',
-                           bytes_body=False, **params):
-        if detail:
-            resp_body = self.FAKE_BACKUP_LIST_WITH_DETAIL
-        else:
-            resp_body = self.FAKE_BACKUP_LIST
-        self.check_service_client_function(
-            self.client.list_backups,
-            'tempest.lib.common.rest_client.RestClient.get',
-            resp_body,
-            to_utf=bytes_body,
-            mock_args=[mock_args],
-            detail=detail,
-            **params)
-
-    def test_list_backups_with_str_body(self):
-        self._test_list_backups()
-
-    def test_list_backups_with_bytes_body(self):
-        self._test_list_backups(bytes_body=True)
-
-    def test_list_backups_with_detail_with_str_body(self):
-        mock_args = "backups/detail"
-        self._test_list_backups(detail=True, mock_args=mock_args)
-
-    def test_list_backups_with_detail_with_bytes_body(self):
-        mock_args = "backups/detail"
-        self._test_list_backups(detail=True, mock_args=mock_args,
-                                bytes_body=True)
-
-    def test_list_backups_with_params(self):
-        # Run the test separately for each param, to avoid assertion error
-        # resulting from randomized params order.
-        mock_args = 'backups?sort_key=name'
-        self._test_list_backups(mock_args=mock_args, sort_key='name')
-
-        mock_args = 'backups/detail?limit=10'
-        self._test_list_backups(detail=True, mock_args=mock_args,
-                                bytes_body=True, limit=10)
diff --git a/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py b/tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
similarity index 96%
rename from tempest/tests/lib/services/volume/v2/test_availability_zone_client.py
rename to tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
index 770565c..4827326 100644
--- a/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import availability_zone_client
+from tempest.lib.services.volume.v3 import availability_zone_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v3/test_backups_client.py b/tempest/tests/lib/services/volume/v3/test_backups_client.py
index f1ce987..5412064 100644
--- a/tempest/tests/lib/services/volume/v3/test_backups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_backups_client.py
@@ -20,6 +20,55 @@
 
 class TestBackupsClient(base.BaseServiceTest):
 
+    FAKE_BACKUP_LIST = {
+        "backups": [
+            {
+                "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
+                "links": [
+                    {
+                        "href": "fake-url-1",
+                        "rel": "self"
+                    },
+                    {
+                        "href": "fake-url-2",
+                        "rel": "bookmark"
+                    }
+                ],
+                "name": "backup001"
+            }
+        ]
+    }
+
+    FAKE_BACKUP_LIST_WITH_DETAIL = {
+        "backups": [
+            {
+                "availability_zone": "az1",
+                "container": "volumebackups",
+                "created_at": "2013-04-02T10:35:27.000000",
+                "description": None,
+                "fail_reason": None,
+                "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
+                "links": [
+                    {
+                        "href": "fake-url-1",
+                        "rel": "self"
+                    },
+                    {
+                        "href": "fake-url-2",
+                        "rel": "bookmark"
+                    }
+                ],
+                "name": "backup001",
+                "object_count": 22,
+                "size": 1,
+                "status": "available",
+                "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
+                "is_incremental": True,
+                "has_dependent_backups": False
+            }
+        ]
+    }
+
     FAKE_BACKUP_UPDATE = {
         "backup": {
             "id": "4c65c15f-a5c5-464b-b92a-90e4c04636a7",
@@ -35,6 +84,46 @@
                                                    'volume',
                                                    'regionOne')
 
+    def _test_list_backups(self, detail=False, mock_args='backups',
+                           bytes_body=False, **params):
+        if detail:
+            resp_body = self.FAKE_BACKUP_LIST_WITH_DETAIL
+        else:
+            resp_body = self.FAKE_BACKUP_LIST
+        self.check_service_client_function(
+            self.client.list_backups,
+            'tempest.lib.common.rest_client.RestClient.get',
+            resp_body,
+            to_utf=bytes_body,
+            mock_args=[mock_args],
+            detail=detail,
+            **params)
+
+    def test_list_backups_with_str_body(self):
+        self._test_list_backups()
+
+    def test_list_backups_with_bytes_body(self):
+        self._test_list_backups(bytes_body=True)
+
+    def test_list_backups_with_detail_with_str_body(self):
+        mock_args = "backups/detail"
+        self._test_list_backups(detail=True, mock_args=mock_args)
+
+    def test_list_backups_with_detail_with_bytes_body(self):
+        mock_args = "backups/detail"
+        self._test_list_backups(detail=True, mock_args=mock_args,
+                                bytes_body=True)
+
+    def test_list_backups_with_params(self):
+        # Run the test separately for each param, to avoid assertion error
+        # resulting from randomized params order.
+        mock_args = 'backups?sort_key=name'
+        self._test_list_backups(mock_args=mock_args, sort_key='name')
+
+        mock_args = 'backups/detail?limit=10'
+        self._test_list_backups(detail=True, mock_args=mock_args,
+                                bytes_body=True, limit=10)
+
     def _test_update_backup(self, bytes_body=False):
         self.check_service_client_function(
             self.client.update_backup,
diff --git a/tempest/tests/lib/services/volume/v2/test_capabilities_client.py b/tempest/tests/lib/services/volume/v3/test_capabilities_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_capabilities_client.py
rename to tempest/tests/lib/services/volume/v3/test_capabilities_client.py
index 3d3f1e1..7efe1ff 100644
--- a/tempest/tests/lib/services/volume/v2/test_capabilities_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_capabilities_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import capabilities_client
+from tempest.lib.services.volume.v3 import capabilities_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
similarity index 98%
rename from tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
rename to tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
index 8de9fb4..c788181 100644
--- a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.lib.services.volume.v2 import encryption_types_client
+from tempest.lib.services.volume.v3 import encryption_types_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_extensions_client.py b/tempest/tests/lib/services/volume/v3/test_extensions_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_extensions_client.py
rename to tempest/tests/lib/services/volume/v3/test_extensions_client.py
index c0ee421..a8bbffd 100644
--- a/tempest/tests/lib/services/volume/v2/test_extensions_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_extensions_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import extensions_client
+from tempest.lib.services.volume.v3 import extensions_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_hosts_client.py b/tempest/tests/lib/services/volume/v3/test_hosts_client.py
similarity index 98%
rename from tempest/tests/lib/services/volume/v2/test_hosts_client.py
rename to tempest/tests/lib/services/volume/v3/test_hosts_client.py
index e107910..67ae4fd 100644
--- a/tempest/tests/lib/services/volume/v2/test_hosts_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_hosts_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import hosts_client
+from tempest.lib.services.volume.v3 import hosts_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_limits_client.py b/tempest/tests/lib/services/volume/v3/test_limits_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_limits_client.py
rename to tempest/tests/lib/services/volume/v3/test_limits_client.py
index 202054c..f94fbe1 100644
--- a/tempest/tests/lib/services/volume/v2/test_limits_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_limits_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import limits_client
+from tempest.lib.services.volume.v3 import limits_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py b/tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
rename to tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
index e715fcc..6190733 100644
--- a/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
@@ -15,7 +15,7 @@
 
 import copy
 
-from tempest.lib.services.volume.v2 import quota_classes_client
+from tempest.lib.services.volume.v3 import quota_classes_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_quotas_client.py b/tempest/tests/lib/services/volume/v3/test_quotas_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_quotas_client.py
rename to tempest/tests/lib/services/volume/v3/test_quotas_client.py
index 6384350..aa5d251 100644
--- a/tempest/tests/lib/services/volume/v2/test_quotas_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_quotas_client.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.lib.services.volume.v2 import quotas_client
+from tempest.lib.services.volume.v3 import quotas_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py b/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
rename to tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
index 8a5f25f..e0f5566 100644
--- a/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import scheduler_stats_client
+from tempest.lib.services.volume.v3 import scheduler_stats_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py b/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
similarity index 94%
rename from tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
rename to tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
index e03a8eb..1b88020 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
@@ -17,9 +17,7 @@
 
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.services.volume.v2 import snapshot_manage_client
-from tempest.lib.services.volume.v3 import snapshot_manage_client \
-    as snapshot_manage_clientv3
+from tempest.lib.services.volume.v3 import snapshot_manage_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
@@ -65,7 +63,7 @@
 
         # NOTE: Use sort_keys for json.dumps so that the expected and actual
         # payloads are guaranteed to be identical for mock_args assert check.
-        with mock.patch.object(snapshot_manage_clientv3.json,
+        with mock.patch.object(snapshot_manage_client.json,
                                'dumps') as mock_dumps:
             mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
 
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py b/tempest/tests/lib/services/volume/v3/test_snapshots_client.py
similarity index 99%
rename from tempest/tests/lib/services/volume/v2/test_snapshots_client.py
rename to tempest/tests/lib/services/volume/v3/test_snapshots_client.py
index c9f57a0..2efd2e6 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_snapshots_client.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.lib.services.volume.v2 import snapshots_client
+from tempest.lib.services.volume.v3 import snapshots_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_transfers_client.py b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
similarity index 95%
rename from tempest/tests/lib/services/volume/v2/test_transfers_client.py
rename to tempest/tests/lib/services/volume/v3/test_transfers_client.py
index 8e7c6f4..d631fe7 100644
--- a/tempest/tests/lib/services/volume/v2/test_transfers_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
@@ -18,9 +18,7 @@
 import mock
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.services.volume.v2 import transfers_client
-from tempest.lib.services.volume.v3 import transfers_client \
-    as transfers_clientv3
+from tempest.lib.services.volume.v3 import transfers_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
@@ -65,7 +63,7 @@
 
         # NOTE: Use sort_keys for json.dumps so that the expected and actual
         # payloads are guaranteed to be identical for mock_args assert check.
-        with mock.patch.object(transfers_clientv3.json, 'dumps') as mock_dumps:
+        with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps:
             mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
 
             self.check_service_client_function(
@@ -86,7 +84,7 @@
 
         # NOTE: Use sort_keys for json.dumps so that the expected and actual
         # payloads are guaranteed to be identical for mock_args assert check.
-        with mock.patch.object(transfers_clientv3.json, 'dumps') as mock_dumps:
+        with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps:
             mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
 
             self.check_service_client_function(
diff --git a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
similarity index 94%
rename from tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
rename to tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
index 0fb66bb..902f027 100644
--- a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
@@ -17,9 +17,7 @@
 
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.services.volume.v2 import volume_manage_client
-from tempest.lib.services.volume.v3 import volume_manage_client \
-    as volume_manage_clientv3
+from tempest.lib.services.volume.v3 import volume_manage_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
@@ -93,7 +91,7 @@
 
         # NOTE: Use sort_keys for json.dumps so that the expected and actual
         # payloads are guaranteed to be identical for mock_args assert check.
-        with mock.patch.object(volume_manage_clientv3.json,
+        with mock.patch.object(volume_manage_client.json,
                                'dumps') as mock_dumps:
             mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
 
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index bc3a753..9534ce8 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -193,3 +193,60 @@
             "raise TestCase.failureException(exception.message)"))), 1)
         self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3(
             "raise TestCase.failureException(ee.message)"))), 0)
+
+    def _test_no_negatve_test_attribute_applied_to_negative_test(
+            self, filename, with_other_decorators=False,
+            with_negative_decorator=True, expected_success=True):
+        check = checks.negative_test_attribute_always_applied_to_negative_tests
+        other_decorators = [
+            "@decorators.idempotent_id(123)",
+            "@utils.requires_ext(extension='ext', service='svc')"
+        ]
+
+        if with_other_decorators:
+            # Include multiple decorators to verify that this check works with
+            # arbitrarily many decorators. These insert decorators above the
+            # @decorators.attr(type=['negative']) decorator.
+            for decorator in other_decorators:
+                self.assertIsNone(check(" %s" % decorator, filename))
+        if with_negative_decorator:
+            self.assertIsNone(
+                check("@decorators.attr(type=['negative'])", filename))
+        if with_other_decorators:
+            # Include multiple decorators to verify that this check works with
+            # arbitrarily many decorators. These insert decorators between
+            # the test and the @decorators.attr(type=['negative']) decorator.
+            for decorator in other_decorators:
+                self.assertIsNone(check(" %s" % decorator, filename))
+        final_result = check(" def test_some_negative_case", filename)
+        if expected_success:
+            self.assertIsNone(final_result)
+        else:
+            self.assertIsInstance(final_result, tuple)
+            self.assertFalse(final_result[0])
+
+    def test_no_negatve_test_attribute_applied_to_negative_test(self):
+        # Check negative filename, negative decorator passes
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something_negative.py")
+        # Check negative filename, negative decorator, other decorators passes
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something_negative.py",
+            with_other_decorators=True)
+
+        # Check non-negative filename skips check, causing pass
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something.py")
+
+        # Check negative filename, no negative decorator fails
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something_negative.py",
+            with_negative_decorator=False,
+            expected_success=False)
+        # Check negative filename, no negative decorator, other decorators
+        # fails
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something_negative.py",
+            with_other_decorators=True,
+            with_negative_decorator=False,
+            expected_success=False)
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index 20c99b2..d2fd2aa 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -49,13 +49,37 @@
 
 sorted_plugins=$(python tools/generate-tempest-plugins-list.py)
 
+name_col_len=$(echo "${sorted_plugins}" | wc -L)
+name_col_len=$(( name_col_len + 2 ))
+
+# Print the title underline for a RST table.
+function title_underline {
+    printf "== "
+    local len=$1
+    while [[ $len -gt 0 ]]; do
+        printf "="
+        len=$(( len - 1))
+    done
+    printf " ===\n"
+}
+
+printf "\n\n"
+title_underline ${name_col_len}
+printf "%-3s %-${name_col_len}s %s\n" "SR" "Plugin Name" "URL"
+title_underline ${name_col_len}
+
+i=0
 for k in ${sorted_plugins}; do
+    i=$((i+1))
     project=${k:0:28}
     giturl="git://git.openstack.org/openstack/${k:0:26}"
-    printf "|%-28s|%-73s|\n" "${project}" "${giturl}"
-    printf "+----------------------------+-------------------------------------------------------------------------+\n"
+    printf "%-3s %-${name_col_len}s %s\n" "$i" "${project}" "${giturl}"
 done
 
+title_underline ${name_col_len}
+
+printf "\n\n"
+
 if [[ -r doc/source/data/tempest-plugins-registry.footer ]]; then
     cat doc/source/data/tempest-plugins-registry.footer
 fi