Merge "Move wait_for_backup_status function to common.waiters"
diff --git a/README.rst b/README.rst
index 3c0463b..f1dac1c 100644
--- a/README.rst
+++ b/README.rst
@@ -92,18 +92,18 @@
    be done using the :ref:`tempest_run` command. This can be done by either
    running::
 
-     $ tempest run
+    $ tempest run
 
    from the Tempest workspace directory. Or you can use the ``--workspace``
    argument to run in the workspace you created regarless of your current
    working directory. For example::
 
-     $ tempest run --workspace cloud-01
+    $ tempest run --workspace cloud-01
 
    There is also the option to use testr directly, or any `testr`_ based test
    runner, like `ostestr`_. For example, from the workspace dir run::
 
-     $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))'
+    $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))'
 
    will run the same set of tests as the default gate jobs.
 
@@ -161,9 +161,9 @@
 of the configuration.
 
 You can generate a new sample tempest.conf file, run the following
-command from the top level of the Tempest directory:
+command from the top level of the Tempest directory::
 
-  tox -egenconfig
+    $ tox -egenconfig
 
 The most important pieces that are needed are the user ids, openstack
 endpoint, and basic flavors and images needed to run tests.
@@ -257,11 +257,11 @@
 and run the tests or use tox to do the same. Tox also contains several existing
 job configurations. For example::
 
-   $ tox -efull
+    $ tox -efull
 
 which will run the same set of tests as the OpenStack gate. (it's exactly how
 the gate invokes Tempest) Or::
 
-  $ tox -esmoke
+    $ tox -esmoke
 
 to run the tests tagged as smoke.
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index fd9ad05..18269bf 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -95,7 +95,7 @@
 accounts will be assigned a role on domain configured in
 ``default_credentials_domain_name``. This will make the accounts provisioned
 usable in a cloud where domain scoped tokens are required by keystone for
-admin operations. Note that the the initial pre-provision admin accounts,
+admin operations. Note that the initial pre-provision admin accounts,
 configured in tempest.conf, must have a role on the same domain as well, for
 Dynamic Credentials to work.
 
@@ -151,7 +151,7 @@
 ``admin_domain_scope`` as ``default_credentials_domain_name`` are configured
 properly in tempest.conf.
 
-Pre-Provisioned Credentials are also know as accounts.yaml or accounts file.
+Pre-Provisioned Credentials are also known as accounts.yaml or accounts file.
 
 Compute
 -------
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index d34023f..285ad5d 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -213,7 +213,7 @@
 * **client_names**: Name of the classes that implement service clients in the
   service clients module.
 
-Example usage of the the service clients in tests::
+Example usage of the service clients in tests::
 
    # my_creds is instance of tempest.lib.auth.Credentials
    # identity_uri is v2 or v3 depending on the configuration
diff --git a/releasenotes/notes/add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml b/releasenotes/notes/add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
index 1ef2b0d..4886f16 100644
--- a/releasenotes/notes/add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
+++ b/releasenotes/notes/add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
@@ -7,6 +7,7 @@
     any maintenance changes.
 
     * encryption_types_client (v1)
+    * encryption_types_client (v2)
     * qos_clients (v1)
     * qos_clients (v2)
     * snapshots_client (v1)
diff --git a/releasenotes/notes/tempest-cleanup-nostandalone-39df2aafb2545d35.yaml b/releasenotes/notes/tempest-cleanup-nostandalone-39df2aafb2545d35.yaml
new file mode 100644
index 0000000..20f310d
--- /dev/null
+++ b/releasenotes/notes/tempest-cleanup-nostandalone-39df2aafb2545d35.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - the already depreacted tempest-cleanup standalone command has been
+    removed. The corresponding functionalities can be accessed through
+    the unified `tempest` command (`tempest cleanup`).
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 2c22408..0ec0e94 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -5,10 +5,10 @@
  .. toctree::
     :maxdepth: 1
 
+    unreleased
     v12.0.0
     v11.0.0
     v10.0.0
-    unreleased
 
 Indices and tables
 ==================
diff --git a/requirements.txt b/requirements.txt
index a773d16..4655b9f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,20 +6,21 @@
 jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
 testtools>=1.4.0 # MIT
 paramiko>=2.0 # LGPLv2.1+
-netaddr!=0.7.16,>=0.7.12 # BSD
+netaddr!=0.7.16,>=0.7.13 # BSD
 testrepository>=0.0.18 # Apache-2.0/BSD
 oslo.concurrency>=3.8.0 # Apache-2.0
 oslo.config>=3.14.0 # Apache-2.0
-oslo.i18n>=2.1.0 # Apache-2.0
-oslo.log>=1.14.0 # Apache-2.0
+oslo.log>=3.11.0 # Apache-2.0
 oslo.serialization>=1.10.0 # Apache-2.0
 oslo.utils>=3.16.0 # Apache-2.0
 six>=1.9.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
 testscenarios>=0.4 # Apache-2.0/BSD
 PyYAML>=3.1.0 # MIT
+python-subunit>=0.0.18 # Apache-2.0/BSD
 stevedore>=1.16.0 # Apache-2.0
 PrettyTable<0.8,>=0.7 # BSD
 os-testr>=0.7.0 # Apache-2.0
 urllib3>=1.15.1 # MIT
 debtcollector>=1.2.0 # Apache-2.0
+unittest2  # BSD
diff --git a/setup.cfg b/setup.cfg
index 50bf891..28e17ef 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -29,7 +29,6 @@
 console_scripts =
     verify-tempest-config = tempest.cmd.verify_tempest_config:main
     run-tempest-stress = tempest.cmd.run_stress:main
-    tempest-cleanup = tempest.cmd.cleanup:main
     tempest-account-generator = tempest.cmd.account_generator:main
     tempest = tempest.cmd.main:main
     skip-tracker = tempest.lib.cmd.skip_tracker:main
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 4f48ad0..61359f1 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -16,7 +16,6 @@
 
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
 from tempest import test
 
 LOG = log.getLogger(__name__)
@@ -30,24 +29,16 @@
         super(AgentsAdminTestJSON, cls).setup_clients()
         cls.client = cls.os_adm.agents_client
 
