Merge "Add missing replication_status in group schema"
diff --git a/.zuul.yaml b/.zuul.yaml
index 9c53ba9..5e3f33a 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -13,6 +13,8 @@
     roles: &base_roles
       - zuul: opendev.org/openstack/devstack
     vars: &base_vars
+      # TODO(gmann): Remove these test skip once nova bug #1882521 is solved
+      tempest_black_regex: "(tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume|tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON|tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached)"
       devstack_services:
         tempest: true
       devstack_local_conf:
@@ -146,7 +148,7 @@
 
 - job:
     name: tempest-full-parallel
-    parent: tempest-full
+    parent: tempest-full-py3
     voting: false
     branches:
       - master
@@ -159,8 +161,6 @@
       tox_envlist: full-parallel
       run_tempest_cleanup: true
       run_tempest_dry_cleanup: true
-      devstack_localrc:
-        USE_PYTHON3: True
 
 - job:
     name: tempest-full-py3
@@ -218,6 +218,7 @@
 - job:
     name: tempest-integrated-compute
     parent: devstack-tempest
+    nodeset: openstack-single-node-bionic
     branches: ^(?!stable/ocata).*$
     description: |
       This job runs integration tests for compute. This is
@@ -225,6 +226,7 @@
       and Glance related tests. This is meant to be run on Nova gate only.
     vars:
       tox_envlist: integrated-compute
+      tempest_black_regex: ""
       devstack_localrc:
         USE_PYTHON3: true
         FORCE_CONFIG_DRIVE: true
@@ -341,9 +343,28 @@
 - job:
     name: tempest-multinode-full
     parent: tempest-multinode-full-base
+    nodeset: openstack-two-node-focal
+    # This job runs on Focal from stable/victoria on.
+    branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train|ussuri)).*$
+    vars:
+      devstack_localrc:
+        USE_PYTHON3: False
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: False
+
+- job:
+    name: tempest-multinode-full
+    parent: tempest-multinode-full-base
     nodeset: openstack-two-node-bionic
-    # This job runs on Bionic from stable/stein on.
-    branches: ^(?!stable/(ocata|pike|queens|rocky)).*$
+    # This job runs on Bionic and on python2. This is for stable/stein and stable/train.
+    # This job is prepared to make sure all stable branches from stable/stein till stable/train
+    # will keep running on bionic. This can be removed once stable/train is EOL.
+    branches:
+      - stable/stein
+      - stable/train
+      - stable/ussuri
     vars:
       devstack_localrc:
         USE_PYTHON3: False
@@ -454,16 +475,19 @@
 - job:
     name: tempest-full-ussuri-py3
     parent: tempest-full-py3
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
 
 - job:
     name: tempest-full-train-py3
     parent: tempest-full-py3
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
 
 - job:
     name: tempest-full-stein-py3
     parent: tempest-full-py3
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/stein
 
 - job:
@@ -471,7 +495,7 @@
     parent: tox
     description: |
       Run tempest plugin sanity check script using tox.
-    nodeset: ubuntu-bionic
+    nodeset: ubuntu-focal
     vars:
       tox_envlist: plugin-sanity-check
     timeout: 5000
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 2a3edf4..59a2f64 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -24,6 +24,7 @@
 
 import os
 import subprocess
+import sys
 
 # Build the plugin registry
 def build_plugin_registry(app):
@@ -31,16 +32,20 @@
         os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
     subprocess.call(['tools/generate-tempest-plugins-list.sh'], cwd=root_dir)
 
+def autodoc_skip_member_handler(app, what, name, obj, skip, options):
+    return skip or (what == "class" and not name.startswith("test"))
+
 def setup(app):
+    app.connect('autodoc-skip-member', autodoc_skip_member_handler)
     if os.getenv('GENERATE_TEMPEST_PLUGIN_LIST', 'true').lower() == 'true':
         app.connect('builder-inited', build_plugin_registry)
 
-
-
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+# sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('../../tempest'))
+sys.path.insert(0, os.path.abspath('../../tempest/api'))
 
 # -- General configuration -----------------------------------------------------
 
@@ -204,3 +209,10 @@
     ('index', 'doc-tempest.tex', u'Tempest Testing Project',
      u'OpenStack Foundation', 'manual'),
 ]
+
+latex_use_xindy = False
+
+latex_elements = {
+    'maxlistdepth': 20,
+    'printindex': '\\footnotesize\\raggedright\\printindex'
+}
diff --git a/doc/source/index.rst b/doc/source/index.rst
index d4dc166..66e68ea 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -56,6 +56,13 @@
 
    supported_version
 
+Description of Tests
+--------------------
+.. toctree::
+   :maxdepth: 2
+
+   tests/modules
+
 For Contributors
 ================
 
diff --git a/doc/source/tests/modules.rst b/doc/source/tests/modules.rst
new file mode 100644
index 0000000..026a7a5
--- /dev/null
+++ b/doc/source/tests/modules.rst
@@ -0,0 +1,21 @@
+Description of Tests
+====================
+
+OpenStack Services Integration Tests
+------------------------------------
+.. toctree::
+   :maxdepth: 2
+
+   scenario/modules
+
+OpenStack Services API Tests
+----------------------------
+.. toctree::
+   :maxdepth: 2
+
+   compute/modules
+   identity/modules
+   image/modules
+   network/modules
+   object_storage/modules
+   volume/modules
diff --git a/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml b/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml
new file mode 100644
index 0000000..2b407ae
--- /dev/null
+++ b/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Added missing client methods for keystone's OS-ENDPOINT-POLICY and
+    OS-EP-FILTER APIs.
diff --git a/releasenotes/notes/add-keystone-v3-ec2-tests-d959b7d36f0bd7fc.yaml b/releasenotes/notes/add-keystone-v3-ec2-tests-d959b7d36f0bd7fc.yaml
new file mode 100644
index 0000000..ab8d748
--- /dev/null
+++ b/releasenotes/notes/add-keystone-v3-ec2-tests-d959b7d36f0bd7fc.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Added missing clients and tests for keystone's v3 EC2 API which already
+    existed for keystone v2.
diff --git a/releasenotes/notes/new-placement-client-methods-e35c473e29494928.yaml b/releasenotes/notes/new-placement-client-methods-e35c473e29494928.yaml
new file mode 100644
index 0000000..9e6d49a
--- /dev/null
+++ b/releasenotes/notes/new-placement-client-methods-e35c473e29494928.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    Add ``placement`` API methods for testing Routed Provider Networks feature.
+    The following API calls are available for tempest from now in the new
+    resource_providers_client:
+
+    * GET /resource_providers
+    * GET /resource_providers/{uuid}
+    * GET /resource_providers/{uuid}/inventories
+    * GET /resource_providers/{uuid}/aggregates
diff --git a/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml b/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml
new file mode 100644
index 0000000..574f6d9
--- /dev/null
+++ b/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml
@@ -0,0 +1,17 @@
+---
+prelude: |
+    This release is to tag the Tempest for OpenStack Victoria release.
+    This release marks the start of Victoria release support in Tempest.
+    After this release, Tempest will support below OpenStack Releases:
+
+    * Victoria
+    * Ussuri
+    * Train
+    * Stein
+
+    Current development of Tempest is for OpenStack Wallaby development
+    cycle. Every Tempest commit is also tested against master during
+    the Wallaby cycle. However, this does not necessarily mean that using
+    Tempest as of this tag will work against a Victoria (or future release)
+    cloud.
+    To be on safe side, use this tag to test the OpenStack Victoria release.
diff --git a/releasenotes/notes/xenapi_apis-conf-fcca549283e53ed6.yaml b/releasenotes/notes/xenapi_apis-conf-fcca549283e53ed6.yaml
new file mode 100644
index 0000000..4d26210
--- /dev/null
+++ b/releasenotes/notes/xenapi_apis-conf-fcca549283e53ed6.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+  - |
+    A number of Compute APIs that only worked with the XenAPI virt driver have
+    been removed in the Compute service. As a result, their corresponding tests
+    are now disabled by default. They can be re-enabled using the new
+    ``[compute_feature_enabled] xenapi_apis`` config option.
diff --git a/requirements.txt b/requirements.txt
index d8738f0..abc0bce 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,7 @@
 cliff!=2.9.0,>=2.8.0 # Apache-2.0
 jsonschema>=3.2.0 # MIT
 testtools>=2.2.0 # MIT
-paramiko>=2.0.0 # LGPLv2.1+
+paramiko>=2.7.0 # LGPLv2.1+
 netaddr>=0.7.18 # BSD
 oslo.concurrency>=3.26.0 # Apache-2.0
 oslo.config>=5.2.0 # Apache-2.0
diff --git a/roles/process-stackviz/README.rst b/roles/process-stackviz/README.rst
deleted file mode 100644
index a8447d2..0000000
--- a/roles/process-stackviz/README.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-Generate stackviz report.
-
-Generate stackviz report using subunit and dstat data, using
-the stackviz archive embedded in test images.
-
-**Role Variables**
-
-.. zuul:rolevar:: devstack_base_dir
-   :default: /opt/stack
-
-   The devstack base directory.
-
-.. zuul:rolevar:: stage_dir
-   :default: "{{ ansible_user_dir }}"
-
-   The stage directory where the input data can be found and
-   the output will be produced.
-
-.. zuul:rolevar:: zuul_work_dir
-   :default: {{ devstack_base_dir }}/tempest
-
-   Directory to work in. It has to be a fully qualified path.
diff --git a/roles/process-stackviz/defaults/main.yaml b/roles/process-stackviz/defaults/main.yaml
deleted file mode 100644
index f3bc32b..0000000
--- a/roles/process-stackviz/defaults/main.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-devstack_base_dir: /opt/stack
-stage_dir: "{{ ansible_user_dir }}"
-zuul_work_dir: "{{ devstack_base_dir }}/tempest"
diff --git a/roles/process-stackviz/tasks/main.yaml b/roles/process-stackviz/tasks/main.yaml
deleted file mode 100644
index e3a0a0e..0000000
--- a/roles/process-stackviz/tasks/main.yaml
+++ /dev/null
@@ -1,70 +0,0 @@
-- name: Check if stackviz archive exists
-  stat:
-    path: "/opt/cache/files/stackviz-latest.tar.gz"
-  register: stackviz_archive
-
-- debug:
-    msg: "Stackviz archive could not be found in /opt/cache/files/stackviz-latest.tar.gz"
-  when: not stackviz_archive.stat.exists
-
-- name: Check if subunit data exists
-  stat:
-    path: "{{ zuul_work_dir }}/testrepository.subunit"
-  register: subunit_input
-
-- debug:
-    msg: "Subunit file could not be found at {{ zuul_work_dir }}/testrepository.subunit"
-  when: not subunit_input.stat.exists
-
-- name: Install stackviz
-  when:
-    - stackviz_archive.stat.exists
-    - subunit_input.stat.exists
-  block:
-    - include_role:
-        name: ensure-pip
-
-    - pip:
-        name: "file://{{ stackviz_archive.stat.path }}"
-        virtualenv: /tmp/stackviz
-        virtualenv_command: '{{ ensure_pip_virtualenv_command }}'
-        extra_args: -U
-
-- name: Deploy stackviz static html+js
-  command: cp -pR /tmp/stackviz/share/stackviz-html {{ stage_dir }}/stackviz
-  when:
-    - stackviz_archive.stat.exists
-    - subunit_input.stat.exists
-
-- name: Check if dstat data exists
-  stat:
-    path: "{{ devstack_base_dir }}/logs/dstat-csv.log"
-  register: dstat_input
-  when:
-    - stackviz_archive.stat.exists
-    - subunit_input.stat.exists
-
-- name: Run stackviz with dstat
-  shell: |
-    cat {{ subunit_input.stat.path }} | \
-      /tmp/stackviz/bin/stackviz-export \
-        --dstat "{{ devstack_base_dir }}/logs/dstat-csv.log" \
-        --env --stdin \
-        {{ stage_dir }}/stackviz/data
-  when:
-    - stackviz_archive.stat.exists
-    - subunit_input.stat.exists
-    - dstat_input.stat.exists
-  failed_when: False
-
-- name: Run stackviz without dstat
-  shell: |
-    cat {{ subunit_input.stat.path }} | \
-      /tmp/stackviz/bin/stackviz-export \
-        --env --stdin \
-        {{ stage_dir }}/stackviz/data
-  when:
-    - stackviz_archive.stat.exists
-    - subunit_input.stat.exists
-    - not dstat_input.stat.exists
-  failed_when: False
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 0901374..4cc5fdd 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -13,12 +13,22 @@
 #    under the License.
 
 from tempest.api.compute import base
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
+CONF = config.CONF
 
+
+# TODO(stephenfin): Remove these tests once the nova Ussuri branch goes EOL
 class AgentsAdminTestJSON(base.BaseV2ComputeAdminTest):
-    """Tests Agents API"""
+    """Tests Compute Agents API"""
+
+    @classmethod
+    def skip_checks(cls):
+        super(AgentsAdminTestJSON, cls).skip_checks()
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise cls.skipException('The os-agents API is not supported.')
 
     @classmethod
     def setup_clients(cls):
@@ -46,7 +56,7 @@
 
     @decorators.idempotent_id('1fc6bdc8-0b6d-4cc7-9f30-9b04fabe5b90')
     def test_create_agent(self):
-        # Create an agent.
+        """Test creating a compute agent"""
         params = self._param_helper(
             hypervisor='kvm', os='win', architecture='x86',
             version='7.0', url='xxx://xxxx/xxx/xxx',
@@ -58,6 +68,7 @@
 
     @decorators.idempotent_id('dc9ffd51-1c50-4f0e-a820-ae6d2a568a9e')
     def test_update_agent(self):
+        """Test updating a compute agent"""
         # Create and update an agent.
         body = self.client.create_agent(**self.params_agent)['agent']
         self.addCleanup(self.client.delete_agent, body['agent_id'])
@@ -71,7 +82,7 @@
 
     @decorators.idempotent_id('470e0b89-386f-407b-91fd-819737d0b335')
     def test_delete_agent(self):
-        # Create an agent and delete it.
+        """Test deleting a compute agent"""
         body = self.client.create_agent(**self.params_agent)['agent']
         self.client.delete_agent(body['agent_id'])
 
@@ -82,7 +93,7 @@
 
     @decorators.idempotent_id('6a326c69-654b-438a-80a3-34bcc454e138')
     def test_list_agents(self):
-        # Create an agent and  list all agents.
+        """Test listing compute 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']
@@ -91,7 +102,7 @@
 
     @decorators.idempotent_id('eabadde4-3cd7-4ec4-a4b5-5a936d2d4408')
     def test_list_agents_with_filter(self):
-        # Create agents and list the agent builds by the filter.
+        """Test listing compute agents 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(
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 45ca10a..ac09cb0 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -46,7 +46,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('0621c53e-d45d-40e7-951d-43e5e257b272')
     def test_flavor_access_list_with_public_flavor(self):
-        # Test to list flavor access with exceptions by querying public flavor
+        """Test listing flavor access of a public flavor should fail"""
         flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
                                     disk=self.disk, is_public='True')
         self.assertRaises(lib_exc.NotFound,
@@ -56,7 +56,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('41eaaade-6d37-4f28-9c74-f21b46ca67bd')
     def test_flavor_non_admin_add(self):
-        # Test to add flavor access as a user without admin privileges.
+        """Test adding flavor access by a non-admin user is forbidden"""
         flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
                                     disk=self.disk, is_public='False')
         self.assertRaises(lib_exc.Forbidden,
@@ -67,7 +67,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('073e79a6-c311-4525-82dc-6083d919cb3a')
     def test_flavor_non_admin_remove(self):
-        # Test to remove flavor access as a user without admin privileges.
+        """Test removing flavor access by a non-admin user should fail"""
         flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
                                     disk=self.disk, is_public='False')
 
@@ -84,6 +84,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('f3592cc0-0306-483c-b210-9a7b5346eddc')
     def test_add_flavor_access_duplicate(self):
+        """Test adding duplicate flavor access to same flavor should fail"""
         # Create a new flavor.
         flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
                                     disk=self.disk, is_public='False')
@@ -104,6 +105,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('1f710927-3bc7-4381-9f82-0ca6e42644b7')
     def test_remove_flavor_access_not_found(self):
+        """Test removing non existent flavor access should fail"""
         # Create a new flavor.
         flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus,
                                     disk=self.disk, is_public='False')
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log.py b/tempest/api/compute/admin/test_instance_usage_audit_log.py
index 1b62249..4dcbb3b 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log.py
@@ -22,6 +22,7 @@
 
 
 class InstanceUsageAuditLogTestJSON(base.BaseV2ComputeAdminTest):
+    """Test instance usage audit logs API"""
 
     @classmethod
     def setup_clients(cls):
@@ -30,12 +31,12 @@
 
     @decorators.idempotent_id('25319919-33d9-424f-9f99-2c203ee48b9d')
     def test_list_instance_usage_audit_logs(self):
-        # list instance usage audit logs
+        """Test listing instance usage audit logs"""
         self.adm_client.list_instance_usage_audit_logs()
 
     @decorators.idempotent_id('6e40459d-7c5f-400b-9e83-449fbc8e7feb')
     def test_get_instance_usage_audit_log(self):
-        # Get instance usage audit log before specified time
+        """Test getting instance usage audit log before specified time"""
         now = datetime.datetime.now()
         self.adm_client.show_instance_usage_audit_log(
             urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S")))
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
index de8e221..84d18c4 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
@@ -23,6 +23,7 @@
 
 
 class InstanceUsageAuditLogNegativeTestJSON(base.BaseV2ComputeAdminTest):
+    """Negative tests of instance usage audit logs"""
 
     @classmethod
     def setup_clients(cls):
@@ -32,7 +33,10 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('a9d33178-d2c9-4131-ad3b-f4ca8d0308a2')
     def test_instance_usage_audit_logs_with_nonadmin_user(self):
-        # the instance_usage_audit_logs API just can be accessed by admin user
+        """Test list/show instance usage audit logs by non-admin should fail
+
+        The instance_usage_audit_logs API just can be accessed by admin user.
+        """
         self.assertRaises(lib_exc.Forbidden,
                           self.instance_usages_audit_log_client.
                           list_instance_usage_audit_logs)
@@ -45,6 +49,10 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('9b952047-3641-41c7-ba91-a809fc5974c8')
     def test_get_instance_usage_audit_logs_with_invalid_time(self):
+        """Test showing instance usage audit logs with invalid time
+
+        Showing instance usage audit logs with invalid time should fail.
+        """
         self.assertRaises(lib_exc.BadRequest,
                           self.adm_client.show_instance_usage_audit_log,
                           "invalid_time")
diff --git a/tempest/api/compute/admin/test_live_migration_negative.py b/tempest/api/compute/admin/test_live_migration_negative.py
index 8327a3b..80c0525 100644
--- a/tempest/api/compute/admin/test_live_migration_negative.py
+++ b/tempest/api/compute/admin/test_live_migration_negative.py
@@ -24,6 +24,8 @@
 
 
 class LiveMigrationNegativeTest(base.BaseV2ComputeAdminTest):
+    """Negative tests of live migration"""
+
     @classmethod
     def skip_checks(cls):
         super(LiveMigrationNegativeTest, cls).skip_checks()
@@ -40,7 +42,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('7fb7856e-ae92-44c9-861a-af62d7830bcb')
     def test_invalid_host_for_migration(self):
-        # Migrating to an invalid host should not change the status
+        """Test migrating to an invalid host should not change the status"""
         target_host = data_utils.rand_name('host')
         server = self.create_test_server(wait_until="ACTIVE")
 
@@ -52,6 +54,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('6e2f94f5-2ee8-4830-bef5-5bc95bb0795b')
     def test_live_block_migration_suspended(self):
+        """Test migrating a suspended server should not change the status"""
         server = self.create_test_server(wait_until="ACTIVE")
 
         self.admin_servers_client.suspend_server(server['id'])
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index 37f5aec..89152d6 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -94,6 +94,16 @@
         # Now boot a server with the copied flavor.
         server = self.create_test_server(
             wait_until='ACTIVE', flavor=flavor['id'])
+        server = self.servers_client.show_server(server['id'])['server']
+
+        # If 'id' not in server['flavor'], we can only compare the flavor
+        # details, so here we should save the to-be-deleted flavor's details,
+        # for the flavor comparison after the server resizing.
+        if not server['flavor'].get('id'):
+            pre_flavor = {}
+            body = self.flavors_client.show_flavor(flavor['id'])['flavor']
+            for key in ['name', 'ram', 'vcpus', 'disk']:
+                pre_flavor[key] = body[key]
 
         # Delete the flavor we used to boot the instance.
         self._flavor_clean_up(flavor['id'])
@@ -110,7 +120,18 @@
                                        'ACTIVE')
 
         server = self.servers_client.show_server(server['id'])['server']
-        self.assert_flavor_equal(flavor['id'], server['flavor'])
+        if server['flavor'].get('id'):
+            msg = ('server flavor is not same as flavor!')
+            self.assertEqual(flavor['id'], server['flavor']['id'], msg)
+        else:
+            self.assertEqual(pre_flavor['name'],
+                             server['flavor']['original_name'],
+                             "original_name in server flavor is not same as "
+                             "flavor name!")
+            for key in ['ram', 'vcpus', 'disk']:
+                msg = ('attribute %s in server flavor is not same as '
+                       'flavor!' % key)
+                self.assertEqual(pre_flavor[key], server['flavor'][key], msg)
 
     def _test_cold_migrate_server(self, revert=False):
         if CONF.compute.min_compute_nodes < 2:
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 0060ffe..9d5e0c9 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -97,9 +97,11 @@
 
 
 class QuotasAdminTestJSON(QuotasAdminTestBase):
+    """Test compute quotas by admin user"""
+
     @decorators.idempotent_id('3b0a7c8f-cf58-46b8-a60c-715a32a8ba7d')
     def test_get_default_quotas(self):
-        # Admin can get the default resource quota set for a tenant
+        """Test admin can get the default compute quota set for a project"""
         expected_quota_set = self.default_quota_set | set(['id'])
         quota_set = self.adm_client.show_default_quota_set(
             self.demo_tenant_id)['quota_set']
@@ -109,7 +111,7 @@
 
     @decorators.idempotent_id('55fbe2bf-21a9-435b-bbd2-4162b0ed799a')
     def test_update_all_quota_resources_for_tenant(self):