-    def setUp(self):
-        super(AgentsAdminTestJSON, self).setUp()
-        params = self._param_helper(
+    @classmethod
+    def resource_setup(cls):
+        super(AgentsAdminTestJSON, cls).resource_setup()
+        cls.params_agent = cls._param_helper(
             hypervisor='common', os='linux', architecture='x86_64',
             version='7.0', url='xxx://xxxx/xxx/xxx',
             md5hash='add6bb58e139be103324d04d82d8f545')
-        body = self.client.create_agent(**params)['agent']
-        self.agent_id = body['agent_id']
 
-    def tearDown(self):
-        try:
-            test_utils.call_and_ignore_notfound_exc(
-                self.client.delete_agent, self.agent_id)
-        except Exception:
-            LOG.exception('Exception raised deleting agent %s', self.agent_id)
-        super(AgentsAdminTestJSON, self).tearDown()
-
-    def _param_helper(self, **kwargs):
+    @staticmethod
+    def _param_helper(**kwargs):
         rand_key = 'architecture'
         if rand_key in kwargs:
             # NOTE: The rand_name is for avoiding agent conflicts.
@@ -71,33 +62,42 @@
 
     @test.idempotent_id('dc9ffd51-1c50-4f0e-a820-ae6d2a568a9e')
     def test_update_agent(self):
-        # Update an agent.
+        # Create and update an agent.
+        body = self.client.create_agent(**self.params_agent)['agent']
+        self.addCleanup(self.client.delete_agent, body['agent_id'])
+        agent_id = body['agent_id']
         params = self._param_helper(
             version='8.0', url='xxx://xxxx/xxx/xxx2',
             md5hash='add6bb58e139be103324d04d82d8f547')
-        body = self.client.update_agent(self.agent_id, **params)['agent']
+        body = self.client.update_agent(agent_id, **params)['agent']
         for expected_item, value in params.items():
             self.assertEqual(value, body[expected_item])
 
     @test.idempotent_id('470e0b89-386f-407b-91fd-819737d0b335')
     def test_delete_agent(self):
-        # Delete an agent.
-        self.client.delete_agent(self.agent_id)
+        # Create an agent and delete it.
+        body = self.client.create_agent(**self.params_agent)['agent']
+        self.client.delete_agent(body['agent_id'])
 
         # Verify the list doesn't contain the deleted agent.
         agents = self.client.list_agents()['agents']
-        self.assertNotIn(self.agent_id, map(lambda x: x['agent_id'], agents))
+        self.assertNotIn(body['agent_id'], map(lambda x: x['agent_id'],
+                                               agents))
 
     @test.idempotent_id('6a326c69-654b-438a-80a3-34bcc454e138')
     def test_list_agents(self):
-        # List all agents.
+        # Create an agent and  list all agents.
+        body = self.client.create_agent(**self.params_agent)['agent']
+        self.addCleanup(self.client.delete_agent, body['agent_id'])
         agents = self.client.list_agents()['agents']
         self.assertTrue(len(agents) > 0, 'Cannot get any agents.(%s)' % agents)
-        self.assertIn(self.agent_id, map(lambda x: x['agent_id'], agents))
+        self.assertIn(body['agent_id'], map(lambda x: x['agent_id'], agents))
 
     @test.idempotent_id('eabadde4-3cd7-4ec4-a4b5-5a936d2d4408')
     def test_list_agents_with_filter(self):
-        # List the agent builds by the filter.
+        # Create agents and list the agent builds by the filter.
+        body = self.client.create_agent(**self.params_agent)['agent']
+        self.addCleanup(self.client.delete_agent, body['agent_id'])
         params = self._param_helper(
             hypervisor='xen', os='linux', architecture='x86',
             version='7.0', url='xxx://xxxx/xxx/xxx1',
@@ -110,4 +110,5 @@
                   ['agents'])
         self.assertTrue(len(agents) > 0, 'Cannot get any agents.(%s)' % agents)
         self.assertIn(agent_id_xen, map(lambda x: x['agent_id'], agents))
-        self.assertNotIn(self.agent_id, map(lambda x: x['agent_id'], agents))
+        self.assertNotIn(body['agent_id'], map(lambda x: x['agent_id'],
+                                               agents))
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 95e7ef1..fde5622 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -161,6 +161,7 @@
         verify_flavor_response_extension(flavor)
 
         # Check if flavor is present in list
+        flag = False
         flavors = self.user_client.list_flavors(detail=True)['flavors']
         for flavor in flavors:
             if flavor['name'] == flavor_name:
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index 6113c04..62dbfe4 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -31,6 +31,8 @@
         super(MigrationsAdminTest, cls).setup_clients()
         cls.client = cls.os_adm.migrations_client
         cls.flavors_admin_client = cls.os_adm.flavors_client
+        cls.admin_hosts_client = cls.os_adm.hosts_client
+        cls.admin_servers_client = cls.os_adm.servers_client
 
     @test.idempotent_id('75c0b83d-72a0-4cf8-a153-631e83e7d53f')
     def test_list_migrations(self):
@@ -103,3 +105,28 @@
 
         server = self.servers_client.show_server(server['id'])['server']
         self.assertEqual(flavor['id'], server['flavor']['id'])
+
+    @test.idempotent_id('4bf0be52-3b6f-4746-9a27-3143636fe30d')
+    @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
+                          'Cold migration not available.')
+    def test_cold_migration(self):
+        if CONF.compute.min_compute_nodes < 2:
+            msg = "Less than 2 compute nodes, skipping multinode tests."
+            raise self.skipException(msg)
+
+        server = self.create_test_server(wait_until="ACTIVE")
+        src_host = self.admin_servers_client.show_server(
+            server['id'])['server']['OS-EXT-SRV-ATTR:host']
+
+        self.admin_servers_client.migrate_server(server['id'])
+
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'VERIFY_RESIZE')
+
+        self.servers_client.confirm_resize_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+        dst_host = self.admin_servers_client.show_server(
+            server['id'])['server']['OS-EXT-SRV-ATTR:host']
+
+        self.assertNotEqual(src_host, dst_host)
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index aabb40c..c9ffcca 100755
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -102,14 +102,18 @@
         params = {'tenant_id': tenant_id}
         body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
-        self.assertEqual([], servers)
+        servers_name = map(lambda x: x['name'], servers)
+        self.assertNotIn(self.s1_name, servers_name)
+        self.assertNotIn(self.s2_name, servers_name)
 
-        # List the admin tenant which has no servers
+        # List the admin tenant shouldn't get servers created by other tenants
         admin_tenant_id = self.client.tenant_id
         params = {'all_tenants': '', 'tenant_id': admin_tenant_id}
         body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
-        self.assertEqual([], servers)
+        servers_name = map(lambda x: x['name'], servers)
+        self.assertNotIn(self.s1_name, servers_name)
+        self.assertNotIn(self.s2_name, servers_name)
 
     @test.idempotent_id('86c7a8f7-50cf-43a9-9bac-5b985317134f')
     def test_list_servers_filter_by_exist_host(self):
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 7c12bf9..b936b23 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -47,22 +47,20 @@
     @classmethod
     def setup_clients(cls):
         super(AttachInterfacesTestJSON, cls).setup_clients()
-        cls.client = cls.os.interfaces_client
         cls.networks_client = cls.os.networks_client
         cls.subnets_client = cls.os.subnets_client
         cls.ports_client = cls.os.ports_client
-        cls.servers_client = cls.servers_client
 
     def wait_for_interface_status(self, server, port_id, status):
         """Waits for an interface to reach a given status."""
-        body = (self.client.show_interface(server, port_id)
+        body = (self.interfaces_client.show_interface(server, port_id)
                 ['interfaceAttachment'])
         interface_status = body['port_state']
         start = int(time.time())
 
         while(interface_status != status):
             time.sleep(self.build_interval)
-            body = (self.client.show_interface(server, port_id)
+            body = (self.interfaces_client.show_interface(server, port_id)
                     ['interfaceAttachment'])
             interface_status = body['port_state']
 
@@ -119,7 +117,7 @@
 
     def _create_server_get_interfaces(self):
         server = self.create_test_server(wait_until='ACTIVE')
-        ifs = (self.client.list_interfaces(server['id'])
+        ifs = (self.interfaces_client.list_interfaces(server['id'])
                ['interfaceAttachments'])
         body = self.wait_for_interface_status(
             server['id'], ifs[0]['port_id'], 'ACTIVE')
@@ -127,7 +125,7 @@
         return server, ifs
 
     def _test_create_interface(self, server):
-        iface = (self.client.create_interface(server['id'])
+        iface = (self.interfaces_client.create_interface(server['id'])
                  ['interfaceAttachment'])
         iface = self.wait_for_interface_status(
             server['id'], iface['port_id'], 'ACTIVE')
@@ -136,7 +134,7 @@
 
     def _test_create_interface_by_network_id(self, server, ifs):
         network_id = ifs[0]['net_id']
-        iface = self.client.create_interface(
+        iface = self.interfaces_client.create_interface(
             server['id'], net_id=network_id)['interfaceAttachment']
         iface = self.wait_for_interface_status(
             server['id'], iface['port_id'], 'ACTIVE')
@@ -148,7 +146,7 @@
         port = self.ports_client.create_port(network_id=network_id)
         port_id = port['port']['id']
         self.addCleanup(self.ports_client.delete_port, port_id)
-        iface = self.client.create_interface(
+        iface = self.interfaces_client.create_interface(
             server['id'], port_id=port_id)['interfaceAttachment']
         iface = self.wait_for_interface_status(
             server['id'], iface['port_id'], 'ACTIVE')
@@ -165,7 +163,7 @@
                                                     1)
 
         fixed_ips = [{'ip_address': ip_list[0]}]
-        iface = self.client.create_interface(
+        iface = self.interfaces_client.create_interface(
             server['id'], net_id=network_id,
             fixed_ips=fixed_ips)['interfaceAttachment']
         self.addCleanup(self.ports_client.delete_port, iface['port_id'])
@@ -176,7 +174,7 @@
 
     def _test_show_interface(self, server, ifs):
         iface = ifs[0]
-        _iface = self.client.show_interface(
+        _iface = self.interfaces_client.show_interface(
             server['id'], iface['port_id'])['interfaceAttachment']
         self._check_interface(iface, port_id=_iface['port_id'],
                               network_id=_iface['net_id'],
@@ -186,14 +184,14 @@
     def _test_delete_interface(self, server, ifs):
         # NOTE(danms): delete not the first or last, but one in the middle
         iface = ifs[1]
-        self.client.delete_interface(server['id'], iface['port_id'])
-        _ifs = (self.client.list_interfaces(server['id'])
+        self.interfaces_client.delete_interface(server['id'], iface['port_id'])
+        _ifs = (self.interfaces_client.list_interfaces(server['id'])
                 ['interfaceAttachments'])
         start = int(time.time())
 
         while len(ifs) == len(_ifs):
             time.sleep(self.build_interval)
-            _ifs = (self.client.list_interfaces(server['id'])
+            _ifs = (self.interfaces_client.list_interfaces(server['id'])
                     ['interfaceAttachments'])
             timed_out = int(time.time()) - start >= self.build_timeout
             if len(ifs) == len(_ifs) and timed_out:
@@ -239,7 +237,7 @@
         iface = self._test_create_interface_by_fixed_ips(server, ifs)
         ifs.append(iface)
 
-        _ifs = (self.client.list_interfaces(server['id'])
+        _ifs = (self.interfaces_client.list_interfaces(server['id'])
                 ['interfaceAttachments'])
         self._compare_iface_list(ifs, _ifs)
 
@@ -302,11 +300,11 @@
 
         for server in servers:
             # attach the port to the server
-            iface = self.client.create_interface(
+            iface = self.interfaces_client.create_interface(
                 server['id'], port_id=port_id)['interfaceAttachment']
             self._check_interface(iface, port_id=port_id)
 
             # detach the port from the server; this is a cast in the compute
             # API so we have to poll the port until the device_id is unset.
-            self.client.delete_interface(server['id'], port_id)
+            self.interfaces_client.delete_interface(server['id'], port_id)
             self.wait_for_port_detach(port_id)
diff --git a/tempest/api/compute/servers/test_multiple_create.py b/tempest/api/compute/servers/test_multiple_create.py
index eb1beb1..4dd26af 100644
--- a/tempest/api/compute/servers/test_multiple_create.py
+++ b/tempest/api/compute/servers/test_multiple_create.py
@@ -21,16 +21,10 @@
 class MultipleCreateTestJSON(base.BaseV2ComputeTest):
     _name = 'multiple-create-test'
 
-    def _generate_name(self):
-        return data_utils.rand_name(self._name)
-
-    def _create_multiple_servers(self, name=None, wait_until=None, **kwargs):
-        # NOTE: This is the right way to create_multiple servers and manage to
-        # get the created servers into the servers list to be cleaned up after
-        # all.
-        kwargs['name'] = name if name else self._generate_name()
-        if wait_until:
-            kwargs['wait_until'] = wait_until
+    def _create_multiple_servers(self, **kwargs):
+        # This is the right way to create_multiple servers and manage to get
+        # the created servers into the servers list to be cleaned up after all.
+        kwargs['name'] = kwargs.get('name', data_utils.rand_name(self._name))
         body = self.create_test_server(**kwargs)
 
         return body
diff --git a/tempest/api/compute/servers/test_multiple_create_negative.py b/tempest/api/compute/servers/test_multiple_create_negative.py
index e5b4f46..c4dbe23 100644
--- a/tempest/api/compute/servers/test_multiple_create_negative.py
+++ b/tempest/api/compute/servers/test_multiple_create_negative.py
@@ -22,13 +22,10 @@
 class MultipleCreateNegativeTestJSON(base.BaseV2ComputeTest):
     _name = 'multiple-create-test'
 
-    def _generate_name(self):
-        return data_utils.rand_name(self._name)
-
-    def _create_multiple_servers(self, name=None, wait_until=None, **kwargs):
+    def _create_multiple_servers(self, **kwargs):
         # This is the right way to create_multiple servers and manage to get
         # the created servers into the servers list to be cleaned up after all.
-        kwargs['name'] = kwargs.get('name', self._generate_name())
+        kwargs['name'] = kwargs.get('name', data_utils.rand_name(self._name))
         body = self.create_test_server(**kwargs)
 
         return body
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index da7085f..c48169f 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -165,7 +165,7 @@
     """Testing volume with shelved instance.
 
     This test checks the attaching and detaching volumes from
-    a shelved or shelved ofload instance.
+    a shelved or shelved offload instance.
     """
 
     min_microversion = '2.20'
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index 373d44b..955b6fb 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -147,3 +147,88 @@
         (self.inherited_roles_client.
          delete_inherited_role_from_group_on_project(
              self.project['id'], self.group['id'], src_role['id']))
+
+    @test.idempotent_id('3acf666e-5354-42ac-8e17-8b68893bcd36')
+    def test_inherit_assign_list_revoke_user_roles_on_domain(self):
+        # Create role
+        src_role = self.roles_client.create_role(
+            name=data_utils.rand_name('Role'))['role']
+        self.addCleanup(self.roles_client.delete_role, src_role['id'])
+
+        # Create a project hierarchy
+        leaf_project_name = data_utils.rand_name('project')
+        leaf_project = self.projects_client.create_project(
+            leaf_project_name, domain_id=self.domain['id'],
+            parent_id=self.project['id'])['project']
+        self.addCleanup(
+            self.projects_client.delete_project, leaf_project['id'])
+
+        # Assign role on domain
+        self.inherited_roles_client.create_inherited_role_on_domains_user(
+            self.domain['id'], self.user['id'], src_role['id'])
+
+        # List "effective" role assignments from user on the parent project
+        assignments = (
+            self.role_assignments.list_user_project_effective_assignments(
+                self.project['id'], self.user['id']))['role_assignments']
+        self.assertNotEmpty(assignments)
+
+        # List "effective" role assignments from user on the leaf project
+        assignments = (
+            self.role_assignments.list_user_project_effective_assignments(
+                leaf_project['id'], self.user['id']))['role_assignments']
+        self.assertNotEmpty(assignments)
+
+        # Revoke role from domain
+        self.inherited_roles_client.delete_inherited_role_from_user_on_domain(
+            self.domain['id'], self.user['id'], src_role['id'])
+
+        # List "effective" role assignments from user on the parent project
+        # should return an empty list
+        assignments = (
+            self.role_assignments.list_user_project_effective_assignments(
+                self.project['id'], self.user['id']))['role_assignments']
+        self.assertEmpty(assignments)
+
+        # List "effective" role assignments from user on the leaf project
+        # should return an empty list
+        assignments = (
+            self.role_assignments.list_user_project_effective_assignments(
+                leaf_project['id'], self.user['id']))['role_assignments']
+        self.assertEmpty(assignments)
+
+    @test.idempotent_id('9f02ccd9-9b57-46b4-8f77-dd5a736f3a06')
+    def test_inherit_assign_list_revoke_user_roles_on_project_tree(self):
+        # Create role
+        src_role = self.roles_client.create_role(
+            name=data_utils.rand_name('Role'))['role']
+        self.addCleanup(self.roles_client.delete_role, src_role['id'])
+
+        # Create a project hierarchy
+        leaf_project_name = data_utils.rand_name('project')
+        leaf_project = self.projects_client.create_project(
+            leaf_project_name, domain_id=self.domain['id'],
+            parent_id=self.project['id'])['project']
+        self.addCleanup(
+            self.projects_client.delete_project, leaf_project['id'])
+
+        # Assign role on parent project
+        self.inherited_roles_client.create_inherited_role_on_projects_user(
+            self.project['id'], self.user['id'], src_role['id'])
+
+        # List "effective" role assignments from user on the leaf project
+        assignments = (
+            self.role_assignments.list_user_project_effective_assignments(
+                leaf_project['id'], self.user['id']))['role_assignments']
+        self.assertNotEmpty(assignments)
+
+        # Revoke role from parent project
+        self.inherited_roles_client.delete_inherited_role_from_user_on_project(
+            self.project['id'], self.user['id'], src_role['id'])
+
+        # List "effective" role assignments from user on the leaf project
+        # should return an empty list
+        assignments = (
+            self.role_assignments.list_user_project_effective_assignments(
+                leaf_project['id'], self.user['id']))['role_assignments']
+        self.assertEmpty(assignments)
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index f5e4943..14bf4f8 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -182,6 +182,7 @@
         cls.creds_client = cls.os_adm.credentials_client
         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
         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/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 443e332..f363d34 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -250,6 +250,16 @@
         self.assertEqual(len(images_list), params['limit'],
                          "Failed to get images by limit")
 
+    @test.idempotent_id('e9a44b91-31c8-4b40-a332-e0a39ffb4dbb')
+    def test_list_image_param_owner(self):
+        # Test to get images by owner
+        image_id = self.created_images[0]
+        # Get image metadata
+        image = self.client.show_image(image_id)
+
+        params = {"owner": image['owner']}
+        self._list_by_param_value_and_assert(params)
+
     @test.idempotent_id('622b925c-479f-4736-860d-adeaf13bc371')
     def test_get_image_schema(self):
         # Test to get image schema
diff --git a/tempest/api/image/v2/test_images_metadefs_resource_types.py b/tempest/api/image/v2/test_images_metadefs_resource_types.py
index a5143a1..3dd432b 100644
--- a/tempest/api/image/v2/test_images_metadefs_resource_types.py
+++ b/tempest/api/image/v2/test_images_metadefs_resource_types.py
@@ -18,7 +18,7 @@
 
 
 class MetadataResourceTypesTest(base.BaseV2ImageTest):
-    """Test the Metadata definition ressource types basic functionality"""
+    """Test the Metadata definition resource types basic functionality"""
 
     @test.idempotent_id('6f358a4e-5ef0-11e6-a795-080027d0d606')
     def test_basic_meta_def_resource_type_association(self):
@@ -34,7 +34,7 @@
         # NOTE(raiesmh08): Here intentionally I have not added addcleanup
         # method for resource type dissociation because its a metadata add and
         # being cleaned as soon as namespace is cleaned at test case level.
-        # When namespace cleans, resource type associaion will automatically
+        # When namespace cleans, resource type association will automatically
         # clean without any error or dependency.
 
         # List resource type associations and validate creation
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index 77008ab..c1462db 100644
--- a/tempest/api/network/test_dhcp_ipv6.py
+++ b/tempest/api/network/test_dhcp_ipv6.py
@@ -30,7 +30,7 @@
 class NetworksTestDHCPv6(base.BaseNetworkTest):
     _ip_version = 6
 
-    """ Test DHCPv6 specific features using SLAAC, stateless and
+    """Test DHCPv6 specific features using SLAAC, stateless and
     stateful settings for subnets. Also it shall check dual-stack
     functionality (IPv4 + IPv6 together).
     The tests include:
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index caf7f14..e5972a9 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -16,6 +16,7 @@
 import socket
 
 import netaddr
+import testtools
 
 from tempest.api.network import base
 from tempest.api.network import base_security_groups as sec_base
@@ -308,11 +309,17 @@
             self.assertIn(security_group, port_show['security_groups'])
 
     @test.idempotent_id('58091b66-4ff4-4cc1-a549-05d60c7acd1a')
+    @testtools.skipUnless(
+        test.is_extension_enabled('security-group', 'network'),
+        'security-group extension not enabled.')
     def test_update_port_with_security_group_and_extra_attributes(self):
         self._update_port_with_security_groups(
             [data_utils.rand_name('secgroup')])
 
     @test.idempotent_id('edf6766d-3d40-4621-bc6e-2521a44c257d')
+    @testtools.skipUnless(
+        test.is_extension_enabled('security-group', 'network'),
+        'security-group extension not enabled.')
     def test_update_port_with_two_security_groups_and_extra_attributes(self):
         self._update_port_with_security_groups(
             [data_utils.rand_name('secgroup'),
@@ -337,6 +344,9 @@
 
     @test.attr(type='smoke')
     @test.idempotent_id('4179dcb9-1382-4ced-84fe-1b91c54f5735')
+    @testtools.skipUnless(
+        test.is_extension_enabled('security-group', 'network'),
+        'security-group extension not enabled.')
     def test_create_port_with_no_securitygroups(self):
         network = self.create_network()
         self.addCleanup(self.networks_client.delete_network, network['id'])
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index a0c0a5f..33e5852 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -79,7 +79,7 @@
         # headers is checked without custom matcher.
         #
         # As the expected response is 204 No Content, Content-Length presence
-        # is not checked here intensionally. According to RFC 7230 a server
+        # is not checked here intentionally. According to RFC 7230 a server
         # MUST NOT send the header in such responses. Thus, clients should not
         # depend on this header. However, the standard does not require them
         # to validate the server's behavior. We leverage that to not refuse
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 8522269..dbe8b4a 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -63,7 +63,8 @@
         # create container with metadata value
         container_name = data_utils.rand_name(name='TestContainer')
 
-        metadata = {'test-container-meta': 'Meta1'}
+        # metadata name using underscores should be converted to hyphens
+        metadata = {'test_container_meta': 'Meta1'}
         resp, _ = self.container_client.create_container(
             container_name,
             metadata=metadata)
@@ -74,7 +75,7 @@
             container_name)
         self.assertIn('x-container-meta-test-container-meta', resp)
         self.assertEqual(resp['x-container-meta-test-container-meta'],
-                         metadata['test-container-meta'])
+                         metadata['test_container_meta'])
 
     @test.idempotent_id('24d16451-1c0c-4e4f-b59c-9840a3aba40e')
     def test_create_container_with_remove_metadata_key(self):
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 919f695..8736f9a 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -954,10 +954,7 @@
         # When the file is not downloaded from Swift server, response does
         # not contain 'X-Timestamp' header. This is the special case, therefore
         # the existence of response headers is checked without custom matcher.
-        self.assertIn('content-type', resp)
-        self.assertIn('x-trans-id', resp)
         self.assertIn('date', resp)
-        self.assertIn('accept-ranges', resp)
         # Check only the format of common headers with custom matcher
         self.assertThat(resp, custom_matchers.AreAllWellFormatted())
 
diff --git a/tempest/api/volume/v3/admin/__init__.py b/tempest/api/volume/admin/v2/__init__.py
similarity index 100%
rename from tempest/api/volume/v3/admin/__init__.py
rename to tempest/api/volume/admin/v2/__init__.py
diff --git a/tempest/api/volume/admin/test_volume_pools.py b/tempest/api/volume/admin/v2/test_volume_pools.py
similarity index 100%
rename from tempest/api/volume/admin/test_volume_pools.py
rename to tempest/api/volume/admin/v2/test_volume_pools.py
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/v2/test_volume_type_access.py
similarity index 100%
rename from tempest/api/volume/admin/test_volume_type_access.py
rename to tempest/api/volume/admin/v2/test_volume_type_access.py
diff --git a/tempest/api/volume/admin/test_volumes_list.py b/tempest/api/volume/admin/v2/test_volumes_list.py
similarity index 100%
rename from tempest/api/volume/admin/test_volumes_list.py
rename to tempest/api/volume/admin/v2/test_volumes_list.py
diff --git a/tempest/api/volume/v3/admin/__init__.py b/tempest/api/volume/admin/v3/__init__.py
similarity index 100%
copy from tempest/api/volume/v3/admin/__init__.py
copy to tempest/api/volume/admin/v3/__init__.py
diff --git a/tempest/api/volume/v3/admin/test_user_messages.py b/tempest/api/volume/admin/v3/test_user_messages.py
similarity index 100%
rename from tempest/api/volume/v3/admin/test_user_messages.py
rename to tempest/api/volume/admin/v3/test_user_messages.py
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index e529538..ee1744d 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -26,11 +26,10 @@
         super(VolumesV2MetadataTest, cls).resource_setup()
         # Create a volume
         cls.volume = cls.create_volume()
-        cls.volume_id = cls.volume['id']
 
     def tearDown(self):
         # Update the metadata to {}
-        self.volumes_client.update_volume_metadata(self.volume_id, {})
+        self.volumes_client.update_volume_metadata(self.volume['id'], {})
         super(VolumesV2MetadataTest, self).tearDown()
 
     @test.idempotent_id('6f5b125b-f664-44bf-910f-751591fe5769')
@@ -41,17 +40,17 @@
                     "key3": "value3",
                     "key4": "<value&special_chars>"}
 
-        body = self.volumes_client.create_volume_metadata(self.volume_id,
+        body = self.volumes_client.create_volume_metadata(self.volume['id'],
                                                           metadata)['metadata']
         # Get the metadata of the volume
         body = self.volumes_client.show_volume_metadata(
-            self.volume_id)['metadata']
+            self.volume['id'])['metadata']
         self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
         # Delete one item metadata of the volume
         self.volumes_client.delete_volume_metadata_item(
-            self.volume_id, "key1")
+            self.volume['id'], "key1")
         body = self.volumes_client.show_volume_metadata(
-            self.volume_id)['metadata']
+            self.volume['id'])['metadata']
         self.assertNotIn("key1", body)
         del metadata["key1"]
         self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
@@ -68,17 +67,17 @@
 
         # Create metadata for the volume
         body = self.volumes_client.create_volume_metadata(
-            self.volume_id, metadata)['metadata']
+            self.volume['id'], metadata)['metadata']
         # Get the metadata of the volume
         body = self.volumes_client.show_volume_metadata(
-            self.volume_id)['metadata']
+            self.volume['id'])['metadata']
         self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
         # Update metadata
         body = self.volumes_client.update_volume_metadata(
-            self.volume_id, update)['metadata']
+            self.volume['id'], update)['metadata']
         # Get the metadata of the volume
         body = self.volumes_client.show_volume_metadata(
-            self.volume_id)['metadata']
+            self.volume['id'])['metadata']
         self.assertEqual(update, body)
 
     @test.idempotent_id('862261c5-8df4-475a-8c21-946e50e36a20')
@@ -93,14 +92,14 @@
                   "key3": "value3_update"}
         # Create metadata for the volume
         body = self.volumes_client.create_volume_metadata(
-            self.volume_id, metadata)['metadata']
+            self.volume['id'], metadata)['metadata']
         self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
         # Update metadata item
         body = self.volumes_client.update_volume_metadata_item(
-            self.volume_id, "key3", update_item)['meta']
+            self.volume['id'], "key3", update_item)['meta']
         # Get the metadata of the volume
         body = self.volumes_client.show_volume_metadata(
-            self.volume_id)['metadata']
+            self.volume['id'])['metadata']
         self.assertThat(body.items(), matchers.ContainsAll(expect.items()))
 
 
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 8f7996a..6be569c 100755
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -14,7 +14,6 @@
 from tempest.common.utils import data_utils
 from tempest.common import waiters
 from tempest import config
-from tempest.lib import decorators
 from tempest import test
 
 CONF = config.CONF
@@ -32,12 +31,8 @@
     def resource_setup(cls):
         super(VolumesV2SnapshotTestJSON, cls).resource_setup()
         cls.volume_origin = cls.create_volume()
-
         cls.name_field = cls.special_fields['name_field']
         cls.descrip_field = cls.special_fields['descrip_field']
-        # Create 2 snapshots
-        for _ in range(2):
-            cls.create_snapshot(cls.volume_origin['id'])
 
     def _detach(self, volume_id):
         """Detach volume."""
@@ -45,31 +40,6 @@
         waiters.wait_for_volume_status(self.volumes_client,
                                        volume_id, 'available')
 
-    def _list_by_param_values_and_assert(self, with_detail=False, **params):
-        """list or list_details with given params and validates result."""
-
-        if with_detail:
-            fetched_snap_list = self.snapshots_client.list_snapshots(
-                detail=True, **params)['snapshots']
-        else:
-            fetched_snap_list = self.snapshots_client.list_snapshots(
-                **params)['snapshots']
-
-        # Validating params of fetched snapshots
-        for snap in fetched_snap_list:
-            for key in params:
-                msg = "Failed to list snapshots %s by %s" % \
-                      ('details' if with_detail else '', key)
-                self.assertEqual(params[key], snap[key], msg)
-
-    def _list_snapshots_by_param_limit(self, limit, expected_elements):
-        """list snapshots by limit param"""
-        # Get snapshots list using limit parameter
-        fetched_snap_list = self.snapshots_client.list_snapshots(
-            limit=limit)['snapshots']
-        # Validating filtered snapshots length equals to expected_elements
-        self.assertEqual(expected_elements, len(fetched_snap_list))
-
     @test.idempotent_id('b467b54c-07a4-446d-a1cf-651dedcc3ff1')
     @test.services('compute')
     def test_snapshot_create_with_volume_in_use(self):
@@ -135,48 +105,6 @@
         # Delete the snapshot
         self.cleanup_snapshot(snapshot)
 
-    @test.idempotent_id('59f41f43-aebf-48a9-ab5d-d76340fab32b')
-    def test_snapshots_list_with_params(self):
-        """list snapshots with params."""
-        # Create a snapshot
-        display_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
-        params = {self.name_field: display_name}
-        snapshot = self.create_snapshot(self.volume_origin['id'], **params)
-        self.addCleanup(self.cleanup_snapshot, snapshot)
-
-        # Verify list snapshots by display_name filter
-        params = {self.name_field: snapshot[self.name_field]}
-        self._list_by_param_values_and_assert(**params)
-
-        # Verify list snapshots by status filter
-        params = {'status': 'available'}
-        self._list_by_param_values_and_assert(**params)
-
-        # Verify list snapshots by status and display name filter
-        params = {'status': 'available',
-                  self.name_field: snapshot[self.name_field]}
-        self._list_by_param_values_and_assert(**params)
-
-    @test.idempotent_id('220a1022-1fcd-4a74-a7bd-6b859156cda2')
-    def test_snapshots_list_details_with_params(self):
-        """list snapshot details with params."""
-        # Create a snapshot
-        display_name = data_utils.rand_name(self.__class__.__name__ + '-snap')
-        params = {self.name_field: display_name}
-        snapshot = self.create_snapshot(self.volume_origin['id'], **params)
-        self.addCleanup(self.cleanup_snapshot, snapshot)
-
-        # Verify list snapshot details by display_name filter
-        params = {self.name_field: snapshot[self.name_field]}
-        self._list_by_param_values_and_assert(with_detail=True, **params)
-        # Verify list snapshot details by status filter
-        params = {'status': 'available'}
-        self._list_by_param_values_and_assert(with_detail=True, **params)
-        # Verify list snapshot details by status and display name filter
-        params = {'status': 'available',
-                  self.name_field: snapshot[self.name_field]}
-        self._list_by_param_values_and_assert(with_detail=True, **params)
-
     @test.idempotent_id('677863d1-3142-456d-b6ac-9924f667a7f4')
     def test_volume_from_snapshot(self):
         # Creates a volume a snapshot passing a size different from the source
@@ -193,25 +121,6 @@
         self.assertEqual(volume['snapshot_id'], src_snap['id'])
         self.assertEqual(int(volume['size']), src_size + 1)
 
-    @test.idempotent_id('db4d8e0a-7a2e-41cc-a712-961f6844e896')
-    def test_snapshot_list_param_limit(self):
-        # List returns limited elements
-        self._list_snapshots_by_param_limit(limit=1, expected_elements=1)
-
-    @test.idempotent_id('a1427f61-420e-48a5-b6e3-0b394fa95400')
-    def test_snapshot_list_param_limit_equals_infinite(self):
-        # List returns all elements when request limit exceeded
-        # snapshots number
-        snap_list = self.snapshots_client.list_snapshots()['snapshots']
-        self._list_snapshots_by_param_limit(limit=100000,
-                                            expected_elements=len(snap_list))
-
-    @decorators.skip_because(bug='1540893')
-    @test.idempotent_id('e3b44b7f-ae87-45b5-8a8c-66110eb24d0a')
-    def test_snapshot_list_param_limit_equals_zero(self):
-        # List returns zero elements
-        self._list_snapshots_by_param_limit(limit=0, expected_elements=0)
-
     def cleanup_snapshot(self, snapshot):
         # Delete the snapshot
         self.snapshots_client.delete_snapshot(snapshot['id'])
diff --git a/tempest/api/volume/test_volumes_snapshots_list.py b/tempest/api/volume/test_volumes_snapshots_list.py
new file mode 100644
index 0000000..4416bef
--- /dev/null
+++ b/tempest/api/volume/test_volumes_snapshots_list.py
@@ -0,0 +1,116 @@
+#    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 import decorators
+from tempest import test
+
+CONF = config.CONF
+
+
+class VolumesV2SnapshotListTestJSON(base.BaseVolumeTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(VolumesV2SnapshotListTestJSON, cls).skip_checks()
+        if not CONF.volume_feature_enabled.snapshot:
+            raise cls.skipException("Cinder volume snapshots are disabled")
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumesV2SnapshotListTestJSON, cls).resource_setup()
+        cls.volume_origin = cls.create_volume()
+        cls.name_field = cls.special_fields['name_field']
+        # Create snapshots with params
+        for _ in range(2):
+            cls.snapshot = cls.create_snapshot(cls.volume_origin['id'])
+
+    def _list_by_param_values_and_assert(self, with_detail=False, **params):
+        """list or list_details with given params and validates result."""
+
+        fetched_snap_list = self.snapshots_client.list_snapshots(
+            detail=with_detail, **params)['snapshots']
+
+        # Validating params of fetched snapshots
+        for snap in fetched_snap_list:
+            for key in params:
+                msg = "Failed to list snapshots %s by %s" % \
+                      ('details' if with_detail else '', key)
+                self.assertEqual(params[key], snap[key], msg)
+
+    def _list_snapshots_by_param_limit(self, limit, expected_elements):
+        """list snapshots by limit param"""
+        # Get snapshots list using limit parameter
+        fetched_snap_list = self.snapshots_client.list_snapshots(
+            limit=limit)['snapshots']
+        # Validating filtered snapshots length equals to expected_elements
+        self.assertEqual(expected_elements, len(fetched_snap_list))
+
+    @test.idempotent_id('59f41f43-aebf-48a9-ab5d-d76340fab32b')
+    def test_snapshots_list_with_params(self):
+        """list snapshots with params."""
+        # Verify list snapshots by display_name filter
+        params = {self.name_field: self.snapshot[self.name_field]}
+        self._list_by_param_values_and_assert(**params)
+
+        # Verify list snapshots by status filter
+        params = {'status': 'available'}
+        self._list_by_param_values_and_assert(**params)
+
+        # Verify list snapshots by status and display name filter
+        params = {'status': 'available',
+                  self.name_field: self.snapshot[self.name_field]}
+        self._list_by_param_values_and_assert(**params)
+
+    @test.idempotent_id('220a1022-1fcd-4a74-a7bd-6b859156cda2')
+    def test_snapshots_list_details_with_params(self):
+        """list snapshot details with params."""
+        # Verify list snapshot details by display_name filter
+        params = {self.name_field: self.snapshot[self.name_field]}
+        self._list_by_param_values_and_assert(with_detail=True, **params)
+        # Verify list snapshot details by status filter
+        params = {'status': 'available'}
+        self._list_by_param_values_and_assert(with_detail=True, **params)
+        # Verify list snapshot details by status and display name filter
+        params = {'status': 'available',
+                  self.name_field: self.snapshot[self.name_field]}
+        self._list_by_param_values_and_assert(with_detail=True, **params)
+
+    @test.idempotent_id('db4d8e0a-7a2e-41cc-a712-961f6844e896')
+    def test_snapshot_list_param_limit(self):
+        # List returns limited elements
+        self._list_snapshots_by_param_limit(limit=1, expected_elements=1)
+
+    @test.idempotent_id('a1427f61-420e-48a5-b6e3-0b394fa95400')
+    def test_snapshot_list_param_limit_equals_infinite(self):
+        # List returns all elements when request limit exceeded
+        # snapshots number
+        snap_list = self.snapshots_client.list_snapshots()['snapshots']
+        self._list_snapshots_by_param_limit(limit=100000,
+                                            expected_elements=len(snap_list))
+
+    @decorators.skip_because(bug='1540893')
+    @test.idempotent_id('e3b44b7f-ae87-45b5-8a8c-66110eb24d0a')
+    def test_snapshot_list_param_limit_equals_zero(self):
+        # List returns zero elements
+        self._list_snapshots_by_param_limit(limit=0, expected_elements=0)
+
+    def cleanup_snapshot(self, snapshot):
+        # Delete the snapshot
+        self.snapshots_client.delete_snapshot(snapshot['id'])
+        self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
+        self.snapshots.remove(snapshot)
+
+
+class VolumesV1SnapshotLimitTestJSON(VolumesV2SnapshotListTestJSON):
+    _api_version = 1
diff --git a/tempest/clients.py b/tempest/clients.py
index 4c677f0..765a526 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -14,7 +14,9 @@
 #    under the License.
 
 import copy
+
 from oslo_log import log as logging
+
 from tempest.common import negative_rest_client
 from tempest import config
 from tempest import exceptions
@@ -115,7 +117,7 @@
                     configuration[service_for_config] = (
                         config.service_client_config(service_for_config))
             except lib_exc.UnknownServiceClient:
-                LOG.warn(
+                LOG.warning(
                     'Could not load configuration for service %s' % service)
 
         return configuration
@@ -248,6 +250,8 @@
                                                        **params_v3)
         self.inherited_roles_client = identity.v3.InheritedRolesClient(
             self.auth_provider, **params_v3)
+        self.role_assignments_client = identity.v3.RoleAssignmentsClient(
+            self.auth_provider, **params_v3)
         self.identity_services_v3_client = identity.v3.ServicesClient(
             self.auth_provider, **params_v3)
         self.policies_client = identity.v3.PoliciesClient(self.auth_provider,
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index ba1f1fa..baa36a2 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -93,7 +93,7 @@
             testr_conf_file.write(testr_conf)
 
     def get_configparser(self, conf_path):
-        config_parse = moves.configparser.SafeConfigParser()
+        config_parse = moves.configparser.ConfigParser()
         config_parse.optionxform = str
         # get any existing values if a config file already exists
         if os.path.isfile(conf_path):
@@ -173,10 +173,10 @@
         workspace_manager = workspace.WorkspaceManager(
             parsed_args.workspace_path)
         name = parsed_args.name or parsed_args.dir.split(os.path.sep)[-1]
-        workspace_manager.register_new_workspace(
-            name, parsed_args.dir, init=True)
         config_dir = parsed_args.config_dir or get_tempest_default_config_dir()
         if parsed_args.show_global_dir:
             print("Global config dir is located at: %s" % config_dir)
             sys.exit(0)
         self.create_working_dir(parsed_args.dir, config_dir)
+        workspace_manager.register_new_workspace(
+            name, parsed_args.dir, init=True)
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index fef836c..236953c 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -264,8 +264,8 @@
 
             run_thread = threading.Thread(target=run_argv_thread)
             run_thread.start()
-            returncodes['subunit-trace'] = subunit_trace.trace(subunit_r,
-                                                               sys.stdout)
+            returncodes['subunit-trace'] = subunit_trace.trace(
+                subunit_r, sys.stdout, post_fails=True, print_failures=True)
             run_thread.join()
             subunit_r.close()
             # python version of pipefail
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index c36c9be..b2e72c5 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -370,10 +370,9 @@
     replace = opts.replace_ext
     global CONF_PARSER
 
-    outfile = sys.stdout
     if update:
         conf_file = _get_config_file()
-        CONF_PARSER = moves.configparser.SafeConfigParser()
+        CONF_PARSER = moves.configparser.ConfigParser()
         CONF_PARSER.optionxform = str
         CONF_PARSER.readfp(conf_file)
 
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index 04c9645..b96b1c0 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -136,8 +136,6 @@
         email = data_utils.rand_name(root) + "@example.com"
         user = self.creds_client.create_user(
             username, user_password, project, email)
-        if 'user' in user:
-            user = user['user']
         role_assigned = False
         if admin:
             self.creds_client.assign_user_role(user, project, self.admin_role)
diff --git a/tempest/config.py b/tempest/config.py
index f6c89ae..b6fca7e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -339,6 +339,10 @@
     cfg.BoolOpt('suspend',
                 default=True,
                 help="Does the test environment support suspend/resume?"),
+    cfg.BoolOpt('cold_migration',
+                default=True,
+                help="Does the test environment support cold migration "
+                     "available?"),
     cfg.BoolOpt('live_migration',
                 default=True,
                 help="Does the test environment support live migration "
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index e2d6585..4123ae5 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -19,7 +19,7 @@
 
 
 PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
-                  'ironic', 'savanna', 'heat', 'sahara']
+                  'ironic', 'heat', 'sahara']
 
 PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
 TEST_DEFINITION = re.compile(r'^\s*def test.*')
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index be3aa49..1239ac5 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -69,7 +69,8 @@
         lines[line_no - 1] = ''.join(('{%s:s}' % patch_id, lines[line_no - 1]))
         self.source_files[filename] = self._quote('\n').join(lines)
 
-    def _save_changes(self, filename, source):
+    @staticmethod
+    def _save_changes(filename, source):
         print('%s fixed' % filename)
         with open(filename, 'w') as f:
             f.write(source)
diff --git a/tempest/services/volume/v2/json/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
similarity index 100%
rename from tempest/services/volume/v2/json/encryption_types_client.py
rename to tempest/lib/services/volume/v2/encryption_types_client.py
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 086b82d..43adfb1 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -36,7 +36,6 @@
         super(TestAggregatesBasicOps, cls).setup_clients()
         # Use admin client by default
         cls.manager = cls.admin_manager
-        super(TestAggregatesBasicOps, cls).resource_setup()
         cls.aggregates_client = cls.manager.aggregates_client
         cls.hosts_client = cls.manager.hosts_client
 
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 34a65cb..3ac6759 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -149,10 +149,18 @@
 
         # delete the floating IP, this should refresh the server addresses
         self.compute_floating_ips_client.delete_floating_ip(floating_ip['id'])
-        server = self.servers_client.show_server(server['id'])['server']
-        address = self._get_floating_ip_in_server_addresses(
-            floating_ip, server)
-        self.assertIsNone(
-            address,
-            "Floating IP '%s' should not be in server addresses: %s" %
-            (floating_ip['ip'], server['addresses']))
+
+        def is_floating_ip_detached_from_server():
+            server_info = self.servers_client.show_server(
+                server['id'])['server']
+            address = self._get_floating_ip_in_server_addresses(
+                floating_ip, server_info)
+            return (not address)
+
+        if not test_utils.call_until_true(
+            is_floating_ip_detached_from_server,
+            CONF.compute.build_timeout,
+            CONF.compute.build_interval):
+            msg = ("Floating IP '%s' should not be in server addresses: %s" %
+                   (floating_ip['ip'], server['addresses']))
+            raise exceptions.TimeoutException(msg)
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 519dbec..a295b6a 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -473,11 +473,11 @@
     def test_hotplug_nic(self):
         """Test hotplug network interface
 
-        1. create a new network, with no gateway (to prevent overwriting VM's
-            gateway)
-        2. connect VM to new network
-        3. set static ip and bring new nic up
-        4. check VM can ping new network dhcp port
+        1. Create a network and a VM.
+        2. Check connectivity to the VM via a public network.
+        3. Create a new network, with no gateway.
+        4. Bring up a new interface
+        5. check the VM reach the new network
 
         """
         self._setup_network_and_servers()
diff --git a/tempest/scenario/test_server_multinode.py b/tempest/scenario/test_server_multinode.py
index 0cf72c3..b323d2a 100644
--- a/tempest/scenario/test_server_multinode.py
+++ b/tempest/scenario/test_server_multinode.py
@@ -42,7 +42,6 @@
         # this is needed so that we can use the availability_zone:host
         # scheduler hint, which is admin_only by default
         cls.servers_client = cls.admin_manager.servers_client
-        super(TestServerMultinode, cls).resource_setup()
 
     @test.idempotent_id('9cecbe35-b9d4-48da-a37e-7ce70aa43d30')
     @test.attr(type='smoke')
diff --git a/tempest/services/identity/v3/__init__.py b/tempest/services/identity/v3/__init__.py
index 3f5c3d5..9b40b77 100644
--- a/tempest/services/identity/v3/__init__.py
+++ b/tempest/services/identity/v3/__init__.py
@@ -28,8 +28,11 @@
 from tempest.lib.services.identity.v3.trusts_client import TrustsClient
 from tempest.lib.services.identity.v3.users_client import UsersClient
 from tempest.services.identity.v3.json.domains_client import DomainsClient
+from tempest.services.identity.v3.json.role_assignments_client import \
+    RoleAssignmentsClient
 
 __all__ = ['CredentialsClient', 'EndPointsClient', 'GroupsClient',
            'IdentityClient', 'InheritedRolesClient', 'PoliciesClient',
-           'ProjectsClient', 'RegionsClient', 'RolesClient', 'ServicesClient',
-           'V3TokenClient', 'TrustsClient', 'UsersClient', 'DomainsClient', ]
+           'ProjectsClient', 'RegionsClient', 'RoleAssignmentsClient',
+           'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient',
+           'UsersClient', 'DomainsClient', ]
diff --git a/tempest/services/identity/v3/json/role_assignments_client.py b/tempest/services/identity/v3/json/role_assignments_client.py
new file mode 100644
index 0000000..9fd7736
--- /dev/null
+++ b/tempest/services/identity/v3/json/role_assignments_client.py
@@ -0,0 +1,31 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# 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 RoleAssignmentsClient(rest_client.RestClient):
+    api_version = "v3"
+
+    def list_user_project_effective_assignments(
+            self, project_id, user_id):
+        """List the effective role assignments for a user in a project."""
+        resp, body = self.get(
+            "role_assignments?scope.project.id=%s&user.id=%s&effective" %
+            (project_id, user_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/v1/json/admin/__init__.py b/tempest/services/volume/v1/json/admin/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/volume/v1/json/admin/__init__.py
+++ /dev/null
diff --git a/tempest/services/volume/v2/__init__.py b/tempest/services/volume/v2/__init__.py
index b63e6f2..fcc34f9 100644
--- a/tempest/services/volume/v2/__init__.py
+++ b/tempest/services/volume/v2/__init__.py
@@ -14,6 +14,8 @@
 
 from tempest.lib.services.volume.v2.availability_zone_client import \
     AvailabilityZoneClient
+from tempest.lib.services.volume.v2.encryption_types_client import \
+    EncryptionTypesClient
 from tempest.lib.services.volume.v2.extensions_client import ExtensionsClient
 from tempest.lib.services.volume.v2.hosts_client import HostsClient
 from tempest.lib.services.volume.v2.qos_client import QosSpecsClient
@@ -22,11 +24,9 @@
 from tempest.lib.services.volume.v2.snapshots_client import SnapshotsClient
 from tempest.lib.services.volume.v2.types_client import TypesClient
 from tempest.services.volume.v2.json.backups_client import BackupsClient
-from tempest.services.volume.v2.json.encryption_types_client import \
-    EncryptionTypesClient
 from tempest.services.volume.v2.json.volumes_client import VolumesClient
 
-__all__ = ['AvailabilityZoneClient', 'ExtensionsClient', 'HostsClient',
-           'QosSpecsClient', 'QuotasClient', 'ServicesClient',
-           'SnapshotsClient', 'TypesClient', 'BackupsClient',
-           'EncryptionTypesClient', 'VolumesClient', ]
+__all__ = ['AvailabilityZoneClient', 'EncryptionTypesClient',
+           'ExtensionsClient', 'HostsClient', 'QosSpecsClient',
+           'QuotasClient', 'ServicesClient', 'SnapshotsClient',
+           'TypesClient', 'BackupsClient', 'VolumesClient', ]
diff --git a/tempest/services/volume/v2/json/admin/__init__.py b/tempest/services/volume/v2/json/admin/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/volume/v2/json/admin/__init__.py
+++ /dev/null
diff --git a/tempest/tests/cmd/test_tempest_init.py b/tempest/tests/cmd/test_tempest_init.py
index 2844371..79510be 100644
--- a/tempest/tests/cmd/test_tempest_init.py
+++ b/tempest/tests/cmd/test_tempest_init.py
@@ -137,3 +137,18 @@
         self.assertTrue(os.path.isfile(fake_file_moved))
         self.assertTrue(os.path.isfile(local_conf_file))
         self.assertTrue(os.path.isfile(local_testr_conf))
+
+    def test_take_action_fails(self):
+        class ParsedArgs(object):
+            workspace_dir = self.useFixture(fixtures.TempDir()).path
+            workspace_path = os.path.join(workspace_dir, 'workspace.yaml')
+            name = 'test'
+            dir_base = self.useFixture(fixtures.TempDir()).path
+            dir = os.path.join(dir_base, 'foo', 'bar')
+            config_dir = self.useFixture(fixtures.TempDir()).path
+            show_global_dir = False
+        pa = ParsedArgs()
+        init_cmd = init.TempestInit(None, None)
+        self.assertRaises(OSError, init_cmd.take_action, pa)
+        # one more trying should be a same error not "workspace already exists"
+        self.assertRaises(OSError, init_cmd.take_action, pa)
diff --git a/test-requirements.txt b/test-requirements.txt
index 04c3d6d..567cf20 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -5,7 +5,7 @@
 # needed for doc build
 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
 python-subunit>=0.0.18 # Apache-2.0/BSD
-oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
+oslosphinx>=4.7.0 # Apache-2.0
 reno>=1.8.0 # Apache2
 mock>=2.0 # BSD
 coverage>=3.6 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index 49cc9fa..7096e60 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = pep8,py35,py34,py27
+envlist = pep8,py35,py34,py27,pip-check-reqs
 minversion = 2.3.1
 skipsdist = True
 
@@ -154,7 +154,18 @@
 # Skipped because of new hacking 0.9: H405
 ignore = E125,E123,E129
 show-source = True
-exclude = .git,.venv,.tox,dist,doc,openstack,*egg
+exclude = .git,.venv,.tox,dist,doc,*egg
 
 [testenv:releasenotes]
 commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+
+[testenv:pip-check-reqs]
+# Do not install test-requirements as that will pollute the virtualenv for
+# determining missing packages.
+# This also means that pip-check-reqs must be installed separately, outside
+# of the requirements.txt files
+deps = pip_check_reqs
+       -r{toxinidir}/requirements.txt
+commands=
+    pip-extra-reqs -d --ignore-file=tempest/tests/* tempest
+    pip-missing-reqs -d --ignore-file=tempest/tests/* tempest