-        # Admin can update all the resource quota limits for a tenant
+        """Test admin can update all the compute quota limits for a project"""
         default_quota_set = self.adm_client.show_default_quota_set(
             self.demo_tenant_id)['quota_set']
         new_quota_set = {'metadata_items': 256, 'ram': 10240,
@@ -140,11 +142,12 @@
     # TODO(afazekas): merge these test cases
     @decorators.idempotent_id('ce9e0815-8091-4abd-8345-7fe5b85faa1d')
     def test_get_updated_quotas(self):
+        """Test that GET shows the updated quota set of project"""
         self._get_updated_quotas()
 
     @decorators.idempotent_id('389d04f0-3a41-405f-9317-e5f86e3c44f0')
     def test_delete_quota(self):
-        # Admin can delete the resource quota set for a project
+        """Test admin can delete the compute quota set for a project"""
         project_name = data_utils.rand_name('ram_quota_project')
         project_desc = project_name + '-desc'
         project = identity.identity_utils(self.os_admin).create_project(
@@ -165,26 +168,40 @@
 
 
 class QuotasAdminTestV236(QuotasAdminTestBase):
-    min_microversion = '2.36'
+    """Test compute quotas with microversion greater than 2.35
+
     # NOTE(gmann): This test tests the Quota APIs response schema
     # for 2.36 microversion. No specific assert or behaviour verification
     # is needed.
+    """
+
+    min_microversion = '2.36'
 
     @decorators.idempotent_id('4268b5c9-92e5-4adc-acf1-3a2798f3d803')
     def test_get_updated_quotas(self):
-        # Checking Quota update, get, get details APIs response schema
+        """Test compute quotas API with microversion greater than 2.35
+
+        Checking compute quota update, get, get details APIs response schema.
+        """
         self._get_updated_quotas()
 
 
 class QuotasAdminTestV257(QuotasAdminTestBase):
-    min_microversion = '2.57'
+    """Test compute quotas with microversion greater than 2.56
+
     # NOTE(gmann): This test tests the Quota APIs response schema
     # for 2.57 microversion. No specific assert or behaviour verification
     # is needed.
+    """
+
+    min_microversion = '2.57'
 
     @decorators.idempotent_id('e641e6c6-e86c-41a4-9e5c-9493c0ae47ad')
     def test_get_updated_quotas(self):
-        # Checking Quota update, get, get details APIs response schema
+        """Test compute quotas API with microversion greater than 2.56
+
+        Checking compute quota update, get, get details APIs response schema.
+        """
         self._get_updated_quotas()
 
 
@@ -212,6 +229,7 @@
     # 'danger' flag.
     @decorators.idempotent_id('7932ab0f-5136-4075-b201-c0e2338df51a')
     def test_update_default_quotas(self):
+        """Test updating default compute quota class set"""
         # get the current 'default' quota class values
         body = (self.adm_client.show_quota_class_set('default')
                 ['quota_class_set'])
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
index dfa801b..f0a6a84 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -20,6 +20,12 @@
 
 
 class SecurityGroupsTestAdminJSON(base.BaseV2ComputeAdminTest):
+    """Test security groups API that requires admin privilege
+
+    Test security groups API that requires admin privilege with compute
+    microversion less than 2.36
+    """
+
     max_microversion = '2.35'
 
     @classmethod
@@ -37,7 +43,17 @@
     @decorators.idempotent_id('49667619-5af9-4c63-ab5d-2cfdd1c8f7f1')
     @utils.services('network')
     def test_list_security_groups_list_all_tenants_filter(self):
-        # Admin can list security groups of all tenants
+        """Test listing security groups with all_tenants filter
+
+        1. Create two security groups for non-admin user
+        2. Create two security groups for admin user
+        3. Fetch all security groups based on 'all_tenants' search filter by
+           admin, check that all four created security groups are present in
+           fetched list
+        4. Fetch all security groups based on 'all_tenants' search filter by
+           non-admin, check only two security groups created by the provided
+           non-admin user are present in fetched list
+        """
         # List of all security groups created
         security_group_list = []
         # Create two security groups for a non-admin tenant
diff --git a/tempest/api/compute/admin/test_server_diagnostics.py b/tempest/api/compute/admin/test_server_diagnostics.py
index 005efdd..d855a62 100644
--- a/tempest/api/compute/admin/test_server_diagnostics.py
+++ b/tempest/api/compute/admin/test_server_diagnostics.py
@@ -19,6 +19,8 @@
 
 
 class ServerDiagnosticsTest(base.BaseV2ComputeAdminTest):
+    """Test server diagnostics with compute microversion less than 2.48"""
+
     min_microversion = None
     max_microversion = '2.47'
 
@@ -29,6 +31,7 @@
 
     @decorators.idempotent_id('31ff3486-b8a0-4f56-a6c0-aab460531db3')
     def test_get_server_diagnostics(self):
+        """Test getting server diagnostics"""
         server_id = self.create_test_server(wait_until='ACTIVE')['id']
         diagnostics = self.client.show_server_diagnostics(server_id)
 
@@ -41,6 +44,8 @@
 
 
 class ServerDiagnosticsV248Test(base.BaseV2ComputeAdminTest):
+    """Test server diagnostics with compute microversion greater than 2.47"""
+
     min_microversion = '2.48'
     max_microversion = 'latest'
 
@@ -51,6 +56,7 @@
 
     @decorators.idempotent_id('64d0d48c-dff1-11e6-bf01-fe55135034f3')
     def test_get_server_diagnostics(self):
+        """Test getting server diagnostics"""
         server_id = self.create_test_server(wait_until='ACTIVE')['id']
         # Response status and filed types will be checked by json schema
         self.client.show_server_diagnostics(server_id)
diff --git a/tempest/api/compute/admin/test_server_diagnostics_negative.py b/tempest/api/compute/admin/test_server_diagnostics_negative.py
index 6215c37..8f14cbc 100644
--- a/tempest/api/compute/admin/test_server_diagnostics_negative.py
+++ b/tempest/api/compute/admin/test_server_diagnostics_negative.py
@@ -18,6 +18,7 @@
 
 
 class ServerDiagnosticsNegativeTest(base.BaseV2ComputeAdminTest):
+    """Negative tests of server diagnostics"""
 
     @classmethod
     def setup_clients(cls):
@@ -27,7 +28,10 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac')
     def test_get_server_diagnostics_by_non_admin(self):
-        # Non-admin user cannot view server diagnostics according to policy
+        """Test getting server diagnostics by non-admin user is forbidden
+
+        Non-admin user cannot view server diagnostics according to policy.
+        """
         server_id = self.create_test_server(wait_until='ACTIVE')['id']
         self.assertRaises(lib_exc.Forbidden,
                           self.client.show_server_diagnostics, server_id)
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 170b2cc..ab1b49a 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -14,10 +14,13 @@
 
 from tempest.api.compute import base
 from tempest.common import waiters
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
+CONF = config.CONF
+
 
 class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
     """Tests Servers API using admin privileges"""
@@ -45,7 +48,7 @@
 
     @decorators.idempotent_id('06f960bb-15bb-48dc-873d-f96e89be7870')
     def test_list_servers_filter_by_error_status(self):
-        # Filter the list of servers by server error status
+        """Test filtering the list of servers by server error status"""
         params = {'status': 'error'}
         self.client.reset_state(self.s1_id, state='error')
         body = self.non_admin_client.list_servers(**params)
@@ -61,6 +64,7 @@
 
     @decorators.idempotent_id('d56e9540-73ed-45e0-9b88-98fc419087eb')
     def test_list_servers_detailed_filter_by_invalid_status(self):
+        """Test filtering the list of servers by invalid server status"""
         params = {'status': 'invalid_status'}
         if self.is_requested_microversion_compatible('2.37'):
             body = self.client.list_servers(detail=True, **params)
@@ -72,8 +76,11 @@
 
     @decorators.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
     def test_list_servers_by_admin(self):
-        # Listing servers by admin user returns a list which doesn't
-        # contain the other tenants' server by default
+        """Test listing servers by admin without other projects
+
+        Listing servers by admin user returns a list which doesn't
+        contain the other projects' server by default.
+        """
         body = self.client.list_servers(detail=True)
         servers = body['servers']
 
@@ -85,8 +92,11 @@
 
     @decorators.idempotent_id('9f5579ae-19b4-4985-a091-2a5d56106580')
     def test_list_servers_by_admin_with_all_tenants(self):
-        # Listing servers by admin user with all tenants parameter
-        # Here should be listed all servers
+        """Test listing servers by admin with all tenants
+
+        Listing servers by admin user with all tenants parameter,
+        all servers should be listed.
+        """
         params = {'all_tenants': ''}
         body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
@@ -98,8 +108,10 @@
     @decorators.related_bug('1659811')
     @decorators.idempotent_id('7e5d6b8f-454a-4ba1-8ae2-da857af8338b')
     def test_list_servers_by_admin_with_specified_tenant(self):
-        # In nova v2, tenant_id is ignored unless all_tenants is specified
+        """Test listing servers by admin with specified project
 
+        In nova v2, tenant_id is ignored unless all_tenants is specified.
+        """
         # List the primary tenant but get nothing due to odd specified behavior
         tenant_id = self.non_admin_client.tenant_id
         params = {'tenant_id': tenant_id}
@@ -128,7 +140,7 @@
 
     @decorators.idempotent_id('86c7a8f7-50cf-43a9-9bac-5b985317134f')
     def test_list_servers_filter_by_exist_host(self):
-        # Filter the list of servers by existent host
+        """Test filtering the list of servers by existent host"""
         server = self.client.show_server(self.s1_id)['server']
         hostname = server['OS-EXT-SRV-ATTR:host']
         params = {'host': hostname, 'all_tenants': '1'}
@@ -144,6 +156,7 @@
 
     @decorators.idempotent_id('ee8ae470-db70-474d-b752-690b7892cab1')
     def test_reset_state_server(self):
+        """Test resetting server state to error/active"""
         # Reset server's state to 'error'
         self.client.reset_state(self.s1_id, state='error')
 
@@ -160,9 +173,11 @@
 
     @decorators.idempotent_id('682cb127-e5bb-4f53-87ce-cb9003604442')
     def test_rebuild_server_in_error_state(self):
-        # The server in error state should be rebuilt using the provided
-        # image and changed to ACTIVE state
+        """Test rebuilding server in error state
 
+        The server in error state should be rebuilt using the provided
+        image and changed to ACTIVE state.
+        """
         # resetting vm state require admin privilege
         self.client.reset_state(self.s1_id, state='error')
         rebuilt_server = self.non_admin_client.rebuild_server(
@@ -188,6 +203,11 @@
 
     @decorators.idempotent_id('7a1323b4-a6a2-497a-96cb-76c07b945c71')
     def test_reset_network_inject_network_info(self):
+        """Test resetting and injecting network info of a server"""
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'The resetNetwork server action is not supported.')
+
         # Reset Network of a Server
         server = self.create_test_server(wait_until='ACTIVE')
         self.client.reset_network(server['id'])
@@ -196,6 +216,7 @@
 
     @decorators.idempotent_id('fdcd9b33-0903-4e00-a1f7-b5f6543068d6')
     def test_create_server_with_scheduling_hint(self):
+        """Test creating server with scheduling hint"""
         # Create a server with scheduler hints.
         hints = {
             'same_host': self.s1_id
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index f720b84..f52d4c0 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -26,7 +26,7 @@
 
 
 class ServersAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
-    """Tests Servers API using admin privileges"""
+    """Negative Tests of Servers API using admin privileges"""
 
     @classmethod
     def setup_clients(cls):
@@ -47,6 +47,7 @@
                           'Resize not available.')
     @decorators.attr(type=['negative'])
     def test_resize_server_using_overlimit_ram(self):
+        """Test resizing server using over limit ram should fail"""
         # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
         self.useFixture(fixtures.LockFixture('compute_quotas'))
         quota_set = self.quotas_client.show_quota_set(
@@ -69,6 +70,7 @@
                           'Resize not available.')
     @decorators.attr(type=['negative'])
     def test_resize_server_using_overlimit_vcpus(self):
+        """Test resizing server using over limit vcpus should fail"""
         # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
         self.useFixture(fixtures.LockFixture('compute_quotas'))
         quota_set = self.quotas_client.show_quota_set(
@@ -89,6 +91,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('b0b4d8af-1256-41ef-9ee7-25f1c19dde80')
     def test_reset_state_server_invalid_state(self):
+        """Test resetting server state to invalid state value should fail"""
         self.assertRaises(lib_exc.BadRequest,
                           self.client.reset_state, self.s1_id,
                           state='invalid')
@@ -96,6 +99,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('4cdcc984-fab0-4577-9a9d-6d558527ee9d')
     def test_reset_state_server_invalid_type(self):
+        """Test resetting server state to invalid state type should fail"""
         self.assertRaises(lib_exc.BadRequest,
                           self.client.reset_state, self.s1_id,
                           state=1)
@@ -103,13 +107,14 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('e741298b-8df2-46f0-81cb-8f814ff2504c')
     def test_reset_state_server_nonexistent_server(self):
+        """Test resetting a non existent server's state should fail"""
         self.assertRaises(lib_exc.NotFound,
                           self.client.reset_state, '999', state='error')
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('46a4e1ca-87ae-4d28-987a-1b6b136a0221')
     def test_migrate_non_existent_server(self):
-        # migrate a non existent server
+        """Test migrating a non existent server should fail"""
         self.assertRaises(lib_exc.NotFound,
                           self.client.migrate_server,
                           data_utils.rand_uuid())
@@ -121,6 +126,7 @@
                           'Suspend is not available.')
     @decorators.attr(type=['negative'])
     def test_migrate_server_invalid_state(self):
+        """Test migrating a server with invalid state should fail"""
         # create server.
         server = self.create_test_server(wait_until='ACTIVE')
         server_id = server['id']
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index edcb1a7..c1236a7 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -68,21 +68,7 @@
 
 
 class TestVolumeSwap(TestVolumeSwapBase):
-    """The test suite for swapping of volume with admin user.
-
-    The following is the scenario outline:
-
-    1. Create a volume "volume1" with non-admin.
-    2. Create a volume "volume2" with non-admin.
-    3. Boot an instance "instance1" with non-admin.
-    4. Attach "volume1" to "instance1" with non-admin.
-    5. Swap volume from "volume1" to "volume2" as admin.
-    6. Check the swap volume is successful and "volume2"
-       is attached to "instance1" and "volume1" is in available state.
-    7. Swap volume from "volume2" to "volume1" as admin.
-    8. Check the swap volume is successful and "volume1"
-       is attached to "instance1" and "volume2" is in available state.
-    """
+    """The test suite for swapping of volume with admin user"""
 
     # NOTE(mriedem): This is an uncommon scenario to call the compute API
     # to swap volumes directly; swap volume is primarily only for volume
@@ -92,6 +78,21 @@
     @decorators.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813')
     @utils.services('volume')
     def test_volume_swap(self):
+        """Test swapping of volume attached to server with admin user
+
+        The following is the scenario outline:
+
+        1. Create a volume "volume1" with non-admin.
+        2. Create a volume "volume2" with non-admin.
+        3. Boot an instance "instance1" with non-admin.
+        4. Attach "volume1" to "instance1" with non-admin.
+        5. Swap volume from "volume1" to "volume2" as admin.
+        6. Check the swap volume is successful and "volume2"
+           is attached to "instance1" and "volume1" is in available state.
+        7. Swap volume from "volume2" to "volume1" as admin.
+        8. Check the swap volume is successful and "volume1"
+           is attached to "instance1" and "volume2" is in available state.
+        """
         # Create two volumes.
         # NOTE(gmann): Volumes are created before server creation so that
         # volumes cleanup can happen successfully irrespective of which volume
@@ -134,6 +135,12 @@
 
 
 class TestMultiAttachVolumeSwap(TestVolumeSwapBase):
+    """Test swapping volume attached to multiple servers
+
+    Test swapping volume attached to multiple servers with microversion
+    greater than 2.59
+    """
+
     min_microversion = '2.60'
     max_microversion = 'latest'
 
@@ -164,6 +171,20 @@
                              condition=CONF.compute.min_compute_nodes > 1)
     @utils.services('volume')
     def test_volume_swap_with_multiattach(self):
+        """Test swapping volume attached to multiple servers
+
+        The following is the scenario outline:
+
+        1. Create a volume "volume1" with non-admin.
+        2. Create a volume "volume2" with non-admin.
+        3. Boot 2 instances "server1" and "server2" with non-admin.
+        4. Attach "volume1" to "server1" with non-admin.
+        5. Attach "volume1" to "server2" with non-admin.
+        6. Swap "volume1" to "volume2" on "server1"
+        7. Check "volume1" is attached to "server2" and not attached to
+           "server1"
+        8. Check "volume2" is attached to "server1".
+        """
         # Create two volumes.
         # NOTE(gmann): Volumes are created before server creation so that
         # volumes cleanup can happen successfully irrespective of which volume
diff --git a/tempest/api/compute/admin/test_volumes_negative.py b/tempest/api/compute/admin/test_volumes_negative.py
index 7b0f48b..10d522b 100644
--- a/tempest/api/compute/admin/test_volumes_negative.py
+++ b/tempest/api/compute/admin/test_volumes_negative.py
@@ -23,6 +23,8 @@
 
 
 class VolumesAdminNegativeTest(base.BaseV2ComputeAdminTest):
+    """Negative tests of volume swapping"""
+
     create_default_network = True
 
     @classmethod
@@ -40,6 +42,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('309b5ecd-0585-4a7e-a36f-d2b2bf55259d')
     def test_update_attached_volume_with_nonexistent_volume_in_uri(self):
+        """Test swapping non existent volume should fail"""
         volume = self.create_volume()
         nonexistent_volume = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound,
@@ -51,6 +54,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('7dcac15a-b107-46d3-a5f6-cb863f4e454a')
     def test_update_attached_volume_with_nonexistent_volume_in_body(self):
+        """Test swapping volume to a non existence volume should fail"""
         volume = self.create_volume()
         self.attach_volume(self.server, volume)
 
@@ -62,6 +66,12 @@
 
 
 class UpdateMultiattachVolumeNegativeTest(base.BaseV2ComputeAdminTest):
+    """Negative tests of swapping volume attached to multiple servers
+
+    Negative tests of swapping volume attached to multiple servers with
+    compute microversion greater than 2.59 and volume microversion greater
+    than 3.26
+    """
 
     min_microversion = '2.60'
     volume_min_microversion = '3.27'
@@ -76,7 +86,16 @@
     @decorators.idempotent_id('7576d497-b7c6-44bd-9cc5-c5b4e50fec71')
     @utils.services('volume')
     def test_multiattach_rw_volume_update_failure(self):
+        """Test swapping volume attached to multi-servers with read-write mode
 
+        1. Create two volumes "vol1" and "vol2"
+        2. Create two instances "server1" and "server2"
+        3. Attach "vol1" to both of these instances
+        4. By default both of these attachments should have an attach_mode of
+           read-write, so trying to swap "vol1" to "vol2" should fail
+        5. Check "vol1" is still attached to both servers
+        6. Check "vol2" is not attached to any server
+        """
         # Create two multiattach capable volumes.
         vol1 = self.create_volume(multiattach=True)
         vol2 = self.create_volume(multiattach=True)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 74570ce..8b847fc 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -566,17 +566,19 @@
         # the state of the volume to change to available. This is so we don't
         # error out when trying to delete the volume during teardown.
         if volume['multiattach']:
+            att = waiters.wait_for_volume_attachment_create(
+                self.volumes_client, volume['id'], server['id'])
             self.addCleanup(waiters.wait_for_volume_attachment_remove,
                             self.volumes_client, volume['id'],
-                            attachment['id'])
+                            att['attachment_id'])
         else:
             self.addCleanup(waiters.wait_for_volume_resource_status,
                             self.volumes_client, volume['id'], 'available')
+            waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                    volume['id'], 'in-use')
         # Ignore 404s on detach in case the server is deleted or the volume
         # is already detached.
         self.addCleanup(self._detach_volume, server, volume)
-        waiters.wait_for_volume_resource_status(self.volumes_client,
-                                                volume['id'], 'in-use')
         return attachment
 
     def create_volume_snapshot(self, volume_id, name=None, description=None,
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index 58861a1..9ab75c5 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -28,6 +28,9 @@
         flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
         flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'],
                              'name': flavor['name']}
+        # description field is added to the response of list_flavors in 2.55
+        if not self.is_requested_microversion_compatible('2.54'):
+            flavor_min_detail.update({'description': flavor['description']})
         self.assertIn(flavor_min_detail, flavors)
 
     @decorators.idempotent_id('6e85fde4-b3cd-4137-ab72-ed5f418e8c24')
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 1f3af5f..561265f 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -28,6 +28,8 @@
 
 
 class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+    """Test image metadata with compute microversion less than 2.39"""
+
     max_microversion = '2.38'
 
     @classmethod
@@ -89,7 +91,10 @@
 
     @decorators.idempotent_id('37ec6edd-cf30-4c53-bd45-ae74db6b0531')
     def test_list_image_metadata(self):
-        # All metadata key/value pairs for an image should be returned
+        """Test listing image metadata
+
+        All metadata key/value pairs for an image should be returned.
+        """
         resp_metadata = self.client.list_image_metadata(self.image_id)
         expected = {'metadata': {
             'os_version': 'value1', 'os_distro': 'value2'}}
@@ -97,7 +102,10 @@
 
     @decorators.idempotent_id('ece7befc-d3ce-42a4-b4be-c3067a418c29')
     def test_set_image_metadata(self):
-        # The metadata for the image should match the new values
+        """Test setting image metadata
+
+        The metadata for the image should match the new values.
+        """
         req_metadata = {'os_version': 'value2', 'architecture': 'value3'}
         self.client.set_image_metadata(self.image_id,
                                        req_metadata)
@@ -108,7 +116,10 @@
 
     @decorators.idempotent_id('7b491c11-a9d5-40fe-a696-7f7e03d3fea2')
     def test_update_image_metadata(self):
-        # The metadata for the image should match the updated values
+        """Test updating image medata
+
+        The metadata for the image should match the updated values.
+        """
         req_metadata = {'os_version': 'alt1', 'architecture': 'value3'}
         self.client.update_image_metadata(self.image_id,
                                           req_metadata)
@@ -122,15 +133,21 @@
 
     @decorators.idempotent_id('4f5db52f-6685-4c75-b848-f4bb363f9aa6')
     def test_get_image_metadata_item(self):
-        # The value for a specific metadata key should be returned
+        """Test getting image metadata item
+
+        The value for a specific metadata key should be returned.
+        """
         meta = self.client.show_image_metadata_item(self.image_id,
                                                     'os_distro')['meta']
         self.assertEqual('value2', meta['os_distro'])
 
     @decorators.idempotent_id('f2de776a-4778-4d90-a5da-aae63aee64ae')
     def test_set_image_metadata_item(self):
-        # The value provided for the given meta item should be set for
-        # the image
+        """Test setting image metadata item
+
+        The value provided for the given meta item should be set for
+        the image.
+        """
         meta = {'os_version': 'alt'}
         self.client.set_image_metadata_item(self.image_id,
                                             'os_version', meta)
@@ -140,7 +157,10 @@
 
     @decorators.idempotent_id('a013796c-ba37-4bb5-8602-d944511def14')
     def test_delete_image_metadata_item(self):
-        # The metadata value/key pair should be deleted from the image
+        """Test deleting image metadata item
+
+        The metadata value/key pair should be deleted from the image.
+        """
         self.client.delete_image_metadata_item(self.image_id,
                                                'os_version')
         resp_metadata = self.client.list_image_metadata(self.image_id)
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index 407fb08..b9806c7 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -20,6 +20,11 @@
 
 
 class ImagesMetadataNegativeTestJSON(base.BaseV2ComputeTest):
+    """Negative tests of image metadata
+
+    Negative tests of image metadata with compute microversion less than 2.39.
+    """
+
     max_microversion = '2.38'
 
     @classmethod
@@ -30,15 +35,14 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('94069db2-792f-4fa8-8bd3-2271a6e0c095')
     def test_list_nonexistent_image_metadata(self):
-        # Negative test: List on nonexistent image
-        # metadata should not happen
+        """Test listing metadata of a non existence image should fail"""
         self.assertRaises(lib_exc.NotFound, self.client.list_image_metadata,
                           data_utils.rand_uuid())
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('a403ef9e-9f95-427c-b70a-3ce3388796f1')
     def test_update_nonexistent_image_metadata(self):
-        # Negative test:An update should not happen for a non-existent image
+        """Test updating metadata of a non existence image should fail"""
         meta = {'os_distro': 'alt1', 'os_version': 'alt2'}
         self.assertRaises(lib_exc.NotFound,
                           self.client.update_image_metadata,
@@ -47,7 +51,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('41ae052c-6ee6-405c-985e-5712393a620d')
     def test_get_nonexistent_image_metadata_item(self):
-        # Negative test: Get on non-existent image should not happen
+        """Test getting metadata of a non existence image should fail"""
         self.assertRaises(lib_exc.NotFound,
                           self.client.show_image_metadata_item,
                           data_utils.rand_uuid(), 'os_version')
@@ -55,7 +59,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('dc64f2ce-77e8-45b0-88c8-e15041d08eaf')
     def test_set_nonexistent_image_metadata(self):
-        # Negative test: Metadata should not be set to a non-existent image
+        """Test setting metadata of a non existence image should fail"""
         meta = {'os_distro': 'alt1', 'os_version': 'alt2'}
         self.assertRaises(lib_exc.NotFound, self.client.set_image_metadata,
                           data_utils.rand_uuid(), meta)
@@ -63,8 +67,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('2154fd03-ab54-457c-8874-e6e3eb56e9cf')
     def test_set_nonexistent_image_metadata_item(self):
-        # Negative test: Metadata item should not be set to a
-        # nonexistent image
+        """Test setting metadata item of a non existence image should fail"""
         meta = {'os_distro': 'alt'}
         self.assertRaises(lib_exc.NotFound,
                           self.client.set_image_metadata_item,
@@ -74,8 +77,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('848e157f-6bcf-4b2e-a5dd-5124025a8518')
     def test_delete_nonexistent_image_metadata_item(self):
-        # Negative test: Shouldn't be able to delete metadata
-        # item from non-existent image
+        """Test deleting metadata item of a non existence image should fail"""
         self.assertRaises(lib_exc.NotFound,
                           self.client.delete_image_metadata_item,
                           data_utils.rand_uuid(), 'os_distro')
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index b811421..23f8326 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -22,6 +22,7 @@
 
 
 class ImagesOneServerTestJSON(base.BaseV2ComputeTest):
+    """Test server images API"""
 
     @classmethod
     def resource_setup(cls):
@@ -54,6 +55,7 @@
 
     @decorators.idempotent_id('3731d080-d4c5-4872-b41a-64d0d0021314')
     def test_create_delete_image(self):
+        """Test create/delete server image"""
         if self.is_requested_microversion_compatible('2.35'):
             MIN_DISK = 'minDisk'
             MIN_RAM = 'minRam'
@@ -93,6 +95,7 @@
 
     @decorators.idempotent_id('3b7c6fe4-dfe7-477c-9243-b06359db51e6')
     def test_create_image_specify_multibyte_character_image_name(self):
+        """Test creating server image with multibyte character image name"""
         # prefix character is:
         # http://unicode.org/cldr/utility/character.jsp?a=20A1
 
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 37f9be3..0296220 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -30,6 +30,8 @@
 
 
 class ImagesOneServerNegativeTestJSON(base.BaseV2ComputeTest):
+    """Negative tests of server images"""
+
     create_default_network = True
 
     def tearDown(self):
@@ -87,7 +89,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('55d1d38c-dd66-4933-9c8e-7d92aeb60ddc')
     def test_create_image_specify_invalid_metadata(self):
-        # Return an error when creating image with invalid metadata
+        """Test creating server image with invalid metadata should fail"""
         meta = {'': ''}
         self.assertRaises(lib_exc.BadRequest, self.create_image_from_server,
                           self.server_id, metadata=meta)
@@ -95,7 +97,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('3d24d11f-5366-4536-bd28-cff32b748eca')
     def test_create_image_specify_metadata_over_limits(self):
-        # Return an error when creating image with meta data over 255 chars
+        """Test creating server image with metadata over 255 should fail"""
         meta = {'a' * 256: 'b' * 256}
         self.assertRaises(lib_exc.BadRequest, self.create_image_from_server,
                           self.server_id, metadata=meta)
@@ -103,8 +105,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('0460efcf-ee88-4f94-acef-1bf658695456')
     def test_create_second_image_when_first_image_is_being_saved(self):
-        # Disallow creating another image when first image is being saved
+        """Test creating another server image when first image is being saved
 
+        Creating another server image when first image is being saved is
+        not allowed.
+        """
         # Create first snapshot
         image = self.create_image_from_server(self.server_id)
         self.addCleanup(self._reset_server)
@@ -123,8 +128,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('084f0cbc-500a-4963-8a4e-312905862581')
     def test_create_image_specify_name_over_character_limit(self):
-        # Return an error if snapshot name over 255 characters is passed
-
+        """Test creating server image with image name over 255 should fail"""
         snapshot_name = ('a' * 256)
         self.assertRaises(lib_exc.BadRequest,
                           self.compute_images_client.create_image,
@@ -133,8 +137,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('0894954d-2db2-4195-a45b-ffec0bc0187e')
     def test_delete_image_that_is_not_yet_active(self):
-        # Return an error while trying to delete an image what is creating
-
+        """Test deleting a non-active server image should fail"""
         image = self.create_image_from_server(self.server_id)
         if api_version_utils.compare_version_header_to_response(
             "OpenStack-API-Version", "compute 2.45", image.response, "lt"):
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 2ac7de3..7930c67 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -31,6 +31,8 @@
 
 
 class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
+    """Test listing server images with compute microversion less than 2.36"""
+
     max_microversion = '2.35'
 
     @classmethod
@@ -129,8 +131,11 @@
 
     @decorators.idempotent_id('a3f5b513-aeb3-42a9-b18e-f091ef73254d')
     def test_list_images_filter_by_status(self):
-        # The list of images should contain only images with the
-        # provided status
+        """Test listing server images filtered by image status
+
+        The list of images should contain only images with the
+        provided image status.
+        """
         params = {'status': 'ACTIVE'}
         images = self.client.list_images(**params)['images']
 
@@ -140,8 +145,11 @@
 
     @decorators.idempotent_id('33163b73-79f5-4d07-a7ea-9213bcc468ff')
     def test_list_images_filter_by_name(self):
-        # List of all images should contain the expected images filtered
-        # by name
+        """Test listing server images filtered by image name
+
+        The list of images should contain only images with the
+        provided image name.
+        """
         params = {'name': self.image1['name']}
         images = self.client.list_images(**params)['images']
 
@@ -153,7 +161,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
                           'Snapshotting is not available.')
     def test_list_images_filter_by_server_id(self):
-        # The images should contain images filtered by server id
+        """Test listing images filtered by server id
+
+        The list of images should contain only images with the
+        provided server id.
+        """
         params = {'server': self.server1['id']}
         images = self.client.list_images(**params)['images']
 
@@ -169,7 +181,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
                           'Snapshotting is not available.')
     def test_list_images_filter_by_server_ref(self):
-        # The list of servers should be filtered by server ref
+        """Test listing images filtered by server link href
+
+        The list of images should contain only images with the
+        provided server link href.
+        """
         server_links = self.server2['links']
 
         # Try all server link types
@@ -188,7 +204,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
                           'Snapshotting is not available.')
     def test_list_images_filter_by_type(self):
-        # The list of servers should be filtered by image type
+        """Test listing images filtered by image type
+
+        The list of images should contain only images with the
+        provided image type.
+        """
         params = {'type': 'snapshot'}
         images = self.client.list_images(**params)['images']
 
@@ -202,13 +222,22 @@
 
     @decorators.idempotent_id('3a484ca9-67ba-451e-b494-7fcf28d32d62')
     def test_list_images_limit_results(self):
-        # Verify only the expected number of results are returned
+        """Test listing images with limited count
+
+        If we use limit=1 when listing images, then only 1 image should be
+        returned.
+        """
         params = {'limit': '1'}
         images = self.client.list_images(**params)['images']
         self.assertEqual(1, len([x for x in images if 'id' in x]))
 
     @decorators.idempotent_id('18bac3ae-da27-436c-92a9-b22474d13aab')
     def test_list_images_filter_by_changes_since(self):
+        """Test listing images filtered by changes-since
+
+        The list of images should contain only images updated since the
+        provided changes-since value.
+        """
         # Verify only updated images are returned in the detailed list
 
         # Becoming ACTIVE will modify the updated time
@@ -220,8 +249,11 @@
 
     @decorators.idempotent_id('9b0ea018-6185-4f71-948a-a123a107988e')
     def test_list_images_with_detail_filter_by_status(self):
-        # Detailed list of all images should only contain images
-        # with the provided status
+        """Test listing server images details filtered by image status
+
+        The list of images should contain only images with the
+        provided image status.
+        """
         params = {'status': 'ACTIVE'}
         images = self.client.list_images(detail=True, **params)['images']
 
@@ -231,8 +263,11 @@
 
     @decorators.idempotent_id('644ea267-9bd9-4f3b-af9f-dffa02396a17')
     def test_list_images_with_detail_filter_by_name(self):
-        # Detailed list of all images should contain the expected
-        # images filtered by name
+        """Test listing server images details filtered by image name
+
+        The list of images should contain only images with the
+        provided image name.
+        """
         params = {'name': self.image1['name']}
         images = self.client.list_images(detail=True, **params)['images']
 
@@ -242,8 +277,11 @@
 
     @decorators.idempotent_id('ba2fa9a9-b672-47cc-b354-3b4c0600e2cb')
     def test_list_images_with_detail_limit_results(self):
-        # Verify only the expected number of results (with full details)
-        # are returned
+        """Test listing images details with limited count
+
+        If we use limit=1 when listing images with full details, then only 1
+        image should be returned.
+        """
         params = {'limit': '1'}
         images = self.client.list_images(detail=True, **params)['images']
         self.assertEqual(1, len(images))
@@ -252,7 +290,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
                           'Snapshotting is not available.')
     def test_list_images_with_detail_filter_by_server_ref(self):
-        # Detailed list of servers should be filtered by server ref
+        """Test listing images details filtered by server link href
+
+        The list of images should contain only images with the
+        provided server link href.
+        """
         server_links = self.server2['links']
 
         # Try all server link types
@@ -271,7 +313,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
                           'Snapshotting is not available.')
     def test_list_images_with_detail_filter_by_type(self):
-        # The detailed list of servers should be filtered by image type
+        """Test listing images details filtered by image type
+
+        The list of images should contain only images with the
+        provided image type.
+        """
         params = {'type': 'snapshot'}
         images = self.client.list_images(detail=True, **params)['images']
         self.client.show_image(self.image_ref)
@@ -286,8 +332,11 @@
 
     @decorators.idempotent_id('7d439e18-ac2e-4827-b049-7e18004712c4')
     def test_list_images_with_detail_filter_by_changes_since(self):
-        # Verify an update image is returned
+        """Test listing images details filtered by changes-since
 
+        The list of images should contain only images updated since the
+        provided changes-since value.
+        """
         # Becoming ACTIVE will modify the updated time
         # Filter by the image's created time
         params = {'changes-since': self.image1['created']}
diff --git a/tempest/api/compute/images/test_list_image_filters_negative.py b/tempest/api/compute/images/test_list_image_filters_negative.py
index 81c59f9..f77da4b 100644
--- a/tempest/api/compute/images/test_list_image_filters_negative.py
+++ b/tempest/api/compute/images/test_list_image_filters_negative.py
@@ -22,6 +22,12 @@
 
 
 class ListImageFiltersNegativeTestJSON(base.BaseV2ComputeTest):
+    """Negative tests of listing images using compute images API
+
+    Negative tests of listing images using compute images API with
+    microversion less than 2.36.
+    """
+
     max_microversion = '2.35'
 
     @classmethod
@@ -39,7 +45,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('391b0440-432c-4d4b-b5da-c5096aa247eb')
     def test_get_nonexistent_image(self):
-        # Check raises a NotFound
+        """Test getting a non existent image should fail"""
         nonexistent_image = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound, self.client.show_image,
                           nonexistent_image)
diff --git a/tempest/api/compute/servers/test_create_server_multi_nic.py b/tempest/api/compute/servers/test_create_server_multi_nic.py
index d0f53fe..bd3f58d 100644
--- a/tempest/api/compute/servers/test_create_server_multi_nic.py
+++ b/tempest/api/compute/servers/test_create_server_multi_nic.py
@@ -24,6 +24,7 @@
 
 
 class ServersTestMultiNic(base.BaseV2ComputeTest):
+    """Test multiple networks in servers"""
 
     @classmethod
     def skip_checks(cls):
@@ -59,8 +60,11 @@
 
     @decorators.idempotent_id('0578d144-ed74-43f8-8e57-ab10dbf9b3c2')
     def test_verify_multiple_nics_order(self):
-        # Verify that the networks order given at the server creation is
-        # preserved within the server.
+        """Test verifying multiple networks order in server
+
+        The networks order given at the server creation is preserved within
+        the server.
+        """
         net1 = self._create_net_subnet_ret_net_from_cidr('19.80.0.0/24')
         net2 = self._create_net_subnet_ret_net_from_cidr('19.86.0.0/24')
 
@@ -95,6 +99,12 @@
 
     @decorators.idempotent_id('1678d144-ed74-43f8-8e57-ab10dbf9b3c2')
     def test_verify_duplicate_network_nics(self):
+        """Test multiple duplicate networks can be used to create server
+
+        Creating server with networks [net1, net2, net1], the server can
+        be created successfully and all three networks are in the server
+        addresses.
+        """
         # Verify that server creation does not fail when more than one nic
         # is created on the same network.
         net1 = self._create_net_subnet_ret_net_from_cidr('19.80.0.0/24')
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index b95db5c..3d55696 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -20,6 +20,8 @@
 
 
 class ListServersNegativeTestJSON(base.BaseV2ComputeTest):
+    """Negative tests of listing servers"""
+
     create_default_network = True
 
     @classmethod
@@ -45,7 +47,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('24a26f1a-1ddc-4eea-b0d7-a90cc874ad8f')
     def test_list_servers_with_a_deleted_server(self):
-        # Verify deleted servers do not show by default in list servers
+        """Test that deleted servers do not show by default in list servers"""
         # List servers and verify server not returned
         body = self.client.list_servers()
         servers = body['servers']
@@ -56,7 +58,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('ff01387d-c7ad-47b4-ae9e-64fa214638fe')
     def test_list_servers_by_non_existing_image(self):
-        # Listing servers for a non existing image returns empty list
+        """Test listing servers for a non existing image returns empty list"""
         body = self.client.list_servers(image='non_existing_image')
         servers = body['servers']
         self.assertEmpty(servers)
@@ -64,7 +66,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('5913660b-223b-44d4-a651-a0fbfd44ca75')
     def test_list_servers_by_non_existing_flavor(self):
-        # Listing servers by non existing flavor returns empty list
+        """Test listing servers by non existing flavor returns empty list"""
         body = self.client.list_servers(flavor='non_existing_flavor')
         servers = body['servers']
         self.assertEmpty(servers)
@@ -72,7 +74,12 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('e2c77c4a-000a-4af3-a0bd-629a328bde7c')
     def test_list_servers_by_non_existing_server_name(self):
-        # Listing servers for a non existent server name returns empty list
+        """Test listing servers for a non existent server name
+
+        Listing servers for a non existent server name should return empty
+        list.
+        """
+
         body = self.client.list_servers(name='non_existing_server_name')
         servers = body['servers']
         self.assertEmpty(servers)
@@ -80,9 +87,13 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
     def test_list_servers_status_non_existing(self):
-        # When invalid status is specified, up to microversion 2.37,
-        # an empty list is returned, and starting from microversion 2.38,
-        # a 400 error is returned in that case.
+        """Test listing servers with non existing status
+
+        When invalid status is specified, up to microversion 2.37,
+        an empty list is returned, and starting from microversion 2.38,
+        a 400 error is returned in that case.
+        """
+
         if self.is_requested_microversion_compatible('2.37'):
             body = self.client.list_servers(status='non_existing_status')
             servers = body['servers']
@@ -94,6 +105,12 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
     def test_list_servers_by_limits_greater_than_actual_count(self):
+        """Test listing servers by limit greater than actual count
+
+        Listing servers by limit greater than actual count should return
+        all servers.
+        """
+
         # Gather the complete list of servers in the project for reference
         full_list = self.client.list_servers()['servers']
         # List servers by specifying a greater value for limit
@@ -104,21 +121,21 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('679bc053-5e70-4514-9800-3dfab1a380a6')
     def test_list_servers_by_limits_pass_string(self):
-        # Return an error if a string value is passed for limit
+        """Test listing servers by non-integer limit should fail"""
         self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
                           limit='testing')
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('62610dd9-4713-4ee0-8beb-fd2c1aa7f950')
     def test_list_servers_by_limits_pass_negative_value(self):
-        # Return an error if a negative value for limit is passed
+        """Test listing servers by negative limit should fail"""
         self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
                           limit=-1)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('87d12517-e20a-4c9c-97b6-dd1628d6d6c9')
     def test_list_servers_by_changes_since_invalid_date(self):
-        # Return an error when invalid date format is passed
+        """Test listing servers by invalid changes-since format should fail"""
         params = {'changes-since': '2011/01/01'}
         self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
                           **params)
@@ -126,7 +143,12 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('74745ad8-b346-45b5-b9b8-509d7447fc1f')
     def test_list_servers_by_changes_since_future_date(self):
-        # Return an empty list when a date in the future is passed.
+        """Test listing servers by a future changes-since date
+
+        Return an empty list when a date in the future is passed as
+        changes-since value.
+        """
+
         # updated_at field may haven't been set at the point in the boot
         # process where build_request still exists, so add
         # {'status': 'ACTIVE'} along with changes-since as filter.
@@ -138,7 +160,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('93055106-2d34-46fe-af68-d9ddbf7ee570')
     def test_list_servers_detail_server_is_deleted(self):
-        # Server details are not listed for a deleted server
+        """Test listing servers detail should not contain deleted server"""
         body = self.client.list_servers(detail=True)
         servers = body['servers']
         actual = [srv for srv in servers
diff --git a/tempest/api/compute/servers/test_multiple_create.py b/tempest/api/compute/servers/test_multiple_create.py
index dcadace..10c76bb 100644
--- a/tempest/api/compute/servers/test_multiple_create.py
+++ b/tempest/api/compute/servers/test_multiple_create.py
@@ -19,11 +19,15 @@
 
 
 class MultipleCreateTestJSON(base.BaseV2ComputeTest):
+    """Test creating multiple servers in one request"""
     create_default_network = True
 
     @decorators.idempotent_id('61e03386-89c3-449c-9bb1-a06f423fd9d1')
     def test_multiple_create(self):
-        # Creating server with min_count=2, 2 servers will be created.
+        """Test creating multiple servers in one request
+
+        Creating server with min_count=2, 2 servers will be created.
+        """
         tenant_network = self.get_tenant_network()
         body, servers = compute.create_test_server(
             self.os_primary,
@@ -40,8 +44,12 @@
 
     @decorators.idempotent_id('864777fb-2f1e-44e3-b5b9-3eb6fa84f2f7')
     def test_multiple_create_with_reservation_return(self):
-        # Creating multiple servers with return_reservation_id=True,
-        # reservation_id will be returned.
+        """Test creating multiple servers with return_reservation_id=True
+
+        Creating multiple servers with return_reservation_id=True,
+        reservation_id will be returned.
+        """
+
         body = self.create_test_server(wait_until='ACTIVE',
                                        min_count=1,
                                        max_count=2,
diff --git a/tempest/api/compute/servers/test_multiple_create_negative.py b/tempest/api/compute/servers/test_multiple_create_negative.py
index 6bdf83b..3a970dd 100644
--- a/tempest/api/compute/servers/test_multiple_create_negative.py
+++ b/tempest/api/compute/servers/test_multiple_create_negative.py
@@ -19,11 +19,12 @@
 
 
 class MultipleCreateNegativeTestJSON(base.BaseV2ComputeTest):
+    """Negative tests of creating multiple servers in one request"""
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('daf29d8d-e928-4a01-9a8c-b129603f3fc0')
     def test_min_count_less_than_one(self):
-        # Creating server with min_count=0 should fail.
+        """Test creating server with min_count=0 should fail"""
         invalid_min_count = 0
         self.assertRaises(lib_exc.BadRequest, self.create_test_server,
                           min_count=invalid_min_count)
@@ -31,7 +32,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('999aa722-d624-4423-b813-0d1ac9884d7a')
     def test_min_count_non_integer(self):
-        # Creating server with non-integer min_count should fail.
+        """Test creating server with non-integer min_count should fail"""
         invalid_min_count = 2.5
         self.assertRaises(lib_exc.BadRequest, self.create_test_server,
                           min_count=invalid_min_count)
@@ -39,7 +40,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('a6f9c2ab-e060-4b82-b23c-4532cb9390ff')
     def test_max_count_less_than_one(self):
-        # Creating server with max_count < 1 shoudld fail.
+        """Test creating server with max_count < 1 shoudld fail"""
         invalid_max_count = 0
         self.assertRaises(lib_exc.BadRequest, self.create_test_server,
                           max_count=invalid_max_count)
@@ -47,7 +48,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('9c5698d1-d7af-4c80-b971-9d403135eea2')
     def test_max_count_non_integer(self):
-        # Creating server with non-integer max_count should fail.
+        """Test creating server with non-integer max_count should fail"""
         invalid_max_count = 2.5
         self.assertRaises(lib_exc.BadRequest, self.create_test_server,
                           max_count=invalid_max_count)
@@ -55,7 +56,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('476da616-f1ef-4271-a9b1-b9fc87727cdf')
     def test_max_count_less_than_min_count(self):
-        # Creating server with max_count less than min_count should fail.
+        """Test creating server with max_count < min_count should fail"""
         min_count = 3
         max_count = 2
         self.assertRaises(lib_exc.BadRequest, self.create_test_server,
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index d477be0..4db6987 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -34,6 +34,8 @@
 
 
 class ServerActionsTestJSON(base.BaseV2ComputeTest):
+    """Test server actions"""
+
     def setUp(self):
         # NOTE(afazekas): Normally we use the same server with all test cases,
         # but if it has an issue, we build a new one
@@ -84,6 +86,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.change_password,
                           'Change password not available.')
     def test_change_server_password(self):
+        """Test changing server's password
+
+        The server's password should be set to the provided password and
+        the user can authenticate with the new password.
+        """
         # Since this test messes with the password and makes the
         # server unreachable, it should create its own server
         validation_resources = self.get_test_validation_resources(
@@ -147,17 +154,24 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('2cb1baf6-ac8d-4429-bf0d-ba8a0ba53e32')
     def test_reboot_server_hard(self):
-        # The server should be power cycled
+        """Test hard rebooting server
+
+        The server should be power cycled.
+        """
         self._test_reboot_server('HARD')
 
     @decorators.skip_because(bug="1014647")
     @decorators.idempotent_id('4640e3ef-a5df-482e-95a1-ceeeb0faa84d')
     def test_reboot_server_soft(self):
-        # The server should be signaled to reboot gracefully
+        """Test soft rebooting server
+
+        The server should be signaled to reboot gracefully.
+        """
         self._test_reboot_server('SOFT')
 
     @decorators.idempotent_id('1d1c9104-1b0a-11e7-a3d4-fa163e65f5ce')
     def test_remove_server_all_security_groups(self):
+        """Test removing all security groups from server"""
         server = self.create_test_server(wait_until='ACTIVE')
 
         # Remove all Security group
@@ -232,12 +246,19 @@
 
     @decorators.idempotent_id('aaa6cdf3-55a7-461a-add9-1c8596b9a07c')
     def test_rebuild_server(self):
+        """Test rebuilding server
+
+        The server should be rebuilt using the provided image and data.
+        """
         self._test_rebuild_server()
 
     @decorators.idempotent_id('30449a88-5aff-4f9b-9866-6ee9b17f906d')
     def test_rebuild_server_in_stop_state(self):
-        # The server in stop state  should be rebuilt using the provided
-        # image and remain in SHUTOFF state
+        """Test rebuilding server in stop state
+
+        The server in stop state should be rebuilt using the provided
+        image and remain in SHUTOFF state.
+        """
         server = self.client.show_server(self.server_id)['server']
         old_image = server['image']['id']
         new_image = (self.image_ref_alt
@@ -274,6 +295,10 @@
     @decorators.idempotent_id('b68bd8d6-855d-4212-b59b-2e704044dace')
     @utils.services('volume')
     def test_rebuild_server_with_volume_attached(self):
+        """Test rebuilding server with volume attached
+
+        The volume should be attached to the instance after rebuild.
+        """
         # create a new volume and attach it to the server
         volume = self.create_volume()
 
@@ -333,6 +358,7 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize not available.')
     def test_resize_server_confirm(self):
+        """Test resizing server and then confirming"""
         self._test_resize_server_confirm(self.server_id, stop=False)
 
     @decorators.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b')
@@ -341,6 +367,7 @@
                           'Resize not available.')
     @utils.services('volume')
     def test_resize_volume_backed_server_confirm(self):
+        """Test resizing a volume backed server and then confirming"""
         # We have to create a new server that is volume-backed since the one
         # from setUp is not volume-backed.
         kwargs = {'volume_backed': True,
@@ -377,14 +404,18 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize not available.')
     def test_resize_server_confirm_from_stopped(self):
+        """Test resizing a stopped server and then confirming"""
         self._test_resize_server_confirm(self.server_id, stop=True)
 
     @decorators.idempotent_id('c03aab19-adb1-44f5-917d-c419577e9e68')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize not available.')
     def test_resize_server_revert(self):
-        # The server's RAM and disk space should return to its original
-        # values after a resize is reverted
+        """Test resizing server and then reverting
+
+        The server's RAM and disk space should return to its original
+        values after a resize is reverted.
+        """
 
         self.client.resize_server(self.server_id, self.flavor_ref_alt)
         # NOTE(zhufl): Explicitly delete the server to get a new one for later
@@ -405,10 +436,13 @@
                           'Resize not available.')
     @utils.services('volume')
     def test_resize_server_revert_with_volume_attached(self):
-        # Tests attaching a volume to a server instance and then resizing
-        # the instance. Once the instance is resized, revert the resize which
-        # should move the instance and volume attachment back to the original
-        # compute host.
+        """Test resizing a volume attached server and then reverting
+
+        Tests attaching a volume to a server instance and then resizing
+        the instance. Once the instance is resized, revert the resize which
+        should move the instance and volume attachment back to the original
+        compute host.
+        """
 
         # Create a blank volume and attach it to the server created in setUp.
         volume = self.create_volume()
@@ -437,7 +471,14 @@
                           'Snapshotting not available, backup not possible.')
     @utils.services('image')
     def test_create_backup(self):
-        # Positive test:create backup successfully and rotate backups correctly
+        """Test creating server backup
+
+        1. create server backup1 with rotation=2, there are 1 backup.
+        2. create server backup2 with rotation=2, there are 2 backups.
+        3. create server backup3, due to the rotation is 2, the first one
+           (backup1) will be deleted, so now there are still 2 backups.
+        """
+
         # create the first and the second backup
 
         # Check if glance v1 is available to determine which client to use. We
@@ -563,8 +604,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
                           'Console output not supported.')
     def test_get_console_output(self):
-        # Positive test:Should be able to GET the console output
-        # for a given server_id and number of lines
+        """Test getting console output for a server
+
+        Should be able to GET the console output for a given server_id and
+        number of lines.
+        """
 
         # This reboot is necessary for outputting some console log after
         # creating an instance backup. If an instance backup, the console
@@ -579,6 +623,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
                           'Console output not supported.')
     def test_get_console_output_with_unlimited_size(self):
+        """Test getting server's console output with unlimited size
+
+        The console output lines length should be bigger than the one
+        of test_get_console_output.
+        """
         server = self.create_test_server(wait_until='ACTIVE')
 
         def _check_full_length_console_log():
@@ -597,8 +646,11 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
                           'Console output not supported.')
     def test_get_console_output_server_id_in_shutoff_status(self):
-        # Positive test:Should be able to GET the console output
-        # for a given server_id in SHUTOFF status
+        """Test getting console output for a server in SHUTOFF status
+
+        Should be able to GET the console output for a given server_id
+        in SHUTOFF status.
+        """
 
         # NOTE: SHUTOFF is irregular status. To avoid test instability,
         #       one server is created only for this test without using
@@ -614,6 +666,7 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.pause,
                           'Pause is not available.')
     def test_pause_unpause_server(self):
+        """Test pausing and unpausing server"""
         self.client.pause_server(self.server_id)
         waiters.wait_for_server_status(self.client, self.server_id, 'PAUSED')
         self.client.unpause_server(self.server_id)
@@ -623,6 +676,7 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
                           'Suspend is not available.')
     def test_suspend_resume_server(self):
+        """Test suspending and resuming server"""
         self.client.suspend_server(self.server_id)
         waiters.wait_for_server_status(self.client, self.server_id,
                                        'SUSPENDED')
@@ -634,6 +688,7 @@
                           'Shelve is not available.')
     @utils.services('image')
     def test_shelve_unshelve_server(self):
+        """Test shelving and unshelving server"""
         if CONF.image_feature_enabled.api_v2:
             glance_client = self.os_primary.image_client_v2
         elif CONF.image_feature_enabled.api_v1:
@@ -673,6 +728,7 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.pause,
                           'Pause is not available.')
     def test_shelve_paused_server(self):
+        """Test shelving a paused server"""
         server = self.create_test_server(wait_until='ACTIVE')
         self.client.pause_server(server['id'])
         waiters.wait_for_server_status(self.client, server['id'], 'PAUSED')
@@ -682,6 +738,7 @@
 
     @decorators.idempotent_id('af8eafd4-38a7-4a4b-bdbc-75145a580560')
     def test_stop_start_server(self):
+        """Test stopping and starting server"""
         self.client.stop_server(self.server_id)
         waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF')
         self.client.start_server(self.server_id)
@@ -689,6 +746,12 @@
 
     @decorators.idempotent_id('80a8094c-211e-440a-ab88-9e59d556c7ee')
     def test_lock_unlock_server(self):
+        """Test locking and unlocking server
+
+        Lock the server, and trying to stop it will fail because locked
+        server is not allowed to be stopped by non-admin user.
+        Then unlock the server, now the server can be stopped and started.
+        """
         # Lock the server,try server stop(exceptions throw),unlock it and retry
         self.client.lock_server(self.server_id)
         self.addCleanup(self.client.unlock_server, self.server_id)
@@ -714,6 +777,10 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.vnc_console,
                           'VNC Console feature is disabled.')
     def test_get_vnc_console(self):
+        """Test getting vnc console from a server
+
+        The returned vnc console url should be in valid format.
+        """
         if self.is_requested_microversion_compatible('2.5'):
             body = self.client.get_vnc_console(
                 self.server_id, type='novnc')['console']
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index c936ce5..5a3f5d0 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -19,6 +19,7 @@
 
 
 class ServerAddressesTestJSON(base.BaseV2ComputeTest):
+    """Test server addresses"""
     create_default_network = True
 
     @classmethod
@@ -36,8 +37,10 @@
     @decorators.idempotent_id('6eb718c0-02d9-4d5e-acd1-4e0c269cef39')
     @utils.services('network')
     def test_list_server_addresses(self):
-        # All public and private addresses for
-        # a server should be returned
+        """Test listing server address
+
+        All public and private addresses for a server should be returned.
+        """
 
         addresses = self.client.list_addresses(self.server['id'])['addresses']
 
@@ -51,8 +54,11 @@
     @decorators.idempotent_id('87bbc374-5538-4f64-b673-2b0e4443cc30')
     @utils.services('network')
     def test_list_server_addresses_by_network(self):
-        # Providing a network type should filter
-        # the addresses return by that type
+        """Test listing server addresses filtered by network addresses
+
+        Providing a network address should filter the addresses same with
+        the specified one.
+        """
 
         addresses = self.client.list_addresses(self.server['id'])['addresses']
 
diff --git a/tempest/api/compute/servers/test_server_metadata.py b/tempest/api/compute/servers/test_server_metadata.py
index 9d87e1c..9f93e76 100644
--- a/tempest/api/compute/servers/test_server_metadata.py
+++ b/tempest/api/compute/servers/test_server_metadata.py
@@ -14,13 +14,26 @@
 #    under the License.
 
 from tempest.api.compute import base
+from tempest import config
 from tempest.lib import decorators
 
+CONF = config.CONF
 
+
+# TODO(stephenfin): Remove these tests once the nova Ussuri branch goes EOL
 class ServerMetadataTestJSON(base.BaseV2ComputeTest):
+    """Test server metadata"""
+
     create_default_network = True
 
     @classmethod
+    def skip_checks(cls):
+        super(ServerMetadataTestJSON, cls).skip_checks()
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise cls.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
+    @classmethod
     def setup_clients(cls):
         super(ServerMetadataTestJSON, cls).setup_clients()
         cls.client = cls.servers_client
@@ -37,7 +50,10 @@
 
     @decorators.idempotent_id('479da087-92b3-4dcf-aeb3-fd293b2d14ce')
     def test_list_server_metadata(self):
-        # All metadata key/value pairs for a server should be returned
+        """Test listing server metadata
+
+        All metadata key/value pairs for a server should be returned.
+        """
         resp_metadata = (self.client.list_server_metadata(self.server['id'])
                          ['metadata'])
 
@@ -47,7 +63,10 @@
 
     @decorators.idempotent_id('211021f6-21de-4657-a68f-908878cfe251')
     def test_set_server_metadata(self):
-        # The server's metadata should be replaced with the provided values
+        """Test setting server metadata
+
+        The server's metadata should be replaced with the provided values
+        """
         # Create a new set of metadata for the server
         req_metadata = {'meta2': 'data2', 'meta3': 'data3'}
         self.client.set_server_metadata(self.server['id'], req_metadata)
@@ -60,8 +79,10 @@
 
     @decorators.idempotent_id('344d981e-0c33-4997-8a5d-6c1d803e4134')
     def test_update_server_metadata(self):
-        # The server's metadata values should be updated to the
-        # provided values
+        """Test updating server metadata
+
+        The server's metadata values should be updated to the provided values.
+        """
         meta = {'key1': 'alt1', 'key3': 'value3'}
         self.client.update_server_metadata(self.server['id'], meta)
 
@@ -73,8 +94,11 @@
 
     @decorators.idempotent_id('0f58d402-e34a-481d-8af8-b392b17426d9')
     def test_update_metadata_empty_body(self):
-        # The original metadata should not be lost if empty metadata body is
-        # passed
+        """Test updating server metadata to empty values
+
+        The original server metadata should not be lost if empty metadata
+        body is passed.
+        """
         meta = {}
         self.client.update_server_metadata(self.server['id'], meta)
         resp_metadata = (self.client.list_server_metadata(self.server['id'])
@@ -84,15 +108,19 @@
 
     @decorators.idempotent_id('3043c57d-7e0e-49a6-9a96-ad569c265e6a')
     def test_get_server_metadata_item(self):
-        # The value for a specific metadata key should be returned
+        """Test getting specific server metadata item"""
         meta = self.client.show_server_metadata_item(self.server['id'],
                                                      'key2')['meta']
         self.assertEqual('value2', meta['key2'])
 
     @decorators.idempotent_id('58c02d4f-5c67-40be-8744-d3fa5982eb1c')
     def test_set_server_metadata_item(self):
-        # The item's value should be updated to the provided value
-        # Update the metadata value
+        """Test updating specific server metadata item
+
+        The metadata item's value should be updated to the provided value.
+        """
+
+        # Update the metadata value.
         meta = {'nova': 'alt'}
         self.client.set_server_metadata_item(self.server['id'], 'nova', meta)
 
@@ -104,7 +132,10 @@
 
     @decorators.idempotent_id('127642d6-4c7b-4486-b7cd-07265a378658')
     def test_delete_server_metadata_item(self):
-        # The metadata value/key pair should be deleted from the server
+        """Test deleting server metadata item
+
+        The metadata value/key pair should be deleted from the server.
+        """
         self.client.delete_server_metadata_item(self.server['id'], 'key1')
 
         # Verify the metadata item has been removed
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index 5688af1..655909c 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -14,12 +14,17 @@
 #    under the License.
 
 from tempest.api.compute import base
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
+CONF = config.CONF
+
 
 class ServerMetadataNegativeTestJSON(base.BaseV2ComputeTest):
+    """Negative tests of server metadata"""
+
     create_default_network = True
 
     @classmethod
@@ -36,10 +41,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('fe114a8f-3a57-4eff-9ee2-4e14628df049')
     def test_server_create_metadata_key_too_long(self):
+        """Test creating server with too long metadata key should fail"""
         # Attempt to start a server with a meta-data key that is > 255
         # characters
 
-        # Tryset_server_metadata_item a few values
+        # Try create a server with the metadata for a few values
         for sz in [256, 257, 511, 1023]:
             key = "k" * sz
             meta = {key: 'data1'}
@@ -52,7 +58,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('92431555-4d8b-467c-b95b-b17daa5e57ff')
     def test_create_server_metadata_blank_key(self):
-        # Blank key should trigger an error.
+        """Test creating server with blank metadata key should fail"""
         meta = {'': 'data1'}
         self.assertRaises(lib_exc.BadRequest,
                           self.create_test_server,
@@ -61,6 +67,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('4d9cd7a3-2010-4b41-b8fe-3bbf0b169466')
     def test_server_metadata_non_existent_server(self):
+        """Test getting metadata item for a non existent server should fail"""
         # GET on a non-existent server should not succeed
         non_existent_server_id = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound,
@@ -71,7 +78,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('f408e78e-3066-4097-9299-3b0182da812e')
     def test_list_server_metadata_non_existent_server(self):
-        # List metadata on a non-existent server should not succeed
+        """Test listing metadata for a non existent server should fail"""
         non_existent_server_id = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound,
                           self.client.list_server_metadata,
@@ -79,9 +86,15 @@
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('0025fbd6-a4ba-4cde-b8c2-96805dcfdabc')
-    def test_wrong_key_passed_in_body(self):
-        # Raise BadRequest if key in uri does not match
-        # the key passed in body.
+    def test_set_metadata_invalid_key(self):
+        """Test setting server metadata item with wrong key in body
+
+        Raise BadRequest if key in uri does not match the key passed in body.
+        """
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
         meta = {'testkey': 'testvalue'}
         self.assertRaises(lib_exc.BadRequest,
                           self.client.set_server_metadata_item,
@@ -90,7 +103,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('0df38c2a-3d4e-4db5-98d8-d4d9fa843a12')
     def test_set_metadata_non_existent_server(self):
-        # Set metadata on a non-existent server should not succeed
+        """Test setting metadata for a non existent server should fail"""
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
         non_existent_server_id = data_utils.rand_uuid()
         meta = {'meta1': 'data1'}
         self.assertRaises(lib_exc.NotFound,
@@ -101,7 +118,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('904b13dc-0ef2-4e4c-91cd-3b4a0f2f49d8')
     def test_update_metadata_non_existent_server(self):
-        # An update should not happen for a non-existent server
+        """Test updating metadata for a non existent server should fail"""
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
         non_existent_server_id = data_utils.rand_uuid()
         meta = {'key1': 'value1', 'key2': 'value2'}
         self.assertRaises(lib_exc.NotFound,
@@ -112,7 +133,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('a452f38c-05c2-4b47-bd44-a4f0bf5a5e48')
     def test_update_metadata_with_blank_key(self):
-        # Blank key should trigger an error
+        """Test updating server metadata to blank key should fail"""
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
         meta = {'': 'data1'}
         self.assertRaises(lib_exc.BadRequest,
                           self.client.update_server_metadata,
@@ -121,7 +146,14 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('6bbd88e1-f8b3-424d-ba10-ae21c45ada8d')
     def test_delete_metadata_non_existent_server(self):
-        # Should not be able to delete metadata item from a non-existent server
+        """Test deleting metadata item from a non existent server
+
+        Should not be able to delete metadata item from a non-existent server.
+        """
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
         non_existent_server_id = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound,
                           self.client.delete_server_metadata_item,
@@ -131,9 +163,15 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('d8c0a210-a5c3-4664-be04-69d96746b547')
     def test_metadata_items_limit(self):
-        # A 403 Forbidden or 413 Overlimit (old behaviour) exception
-        # will be raised while exceeding metadata items limit for
-        # tenant.
+        """Test set/update server metadata over limit should fail
+
+        A 403 Forbidden or 413 Overlimit (old behaviour) exception
+        will be raised while exceeding metadata items limit for project.
+        """
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
         quota_set = self.quotas_client.show_quota_set(
             self.tenant_id)['quota_set']
         quota_metadata = quota_set['metadata_items']
@@ -157,8 +195,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('96100343-7fa9-40d8-80fa-d29ef588ce1c')
     def test_set_server_metadata_blank_key(self):
-        # Raise a bad request error for blank key.
-        # set_server_metadata will replace all metadata with new value
+        """Test setting server metadata with blank key should fail"""
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
         meta = {'': 'data1'}
         self.assertRaises(lib_exc.BadRequest,
                           self.client.set_server_metadata,
@@ -167,8 +208,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('64a91aee-9723-4863-be44-4c9d9f1e7d0e')
     def test_set_server_metadata_missing_metadata(self):
-        # Raise a bad request error for a missing metadata field
-        # set_server_metadata will replace all metadata with new value
+        """Test setting server metadata without metadata field should fail"""
+        if not CONF.compute_feature_enabled.xenapi_apis:
+            raise self.skipException(
+                'Metadata is read-only on non-Xen-based deployments.')
+
         meta = {'meta1': 'data1'}
         self.assertRaises(lib_exc.BadRequest,
                           self.client.set_server_metadata,
diff --git a/tempest/api/compute/servers/test_server_password.py b/tempest/api/compute/servers/test_server_password.py
index 7b31ede..f61d4fd 100644
--- a/tempest/api/compute/servers/test_server_password.py
+++ b/tempest/api/compute/servers/test_server_password.py
@@ -19,6 +19,8 @@
 
 
 class ServerPasswordTestJSON(base.BaseV2ComputeTest):
+    """Test server password"""
+
     create_default_network = True
 
     @classmethod
@@ -28,8 +30,10 @@
 
     @decorators.idempotent_id('f83b582f-62a8-4f22-85b0-0dee50ff783a')
     def test_get_server_password(self):
+        """Test getting password of a server"""
         self.servers_client.show_password(self.server['id'])
 
     @decorators.idempotent_id('f8229e8b-b625-4493-800a-bde86ac611ea')
     def test_delete_server_password(self):
+        """Test deleting password from a server"""
         self.servers_client.delete_password(self.server['id'])
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index ba2adbb..8a05e7a 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -29,6 +29,7 @@
 
 class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
     """Test servers with injected files"""
+    max_microversion = '2.56'
 
     @classmethod
     def setup_credentials(cls):
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 3a4bd6d..cc013e3 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -25,6 +25,7 @@
 
 
 class ServersTestJSON(base.BaseV2ComputeTest):
+    """Test servers API"""
     create_default_network = True
 
     @classmethod
@@ -37,8 +38,11 @@
                           enable_instance_password,
                           'Instance password not available.')
     def test_create_server_with_admin_password(self):
-        # If an admin password is provided on server creation, the server's
-        # root password should be set to that password.
+        """Test creating server with admin password
+
+        If an admin password is provided on server creation, the server's
+        root password should be set to that password.
+        """
         server = self.create_test_server(adminPass='testpassword')
         self.addCleanup(self.delete_server, server['id'])
 
@@ -47,8 +51,7 @@
 
     @decorators.idempotent_id('8fea6be7-065e-47cf-89b8-496e6f96c699')
     def test_create_with_existing_server_name(self):
-        # Creating a server with a name that already exists is allowed
-
+        """Test creating a server with already existing name is allowed"""
         # TODO(sdague): clear out try, we do cleanup one layer up
         server_name = data_utils.rand_name(
             self.__class__.__name__ + '-server')
@@ -69,8 +72,7 @@
 
     @decorators.idempotent_id('f9e15296-d7f9-4e62-b53f-a04e89160833')
     def test_create_specify_keypair(self):
-        # Specify a keypair while creating a server
-
+        """Test creating server with keypair"""
         key_name = data_utils.rand_name('key')
         self.keypairs_client.create_keypair(name=key_name)
         self.addCleanup(self.keypairs_client.delete_keypair, key_name)
@@ -97,7 +99,7 @@
 
     @decorators.idempotent_id('5e6ccff8-349d-4852-a8b3-055df7988dd2')
     def test_update_server_name(self):
-        # The server name should be changed to the provided value
+        """Test updating server name to the provided value"""
         server = self.create_test_server(wait_until='ACTIVE')
         self.addCleanup(self.delete_server, server['id'])
         # Update instance name with non-ASCII characters
@@ -115,7 +117,7 @@
 
     @decorators.idempotent_id('89b90870-bc13-4b73-96af-f9d4f2b70077')
     def test_update_access_server_address(self):
-        # The server's access addresses should reflect the provided values
+        """Test updating server's access addresses to the provided value"""
         server = self.create_test_server(wait_until='ACTIVE')
         self.addCleanup(self.delete_server, server['id'])
 
@@ -132,7 +134,7 @@
 
     @decorators.idempotent_id('38fb1d02-c3c5-41de-91d3-9bc2025a75eb')
     def test_create_server_with_ipv6_addr_only(self):
-        # Create a server without an IPv4 address(only IPv6 address).
+        """Test creating server with ipv6 address only(no ipv4 address)"""
         server = self.create_test_server(accessIPv6='2001:2001::3',
                                          wait_until='ACTIVE')
         self.addCleanup(self.delete_server, server['id'])
@@ -142,17 +144,22 @@
     @decorators.related_bug('1730756')
     @decorators.idempotent_id('defbaca5-d611-49f5-ae21-56ee25d2db49')
     def test_create_server_specify_multibyte_character_name(self):
-        # prefix character is:
-        # http://unicode.org/cldr/utility/character.jsp?a=20A1
+        """Test creating server with multi character name
 
-        # We use a string with 3 byte utf-8 character due to nova
-        # will return 400(Bad Request) if we attempt to send a name which has
-        # 4 byte utf-8 character.
+        prefix character is:
+        http://unicode.org/cldr/utility/character.jsp?a=20A1
+
+        We use a string with 3 byte utf-8 character due to nova
+        will return 400(Bad Request) if we attempt to send a name which has
+        4 byte utf-8 character.
+        """
         utf8_name = data_utils.rand_name(b'\xe2\x82\xa1'.decode('utf-8'))
         self.create_test_server(name=utf8_name, wait_until='ACTIVE')
 
 
 class ServerShowV247Test(base.BaseV2ComputeTest):
+    """Test servers API with compute microversion greater than 2.46"""
+
     min_microversion = '2.47'
     max_microversion = 'latest'
 
@@ -164,12 +171,14 @@
 
     @decorators.idempotent_id('88b0bdb2-494c-11e7-a919-92ebcb67fe33')
     def test_show_server(self):
+        """Test getting server detail"""
         server = self.create_test_server()
         # All fields will be checked by API schema
         self.servers_client.show_server(server['id'])
 
     @decorators.idempotent_id('8de397c2-57d0-4b90-aa30-e5d668f21a8b')
     def test_update_rebuild_list_server(self):
+        """Test update/rebuild/list server"""
         server = self.create_test_server()
         # Checking update API response schema
         self.servers_client.update_server(server['id'])
@@ -184,6 +193,8 @@
 
 
 class ServerShowV263Test(base.BaseV2ComputeTest):
+    """Test servers API with compute microversion greater than 2.62"""
+
     min_microversion = '2.63'
     max_microversion = 'latest'
 
@@ -195,6 +206,7 @@
                           'required to test image certificate validation.')
     @decorators.idempotent_id('71b8e3d5-11d2-494f-b917-b094a4afed3c')
     def test_show_update_rebuild_list_server(self):
+        """Test show/update/rebuild/list server"""
         trusted_certs = CONF.compute.certified_image_trusted_certs
         server = self.create_test_server(
             image_id=CONF.compute.certified_image_ref,
diff --git a/tempest/api/compute/servers/test_servers_microversions.py b/tempest/api/compute/servers/test_servers_microversions.py
index 2434884..566d04a 100644
--- a/tempest/api/compute/servers/test_servers_microversions.py
+++ b/tempest/api/compute/servers/test_servers_microversions.py
@@ -32,11 +32,13 @@
 
 
 class ServerShowV254Test(base.BaseV2ComputeTest):
+    """Test servers API schema for compute microversion greater than 2.53"""
     min_microversion = '2.54'
     max_microversion = 'latest'
 
     @decorators.idempotent_id('09170a98-4940-4637-add7-1a35121f1a5a')
     def test_rebuild_server(self):
+        """Test rebuilding server with microversion greater than 2.53"""
         server = self.create_test_server(wait_until='ACTIVE')
         keypair_name = data_utils.rand_name(
             self.__class__.__name__ + '-keypair')
@@ -52,11 +54,13 @@
 
 
 class ServerShowV257Test(base.BaseV2ComputeTest):
+    """Test servers API schema for compute microversion greater than 2.56"""
     min_microversion = '2.57'
     max_microversion = 'latest'
 
     @decorators.idempotent_id('803df848-080a-4261-8f11-b020cd9b6f60')
     def test_rebuild_server(self):
+        """Test rebuilding server with microversion greater than 2.56"""
         server = self.create_test_server(wait_until='ACTIVE')
         user_data = "ZWNobyAiaGVsbG8gd29ybGQi"
         # Checking rebuild API response schema
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 6676358..4f85048 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -60,7 +60,8 @@
         server = cls.create_test_server(wait_until='ACTIVE')
         cls.server_id = server['id']
 
-        server = cls.create_test_server()
+        # Wait until the instance is active to avoid the delete racing
+        server = cls.create_test_server(wait_until='ACTIVE')
         cls.client.delete_server(server['id'])
         waiters.wait_for_server_termination(cls.client, server['id'])
         cls.deleted_server_id = server['id']
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index dfd6ca4..b2e02c5 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -28,6 +28,8 @@
 # TODO(mriedem): Remove this test class once the nova queens branch goes into
 # extended maintenance mode.
 class VirtualInterfacesTestJSON(base.BaseV2ComputeTest):
+    """Test virtual interfaces API with compute microversion less than 2.44"""
+
     max_microversion = '2.43'
 
     depends_on_nova_network = True
@@ -47,9 +49,7 @@
     @decorators.idempotent_id('96c4e2ef-5e4d-4d7f-87f5-fed6dca18016')
     @utils.services('network')
     def test_list_virtual_interfaces(self):
-        # Positive test:Should be able to GET the virtual interfaces list
-        # for a given server_id
-
+        """Test listing virtual interfaces of a server"""
         if CONF.service_available.neutron:
             with testtools.ExpectedException(exceptions.BadRequest):
                 self.client.list_virtual_interfaces(self.server['id'])
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
index f6e8bc9..5667281 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -23,6 +23,12 @@
 # TODO(mriedem): Remove this test class once the nova queens branch goes into
 # extended maintenance mode.
 class VirtualInterfacesNegativeTestJSON(base.BaseV2ComputeTest):
+    """Negative tests of virtual interfaces API
+
+    Negative tests of virtual interfaces API for compute microversion less
+    than 2.44.
+    """
+
     max_microversion = '2.43'
 
     depends_on_nova_network = True
@@ -37,8 +43,7 @@
     @decorators.idempotent_id('64ebd03c-1089-4306-93fa-60f5eb5c803c')
     @utils.services('network')
     def test_list_virtual_interfaces_invalid_server_id(self):
-        # Negative test: Should not be able to GET virtual interfaces
-        # for an invalid server_id
+        """Test listing virtual interfaces of an invalid server should fail"""
         invalid_server_id = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound,
                           self.servers_client.list_virtual_interfaces,
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index a62492d..5fe0e3b 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -20,6 +20,7 @@
 
 
 class QuotasTestJSON(base.BaseV2ComputeTest):
+    """Test compute quotas"""
 
     @classmethod
     def skip_checks(cls):
@@ -59,7 +60,7 @@
 
     @decorators.idempotent_id('f1ef0a97-dbbb-4cca-adc5-c9fbc4f76107')
     def test_get_quotas(self):
-        # User can get the quota set for it's tenant
+        """Test user can get the compute quota set for it's project"""
         expected_quota_set = self.default_quota_set | set(['id'])
         quota_set = self.client.show_quota_set(self.tenant_id)['quota_set']
         self.assertEqual(quota_set['id'], self.tenant_id)
@@ -75,7 +76,7 @@
 
     @decorators.idempotent_id('9bfecac7-b966-4f47-913f-1a9e2c12134a')
     def test_get_default_quotas(self):
-        # User can get the default quota set for it's tenant
+        """Test user can get the default compute quota set for it's project"""
         expected_quota_set = self.default_quota_set | set(['id'])
         quota_set = (self.client.show_default_quota_set(self.tenant_id)
                      ['quota_set'])
@@ -85,7 +86,7 @@
 
     @decorators.idempotent_id('cd65d997-f7e4-4966-a7e9-d5001b674fdc')
     def test_compare_tenant_quotas_with_default_quotas(self):
-        # Tenants are created with the default quota values
+        """Test tenants are created with the default compute quota values"""
         default_quota_set = \
             self.client.show_default_quota_set(self.tenant_id)['quota_set']
         tenant_quota_set = (self.client.show_quota_set(self.tenant_id)
diff --git a/tempest/api/compute/volumes/test_volume_snapshots.py b/tempest/api/compute/volumes/test_volume_snapshots.py
index f3ccf8d..30bea60 100644
--- a/tempest/api/compute/volumes/test_volume_snapshots.py
+++ b/tempest/api/compute/volumes/test_volume_snapshots.py
@@ -24,6 +24,7 @@
 
 
 class VolumesSnapshotsTestJSON(base.BaseV2ComputeTest):
+    """Test volume snapshots with compute microversion less than 2.36"""
 
     # These tests will fail with a 404 starting from microversion 2.36. For
     # more information, see:
@@ -48,6 +49,7 @@
 
     @decorators.idempotent_id('cd4ec87d-7825-450d-8040-6e2068f2da8f')
     def test_volume_snapshot_create_get_list_delete(self):
+        """Test create/get/list/delete volume snapshot"""
         volume = self.create_volume()
         self.addCleanup(self.delete_volume, volume['id'])
 
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
index 0d23c1f..554f418 100644
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -25,6 +25,7 @@
 
 
 class VolumesGetTestJSON(base.BaseV2ComputeTest):
+    """Test compute volumes API with microversion less than 2.36"""
 
     # These tests will fail with a 404 starting from microversion 2.36. For
     # more information, see:
@@ -45,7 +46,7 @@
 
     @decorators.idempotent_id('f10f25eb-9775-4d9d-9cbe-1cf54dae9d5f')
     def test_volume_create_get_delete(self):
-        # CREATE, GET, DELETE Volume
+        """Test create/get/delete volume"""
         v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
         metadata = {'Type': 'work'}
         # Create volume
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index 28bc174..0b37264 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -21,6 +21,8 @@
 
 
 class VolumesTestJSON(base.BaseV2ComputeTest):
+    """Test listing volumes with compute microversion less than 2.36"""
+
     # NOTE: This test creates a number of 1G volumes. To run successfully,
     # ensure that the backing file for the volume group that Nova uses
     # has space for at least 3 1G volumes!
@@ -57,7 +59,7 @@
 
     @decorators.idempotent_id('bc2dd1a0-15af-48e5-9990-f2e75a48325d')
     def test_volume_list(self):
-        # Should return the list of Volumes
+        """Test listing volumes should return all volumes"""
         # Fetch all Volumes
         fetched_list = self.client.list_volumes()['volumes']
         # Now check if all the Volumes created in setup are in fetched list
@@ -72,7 +74,7 @@
 
     @decorators.idempotent_id('bad0567a-5a4f-420b-851e-780b55bb867c')
     def test_volume_list_with_details(self):
-        # Should return the list of Volumes with details
+        """Test listing volumes with detail should return all volumes"""
         # Fetch all Volumes
         fetched_list = self.client.list_volumes(detail=True)['volumes']
         # Now check if all the Volumes created in setup are in fetched list
@@ -87,7 +89,11 @@
 
     @decorators.idempotent_id('1048ed81-2baf-487a-b284-c0622b86e7b8')
     def test_volume_list_param_limit(self):
-        # Return the list of volumes based on limit set
+        """Test listing volumes based on limit set
+
+        If we list volumes with limit=2, then only 2 volumes should be
+        returned.
+        """
         params = {'limit': 2}
         fetched_vol_list = self.client.list_volumes(**params)['volumes']
 
@@ -96,7 +102,11 @@
 
     @decorators.idempotent_id('33985568-4965-49d5-9bcc-0aa007ca5b7a')
     def test_volume_list_with_detail_param_limit(self):
-        # Return the list of volumes with details based on limit set.
+        """Test listing volumes with detail based on limit set
+
+        If we list volumes with detail with limit=2, then only 2 volumes with
+        detail should be returned.
+        """
         params = {'limit': 2}
         fetched_vol_list = self.client.list_volumes(detail=True,
                                                     **params)['volumes']
@@ -106,7 +116,12 @@
 
     @decorators.idempotent_id('51c22651-a074-4ea7-af0b-094f9331303e')
     def test_volume_list_param_offset_and_limit(self):
-        # Return the list of volumes based on offset and limit set.
+        """Test listing volumes based on offset and limit set
+
+        If we list volumes with offset=1 and limit=1, then 1 volume located
+        in the position 1 in the all volumes list should be returned.
+        (The items in the all volumes list start from position 0.)
+        """
         # get all volumes list
         all_vol_list = self.client.list_volumes()['volumes']
         params = {'offset': 1, 'limit': 1}
@@ -123,7 +138,13 @@
 
     @decorators.idempotent_id('06b6abc4-3f10-48e9-a7a1-3facc98f03e5')
     def test_volume_list_with_detail_param_offset_and_limit(self):
-        # Return the list of volumes details based on offset and limit set.
+        """Test listing volumes with detail based on offset and limit set
+
+        If we list volumes with detail with offset=1 and limit=1, then 1
+        volume with detail located in the position 1 in the all volumes list
+        should be returned.
+        (The items in the all volumes list start from position 0.)
+        """
         # get all volumes list
         all_vol_list = self.client.list_volumes(detail=True)['volumes']
         params = {'offset': 1, 'limit': 1}
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index 444ce93..f553e32 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -23,6 +23,7 @@
 
 
 class VolumesNegativeTest(base.BaseV2ComputeTest):
+    """Negative tests of volumes with compute microversion less than 2.36"""
 
     # These tests will fail with a 404 starting from microversion 2.36. For
     # more information, see:
@@ -44,7 +45,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('c03ea686-905b-41a2-8748-9635154b7c57')
     def test_volume_get_nonexistent_volume_id(self):
-        # Negative: Should not be able to get details of nonexistent volume
+        """Test getting details of a non existent volume should fail"""
         # Creating a nonexistent volume id
         # Trying to GET a non existent volume
         self.assertRaises(lib_exc.NotFound, self.client.show_volume,
@@ -53,7 +54,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('54a34226-d910-4b00-9ef8-8683e6c55846')
     def test_volume_delete_nonexistent_volume_id(self):
-        # Negative: Should not be able to delete nonexistent Volume
+        """Test deleting a nonexistent volume should fail"""
         # Creating nonexistent volume id
         # Trying to DELETE a non existent volume
         self.assertRaises(lib_exc.NotFound, self.client.delete_volume,
@@ -62,8 +63,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('5125ae14-152b-40a7-b3c5-eae15e9022ef')
     def test_create_volume_with_invalid_size(self):
-        # Negative: Should not be able to create volume with invalid size
-        # in request
+        """Test creating volume with invalid size should fail"""
         v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
         metadata = {'Type': 'work'}
         self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
@@ -72,8 +72,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('131cb3a1-75cc-4d40-b4c3-1317f64719b0')
     def test_create_volume_without_passing_size(self):
-        # Negative: Should not be able to create volume without passing size
-        # in request
+        """Test creating volume without specifying size should fail"""
         v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
         metadata = {'Type': 'work'}
         self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
@@ -82,7 +81,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('8cce995e-0a83-479a-b94d-e1e40b8a09d1')
     def test_create_volume_with_size_zero(self):
-        # Negative: Should not be able to create volume with size zero
+        """Test creating volume with size=0 should fail"""
         v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
         metadata = {'Type': 'work'}
         self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
@@ -91,14 +90,13 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('62bab09a-4c03-4617-8cca-8572bc94af9b')
     def test_get_volume_without_passing_volume_id(self):
-        # Negative: Should not be able to get volume when empty ID is passed
+        """Test getting volume details without volume id should fail"""
         self.assertRaises(lib_exc.NotFound, self.client.show_volume, '')
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('62972737-124b-4513-b6cf-2f019f178494')
     def test_delete_invalid_volume_id(self):
-        # Negative: Should not be able to delete volume when invalid ID is
-        # passed
+        """Test deleting volume with an invalid volume id should fail"""
         self.assertRaises(lib_exc.NotFound,
                           self.client.delete_volume,
                           data_utils.rand_name('invalid'))
@@ -106,5 +104,5 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('0d1417c5-4ae8-4c2c-adc5-5f0b864253e5')
     def test_delete_volume_without_passing_volume_id(self):
-        # Negative: Should not be able to delete volume when empty ID is passed
+        """Test deleting volume without volume id should fail"""
         self.assertRaises(lib_exc.NotFound, self.client.delete_volume, '')
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 282343c..5722f0e 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -192,6 +192,7 @@
             cls.os_primary.identity_versions_v3_client
         cls.non_admin_app_creds_client = \
             cls.os_primary.application_credentials_client
+        cls.non_admin_access_rules_client = cls.os_primary.access_rules_client
 
 
 class BaseIdentityV3AdminTest(BaseIdentityV3Test):
diff --git a/tempest/api/identity/v3/test_access_rules.py b/tempest/api/identity/v3/test_access_rules.py
new file mode 100644
index 0000000..608eb59
--- /dev/null
+++ b/tempest/api/identity/v3/test_access_rules.py
@@ -0,0 +1,84 @@
+# Copyright 2019 SUSE LLC
+#
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.identity import base
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+CONF = config.CONF
+
+
+class AccessRulesV3Test(base.BaseIdentityV3Test):
+
+    @classmethod
+    def skip_checks(cls):
+        super(AccessRulesV3Test, cls).skip_checks()
+        if not CONF.identity_feature_enabled.access_rules:
+            raise cls.skipException("Application credential access rules are "
+                                    "not available in this environment")
+
+    @classmethod
+    def resource_setup(cls):
+        super(AccessRulesV3Test, cls).resource_setup()
+        cls.user_id = cls.os_primary.credentials.user_id
+        cls.project_id = cls.os_primary.credentials.project_id
+
+    def setUp(self):
+        super(AccessRulesV3Test, self).setUp()
+        ac = self.non_admin_app_creds_client
+        access_rules = [
+            {
+                "path": "/v2.1/servers/*/ips",
+                "method": "GET",
+                "service": "compute"
+            }
+        ]
+        self.app_cred = ac.create_application_credential(
+            self.user_id,
+            name=data_utils.rand_name('application_credential'),
+            access_rules=access_rules
+        )['application_credential']
+
+    @decorators.idempotent_id('2354c498-5119-4ba5-9f0d-44f16f78fb0e')
+    def test_list_access_rules(self):
+        ar = self.non_admin_access_rules_client.list_access_rules(self.user_id)
+        self.assertEqual(1, len(ar['access_rules']))
+
+    @decorators.idempotent_id('795dd507-ca1e-40e9-ba90-ff0a08689ba4')
+    def test_show_access_rule(self):
+        access_rule_id = self.app_cred['access_rules'][0]['id']
+        self.non_admin_access_rules_client.show_access_rule(
+            self.user_id, access_rule_id)
+
+    @decorators.idempotent_id('278757e9-e193-4bf8-adf2-0b0a229a17d0')
+    def test_delete_access_rule(self):
+        access_rule_id = self.app_cred['access_rules'][0]['id']
+        app_cred_id = self.app_cred['id']
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.non_admin_access_rules_client.delete_access_rule,
+            self.user_id,
+            access_rule_id)
+        self.non_admin_app_creds_client.delete_application_credential(
+            self.user_id, app_cred_id)
+        ar = self.non_admin_access_rules_client.list_access_rules(self.user_id)
+        self.assertEqual(1, len(ar['access_rules']))
+        self.non_admin_access_rules_client.delete_access_rule(
+            self.user_id, access_rule_id)
+        ar = self.non_admin_access_rules_client.list_access_rules(self.user_id)
+        self.assertEqual(0, len(ar['access_rules']))
diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py
index ef1bbdf..77ad720 100644
--- a/tempest/api/identity/v3/test_application_credentials.py
+++ b/tempest/api/identity/v3/test_application_credentials.py
@@ -19,8 +19,11 @@
 from oslo_utils import timeutils
 
 from tempest.api.identity import base
+from tempest import config
 from tempest.lib import decorators
 
+CONF = config.CONF
+
 
 class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test):
     """Test application credentials"""
@@ -65,6 +68,24 @@
         expires_str = expires_at.isoformat()
         self.assertEqual(expires_str, app_cred['expires_at'])
 
+    @decorators.idempotent_id('529936eb-aa5d-463d-9f79-01c113d3b88f')
+    def test_create_application_credential_access_rules(self):
+        if not CONF.identity_feature_enabled.access_rules:
+            raise self.skipException("Application credential access rules are "
+                                     "not available in this environment")
+        access_rules = [
+            {
+                "path": "/v2.1/servers/*/ips",
+                "method": "GET",
+                "service": "compute"
+            }
+        ]
+        app_cred = self.create_application_credential(
+            access_rules=access_rules)
+        access_rule_resp = app_cred['access_rules'][0]
+        access_rule_resp.pop('id')
+        self.assertDictEqual(access_rules[0], access_rule_resp)
+
     @decorators.idempotent_id('ff0cd457-6224-46e7-b79e-0ada4964a8a6')
     def test_list_application_credentials(self):
         """Test listing application credentials"""
diff --git a/tempest/api/identity/v3/test_ec2_credentials.py b/tempest/api/identity/v3/test_ec2_credentials.py
new file mode 100644
index 0000000..a2cbc4a
--- /dev/null
+++ b/tempest/api/identity/v3/test_ec2_credentials.py
@@ -0,0 +1,113 @@
+# Copyright 2020 SUSE LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.identity import base
+from tempest.common import utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class EC2CredentialsTest(base.BaseIdentityV3Test):
+
+    @classmethod
+    def skip_checks(cls):
+        super(EC2CredentialsTest, cls).skip_checks()
+        if not utils.is_extension_enabled('OS-EC2', 'identity'):
+            msg = "OS-EC2 identity extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(EC2CredentialsTest, cls).resource_setup()
+        cls.creds = cls.os_primary.credentials
+
+    @decorators.idempotent_id('b0f55a29-54e5-4166-999d-712347e0c920')
+    def test_create_ec2_credential(self):
+        """Create user ec2 credential."""
+        resp = self.non_admin_users_client.create_user_ec2_credential(
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
+        access = resp['access']
+        self.addCleanup(
+            self.non_admin_users_client.delete_user_ec2_credential,
+            self.creds.user_id, access)
+        self.assertNotEmpty(resp['access'])
+        self.assertNotEmpty(resp['secret'])
+        self.assertEqual(self.creds.user_id, resp['user_id'])
+        self.assertEqual(self.creds.tenant_id, resp['tenant_id'])
+
+    @decorators.idempotent_id('897813f0-160c-4fdc-aabc-24ee635ce4a9')
+    def test_list_ec2_credentials(self):
+        """Get the list of user ec2 credentials."""
+        created_creds = []
+        # create first ec2 credentials
+        creds1 = self.non_admin_users_client.create_user_ec2_credential(
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
+        created_creds.append(creds1['access'])
+        self.addCleanup(
+            self.non_admin_users_client.delete_user_ec2_credential,
+            self.creds.user_id, creds1['access'])
+
+        # create second ec2 credentials
+        creds2 = self.non_admin_users_client.create_user_ec2_credential(
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
+        created_creds.append(creds2['access'])
+        self.addCleanup(
+            self.non_admin_users_client.delete_user_ec2_credential,
+            self.creds.user_id, creds2['access'])
+
+        # get the list of user ec2 credentials
+        resp = self.non_admin_users_client.list_user_ec2_credentials(
+            self.creds.user_id)["credentials"]
+        fetched_creds = [cred['access'] for cred in resp]
+        # created credentials should be in a fetched list
+        missing = [cred for cred in created_creds
+                   if cred not in fetched_creds]
+        self.assertEmpty(missing,
+                         "Failed to find ec2_credentials %s in fetched list" %
+                         ', '.join(cred for cred in missing))
+
+    @decorators.idempotent_id('8b8d1010-5958-48df-a6cd-5e3df72e6bcf')
+    def test_show_ec2_credential(self):
+        """Get the definite user ec2 credential."""
+        resp = self.non_admin_users_client.create_user_ec2_credential(
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
+        self.addCleanup(
+            self.non_admin_users_client.delete_user_ec2_credential,
+            self.creds.user_id, resp['access'])
+
+        ec2_creds = self.non_admin_users_client.show_user_ec2_credential(
+            self.creds.user_id, resp['access']
+        )["credential"]
+        for key in ['access', 'secret', 'user_id', 'tenant_id']:
+            self.assertEqual(ec2_creds[key], resp[key])
+
+    @decorators.idempotent_id('9408d61b-8be0-4a8d-9b85-14f61edb456b')
+    def test_delete_ec2_credential(self):
+        """Delete user ec2 credential."""
+        resp = self.non_admin_users_client.create_user_ec2_credential(
+            self.creds.user_id,
+            tenant_id=self.creds.tenant_id)["credential"]
+        access = resp['access']
+        self.non_admin_users_client.delete_user_ec2_credential(
+            self.creds.user_id, access)
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.non_admin_users_client.show_user_ec2_credential,
+            self.creds.user_id,
+            access)
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index ae7b3e4..d3dc19a 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -18,6 +18,7 @@
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions
 import tempest.test
 
 CONF = config.CONF
@@ -155,6 +156,15 @@
                         namespace_name)
         return namespace
 
+    @classmethod
+    def get_available_stores(cls):
+        stores = []
+        try:
+            stores = cls.client.info_stores()['stores']
+        except exceptions.NotFound:
+            pass
+        return stores
+
 
 class BaseV2MemberImageTest(BaseV2ImageTest):
 
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index c1a7211..28299a4 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -20,6 +20,7 @@
 
 from oslo_log import log as logging
 from tempest.api.image import base
+from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -113,6 +114,95 @@
         self.client.wait_for_resource_activation(image['id'])
 
 
+class MultiStoresImportImagesTest(base.BaseV2ImageTest):
+    """Test importing image in multiple stores"""
+    @classmethod
+    def skip_checks(cls):
+        super(MultiStoresImportImagesTest, cls).skip_checks()
+        if not CONF.image_feature_enabled.import_image:
+            skip_msg = (
+                "%s skipped as image import is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(MultiStoresImportImagesTest, cls).resource_setup()
+        cls.available_import_methods = cls.client.info_import()[
+            'import-methods']['value']
+        if not cls.available_import_methods:
+            raise cls.skipException('Server does not support '
+                                    'any import method')
+
+        # NOTE(pdeore): Skip if glance-direct import method and mutlistore
+        # are not enabled/configured, or only one store is configured in
+        # multiple stores setup.
+        cls.available_stores = cls.get_available_stores()
+        if ('glance-direct' not in cls.available_import_methods or
+                not len(cls.available_stores) > 1):
+            raise cls.skipException(
+                'Either glance-direct import method not present in %s or '
+                'None or only one store is '
+                'configured %s' % (cls.available_import_methods,
+                                   cls.available_stores))
+
+    def _create_and_stage_image(self, all_stores=False):
+        """Create Image & stage image file for glance-direct import method."""
+        image_name = data_utils.rand_name('test-image')
+        container_format = CONF.image.container_formats[0]
+        disk_format = CONF.image.disk_formats[0]
+        image = self.create_image(name=image_name,
+                                  container_format=container_format,
+                                  disk_format=disk_format,
+                                  visibility='private')
+        self.assertEqual('queued', image['status'])
+
+        self.client.stage_image_file(
+            image['id'],
+            six.BytesIO(data_utils.random_bytes(10485760)))
+        # Check image status is 'uploading'
+        body = self.client.show_image(image['id'])
+        self.assertEqual(image['id'], body['id'])
+        self.assertEqual('uploading', body['status'])
+
+        if all_stores:
+            stores_list = ','.join([store['id']
+                                    for store in self.available_stores])
+        else:
+            stores = [store['id'] for store in self.available_stores]
+            stores_list = stores[::len(stores) - 1]
+
+        return body, stores_list
+
+    @decorators.idempotent_id('bf04ff00-3182-47cb-833a-f1c6767b47fd')
+    def test_glance_direct_import_image_to_all_stores(self):
+        """Test image is imported in all available stores
+
+        Create image, import image to all available stores using glance-direct
+        import method and verify that import succeeded.
+        """
+        image, stores = self._create_and_stage_image(all_stores=True)
+
+        self.client.image_import(
+            image['id'], method='glance-direct', all_stores=True)
+
+        waiters.wait_for_image_imported_to_stores(self.client,
+                                                  image['id'], stores)
+
+    @decorators.idempotent_id('82fb131a-dd2b-11ea-aec7-340286b6c574')
+    def test_glance_direct_import_image_to_specific_stores(self):
+        """Test image is imported in all available stores
+
+        Create image, import image to specified store(s) using glance-direct
+        import method and verify that import succeeded.
+        """
+        image, stores = self._create_and_stage_image()
+        self.client.image_import(image['id'], method='glance-direct',
+                                 stores=stores)
+
+        waiters.wait_for_image_imported_to_stores(self.client, image['id'],
+                                                  (','.join(stores)))
+
+
 class BasicOperationsImagesTest(base.BaseV2ImageTest):
     """Here we test the basic operations of images"""
 
diff --git a/tempest/api/network/admin/test_floating_ips_admin_actions.py b/tempest/api/network/admin/test_floating_ips_admin_actions.py
index ad7dfb3..a8dae7c 100644
--- a/tempest/api/network/admin/test_floating_ips_admin_actions.py
+++ b/tempest/api/network/admin/test_floating_ips_admin_actions.py
@@ -62,7 +62,7 @@
         This test performs below operations:
         1. Create couple floating ips for admin and non-admin users.
         2. Verify if admin can access all floating ips including other user
-           and non-admin user can only access its own floating ips.
+        and non-admin user can only access its own floating ips.
         """
         # Create floating ip from admin user
         floating_ip_admin = self.admin_floating_ips_client.create_floatingip(
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index ff5026b..0b9d381 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -65,6 +65,8 @@
             name=data_utils.rand_name(self.__class__.__name__),
             allowed_address_pairs=allowed_address_pairs)
         port_id = body['port']['id']
+        self.addCleanup(self.ports_client.wait_for_resource_deletion,
+                        port_id)
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.ports_client.delete_port, port_id)
 
@@ -82,6 +84,8 @@
             network_id=self.network['id'],
             name=data_utils.rand_name(self.__class__.__name__))
         port_id = body['port']['id']
+        self.addCleanup(self.ports_client.wait_for_resource_deletion,
+                        port_id)
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.ports_client.delete_port, port_id)
         if mac_address is None:
@@ -115,6 +119,8 @@
             network_id=self.network['id'],
             name=data_utils.rand_name(self.__class__.__name__))
         newportid = resp['port']['id']
+        self.addCleanup(self.ports_client.wait_for_resource_deletion,
+                        newportid)
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.ports_client.delete_port, newportid)
         ipaddress = resp['port']['fixed_ips'][0]['ip_address']
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index c5c30e3..da8ad66 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -28,6 +28,7 @@
 
 
 class AccountTest(base.BaseObjectTest):
+    """Test account metadata and containers"""
 
     credentials = [['operator', CONF.object_storage.operator_role],
                    ['operator_alt', CONF.object_storage.operator_role]]
@@ -54,7 +55,7 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('3499406a-ae53-4f8c-b43a-133d4dc6fe3f')
     def test_list_containers(self):
-        # list of all containers should not be empty
+        """Test listing containers"""
         resp, container_list = self.account_client.list_account_containers()
         self.assertHeaders(resp, 'Account', 'GET')
 
@@ -66,11 +67,10 @@
 
     @decorators.idempotent_id('884ec421-fbad-4fcc-916b-0580f2699565')
     def test_list_no_containers(self):
-        # List request to empty account
+        """Test listing containers for an account without container"""
 
         # To test listing no containers, create new user other than
         # the base user of this instance.
-
         resp, container_list = \
             self.os_operator.account_client.list_account_containers()
 
@@ -103,7 +103,7 @@
 
     @decorators.idempotent_id('1c7efa35-e8a2-4b0b-b5ff-862c7fd83704')
     def test_list_containers_with_format_json(self):
-        # list containers setting format parameter to 'json'
+        """Test listing containers setting format parameter to 'json'"""
         params = {'format': 'json'}
         resp, container_list = self.account_client.list_account_containers(
             params=params)
@@ -115,7 +115,7 @@
 
     @decorators.idempotent_id('4477b609-1ca6-4d4b-b25d-ad3f01086089')
     def test_list_containers_with_format_xml(self):
-        # list containers setting format parameter to 'xml'
+        """Test listing containers setting format parameter to 'xml'"""
         params = {'format': 'xml'}
         resp, container_list = self.account_client.list_account_containers(
             params=params)
@@ -133,13 +133,18 @@
         not CONF.object_storage_feature_enabled.discoverability,
         'Discoverability function is disabled')
     def test_list_extensions(self):
+        """Test listing capabilities"""
         resp = self.capabilities_client.list_capabilities()
 
         self.assertThat(resp, custom_matchers.AreAllWellFormatted())
 
     @decorators.idempotent_id('5cfa4ab2-4373-48dd-a41f-a532b12b08b2')
     def test_list_containers_with_limit(self):
-        # list containers one of them, half of them then all of them
+        """Test listing containers with limit parameter
+
+        Listing containers limited to one of them, half of them, and then all
+        of them.
+        """
         for limit in (1, self.containers_count // 2,
                       self.containers_count):
             params = {'limit': limit}
@@ -151,10 +156,11 @@
 
     @decorators.idempotent_id('638f876d-6a43-482a-bbb3-0840bca101c6')
     def test_list_containers_with_marker(self):
-        # list containers using marker param
-        # first expect to get 0 container as we specified last
-        # the container as marker
-        # second expect to get the bottom half of the containers
+        """Test listing containers with marker parameter
+
+        First expect to get 0 container as we specified the last container
+        as marker, second expect to get the bottom half of the containers.
+        """
         params = {'marker': self.containers[-1]}
         resp, container_list = \
             self.account_client.list_account_containers(params=params)
@@ -172,10 +178,11 @@
 
     @decorators.idempotent_id('5ca164e4-7bde-43fa-bafb-913b53b9e786')
     def test_list_containers_with_end_marker(self):
-        # list containers using end_marker param
-        # first expect to get 0 container as we specified first container as
-        # end_marker
-        # second expect to get the top half of the containers
+        """Test listing containers with end_marker parameter
+
+        First expect to get 0 container as we specified first container as
+        end_marker, second expect to get the top half of the containers
+        """
         params = {'end_marker': self.containers[0]}
         resp, container_list = \
             self.account_client.list_account_containers(params=params)
@@ -190,7 +197,12 @@
 
     @decorators.idempotent_id('ac8502c2-d4e4-4f68-85a6-40befea2ef5e')
     def test_list_containers_with_marker_and_end_marker(self):
-        # list containers combining marker and end_marker param
+        """Test listing containers with marker and end_marker parameter
+
+        If we use the first container as marker, and the last container as
+        end_marker, then we should get all containers excluding the first one
+        and the last one.
+        """
         params = {'marker': self.containers[0],
                   'end_marker': self.containers[self.containers_count - 1]}
         resp, container_list = self.account_client.list_account_containers(
@@ -200,8 +212,10 @@
 
     @decorators.idempotent_id('f7064ae8-dbcc-48da-b594-82feef6ea5af')
     def test_list_containers_with_limit_and_marker(self):
-        # list containers combining marker and limit param
-        # result are always limitated by the limit whatever the marker
+        """Test listing containers combining marker and limit parameter
+
+        Result are always limited by the limit whatever the marker.
+        """
         for marker in random.choice(self.containers):
             limit = random.randint(0, self.containers_count - 1)
             params = {'marker': marker,
@@ -215,6 +229,10 @@
 
     @decorators.idempotent_id('888a3f0e-7214-4806-8e50-5e0c9a69bb5e')
     def test_list_containers_with_limit_and_end_marker(self):
+        """Test listing containers combining end_marker and limit parameter
+
+        Result are always limited by the limit whatever the end_marker.
+        """
         # list containers combining limit and end_marker param
         limit = random.randint(1, self.containers_count)
         params = {'limit': limit,
@@ -227,7 +245,11 @@
 
     @decorators.idempotent_id('8cf98d9c-e3a0-4e44-971b-c87656fdddbd')
     def test_list_containers_with_limit_and_marker_and_end_marker(self):
-        # list containers combining limit, marker and end_marker param
+        """Test listing containers combining marker and end_marker and limit
+
+        Result are always limited by the limit whatever the marker and the
+        end_marker.
+        """
         limit = random.randint(1, self.containers_count)
         params = {'limit': limit,
                   'marker': self.containers[0],
@@ -240,7 +262,7 @@
 
     @decorators.idempotent_id('365e6fc7-1cfe-463b-a37c-8bd08d47b6aa')
     def test_list_containers_with_prefix(self):
-        # list containers that have a name that starts with a prefix
+        """Test listing containers that have a name starting with a prefix"""
         prefix = 'tempest-a'
         params = {'prefix': prefix}
         resp, container_list = self.account_client.list_account_containers(
@@ -252,7 +274,7 @@
 
     @decorators.idempotent_id('b1811cff-d1ed-4c15-a52e-efd8de41cf34')
     def test_list_containers_reverse_order(self):
-        # list containers in reverse order
+        """Test listing containers in reverse order"""
         _, orig_container_list = self.account_client.list_account_containers()
 
         params = {'reverse': True}
@@ -265,8 +287,7 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('4894c312-6056-4587-8d6f-86ffbf861f80')
     def test_list_account_metadata(self):
-        # list all account metadata
-
+        """Test listing account metadata"""
         # set metadata to account
         metadata = {'test-account-meta1': 'Meta1',
                     'test-account-meta2': 'Meta2'}
@@ -282,14 +303,14 @@
 
     @decorators.idempotent_id('b904c2e3-24c2-4dba-ad7d-04e90a761be5')
     def test_list_no_account_metadata(self):
-        # list no account metadata
+        """Test listing account metadata for account without metadata"""
         resp, _ = self.account_client.list_account_metadata()
         self.assertHeaders(resp, 'Account', 'HEAD')
         self.assertNotIn('x-account-meta-', str(resp))
 
     @decorators.idempotent_id('e2a08b5f-3115-4768-a3ee-d4287acd6c08')
     def test_update_account_metadata_with_create_metadata(self):
-        # add metadata to account
+        """Test adding metadata to account"""
         metadata = {'test-account-meta1': 'Meta1'}
         resp, _ = self.account_client.create_update_or_delete_account_metadata(
             create_update_metadata=metadata)
@@ -305,7 +326,7 @@
 
     @decorators.idempotent_id('9f60348d-c46f-4465-ae06-d51dbd470953')
     def test_update_account_metadata_with_delete_metadata(self):
-        # delete metadata from account
+        """Test deleting metadata from account"""
         metadata = {'test-account-meta1': 'Meta1'}
         self.account_client.create_update_or_delete_account_metadata(
             create_update_metadata=metadata)
@@ -318,8 +339,11 @@
 
     @decorators.idempotent_id('64fd53f3-adbd-4639-af54-436e4982dbfb')
     def test_update_account_metadata_with_create_metadata_key(self):
-        # if the value of metadata is not set, the metadata is not
-        # registered at a server
+        """Test adding metadata to account with empty value
+
+        Adding metadata with empty value to account, the metadata is not
+        registered.
+        """
         metadata = {'test-account-meta1': ''}
         resp, _ = self.account_client.create_update_or_delete_account_metadata(
             create_update_metadata=metadata)
@@ -330,8 +354,11 @@
 
     @decorators.idempotent_id('d4d884d3-4696-4b85-bc98-4f57c4dd2bf1')
     def test_update_account_metadata_with_delete_metadata_key(self):
-        # Although the value of metadata is not set, the feature of
-        # deleting metadata is valid
+        """Test deleting metadata from account with empty value
+
+        Although the value of metadata is not set, the feature of deleting
+        metadata is valid, so the metadata is removed from account.
+        """
         metadata_1 = {'test-account-meta1': 'Meta1'}
         self.account_client.create_update_or_delete_account_metadata(
             create_update_metadata=metadata_1)
@@ -345,7 +372,11 @@
 
     @decorators.idempotent_id('8e5fc073-59b9-42ee-984a-29ed11b2c749')
     def test_update_account_metadata_with_create_and_delete_metadata(self):
-        # Send a request adding and deleting metadata requests simultaneously
+        """Test adding and deleting metadata simultaneously
+
+        Send a request adding and deleting metadata requests simultaneously,
+        both adding and deleting of metadata will succeed.
+        """
         metadata_1 = {'test-account-meta1': 'Meta1'}
         self.account_client.create_update_or_delete_account_metadata(
             create_update_metadata=metadata_1)
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index bdcb5ae..c5334a9 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -33,6 +33,8 @@
 
 
 class ContainerSyncTest(base.BaseObjectTest):
+    """Test container synchronization"""
+
     credentials = [['operator', CONF.object_storage.operator_role],
                    ['operator_alt', CONF.object_storage.operator_role]]
 
@@ -129,6 +131,7 @@
         not CONF.object_storage_feature_enabled.container_sync,
         'Old-style container sync function is disabled')
     def test_container_synchronization(self):
+        """Test container synchronization"""
         def make_headers(cont, cont_client):
             # tell first container to synchronize to a second
             client_proxy_ip = \
diff --git a/tempest/api/object_storage/test_container_sync_middleware.py b/tempest/api/object_storage/test_container_sync_middleware.py
index e77b079..db6cfa4 100644
--- a/tempest/api/object_storage/test_container_sync_middleware.py
+++ b/tempest/api/object_storage/test_container_sync_middleware.py
@@ -27,6 +27,7 @@
 
 
 class ContainerSyncMiddlewareTest(test_container_sync.ContainerSyncTest):
+    """Test containers synchronization specifying realm and cluster"""
 
     @classmethod
     def resource_setup(cls):
@@ -41,6 +42,7 @@
     @decorators.idempotent_id('ea4645a1-d147-4976-82f7-e5a7a3065f80')
     @utils.requires_ext(extension='container_sync', service='object')
     def test_container_synchronization(self):
+        """Test container synchronization specifying realm and cluster"""
         def make_headers(cont, cont_client):
             # tell first container to synchronize to a second
             account_name = cont_client.base_url.split('/')[-1]
diff --git a/tempest/api/object_storage/test_crossdomain.py b/tempest/api/object_storage/test_crossdomain.py
index 1567e06..c611ed6 100644
--- a/tempest/api/object_storage/test_crossdomain.py
+++ b/tempest/api/object_storage/test_crossdomain.py
@@ -19,6 +19,7 @@
 
 
 class CrossdomainTest(base.BaseObjectTest):
+    """Test crossdomain policy"""
 
     @classmethod
     def resource_setup(cls):
@@ -37,6 +38,7 @@
     @decorators.idempotent_id('d1b8b031-b622-4010-82f9-ff78a9e915c7')
     @utils.requires_ext(extension='crossdomain', service='object')
     def test_get_crossdomain_policy(self):
+        """Test getting crossdomain policy"""
         url = self.account_client._get_base_version_url() + "crossdomain.xml"
         resp, body = self.account_client.raw_request(url, "GET")
         self.account_client._error_checker(resp, body)
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 86f7c8c..6f6e32f 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -21,6 +21,8 @@
 
 
 class ObjectExpiryTest(base.BaseObjectTest):
+    """Test object expiry"""
+
     @classmethod
     def resource_setup(cls):
         super(ObjectExpiryTest, cls).resource_setup()
@@ -83,6 +85,7 @@
 
     @decorators.idempotent_id('fb024a42-37f3-4ba5-9684-4f40a7910b41')
     def test_get_object_after_expiry_time(self):
+        """Test object is expired after x-delete-after time"""
         # the 10s is important, because the get calls can take 3s each
         # some times
         metadata = {'X-Delete-After': '10'}
@@ -90,5 +93,6 @@
 
     @decorators.idempotent_id('e592f18d-679c-48fe-9e36-4be5f47102c5')
     def test_get_object_at_expiry_time(self):
+        """Test object is expired at x-delete-at time"""
         metadata = {'X-Delete-At': str(int(time.time()) + 10)}
         self._test_object_expiry(metadata)
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index acb578d..4ecbcad 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -29,6 +29,7 @@
 
 
 class ObjectTest(base.BaseObjectTest):
+    """Test storage object"""
 
     @classmethod
     def resource_setup(cls):
@@ -78,6 +79,7 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('5b4ce26f-3545-46c9-a2ba-5754358a4c62')
     def test_create_object(self):
+        """Test creating object and checking the object's uploaded content"""
         # create object
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
@@ -97,7 +99,7 @@
 
     @decorators.idempotent_id('5daebb1d-f0d5-4dc9-b541-69672eff00b0')
     def test_create_object_with_content_disposition(self):
-        # create object with content_disposition
+        """Test creating object with content-disposition"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         metadata = {}
@@ -119,7 +121,7 @@
 
     @decorators.idempotent_id('605f8317-f945-4bee-ae91-013f1da8f0a0')
     def test_create_object_with_content_encoding(self):
-        # create object with content_encoding
+        """Test creating object with content-encoding"""
         object_name = data_utils.rand_name(name='TestObject')
 
         # put compressed string
@@ -146,7 +148,7 @@
 
     @decorators.idempotent_id('73820093-0503-40b1-a478-edf0e69c7d1f')
     def test_create_object_with_etag(self):
-        # create object with etag
+        """Test creating object with Etag"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         md5 = hashlib.md5(data).hexdigest()
@@ -165,8 +167,7 @@
 
     @decorators.idempotent_id('84dafe57-9666-4f6d-84c8-0814d37923b8')
     def test_create_object_with_expect_continue(self):
-        # create object with expect_continue
-
+        """Test creating object with expect_continue"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
 
@@ -182,7 +183,7 @@
 
     @decorators.idempotent_id('4f84422a-e2f2-4403-b601-726a4220b54e')
     def test_create_object_with_transfer_encoding(self):
-        # create object with transfer_encoding
+        """Test creating object with transfer_encoding"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes(1024)
         headers = {'Transfer-Encoding': 'chunked'}
@@ -202,7 +203,10 @@
 
     @decorators.idempotent_id('0f3d62a6-47e3-4554-b0e5-1a5dc372d501')
     def test_create_object_with_x_fresh_metadata(self):
-        # create object with x_fresh_metadata
+        """Test creating object with x-fresh-metadata
+
+        The previous added metadata will be cleared.
+        """
         object_name_base = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         metadata_1 = {'X-Object-Meta-test-meta': 'Meta'}
@@ -228,7 +232,7 @@
 
     @decorators.idempotent_id('1c7ed3e4-2099-406b-b843-5301d4811baf')
     def test_create_object_with_x_object_meta(self):
-        # create object with object_meta
+        """Test creating object with x-object-meta"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -247,7 +251,7 @@
 
     @decorators.idempotent_id('e4183917-33db-4153-85cc-4dacbb938865')
     def test_create_object_with_x_object_metakey(self):
-        # create object with the blank value of metadata
+        """Test creating object with the blank value of metadata"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         metadata = {'X-Object-Meta-test-meta': ''}
@@ -266,7 +270,10 @@
 
     @decorators.idempotent_id('ce798afc-b278-45de-a5ce-2ea124b98b99')
     def test_create_object_with_x_remove_object_meta(self):
-        # create object with x_remove_object_meta
+        """Test creating object with x-remove-object-meta
+
+        The metadata will be removed from the object.
+        """
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         metadata_add = {'X-Object-Meta-test-meta': 'Meta'}
@@ -289,7 +296,11 @@
 
     @decorators.idempotent_id('ad21e342-7916-4f9e-ab62-a1f885f2aaf9')
     def test_create_object_with_x_remove_object_metakey(self):
-        # create object with the blank value of remove metadata
+        """Test creating object with the blank value of remove metadata
+
+        Creating object with blank metadata 'X-Remove-Object-Meta-test-meta',
+        metadata 'x-object-meta-test-meta' will be removed from the object.
+        """
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         metadata_add = {'X-Object-Meta-test-meta': 'Meta'}
@@ -312,7 +323,7 @@
 
     @decorators.idempotent_id('17738d45-03bd-4d45-9e0b-7b2f58f98687')
     def test_delete_object(self):
-        # create object
+        """Test deleting object"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         resp, _ = self.object_client.create_object(self.container_name,
@@ -325,7 +336,7 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('7a94c25d-66e6-434c-9c38-97d4e2c29945')
     def test_update_object_metadata(self):
-        # update object metadata
+        """Test updating object metadata"""
         object_name, _ = self.create_object(self.container_name)
 
         metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -343,7 +354,7 @@
 
     @decorators.idempotent_id('48650ed0-c189-4e1e-ad6b-1d4770c6e134')
     def test_update_object_metadata_with_remove_metadata(self):
-        # update object metadata with remove metadata
+        """Test updating object metadata with remove metadata"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         create_metadata = {'X-Object-Meta-test-meta1': 'Meta1'}
@@ -366,6 +377,11 @@
 
     @decorators.idempotent_id('f726174b-2ded-4708-bff7-729d12ce1f84')
     def test_update_object_metadata_with_create_and_remove_metadata(self):
+        """Test updating object with creation and deletion of metadata
+
+        Update object with creation and deletion of metadata with one
+        request, both operations will succeed.
+        """
         # creation and deletion of metadata with one request
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
@@ -392,8 +408,7 @@
 
     @decorators.idempotent_id('08854588-6449-4bb7-8cca-f2e1040f5e6f')
     def test_update_object_metadata_with_x_object_manifest(self):
-        # update object metadata with x_object_manifest
-
+        """Test updating object metadata with x_object_manifest"""
         # uploading segments
         object_name, _ = self._upload_segments()
         # creating a manifest file
@@ -418,7 +433,7 @@
 
     @decorators.idempotent_id('0dbbe89c-6811-4d84-a2df-eca2bdd40c0e')
     def test_update_object_metadata_with_x_object_metakey(self):
-        # update object metadata with a blank value of metadata
+        """Test updating object metadata with a blank value of metadata"""
         object_name, _ = self.create_object(self.container_name)
 
         update_metadata = {'X-Object-Meta-test-meta': ''}
@@ -436,7 +451,7 @@
 
     @decorators.idempotent_id('9a88dca4-b684-425b-806f-306cd0e57e42')
     def test_update_object_metadata_with_x_remove_object_metakey(self):
-        # update object metadata with a blank value of remove metadata
+        """Test updating object metadata with blank remove metadata value"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.arbitrary_string()
         create_metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -460,7 +475,7 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('9a447cf6-de06-48de-8226-a8c6ed31caf2')
     def test_list_object_metadata(self):
-        # get object metadata
+        """Test listing object metadata"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -478,7 +493,7 @@
 
     @decorators.idempotent_id('170fb90e-f5c3-4b1f-ae1b-a18810821172')
     def test_list_no_object_metadata(self):
-        # get empty list of object metadata
+        """Test listing object metadata for object without metadata"""
         object_name, _ = self.create_object(self.container_name)
 
         resp, _ = self.object_client.list_object_metadata(
@@ -489,8 +504,7 @@
 
     @decorators.idempotent_id('23a3674c-d6de-46c3-86af-ff92bfc8a3da')
     def test_list_object_metadata_with_x_object_manifest(self):
-        # get object metadata with x_object_manifest
-
+        """Test getting object metadata with x_object_manifest"""
         # uploading segments
         object_name, _ = self._upload_segments()
         # creating a manifest file
@@ -530,7 +544,7 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('02610ba7-86b7-4272-9ed8-aa8d417cb3cd')
     def test_get_object(self):
-        # retrieve object's data (in response body)
+        """Test retrieving object's data (in response body)"""
 
         # create object
         object_name, data = self.create_object(self.container_name)
@@ -543,7 +557,7 @@
 
     @decorators.idempotent_id('005f9bf6-e06d-41ec-968e-96c78e0b1d82')
     def test_get_object_with_metadata(self):
-        # get object with metadata
+        """Test getting object with metadata"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -562,7 +576,7 @@
 
     @decorators.idempotent_id('05a1890e-7db9-4a6c-90a8-ce998a2bddfa')
     def test_get_object_with_range(self):
-        # get object with range
+        """Test getting object with range"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes(100)
         self.object_client.create_object(self.container_name,
@@ -580,7 +594,7 @@
 
     @decorators.idempotent_id('11b4515b-7ba7-4ca8-8838-357ded86fc10')
     def test_get_object_with_x_object_manifest(self):
-        # get object with x_object_manifest
+        """Test getting object with x_object_manifest"""
 
         # uploading segments
         object_name, data_segments = self._upload_segments()
@@ -623,7 +637,7 @@
 
     @decorators.idempotent_id('c05b4013-e4de-47af-be84-e598062b16fc')
     def test_get_object_with_if_match(self):
-        # get object with if_match
+        """Test getting object with if_match"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes(10)
         create_md5 = hashlib.md5(data).hexdigest()
@@ -643,7 +657,7 @@
 
     @decorators.idempotent_id('be133639-e5d2-4313-9b1f-2d59fc054a16')
     def test_get_object_with_if_modified_since(self):
-        # get object with if_modified_since
+        """Test getting object with if_modified_since"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         time_now = time.time()
@@ -663,7 +677,7 @@
 
     @decorators.idempotent_id('641500d5-1612-4042-a04d-01fc4528bc30')
     def test_get_object_with_if_none_match(self):
-        # get object with if_none_match
+        """Test getting object with if_none_match"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
         create_md5 = hashlib.md5(data).hexdigest()
@@ -685,7 +699,7 @@
 
     @decorators.idempotent_id('0aa1201c-10aa-467a-bee7-63cbdd463152')
     def test_get_object_with_if_unmodified_since(self):
-        # get object with if_unmodified_since
+        """Test getting object with if_unmodified_since"""
         object_name, data = self.create_object(self.container_name)
 
         time_now = time.time()
@@ -700,7 +714,7 @@
 
     @decorators.idempotent_id('94587078-475f-48f9-a40f-389c246e31cd')
     def test_get_object_with_x_newest(self):
-        # get object with x_newest
+        """Test getting object with x_newest"""
         object_name, data = self.create_object(self.container_name)
 
         list_metadata = {'X-Newest': 'true'}
@@ -713,6 +727,7 @@
 
     @decorators.idempotent_id('1a9ab572-1b66-4981-8c21-416e2a5e6011')
     def test_copy_object_in_same_container(self):
+        """Test copying object to another object in same container"""
         # create source object
         src_object_name = data_utils.rand_name(name='SrcObject')
         src_data = data_utils.random_bytes(size=len(src_object_name) * 2)
@@ -742,7 +757,7 @@
 
     @decorators.idempotent_id('2248abba-415d-410b-9c30-22dff9cd6e67')
     def test_copy_object_to_itself(self):
-        # change the content type of an existing object
+        """Test changing the content type of an existing object"""
 
         # create object
         object_name, _ = self.create_object(self.container_name)
@@ -769,6 +784,7 @@
 
     @decorators.idempotent_id('06f90388-2d0e-40aa-934c-e9a8833e958a')
     def test_copy_object_2d_way(self):
+        """Test copying object's data to the new object using COPY"""
         # create source object
         src_object_name = data_utils.rand_name(name='SrcObject')
         src_data = data_utils.random_bytes(size=len(src_object_name) * 2)
@@ -793,6 +809,7 @@
 
     @decorators.idempotent_id('aa467252-44f3-472a-b5ae-5b57c3c9c147')
     def test_copy_object_across_containers(self):
+        """Test copying object to another container"""
         # create a container to use as a source container
         src_container_name = data_utils.rand_name(name='TestSourceContainer')
         self.container_client.update_container(src_container_name)
@@ -837,6 +854,7 @@
 
     @decorators.idempotent_id('5a9e2cc6-85b6-46fc-916d-0cbb7a88e5fd')
     def test_copy_object_with_x_fresh_metadata(self):
+        """Test copying objectwith x_fresh_metadata"""
         # create source object
         metadata = {'x-object-meta-src': 'src_value'}
         src_object_name, data = self.create_object(self.container_name,
@@ -858,6 +876,7 @@
 
     @decorators.idempotent_id('a28a8b99-e701-4d7e-9d84-3b66f121460b')
     def test_copy_object_with_x_object_metakey(self):
+        """Test copying object with x_object_metakey"""
         # create source object
         metadata = {'x-object-meta-src': 'src_value'}
         src_obj_name, data = self.create_object(self.container_name,
@@ -881,6 +900,7 @@
 
     @decorators.idempotent_id('edabedca-24c3-4322-9b70-d6d9f942a074')
     def test_copy_object_with_x_object_meta(self):
+        """Test copying object with x_object_meta"""
         # create source object
         metadata = {'x-object-meta-src': 'src_value'}
         src_obj_name, data = self.create_object(self.container_name,
@@ -904,6 +924,7 @@
 
     @decorators.idempotent_id('e3e6a64a-9f50-4955-b987-6ce6767c97fb')
     def test_object_upload_in_segments(self):
+        """Test uploading object in segments"""
         # create object
         object_name = data_utils.rand_name(name='LObject')
         data = data_utils.arbitrary_string()
@@ -947,10 +968,13 @@
 
     @decorators.idempotent_id('50d01f12-526f-4360-9ac2-75dd508d7b68')
     def test_get_object_if_different(self):
-        # http://en.wikipedia.org/wiki/HTTP_ETag
-        # Make a conditional request for an object using the If-None-Match
-        # header, it should get downloaded only if the local file is different,
-        # otherwise the response code should be 304 Not Modified
+        """Test getting object content only when the local file is different
+
+        http://en.wikipedia.org/wiki/HTTP_ETag
+        Make a conditional request for an object using the If-None-Match
+        header, it should get downloaded only if the local file is different,
+        otherwise the response code should be 304 Not Modified
+        """
         object_name, data = self.create_object(self.container_name)
         # local copy is identical, no download
         md5 = hashlib.md5(data).hexdigest()
@@ -975,6 +999,7 @@
 
 
 class PublicObjectTest(base.BaseObjectTest):
+    """Test public storage object"""
 
     credentials = [['operator', CONF.object_storage.operator_role],
                    ['operator_alt', CONF.object_storage.operator_role]]
@@ -1000,9 +1025,11 @@
 
     @decorators.idempotent_id('07c9cf95-c0d4-4b49-b9c8-0ef2c9b27193')
     def test_access_public_container_object_without_using_creds(self):
-        # make container public-readable and access an object in it object
-        # anonymously, without using credentials
+        """Test accessing public container object without using credentials
 
+        Make container public-readable and access an object in it object
+        anonymously, without using credentials.
+        """
         # update container metadata to make it publicly readable
         cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
         resp_meta, body = (
@@ -1040,8 +1067,11 @@
 
     @decorators.idempotent_id('54e2a2fe-42dc-491b-8270-8e4217dd4cdc')
     def test_access_public_object_with_another_user_creds(self):
-        # make container public-readable and access an object in it using
-        # another user's credentials
+        """Test accessing public object with another user's credentials
+
+        Make container public-readable and access an object in it using
+        another user's credentials.
+        """
         cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
         resp_meta, body = (
             self.container_client.create_update_or_delete_container_metadata(
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 8bb2e6e..7e553ca 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -27,6 +27,7 @@
 
 
 class ObjectSloTest(base.BaseObjectTest):
+    """Test static large object"""
 
     def setUp(self):
         super(ObjectSloTest, self).setUp()
@@ -108,7 +109,7 @@
     @decorators.idempotent_id('2c3f24a6-36e8-4711-9aa2-800ee1fc7b5b')
     @utils.requires_ext(extension='slo', service='object')
     def test_upload_manifest(self):
-        # create static large object from multipart manifest
+        """Test creating static large object from multipart manifest"""
         manifest = self._create_manifest()
 
         params = {'multipart-manifest': 'put'}
@@ -123,7 +124,10 @@
     @decorators.idempotent_id('e69ad766-e1aa-44a2-bdd2-bf62c09c1456')
     @utils.requires_ext(extension='slo', service='object')
     def test_list_large_object_metadata(self):
-        # list static large object metadata using multipart manifest
+        """Test listing static large object metadata
+
+        List static large object metadata using multipart manifest
+        """
         object_name = self._create_large_object()
 
         resp, _ = self.object_client.list_object_metadata(
@@ -135,7 +139,7 @@
     @decorators.idempotent_id('49bc49bc-dd1b-4c0f-904e-d9f10b830ee8')
     @utils.requires_ext(extension='slo', service='object')
     def test_retrieve_large_object(self):
-        # list static large object using multipart manifest
+        """Test listing static large object using multipart manifest"""
         object_name = self._create_large_object()
 
         resp, body = self.object_client.get_object(
@@ -150,7 +154,7 @@
     @decorators.idempotent_id('87b6dfa1-abe9-404d-8bf0-6c3751e6aa77')
     @utils.requires_ext(extension='slo', service='object')
     def test_delete_large_object(self):
-        # delete static large object using multipart manifest
+        """Test deleting static large object using multipart manifest"""
         object_name = self._create_large_object()
 
         params_del = {'multipart-manifest': 'delete'}
diff --git a/tempest/api/volume/admin/test_backends_capabilities.py b/tempest/api/volume/admin/test_backends_capabilities.py
index 1351704..3c76eca 100644
--- a/tempest/api/volume/admin/test_backends_capabilities.py
+++ b/tempest/api/volume/admin/test_backends_capabilities.py
@@ -20,6 +20,7 @@
 
 
 class BackendsCapabilitiesAdminTestsJSON(base.BaseVolumeAdminTest):
+    """Test backends capabilities"""
 
     @classmethod
     def resource_setup(cls):
@@ -32,14 +33,16 @@
 
     @decorators.idempotent_id('3750af44-5ea2-4cd4-bc3e-56e7e6caf854')
     def test_get_capabilities_backend(self):
-        # Test backend properties
+        """Test getting backend capabilities"""
         # Check response schema
         self.admin_capabilities_client.show_backend_capabilities(self.hosts[0])
 
     @decorators.idempotent_id('a9035743-d46a-47c5-9cb7-3c80ea16dea0')
     def test_compare_volume_stats_values(self):
-        # Test values comparison between show_backend_capabilities
-        # to show_pools
+        """Test comparing volume stats values
+
+        Compare volume stats between show_backend_capabilities and show_pools.
+        """
         VOLUME_STATS = ('vendor_name',
                         'volume_backend_name',
                         'storage_protocol')
diff --git a/tempest/api/volume/admin/test_encrypted_volumes_extend.py b/tempest/api/volume/admin/test_encrypted_volumes_extend.py
new file mode 100644
index 0000000..7339179
--- /dev/null
+++ b/tempest/api/volume/admin/test_encrypted_volumes_extend.py
@@ -0,0 +1,35 @@
+#    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.
+
+import testtools
+
+from tempest.api.volume import base
+from tempest.api.volume import test_volumes_extend as extend
+from tempest.common import utils
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class EncryptedVolumesExtendAttachedTest(extend.BaseVolumesExtendAttachedTest,
+                                         base.BaseVolumeAdminTest):
+    """Tests extending the size of an attached encrypted volume."""
+
+    @decorators.idempotent_id('e93243ec-7c37-4b5b-a099-ebf052c13216')
+    @testtools.skipUnless(
+        CONF.volume_feature_enabled.extend_attached_encrypted_volume,
+        "Attached encrypted volume extend is disabled.")
+    @utils.services('compute')
+    def test_extend_attached_encrypted_volume_luksv1(self):
+        volume = self.create_encrypted_volume(encryption_provider="luks")
+        self._test_extend_attached_volume(volume)
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index c57766e..0a8b56d 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -62,12 +62,26 @@
 
 
 class GroupSnapshotsTest(BaseGroupSnapshotsTest):
+    """Test group snapshot"""
+
     _api_version = 3
     min_microversion = '3.14'
     max_microversion = 'latest'
 
     @decorators.idempotent_id('1298e537-f1f0-47a3-a1dd-8adec8168897')
     def test_group_snapshot_create_show_list_delete(self):
+        """Test create/show/list/delete group snapshot
+
+        1. Create volume type "volume_type1"
+        2. Create group type "group_type1"
+        3. Create group "group1" with "group_type1" and "volume_type1"
+        4. Create volume "volume1" with "volume_type1" and "group1"
+        5. Create group snapshot "group_snapshot1" with "group1"
+        6. Check snapshot created from "volume1" reaches available status
+        7. Check the created group snapshot "group_snapshot1" is in the list
+           of all group snapshots
+        8. Delete group snapshot "group_snapshot1"
+        """
         # Create volume type
         volume_type = self.create_volume_type()
 
@@ -118,6 +132,18 @@
 
     @decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
     def test_create_group_from_group_snapshot(self):
+        """Test creating group from group snapshot
+
+        1. Create volume type "volume_type1"
+        2. Create group type "group_type1"
+        3. Create group "group1" with "group_type1" and "volume_type1"
+        4. Create volume "volume1" with "volume_type1" and "group1"
+        5. Create group snapshot "group_snapshot1" with "group1"
+        6. Check snapshot created from "volume1" reaches available status
+        7. Create group "group2" from "group_snapshot1"
+        8. Check the volumes belonging to "group2" reach available status
+        9. Check "group2" reaches available status
+        """
         # Create volume type
         volume_type = self.create_volume_type()
 
@@ -161,6 +187,20 @@
     @decorators.idempotent_id('7d7fc000-0b4c-4376-a372-544116d2e127')
     @decorators.related_bug('1739031')
     def test_delete_group_snapshots_following_updated_volumes(self):
+        """Test deleting group snapshot following updated volumes
+
+        1. Create volume type "volume_type1"
+        2. Create group type "group_type1"
+        3. Create group "group1" with "group_type1" and "volume_type1"
+        4. Create 2 volumes "volume1" and "volume2"
+           with "volume_type1" and "group1"
+        5. For each created volume, removing and then adding back to "group1"
+        6. Create group snapshot "group_snapshot1" with "group1"
+        7. Check snapshots created from "volume1" and "volume2" reach
+           available status
+        8. Delete "group_snapshot1"
+        9. Check snapshots created from "volume1" and "volume2" are deleted
+        """
         volume_type = self.create_volume_type()
 
         group_type = self.create_group_type()
@@ -211,6 +251,8 @@
 
 
 class GroupSnapshotsV319Test(BaseGroupSnapshotsTest):
+    """Test group snapshot with volume microversion greater than 3.18"""
+
     _api_version = 3
     min_microversion = '3.19'
     max_microversion = 'latest'
@@ -218,6 +260,7 @@
     @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
     @decorators.skip_because(bug='1770179')
     def test_reset_group_snapshot_status(self):
+        """Test resetting group snapshot status to creating/available/error"""
         # Create volume type
         volume_type = self.create_volume_type()
 
diff --git a/tempest/api/volume/admin/test_group_type_specs.py b/tempest/api/volume/admin/test_group_type_specs.py
index c5e6d1a..159c6fb 100644
--- a/tempest/api/volume/admin/test_group_type_specs.py
+++ b/tempest/api/volume/admin/test_group_type_specs.py
@@ -19,12 +19,15 @@
 
 
 class GroupTypeSpecsTest(base.BaseVolumeAdminTest):
+    """Test group type specs"""
+
     _api_version = 3
     min_microversion = '3.11'
     max_microversion = 'latest'
 
     @decorators.idempotent_id('bb4e30d0-de6e-4f4d-866c-dcc48d023b4e')
     def test_group_type_specs_create_show_update_list_delete(self):
+        """Test create/show/update/list/delete group type specs"""
         # Create new group type
         group_type = self.create_group_type()
 
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
index 6723207..3993020 100644
--- a/tempest/api/volume/admin/test_group_types.py
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -19,13 +19,15 @@
 
 
 class GroupTypesTest(base.BaseVolumeAdminTest):
+    """Test group types"""
+
     _api_version = 3
     min_microversion = '3.11'
     max_microversion = 'latest'
 
     @decorators.idempotent_id('dd71e5f9-393e-4d4f-90e9-fa1b8d278864')
     def test_group_type_create_list_update_show(self):
-        # Create/list/show group type.
+        """Test create/list/update/show group type"""
         name = data_utils.rand_name(self.__class__.__name__ + '-group-type')
         description = data_utils.rand_name("group-type-description")
         group_specs = {"consistent_group_snapshot_enabled": "<is> False"}
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index c5c70d2..a5de987 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -21,6 +21,7 @@
 
 
 class VolumeMultiBackendTest(base.BaseVolumeAdminTest):
+    """Test volume multi backends"""
 
     @classmethod
     def skip_checks(cls):
@@ -78,24 +79,49 @@
 
     @decorators.idempotent_id('c1a41f3f-9dad-493e-9f09-3ff197d477cc')
     def test_backend_name_reporting(self):
-        # get volume id which created by type without prefix
+        """Test backend name reporting for volume when type is without prefix
+
+        1. Create volume type, with 'volume_backend_name' as extra spec key
+        2. Create volume using the created volume type
+        3. Check 'os-vol-host-attr:host' of the volume info, the value should
+           contain '@' character, like 'cinder@CloveStorage#tecs_backend'
+        """
         for volume_id in self.volume_id_list_without_prefix:
             self._test_backend_name_reporting_by_volume_id(volume_id)
 
     @decorators.idempotent_id('f38e647f-ab42-4a31-a2e7-ca86a6485215')
     def test_backend_name_reporting_with_prefix(self):
-        # get volume id which created by type with prefix
+        """Test backend name reporting for volume when type is with prefix
+
+        1. Create volume type, with 'capabilities:volume_backend_name' as
+           extra spec key
+        2. Create volume using the created volume type
+        3. Check 'os-vol-host-attr:host' of the volume info, the value should
+           contain '@' character, like 'cinder@CloveStorage#tecs_backend'
+        """
         for volume_id in self.volume_id_list_with_prefix:
             self._test_backend_name_reporting_by_volume_id(volume_id)
 
     @decorators.idempotent_id('46435ab1-a0af-4401-8373-f14e66b0dd58')
     def test_backend_name_distinction(self):
-        # get volume ids which created by type without prefix
+        """Test volume backend distinction when type is without prefix
+
+        1. For each backend, create volume type with 'volume_backend_name'
+           as extra spec key
+        2. Create volumes using the created volume types
+        3. Check 'os-vol-host-attr:host' of each created volume is different.
+        """
         self._test_backend_name_distinction(self.volume_id_list_without_prefix)
 
     @decorators.idempotent_id('4236305b-b65a-4bfc-a9d2-69cb5b2bf2ed')
     def test_backend_name_distinction_with_prefix(self):
-        # get volume ids which created by type without prefix
+        """Test volume backend distinction when type is with prefix
+
+        1. For each backend, create volume type with
+           'capabilities:volume_backend_name' as extra spec key
+        2. Create volumes using the created volume types
+        3. Check 'os-vol-host-attr:host' of each created volume is different.
+        """
         self._test_backend_name_distinction(self.volume_id_list_with_prefix)
 
     def _get_volume_host(self, volume_id):
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index 41849bc..4fca240 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -22,6 +22,8 @@
 
 
 class SnapshotsActionsTest(base.BaseVolumeAdminTest):
+    """Test volume snapshot actions"""
+
     @classmethod
     def skip_checks(cls):
         super(SnapshotsActionsTest, cls).skip_checks()
@@ -65,7 +67,7 @@
 
     @decorators.idempotent_id('3e13ca2f-48ea-49f3-ae1a-488e9180d535')
     def test_reset_snapshot_status(self):
-        # Reset snapshot status to creating
+        """Test resetting snapshot status to creating"""
         status = 'creating'
         self.admin_snapshots_client.reset_snapshot_status(
             self.snapshot['id'], status)
@@ -74,6 +76,10 @@
 
     @decorators.idempotent_id('41288afd-d463-485e-8f6e-4eea159413eb')
     def test_update_snapshot_status(self):
+        """Test updating snapshot
+
+        Update snapshot status to 'error' and progress to '80%'.
+        """
         # Reset snapshot status to creating
         status = 'creating'
         self.admin_snapshots_client.reset_snapshot_status(
@@ -95,20 +101,20 @@
 
     @decorators.idempotent_id('05f711b6-e629-4895-8103-7ca069f2073a')
     def test_snapshot_force_delete_when_snapshot_is_creating(self):
-        # test force delete when status of snapshot is creating
+        """Test force delete when status of snapshot is creating"""
         self._create_reset_and_force_delete_temp_snapshot('creating')
 
     @decorators.idempotent_id('92ce8597-b992-43a1-8868-6316b22a969e')
     def test_snapshot_force_delete_when_snapshot_is_deleting(self):
-        # test force delete when status of snapshot is deleting
+        """Test force delete when status of snapshot is deleting"""
         self._create_reset_and_force_delete_temp_snapshot('deleting')
 
     @decorators.idempotent_id('645a4a67-a1eb-4e8e-a547-600abac1525d')
     def test_snapshot_force_delete_when_snapshot_is_error(self):
-        # test force delete when status of snapshot is error
+        """Test force delete when status of snapshot is error"""
         self._create_reset_and_force_delete_temp_snapshot('error')
 
     @decorators.idempotent_id('bf89080f-8129-465e-9327-b2f922666ba5')
     def test_snapshot_force_delete_when_snapshot_is_error_deleting(self):
-        # test force delete when status of snapshot is error_deleting
+        """Test force delete when status of snapshot is error_deleting"""
         self._create_reset_and_force_delete_temp_snapshot('error_deleting')
diff --git a/tempest/api/volume/admin/test_volume_quota_classes.py b/tempest/api/volume/admin/test_volume_quota_classes.py
index ee52354..f482788 100644
--- a/tempest/api/volume/admin/test_volume_quota_classes.py
+++ b/tempest/api/volume/admin/test_volume_quota_classes.py
@@ -30,6 +30,7 @@
 
 
 class VolumeQuotaClassesTest(base.BaseVolumeAdminTest):
+    """Test volume quota classes"""
 
     def setUp(self):
         # Note(jeremy.zhang): All test cases in this class need to externally
@@ -44,6 +45,7 @@
 
     @decorators.idempotent_id('abb9198e-67d0-4b09-859f-4f4a1418f176')
     def test_show_default_quota(self):
+        """Test showing default volume quota class set"""
         # response body is validated by schema
         default_quotas = self.admin_quota_classes_client.show_quota_class_set(
             'default')['quota_class_set']
@@ -51,6 +53,11 @@
 
     @decorators.idempotent_id('a7644c63-2669-467a-b00e-452dd5c5397b')
     def test_update_default_quota(self):
+        """Test updating default volume quota class set
+
+        Check current project and new project's default quota are updated
+        to the provided one.
+        """
         LOG.debug("Get the current default quota class values")
         body = self.admin_quota_classes_client.show_quota_class_set(
             'default')['quota_class_set']
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index ecc850e..ebcd3b7 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -23,17 +23,18 @@
 
 
 class VolumeTypesTest(base.BaseVolumeAdminTest):
+    """Test volume types"""
 
     @decorators.idempotent_id('9d9b28e3-1b2e-4483-a2cc-24aa0ea1de54')
     def test_volume_type_list(self):
-        # List volume types.
+        """Test listing volume types"""
         body = \
             self.admin_volume_types_client.list_volume_types()['volume_types']
         self.assertIsInstance(body, list)
 
     @decorators.idempotent_id('c03cc62c-f4e9-4623-91ec-64ce2f9c1260')
     def test_volume_crud_with_volume_type_and_extra_specs(self):
-        # Create/update/get/delete volume with volume_type and extra spec.
+        """Test create/update/get/delete volume with volume_type"""
         volume_types = list()
         vol_name = data_utils.rand_name(self.__class__.__name__ + '-volume')
         proto = CONF.volume.storage_protocol
@@ -80,7 +81,7 @@
 
     @decorators.idempotent_id('4e955c3b-49db-4515-9590-0c99f8e471ad')
     def test_volume_type_create_get_delete(self):
-        # Create/get volume type.
+        """Test create/get/delete volume type"""
         name = data_utils.rand_name(self.__class__.__name__ + '-volume-type')
         description = data_utils.rand_name("volume-type-description")
         proto = CONF.volume.storage_protocol
@@ -118,7 +119,7 @@
 
     @decorators.idempotent_id('7830abd0-ff99-4793-a265-405684a54d46')
     def test_volume_type_encryption_create_get_update_delete(self):
-        # Create/get/update/delete encryption type.
+        """Test create/get/update/delete volume encryption type"""
         create_kwargs = {'provider': 'LuksEncryptor',
                          'control_location': 'front-end'}
         volume_type_id = self.create_volume_type()['id']
@@ -175,6 +176,7 @@
 
     @decorators.idempotent_id('cf9f07c6-db9e-4462-a243-5933ad65e9c8')
     def test_volume_type_update(self):
+        """Test updating volume type details"""
         # Create volume type
         volume_type = self.create_volume_type()
 
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index fe249d6..6b2a278 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -20,6 +20,7 @@
 
 
 class ExtraSpecsNegativeTest(base.BaseVolumeAdminTest):
+    """Negative tests of volume type extra specs"""
 
     @classmethod
     def resource_setup(cls):
@@ -30,7 +31,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('08961d20-5cbb-4910-ac0f-89ad6dbb2da1')
     def test_update_no_body(self):
-        # Should not update volume type extra specs with no body
+        """Test updating volume type extra specs with no body should fail"""
         self.assertRaises(
             lib_exc.BadRequest,
             self.admin_volume_types_client.update_volume_type_extra_specs,
@@ -39,7 +40,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('25e5a0ee-89b3-4c53-8310-236f76c75365')
     def test_update_nonexistent_extra_spec_id(self):
-        # Should not update volume type extra specs with nonexistent id.
+        """Test updating volume type extra specs with non existent name
+
+        Updating volume type extra specs with non existent extra spec name
+        should fail.
+        """
         extra_spec = {"spec1": "val2"}
         self.assertRaises(
             lib_exc.BadRequest,
@@ -50,7 +55,10 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('9bf7a657-b011-4aec-866d-81c496fbe5c8')
     def test_update_none_extra_spec_id(self):
-        # Should not update volume type extra specs with none id.
+        """Test updating volume type extra specs without name
+
+        Updating volume type extra specs without extra spec name should fail.
+        """
         extra_spec = {"spec1": "val2"}
         self.assertRaises(
             lib_exc.BadRequest,
@@ -60,8 +68,7 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('a77dfda2-9100-448e-9076-ed1711f4bdfc')
     def test_update_multiple_extra_spec(self):
-        # Should not update volume type extra specs with multiple specs as
-        # body.
+        """Test updating multiple volume type extra specs should fail"""
         extra_spec = {"spec1": "val2", "spec2": "val1"}
         self.assertRaises(
             lib_exc.BadRequest,
@@ -72,8 +79,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('49d5472c-a53d-4eab-a4d3-450c4db1c545')
     def test_create_nonexistent_type_id(self):
-        # Should not create volume type extra spec for nonexistent volume
-        # type id.
+        """Test creating volume type extra specs for non existent volume type
+
+        Creating volume type extra specs for non existent volume type should
+        fail.
+        """
         extra_specs = {"spec2": "val1"}
         self.assertRaises(
             lib_exc.NotFound,
@@ -83,7 +93,10 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('c821bdc8-43a4-4bf4-86c8-82f3858d5f7d')
     def test_create_none_body(self):
-        # Should not create volume type extra spec for none POST body.
+        """Test creating volume type extra spec with none POST body
+
+        Creating volume type extra spec with none POST body should fail.
+        """
         self.assertRaises(
             lib_exc.BadRequest,
             self.admin_volume_types_client.create_volume_type_extra_specs,
@@ -92,7 +105,10 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('bc772c71-1ed4-4716-b945-8b5ed0f15e87')
     def test_create_invalid_body(self):
-        # Should not create volume type extra spec for invalid POST body.
+        """Test creating volume type extra spec with invalid POST body
+
+        Creating volume type extra spec with invalid POST body should fail.
+        """
         self.assertRaises(
             lib_exc.BadRequest,
             self.admin_volume_types_client.create_volume_type_extra_specs,
@@ -101,8 +117,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('031cda8b-7d23-4246-8bf6-bbe73fd67074')
     def test_delete_nonexistent_volume_type_id(self):
-        # Should not delete volume type extra spec for nonexistent
-        # type id.
+        """Test deleting volume type extra spec for non existent volume type
+
+        Deleting volume type extra spec for non existent volume type should
+        fail.
+        """
         self.assertRaises(
             lib_exc.NotFound,
             self.admin_volume_types_client.delete_volume_type_extra_specs,
@@ -111,7 +130,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('dee5cf0c-cdd6-4353-b70c-e847050d71fb')
     def test_list_nonexistent_volume_type_id(self):
-        # Should not list volume type extra spec for nonexistent type id.
+        """Test listing volume type extra spec for non existent volume type
+
+        Listing volume type extra spec for non existent volume type should
+        fail.
+        """
         self.assertRaises(
             lib_exc.NotFound,
             self.admin_volume_types_client.list_volume_types_extra_specs,
@@ -120,7 +143,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('9f402cbd-1838-4eb4-9554-126a6b1908c9')
     def test_get_nonexistent_volume_type_id(self):
-        # Should not get volume type extra spec for nonexistent type id.
+        """Test getting volume type extra spec for non existent volume type
+
+        Getting volume type extra spec for non existent volume type should
+        fail.
+        """
         self.assertRaises(
             lib_exc.NotFound,
             self.admin_volume_types_client.show_volume_type_extra_specs,
@@ -129,8 +156,11 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('c881797d-12ff-4f1a-b09d-9f6212159753')
     def test_get_nonexistent_extra_spec_name(self):
-        # Should not get volume type extra spec for nonexistent extra spec
-        # name.
+        """Test getting volume type extra spec for non existent spec name
+
+        Getting volume type extra spec for non existent extra spec name should
+        fail.
+        """
         self.assertRaises(
             lib_exc.NotFound,
             self.admin_volume_types_client.show_volume_type_extra_specs,
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index bd4b3fa..835cc1d 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -26,6 +26,7 @@
 
 
 class VolumesBackupsAdminTest(base.BaseVolumeAdminTest):
+    """Test volume backups"""
 
     @classmethod
     def skip_checks(cls):
@@ -118,6 +119,7 @@
 
     @decorators.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
     def test_volume_backup_reset_status(self):
+        """Test resetting volume backup status to error"""
         # Create a volume
         volume = self.create_volume()
         # Create a backup
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 7af5927..c538e60 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -302,6 +302,27 @@
         cls.addClassResourceCleanup(cls.clear_volume_type, volume_type['id'])
         return volume_type
 
+    def create_encryption_type(self, type_id=None, provider=None,
+                               key_size=None, cipher=None,
+                               control_location=None):
+        if not type_id:
+            volume_type = self.create_volume_type()
+            type_id = volume_type['id']
+        self.admin_encryption_types_client.create_encryption_type(
+            type_id, provider=provider, key_size=key_size, cipher=cipher,
+            control_location=control_location)
+
+    def create_encrypted_volume(self, encryption_provider, key_size=256,
+                                cipher='aes-xts-plain64',
+                                control_location='front-end'):
+        volume_type = self.create_volume_type()
+        self.create_encryption_type(type_id=volume_type['id'],
+                                    provider=encryption_provider,
+                                    key_size=key_size,
+                                    cipher=cipher,
+                                    control_location=control_location)
+        return self.create_volume(volume_type=volume_type['name'])
+
     def create_group_type(self, name=None, **kwargs):
         """Create a test group-type"""
         name = name or data_utils.rand_name(
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 041823d..7441f1d 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -61,7 +61,7 @@
         self.assertEqual(extend_size, resized_volume['size'])
 
 
-class VolumesExtendAttachedTest(base.BaseVolumeTest):
+class BaseVolumesExtendAttachedTest(base.BaseVolumeTest):
     """Tests extending the size of an attached volume."""
     create_default_network = True
 
@@ -100,14 +100,9 @@
                     event['finish_time']):
                 return event
 
-    @decorators.idempotent_id('301f5a30-1c6f-4ea0-be1a-91fd28d44354')
-    @testtools.skipUnless(CONF.volume_feature_enabled.extend_attached_volume,
-                          "Attached volume extend is disabled.")
-    @utils.services('compute')
-    def test_extend_attached_volume(self):
+    def _test_extend_attached_volume(self, volume):
         """This is a happy path test which does the following:
 
-        * Create a volume at the configured volume_size.
         * Create a server instance.
         * Attach the volume to the server.
         * Wait for the volume status to be "in-use".
@@ -119,8 +114,6 @@
           if we timeout waiting for the instance action event to show up, or
           if the action on the server fails.
         """
-        # Create a test volume. Will be automatically cleaned up on teardown.
-        volume = self.create_volume()
         # Create a test server. Will be automatically cleaned up on teardown.
         server = self.create_server()
         # Attach the volume to the server and wait for the volume status to be
@@ -182,3 +175,14 @@
             "%(request_id)s." %
             {'result': event['result'],
              'request_id': action['request_id']})
+
+
+class VolumesExtendAttachedTest(BaseVolumesExtendAttachedTest):
+
+    @decorators.idempotent_id('301f5a30-1c6f-4ea0-be1a-91fd28d44354')
+    @testtools.skipUnless(CONF.volume_feature_enabled.extend_attached_volume,
+                          "Attached volume extend is disabled.")
+    @utils.services('compute')
+    def test_extend_attached_volume(self):
+        volume = self.create_volume()
+        self._test_extend_attached_volume(volume)
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 76c22f0..389d3be 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -127,14 +127,14 @@
     def test_update_volume_with_nonexistent_volume_id(self):
         """Test updating non existent volume should fail"""
         self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
-                          volume_id=data_utils.rand_uuid())
+                          volume_id=data_utils.rand_uuid(), name="n")
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('e66e40d6-65e6-4e75-bdc7-636792fa152d')
     def test_update_volume_with_invalid_volume_id(self):
         """Test updating volume with invalid volume id should fail"""
         self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
-                          volume_id=data_utils.rand_name('invalid'))
+                          volume_id=data_utils.rand_name('invalid'), name="n")
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('72aeca85-57a5-4c1f-9057-f320f9ea575b')
diff --git a/tempest/clients.py b/tempest/clients.py
index 1db93a0..6d19a0c 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -44,7 +44,7 @@
         self._set_object_storage_clients()
         self._set_image_clients()
         self._set_network_clients()
-        self.placement_client = self.placement.PlacementClient()
+        self._set_placement_clients()
         # TODO(andreaf) This is maintained for backward compatibility
         # with plugins, but it should removed eventually, since it was
         # never a stable interface and it's not useful anyways
@@ -139,6 +139,11 @@
         self.snapshots_extensions_client = self.compute.SnapshotsClient(
             **params_volume)
 
+    def _set_placement_clients(self):
+        self.placement_client = self.placement.PlacementClient()
+        self.resource_providers_client = \
+            self.placement.ResourceProvidersClient()
+
     def _set_identity_clients(self):
         # Clients below use the admin endpoint type of Keystone API v2
         params_v2_admin = {
@@ -203,6 +208,8 @@
             **params_v3)
         self.application_credentials_client = \
             self.identity_v3.ApplicationCredentialsClient(**params_v3)
+        self.access_rules_client = \
+            self.identity_v3.AccessRulesClient(**params_v3)
 
         # Token clients do not use the catalog. They only need default_params.
         # They read auth_url, so they should only be set if the corresponding
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index d84f3a3..6e93d69 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -44,11 +44,14 @@
 
     :return: default config dir
     """
+    # NOTE: The default directory should be on a Linux box.
     global_conf_dir = '/etc/tempest'
     xdg_config = os.environ.get('XDG_CONFIG_HOME',
-                                os.path.expanduser('~/.config'))
+                                os.path.expanduser(os.path.join('~',
+                                                                '.config')))
     user_xdg_global_path = os.path.join(xdg_config, 'tempest')
-    user_global_path = os.path.join(os.path.expanduser('~'), '.tempest/etc')
+    user_global_path = os.path.join(os.path.expanduser('~'),
+                                    '.tempest', 'etc')
     if os.path.isdir(global_conf_dir):
         return global_conf_dir
     elif os.path.isdir(user_xdg_global_path):
@@ -121,7 +124,7 @@
     def generate_sample_config(self, local_dir):
         conf_generator = os.path.join(os.path.dirname(__file__),
                                       'config-generator.tempest.conf')
-        output_file = os.path.join(local_dir, 'etc/tempest.conf.sample')
+        output_file = os.path.join(local_dir, 'etc', 'tempest.conf.sample')
         if os.path.isfile(conf_generator):
             generator.main(['--config-file', conf_generator, '--output-file',
                             output_file])
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 14790d6..cc8778b 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -187,6 +187,28 @@
     raise lib_exc.TimeoutException(message)
 
 
+def wait_for_image_imported_to_stores(client, image_id, stores):
+    """Waits for an image to be imported to all requested stores.
+
+    The client should also have build_interval and build_timeout attributes.
+    """
+
+    start = int(time.time())
+    while int(time.time()) - start < client.build_timeout:
+        image = client.show_image(image_id)
+        if image['status'] == 'active' and image['stores'] == stores:
+            return
+
+        time.sleep(client.build_interval)
+
+    message = ('Image %(image_id)s failed to import '
+               'on stores: %s' % str(image['os_glance_failed_import']))
+    caller = test_utils.find_test_caller()
+    if caller:
+        message = '(%s) %s' % (caller, message)
+    raise lib_exc.TimeoutException(message)
+
+
 def wait_for_volume_resource_status(client, resource_id, status):
     """Waits for a volume resource to reach a given status.
 
@@ -223,6 +245,25 @@
              resource_name, resource_id, status, time.time() - start)
 
 
+def wait_for_volume_attachment_create(client, volume_id, server_id):
+    """Waits for a volume attachment to be created at a given volume."""
+    start = int(time.time())
+    while True:
+        attachments = client.show_volume(volume_id)['volume']['attachments']
+        found = [a for a in attachments if a['server_id'] == server_id]
+        if found:
+            LOG.info('Attachment %s created for volume %s to server %s after '
+                     'waiting for %f seconds', found[0]['attachment_id'],
+                     volume_id, server_id, time.time() - start)
+            return found[0]
+        time.sleep(client.build_interval)
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('Failed to attach volume %s to server %s '
+                       'within the required time (%s s).' %
+                       (volume_id, server_id, client.build_timeout))
+            raise lib_exc.TimeoutException(message)
+
+
 def wait_for_volume_attachment_remove(client, volume_id, attachment_id):
     """Waits for a volume attachment to be removed from a given volume."""
     start = int(time.time())
diff --git a/tempest/config.py b/tempest/config.py
index 989b8ef..2f2c2e9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -250,6 +250,11 @@
                 default=False,
                 help='Does the environment have application credentials '
                      'enabled?'),
+    # Access rules for application credentials is a default feature in Train.
+    # This config option can removed once Stein is EOL.
+    cfg.BoolOpt('access_rules',
+                default=False,
+                help='Does the environment have access rules enabled?'),
     cfg.BoolOpt('immutable_user_source',
                 default=False,
                 help='Set to True if the environment has a read-only '
@@ -598,6 +603,18 @@
                 help='Does the test environment support attaching a volume to '
                      'more than one instance? This depends on hypervisor and '
                      'volume backend/type and compute API version 2.60.'),
+    cfg.BoolOpt('xenapi_apis',
+                default=False,
+                help='Does the test environment support the XenAPI-specific '
+                     'APIs: os-agents, writeable server metadata and the '
+                     'resetNetwork server action? '
+                     'These were removed in Victoria alongside the XenAPI '
+                     'virt driver.',
+                deprecated_for_removal=True,
+                deprecated_reason="On Nova side, XenAPI virt driver and the "
+                                  "APIs that only worked with that driver "
+                                  "have been removed and there's nothing to "
+                                  "test after Ussuri."),
 ]
 
 
@@ -1000,7 +1017,15 @@
                      'which is currently attached to a server instance? This '
                      'depends on the 3.42 volume API microversion and the '
                      '2.51 compute API microversion. Also, not all volume or '
-                     'compute backends support this operation.')
+                     'compute backends support this operation.'),
+    cfg.BoolOpt('extend_attached_encrypted_volume',
+                default=False,
+                help='Does the cloud support extending the size of an '
+                     'encrypted volume  which is currently attached to a '
+                     'server instance? This depends on the 3.42 volume API '
+                     'microversion and the 2.51 compute API microversion. '
+                     'Also, not all volume or compute backends support this '
+                     'operation.')
 ]
 
 
@@ -1174,7 +1199,7 @@
 
 The best use case is investigating used resources of one test.
 A test can be run as follows:
- $ ostestr --pdb TEST_ID
+ $ stestr run --pdb TEST_ID
 or
  $ python -m testtools.run TEST_ID"""),
 ]
diff --git a/tempest/lib/api_schema/response/compute/v2_71/servers.py b/tempest/lib/api_schema/response/compute/v2_71/servers.py
index 5cf0f8a..f4c01ee 100644
--- a/tempest/lib/api_schema/response/compute/v2_71/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_71/servers.py
@@ -79,3 +79,6 @@
 check_tag_existence = copy.deepcopy(servers270.check_tag_existence)
 update_tag = copy.deepcopy(servers270.update_tag)
 delete_tag = copy.deepcopy(servers270.delete_tag)
+attach_volume = copy.deepcopy(servers270.attach_volume)
+show_volume_attachment = copy.deepcopy(servers270.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers270.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_73/servers.py b/tempest/lib/api_schema/response/compute/v2_73/servers.py
index 6e491e9..ae7ebc4 100644
--- a/tempest/lib/api_schema/response/compute/v2_73/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_73/servers.py
@@ -76,3 +76,6 @@
 check_tag_existence = copy.deepcopy(servers271.check_tag_existence)
 update_tag = copy.deepcopy(servers271.update_tag)
 delete_tag = copy.deepcopy(servers271.delete_tag)
+attach_volume = copy.deepcopy(servers271.attach_volume)
+show_volume_attachment = copy.deepcopy(servers271.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers271.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/volume/backups.py b/tempest/lib/api_schema/response/volume/backups.py
index 9e85f5f..cba7981 100644
--- a/tempest/lib/api_schema/response/volume/backups.py
+++ b/tempest/lib/api_schema/response/volume/backups.py
@@ -66,7 +66,7 @@
                 'properties': {
                     'id': {'type': 'string', 'format': 'uuid'},
                     'links': parameter_types.links,
-                    'name': {'type': 'string'},
+                    'name': {'type': ['string', 'null']},
                     # TODO(zhufl): metadata is added in 3.43, we should move it
                     # to the 3.43 schema file when microversion is supported
                     # in volume interfaces.
@@ -91,7 +91,7 @@
                 'properties': {
                     'id': {'type': 'string', 'format': 'uuid'},
                     'links': parameter_types.links,
-                    'name': {'type': 'string'},
+                    'name': {'type': ['string', 'null']},
                     'metadata': {'^.+$': {'type': 'string'}}
                 },
                 'additionalProperties': False,
diff --git a/tempest/lib/api_schema/response/volume/manage_volume.py b/tempest/lib/api_schema/response/volume/manage_volume.py
new file mode 100644
index 0000000..d3acfd9
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/manage_volume.py
@@ -0,0 +1,27 @@
+# Copyright 2018 ZTE Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.api_schema.response.volume import volumes
+
+
+manage_volume = {
+    'status_code': [202],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'volume': volumes.common_show_volume},
+        'additionalProperties': False,
+        'required': ['volume']
+    }
+}
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index 71ecb32..b34066f 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -16,6 +16,7 @@
 
 import argparse
 import ast
+import contextlib
 import importlib
 import inspect
 import os
@@ -28,7 +29,7 @@
 
 DECORATOR_MODULE = 'decorators'
 DECORATOR_NAME = 'idempotent_id'
-DECORATOR_IMPORT = 'tempest.%s' % DECORATOR_MODULE
+DECORATOR_IMPORT = 'tempest.lib.%s' % DECORATOR_MODULE
 IMPORT_LINE = 'from tempest.lib import %s' % DECORATOR_MODULE
 DECORATOR_TEMPLATE = "@%s.%s('%%s')" % (DECORATOR_MODULE,
                                         DECORATOR_NAME)
@@ -180,34 +181,125 @@
         elif isinstance(node, ast.ImportFrom):
             return '%s.%s' % (node.module, node.names[0].name)
 
+    @contextlib.contextmanager
+    def ignore_site_packages_paths(self):
+        """Removes site-packages directories from the sys.path
+
+        Source:
+            - StackOverflow: https://stackoverflow.com/questions/22195382/
+            - Author: https://stackoverflow.com/users/485844/
+        """
+
+        paths = sys.path
+        # remove all third-party paths
+        # so that only stdlib imports will succeed
+        sys.path = list(filter(
+            None,
+            filter(lambda i: 'site-packages' not in i, sys.path)
+        ))
+        yield
+        sys.path = paths
+
+    def is_std_lib(self, module):
+        """Checks whether the module is part of the stdlib or not
+
+        Source:
+            - StackOverflow: https://stackoverflow.com/questions/22195382/
+            - Author: https://stackoverflow.com/users/485844/
+        """
+
+        if module in sys.builtin_module_names:
+            return True
+
+        with self.ignore_site_packages_paths():
+            imported_module = sys.modules.pop(module, None)
+            try:
+                importlib.import_module(module)
+            except ImportError:
+                return False
+            else:
+                return True
+            finally:
+                if imported_module:
+                    sys.modules[module] = imported_module
+
     def _add_import_for_test_uuid(self, patcher, src_parsed, source_path):
-        with open(source_path) as f:
-            src_lines = f.read().split('\n')
-        line_no = 0
-        tempest_imports = [node for node in src_parsed.body
+        import_list = [node for node in src_parsed.body
+                       if isinstance(node, ast.Import) or
+                       isinstance(node, ast.ImportFrom)]
+
+        if not import_list:
+            print("(WARNING) %s: The file is not valid as it does not contain "
+                  "any import line! Therefore the import needed by "
+                  "@decorators.idempotent_id is not added!" % source_path)
+            return
+
+        tempest_imports = [node for node in import_list
                            if self._import_name(node) and
                            'tempest.' in self._import_name(node)]
-        if not tempest_imports:
-            import_snippet = '\n'.join(('', IMPORT_LINE, ''))
-        else:
-            for node in tempest_imports:
-                if self._import_name(node) < DECORATOR_IMPORT:
-                    continue
-                else:
-                    line_no = node.lineno
-                    import_snippet = IMPORT_LINE
-                    break
+
+        for node in tempest_imports:
+            if self._import_name(node) < DECORATOR_IMPORT:
+                continue
             else:
-                line_no = tempest_imports[-1].lineno
-                while True:
-                    if (not src_lines[line_no - 1] or
-                            getattr(self._next_node(src_parsed.body,
-                                                    tempest_imports[-1]),
-                                    'lineno') == line_no or
-                            line_no == len(src_lines)):
-                        break
-                    line_no += 1
-                import_snippet = '\n'.join((IMPORT_LINE, ''))
+                line_no = node.lineno
+                break
+        else:
+            if tempest_imports:
+                line_no = tempest_imports[-1].lineno + 1
+
+        # Insert import line between existing tempest imports
+        if tempest_imports:
+            patcher.add_patch(source_path, IMPORT_LINE, line_no)
+            return
+
+        # Group space separated imports together
+        grouped_imports = {}
+        first_import_line = import_list[0].lineno
+        for idx, import_line in enumerate(import_list, first_import_line):
+            group_no = import_line.lineno - idx
+            group = grouped_imports.get(group_no, [])
+            group.append(import_line)
+            grouped_imports[group_no] = group
+
+        if len(grouped_imports) > 3:
+            print("(WARNING) %s: The file contains more than three import "
+                  "groups! This is not valid according to the PEP8 "
+                  "style guide. " % source_path)
+
+        # Divide grouped_imports into groupes based on PEP8 style guide
+        pep8_groups = {}
+        package_name = self.package.__name__.split(".")[0]
+        for key in grouped_imports:
+            module = self._import_name(grouped_imports[key][0]).split(".")[0]
+            if module.startswith(package_name):
+                group = pep8_groups.get('3rd_group', [])
+                pep8_groups['3rd_group'] = group + grouped_imports[key]
+            elif self.is_std_lib(module):
+                group = pep8_groups.get('1st_group', [])
+                pep8_groups['1st_group'] = group + grouped_imports[key]
+            else:
+                group = pep8_groups.get('2nd_group', [])
+                pep8_groups['2nd_group'] = group + grouped_imports[key]
+
+        for node in pep8_groups.get('2nd_group', []):
+            if self._import_name(node) < DECORATOR_IMPORT:
+                continue
+            else:
+                line_no = node.lineno
+                import_snippet = IMPORT_LINE
+                break
+        else:
+            if pep8_groups.get('2nd_group', []):
+                line_no = pep8_groups['2nd_group'][-1].lineno + 1
+                import_snippet = IMPORT_LINE
+            elif pep8_groups.get('1st_group', []):
+                line_no = pep8_groups['1st_group'][-1].lineno + 1
+                import_snippet = '\n' + IMPORT_LINE
+            else:
+                line_no = pep8_groups['3rd_group'][0].lineno
+                import_snippet = IMPORT_LINE + '\n\n'
+
         patcher.add_patch(source_path, import_snippet, line_no)
 
     def get_tests(self):
diff --git a/tempest/lib/cmd/skip_tracker.py b/tempest/lib/cmd/skip_tracker.py
index 87806b7..95376e3 100755
--- a/tempest/lib/cmd/skip_tracker.py
+++ b/tempest/lib/cmd/skip_tracker.py
@@ -31,10 +31,11 @@
 except ImportError:
     launchpad = None
 
-LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
+LPCACHEDIR = os.path.expanduser(os.path.join('~', '.launchpadlib', 'cache'))
 LOG = logging.getLogger(__name__)
 
-BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
+                                       '..', '..', '..'))
 TESTDIR = os.path.join(BASEDIR, 'tempest')
 
 
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index 1011504..641d727 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -172,7 +172,7 @@
         return self.is_multi_user()
 
     def _create_hash_file(self, hash_string):
-        path = os.path.join(os.path.join(self.accounts_dir, hash_string))
+        path = os.path.join(self.accounts_dir, hash_string)
         if not os.path.isfile(path):
             with open(path, 'w') as fd:
                 fd.write(self.name)
@@ -194,8 +194,7 @@
             if res:
                 return _hash
             else:
-                path = os.path.join(os.path.join(self.accounts_dir,
-                                                 _hash))
+                path = os.path.join(self.accounts_dir, _hash)
                 with open(path, 'r') as fd:
                     names.append(fd.read())
         msg = ('Insufficient number of users provided. %s have allocated all '
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index 808e0fb..ebe2d61 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -124,7 +124,7 @@
     def decorator(f):
         f = testtools.testcase.attr('id-%s' % id)(f)
         if f.__doc__:
-            f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
+            f.__doc__ = 'Test idempotent id: %s\n\n%s' % (id, f.__doc__)
         else:
             f.__doc__ = 'Test idempotent id: %s' % id
         return f
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index da1c51c..86fa991 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -12,6 +12,8 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+from tempest.lib.services.identity.v3.access_rules_client import \
+    AccessRulesClient
 from tempest.lib.services.identity.v3.application_credentials_client import \
     ApplicationCredentialsClient
 from tempest.lib.services.identity.v3.catalog_client import \
@@ -48,9 +50,10 @@
 from tempest.lib.services.identity.v3.users_client import UsersClient
 from tempest.lib.services.identity.v3.versions_client import VersionsClient
 
-__all__ = ['ApplicationCredentialsClient', 'CatalogClient',
-           'CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
-           'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient',
+__all__ = ['AccessRulesClient', 'ApplicationCredentialsClient',
+           'CatalogClient', 'CredentialsClient', 'DomainsClient',
+           'DomainConfigurationClient', 'EndPointGroupsClient',
+           'EndPointsClient', 'EndPointsFilterClient',
            'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
            'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient',
            'ProjectsClient', 'ProjectTagsClient', 'RegionsClient',
diff --git a/tempest/lib/services/identity/v3/access_rules_client.py b/tempest/lib/services/identity/v3/access_rules_client.py
new file mode 100644
index 0000000..4f13e47
--- /dev/null
+++ b/tempest/lib/services/identity/v3/access_rules_client.py
@@ -0,0 +1,68 @@
+# Copyright 2019 SUSE LLC
+#
+# 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.
+
+"""
+https://docs.openstack.org/api-ref/identity/v3/index.html#application-credentials
+"""
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class AccessRulesClient(rest_client.RestClient):
+    api_version = "v3"
+
+    def show_access_rule(self, user_id, access_rule_id):
+        """Gets details of an access rule.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3/index.html#show-access-rule-details
+        """
+        resp, body = self.get('users/%s/access_rules/%s' %
+                              (user_id, access_rule_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_access_rules(self, user_id, **params):
+        """Lists out all of a user's access rules.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3/index.html#list-access-rules
+        """
+        url = 'users/%s/access_rules' % user_id
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_access_rule(self, user_id, access_rule_id):
+        """Deletes an access rule.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3/index.html#delete-access-rule
+        """
+        resp, body = self.delete('users/%s/access_rules/%s' %
+                                 (user_id, access_rule_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/endpoint_filter_client.py b/tempest/lib/services/identity/v3/endpoint_filter_client.py
index ce84869..2d5c8c9 100644
--- a/tempest/lib/services/identity/v3/endpoint_filter_client.py
+++ b/tempest/lib/services/identity/v3/endpoint_filter_client.py
@@ -66,3 +66,57 @@
             % (project_id, endpoint_id))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def list_endpoint_groups_for_project(self, project_id):
+        """List Endpoint Groups Associated with Project."""
+        resp, body = self.get(
+            self.ep_filter + '/projects/%s/endpoint_groups'
+            % project_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_projects_for_endpoint_group(self, endpoint_group_id):
+        """List Projects Associated with Endpoint Group."""
+        resp, body = self.get(
+            self.ep_filter + '/endpoint_groups/%s/projects'
+            % endpoint_group_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_endpoints_for_endpoint_group(self, endpoint_group_id):
+        """List Endpoints Associated with Endpoint Group."""
+        resp, body = self.get(
+            self.ep_filter + '/endpoint_groups/%s/endpoints'
+            % endpoint_group_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
+        """Create Endpoint Group to Project Association."""
+        body = None
+        resp, body = self.put(
+            self.ep_filter + '/endpoint_groups/%s/projects/%s'
+            % (endpoint_group_id, project_id), body)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_endpoint_group_for_project(self, endpoint_group_id, project_id):
+        """Get Endpoint Group to Project Association."""
+        resp, body = self.get(
+            self.ep_filter + '/endpoint_groups/%s/projects/%s'
+            % (endpoint_group_id, project_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_endpoint_group_from_project(
+        self, endpoint_group_id, project_id):
+        """Delete Endpoint Group to Project Association."""
+        resp, body = self.delete(
+            self.ep_filter + '/endpoint_groups/%s/projects/%s'
+            % (endpoint_group_id, project_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/policies_client.py b/tempest/lib/services/identity/v3/policies_client.py
index 31c0d18..41def38 100644
--- a/tempest/lib/services/identity/v3/policies_client.py
+++ b/tempest/lib/services/identity/v3/policies_client.py
@@ -185,3 +185,27 @@
         resp, body = self.delete(url)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def list_endpoints_for_policy(self, policy_id):
+        """List policy and service endpoint associations.
+
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/#list-policy-and-service-endpoint-associations
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints".format(policy_id)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_policy_for_endpoint(self, endpoint_id):
+        """Show the effective policy associated with an endpoint
+
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/#show-the-effective-policy-associated-with-an-endpoint
+        """
+        url = "endpoints/{0}/OS-ENDPOINT-POLICY/policy".format(endpoint_id)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/users_client.py b/tempest/lib/services/identity/v3/users_client.py
index f47730f..bba02a4 100644
--- a/tempest/lib/services/identity/v3/users_client.py
+++ b/tempest/lib/services/identity/v3/users_client.py
@@ -118,3 +118,30 @@
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
+
+    def create_user_ec2_credential(self, user_id, **kwargs):
+        post_body = json.dumps(kwargs)
+        resp, body = self.post('/users/%s/credentials/OS-EC2' % user_id,
+                               post_body)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_user_ec2_credential(self, user_id, access):
+        resp, body = self.delete('/users/%s/credentials/OS-EC2/%s' %
+                                 (user_id, access))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_user_ec2_credentials(self, user_id):
+        resp, body = self.get('/users/%s/credentials/OS-EC2' % user_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_user_ec2_credential(self, user_id, access):
+        resp, body = self.get('/users/%s/credentials/OS-EC2/%s' %
+                              (user_id, access))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/placement/__init__.py b/tempest/lib/services/placement/__init__.py
index 5c20c57..daeaeab 100644
--- a/tempest/lib/services/placement/__init__.py
+++ b/tempest/lib/services/placement/__init__.py
@@ -14,5 +14,7 @@
 
 from tempest.lib.services.placement.placement_client import \
     PlacementClient
+from tempest.lib.services.placement.resource_providers_client import \
+    ResourceProvidersClient
 
-__all__ = ['PlacementClient']
+__all__ = ['PlacementClient', 'ResourceProvidersClient']
diff --git a/tempest/lib/services/placement/resource_providers_client.py b/tempest/lib/services/placement/resource_providers_client.py
new file mode 100644
index 0000000..56f6409
--- /dev/null
+++ b/tempest/lib/services/placement/resource_providers_client.py
@@ -0,0 +1,82 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.placement import base_placement_client
+
+
+class ResourceProvidersClient(base_placement_client.BasePlacementClient):
+    """Client class for resource provider related methods
+
+    This client class aims to support read-only API operations for resource
+    providers. The following resources are supported:
+    * resource providers
+    * resource provider inventories
+    * resource provider aggregates
+    """
+
+    def list_resource_providers(self, **params):
+        """List resource providers.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/placement/#list-resource-providers
+        """
+        url = '/resource_providers'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_resource_provider(self, rp_uuid):
+        """Show resource provider.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/placement/#show-resource-provider
+        """
+        url = '/resource_providers/%s' % rp_uuid
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_resource_provider_inventories(self, rp_uuid):
+        """List resource provider inventories.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/placement/#list-resource-provider-inventories
+        """
+        url = '/resource_providers/%s/inventories' % rp_uuid
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_resource_provider_aggregates(self, rp_uuid):
+        """List resource provider aggregates.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/placement/#list-resource-provider-aggregates
+        """
+        url = '/resource_providers/%s/aggregates' % rp_uuid
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/volume_manage_client.py b/tempest/lib/services/volume/v3/volume_manage_client.py
index 85b1b82..f6642c5 100644
--- a/tempest/lib/services/volume/v3/volume_manage_client.py
+++ b/tempest/lib/services/volume/v3/volume_manage_client.py
@@ -15,6 +15,7 @@
 
 from oslo_serialization import jsonutils as json
 
+from tempest.lib.api_schema.response.volume import manage_volume as schema
 from tempest.lib.common import rest_client
 
 
@@ -30,6 +31,6 @@
         """
         post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('os-volume-manage', post_body)
-        self.expected_success(202, resp.status)
         body = json.loads(body)
+        self.validate_response(schema.manage_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/test_minbw_allocation_placement.py b/tempest/scenario/test_minbw_allocation_placement.py
index e7085f6..74d4ed9 100644
--- a/tempest/scenario/test_minbw_allocation_placement.py
+++ b/tempest/scenario/test_minbw_allocation_placement.py
@@ -124,8 +124,11 @@
             resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
                                   self.SMALLEST_POSSIBLE_BW))
         if len(alloc_candidates['provider_summaries']) == 0:
-            self.fail('No allocation candidates are available for %s:%s' %
-                      (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
+            # Skip if the backend does not support QoS minimum bandwidth
+            # allocation in Placement API
+            raise self.skipException(
+                'No allocation candidates are available for %s:%s' %
+                (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
 
         # Just to be sure check with impossible high (placement max_int),
         # allocation
@@ -175,7 +178,13 @@
         for rp, resources in allocations['allocations'].items():
             if self.INGRESS_RESOURCE_CLASS in resources['resources']:
                 bw_resource_in_alloc = True
+                allocation_rp = rp
         self.assertTrue(bw_resource_in_alloc)
+        # Check that binding_profile of the port is not empty and equals with
+        # the rp uuid
+        port = self.os_admin.ports_client.show_port(valid_port['id'])
+        self.assertEqual(allocation_rp,
+                         port['port']['binding:profile']['allocation'])
 
         # boot another vm with max int bandwidth
         not_valid_port = self.create_port(
@@ -193,3 +202,6 @@
         server2 = self.servers_client.show_server(server2['id'])
         self.assertIn('fault', server2['server'])
         self.assertIn('No valid host', server2['server']['fault']['message'])
+        # Check that binding_profile of the port is empty
+        port = self.os_admin.ports_client.show_port(not_valid_port['id'])
+        self.assertEqual(0, len(port['port']['binding:profile']))
diff --git a/tempest/test_discover/test_discover.py b/tempest/test_discover/test_discover.py
index 143c6e1..5816ab1 100644
--- a/tempest/test_discover/test_discover.py
+++ b/tempest/test_discover/test_discover.py
@@ -30,8 +30,8 @@
     base_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
     base_path = os.path.split(base_path)[0]
     # Load local tempest tests
-    for test_dir in ['tempest/api', 'tempest/scenario']:
-        full_test_dir = os.path.join(base_path, test_dir)
+    for test_dir in ['api', 'scenario']:
+        full_test_dir = os.path.join(base_path, 'tempest', test_dir)
         if not pattern:
             suite.addTests(loader.discover(full_test_dir,
                                            top_level_dir=base_path))
diff --git a/tempest/tests/cmd/test_tempest_init.py b/tempest/tests/cmd/test_tempest_init.py
index 9042b12..fce0882 100644
--- a/tempest/tests/cmd/test_tempest_init.py
+++ b/tempest/tests/cmd/test_tempest_init.py
@@ -40,7 +40,7 @@
 
     def test_generate_sample_config(self):
         local_dir = self.useFixture(fixtures.TempDir())
-        etc_dir_path = os.path.join(local_dir.path, 'etc/')
+        etc_dir_path = os.path.join(local_dir.path, 'etc')
         os.mkdir(etc_dir_path)
         init_cmd = init.TempestInit(None, None)
         local_sample_conf_file = os.path.join(etc_dir_path,
@@ -56,7 +56,7 @@
 
     def test_update_local_conf(self):
         local_dir = self.useFixture(fixtures.TempDir())
-        etc_dir_path = os.path.join(local_dir.path, 'etc/')
+        etc_dir_path = os.path.join(local_dir.path, 'etc')
         os.mkdir(etc_dir_path)
         lock_dir = os.path.join(local_dir.path, 'tempest_lock')
         config_path = os.path.join(etc_dir_path, 'tempest.conf')
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 721fd76..277e049 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -579,7 +579,7 @@
                           os, 'fakeservice')
 
     def test_get_config_file(self):
-        conf_dir = os.path.join(os.getcwd(), 'etc/')
+        conf_dir = os.path.join(os.getcwd(), 'etc')
         conf_file = "tempest.conf.sample"
         local_sample_conf_file = os.path.join(conf_dir, conf_file)
 
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 5f8b990..73924bd 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -234,6 +234,29 @@
                                     mock.call(volume_id)])
         mock_sleep.assert_called_once_with(1)
 
+    def test_wait_for_volume_attachment_create(self):
+        vol_detached = {'volume': {'attachments': []}}
+        vol_attached = {'volume': {'attachments': [
+                       {'id': uuids.volume_id,
+                        'attachment_id': uuids.attachment_id,
+                        'server_id': uuids.server_id,
+                        'volume_id': uuids.volume_id}]}}
+        show_volume = mock.MagicMock(side_effect=[
+            vol_detached, vol_detached, vol_attached])
+        client = mock.Mock(spec=volumes_client.VolumesClient,
+                           build_interval=1,
+                           build_timeout=5,
+                           show_volume=show_volume)
+        self.patch('time.time')
+        self.patch('time.sleep')
+        att = waiters.wait_for_volume_attachment_create(
+            client, uuids.volume_id, uuids.server_id)
+        assert att == vol_attached['volume']['attachments'][0]
+        # Assert that show volume is called until the attachment is removed.
+        show_volume.assert_has_calls([mock.call(uuids.volume_id),
+                                      mock.call(uuids.volume_id),
+                                      mock.call(uuids.volume_id)])
+
     def test_wait_for_volume_attachment(self):
         vol_detached = {'volume': {'attachments': []}}
         vol_attached = {'volume': {'attachments': [
@@ -249,9 +272,9 @@
         waiters.wait_for_volume_attachment_remove(client, uuids.volume_id,
                                                   uuids.attachment_id)
         # Assert that show volume is called until the attachment is removed.
-        show_volume.assert_has_calls = [mock.call(uuids.volume_id),
-                                        mock.call(uuids.volume_id),
-                                        mock.call(uuids.volume_id)]
+        show_volume.assert_has_calls([mock.call(uuids.volume_id),
+                                      mock.call(uuids.volume_id),
+                                      mock.call(uuids.volume_id)])
 
     def test_wait_for_volume_attachment_timeout(self):
         show_volume = mock.MagicMock(return_value={
diff --git a/tempest/tests/lib/cmd/test_check_uuid.py b/tempest/tests/lib/cmd/test_check_uuid.py
index 28ebca1..428e047 100644
--- a/tempest/tests/lib/cmd/test_check_uuid.py
+++ b/tempest/tests/lib/cmd/test_check_uuid.py
@@ -10,6 +10,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import ast
 import importlib
 import os
 import sys
@@ -95,6 +96,8 @@
 
 
 class TestTestChecker(base.TestCase):
+    IMPORT_LINE = "from tempest.lib import decorators\n"
+
     def _test_add_uuid_to_test(self, source_file):
         class Fake_test_node():
             lineno = 1
@@ -127,55 +130,69 @@
                        "        pass")
         self._test_add_uuid_to_test(source_file)
 
+    @staticmethod
+    def get_mocked_ast_object(lineno, col_offset, module, name, object_type):
+        ast_object = mock.Mock(spec=object_type)
+        name_obj = mock.Mock()
+        ast_object.lineno = lineno
+        ast_object.col_offset = col_offset
+        name_obj.name = name
+        ast_object.module = module
+        ast_object.names = [name_obj]
+
+        return ast_object
+
     def test_add_import_for_test_uuid_no_tempest(self):
         patcher = check_uuid.SourcePatcher()
         checker = check_uuid.TestChecker(importlib.import_module('tempest'))
-        fake_file = tempfile.NamedTemporaryFile("w+t")
+        fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
+        source_code = "from unittest import mock\n"
+        fake_file.write(source_code)
+        fake_file.close()
 
         class Fake_src_parsed():
-            body = ['test_node']
-        checker._import_name = mock.Mock(return_value='fake_module')
+            body = [TestTestChecker.get_mocked_ast_object(
+                1, 4, 'unittest', 'mock', ast.ImportFrom)]
 
-        checker._add_import_for_test_uuid(patcher, Fake_src_parsed(),
+        checker._add_import_for_test_uuid(patcher, Fake_src_parsed,
                                           fake_file.name)
-        (patch_id, patch), = patcher.patches.items()
-        self.assertEqual(patcher._quote('\n' + check_uuid.IMPORT_LINE + '\n'),
-                         patch)
-        self.assertEqual('{%s:s}' % patch_id,
-                         patcher.source_files[fake_file.name])
+        patcher.apply_patches()
+
+        with open(fake_file.name, "r") as f:
+            expected_result = source_code + '\n' + TestTestChecker.IMPORT_LINE
+            self.assertTrue(expected_result == f.read())
 
     def test_add_import_for_test_uuid_tempest(self):
         patcher = check_uuid.SourcePatcher()
         checker = check_uuid.TestChecker(importlib.import_module('tempest'))
         fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
-        test1 = ("    def test_test():\n"
-                 "        pass\n")
-        test2 = ("    def test_another_test():\n"
-                 "        pass\n")
-        source_code = test1 + test2
+        source_code = "from tempest import a_fake_module\n"
         fake_file.write(source_code)
         fake_file.close()
 
-        def fake_import_name(node):
-            return node.name
-        checker._import_name = fake_import_name
+        class Fake_src_parsed:
+            body = [TestTestChecker.get_mocked_ast_object(
+                1, 4, 'tempest', 'a_fake_module', ast.ImportFrom)]
 
-        class Fake_node():
-            def __init__(self, lineno, col_offset, name):
-                self.lineno = lineno
-                self.col_offset = col_offset
-                self.name = name
-
-        class Fake_src_parsed():
-            body = [Fake_node(1, 4, 'tempest.a_fake_module'),
-                    Fake_node(3, 4, 'another_fake_module')]
-
-        checker._add_import_for_test_uuid(patcher, Fake_src_parsed(),
+        checker._add_import_for_test_uuid(patcher, Fake_src_parsed,
                                           fake_file.name)
-        (patch_id, patch), = patcher.patches.items()
-        self.assertEqual(patcher._quote(check_uuid.IMPORT_LINE + '\n'),
-                         patch)
-        expected_source = patcher._quote(test1) + '{' + patch_id + ':s}' +\
-            patcher._quote(test2)
-        self.assertEqual(expected_source,
-                         patcher.source_files[fake_file.name])
+        patcher.apply_patches()
+
+        with open(fake_file.name, "r") as f:
+            expected_result = source_code + TestTestChecker.IMPORT_LINE
+            self.assertTrue(expected_result == f.read())
+
+    def test_add_import_no_import(self):
+        patcher = check_uuid.SourcePatcher()
+        patcher.add_patch = mock.Mock()
+        checker = check_uuid.TestChecker(importlib.import_module('tempest'))
+        fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
+        fake_file.close()
+
+        class Fake_src_parsed:
+            body = []
+
+        checker._add_import_for_test_uuid(patcher, Fake_src_parsed,
+                                          fake_file.name)
+
+        self.assertTrue(not patcher.add_patch.called)
diff --git a/tempest/tests/lib/services/identity/v3/test_access_rules_client.py b/tempest/tests/lib/services/identity/v3/test_access_rules_client.py
new file mode 100644
index 0000000..71c9cde
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_access_rules_client.py
@@ -0,0 +1,97 @@
+# Copyright 2019 SUSE LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import access_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestAccessRulesClient(base.BaseServiceTest):
+    FAKE_LIST_ACCESS_RULES = {
+        "links": {
+            "self": "https://example.com/identity/v3/users/" +
+                    "3e0716ae/access_rules",
+            "previous": None,
+            "next": None
+        },
+        "access_rules": [
+            {
+                "path": "/v2.0/metrics",
+                "links": {
+                    "self": "https://example.com/identity/v3/access_rules/" +
+                            "07d719df00f349ef8de77d542edf010c"
+                },
+                "id": "07d719df00f349ef8de77d542edf010c",
+                "service": "monitoring",
+                "method": "GET"
+            }
+        ]
+    }
+
+    FAKE_ACCESS_RULE_INFO = {
+        "access_rule": {
+            "path": "/v2.0/metrics",
+            "links": {
+                "self": "https://example.com/identity/v3/access_rules/" +
+                        "07d719df00f349ef8de77d542edf010c"
+            },
+            "id": "07d719df00f349ef8de77d542edf010c",
+            "service": "monitoring",
+            "method": "GET"
+        }
+    }
+
+    def setUp(self):
+        super(TestAccessRulesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = access_rules_client.AccessRulesClient(
+            fake_auth, 'identity', 'regionOne')
+
+    def _test_show_access_rule(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_access_rule,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ACCESS_RULE_INFO,
+            bytes_body,
+            user_id="123456",
+            access_rule_id="5499a186")
+
+    def _test_list_access_rules(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_access_rules,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ACCESS_RULES,
+            bytes_body,
+            user_id="123456")
+
+    def test_show_access_rule_with_str_body(self):
+        self._test_show_access_rule()
+
+    def test_show_access_rule_with_bytes_body(self):
+        self._test_show_access_rule(bytes_body=True)
+
+    def test_list_access_rule_with_str_body(self):
+        self._test_list_access_rules()
+
+    def test_list_access_rule_with_bytes_body(self):
+        self._test_list_access_rules(bytes_body=True)
+
+    def test_delete_access_rule(self):
+        self.check_service_client_function(
+            self.client.delete_access_rule,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            user_id="123456",
+            access_rule_id="5499a186",
+            status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
index 7faf6a0..e5f7a66 100644
--- a/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
@@ -83,6 +83,36 @@
         }
     }
 
+    FAKE_LIST_ENDPOINT_GROUPS_FOR_PROJECT = {
+        "endpoint_groups": [
+            {
+                "endpoint_group": {
+                    "description": "endpoint group description #2",
+                    "filters": {
+                        "interface": "admin"
+                    },
+                    "id": "3de68c",
+                    "name": "endpoint group name #2"
+                }
+            }
+            ],
+        "links": {
+            "self": "https://url/identity/v3/OS-EP-FILTER/endpoint_groups",
+        }
+    }
+
+    FAKE_PROJECT_INFO = {
+        "project": {
+            "domain_id": "1789d1",
+            "id": "263fd9",
+            "links": {
+                "self": "http://example.com/identity/v3/projects/263fd9"
+            },
+            "name": "project name #1",
+            "description": "project description #1"
+        }
+    }
+
     def setUp(self):
         super(TestEndPointsFilterClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -137,6 +167,52 @@
             project_id=3,
             endpoint_id=4)
 
+    def _test_list_endpoint_groups_for_project(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_endpoint_groups_for_project,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ENDPOINT_GROUPS_FOR_PROJECT,
+            bytes_body,
+            status=200,
+            project_id=3)
+
+    def _test_list_projects_for_endpoint_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_projects_for_endpoint_group,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_PROJECTS_FOR_ENDPOINTS,
+            bytes_body,
+            status=200,
+            endpoint_group_id=5)
+
+    def _test_list_endpoints_for_endpoint_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_endpoints_for_endpoint_group,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ENDPOINTS_FOR_PROJECTS,
+            bytes_body,
+            status=200,
+            endpoint_group_id=5)
+
+    def _test_add_endpoint_group_to_project(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.add_endpoint_group_to_project,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            status=204,
+            endpoint_group_id=5,
+            project_id=6)
+
+    def _test_show_endpoint_group_for_project(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_endpoint_group_for_project,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_PROJECT_INFO,
+            bytes_body,
+            endpoint_group_id=5,
+            project_id=6)
+
     def test_add_endpoint_to_project_with_str_body(self):
         self._test_add_endpoint_to_project()
 
@@ -163,3 +239,43 @@
 
     def test_delete_endpoint_from_project(self):
         self._test_delete_endpoint_from_project()
+
+    def test_list_endpoint_groups_for_project_with_str_body(self):
+        self._test_list_endpoint_groups_for_project()
+
+    def test_list_endpoint_groups_for_project_with_bytes_body(self):
+        self._test_list_endpoint_groups_for_project(bytes_body=True)
+
+    def test_list_projects_for_endpoint_group_with_str_body(self):
+        self._test_list_projects_for_endpoint_group()
+
+    def test_list_projects_for_endpoint_group_with_bytes_body(self):
+        self._test_list_projects_for_endpoint_group(bytes_body=True)
+
+    def test_list_endpoints_for_endpoint_group_with_str_body(self):
+        self._test_list_endpoints_for_endpoint_group()
+
+    def test_list_endpoints_for_endpoint_group_with_bytes_body(self):
+        self._test_list_endpoints_for_endpoint_group(bytes_body=True)
+
+    def test_add_endpoint_group_to_project_with_str_body(self):
+        self._test_add_endpoint_group_to_project()
+
+    def test_add_endpoint_group_to_project_with_bytes_body(self):
+        self._test_add_endpoint_group_to_project(bytes_body=True)
+
+    def test_show_endpoint_group_for_project_with_str_body(self):
+        self._test_show_endpoint_group_for_project()
+
+    def test_show_endpoint_group_for_project_with_bytes_body(self):
+        self._test_show_endpoint_group_for_project(bytes_body=True)
+
+    def test_delete_endpoint_group_from_project(self):
+        self.check_service_client_function(
+            self.client.delete_endpoint_group_from_project,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            False,
+            status=204,
+            endpoint_group_id=5,
+            project_id=5)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
index ca15dd1..0efc462 100644
--- a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
@@ -54,12 +54,44 @@
     }
 
     FAKE_SERVICE_ID = "a4dc5060-f757-4662-b658-edd2aefbb41d"
+    FAKE_ENDPOINT_ID = "b335d394-cdb9-4519-b95d-160b7706e54ew"
+
+    FAKE_UPDATE_ENDPOINT = {
+        "endpoint": {
+            "id": "828384",
+            "interface": "internal",
+            "links": {
+                "self": "http://example.com/identity/v3/"
+                        "endpoints/828384"
+            },
+            "region_id": "north",
+            "service_id": "686766",
+            "url": "http://example.com/identity/v3/"
+                   "endpoints/828384"
+        }
+    }
+
+    FAKE_SHOW_ENDPOINT = {
+        "endpoint": {
+            "enabled": True,
+            "id": "01c3d5b92f7841ac83fb4b26173c12c7",
+            "interface": "admin",
+            "links": {
+                "self": "http://example.com/identity/v3/"
+                        "endpoints/828384"
+            },
+            "region": "RegionOne",
+            "region_id": "RegionOne",
+            "service_id": "3b2d6ad7e02c4cde8498a547601f1b8f",
+            "url": "http://23.253.211.234:9696/"
+        }
+    }
 
     def setUp(self):
         super(TestEndpointsClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.client = endpoints_client.EndPointsClient(fake_auth,
-                                                       'identity', 'regionOne')
+        self.client = endpoints_client.EndPointsClient(
+            fake_auth, 'identity', 'regionOne')
 
     def _test_create_endpoint(self, bytes_body=False):
         self.check_service_client_function(
@@ -84,6 +116,38 @@
             mock_args=[mock_args],
             **params)
 
+    def _test_update_endpoint(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_endpoint,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_UPDATE_ENDPOINT,
+            bytes_body,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            interface="public",
+            region_id="north",
+            url="http://example.com/identity/v3/endpoints/828384",
+            service_id=self.FAKE_SERVICE_ID)
+
+    def _test_show_endpoint(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_endpoint,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_ENDPOINT,
+            bytes_body,
+            endpoint_id="3456")
+
+    def test_update_endpoint_with_str_body(self):
+        self._test_update_endpoint()
+
+    def test_update_endpoint_with_bytes_body(self):
+        self._test_update_endpoint(bytes_body=True)
+
+    def test_show_endpoint_with_str_body(self):
+        self._test_show_endpoint()
+
+    def test_show_endpoint_with_bytes_body(self):
+        self._test_show_endpoint(bytes_body=True)
+
     def test_create_endpoint_with_str_body(self):
         self._test_create_endpoint()
 
diff --git a/tempest/tests/lib/services/identity/v3/test_policies_client.py b/tempest/tests/lib/services/identity/v3/test_policies_client.py
index 0237475..4fc800a 100644
--- a/tempest/tests/lib/services/identity/v3/test_policies_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_policies_client.py
@@ -44,6 +44,34 @@
             }
         }
 
+    FAKE_ENDPOINT_INFO = {
+        "endpoints": [
+            {
+                "id": "1",
+                "interface": "public",
+                "links": {
+                    "self": "http://example.com/identity/v3/endpoints/1"
+                },
+                "region": "north",
+                "service_id": "9242e05f0c23467bbd1cf1f7a6e5e596",
+                "url": "http://example.com/identity/"
+            },
+            {
+                "id": "1",
+                "interface": "internal",
+                "links": {
+                    "self": "http://example.com/identity/v3/endpoints/1"
+                },
+                "region": "south",
+                "service_id": "9242e05f0c23467bbd1cf1f7a6e5e596",
+                "url": "http://example.com/identity/"
+            }
+        ],
+        "links": {
+            "self": "http://exmp.com/identity/v3/OS-ENDPOINT-POLICY/policies/1"
+        }
+    }
+
     FAKE_LIST_POLICIES = {
         "links": {
             "next": None,
@@ -238,3 +266,33 @@
             service_id=self.FAKE_SERVICE_ID,
             region_id=self.FAKE_REGION_ID,
             status=204)
+
+    def _test_list_endpoints_for_policy(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_endpoints_for_policy,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ENDPOINT_INFO,
+            bytes_body,
+            policy_id=self.FAKE_POLICY_ID,
+            status=200)
+
+    def test_list_endpoints_for_policy_with_str_body(self):
+        self._test_list_endpoints_for_policy()
+
+    def test_list_endpoints_for_policy_with_bytes_body(self):
+        self._test_list_endpoints_for_policy(bytes_body=True)
+
+    def _test_list_policy_for_endpoint(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_policy_for_endpoint,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_POLICY_INFO,
+            bytes_body,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            status=200)
+
+    def test_list_policy_for_endpoint_with_str_body(self):
+        self._test_list_policy_for_endpoint()
+
+    def test_list_policy_for_endpoint_with_bytes_body(self):
+        self._test_list_policy_for_endpoint(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_users_client.py b/tempest/tests/lib/services/identity/v3/test_users_client.py
index c0dfdae..7be0480 100644
--- a/tempest/tests/lib/services/identity/v3/test_users_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_users_client.py
@@ -141,6 +141,35 @@
         ]
     }
 
+    FAKE_USER_EC2_CREDENTIAL_INFO = {
+        "credential": {
+            'user_id': '9beb0e12f3e5416db8d7cccfc785db3b',
+            'access': '79abf59acc77492a86170cbe2f1feafa',
+            'secret': 'c4e7d3a691fd4563873d381a40320f46',
+            'trust_id': None,
+            'tenant_id': '596557269d7b4dd78631a602eb9f151d'
+        }
+    }
+
+    FAKE_LIST_USER_EC2_CREDENTIALS = {
+        "credentials": [
+            {
+                'user_id': '9beb0e12f3e5416db8d7cccfc785db3b',
+                'access': '79abf59acc77492a86170cbe2f1feafa',
+                'secret': 'c4e7d3a691fd4563873d381a40320f46',
+                'trust_id': None,
+                'tenant_id': '596557269d7b4dd78631a602eb9f151d'
+            },
+            {
+                'user_id': '3beb0e12f3e5416db8d7cccfc785de4r',
+                'access': '45abf59acc77492a86170cbe2f1fesde',
+                'secret': 'g4e7d3a691fd4563873d381a40320e45',
+                'trust_id': None,
+                'tenant_id': '123557269d7b4dd78631a602eb9f112f'
+            }
+        ]
+    }
+
     def setUp(self):
         super(TestUsersClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -201,6 +230,33 @@
             user_id='817fb3c23fd7465ba6d7fe1b1320121d',
         )
 
+    def _test_create_user_ec2_credential(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_user_ec2_credential,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_USER_EC2_CREDENTIAL_INFO,
+            bytes_body,
+            status=201,
+            user_id="1",
+            tenant_id="123")
+
+    def _test_show_user_ec2_credential(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_user_ec2_credential,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_USER_EC2_CREDENTIAL_INFO,
+            bytes_body,
+            user_id="1",
+            access="123")
+
+    def _test_list_user_ec2_credentials(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_user_ec2_credentials,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_USER_EC2_CREDENTIALS,
+            bytes_body,
+            user_id="1")
+
     def test_create_user_with_string_body(self):
         self._test_create_user()
 
@@ -255,3 +311,30 @@
             user_id='817fb3c23fd7465ba6d7fe1b1320121d',
             password='NewTempestPassword',
             original_password='OldTempestPassword')
+
+    def test_create_user_ec2_credential_with_str_body(self):
+        self._test_create_user_ec2_credential()
+
+    def test_create_user_ec2_credential_with_bytes_body(self):
+        self._test_create_user_ec2_credential(bytes_body=True)
+
+    def test_show_user_ec2_credential_with_str_body(self):
+        self._test_show_user_ec2_credential()
+
+    def test_show_user_ec2_credential_with_bytes_body(self):
+        self._test_show_user_ec2_credential(bytes_body=True)
+
+    def test_list_user_ec2_credentials_with_str_body(self):
+        self._test_list_user_ec2_credentials()
+
+    def test_list_user_ec2_credentials_with_bytes_body(self):
+        self._test_list_user_ec2_credentials(bytes_body=True)
+
+    def test_delete_user_ec2_credential(self):
+        self.check_service_client_function(
+            self.client.delete_user_ec2_credential,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            user_id="123",
+            access="1234",
+            status=204)
diff --git a/tempest/tests/lib/services/network/test_floating_ips_client.py b/tempest/tests/lib/services/network/test_floating_ips_client.py
index c5b1845..e8f2e5a 100644
--- a/tempest/tests/lib/services/network/test_floating_ips_client.py
+++ b/tempest/tests/lib/services/network/test_floating_ips_client.py
@@ -27,6 +27,8 @@
             {
                 "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f",
                 "description": "for test",
+                "dns_domain": "my-domain.org.",
+                "dns_name": "myfip",
                 "created_at": "2016-12-21T10:55:50Z",
                 "updated_at": "2016-12-21T10:55:53Z",
                 "revision_number": 1,
@@ -37,11 +39,24 @@
                 "floating_ip_address": "172.24.4.228",
                 "port_id": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
                 "id": "2f245a7b-796b-4f26-9cf9-9e82d248fda7",
-                "status": "ACTIVE"
+                "status": "ACTIVE",
+                "port_details": {
+                    "status": "ACTIVE",
+                    "name": "",
+                    "admin_state_up": True,
+                    "network_id": "02dd8479-ef26-4398-a102-d19d0a7b3a1f",
+                    "device_owner": "compute:nova",
+                    "mac_address": "fa:16:3e:b1:3b:30",
+                    "device_id": "8e3941b4-a6e9-499f-a1ac-2a4662025cba"
+                },
+                "tags": ["tag1,tag2"],
+                "port_forwardings": []
             },
             {
                 "router_id": None,
                 "description": "for test",
+                "dns_domain": "my-domain.org.",
+                "dns_name": "myfip2",
                 "created_at": "2016-12-21T11:55:50Z",
                 "updated_at": "2016-12-21T11:55:53Z",
                 "revision_number": 2,
@@ -52,7 +67,10 @@
                 "floating_ip_address": "172.24.4.227",
                 "port_id": None,
                 "id": "61cea855-49cb-4846-997d-801b70c71bdd",
-                "status": "DOWN"
+                "status": "DOWN",
+                "port_details": None,
+                "tags": ["tag1,tag2"],
+                "port_forwardings": []
             }
         ]
     }
diff --git a/tempest/tests/lib/services/network/test_networks_client.py b/tempest/tests/lib/services/network/test_networks_client.py
index 078f4b0..17233bc 100644
--- a/tempest/tests/lib/services/network/test_networks_client.py
+++ b/tempest/tests/lib/services/network/test_networks_client.py
@@ -31,12 +31,17 @@
                     "nova"
                 ],
                 "created_at": "2016-03-08T20:19:41",
+                "dns_domain": "my-domain.org.",
                 "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+                "ipv4_address_scope": None,
+                "ipv6_address_scope": None,
+                "l2_adjacency": False,
                 "mtu": 0,
                 "name": "net1",
                 "port_security_enabled": True,
                 "project_id": "4fd44f30292945e481c7b8a0c8908869",
                 "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
+                "revision_number": 1,
                 "router:external": False,
                 "shared": False,
                 "status": "ACTIVE",
@@ -46,7 +51,8 @@
                 "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
                 "updated_at": "2016-03-08T20:19:41",
                 "vlan_transparent": True,
-                "description": ""
+                "description": "",
+                "is_default": False
             },
             {
                 "admin_state_up": True,
@@ -54,12 +60,18 @@
                 "availability_zones": [
                     "nova"
                 ],
+                "created_at": "2016-03-08T20:19:41",
+                "dns_domain": "my-domain.org.",
                 "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+                "ipv4_address_scope": None,
+                "ipv6_address_scope": None,
+                "l2_adjacency": False,
                 "mtu": 0,
                 "name": "net2",
                 "port_security_enabled": True,
                 "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
                 "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819",
+                "revision_number": 3,
                 "router:external": False,
                 "shared": False,
                 "status": "ACTIVE",
@@ -69,7 +81,8 @@
                 "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
                 "updated_at": "2016-03-08T20:19:41",
                 "vlan_transparent": False,
-                "description": ""
+                "description": "",
+                "is_default": False
             }
         ]
     }
@@ -108,6 +121,7 @@
                 "alive": True,
                 "topic": "dhcp_agent",
                 "host": "osboxes",
+                "ha_state": None,
                 "agent_type": "DHCP agent",
                 "resource_versions": {},
                 "created_at": "2017-06-19 21:39:51",
diff --git a/tempest/tests/lib/services/placement/test_resource_providers_client.py b/tempest/tests/lib/services/placement/test_resource_providers_client.py
new file mode 100644
index 0000000..11aeaf2
--- /dev/null
+++ b/tempest/tests/lib/services/placement/test_resource_providers_client.py
@@ -0,0 +1,119 @@
+#    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.placement import resource_providers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestResourceProvidersClient(base.BaseServiceTest):
+    FAKE_RESOURCE_PROVIDER_UUID = '3722a86e-a563-11e9-9abb-c3d41b6d3abf'
+    FAKE_ROOT_PROVIDER_UUID = '4a6a57c8-a563-11e9-914e-f3e0478fce53'
+    FAKE_RESOURCE_PROVIDER = {
+        'generation': 0,
+        'name': 'Ceph Storage Pool',
+        'uuid': FAKE_RESOURCE_PROVIDER_UUID,
+        'parent_provider_uuid': FAKE_ROOT_PROVIDER_UUID,
+        'root_provider_uuid': FAKE_ROOT_PROVIDER_UUID
+    }
+
+    FAKE_RESOURCE_PROVIDERS = {
+        'resource_providers': [FAKE_RESOURCE_PROVIDER]
+    }
+
+    FAKE_RESOURCE_PROVIDER_INVENTORIES = {
+        'inventories': {
+            'DISK_GB': {
+                'allocation_ratio': 1.0,
+                'max_unit': 35,
+                'min_unit': 1,
+                'reserved': 0,
+                'step_size': 1,
+                'total': 35
+            }
+        },
+        'resource_provider_generation': 7
+    }
+
+    FAKE_AGGREGATE_UUID = '1166be40-a567-11e9-9f2a-53827f9311fa'
+    FAKE_RESOURCE_PROVIDER_AGGREGATES = {
+        'aggregates': [FAKE_AGGREGATE_UUID]
+    }
+
+    def setUp(self):
+        super(TestResourceProvidersClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = resource_providers_client.ResourceProvidersClient(
+            fake_auth, 'placement', 'regionOne')
+
+    def _test_list_resource_providers(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_resource_providers,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_RESOURCE_PROVIDERS,
+            to_utf=bytes_body,
+            status=200
+        )
+
+    def test_list_resource_providers_with_bytes_body(self):
+        self._test_list_resource_providers()
+
+    def test_list_resource_providers_with_str_body(self):
+        self._test_list_resource_providers(bytes_body=True)
+
+    def _test_show_resource_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_resource_provider,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_RESOURCE_PROVIDER,
+            to_utf=bytes_body,
+            status=200,
+            rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID
+        )
+
+    def test_show_resource_provider_with_str_body(self):
+        self._test_show_resource_provider()
+
+    def test_show_resource_provider_with_bytes_body(self):
+        self._test_show_resource_provider(bytes_body=True)
+
+    def _test_list_resource_provider_inventories(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_resource_provider_inventories,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_RESOURCE_PROVIDER_INVENTORIES,
+            to_utf=bytes_body,
+            status=200,
+            rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID
+        )
+
+    def test_list_resource_provider_inventories_with_str_body(self):
+        self._test_list_resource_provider_inventories()
+
+    def test_list_resource_provider_inventories_with_bytes_body(self):
+        self._test_list_resource_provider_inventories(bytes_body=True)
+
+    def _test_list_resource_provider_aggregates(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_resource_provider_aggregates,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_RESOURCE_PROVIDER_AGGREGATES,
+            to_utf=bytes_body,
+            status=200,
+            rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID
+        )
+
+    def test_list_resource_provider_aggregates_with_str_body(self):
+        self._test_list_resource_provider_aggregates()
+
+    def test_list_resource_provider_aggregates_with_bytes_body(self):
+        self._test_list_resource_provider_aggregates(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
index d4313a2..3d47caf 100644
--- a/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
@@ -54,7 +54,6 @@
                 }
             ],
             "availability_zone": "nova",
-            "os-vol-host-attr:host": "controller1@rbd#rbd",
             "encrypted": False,
             "updated_at": None,
             "replication_status": None,
@@ -62,15 +61,12 @@
             "id": "c07cd4a4-b52b-4511-a176-fbaa2011a227",
             "size": 0,
             "user_id": "142d8663efce464c89811c63e45bd82e",
-            "os-vol-tenant-attr:tenant_id": "f21a9c86d7114bf99c711f4874d80474",
-            "os-vol-mig-status-attr:migstat": None,
             "metadata": {},
             "status": "creating",
             "description": "volume-manage-description",
             "multiattach": False,
             "source_volid": None,
             "consistencygroup_id": None,
-            "os-vol-mig-status-attr:name_id": None,
             "name": "volume-managed",
             "bootable": "false",
             "created_at": "2017-07-11T09:14:01.000000",
diff --git a/tools/tempest-integrated-gate-networking-blacklist.txt b/tools/tempest-integrated-gate-networking-blacklist.txt
index 263b2e4..97808d9 100644
--- a/tools/tempest-integrated-gate-networking-blacklist.txt
+++ b/tools/tempest-integrated-gate-networking-blacklist.txt
@@ -17,3 +17,8 @@
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
+
+# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
+tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
+tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
+tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-integrated-gate-placement-blacklist.txt b/tools/tempest-integrated-gate-placement-blacklist.txt
index efba796..657bda2 100644
--- a/tools/tempest-integrated-gate-placement-blacklist.txt
+++ b/tools/tempest-integrated-gate-placement-blacklist.txt
@@ -17,3 +17,8 @@
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
+
+# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
+tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
+tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
+tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-blacklist.txt
index 1ef6bb5..cbd3e9d 100644
--- a/tools/tempest-integrated-gate-storage-blacklist.txt
+++ b/tools/tempest-integrated-gate-storage-blacklist.txt
@@ -12,3 +12,8 @@
 tempest.scenario.test_network_basic_ops.TestNetworkBasicOps
 tempest.scenario.test_network_v6.TestGettingAddress
 tempest.scenario.test_security_groups_basic_ops.TestSecurityGroupsBasicOps
+
+# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
+tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
+tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
+tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tox.ini b/tox.ini
index 0477d6f..2ea8129 100644
--- a/tox.ini
+++ b/tox.ini
@@ -279,18 +279,33 @@
 [testenv:docs]
 deps =
   -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-  -r{toxinidir}/requirements.txt
   -r{toxinidir}/doc/requirements.txt
 commands =
+  sphinx-apidoc -f -o doc/source/tests/compute tempest/api/compute
+  sphinx-apidoc -f -o doc/source/tests/identity tempest/api/identity
+  sphinx-apidoc -f -o doc/source/tests/image tempest/api/image
+  sphinx-apidoc -f -o doc/source/tests/network tempest/api/network
+  sphinx-apidoc -f -o doc/source/tests/object_storage tempest/api/object_storage
+  sphinx-apidoc -f -o doc/source/tests/scenario tempest/scenario
+  sphinx-apidoc -f -o doc/source/tests/volume tempest/api/volume
   rm -rf doc/build
   sphinx-build -W -b html doc/source doc/build/html
-whitelist_externals = rm
+whitelist_externals =
+    rm
 
 [testenv:pdf-docs]
 deps = {[testenv:docs]deps}
 whitelist_externals =
+   rm
    make
 commands =
+   sphinx-apidoc -f -o doc/source/tests/compute tempest/api/compute
+   sphinx-apidoc -f -o doc/source/tests/identity tempest/api/identity
+   sphinx-apidoc -f -o doc/source/tests/image tempest/api/image
+   sphinx-apidoc -f -o doc/source/tests/network tempest/api/network
+   sphinx-apidoc -f -o doc/source/tests/object_storage tempest/api/object_storage
+   sphinx-apidoc -f -o doc/source/tests/scenario tempest/scenario
+   sphinx-apidoc -f -o doc/source/tests/volume tempest/api/volume
    sphinx-build -W -b latex doc/source doc/build/pdf
    make -C doc/build/pdf
 
@@ -349,7 +364,6 @@
 [testenv:releasenotes]
 deps =
   -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-  -r{toxinidir}/requirements.txt
   -r{toxinidir}/doc/requirements.txt
 commands =
   rm -rf releasenotes/build