Merge "Adds protocol options for test_cross_tenant_traffic"
diff --git a/.gitignore b/.gitignore
index 7cb052f..06a2281 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,8 @@
 *.pyc
 __pycache__/
 etc/accounts.yaml
-etc/tempest.conf
+etc/*.conf
 etc/tempest.conf.sample
-etc/logging.conf
 include/swift_objects/swift_small
 include/swift_objects/swift_medium
 include/swift_objects/swift_large
diff --git a/.gitreview b/.gitreview
index 84b5114..a475594 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
 [gerrit]
-host=review.openstack.org
+host=review.opendev.org
 port=29418
 project=openstack/tempest.git
diff --git a/.mailmap b/.mailmap
index 3ea6ab0..fc74aa9 100644
--- a/.mailmap
+++ b/.mailmap
@@ -6,16 +6,18 @@
 Andrea Frittoli <andrea.frittoli@gmail.com> <andrea.frittoli@hpe.com>
 Daryl Walleck <daryl.walleck@rackspace.com> <daryl.walleck@rackspace.com>
 David Kranz <dkranz@redhat.com> David Kranz <david.kranz@qrclab.com>
-Ghanshyam <ghanshyam.mann@nectechnologies.in> <ghanshyam.mann@nectechnologies.in>
-Ghanshyam <ghanshyam.mann@nectechnologies.in> <ghanshyam.mann@nectechnologies.in>
+Ghanshyam Mann <gmann@ghanshyammann.com> <ghanshyam.mann@nectechnologies.in>
+Ghanshyam Mann <gmann@ghanshyammann.com> <ghanshyam.mann@india.nec.com>
+Ghanshyam Mann <gmann@ghanshyammann.com> <ghanshyammann@gmail.com>
 Jay Pipes <jaypipes@gmail.com> <jpipes@librebox.gateway.2wire.net>
 Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
 Ken'ichi Ohmichi <ken-oomichi@wx.jp.nec.com> <oomichi@mxs.nes.nec.co.jp>
 Ken'ichi Ohmichi <ken-oomichi@wx.jp.nec.com> <ken1ohmichi@gmail.com>
 Marc Koderer <marc@koderer.com> <m.koderer@telekom.de>
-Masayuki Igawa <masayuki@igawa.me> <igawa@mxs.nes.nec.co.jp>
-Masayuki Igawa <masayuki@igawa.me> <mas-igawa@ut.jp.nec.com>
-Masayuki Igawa <masayuki@igawa.me> <masayuki.igawa@gmail.com>
+Masayuki Igawa <masayuki@igawa.io> <igawa@mxs.nes.nec.co.jp>
+Masayuki Igawa <masayuki@igawa.io> <mas-igawa@ut.jp.nec.com>
+Masayuki Igawa <masayuki@igawa.io> <masayuki.igawa@gmail.com>
+Masayuki Igawa <masayuki@igawa.io> <masayuki@igawa.me>
 Matthew Treinish <mtreinish@kortar.org> <treinish@linux.vnet.ibm.com>
 Nayna Patel <nayna.patel@hp.com> <nayna.patel@hp.com>
 ravikumar-venkatesan <ravikumar.venkatesan@hp.com> <ravikumar.venkatesan@hp.com>
diff --git a/.stestr.conf b/.stestr.conf
index e3201c1..818c743 100644
--- a/.stestr.conf
+++ b/.stestr.conf
@@ -1,4 +1,3 @@
 [DEFAULT]
 test_path=./tempest/test_discover
 group_regex=([^\.]*\.)*
-
diff --git a/.testr.conf b/.testr.conf
deleted file mode 100644
index 95a4fb4..0000000
--- a/.testr.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-[DEFAULT]
-test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
-             OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
-             OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \
-             OS_TEST_LOCK_PATH=${OS_TEST_LOCK_PATH:-${TMPDIR:-'/tmp'}} \
-             ${PYTHON:-python} -m subunit.run discover -t ${OS_TOP_LEVEL:-./} ${OS_TEST_PATH:-./tempest/test_discover} $LISTOPT $IDOPTION
-test_id_option=--load-list $IDFILE
-test_list_option=--list
-group_regex=([^\.]*\.)*
diff --git a/.zuul.yaml b/.zuul.yaml
index 5b73695..0d2005b 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,19 +1,499 @@
 - job:
     name: devstack-tempest
     parent: devstack
-    description: Base Tempest job.
+    description: |
+      Base Tempest job.
+
+      This Tempest job provides the base for both the single and multi-node
+      test setup. To run a multi-node test inherit from devstack-tempest and
+      set the nodeset to a multi-node one.
     required-projects:
-      - openstack/tempest
+      - opendev.org/openstack/tempest
     timeout: 7200
     roles:
-      - zuul: openstack-dev/devstack
+      - zuul: opendev.org/openstack/devstack
     vars:
       devstack_services:
-        tempest: True
+        tempest: true
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            compute:
+              min_compute_nodes: "{{ groups['compute'] | default(['controller']) | length }}"
+      test_results_stage_name: test_results
+      zuul_copy_output:
+        '{{ devstack_base_dir }}/tempest/etc/tempest.conf': logs
+        '{{ devstack_base_dir }}/tempest/etc/accounts.yaml': logs
+        '{{ devstack_base_dir }}/tempest/tempest.log': logs
+        '{{ stage_dir }}/{{ test_results_stage_name }}.subunit': logs
+        '{{ stage_dir }}/{{ test_results_stage_name }}.html': logs
+        '{{ stage_dir }}/stackviz': logs
+      extensions_to_txt:
+        conf: true
+        log: true
+        yaml: true
+        yml: true
     run: playbooks/devstack-tempest.yaml
+    post-run: playbooks/post-tempest.yaml
+
+- job:
+    name: tempest-all
+    parent: devstack-tempest
+    description: |
+      Integration test that runs all tests.
+      Former name for this job was:
+        * legacy-periodic-tempest-dsvm-all-master
+    vars:
+      tox_envlist: all
+      tempest_test_regex: tempest
+      devstack_localrc:
+        ENABLE_FILE_INJECTION: true
+
+- job:
+    name: devstack-tempest-ipv6
+    parent: devstack-ipv6
+    description: |
+      Base Tempest IPv6 job.
+    required-projects:
+      - opendev.org/openstack/tempest
+    timeout: 7200
+    roles:
+      - zuul: opendev.org/openstack/devstack
+    vars:
+      devstack_services:
+        tempest: true
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            compute:
+              min_compute_nodes: "{{ groups['compute'] | default(['controller']) | length }}"
+      test_results_stage_name: test_results
+      zuul_copy_output:
+        '{{ devstack_base_dir }}/tempest/etc/tempest.conf': logs
+        '{{ devstack_base_dir }}/tempest/etc/accounts.yaml': logs
+        '{{ devstack_base_dir }}/tempest/tempest.log': logs
+        '{{ stage_dir }}/{{ test_results_stage_name }}.subunit': logs
+        '{{ stage_dir }}/{{ test_results_stage_name }}.html': logs
+        '{{ stage_dir }}/stackviz': logs
+      extensions_to_txt:
+        conf: true
+        log: true
+        yaml: true
+        yml: true
+    run: playbooks/devstack-tempest.yaml
+    post-run: playbooks/post-tempest.yaml
+
+- job:
+    name: tempest-full
+    parent: devstack-tempest
+    # This currently works from stable/pike on.
+    # Before stable/pike, legacy version of tempest-full
+    # 'legacy-tempest-dsvm-neutron-full' run.
+    branches: ^(?!stable/ocata).*$
+    description: |
+      Base integration test with Neutron networking and py27.
+      Former names for this job where:
+        * legacy-tempest-dsvm-neutron-full
+        * gate-tempest-dsvm-neutron-full-ubuntu-xenial
+    vars:
+      tox_envlist: full
+      devstack_localrc:
+        ENABLE_FILE_INJECTION: true
+        ENABLE_VOLUME_MULTIATTACH: true
+
+- job:
+    name: tempest-full-oslo-master
+    parent: tempest-full
+    description: |
+      Integration test using current git of oslo libs.
+      This ensures that when oslo libs get released that they
+      do not break OpenStack server projects.
+
+      Former name for this job was
+      periodic-tempest-dsvm-oslo-latest-full-master.
+    timeout: 10800
+    required-projects:
+      - opendev.org/openstack/oslo.cache
+      - opendev.org/openstack/oslo.concurrency
+      - opendev.org/openstack/oslo.config
+      - opendev.org/openstack/oslo.context
+      - opendev.org/openstack/oslo.db
+      - opendev.org/openstack/oslo.i18n
+      - opendev.org/openstack/oslo.log
+      - opendev.org/openstack/oslo.messaging
+      - opendev.org/openstack/oslo.middleware
+      - opendev.org/openstack/oslo.policy
+      - opendev.org/openstack/oslo.privsep
+      - opendev.org/openstack/oslo.reports
+      - opendev.org/openstack/oslo.rootwrap
+      - opendev.org/openstack/oslo.serialization
+      - opendev.org/openstack/oslo.service
+      - opendev.org/openstack/oslo.utils
+      - opendev.org/openstack/oslo.versionedobjects
+      - opendev.org/openstack/oslo.vmware
+
+- job:
+    name: tempest-full-parallel
+    parent: tempest-full
+    voting: false
+    branches:
+      - master
+    description: |
+      Base integration test with Neutron networking and py27.
+      It includes all scenarios as it was in the past.
+      This job runs all scenario tests in parallel!
+    vars:
+      tox_envlist: full-parallel
+
+- job:
+    name: tempest-full-py3
+    parent: devstack-tempest
+    # This currently works from stable/pike on.
+    # Before stable/pike, legacy version of tempest-full
+    # 'legacy-tempest-dsvm-neutron-full' run.
+    branches: ^(?!stable/ocata).*$
+    description: |
+      Base integration test with Neutron networking and py3.
+      Former names for this job where:
+        * legacy-tempest-dsvm-py35
+        * gate-tempest-dsvm-py35
+    vars:
+      tox_envlist: full
+      devstack_localrc:
+        USE_PYTHON3: true
+        FORCE_CONFIG_DRIVE: true
+        ENABLE_VOLUME_MULTIATTACH: true
+      devstack_services:
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        # without Swift, c-bak cannot run (in the Gate at least)
+        c-bak: false
+
+- job:
+    name: tempest-full-py3-ipv6
+    parent: devstack-tempest-ipv6
+    # This currently works from stable/pike on.
+    # Before stable/pike, legacy version of tempest-full
+    # 'legacy-tempest-dsvm-neutron-full' run.
+    branches: ^(?!stable/ocata).*$
+    description: |
+      Base integration test with Neutron networking, IPv6 and py3.
+    vars:
+      tox_envlist: full
+      devstack_localrc:
+        USE_PYTHON3: true
+        FORCE_CONFIG_DRIVE: true
+      devstack_services:
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        # without Swift, c-bak cannot run (in the Gate at least)
+        c-bak: false
+
+- job:
+    name: tempest-multinode-full-base
+    parent: devstack-tempest
+    description: |
+      Base multinode integration test with Neutron networking and py27.
+      Former names for this job were:
+        * neutron-tempest-multinode-full
+        * legacy-tempest-dsvm-neutron-multinode-full
+        * gate-tempest-dsvm-neutron-multinode-full-ubuntu-xenial-nv
+      This job includes two nodes, controller / tempest plus a subnode, but
+      it can be used with different topologies, as long as a controller node
+      and a tempest one exist.
+    timeout: 10800
+    vars:
+      tox_envlist: full
+      devstack_localrc:
+        FORCE_CONFIG_DRIVE: false
+        NOVA_ALLOW_MOVE_TO_SAME_HOST: false
+        LIVE_MIGRATION_AVAILABLE: true
+        USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true
+    group-vars:
+      peers:
+        devstack_localrc:
+          NOVA_ALLOW_MOVE_TO_SAME_HOST: false
+          LIVE_MIGRATION_AVAILABLE: true
+          USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true
+
+- 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)).*$
+
+- job:
+    name: tempest-multinode-full
+    parent: tempest-multinode-full-base
+    nodeset: openstack-two-node-xenial
+    # This job runs on Xenial and this is for stable/pike, stable/queens
+    # and stable/rocky. This job is prepared to make sure all stable branches
+    # before stable/stein will keep running on xenial. This job can be
+    # removed once stable/rocky is EOL.
+    branches:
+      - stable/pike
+      - stable/queens
+      - stable/rocky
+
+- job:
+    name: tempest-multinode-full-py3
+    parent: tempest-multinode-full
+    vars:
+      devstack_localrc:
+        USE_PYTHON3: true
+
+- job:
+    name: tempest-full-py3-opensuse150
+    parent: tempest-full-py3
+    nodeset: devstack-single-node-opensuse-150
+    description: |
+      Base integration test with Neutron networking and py36 running
+      on openSUSE Leap 15.0
+    voting: false
+
+- job:
+    name: tempest-slow
+    parent: tempest-multinode-full
+    description: |
+      This multinode integration job will run all the tests tagged as slow.
+      It enables the lvm multibackend setup to cover few scenario tests.
+      This job will run only slow tests (API or Scenario) serially.
+
+      Former names for this job were:
+        * legacy-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend
+        * tempest-scenario-multinode-lvm-multibackend
+    timeout: 10800
+    vars:
+      tox_envlist: slow-serial
+      devstack_localrc:
+        CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
+        ENABLE_VOLUME_MULTIATTACH: true
+      tempest_concurrency: 2
+    group-vars:
+      # NOTE(mriedem): The ENABLE_VOLUME_MULTIATTACH variable is used on both
+      # the controller and subnode prior to Rocky so we have to make sure the
+      # variable is set in both locations.
+      subnode:
+        devstack_localrc:
+          ENABLE_VOLUME_MULTIATTACH: true
+
+- job:
+    name: tempest-slow-py3
+    parent: tempest-slow
+    vars:
+      devstack_localrc:
+        USE_PYTHON3: true
+      devstack_services:
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        # without Swift, c-bak cannot run (in the Gate at least)
+        c-bak: false
+
+- job:
+    name: tempest-full-stein
+    parent: tempest-full
+    override-checkout: stable/stein
+
+- job:
+    name: tempest-full-stein-py3
+    parent: tempest-full-py3
+    override-checkout: stable/stein
+
+- job:
+    name: tempest-full-rocky
+    parent: tempest-full
+    nodeset: openstack-single-node-xenial
+    override-checkout: stable/rocky
+
+- job:
+    name: tempest-full-rocky-py3
+    parent: tempest-full-py3
+    nodeset: openstack-single-node-xenial
+    override-checkout: stable/rocky
+
+- job:
+    name: tempest-full-queens
+    parent: tempest-full
+    nodeset: openstack-single-node-xenial
+    override-checkout: stable/queens
+
+- job:
+    name: tempest-full-queens-py3
+    parent: tempest-full-py3
+    nodeset: openstack-single-node-xenial
+    override-checkout: stable/queens
+
+- job:
+    name: tempest-full-pike
+    parent: tempest-full
+    nodeset: openstack-single-node-xenial
+    override-checkout: stable/pike
+
+- job:
+    name: tempest-tox-plugin-sanity-check
+    parent: tox
+    description: |
+      Run tempest plugin sanity check script using tox.
+    nodeset: ubuntu-bionic
+    vars:
+      tox_envlist: plugin-sanity-check
+    voting: false
+    timeout: 5000
+    irrelevant-files:
+      - ^.*\.rst$
+      - ^doc/.*$
+      - ^etc/.*$
+      - ^releasenotes/.*$
+      - ^tempest/hacking/.*$
+      - ^tempest/tests/.*$
+    required-projects:
+      - opendev.org/airship/tempest-plugin
+      - opendev.org/x/almanach
+      - opendev.org/openstack/aodh
+      - opendev.org/openstack/barbican-tempest-plugin
+      - opendev.org/openstack/blazar-tempest-plugin
+      - opendev.org/openstack/ceilometer
+      - opendev.org/openstack/cinder-tempest-plugin
+      - opendev.org/openstack/cloudkitty-tempest-plugin
+      - opendev.org/openstack/congress-tempest-plugin
+      - opendev.org/openstack/cyborg-tempest-plugin
+      - opendev.org/openstack/designate-tempest-plugin
+      - opendev.org/openstack/ec2api-tempest-plugin
+      - opendev.org/openstack/freezer
+      - opendev.org/openstack/freezer-api
+      - opendev.org/openstack/freezer-tempest-plugin
+      - opendev.org/x/gabbi-tempest
+      - opendev.org/x/gce-api
+      - opendev.org/x/glare
+      - opendev.org/openstack/heat-tempest-plugin
+      - opendev.org/x/intel-nfv-ci-tests
+      - opendev.org/openstack/ironic-tempest-plugin
+      - opendev.org/openstack/ironic-inspector
+      - opendev.org/openstack/keystone-tempest-plugin
+      - opendev.org/x/kingbird
+      - opendev.org/openstack/kuryr-tempest-plugin
+      - opendev.org/openstack/magnum
+      - opendev.org/openstack/magnum-tempest-plugin
+      - opendev.org/openstack/manila
+      - opendev.org/openstack/manila-tempest-plugin
+      - opendev.org/openstack/mistral-tempest-plugin
+      - opendev.org/x/mogan
+      - opendev.org/openstack/monasca-api
+      - opendev.org/openstack/monasca-log-api
+      - opendev.org/openstack/monasca-tempest-plugin
+      - opendev.org/openstack/murano-tempest-plugin
+      - opendev.org/x/networking-ansible
+      - opendev.org/openstack/networking-bgpvpn
+      - opendev.org/x/networking-cisco
+      - opendev.org/x/networking-fortinet
+      - opendev.org/openstack/networking-generic-switch
+      - opendev.org/openstack/networking-l2gw-tempest-plugin
+      - opendev.org/openstack/networking-midonet
+      - opendev.org/openstack/networking-sfc
+      - opendev.org/x/networking-spp
+      - opendev.org/openstack/neutron
+      - opendev.org/openstack/neutron-dynamic-routing
+      - opendev.org/openstack/neutron-fwaas
+      - opendev.org/openstack/neutron-lbaas
+      - opendev.org/openstack/neutron-tempest-plugin
+      - opendev.org/openstack/neutron-vpnaas
+      - opendev.org/x/nova-lxd
+      - opendev.org/x/novajoin-tempest-plugin
+      - opendev.org/openstack/octavia-tempest-plugin
+      - opendev.org/openstack/oswin-tempest-plugin
+      - opendev.org/openstack/panko
+      - opendev.org/openstack/patrole
+      - opendev.org/openstack/python-watcherclient
+      - opendev.org/openstack/qinling
+      - opendev.org/openstack/requirements
+      - opendev.org/openstack/sahara-tests
+      - opendev.org/openstack/senlin
+      - opendev.org/openstack/senlin-tempest-plugin
+      - opendev.org/openstack/solum-tempest-plugin
+      - opendev.org/x/tap-as-a-service
+      - opendev.org/openstack/telemetry-tempest-plugin
+      - opendev.org/openstack/tempest-horizon
+      - opendev.org/x/tobiko
+      - opendev.org/x/trio2o
+      - opendev.org/openstack/tripleo-common-tempest-plugin
+      - opendev.org/openstack/trove-tempest-plugin
+      - opendev.org/x/valet
+      - opendev.org/openstack/vitrage-tempest-plugin
+      - opendev.org/x/vmware-nsx-tempest-plugin
+      - opendev.org/openstack/watcher-tempest-plugin
+      - opendev.org/openstack/zaqar-tempest-plugin
+      - opendev.org/openstack/zun-tempest-plugin
+
+- job:
+    name: tempest-cinder-v2-api
+    parent: devstack-tempest
+    branches:
+      - master
+    description: |
+      This job runs the cinder API test against v2 endpoint.
+    vars:
+      tox_envlist: all
+      tempest_test_regex: api.*volume
+      devstack_localrc:
+        TEMPEST_VOLUME_TYPE: volumev2
+
+- job:
+    name: tempest-full-test-account-py3
+    parent: tempest-full-py3
+    description: |
+      This job runs the full set of tempest tests using pre-provisioned
+      credentials instead of dynamic credentials and py3.
+      Former names for this job were:
+        - legacy-tempest-dsvm-full-test-accounts
+        - legacy-tempest-dsvm-neutron-full-test-accounts
+        - legacy-tempest-dsvm-identity-v3-test-accounts
+    vars:
+      devstack_localrc:
+        TEMPEST_USE_TEST_ACCOUNTS: True
+
+- job:
+    name: tempest-full-test-account-no-admin-py3
+    parent: tempest-full-test-account-py3
+    description: |
+      This job runs the full set of tempest tests using pre-provisioned
+      credentials and py3 without having an admin account.
+      Former name for this job was:
+        - legacy-tempest-dsvm-neutron-full-non-admin
+
+    vars:
+      devstack_localrc:
+        TEMPEST_HAS_ADMIN: False
+
+- job:
+    name: tempest-pg-full
+    parent: tempest-full
+    description: |
+      Base integration test with Neutron networking and py27 and PostgreSQL.
+      Former name for this job was legacy-tempest-dsvm-neutron-pg-full.
+    vars:
+      devstack_localrc:
+        ENABLE_FILE_INJECTION: true
+        DATABASE_TYPE: postgresql
 
 - project:
-    name: openstack/tempest
+    templates:
+      - check-requirements
+      - integrated-gate
+      - integrated-gate-py3
+      - openstack-cover-jobs
+      - openstack-python-jobs
+      - openstack-python35-jobs
+      - openstack-python36-jobs
+      - openstack-python37-jobs
+      - publish-openstack-docs-pti
+      - release-notes-jobs-python3
     check:
       jobs:
         - devstack-tempest:
@@ -21,3 +501,140 @@
               - ^playbooks/
               - ^roles/
               - ^.zuul.yaml$
+        - devstack-tempest-ipv6:
+            voting: false
+            files:
+              - ^playbooks/
+              - ^roles/
+              - ^.zuul.yaml$
+        - tempest-full-parallel:
+            # Define list of irrelevant files to use everywhere else
+            irrelevant-files: &tempest-irrelevant-files
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+              - ^tools/.*$
+        - tempest-full-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3-ipv6:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-stein:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-stein-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-rocky:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-rocky-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-queens:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-queens-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-pike:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-multinode-full:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-multinode-full-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-tox-plugin-sanity-check:
+            irrelevant-files:
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+              # tools/ is not here since this relies on a script in tools/.
+        - tempest-slow:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-slow-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - nova-live-migration:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-grenade-multinode:
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-grenade:
+            irrelevant-files: *tempest-irrelevant-files
+        - grenade-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - devstack-plugin-ceph-tempest:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - puppet-openstack-integration-4-scenario001-tempest-centos-7:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - puppet-openstack-integration-4-scenario002-tempest-centos-7:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - puppet-openstack-integration-4-scenario003-tempest-centos-7:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - puppet-openstack-integration-4-scenario004-tempest-centos-7:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-tempest-dvr:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full:
+            irrelevant-files: *tempest-irrelevant-files
+        - interop-tempest-consistency:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-test-account-py3:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-test-account-no-admin-py3:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+    gate:
+      jobs:
+        - tempest-slow-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-grenade-multinode:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full:
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-grenade:
+            irrelevant-files: *tempest-irrelevant-files
+        - grenade-py3:
+            irrelevant-files: *tempest-irrelevant-files
+    experimental:
+      jobs:
+        - tempest-cinder-v2-api:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-all:
+            irrelevant-files: *tempest-irrelevant-files
+        - legacy-tempest-dsvm-neutron-dvr-multinode-full:
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-tempest-dvr-ha-multinode-full:
+            irrelevant-files: *tempest-irrelevant-files
+        - nova-cells-v1:
+            irrelevant-files: *tempest-irrelevant-files
+        - nova-tempest-v2-api:
+            irrelevant-files: *tempest-irrelevant-files
+        - legacy-tempest-dsvm-lvm-multibackend:
+            irrelevant-files: *tempest-irrelevant-files
+        - devstack-plugin-ceph-tempest-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-pg-full:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3-opensuse150:
+            irrelevant-files: *tempest-irrelevant-files
+    periodic-stable:
+      jobs:
+        - tempest-full-stein
+        - tempest-full-stein-py3
+        - tempest-full-rocky
+        - tempest-full-rocky-py3
+        - tempest-full-queens
+        - tempest-full-queens-py3
+        - tempest-full-pike
+    periodic:
+      jobs:
+        - tempest-all
+        - tempest-full-oslo-master
diff --git a/HACKING.rst b/HACKING.rst
index 8407734..204b3c7 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -6,17 +6,17 @@
 - Step 2: Read on
 
 Tempest Specific Commandments
-------------------------------
+-----------------------------
 
 - [T102] Cannot import OpenStack python clients in tempest/api &
-         tempest/scenario tests
+  tempest/scenario tests
 - [T104] Scenario tests require a services decorator
 - [T105] Tests cannot use setUpClass/tearDownClass
 - [T106] vim configuration should not be kept in source files.
 - [T107] Check that a service tag isn't in the module path
 - [T108] Check no hyphen at the end of rand_name() argument
 - [T109] Cannot use testtools.skip decorator; instead use
-         decorators.skip_because from tempest.lib
+  decorators.skip_because from tempest.lib
 - [T110] Check that service client names of GET should be consistent
 - [T111] Check that service client names of DELETE should be consistent
 - [T112] Check that tempest.lib should not import local tempest code
@@ -25,6 +25,10 @@
 - [T115] Check that admin tests should exist under admin path
 - [N322] Method's default argument shouldn't be mutable
 - [T116] Unsupported 'message' Exception attribute in PY3
+- [T117] Check negative tests have ``@decorators.attr(type=['negative'])``
+  applied.
+
+It is recommended to use ``tox -eautopep8`` before submitting a patch.
 
 Test Data/Configuration
 -----------------------
@@ -33,6 +37,30 @@
 - Clean up test data at the completion of each test
 - Use configuration files for values that will vary by environment
 
+Supported OpenStack Components
+------------------------------
+
+Tempest's :ref:`library` and :ref:`plugin interface <tempest_plugin>` can be
+leveraged to support integration testing for virtually any OpenStack component.
+
+However, Tempest only offers **in-tree** integration testing coverage for the
+following components:
+
+* Cinder
+* Glance
+* Keystone
+* Neutron
+* Nova
+* Swift
+
+Historically, Tempest offered in-tree testing for other components as well, but
+since the introduction of the `External Plugin Interface`_, Tempest's in-tree
+testing scope has been limited to the projects above. Integration tests for
+projects not included above should go into one of the
+`relevant plugin projects`_.
+
+.. _External Plugin Interface: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/tempest-external-plugin-interface.html
+.. _relevant plugin projects: https://docs.openstack.org/tempest/latest/plugin-registry.html#detected-plugins
 
 Exception Handling
 ------------------
@@ -84,7 +112,7 @@
 It is recommended to use testtools `matcher`_ for the more tricky assertions.
 You can implement your own specific `matcher`_ as well.
 
-.. _matcher: http://testtools.readthedocs.org/en/latest/for-test-authors.html#matchers
+.. _matcher: https://testtools.readthedocs.org/en/latest/for-test-authors.html#matchers
 
 If the test case fails you can see the related logs and the information
 carried by the exception (exception class, backtrack and exception info).
@@ -106,7 +134,7 @@
 test method. You specify the services with the ``tempest.common.utils.services``
 decorator. For example:
 
-@utils.services('compute', 'image')
+``@utils.services('compute', 'image')``
 
 Valid service tag names are the same as the list of directories in tempest.api
 that have tests.
@@ -118,41 +146,89 @@
 in ``tempest.api.compute`` would require a service tag for those services,
 however they do not need to be tagged as ``compute``.
 
+Test Attributes
+---------------
+Tempest leverages `test attributes`_ which are a simple but effective way of
+distinguishing between different "types" of API tests. A test can be "tagged"
+with such attributes using the ``decorators.attr`` decorator, for example::
+
+    @decorators.attr(type=['negative'])
+    def test_aggregate_create_aggregate_name_length_less_than_1(self):
+        [...]
+
+These test attributes can be used for test selection via regular expressions.
+For example, ``(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)`` runs all the tests
+in the ``scenario`` test module, *except* for those tagged with the ``slow``
+attribute (via a negative lookahead in the regular expression). These
+attributes are used in Tempest's ``tox.ini`` as well as Tempest's Zuul job
+definitions for specifying particular batches of Tempest test suites to run.
+
+.. _test attributes: https://testtools.readthedocs.io/en/latest/for-test-authors.html?highlight=attr#test-attributes
+
+Negative Attribute
+^^^^^^^^^^^^^^^^^^
+The ``type='negative'`` attribute is used to signify that a test is a negative
+test, which is a test that handles invalid input gracefully. This attribute
+should be applied to all negative test scenarios.
+
+This attribute must be applied to each test that belongs to a negative test
+class, i.e. a test class name ending with "Negative.*" substring.
+
+Slow Attribute
+^^^^^^^^^^^^^^
+The ``type='slow'`` attribute is used to signify that a test takes a long time
+to run, relatively speaking. This attribute is usually applied to
+:ref:`scenario tests <scenario_field_guide>`, which involve a complicated
+series of API operations, the total runtime of which can be relatively long.
+This long runtime has performance implications on `Zuul`_ jobs, which is why
+the ``slow`` attribute is leveraged to run slow tests on a selective basis,
+to keep total `Zuul`_ job runtime down to a reasonable time frame.
+
+.. _Zuul: https://docs.openstack.org/infra/zuul/
+
+Smoke Attribute
+^^^^^^^^^^^^^^^
+The ``type='smoke'`` attribute is used to signify that a test is a so-called
+smoke test, which is a type of test that tests the most vital OpenStack
+functionality, like listing servers or flavors or creating volumes. The
+attribute should be sparingly applied to only the tests that sanity-check the
+most essential functionality of an OpenStack cloud.
+
 Test fixtures and resources
 ---------------------------
 Test level resources should be cleaned-up after the test execution. Clean-up
-is best scheduled using `addCleanup` which ensures that the resource cleanup
+is best scheduled using ``addCleanup`` which ensures that the resource cleanup
 code is always invoked, and in reverse order with respect to the creation
 order.
 
-Test class level resources should be defined in the `resource_setup` method of
-the test class, except for any credential obtained from the credentials
-provider, which should be set-up in the `setup_credentials` method.
-Cleanup is best scheduled using `addClassResourceCleanup` which ensures that
+Test class level resources should be defined in the ``resource_setup`` method
+of the test class, except for any credential obtained from the credentials
+provider, which should be set-up in the ``setup_credentials`` method.
+Cleanup is best scheduled using ``addClassResourceCleanup`` which ensures that
 the cleanup code is always invoked, and in reverse order with respect to the
 creation order.
 
 In both cases - test level and class level cleanups - a wait loop should be
 scheduled before the actual delete of resources with an asynchronous delete.
 
-The test base class `BaseTestCase` defines Tempest framework for class level
-fixtures. `setUpClass` and `tearDownClass` are defined here and cannot be
+The test base class ``BaseTestCase`` defines Tempest framework for class level
+fixtures. ``setUpClass`` and ``tearDownClass`` are defined here and cannot be
 overwritten by subclasses (enforced via hacking rule T105).
 
 Set-up is split in a series of steps (setup stages), which can be overwritten
 by test classes. Set-up stages are:
 
-- `skip_checks`
-- `setup_credentials`
-- `setup_clients`
-- `resource_setup`
+- ``skip_checks``
+- ``setup_credentials``
+- ``setup_clients``
+- ``resource_setup``
 
 Tear-down is also split in a series of steps (teardown stages), which are
 stacked for execution only if the corresponding setup stage had been
 reached during the setup phase. Tear-down stages are:
 
-- `clear_credentials` (defined in the base test class)
-- `resource_cleanup`
+- ``clear_credentials`` (defined in the base test class)
+- ``resource_cleanup``
 
 Skipping Tests
 --------------
@@ -178,7 +254,7 @@
 All negative tests should be based on `API-WG guideline`_ . Such negative
 tests can block any changes from accurate failure code to invalid one.
 
-.. _API-WG guideline: http://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications
+.. _API-WG guideline: https://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications
 
 If facing some gray area which is not clarified on the above guideline, propose
 a new guideline to the API-WG. With a proposal to the API-WG we will be able to
@@ -299,18 +375,19 @@
 docstrings for the workflow in each test methods can be used instead. A good
 example of this would be::
 
-    class TestVolumeBootPattern(manager.ScenarioTest):
-        """
-        This test case attempts to reproduce the following steps:
+    class TestServerBasicOps(manager.ScenarioTest):
 
-         * Create in Cinder some bootable volume importing a Glance image
-         * Boot an instance from the bootable volume
-         * Write content to the volume
-         * Delete an instance and Boot a new instance from the volume
-         * Check written content in the instance
-         * Create a volume snapshot while the instance is running
-         * Boot an additional instance from the new snapshot based volume
-         * Check written content in the instance booted from snapshot
+        """The test suite for server basic operations
+
+        This smoke test case follows this basic set of operations:
+         * Create a keypair for use in launching an instance
+         * Create a security group to control network access in instance
+         * Add simple permissive rules to the security group
+         * Launch an instance
+         * Perform ssh to instance
+         * Verify metadata service
+         * Verify metadata on config_drive
+         * Terminate the instance
         """
 
 Test Identification with Idempotent ID
@@ -363,13 +440,24 @@
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 When adding tests for new features that were not in previous releases of the
-projects the new test has to be properly skipped with a feature flag. Whether
-this is just as simple as using the @utils.requires_ext() decorator to
-check if the required extension (or discoverable optional API) is enabled or
+projects the new test has to be properly skipped with a feature flag. This can
+be just as simple as using the ``@utils.requires_ext()`` or
+``testtools.skipUnless`` decorators to check if the required extension (or
+discoverable optional API) or feature is enabled or can be as difficult as
 adding a new config option to the appropriate section. If there isn't a method
 of selecting the new **feature** from the config file then there won't be a
-mechanism to disable the test with older stable releases and the new test won't
-be able to merge.
+mechanism to disable the test with older stable releases and the new test
+won't be able to merge.
+
+Introduction of a new feature flag requires specifying a default value for
+the corresponding config option that is appropriate in the latest OpenStack
+release. Because Tempest is branchless, the feature flag's default value will
+need to be overridden to a value that is appropriate in earlier releases
+in which the feature isn't available. In DevStack, this can be accomplished
+by modifying Tempest's `lib installation script`_ for previous branches
+(because DevStack is branched).
+
+.. _lib installation script: https://opendev.org/openstack/devstack/src/branch/master/lib/tempest
 
 2. Bug fix on core project needing Tempest changes
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -385,7 +473,7 @@
 
 Otherwise the bug fix won't be able to land in the project.
 
-Handily, `Zuul’s cross-repository dependencies
+Handily, `Zuul's cross-repository dependencies
 <https://docs.openstack.org/infra/zuul/user/gating.html#cross-project-dependencies>`_.
 can be leveraged to do without step 2 and to have steps 3 and 4 happen
 "atomically". To do that, make the patch written in step 1 to depend (refer to
diff --git a/README.rst b/README.rst
index c67362a..e8206ee 100644
--- a/README.rst
+++ b/README.rst
@@ -2,7 +2,7 @@
 Team and repository tags
 ========================
 
-.. image:: http://governance.openstack.org/badges/tempest.svg
+.. image:: https://governance.openstack.org/tc/badges/tempest.svg
     :target: https://governance.openstack.org/tc/reference/tags/index.html
 
 .. Change things from this point on
@@ -15,7 +15,7 @@
 
 This is a set of integration tests to be run against a live OpenStack
 cluster. Tempest has batteries of tests for OpenStack API validation,
-Scenarios, and other specific tests useful in validating an OpenStack
+scenarios, and other specific tests useful in validating an OpenStack
 deployment.
 
 Design Principles
@@ -61,7 +61,7 @@
 #. You first need to install Tempest. This is done with pip after you check out
    the Tempest repo::
 
-    $ git clone http://git.openstack.org/openstack/tempest
+    $ git clone https://opendev.org/openstack/tempest
     $ pip install tempest/
 
    This can be done within a venv, but the assumption for this guide is that
@@ -95,10 +95,12 @@
    command. Tempest is expecting a ``tempest.conf`` file in etc/ so if only a
    sample exists you must rename or copy it to tempest.conf before making
    any changes to it otherwise Tempest will not know how to load it. For
-   details on configuring Tempest refer to the :ref:`tempest-configuration`.
+   details on configuring Tempest refer to the
+   `Tempest Configuration <https://docs.openstack.org/tempest/latest/configuration.html#tempest-configuration>`_
 
 #. Once the configuration is done you're now ready to run Tempest. This can
-   be done using the :ref:`tempest_run` command. This can be done by either
+   be done using the `Tempest Run <https://docs.openstack.org/tempest/latest/run.html#tempest-run>`_
+   command. This can be done by either
    running::
 
     $ tempest run
@@ -109,15 +111,18 @@
 
     $ tempest run --workspace cloud-01
 
-   There is also the option to use testr directly, or any `testr`_ based test
-   runner, like `ostestr`_. For example, from the workspace dir run::
+   There is also the option to use `stestr`_ directly. For example, from
+   the workspace dir run::
 
-    $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))'
+    $ stestr run --black-regex '\[.*\bslow\b.*\]' '^tempest\.(api|scenario)'
 
-   will run the same set of tests as the default gate jobs.
+   will run the same set of tests as the default gate jobs. Or you can
+   use `unittest`_ compatible test runners such as `testr`_, `pytest`_ etc.
 
+.. _unittest: https://docs.python.org/3/library/unittest.html
 .. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
-.. _ostestr: https://docs.openstack.org/os-testr/latest/
+.. _stestr: https://stestr.readthedocs.org/en/latest/MANUAL.html
+.. _pytest: https://docs.pytest.org/en/latest/
 
 Library
 -------
@@ -129,11 +134,12 @@
 stable interface and there are no guarantees on the Python API unless otherwise
 stated.
 
-For more details refer to the library documentation here: :ref:`library`
+For more details refer to the `library documentation
+<https://docs.openstack.org/tempest/latest/library.html#library>`_
 
 Release Versioning
 ------------------
-`Tempest Release Notes <http://docs.openstack.org/releasenotes/tempest>`_
+`Tempest Release Notes <https://docs.openstack.org/releasenotes/tempest>`_
 shows what changes have been released on each version.
 
 Tempest's released versions are broken into 2 sets of information. Depending on
@@ -159,14 +165,16 @@
 interface and when Z is incremented it's a bug fix release for the library.
 Also note that both Y and Z are reset to 0 at each increment of X.
 
-.. _semver: http://semver.org/
+.. _semver: https://semver.org/
 
 Configuration
 -------------
 
 Detailed configuration of Tempest is beyond the scope of this
-document see :ref:`tempest-configuration` for more details on configuring
-Tempest. The ``etc/tempest.conf.sample`` attempts to be a self-documenting
+document, see `Tempest Configuration Documentation
+<https://docs.openstack.org/tempest/latest/configuration.html#tempest-configuration>`_
+for more details on configuring Tempest.
+The ``etc/tempest.conf.sample`` attempts to be a self-documenting
 version of the configuration.
 
 You can generate a new sample tempest.conf file, run the following
@@ -190,20 +198,27 @@
 is ``test_path=./tempest/test_discover`` which will only run test discover on the
 Tempest suite.
 
-Alternatively, there are the py27 and py35 tox jobs which will run the unit
+Alternatively, there are the py27 and py36 tox jobs which will run the unit
 tests with the corresponding version of python.
 
-Python 2.6
-----------
+One common activity is to just run a single test, you can do this with tox
+simply by specifying to just run py27 or py36 tests against a single test::
 
-Starting in the Kilo release the OpenStack services dropped all support for
-python 2.6. This change has been mirrored in Tempest, starting after the
-tempest-2 tag. This means that proposed changes to Tempest which only fix
-python 2.6 compatibility will be rejected, and moving forward more features not
-present in python 2.6 will be used. If you're running your OpenStack services
-on an earlier release with python 2.6 you can easily run Tempest against it
-from a remote system running python 2.7. (or deploy a cloud guest in your cloud
-that has python 2.7)
+    $ tox -e py36 -- -n tempest.tests.test_microversions.TestMicroversionsTestsClass.test_config_version_none_23
+
+Or all tests in the test_microversions.py file::
+
+    $ tox -e py36 -- -n tempest.tests.test_microversions
+
+You may also use regular expressions to run any matching tests::
+
+    $ tox -e py36 -- test_microversions
+
+Additionally, when running a single test, or test-file, the ``-n/--no-discover``
+argument is no longer required, however it may perform faster if included.
+
+For more information on these options and details about stestr, please see the
+`stestr documentation <https://stestr.readthedocs.io/en/latest/MANUAL.html>`_.
 
 Python 3.x
 ----------
diff --git a/REVIEWING.rst b/REVIEWING.rst
index 7d28320..498ce66 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -2,7 +2,7 @@
 ======================
 
 To start read the `OpenStack Common Review Checklist
-<http://docs.openstack.org/infra/manual/developers.html#peer-review>`_
+<https://docs.openstack.org/infra/manual/developers.html#peer-review>`_
 
 
 Ensuring code is executed
@@ -16,7 +16,7 @@
 If a new test is added that depends on a new config option (like a feature
 flag), the commit message must reference a change in DevStack or DevStack-Gate
 that enables the execution of this newly introduced test. This reference could
-either be a `Cross-Repository Dependency <http://docs.openstack.org/infra/
+either be a `Cross-Repository Dependency <https://docs.openstack.org/infra/
 manual/developers.html#cross-repository-dependencies>`_ or a simple link
 to a Gerrit review.
 
@@ -36,14 +36,17 @@
 For any change that adds new functionality to either common functionality or an
 out-of-band tool unit tests are required. This is to ensure we don't introduce
 future regressions and to test conditions which we may not hit in the gate runs.
-Tests, and service clients aren't required to have unit tests since they should
-be self verifying by running them in the gate.
+API and scenario tests aren't required to have unit tests since they should
+be self-verifying by running them in the gate. All service clients, on the
+other hand, `must have`_ unit tests, as they belong to ``tempest/lib``.
+
+.. _must have: https://docs.openstack.org/tempest/latest/library.html#testing
 
 
 API Stability
 -------------
-Tests should only be added for a published stable APIs. If a patch contains
-tests for an API which hasn't been marked as stable or for an API that which
+Tests should only be added for published stable APIs. If a patch contains
+tests for an API which hasn't been marked as stable or for an API which
 doesn't conform to the `API stability guidelines
 <https://wiki.openstack.org/wiki/Governance/Approved/APIStability>`_ then it
 should not be approved.
@@ -99,6 +102,39 @@
 scenario tests this is up to the reviewers discretion whether a docstring is
 required or not.
 
+
+Test Removal and Refactoring
+----------------------------
+Make sure that any test that is renamed, relocated (e.g. moved to another
+class), or removed does not belong to the `interop`_ testing suite -- which
+includes a select suite of Tempest tests for the purposes of validating that
+OpenStack vendor clouds are interoperable -- or a project's `whitelist`_ or
+`blacklist`_ files.
+
+It is of critical importance that no interop, whitelist or blacklist test
+reference be broken by a patch set introduced to Tempest that renames,
+relocates or removes a referenced test.
+
+Please check the existence of code which references Tempest tests with:
+http://codesearch.openstack.org/
+
+Interop
+^^^^^^^
+Make sure that modifications to an `interop`_ test are backwards-compatible.
+This means that code modifications to tests should not undermine the quality of
+the validation currently performed by the test or significantly alter the
+behavior of the test.
+
+Removal
+^^^^^^^
+Reference the :ref:`test-removal` guidelines for understanding best practices
+associated with test removal.
+
+.. _interop: https://www.openstack.org/brand/interop
+.. _whitelist: https://docs.openstack.org/tempest/latest/run.html#test-selection
+.. _blacklist: https://docs.openstack.org/tempest/latest/run.html#test-selection
+
+
 Release Notes
 -------------
 Release notes are how we indicate to users and other consumers of Tempest what
@@ -113,16 +149,42 @@
 
 .. _reno: https://docs.openstack.org/reno/latest/
 
+
 Deprecated Code
 ---------------
 Sometimes we have some bugs in deprecated code. Basically, we leave it. Because
 we don't need to maintain it. However, if the bug is critical, we might need to
 fix it. When it will happen, we will deal with it on a case-by-case basis.
 
+
 When to approve
 ---------------
- * Every patch needs two +2s before being approved.
- * Its ok to hold off on an approval until a subject matter expert reviews it
- * If a patch has already been approved but requires a trivial rebase to merge,
-   you do not have to wait for a second +2, since the patch has already had
-   two +2s.
+* It's OK to hold off on an approval until a subject matter expert reviews it.
+* Every patch needs two +2's before being approved.
+* However, a single Tempest core reviewer can approve patches without waiting
+  for another +2 in the following cases:
+
+  * If a patch has already been approved but requires a trivial rebase to
+    merge, then there is no need to wait for a second +2, since the patch has
+    already had two +2's.
+  * If any trivial patch set fixes one of the items below:
+
+    * Documentation or code comment typo
+    * Documentation ref link
+    * Example: `example`_
+
+    .. note::
+
+      Any other small documentation, CI job, or code change does not fall under
+      this category.
+
+  * If the patch **unblocks** a failing project gate, provided that:
+
+    * the project's PTL +1's the change
+    * the patch does not affect any other project's testing gates
+    * the patch does not cause any negative side effects
+
+  Note that such a policy should be used judiciously, as we should strive to
+  have two +2's on each patch set, prior to approval.
+
+.. _example: https://review.opendev.org/#/c/611032/
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..d959d44
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,6 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+openstackdocstheme>=1.18.1 # Apache-2.0
+reno>=2.5.0 # Apache-2.0
+sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 0a061b8..c2ea628 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -64,7 +64,7 @@
 # openstackdocstheme options
 repository_name = 'openstack/tempest'
 bug_project = 'tempest'
-bug_tag = ''
+bug_tag = 'doc'
 
 # Must set this variable to include year, month, day, hours, and minutes.
 html_last_updated_fmt = '%Y-%m-%d %H:%M'
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 8f2865a..2e5f706 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -17,10 +17,10 @@
 Tempest allows for configuring a set of admin credentials in the ``auth``
 section, via the following parameters:
 
- #. ``admin_username``
- #. ``admin_password``
- #. ``admin_project_name``
- #. ``admin_domain_name``
+#. ``admin_username``
+#. ``admin_password``
+#. ``admin_project_name``
+#. ``admin_domain_name``
 
 Admin credentials are not mandatory to run Tempest, but when provided they
 can be used to:
@@ -47,9 +47,9 @@
 to provide it with information about how it communicates with keystone.
 This involves configuring the following options in the ``identity`` section:
 
- - ``auth_version``
- - ``uri``
- - ``uri_v3``
+- ``auth_version``
+- ``uri``
+- ``uri_v3``
 
 The ``auth_version`` option is used to tell Tempest whether it should be using
 Keystone's v2 or v3 api for communicating with Keystone. The two uri options are
@@ -74,12 +74,12 @@
 an admin user, and an alternate user. To enable and use dynamic credentials you
 only need to configure two things:
 
- #. A set of admin credentials with permissions to create users and
-    projects. This is specified in the ``auth`` section with the
-    ``admin_username``, ``admin_project_name``, ``admin_domain_name`` and
-    ``admin_password`` options
- #. To enable dynamic credentials in the ``auth`` section with the
-    ``use_dynamic_credentials`` option.
+#. A set of admin credentials with permissions to create users and
+   projects. This is specified in the ``auth`` section with the
+   ``admin_username``, ``admin_project_name``, ``admin_domain_name`` and
+   ``admin_password`` options
+#. To enable dynamic credentials in the ``auth`` section with the
+   ``use_dynamic_credentials`` option.
 
 This is also currently the default credential provider enabled by Tempest, due
 to its common use and ease of configuration.
@@ -115,21 +115,21 @@
 
 To enable and use locking test accounts you need do a few things:
 
- #. Create an accounts.yaml file which contains the set of pre-existing
-    credentials to use for testing. To make sure you don't have a credentials
-    starvation issue when running in parallel make sure you have at least two
-    times the number of worker processes you are using to execute Tempest
-    available in the file. (If running serially the worker count is 1.)
+#. Create an accounts.yaml file which contains the set of pre-existing
+   credentials to use for testing. To make sure you don't have a credentials
+   starvation issue when running in parallel make sure you have at least two
+   times the number of worker processes you are using to execute Tempest
+   available in the file. (If running serially the worker count is 1.)
 
-    You can check the accounts.yaml.sample file packaged in Tempest for the yaml
-    format.
- #. Provide Tempest with the location of your accounts.yaml file with the
-    ``test_accounts_file`` option in the ``auth`` section
+   You can check the accounts.yaml.sample file packaged in Tempest for the yaml
+   format.
+#. Provide Tempest with the location of your accounts.yaml file with the
+   ``test_accounts_file`` option in the ``auth`` section
 
-    *NOTE: Be sure to use a full path for the file; otherwise Tempest will
-    likely not find it.*
+   *NOTE: Be sure to use a full path for the file; otherwise Tempest will
+   likely not find it.*
 
- #. Set ``use_dynamic_credentials = False`` in the ``auth`` group
+#. Set ``use_dynamic_credentials = False`` in the ``auth`` group
 
 It is worth pointing out that each set of credentials in the accounts.yaml
 should have a unique project. This is required to provide proper isolation
@@ -162,8 +162,8 @@
 can use to boot the servers with. There are two options in the Tempest config
 for doing this:
 
- #. ``flavor_ref``
- #. ``flavor_ref_alt``
+#. ``flavor_ref``
+#. ``flavor_ref_alt``
 
 Both of these options are in the ``compute`` section of the config file and take
 in the flavor id (not the name) from Nova. The ``flavor_ref`` option is what
@@ -172,7 +172,7 @@
 resize test).
 
 Using a smaller flavor is generally recommended. When larger flavors are used,
-the extra time required to bring up servers will likely affect total run time
+the extra time required to bring up servers will likely affect the total run time
 and probably require tweaking timeout values to ensure tests have ample time to
 finish.
 
@@ -181,8 +181,8 @@
 Just like with flavors, Tempest needs to know which images to use for booting
 servers. There are two options in the compute section just like with flavors:
 
- #. ``image_ref``
- #. ``image_ref_alt``
+#. ``image_ref``
+#. ``image_ref_alt``
 
 Both options are expecting an image id (not name) from Nova. The ``image_ref``
 option is what will be used for booting the majority of servers in Tempest.
@@ -192,13 +192,13 @@
 
 There are also options in the ``scenario`` section for images:
 
- #. ``img_file``
- #. ``img_dir``
- #. ``aki_img_file``
- #. ``ari_img_file``
- #. ``ami_img_file``
- #. ``img_container_format``
- #. ``img_disk_format``
+#. ``img_file``
+#. ``img_dir``
+#. ``aki_img_file``
+#. ``ari_img_file``
+#. ``ami_img_file``
+#. ``img_container_format``
+#. ``img_disk_format``
 
 However, unlike the other image options, these are used for a very small subset
 of scenario tests which are uploading an image. These options are used to tell
@@ -207,7 +207,7 @@
 
 The behavior of these options is a bit convoluted (which will likely be fixed in
 future versions). You first need to specify ``img_dir``, which is the directory
-in which Tempest will look for the image files. First it will check if the
+in which Tempest will look for the image files. First, it will check if the
 filename set for ``img_file`` could be found in ``img_dir``. If it is found then
 the ``img_container_format`` and ``img_disk_format`` options are used to upload
 that image to glance. However, if it is not found, Tempest will look for the
@@ -239,7 +239,7 @@
 """"""""""""""""""""""""""""""""""
 When Tempest creates servers for testing, some tests require being able to
 connect those servers. Depending on the configuration of the cloud, the methods
-for doing this can be different. In certain configurations it is required to
+for doing this can be different. In certain configurations, it is required to
 specify a single network with server create calls. Accordingly, Tempest provides
 a few different methods for providing this information in configuration to try
 and ensure that regardless of the cloud's configuration it'll still be able to
@@ -261,7 +261,7 @@
 
 To set a fixed network name simply:
 
- #. Set the ``fixed_network_name`` option in the ``compute`` group
+#. Set the ``fixed_network_name`` option in the ``compute`` group
 
 In the case that the configured fixed network name can not be found by a user
 network list call, it will be treated like one was not provided except that a
@@ -297,10 +297,10 @@
 ''''''''''''''''''''''''
 With dynamic credentials enabled and using nova-network, your only option for
 configuration is to either set a fixed network name or not. However, in most
-cases it shouldn't matter because nova-network should have no problem booting a
+cases, it shouldn't matter because nova-network should have no problem booting a
 server with multiple networks. If this is not the case for your cloud then using
 an accounts file is recommended because it provides the necessary flexibility to
-describe your configuration. Dynamic credentials is not able to dynamically
+describe your configuration. Dynamic credentials are not able to dynamically
 allocate things as necessary if Neutron is not enabled.
 
 With Neutron and dynamic credentials enabled there should not be any additional
@@ -329,9 +329,9 @@
 
 To enable remote access to servers, there are 3 options at a minimum that are used:
 
- #. ``run_validation``
- #. ``connect_method``
- #. ``auth_method``
+#. ``run_validation``
+#. ``connect_method``
+#. ``auth_method``
 
 The ``run_validation`` is used to enable or disable ssh connectivity for
 all tests (with the exception of scenario tests which do not have a flag for
@@ -352,7 +352,7 @@
 OpenStack is really a constellation of several different projects which
 are running together to create a cloud. However which projects you're running
 is not set in stone, and which services are running is up to the deployer.
-Tempest however needs to know which services are available so it can figure
+Tempest, however, needs to know which services are available so it can figure
 out which tests it is able to run and certain setup steps which differ based
 on the available services.
 
@@ -370,9 +370,9 @@
 service catalog. There are three options for each service section to accomplish
 this:
 
- #. ``catalog_type``
- #. ``endpoint_type``
- #. ``region``
+#. ``catalog_type``
+#. ``endpoint_type``
+#. ``region``
 
 Setting ``catalog_type`` and ``endpoint_type`` should normally give Tempest
 enough information to determine which endpoint it should pull from the service
@@ -390,8 +390,8 @@
 
 .. note::
 
-    Tempest does not serve all kinds of fancy URLs in the service catalog.  The
-    service catalog should be in a standard format (which is going to be
+    Tempest does not serve all kinds of fancy URLs in the service catalog.
+    The service catalog should be in a standard format (which is going to be
     standardized at the Keystone level).
     Tempest expects URLs in the Service catalog in the following format:
 
@@ -400,7 +400,7 @@
     Examples:
 
     * Good - ``http://example.com:1234/v2.0``
-    * Wouldn’t work -  ``http://example.com:1234/xyz/v2.0/``
+    * Wouldn't work -  ``http://example.com:1234/xyz/v2.0/``
       (adding prefix/suffix around version etc)
 
 Service Feature Configuration
@@ -413,10 +413,10 @@
 certain operations and features aren't supported depending on the configuration.
 These features may or may not be discoverable from the API so the burden is
 often on the user to figure out what is supported by the cloud they're talking
-to.  Besides the obvious interoperability issues with this it also leaves
+to. Besides the obvious interoperability issues with this, it also leaves
 Tempest in an interesting situation trying to figure out which tests are
 expected to work. However, Tempest tests do not rely on dynamic API discovery
-for a feature (assuming one exists). Instead Tempest has to be explicitly
+for a feature (assuming one exists). Instead, Tempest has to be explicitly
 configured as to which optional features are enabled. This is in order to
 prevent bugs in the discovery mechanisms from masking failures.
 
@@ -432,8 +432,8 @@
 ^^^^^^^^^^^^^^
 The service feature-enabled sections often contain an ``api-extensions`` option
 (or in the case of Swift a ``discoverable_apis`` option). This is used to tell
-Tempest which api extensions (or configurable middleware) is used in your
+Tempest which API extensions (or configurable middleware) is used in your
 deployment. It has two valid config states: either it contains a single value
-``all`` (which is the default) which means that every api extension is assumed
+``all`` (which is the default) which means that every API extension is assumed
 to be enabled, or it is set to a list of each individual extension that is
 enabled for that service.
diff --git a/doc/source/data/tempest-plugins-registry.header b/doc/source/data/tempest-plugins-registry.header
index 9821e8e..831d8a6 100644
--- a/doc/source/data/tempest-plugins-registry.header
+++ b/doc/source/data/tempest-plugins-registry.header
@@ -3,9 +3,9 @@
   job.  You should edit the files data/tempest-plugins-registry.footer
   and data/tempest-plugins-registry.header instead of this one.
 
-==========================
- Tempest Plugin Registry
-==========================
+=======================
+Tempest Plugin Registry
+=======================
 
 Since we've created the external plugin mechanism, it's gotten used by
 a lot of projects. The following is a list of plugins that currently
@@ -17,7 +17,3 @@
 The following are plugins that a script has found in the openstack/
 namespace, which includes but is not limited to official OpenStack
 projects.
-
-+----------------------------+-------------------------------------------------------------------------+
-|Plugin Name                 |URL                                                                      |
-+----------------------------+-------------------------------------------------------------------------+
diff --git a/doc/source/index.rst b/doc/source/index.rst
index f562850..fecf98a 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -80,6 +80,14 @@
 
    library
 
+Support Policy
+--------------
+
+.. toctree::
+   :maxdepth: 2
+
+   stable_branch_support_policy
+
 Indices and tables
 ==================
 
diff --git a/doc/source/library.rst b/doc/source/library.rst
index 074d642..6a12c45 100644
--- a/doc/source/library.rst
+++ b/doc/source/library.rst
@@ -4,12 +4,12 @@
 =============================
 
 Tempest provides a stable library interface that provides external tools or
-test suites an interface for reusing pieces of tempest code. Any public
-interface that lives in tempest/lib in the tempest repo is treated as a stable
+test suites an interface for reusing pieces of Tempest code. Any public
+interface that lives in tempest/lib in the Tempest repo is treated as a stable
 public interface and it should be safe to external consume that. Every effort
 goes into maintaining backwards compatibility with any change.
 The library is self contained and doesn't have any dependency on
-other tempest internals outside of lib (including no usage of tempest
+other Tempest internals outside of lib (including no usage of Tempest
 configuration).
 
 Stability
@@ -32,7 +32,7 @@
 Making changes
 ''''''''''''''
 When making changes to tempest/lib you have to be conscious of the effect of
-any changes on external consumers. If your proposed changeset will change the
+any changes on external consumers. If your proposed change set will change the
 default behaviour of any interface, or make something which previously worked
 not after your change, then it is not acceptable. Every effort needs to go into
 preserving backwards compatibility in changes.
@@ -40,19 +40,19 @@
 Reviewing
 '''''''''
 When reviewing a proposed change to tempest/lib code we need to be careful to
-ensure that we don't break backwards compatibility. For patches that change
-existing interfaces we have to be careful to make sure we don't break any
+ensure that we don't break backward compatibility. For patches that change
+existing interfaces, we have to be careful to make sure we don't break any
 external consumers. Some common red flags are:
 
- * a change to an existing API requires a change outside the library directory
-   where the interface is being consumed
- * a unit test has to be significantly changed to make the proposed change pass
+* a change to an existing API requires a change outside the library directory
+  where the interface is being consumed
+* a unit test has to be significantly changed to make the proposed change pass
 
 Testing
 '''''''
 When adding a new interface to the library we need to at a minimum have unit
 test coverage. A proposed change to add an interface to tempest/lib that
-doesn't have unit tests shouldn't be accepted. Ideally these unit tests will
+doesn't have unit tests shouldn't be accepted. Ideally, these unit tests will
 provide sufficient coverage to ensure a stable interface moving forward.
 
 Current Library APIs
diff --git a/doc/source/library/credential_providers.rst b/doc/source/library/credential_providers.rst
index d96c97a..d25f85c 100644
--- a/doc/source/library/credential_providers.rst
+++ b/doc/source/library/credential_providers.rst
@@ -49,7 +49,7 @@
               public_network_id=CONF.network.public_network_id,
               create_networks=(CONF.auth.create_isolated_networks and not
                                CONF.network.shared_physical_network),
-              resource_prefix=CONF.resources_prefix,
+              resource_prefix='tempest',
               credentials_domain=CONF.auth.default_credentials_domain_name,
               admin_role=CONF.identity.admin_role,
               identity_uri=CONF.identity.uri_v3,
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index acf5593..b4f06e3 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -33,46 +33,46 @@
 Tempest will cover only integration testing of applicable microversions with
 below exceptions:
 
-  #. Test covers a feature which is important for interoperability. This covers tests requirement
-     from Defcore.
-  #. Test needed to fill Schema gaps.
-     Tempest validates API responses with defined JSON schema. API responses can be different on
-     each microversion and the JSON schemas need to be defined separately for the microversion.
-     While implementing new integration tests for a specific microversion, there
-     may be a gap in the JSON schemas (caused by previous microversions) implemented
-     in Tempest.
-     Filling that gap while implementing the new integration test cases is not efficient due to
-     many reasons:
+#. Test covers a feature which is important for interoperability. This covers tests requirement
+   from Defcore.
+#. Test needed to fill Schema gaps.
+   Tempest validates API responses with defined JSON schema. API responses can be different on
+   each microversion and the JSON schemas need to be defined separately for the microversion.
+   While implementing new integration tests for a specific microversion, there
+   may be a gap in the JSON schemas (caused by previous microversions) implemented
+   in Tempest.
+   Filling that gap while implementing the new integration test cases is not efficient due to
+   many reasons:
 
-     * Hard to review
-     * Sync between multiple integration tests patches which try to fill the same schema gap at same
-       time
-     * Might delay the microversion change on project side where project team wants Tempest
-       tests to verify the results.
+   * Hard to review
+   * Sync between multiple integration tests patches which try to fill the same schema gap at same
+     time
+   * Might delay the microversion change on project side where project team wants Tempest
+     tests to verify the results.
 
-     Tempest will allow to fill the schema gaps at the end of each cycle, or more
-     often if required.
-     Schema gap can be filled with testing those with a minimal set of tests. Those
-     tests might not be integration tests and might be already covered on project
-     side also.
-     This exception is needed because:
+   Tempest will allow to fill the schema gaps at the end of each cycle, or more
+   often if required.
+   Schema gap can be filled with testing those with a minimal set of tests. Those
+   tests might not be integration tests and might be already covered on project
+   side also.
+   This exception is needed because:
 
-     * Allow to create microversion response schema in Tempest at the same time that projects are
-       implementing their API microversions. This will make implementation easier for adding
-       required tests before a new microversion change can be merged in the corresponding project
-       and hence accelerate the development of microversions.
-     * New schema must be verified by at least one test case which exercises such schema.
+   * Allow to create microversion response schema in Tempest at the same time that projects are
+     implementing their API microversions. This will make implementation easier for adding
+     required tests before a new microversion change can be merged in the corresponding project
+     and hence accelerate the development of microversions.
+   * New schema must be verified by at least one test case which exercises such schema.
 
-     For example:
-        If any projects implemented 4 API microversion say- v2.3, v2.4, v2.5, v2.6
-        Assume microversion v2.3, v2.4, v2.6 change the API Response which means Tempest
-        needs to add JSON schema for v2.3, v2.4, v2.6.
-        In that case if only 1 or 2 tests can verify all new schemas then we do not need
-        separate tests for each new schemas. In worst case, we have to add 3 separate tests.
-  #. Test covers service behavior at large scale with involvement of more deep layer like hypervisor
-     etc not just API/DB layer. This type of tests will be added case by case basis and
-     with project team consultation about why it cannot be covered on project side and worth to test
-     in Tempest.
+   For example:
+      If any projects implemented 4 API microversion say- v2.3, v2.4, v2.5, v2.6
+      Assume microversion v2.3, v2.4, v2.6 change the API Response which means Tempest
+      needs to add JSON schema for v2.3, v2.4, v2.6.
+      In that case if only 1 or 2 tests can verify all new schemas then we do not need
+      separate tests for each new schemas. In worst case, we have to add 3 separate tests.
+#. Test covers service behavior at large scale with involvement of more deep layer like hypervisor
+   etc not just API/DB layer. This type of tests will be added case by case basis and
+   with project team consultation about why it cannot be covered on project side and worth to test
+   in Tempest.
 
 Project Scope For Microversion Testing
 """"""""""""""""""""""""""""""""""""""
@@ -294,72 +294,156 @@
 
 * Compute
 
- * `2.1`_
+  * `2.1`_
 
- .. _2.1:  https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id1
+  .. _2.1:  https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id1
 
- * `2.2`_
+  * `2.2`_
 
- .. _2.2: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2
+  .. _2.2: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2
 
- * `2.10`_
+  * `2.6`_
 
- .. _2.10: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9
+  .. _2.6: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id5
 
- * `2.20`_
+  * `2.8`_
 
- .. _2.20: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18
+  .. _2.8: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id7
 
- * `2.25`_
+  * `2.9`_
 
- .. _2.25: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka
+  .. _2.9: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id8
 
- * `2.32`_
+  * `2.10`_
 
- .. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
+  .. _2.10: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9
 
- * `2.37`_
+  * `2.19`_
 
- .. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34
+  .. _2.19: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id17
 
- * `2.42`_
+  * `2.20`_
 
- .. _2.42: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-ocata
+  .. _2.20: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18
 
- * `2.47`_
+  * `2.21`_
 
- .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id42
+  .. _2.21: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id19
 
- * `2.48`_
+  * `2.25`_
 
- .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
+  .. _2.25: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka
+
+  * `2.26`_
+
+  .. _2.26: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id23
+
+  * `2.28`_
+
+  .. _2.28: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id25
+
+  * `2.32`_
+
+  .. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
+
+  * `2.36`_
+
+  .. _2.36: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion
+
+  * `2.37`_
+
+  .. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34
+
+  * `2.39`_
+
+  .. _2.39: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id35
+
+  * `2.41`_
+
+  .. _2.41: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id37
+
+  * `2.42`_
+
+  .. _2.42: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-ocata
+
+  * `2.47`_
+
+  .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id42
+
+  * `2.48`_
+
+  .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
+
+  * `2.49`_
+
+  .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
+
+  * `2.53`_
+
+  .. _2.53: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-pike
+
+  * `2.54`_
+
+  .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49
+
+  * `2.55`_
+
+  .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id50
+
+  * `2.57`_
+
+  .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id52
+
+  * `2.60`_
+
+  .. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-queens
+
+  * `2.61`_
+
+  .. _2.61: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id55
+
+  * `2.63`_
+
+  .. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id57
+
+  * `2.70`_
+
+  .. _2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id63
+
+  * `2.71`_
+
+  .. _2.71: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id64
 
 * Volume
 
- * `3.3`_
+  * `3.3`_
 
- .. _3.3:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id3
+  .. _3.3:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id3
 
- * `3.9`_
+  * `3.9`_
 
- .. _3.9:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id9
+  .. _3.9:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id9
 
- * `3.11`_
+  * `3.11`_
 
- .. _3.11:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id11
+  .. _3.11:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id11
 
- * `3.12`_
+  * `3.12`_
 
- .. _3.12:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id12
+  .. _3.12:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id12
 
- * `3.14`_
+  * `3.13`_
 
- .. _3.14:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id14
+  .. _3.13:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id13
 
- * `3.19`_
+  * `3.14`_
 
- .. _3.19:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id18
+  .. _3.14:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id14
 
- * `3.20`_
+  * `3.19`_
 
- .. _3.20:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id19
+  .. _3.19:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id18
+
+  * `3.20`_
+
+  .. _3.20:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id19
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 2afb1e5..a9e2059 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -5,24 +5,24 @@
 =============================
 
 Tempest has an external test plugin interface which enables anyone to integrate
-an external test suite as part of a tempest run. This will let any project
-leverage being run with the rest of the tempest suite while not requiring the
-tests live in the tempest tree.
+an external test suite as part of a Tempest run. This will let any project
+leverage being run with the rest of the Tempest suite while not requiring the
+tests live in the Tempest tree.
 
 Creating a plugin
 =================
 
 Creating a plugin is fairly straightforward and doesn't require much additional
 effort on top of creating a test suite using tempest.lib. One thing to note with
-doing this is that the interfaces exposed by tempest are not considered stable
-(with the exception of configuration variables which ever effort goes into
-ensuring backwards compatibility). You should not need to import anything from
-tempest itself except where explicitly noted.
+doing this is that the interfaces exposed by Tempest are not considered stable
+(with the exception of configuration variables whichever effort goes into
+ensuring backward compatibility). You should not need to import anything from
+Tempest itself except where explicitly noted.
 
 Stable Tempest APIs plugins may use
 -----------------------------------
 
-As noted above, several tempest APIs are acceptable to use from plugins, while
+As noted above, several Tempest APIs are acceptable to use from plugins, while
 others are not. A list of stable APIs available to plugins is provided below:
 
 * tempest.lib.*
@@ -32,7 +32,7 @@
 * tempest.clients
 * tempest.test
 
-If there is an interface from tempest that you need to rely on in your plugin
+If there is an interface from Tempest that you need to rely on in your plugin
 which is not listed above, it likely needs to be migrated to tempest.lib. In
 that situation, file a bug, push a migration patch, etc. to expedite providing
 the interface in a reliable manner.
@@ -43,7 +43,7 @@
 In order to create the basic structure with base classes and test directories
 you can use the tempest-plugin-cookiecutter project::
 
-  > pip install -U cookiecutter && cookiecutter https://git.openstack.org/openstack/tempest-plugin-cookiecutter
+  > pip install -U cookiecutter && cookiecutter https://opendev.org/openstack/tempest-plugin-cookiecutter
 
   Cloning into 'tempest-plugin-cookiecutter'...
   remote: Counting objects: 17, done.
@@ -62,7 +62,7 @@
 -----------
 
 Once you've created your plugin class you need to add an entry point to your
-project to enable tempest to find the plugin. The entry point must be added
+project to enable Tempest to find the plugin. The entry point must be added
 to the "tempest.test_plugins" namespace.
 
 If you are using pbr this is fairly straightforward, in the setup.cfg just add
@@ -77,9 +77,9 @@
 Standalone Plugin vs In-repo Plugin
 -----------------------------------
 
-Since all that's required for a plugin to be detected by tempest is a valid
+Since all that's required for a plugin to be detected by Tempest is a valid
 setuptools entry point in the proper namespace there is no difference from the
-tempest perspective on either creating a separate python package to
+Tempest perspective on either creating a separate python package to
 house the plugin or adding the code to an existing python project. However,
 there are tradeoffs to consider when deciding which approach to take when
 creating a new plugin.
@@ -91,12 +91,12 @@
 single version of the test code across project release boundaries (see the
 `Branchless Tempest Spec`_ for more details on this). It also greatly
 simplifies the install time story for external users. Instead of having to
-install the right version of a project in the same python namespace as tempest
+install the right version of a project in the same python namespace as Tempest
 they simply need to pip install the plugin in that namespace. It also means
-that users don't have to worry about inadvertently installing a tempest plugin
+that users don't have to worry about inadvertently installing a Tempest plugin
 when they install another package.
 
-.. _Branchless Tempest Spec: http://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
+.. _Branchless Tempest Spec: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
 
 The sole advantage to integrating a plugin into an existing python project is
 that it enables you to land code changes at the same time you land test changes
@@ -108,9 +108,9 @@
 Plugin Class
 ============
 
-To provide tempest with all the required information it needs to be able to run
-your plugin you need to create a plugin class which tempest will load and call
-to get information when it needs. To simplify creating this tempest provides an
+To provide Tempest with all the required information it needs to be able to run
+your plugin you need to create a plugin class which Tempest will load and call
+to get information when it needs. To simplify creating this Tempest provides an
 abstract class that should be used as the parent for your plugin. To use this
 you would do something like the following:
 
@@ -132,7 +132,7 @@
 
 Plugin Structure
 ================
-While there are no hard and fast rules for the structure a plugin, there are
+While there are no hard and fast rules for the structure of a plugin, there are
 basically no constraints on what the plugin looks like as long as the 2 steps
 above are done. However,  there are some recommended patterns to follow to make
 it easy for people to contribute and work with your plugin. For example, if you
@@ -147,7 +147,7 @@
       services/
         client.py
 
-That will mirror what people expect from tempest. The file
+That will mirror what people expect from Tempest. The file
 
 * **config.py**: contains any plugin specific configuration variables
 * **plugin.py**: contains the plugin class used for the entry point
@@ -156,14 +156,14 @@
 * **services**: where the plugin specific service clients are
 
 Additionally, when you're creating the plugin you likely want to follow all
-of the tempest developer and reviewer documentation to ensure that the tests
-being added in the plugin act and behave like the rest of tempest.
+of the Tempest developer and reviewer documentation to ensure that the tests
+being added in the plugin act and behave like the rest of Tempest.
 
 Dealing with configuration options
 ----------------------------------
 
-Historically Tempest didn't provide external guarantees on its configuration
-options. However, with the introduction of the plugin interface this is no
+Historically, Tempest didn't provide external guarantees on its configuration
+options. However, with the introduction of the plugin interface, this is no
 longer the case. An external plugin can rely on using any configuration option
 coming from Tempest, there will be at least a full deprecation cycle for any
 option before it's removed. However, just the options provided by Tempest
@@ -171,7 +171,7 @@
 configuration options you should use the ``register_opts`` and
 ``get_opt_lists`` methods to pass them to Tempest when the plugin is loaded.
 When adding configuration options the ``register_opts`` method gets passed the
-CONF object from tempest. This enables the plugin to add options to both
+CONF object from Tempest. This enables the plugin to add options to both
 existing sections and also create new configuration sections for new options.
 
 Service Clients
@@ -325,23 +325,23 @@
 
 Tempest will automatically discover any installed plugins when it is run. So by
 just installing the python packages which contain your plugin you'll be using
-them with tempest, nothing else is really required.
+them with Tempest, nothing else is really required.
 
 However, you should take care when installing plugins. By their very nature
-there are no guarantees when running tempest with plugins enabled about the
+there are no guarantees when running Tempest with plugins enabled about the
 quality of the plugin. Additionally, while there is no limitation on running
-with multiple plugins it's worth noting that poorly written plugins might not
+with multiple plugins, it's worth noting that poorly written plugins might not
 properly isolate their tests which could cause unexpected cross interactions
 between plugins.
 
 Notes for using plugins with virtualenvs
 ----------------------------------------
 
-When using a tempest inside a virtualenv (like when running under tox) you have
+When using a Tempest inside a virtualenv (like when running under tox) you have
 to ensure that the package that contains your plugin is either installed in the
 venv too or that you have system site-packages enabled. The virtualenv will
-isolate the tempest install from the rest of your system so just installing the
-plugin package on your system and then running tempest inside a venv will not
+isolate the Tempest install from the rest of your system so just installing the
+plugin package on your system and then running Tempest inside a venv will not
 work.
 
 Tempest also exposes a tox job, all-plugin, which will setup a tox virtualenv
diff --git a/doc/source/stable_branch_support_policy.rst b/doc/source/stable_branch_support_policy.rst
new file mode 100644
index 0000000..87e3ad1
--- /dev/null
+++ b/doc/source/stable_branch_support_policy.rst
@@ -0,0 +1,30 @@
+Stable Branch Support Policy
+============================
+
+Since the `Extended Maintenance policy`_ for stable branches was adopted
+OpenStack projects will keep stable branches around after a "stable" or
+"maintained" period for a phase of indeterminate length called "Extended
+Maintenance". Prior to this resolution Tempest supported all stable branches
+which were supported upstream. This policy does not scale under the new model
+as Tempest would be responsible for gating proposed changes against an ever
+increasing number of branches. Therefore due to resource constraints, Tempest
+will only provide support for branches in the "Maintained" phase from the
+documented `Support Phases`_. When a branch moves from the *Maintained* to the
+*Extended Maintenance* phase, Tempest will tag the removal of support for that
+branch as it has in the past when a branch goes end of life.
+
+The expectation for *Extended Maintenance* phase branches is that they will continue
+running Tempest during that phase of support. Since the REST APIs are stable
+interfaces across release boundaries, branches in these phases should run
+Tempest from master as long as possible. But, because we won't be actively
+testing branches in these phases, it's possible that we'll introduce changes to
+Tempest on master which will break support on *Extended Maintenance* phase
+branches. When this happens the expectation for those branches is to either
+switch to running Tempest from a tag with support for the branch, or blacklist
+a newly introduced test (if that is the cause of the issue). Tempest will not
+be creating stable branches to support *Extended Maintenance* phase branches, as
+the burden is on the *Extended Maintenance* phase branche maintainers, not the Tempest
+project, to support that branch.
+
+.. _Extended Maintenance policy: https://governance.openstack.org/tc/resolutions/20180301-stable-branch-eol.html
+.. _Support Phases: https://docs.openstack.org/project-team-guide/stable-branches.html#maintenance-phases
diff --git a/doc/source/test_removal.rst b/doc/source/test_removal.rst
index 07c3046..ff4fa09 100644
--- a/doc/source/test_removal.rst
+++ b/doc/source/test_removal.rst
@@ -1,21 +1,23 @@
+.. _test-removal:
+
 Tempest Test Removal Procedure
 ==============================
 
-Historically tempest was the only way of doing functional testing and
-integration testing in OpenStack. This was mostly only an artifact of tempest
+Historically, Tempest was the only way of doing functional testing and
+integration testing in OpenStack. This was mostly only an artifact of Tempest
 being the only proven pattern for doing this, not an artifact of a design
-decision. However, moving forward as functional testing is being spun up in
-each individual project we really only want tempest to be the integration test
-suite it was intended to be; testing the high level interactions between
-projects through REST API requests. In this model there are probably existing
-tests that aren't the best fit living in tempest. However, since tempest is
+decision. However, moving forward, as functional testing is being spun up in
+each individual project, we really only want Tempest to be the integration test
+suite it was intended to be: testing the high-level interactions between
+projects through REST API requests. In this model, there are probably existing
+tests that aren't the best fit living in Tempest. However, since Tempest is
 largely still the only gating test suite in this space we can't carelessly rip
 out everything from the tree. This document outlines the procedure which was
 developed to ensure we minimize the risk for removing something of value from
-the tempest tree.
+the Tempest tree.
 
-This procedure might seem overly conservative and slow paced, but this is by
-design to try and ensure we don't remove something that is actually providing
+This procedure might seem overly conservative and slow-paced, but this is by
+design to try to ensure we don't remove something that is actually providing
 value. Having potential duplication between testing is not a big deal
 especially compared to the alternative of removing something which is actually
 providing value and is actively catching bugs, or blocking incorrect patches
@@ -27,24 +29,24 @@
 3 prong rule for removal
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-In the proposal etherpad we'll be looking for answers to 3 questions
+In the proposal etherpad we'll be looking for answers to 3 questions:
 
- #. The tests proposed for removal must have equiv. coverage in a different
-    project's test suite (whether this is another gating test project, or an in
-    tree functional test suite). For API tests preferably the other project will
-    have a similar source of friction in place to prevent breaking api changes
-    so that we don't regress and let breaking api changes slip through the
-    gate.
- #. The test proposed for removal has a failure rate <  0.50% in the gate over
-    the past release (the value and interval will likely be adjusted in the
-    future)
+#. The tests proposed for removal must have equiv. coverage in a different
+   project's test suite (whether this is another gating test project, or an in
+   tree functional test suite). For API tests preferably the other project will
+   have a similar source of friction in place to prevent breaking API changes
+   so that we don't regress and let breaking API changes slip through the
+   gate.
+#. The test proposed for removal has a failure rate <  0.50% in the gate over
+   the past release (the value and interval will likely be adjusted in the
+   future)
 
-    .. _`prong #3`:
- #. There must not be an external user/consumer of tempest
-    that depends on the test proposed for removal
+   .. _`prong #3`:
+#. There must not be an external user/consumer of Tempest
+   that depends on the test proposed for removal
 
 The answers to 1 and 2 are easy to verify. For 1 just provide a link to the new
-test location. If you are linking to the tempest removal patch please also put
+test location. If you are linking to the Tempest removal patch please also put
 a Depends-On in the commit message for the commit which moved the test into
 another repo.
 
@@ -62,64 +64,73 @@
 The Old Way using subunit2sql directly
 """"""""""""""""""""""""""""""""""""""
 
-SELECT * from tests where test_id like "%test_id%";
-(where $test_id is the full test_id, but truncated to the class because of
-setUpClass or tearDownClass failures)
+``SELECT * from tests where test_id like "%test_id%";``
+(where ``$test_id`` is the full test_id, but truncated to the class because of
+``setUpClass`` or ``tearDownClass`` failures)
 
 You can access the infra mysql subunit2sql db w/ read-only permissions with:
 
- * hostname: logstash.openstack.org
- * username: query
- * password: query
- * db_name: subunit2sql
+* hostname: logstash.openstack.org
+* username: query
+* password: query
+* db_name: subunit2sql
 
 For example if you were trying to remove the test with the id:
-tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON.test_get_flavor_details_for_deleted_flavor
+``tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON.test_get_flavor_details_for_deleted_flavor``
 you would run the following:
 
- #. run: "mysql -u query -p -h logstash.openstack.org subunit2sql" to connect
-    to the subunit2sql db
- #. run the query: MySQL [subunit2sql]> select * from tests where test_id like
-    "tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON%";
-    which will return a table of all the tests in the class (but it will also
-    catch failures in setUpClass and tearDownClass)
- #. paste the output table with numbers and the mysql command you ran to
-    generate it into the etherpad.
+#. run the command: ``mysql -u query -p -h logstash.openstack.org subunit2sql``
+   to connect to the subunit2sql db
+#. run the query:
 
-Eventually a cli interface will be created to make that a bit more friendly.
+   .. code-block:: console
+
+       MySQL [subunit2sql]> select * from tests where test_id like \
+       "tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON%";
+
+   which will return a table of all the tests in the class (but it will also
+   catch failures in ``setUpClass`` and ``tearDownClass``)
+#. paste the output table with numbers and the mysql command you ran to
+   generate it into the etherpad.
+
+Eventually, a CLI interface will be created to make that a bit more friendly.
 Also a dashboard is in the works so we don't need to manually run the command.
 
 The intent of the 2nd prong is to verify that moving the test into a project
-specific testing is preventing bugs (assuming the tempest tests were catching
-issues) from bubbling up a layer into tempest jobs. If we're seeing failure
+specific testing is preventing bugs (assuming the Tempest tests were catching
+issues) from bubbling up a layer into Tempest jobs. If we're seeing failure
 rates above a certain threshold in the gate checks that means the functional
 testing isn't really being effective in catching that bug (and therefore
-blocking it from landing) and having the testing run in tempest still has
+blocking it from landing) and having the testing run in Tempest still has
 value.
 
-However for the 3rd prong verification is a bit more subjective. The original
+However, for the 3rd prong verification is a bit more subjective. The original
 intent of this prong was mostly for refstack/defcore and also for things that
 running on the stable branches. We don't want to remove any tests if that
-would break our api consistency checking between releases, or something that
-defcore/refstack is depending on being in tempest. It's worth pointing out
-that if a test is used in defcore as part of interop testing then it will
-probably have continuing value being in tempest as part of the
+would break our API consistency checking between releases, or something that
+defcore/refstack is depending on being in Tempest. It's worth pointing out
+that if a test is used in `defcore`_ as part of `interop`_ testing then it will
+probably have continuing value being in Tempest as part of the
 integration/integrated tests in general. This is one area where some overlap
-is expected between testing in projects and tempest, which is not a bad thing.
+is expected between testing in projects and Tempest, which is not a bad thing.
+
+.. _defcore: https://wiki.openstack.org/wiki/Governance/InteropWG
+.. _interop: https://www.openstack.org/brand/interop
 
 Discussing the 3rd prong
 """"""""""""""""""""""""
 
 There are 2 approaches to addressing the 3rd prong. Either it can be raised
-during a qa meeting during the tempest discussion. Please put it on the agenda
+during a QA meeting during the Tempest discussion. Please put it on the agenda
 well ahead of the scheduled meeting. Since the meeting time will be well known
 ahead of time anyone who depends on the tests will have ample time beforehand
 to outline any concerns on the before the meeting. To give ample time for
 people to respond to removal proposals please add things to the agenda by the
 Monday before the meeting.
 
-The other option is to raise the removal on the openstack-dev mailing list.
-(for example see: http://lists.openstack.org/pipermail/openstack-dev/2016-February/086218.html )
+The other option is to raise the removal on the openstack-discuss mailing list.
+(for example see: http://lists.openstack.org/pipermail/openstack-dev/2016-February/086218.html
+or http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003574.html )
 This will raise the issue to the wider community and attract at least the same
 (most likely more) attention than discussing it during the irc meeting. The
 only downside is that it might take more time to get a response, given the
@@ -128,61 +139,61 @@
 Exceptions to this procedure
 ----------------------------
 
-For the most part all tempest test removals have to go through this procedure
+For the most part, all Tempest test removals have to go through this procedure
 there are a couple of exceptions though:
 
- #. The class of testing has been decided to be outside the scope of tempest.
- #. A revert for a patch which added a broken test, or testing which didn't
-    actually run in the gate (basically any revert for something which
-    shouldn't have been added)
- #. Tests that would become out of scope as a consequence of an API change,
-    as described in `API Compatibility`_.
-    Such tests cannot live in Tempest because of the branchless nature of
-    Tempest. Such test must still honor `prong #3`_.
+#. The class of testing has been decided to be outside the scope of Tempest.
+#. A revert for a patch which added a broken test, or testing which didn't
+   actually run in the gate (basically any revert for something which
+   shouldn't have been added)
+#. Tests that would become out of scope as a consequence of an API change,
+   as described in `API Compatibility`_.
+   Such tests cannot live in Tempest because of the branchless nature of
+   Tempest. Such tests must still honor `prong #3`_.
 
-For the first exception type the only types of testing in tree which have been
+For the first exception type, the only types of testing in the tree which have been
 declared out of scope at this point are:
 
- * The CLI tests (which should be completely removed at this point)
- * Neutron Adv. Services testing (which should be completely removed at this
-   point)
- * XML API Tests (which should be completely removed at this point)
- * EC2 API/boto tests (which should be completely removed at this point)
+* The CLI tests (which should be completely removed at this point)
+* Neutron Adv. Services testing (which should be completely removed at this
+  point)
+* XML API Tests (which should be completely removed at this point)
+* EC2 API/boto tests (which should be completely removed at this point)
 
-For tests that fit into this category the only criteria for removal is that
+For tests that fit into this category, the only criteria for removal is that
 there is equivalent testing elsewhere.
 
 Tempest Scope
 ^^^^^^^^^^^^^
 
-Starting in the liberty cycle tempest has defined a set of projects which
-are defined as in scope for direct testing in tempest. As of today that list
+Starting in the liberty cycle Tempest, has defined a set of projects which
+are defined as in scope for direct testing in Tempest. As of today that list
 is:
 
- * Keystone
- * Nova
- * Glance
- * Cinder
- * Neutron
- * Swift
+* Keystone
+* Nova
+* Glance
+* Cinder
+* Neutron
+* Swift
 
-anything that lives in tempest which doesn't test one of these projects can be
+Anything that lives in Tempest which doesn't test one of these projects can be
 removed assuming there is equivalent testing elsewhere. Preferably using the
 `tempest plugin mechanism`_
-to maintain continuity after migrating the tests out of tempest.
+to maintain continuity after migrating the tests out of Tempest.
 
 .. _tempest plugin mechanism: https://docs.openstack.org/tempest/latest/plugin.html
 
 API Compatibility
 """""""""""""""""
 
-If an API introduces a non-discoverable, backward incompatible change, and
-such change is not backported to all versions supported by Tempest, tests for
+If an API introduces a non-discoverable, backward-incompatible change, and
+such a change is not backported to all versions supported by Tempest, tests for
 that API cannot live in Tempest anymore.
 This is because tests would not be able to know or control which API response
 to expect, and thus would not be able to enforce a specific behavior.
 
-If a test exists in Tempest that would meet this criteria as consequence of a
-change, the test must be removed according to the procedure discussed into
+If a test exists in Tempest that would meet these criteria as a consequence of a
+change, the test must be removed according to the procedure discussed in
 this document. The API change should not be merged until all conditions
 required for test removal can be met.
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
index 5a2876e..0a29b7b 100644
--- a/doc/source/write_tests.rst
+++ b/doc/source/write_tests.rst
@@ -4,7 +4,7 @@
 ##########################
 
 This guide serves as a starting point for developers working on writing new
-Tempest tests. At a high level tests in Tempest are just tests that conform to
+Tempest tests. At a high level, tests in Tempest are just tests that conform to
 the standard python `unit test`_ framework. But there are several aspects of
 that are unique to Tempest and its role as an integration test suite running
 against a real cloud.
@@ -36,12 +36,12 @@
 In standard unittest the lifecycle of a TestCase can be described in the
 following phases:
 
- #. setUpClass
- #. setUp
- #. Test Execution
- #. tearDown
- #. doCleanups
- #. tearDownClass
+#. setUpClass
+#. setUp
+#. Test Execution
+#. tearDown
+#. doCleanups
+#. tearDownClass
 
 setUpClass
 ----------
@@ -54,18 +54,18 @@
 To accomplish this you do **not** define a setUpClass function, instead there
 are a number of predefined phases to setUpClass that are used. The phases are:
 
- * skip_checks
- * setup_credentials
- * setup_clients
- * resource_setup
+* skip_checks
+* setup_credentials
+* setup_clients
+* resource_setup
 
 which is executed in that order. Cleanup of resources provisioned during
 the resource_setup must be scheduled right after provisioning using
-the addClassResourceCleanp helper. The resource cleanups stacked this way
+the addClassResourceCleanup helper. The resource cleanups stacked this way
 are executed in reverse order during tearDownClass, before the cleanup of
 test credentials takes place. An example of a TestCase which defines all
 of these would be::
-  
+
   from tempest.common import waiters
   from tempest import config
   from tempest.lib.common.utils import test_utils
diff --git a/playbooks/devstack-tempest.yaml b/playbooks/devstack-tempest.yaml
index a684984..5f87abd 100644
--- a/playbooks/devstack-tempest.yaml
+++ b/playbooks/devstack-tempest.yaml
@@ -3,10 +3,15 @@
 # avoid zuul retrying on legitimate failures.
 - hosts: all
   roles:
-    - run-devstack
+    - orchestrate-devstack
 
 # We run tests only on one node, regardless how many nodes are in the system
 - hosts: tempest
+  environment:
+    # This enviroment variable is used by the optional tempest-gabbi
+    # job provided by the gabbi-tempest plugin. It can be safely ignored
+    # if that plugin is not being used.
+    GABBI_TEMPEST_PATH: "{{ gabbi_tempest_path | default('') }}"
   roles:
     - setup-tempest-run-dir
     - setup-tempest-data-dir
diff --git a/playbooks/post-tempest.yaml b/playbooks/post-tempest.yaml
new file mode 100644
index 0000000..6e0bcad
--- /dev/null
+++ b/playbooks/post-tempest.yaml
@@ -0,0 +1,6 @@
+- hosts: tempest
+  become: true
+  roles:
+    - role: fetch-subunit-output
+      zuul_work_dir: '{{ devstack_base_dir }}/tempest'
+    - role: process-stackviz
diff --git a/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml b/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml
index 3e43f9a..985acb0 100644
--- a/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml
+++ b/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml
@@ -3,4 +3,4 @@
   - A new helper method `service_client_config` has been added
     to the stable module config.py that returns extracts from
     configuration into a dictionary the configuration settings
-    relevant for the initisialisation of a service client.
+    relevant for the initialization of a service client.
diff --git a/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml b/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
index 0075a36..9ae46fd 100644
--- a/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
+++ b/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
@@ -2,8 +2,8 @@
 upgrade:
   - The ``JSON_ENC`` and ``TXT_ENC`` option in the ``_error_checker``
     section have been added with additional content-type which are
-    defined in RFC7231 but missing in the currnt rest_client.py file.
+    defined in RFC7231 but missing in the current rest_client.py file.
     The lack of these additional content-type will cause defcore test
     to fail for OpenStack public cloud which uses tomcat module in the
     api gateway. The additions are ``application/json;charset=utf-8``,
-    ``text/html;charset=utf-8``,``text/plain;charset=utf-8``
\ No newline at end of file
+    ``text/html;charset=utf-8``, ``text/plain;charset=utf-8``
diff --git a/releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml b/releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml
index 73900ca..e9c3694 100644
--- a/releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml
+++ b/releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml
@@ -1,6 +1,6 @@
 ---
 features:
   - |
-    Adds a new cli option to tempest run, --combine, which is used to indicate
-    you want the subunit stream output combined with the previous run's in
-    the testr repository
+    Adds a new cli option to tempest run, ``--combine``, which is used
+    to indicate you want the subunit stream output combined with the
+    previous run's in the testr repository
diff --git a/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml b/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
index 9927971..18ad5b9 100644
--- a/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
+++ b/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
@@ -2,7 +2,7 @@
 features:
   - |
     Add server tags APIs to the servers_client library.
-    This feature enables the possibility of upating, deleting
+    This feature enables the possibility of updating, deleting
     and checking existence of a tag on a server, as well
     as updating and deleting all tags on a server.
 
diff --git a/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml b/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
index f679208..0b45d0d 100644
--- a/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
+++ b/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
@@ -6,5 +6,5 @@
 deprecations:
   - The resources_prefix is marked as deprecated because it is
     enough to set 'tempest' as the prefix on rand_name() to
-    ideintify resources which are created by Tempest and no
+    identify resources which are created by Tempest and no
     projects set this option on OpenStack dev community.
diff --git a/releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml b/releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
index 9d7102f..5b4a96d 100644
--- a/releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
+++ b/releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
@@ -1,5 +1,5 @@
 ---
 upgrade:
   - |
-    The deprecated config option 'allow_port_security_disabled' from compute_feature_enabled
-    group has been removed.
+    The deprecated config option ``allow_port_security_disabled`` from
+    ``compute_feature_enabled`` group has been removed.
diff --git a/releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml b/releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
index b4e4dd1..c8b0ca8 100644
--- a/releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
+++ b/releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
@@ -8,4 +8,4 @@
     - ``compute.ssh_user`` (available as ``validation.image_ssh_user``)
     - ``scenario.ssh_user`` (available as ``validation.image_ssh_user``)
     - ``compute.network_for_ssh`` (available as ``validation.network_for_ssh``)
-    - ``compute.ping_timeout `` (available as ``validation.ping_timeout``)
+    - ``compute.ping_timeout`` (available as ``validation.ping_timeout``)
diff --git a/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml b/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml
new file mode 100644
index 0000000..21b74a6
--- /dev/null
+++ b/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml
@@ -0,0 +1,17 @@
+---
+features:
+  - |
+    Add basic read-only Placement client to Tempest to make possible the
+    testing of the placement based bandwidth allocation feature.
+    The following API calls are available for tempest from now:
+
+    * GET /allocation_candidates
+    * GET /allocations/{consumer_uuid}
+
+    Add new config group ``placement``, with the config options:
+
+    * ``endpoint_type`` to use for communication with placement service.
+    * ``catalog_type`` of the placement service.
+    * ``region`` as the placement region name to use.
+    * ``min_microversion`` and ``max_microversion`` as the range between
+      placement API requests are sent.
diff --git a/releasenotes/notes/add-additional-methods-to-policy-client-library-b8279c18335588c9.yaml b/releasenotes/notes/add-additional-methods-to-policy-client-library-b8279c18335588c9.yaml
new file mode 100644
index 0000000..cd5284d
--- /dev/null
+++ b/releasenotes/notes/add-additional-methods-to-policy-client-library-b8279c18335588c9.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add ``v3-ext/OS-ENDPOINT-POLICY`` API calls to support creation, deletion and
+    retrieval of associations between service endpoints and policies. Such associations
+    enable an endpoint to request its policy.
diff --git a/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml b/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml
new file mode 100644
index 0000000..03d0ae8
--- /dev/null
+++ b/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add ``enable_service``, ``disable_service`` , ``disable_log_reason``,
+    ``freeze_host`` and ``thaw_host`` API endpoints to volume v3
+    ``services_client``.
diff --git a/releasenotes/notes/add-group-type-specs-apis-to-v3-group-types-client-10390b52dedede54.yaml b/releasenotes/notes/add-group-type-specs-apis-to-v3-group-types-client-10390b52dedede54.yaml
new file mode 100644
index 0000000..404319d
--- /dev/null
+++ b/releasenotes/notes/add-group-type-specs-apis-to-v3-group-types-client-10390b52dedede54.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Add group type specs APIs to v3 group_types_client library.
+
+    * create_or_update_group_type_specs
+    * list_group_type_specs
+    * show_group_type_specs_item
+    * update_group_type_specs_item
+    * delete_group_type_specs_item
diff --git a/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml b/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml
new file mode 100644
index 0000000..931d689
--- /dev/null
+++ b/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    Add a new config setting ``immutable_user_source`` in the
+    ``[identity-feature-enabled]`` group that defaults to false.
+    This setting, combined with the usage of the ``@testtools.skipIf()``
+    decorator, will allow tests that require user creation, deletion,
+    or modification to skip instead of failing in environments that
+    are LDAP-backed. In such environments, the user source is read-only,
+    so this feature flag is needed to allow such tests to gracefully skip
+    without having to blacklist them.
diff --git a/releasenotes/notes/add-load-list-cmd-35a4a2e6ea0a36fd.yaml b/releasenotes/notes/add-load-list-cmd-35a4a2e6ea0a36fd.yaml
index 403bbad..145e7dd 100644
--- a/releasenotes/notes/add-load-list-cmd-35a4a2e6ea0a36fd.yaml
+++ b/releasenotes/notes/add-load-list-cmd-35a4a2e6ea0a36fd.yaml
@@ -1,7 +1,7 @@
 ---
 features:
   - |
-    Adds a new cli option to tempest run, --load-list <list-file>
+    Adds a new cli option to tempest run, ``--load-list <list-file>``
     to specify target tests to run from a list-file. The list-file
-    supports the output format of the tempest run --list-tests
+    supports the output format of the tempest run ``--list-tests``
     command.
diff --git a/releasenotes/notes/add-migrate-volume-and-list-hosts-to-v3-volume-client-library-ad3529260db58f00.yaml b/releasenotes/notes/add-migrate-volume-and-list-hosts-to-v3-volume-client-library-ad3529260db58f00.yaml
new file mode 100644
index 0000000..ca6a78d
--- /dev/null
+++ b/releasenotes/notes/add-migrate-volume-and-list-hosts-to-v3-volume-client-library-ad3529260db58f00.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Add list host API support to the volume v3 client library.
+    This feature enables callers to list all hosts for a given project.
+  - |
+    Add migrate volume API support to the volume v3 client library.
+    This features allows callers to migrate volumes between backends.
diff --git a/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml b/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
new file mode 100644
index 0000000..19d47d1
--- /dev/null
+++ b/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - A new config option 'port_profile' is added to the section
+    'network' to specify capabilities of the port.
+    By default this is set to {}. When using OVS HW offload
+    feature we need to create Neutron port with a certain
+    capability. This is done by creating Neutron port with
+    binding profile. To be able to test this we need profile
+    capability support in Tempest as well.
diff --git a/releasenotes/notes/add-profiler-config-options-db7c4ae6d338ee5c.yaml b/releasenotes/notes/add-profiler-config-options-db7c4ae6d338ee5c.yaml
new file mode 100644
index 0000000..2245044
--- /dev/null
+++ b/releasenotes/notes/add-profiler-config-options-db7c4ae6d338ee5c.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Add support of `OSProfiler library`_ for profiling and distributed
+    tracing of OpenStack. A new config option ``key`` in section ``profiler``
+    is added, the option sets the secret key used to enable profiling in
+    OpenStack services. The value needs to correspond to the one specified
+    in [profiler]/hmac_keys option of OpenStack services.
+
+    .. _OSProfiler library: https://docs.openstack.org/osprofiler/
diff --git a/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml b/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml
new file mode 100644
index 0000000..f245dcb
--- /dev/null
+++ b/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml
@@ -0,0 +1,16 @@
+---
+features:
+  - |
+    A new parameter ``follow_redirects`` has been added to the class
+    ``RestClient``, which is passed through to ``ClosingHttp`` or
+    ``ClosingProxyHttp`` respectively. The default value is ``True``
+    which corresponds to the previous behaviour of following up to five
+    redirections before returning a response. Setting
+    ``follow_redirects = False`` allows to disable this behaviour, so
+    that any redirect that is received is directly returned to the caller.
+    This allows tests to verify that an API is responding with a redirect.
+fixes:
+  - |
+    [`bug 1616892 <https://bugs.launchpad.net/tempest/+bug/1616892>`_]
+    Tempest now allows tests to verify that an API responds with a
+    redirect.
diff --git a/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml b/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml
index 8fdf4f0..abd2610 100644
--- a/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml
+++ b/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml
@@ -1,4 +1,5 @@
 ---
 features:
   - |
-    Add --save-state option to allow saving state of cloud before tempest run.
+    Add ``--save-state`` option to allow saving state of cloud before
+    tempest run.
diff --git a/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml b/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml
new file mode 100644
index 0000000..6efe7e6
--- /dev/null
+++ b/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add show default quotas API to network quotas_client library.
+    This feature enables the possibility to show default network quotas for
+    a specified project.
diff --git a/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml b/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml
new file mode 100644
index 0000000..9e13afc
--- /dev/null
+++ b/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add show encryption specs item API to v2 encryption_types_client library.
+    This feature enables the possibility to show specific encryption specs for
+    a volume type.
diff --git a/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml b/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml
new file mode 100644
index 0000000..6c44ba0
--- /dev/null
+++ b/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Add extension API show quota details to network quotas_client library.
+    This feature enables the possibility to show a quota set for a specified
+    project that includes the quota's used, limit and reserved counts per
+    resource.
diff --git a/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml b/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml
new file mode 100644
index 0000000..dd4a90b
--- /dev/null
+++ b/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml
@@ -0,0 +1,17 @@
+---
+features:
+  - |
+    Add a new parameter called ``bug_type`` to
+    ``tempest.lib.decorators.related_bug`` and
+    ``tempest.lib.decorators.skip_because`` decorators, which accepts
+    2 values:
+
+    * launchpad
+    * storyboard
+
+    This offers the possibility of tracking bugs related to tests using
+    launchpad or storyboard references. The default value is launchpad
+    for backward compatibility.
+
+    Passing in a non-digit ``bug`` value to either decorator will raise
+    a ``InvalidParam`` exception (previously ``ValueError``).
diff --git a/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml b/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
index e23abe3..9c30a0c 100644
--- a/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
+++ b/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
@@ -2,4 +2,4 @@
 features:
   - Add support of args and kwargs when calling func in call_until_true,
     also to log the cost time when call_until_true returns True or False
-    for debuggin.
+    for debugging.
diff --git a/releasenotes/notes/add-unstable_test-decorator-a73cf97d4ffcc796.yaml b/releasenotes/notes/add-unstable_test-decorator-a73cf97d4ffcc796.yaml
new file mode 100644
index 0000000..2203fd1
--- /dev/null
+++ b/releasenotes/notes/add-unstable_test-decorator-a73cf97d4ffcc796.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    New decorator ``unstable_test`` is added to ``tempest.lib.decorators``.
+    It can be used to mark some test as unstable thus it will be still run
+    by tempest but job will not fail if this test will fail. Such test will
+    be skipped in case of failure.
+    It can be used for example when there is known bug related which cause
+    irregular tests failures. Marking such test as unstable will help other
+    developers to get their job done and still run this test to get additional
+    debug data or to confirm if some potential fix really solved the issue.
diff --git a/releasenotes/notes/add-update-api-to-group-types-client-09c06ccdf80d5003.yaml b/releasenotes/notes/add-update-api-to-group-types-client-09c06ccdf80d5003.yaml
new file mode 100644
index 0000000..14458d6
--- /dev/null
+++ b/releasenotes/notes/add-update-api-to-group-types-client-09c06ccdf80d5003.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add update group types API to v3 ``group_types_client`` library;
+    min_microversion of this API is 3.11.
diff --git a/releasenotes/notes/add-update-flavor--api-to-flavors-client-a859542fe54aab7c.yaml b/releasenotes/notes/add-update-flavor--api-to-flavors-client-a859542fe54aab7c.yaml
new file mode 100644
index 0000000..222a99f
--- /dev/null
+++ b/releasenotes/notes/add-update-flavor--api-to-flavors-client-a859542fe54aab7c.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add update flavor API to compute flavors_client library.
diff --git a/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml b/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml
new file mode 100644
index 0000000..21068ec
--- /dev/null
+++ b/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Adds the new method to AgentsClient that implements agent deletion
+    according to the API [0].
+    [0] https://developer.openstack.org/api-ref/network/v2/index.html#delete-agent
+
diff --git a/releasenotes/notes/bp-application-credentials-df69b1f617db1bb9.yaml b/releasenotes/notes/bp-application-credentials-df69b1f617db1bb9.yaml
new file mode 100644
index 0000000..53125ef
--- /dev/null
+++ b/releasenotes/notes/bp-application-credentials-df69b1f617db1bb9.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    [`blueprint application-credentials <https://blueprints.launchpad.net/keystone/+spec/application-credentials>`_]
+    Tempest can test keystone's application credentials interface. A new client
+    library is added for application credentials, and a new config option,
+    ``[identity-feature-enabled]/application_credentials``, can control whether
+    the application credentials feature is tested (defaults to False,
+    indicating the feature is not enabled in the cloud under test).
diff --git a/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml b/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml
new file mode 100644
index 0000000..a2e23fd
--- /dev/null
+++ b/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+  - |
+    Fixed bug #1791007. ``tempest workspace register`` and ``tempest workspace rename`` CLI will
+    error if None or empty string is passed in --name arguments. Earlier both CLI used to accept
+    the None or empty string as name which was confusing.
+
+
diff --git a/releasenotes/notes/bug-1799883-6ea95fc64f70c9ef.yaml b/releasenotes/notes/bug-1799883-6ea95fc64f70c9ef.yaml
new file mode 100644
index 0000000..630908f
--- /dev/null
+++ b/releasenotes/notes/bug-1799883-6ea95fc64f70c9ef.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    Fixed bug #1799883. ``tempest workspace register`` and ``tempest workspace move`` CLI
+    will now validate the value of the ``--path`` CLI argument and raise an exception if
+    it is None or empty string. Earlier both CLI actions were accepting None or empty path
+    which was confusing.
diff --git a/releasenotes/notes/bug-1808473-54ada26ab78e7b02.yaml b/releasenotes/notes/bug-1808473-54ada26ab78e7b02.yaml
new file mode 100644
index 0000000..c280198
--- /dev/null
+++ b/releasenotes/notes/bug-1808473-54ada26ab78e7b02.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    Fixed bug #1808473. ``tempest run`` CLI will error if a non-exist config file is
+    input to parameter --config-file. Earlier non-exist config value was silently
+    getting ignored and the default config file was used instead which used to give
+    false behavior to the user on using the passed config file.
diff --git a/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml b/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml
new file mode 100644
index 0000000..1dda4e1
--- /dev/null
+++ b/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    Cinder CLI calls have now been updated to use the ``--os-endpoint-type``
+    option instead of ``--endpoint-type``. The latter had been deprecated in
+    Cinder and has been removed in the Rocky release.
diff --git a/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml b/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml
new file mode 100644
index 0000000..2a0a86c
--- /dev/null
+++ b/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml
@@ -0,0 +1,9 @@
+---
+other:
+  - |
+    The CLIClient class, when it calls a command line client, uses
+    ``--os-project-name`` instead of ``--os-tenant-name`` for the
+    project, and passes ``--os-identity-api-version`` (default empty).
+    All CLI clients still available in supported releases of OpenStack
+    which are wrapped by the ``cmd_with_auth()`` method support those
+    switches.
diff --git a/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml b/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml
index 305e756..cc6c51b 100644
--- a/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml
+++ b/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml
@@ -3,7 +3,7 @@
   - |
     Add a new function called ``compare_version_header_to_response`` to
     ``tempest.lib.common.api_version_utils``, which compares the API
-    micoversion in the response header to another microversion using the
+    microversion in the response header to another microversion using the
     comparators defined in
     ``tempest.lib.common.api_version_request.APIVersionRequest``.
 
diff --git a/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml b/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml
new file mode 100644
index 0000000..c707f14
--- /dev/null
+++ b/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    The ``tempest.lib.decorators.attr`` decorator now supports a ``condition``
+    kwarg which can be used to conditionally apply the attr to the test
+    function if the condition evaluates to True.
diff --git a/releasenotes/notes/config-image-api-v1-default-to-false-39d5f2xafc534ab1.yaml b/releasenotes/notes/config-image-api-v1-default-to-false-39d5f2xafc534ab1.yaml
new file mode 100644
index 0000000..5efa4a9
--- /dev/null
+++ b/releasenotes/notes/config-image-api-v1-default-to-false-39d5f2xafc534ab1.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+  - |
+    Changed the default value of 'api_v1' config option in the
+    'image-feature-enabled' group to False from True, because
+    glance v1 APIs are deprecated. Please set True explicitly
+    on the option if still testing glance v1 APIs.
diff --git a/releasenotes/notes/config-volume-multiattach-ea8138dfa4fd308c.yaml b/releasenotes/notes/config-volume-multiattach-ea8138dfa4fd308c.yaml
new file mode 100644
index 0000000..8d53dda
--- /dev/null
+++ b/releasenotes/notes/config-volume-multiattach-ea8138dfa4fd308c.yaml
@@ -0,0 +1,12 @@
+---
+other:
+  - |
+    A new configuration option ``[compute-feature-enabled]/volume_multiattach``
+    has been added which defaults to False. Set this to True to enable volume
+    multiattach testing. These tests require that compute API version 2.60 is
+    available and block storage API version 3.44 is available.
+
+    .. note:: In the Queens release, the only compute driver that supports
+      volume multiattach is the libvirt driver, and only then when qemu<2.10
+      or libvirt>=3.10. The only volume backend in Queens that supports volume
+      multiattach is lvm.
diff --git a/releasenotes/notes/correct-port-profile-config-option-d67f5cb31f1bc34c.yaml b/releasenotes/notes/correct-port-profile-config-option-d67f5cb31f1bc34c.yaml
new file mode 100644
index 0000000..2830aa2
--- /dev/null
+++ b/releasenotes/notes/correct-port-profile-config-option-d67f5cb31f1bc34c.yaml
@@ -0,0 +1,17 @@
+---
+fixes:
+  - |
+    Patch https://review.opendev.org/#/c/499575/ introduced
+    support creating Neutron port with certain capabilities.
+    Currently capabilities list interpreted as string this change
+    fix it.
+
+    tempest.conf
+    [network]
+    port_profile = capabilities:[switchdev]
+
+    result:
+    {'capabilities':'[switchdev]'}
+
+    expected:
+    {'capabilities': ['switchdev']}
diff --git a/releasenotes/notes/create-mount-config-drive-to-lib-1a6e912b8afbcc7e.yaml b/releasenotes/notes/create-mount-config-drive-to-lib-1a6e912b8afbcc7e.yaml
new file mode 100644
index 0000000..f92cd78
--- /dev/null
+++ b/releasenotes/notes/create-mount-config-drive-to-lib-1a6e912b8afbcc7e.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - A function has been added to the common library to allow mounting and
+    unmounting of the config drive consistently.
diff --git a/releasenotes/notes/deprecate-dns_servers-option-0xf2f297ee47a5ff.yaml b/releasenotes/notes/deprecate-dns_servers-option-0xf2f297ee47a5ff.yaml
new file mode 100644
index 0000000..30551cb
--- /dev/null
+++ b/releasenotes/notes/deprecate-dns_servers-option-0xf2f297ee47a5ff.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+  - |
+    The config option ``CONF.network.dns_servers`` is no longer used
+    anywhere, so it is deprecated and will be removed in the future.
+
diff --git a/releasenotes/notes/deprecate-scheduler-available-filters-cbca2017ba3cf2aa.yaml b/releasenotes/notes/deprecate-scheduler-available-filters-cbca2017ba3cf2aa.yaml
new file mode 100644
index 0000000..d0c3a7d
--- /dev/null
+++ b/releasenotes/notes/deprecate-scheduler-available-filters-cbca2017ba3cf2aa.yaml
@@ -0,0 +1,13 @@
+---
+deprecations:
+  - |
+    The ``scheduler_available_filters`` option is being deprecated in favor of
+    ``scheduler_enabled_filters``. The new name is more indicative of what the
+    option means. ``scheduler_enabled_filters``'s default value is set to the
+    default value of Nova's ``enabled_filters``.
+    ``scheduler_available_filters``'s default was `all`. There was confusion
+    around this value. Sometimes it was understood to mean the default Nova
+    filters are enabled, other times it was understood to mean all filters are
+    enabled. While `all` is still allowed for ``scheduler_enabled_filters`` for
+    backwards compatibility, it is strongly recommended to provide an explicit
+    list of filters that matches what's configured in nova.conf.
diff --git a/releasenotes/notes/deprecate-volume-api-selection-config-options-b95c5c0ccbf38916.yaml b/releasenotes/notes/deprecate-volume-api-selection-config-options-b95c5c0ccbf38916.yaml
new file mode 100644
index 0000000..1bea6d0
--- /dev/null
+++ b/releasenotes/notes/deprecate-volume-api-selection-config-options-b95c5c0ccbf38916.yaml
@@ -0,0 +1,19 @@
+---
+deprecations:
+  - |
+    The v2 volume API has been deprecated since Pike release.
+    Volume v3 API is current and Tempest volume tests can
+    be run against v2 or v3 API based on config option
+    ``CONF.volume.catalog_type``. If catalog_type is ``volumev2``, then
+    all the volume tests will run against v2 API. If catalog_type is
+    ``volumev3`` which is default in Tempest, then all the volume
+    tests will run against v3 API.
+    That makes below config options unusable in Tempest which used to
+    select the target volume API for volume tests.
+
+    * ``CONF.volume-feature-enabled.api_v2``
+    * ``CONF.volume-feature-enabled.api_v3``
+
+    Tempest deprecate the above two config options in Rocky release
+    and will be removed in future. Alternatively ``CONF.volume.catalog_type``
+    can be used to run the Tempest against volume v2 or v3 API.
diff --git a/releasenotes/notes/enable-volume-multiattach-fd5e9bf0e96b56ce.yaml b/releasenotes/notes/enable-volume-multiattach-fd5e9bf0e96b56ce.yaml
new file mode 100644
index 0000000..0959b22
--- /dev/null
+++ b/releasenotes/notes/enable-volume-multiattach-fd5e9bf0e96b56ce.yaml
@@ -0,0 +1,10 @@
+---
+upgrade:
+  - |
+    The ``tempest-full``, ``tempest-full-py3`` and ``tempest-slow`` zuul v3
+    job configurations now set ``ENABLE_VOLUME_MULTIATTACH: true`` in the
+    ``devstack_localrc`` variables section. If you have a plugin job
+    configuration that inherits from one of these jobs and the backend cinder
+    volume driver or nova compute driver do not support volume multiattach then
+    you should override and set this variable to
+    ``ENABLE_VOLUME_MULTIATTACH: false`` in your job configuration.
diff --git a/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml b/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml
index 775a383..a002fb8 100644
--- a/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml
+++ b/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml
@@ -1,6 +1,6 @@
 ---
 fixes:
   - |
-    Fix list_group_snapshots API in v3 group_snapshots_client: Bug#1715786.
+    Fix list_group_snapshots API in v3 group_snapshots_client: Bug#1715786.
     The url path for list group snapshots with details API is changed from
     ``?detail=True`` to ``/detail``.
diff --git a/releasenotes/notes/fix-show-image-file-expected-code-92d97342d0f6d60e.yaml b/releasenotes/notes/fix-show-image-file-expected-code-92d97342d0f6d60e.yaml
new file mode 100644
index 0000000..a4050a5
--- /dev/null
+++ b/releasenotes/notes/fix-show-image-file-expected-code-92d97342d0f6d60e.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    Fix show_image_file interface in v2 ImagesClient: Bug#1756264.
+    The expected success code of show_image_file is changed from
+    ``200`` to ``[200, 204, 206]``.
diff --git a/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml b/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml
new file mode 100644
index 0000000..dfbcc7d
--- /dev/null
+++ b/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml
@@ -0,0 +1,12 @@
+---
+features:
+  - |
+    Add ``project_tags_client`` to the identity v3 library. This feature
+    enables the possibility of invoking the following API actions:
+
+    * update_project_tag
+    * list_project_tags
+    * update_all_project_tags
+    * check_project_tag_existence
+    * delete_project_tag
+    * delete_all_project_tags
diff --git a/releasenotes/notes/lib_api_microversion_fixture-f52308fc6b6b89f2.yaml b/releasenotes/notes/lib_api_microversion_fixture-f52308fc6b6b89f2.yaml
new file mode 100644
index 0000000..d707fc7
--- /dev/null
+++ b/releasenotes/notes/lib_api_microversion_fixture-f52308fc6b6b89f2.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    New library interface to set the API microversion on Service Clients.
+    ``APIMicroversionFixture,`` can be used to set the API microversion
+    on multiple services. This Fixture will take care of reseting the service
+    microversion to None once test is finished.
diff --git a/releasenotes/notes/network-show-version-18e1707a4df0a3d3.yaml b/releasenotes/notes/network-show-version-18e1707a4df0a3d3.yaml
new file mode 100644
index 0000000..36a9710
--- /dev/null
+++ b/releasenotes/notes/network-show-version-18e1707a4df0a3d3.yaml
@@ -0,0 +1,7 @@
+---
+features:
+- |
+    Add ``show_version`` function to the ``NetworkVersionsClient`` client. This
+    allows the possibility of getting details for Networking API.
+
+    .. API reference: https://developer.openstack.org/api-ref/network/v2/index.html#show-api-v2-details
diff --git a/releasenotes/notes/omit_X-Subject-Token_from_log-1bf5fef88c80334b.yaml b/releasenotes/notes/omit_X-Subject-Token_from_log-1bf5fef88c80334b.yaml
new file mode 100644
index 0000000..51c8f79
--- /dev/null
+++ b/releasenotes/notes/omit_X-Subject-Token_from_log-1bf5fef88c80334b.yaml
@@ -0,0 +1,7 @@
+---
+security:
+  - |
+    The x-subject-token of a response header is ommitted from log,
+    but clients specify the same token on a request header on
+    Keystone API and that was not omitted. In this release,
+    that has been omitted for a security reason.
diff --git a/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml b/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml
new file mode 100644
index 0000000..e15d387
--- /dev/null
+++ b/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml
@@ -0,0 +1,12 @@
+---
+upgrade:
+  - |
+    Below config options or feature flags were deprecated for removal.
+    It's time to remove them as all supported stable branches are
+    good to handle them.
+
+    * ``[identity-feature-enabled].forbid_global_implied_dsr``
+    * ``[image-feature-enabled].deactivate_image``
+    * ``[default].resources_prefix``
+    * config group ``orchestration``
+    * ``[service_available].heat``
diff --git a/releasenotes/notes/removal-deprecated-volume-config-options-21c4412f3c600923.yaml b/releasenotes/notes/removal-deprecated-volume-config-options-21c4412f3c600923.yaml
new file mode 100644
index 0000000..32147c7
--- /dev/null
+++ b/releasenotes/notes/removal-deprecated-volume-config-options-21c4412f3c600923.yaml
@@ -0,0 +1,24 @@
+---
+upgrade:
+  - |
+    Below config option was deprecated for removal since juno release.
+    It's time to remove it as all supported stable branches and Tempest plugins
+    are good to handle it.
+
+    * ``[volume_feature_enabled].api_v1``
+
+    Also Tempest removes the below corresponding service clients alias from
+    client.py which were being set based on above removed config option.
+
+    * self.backups_client
+    * self.encryption_types_client
+    * self.snapshots_client
+    * self.volume_availability_zone_client
+    * self.volume_hosts_client
+    * self.volume_limits_client
+    * self.volume_qos_client
+    * self.volume_quotas_client
+    * self.volume_services_client
+    * self.volume_types_client
+    * self.volumes_client
+    * self.volumes_extension_client
diff --git a/releasenotes/notes/remove-allow_tenant_isolation-option-03f0d998eb498d44.yaml b/releasenotes/notes/remove-allow_tenant_isolation-option-03f0d998eb498d44.yaml
new file mode 100644
index 0000000..4f4516b
--- /dev/null
+++ b/releasenotes/notes/remove-allow_tenant_isolation-option-03f0d998eb498d44.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    Remove deprecated config option ``allow_tenant_isolation`` from
+    ``auth`` and ``compute`` groups. Use ``use_dynamic_credentials`` directly
+    instead of the removed option.
diff --git a/releasenotes/notes/remove-deprecated-find-test-caller-f4525cd043bfd1b6.yaml b/releasenotes/notes/remove-deprecated-find-test-caller-f4525cd043bfd1b6.yaml
new file mode 100644
index 0000000..f22736f
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-find-test-caller-f4525cd043bfd1b6.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+  - |
+    ``tempest.lib.common.utils.misc.find_test_caller`` was deprecated during
+    Kilo release cycle in favor of
+    ``tempest.lib.common.utils.test_utils.find_test_caller``. The deprecated
+    version of ``find_test_caller`` is removed.
diff --git a/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml b/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml
new file mode 100644
index 0000000..621731d
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+  - |
+    Remove the deprecated decorator ``skip_unless_attr`` in lib/decorators.py.
diff --git a/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml b/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml
new file mode 100644
index 0000000..17866e5
--- /dev/null
+++ b/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml
@@ -0,0 +1,9 @@
+---
+upgrade:
+  - |
+    The tox ostestr job (normally invoked with ``tox -eostestr``) has been
+    removed. This was lightly used, and in the near future ostestr will be
+    removed from the tempest requirements file. If you were relying on this
+    functionality you can replicate it by using the venv-tempest tox job. For
+    example, simply running ``tox -evenv-tempest -- ostestr`` will do the same
+    thing the old ostestr job did.
diff --git a/releasenotes/notes/start-of-queens-support-fea9051ba1d85fc7.yaml b/releasenotes/notes/start-of-queens-support-fea9051ba1d85fc7.yaml
new file mode 100644
index 0000000..10b2300
--- /dev/null
+++ b/releasenotes/notes/start-of-queens-support-fea9051ba1d85fc7.yaml
@@ -0,0 +1,12 @@
+---
+prelude: >
+    This release marks the start of Queens release support in Tempest.
+    This release also marks the end of support for Newton in Tempest.
+other:
+    - OpenStack Releases supported after this release are **Queens**, **Pike**,
+      and **Ocata**.
+
+      The release under current development of this tag is Rocky, meaning
+      that every Tempest commit is also tested against master during the Rocky
+      cycle. However, this does not necessarily mean that using Tempest as of
+      this tag will work against a Rocky (or future release) cloud.
diff --git a/releasenotes/notes/switch-to-stestr-8c9f834b3f5a55d8.yaml b/releasenotes/notes/switch-to-stestr-8c9f834b3f5a55d8.yaml
new file mode 100644
index 0000000..9e2f1ba
--- /dev/null
+++ b/releasenotes/notes/switch-to-stestr-8c9f834b3f5a55d8.yaml
@@ -0,0 +1,13 @@
+---
+features:
+- The Tempest CLI commands have switched from calling testrepository internally
+  to use stestr instead. This means that all of the features and bug fixes from
+  moving to stestr are available to the tempest commands.
+
+upgrade:
+- Tempest CLI commands will no long rely on anything from testr. This means any
+  data in existing testr internals that were being exposed are no longer
+  present. For example things like the .testr directories will be silently
+  ignored. There is a potential incompatibility for existing users who are
+  relying on test results being stored by testr. Anything relying on previous
+  testr behavior will need to be updated to handle stestr.
diff --git a/releasenotes/notes/tempest-default-run_validations-9640c41b6a4a9121.yaml b/releasenotes/notes/tempest-default-run_validations-9640c41b6a4a9121.yaml
new file mode 100644
index 0000000..8ff0b5c
--- /dev/null
+++ b/releasenotes/notes/tempest-default-run_validations-9640c41b6a4a9121.yaml
@@ -0,0 +1,10 @@
+---
+upgrade:
+  - |
+    ``CONF.validation.run_validation`` default enabled.
+    This option required to be set ``true`` in order to run api tests
+    stability when the guest cooperation required. For example when
+    the guest needs react on Volume/Interface detach.
+    The ssh test makes sure the VM is alive and ready
+    when the detach needs to happen.
+    The option was enabled on the gate for a long time.
diff --git a/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml
new file mode 100644
index 0000000..d67cdb8
--- /dev/null
+++ b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    The ``update_service`` API is added to the ``services_client`` compute
+    library. This API is introduced in microversion 2.53 and supersedes
+    the following APIs:
+
+    * ``PUT /os-services/disable`` (``disable_service``)
+    * ``PUT /os-services/disable-log-reason`` (``disable_log_reason``)
+    * ``PUT /os-services/enable`` (``enable_service``)
+    * ``PUT /os-services/force-down`` (``update_forced_down``)
diff --git a/releasenotes/notes/tempest-rocky-release-0fc3312053923380.yaml b/releasenotes/notes/tempest-rocky-release-0fc3312053923380.yaml
new file mode 100644
index 0000000..e9c77a6
--- /dev/null
+++ b/releasenotes/notes/tempest-rocky-release-0fc3312053923380.yaml
@@ -0,0 +1,16 @@
+---
+prelude: >
+    This release is to tag the Tempest for OpenStack Rocky release.
+    After this release, Tempest will support below OpenStack Releases:
+
+      * Rocky
+      * Queens
+      * Pike
+      * Ocata
+
+    Current development of Tempest is for OpenStack Stein development
+    cycle. Every Tempest commit is also tested against master during
+    the Stein cycle. However, this does not necessarily mean that using
+    Tempest as of this tag will work against a Stein (or future release)
+    cloud.
+    To be on safe side, use this tag to test the OpenStack Rocky release.
\ No newline at end of file
diff --git a/releasenotes/notes/tempest-run-fix-updates-564b41706decbba1.yaml b/releasenotes/notes/tempest-run-fix-updates-564b41706decbba1.yaml
new file mode 100644
index 0000000..0f9a0f6
--- /dev/null
+++ b/releasenotes/notes/tempest-run-fix-updates-564b41706decbba1.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    Adds a new CLI arg in tempest run, ``--black-regex``, which is a
+    regex to exclude the tests that match it.
+fixes:
+  - |
+    Fixes tempest run CLI args mutually exclusive behavior which should not
+    be the case anymore (Bug#1751201).
diff --git a/releasenotes/notes/tempest-stein-release-18bad34136a2e6ef.yaml b/releasenotes/notes/tempest-stein-release-18bad34136a2e6ef.yaml
new file mode 100644
index 0000000..212cf7d
--- /dev/null
+++ b/releasenotes/notes/tempest-stein-release-18bad34136a2e6ef.yaml
@@ -0,0 +1,18 @@
+---
+prelude: >
+    This release is to tag the Tempest for OpenStack Stein release.
+    This release marks the start of Stein release support in Tempest and
+    the end of support for Ocata in Tempest.
+    After this release, Tempest will support below OpenStack Releases:
+
+      * Stein
+      * Rocky
+      * Queens
+      * Pike
+
+    Current development of Tempest is for OpenStack Train development
+    cycle. Every Tempest commit is also tested against master during
+    the Train cycle. However, this does not necessarily mean that using
+    Tempest as of this tag will work against a Train (or future release)
+    cloud.
+    To be on safe side, use this tag to test the OpenStack Stein release.
diff --git a/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml b/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
index ec21098..c69ed50 100644
--- a/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
+++ b/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
@@ -1,5 +1,5 @@
 ---
 features:
   - |
-    Added tempest workspace remove --name <workspace_name> --rmdir
+    Added tempest workspace remove ``--name <workspace_name> --rmdir``
     feature to delete the workspace directory as well as entry.
diff --git a/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml b/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml
new file mode 100644
index 0000000..d3a6851
--- /dev/null
+++ b/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    New string configuration option ``vnc_server_header`` is added
+    to ``compute-feature-enabled`` section. It allows the expected
+    VNC server name in the response header to be specified. For example,
+    obvious at hand names are 'WebSockify', 'nginx'.
+fixes:
+  - |
+    Fix VNC server response header issue when it is behind reverse proxy
diff --git a/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml b/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml
new file mode 100644
index 0000000..ddd1704
--- /dev/null
+++ b/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    A new boolean configuration option
+    ``[compute-feature-enabled]/volume_backed_live_migration`` has been added.
+    If enabled, tests which validate the behavior of Nova's *volume-backed live
+    migration* feature will be executed. The option defaults to ``False``.
diff --git a/releasenotes/notes/volume-service-testing-default-to-v3-endpoints-20b86895a590925d.yaml b/releasenotes/notes/volume-service-testing-default-to-v3-endpoints-20b86895a590925d.yaml
new file mode 100644
index 0000000..ea69293
--- /dev/null
+++ b/releasenotes/notes/volume-service-testing-default-to-v3-endpoints-20b86895a590925d.yaml
@@ -0,0 +1,8 @@
+---
+upgrade:
+  - |
+    The volume config option ``catalog_type`` default is changed to
+    ``volumev3`` which is v3 API endpoint configured in devstack.
+    With this change Tempest will be testing v3 API as default.
+    User who want to test v2 API can still test by configuring the
+    ``catalog_type`` to v2 endpoint.
diff --git a/releasenotes/notes/volume-v3-service-clients-a863a6336af56cca.yaml b/releasenotes/notes/volume-v3-service-clients-a863a6336af56cca.yaml
new file mode 100644
index 0000000..b572a34
--- /dev/null
+++ b/releasenotes/notes/volume-v3-service-clients-a863a6336af56cca.yaml
@@ -0,0 +1,12 @@
+---
+features:
+  - |
+    Adds volume service clients for v3 APIs. As v3 base API should be
+    identical to v2 APIs, we just copy all existing v2 service client
+    for v3 API.
+deprecations:
+  - |
+    Deprecates the volume service clients for v2 APIs. Volume v2 APIs
+    are deprecated in all supported stable branches, so it's time
+    to deprecate the tempest service clients for v2 APIs and remove in future
+    release.
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index ae3dca1..57ec7e1 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -65,16 +65,12 @@
 project = u'tempest Release Notes'
 copyright = u'2016, tempest Developers'
 
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-from tempest.version import version_info as tempest_version
+# Release do not need a version number in the title, they
+# cover multiple versions.
 # The full version, including alpha/beta/rc tags.
-release = tempest_version.version_string_with_vcs()
+release = ''
 # The short X.Y version.
-version = tempest_version.canonical_version_string()
+version = ''
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index df1de46..e5d5bfe 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -2,19 +2,24 @@
  Tempest Release Notes
 ===========================
 
- .. toctree::
-    :maxdepth: 1
+.. toctree::
+   :maxdepth: 1
 
-    unreleased
-    v17.0.0
-    v16.1.0
-    v16.0.0
-    v15.0.0
-    v14.0.0
-    v13.0.0
-    v12.0.0
-    v11.0.0
-    v10.0.0
+   unreleased
+   v20.0.0
+   v19.0.0
+   v18.0.0
+   v17.2.0
+   v17.1.0
+   v17.0.0
+   v16.1.0
+   v16.0.0
+   v15.0.0
+   v14.0.0
+   v13.0.0
+   v12.0.0
+   v11.0.0
+   v10.0.0
 
 Indices and tables
 ==================
diff --git a/releasenotes/source/v17.1.0.rst b/releasenotes/source/v17.1.0.rst
new file mode 100644
index 0000000..b8fd570
--- /dev/null
+++ b/releasenotes/source/v17.1.0.rst
@@ -0,0 +1,6 @@
+=====================
+v17.1.0 Release Notes
+=====================
+
+.. release-notes:: 17.1.0 Release Notes
+   :version: 17.1.0
diff --git a/releasenotes/source/v17.2.0.rst b/releasenotes/source/v17.2.0.rst
new file mode 100644
index 0000000..8566ae4
--- /dev/null
+++ b/releasenotes/source/v17.2.0.rst
@@ -0,0 +1,6 @@
+=====================
+v17.2.0 Release Notes
+=====================
+
+.. release-notes:: 17.2.0 Release Notes
+   :version: 17.2.0
diff --git a/releasenotes/source/v18.0.0.rst b/releasenotes/source/v18.0.0.rst
new file mode 100644
index 0000000..aa05db3
--- /dev/null
+++ b/releasenotes/source/v18.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v18.0.0 Release Notes
+=====================
+
+.. release-notes:: 18.0.0 Release Notes
+   :version: 18.0.0
diff --git a/releasenotes/source/v19.0.0.rst b/releasenotes/source/v19.0.0.rst
new file mode 100644
index 0000000..bcffe5d
--- /dev/null
+++ b/releasenotes/source/v19.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v19.0.0 Release Notes
+=====================
+
+.. release-notes:: 19.0.0 Release Notes
+   :version: 19.0.0
diff --git a/releasenotes/source/v20.0.0.rst b/releasenotes/source/v20.0.0.rst
new file mode 100644
index 0000000..28c5431
--- /dev/null
+++ b/releasenotes/source/v20.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v20.0.0 Release Notes
+=====================
+
+.. release-notes:: 20.0.0 Release Notes
+   :version: 20.0.0
diff --git a/requirements.txt b/requirements.txt
index 4b8de27..bf38fae 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,23 +3,22 @@
 # process, which may cause wedges in the gate later.
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
 cliff!=2.9.0,>=2.8.0 # Apache-2.0
-jsonschema<3.0.0,>=2.6.0 # MIT
-testtools>=1.4.0 # MIT
+jsonschema>=2.6.0 # MIT
+testtools>=2.2.0 # MIT
 paramiko>=2.0.0 # LGPLv2.1+
 netaddr>=0.7.18 # BSD
-testrepository>=0.0.18 # Apache-2.0/BSD
-oslo.concurrency>=3.20.0 # Apache-2.0
-oslo.config>=4.6.0 # Apache-2.0
-oslo.log>=3.30.0 # Apache-2.0
+oslo.concurrency>=3.26.0 # Apache-2.0
+oslo.config>=5.2.0 # Apache-2.0
+oslo.log>=3.36.0 # Apache-2.0
+stestr>=1.0.0 # Apache-2.0
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
-oslo.utils>=3.31.0 # Apache-2.0
-six>=1.9.0 # MIT
+oslo.utils>=3.33.0 # Apache-2.0
+six>=1.10.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
-PyYAML>=3.10 # MIT
-python-subunit>=0.0.18 # Apache-2.0/BSD
+PyYAML>=3.12 # MIT
+python-subunit>=1.0.0 # Apache-2.0/BSD
 stevedore>=1.20.0 # Apache-2.0
 PrettyTable<0.8,>=0.7.1 # BSD
-os-testr>=1.0.0 # Apache-2.0
 urllib3>=1.21.1 # MIT
 debtcollector>=1.2.0 # Apache-2.0
 unittest2>=1.1.0 # BSD
diff --git a/roles/process-stackviz/README.rst b/roles/process-stackviz/README.rst
new file mode 100644
index 0000000..a8447d2
--- /dev/null
+++ b/roles/process-stackviz/README.rst
@@ -0,0 +1,22 @@
+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
new file mode 100644
index 0000000..f3bc32b
--- /dev/null
+++ b/roles/process-stackviz/defaults/main.yaml
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 0000000..3724e0e
--- /dev/null
+++ b/roles/process-stackviz/tasks/main.yaml
@@ -0,0 +1,65 @@
+- 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
+  pip:
+    name: "file://{{ stackviz_archive.stat.path }}"
+    virtualenv: /tmp/stackviz
+    extra_args: -U
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- 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/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index a75fc31..e1787b6 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -12,7 +12,58 @@
 
    The number of parallel test processes.
 
-.. zuul:rolevar:: tox_venvlist
+.. zuul:rolevar:: tempest_test_regex
+   :default: ''
+
+   A regular expression used to select the tests.
+
+   It works only when used with some specific tox environments
+   ('all', 'all-plugin'.)
+
+   Multi-line and commented regexs can be achieved by doing this:
+
+       ::
+           vars:
+             tempest_test_regex: |
+               (?x)    # Ignore comments and whitespaces
+               # Line with only a comment.
+               (tempest\.(api|scenario|thirdparty)).*$    # Run only api scenario and third party
+
+.. zuul:rolevar:: tempest_test_blacklist
+
+   Specifies a blacklist file to skip tests that are not needed.
+
+   Pass a full path to the file.
+
+.. zuul:rolevar:: tox_envlist
    :default: smoke
 
    The Tempest tox environment to run.
+
+.. zuul:rolevar:: tempest_black_regex
+   :default: ''
+
+   A regular expression used to skip the tests.
+
+   It works only when used with some specific tox environments
+   ('all', 'all-plugin'.)
+
+   Multi-line and commented regexs can be achieved by doing this:
+
+       ::
+           vars:
+             tempest_black_regex: |
+               (?x)    # Ignore comments and whitespaces
+               # Line with only a comment.
+               (tempest.api.identity).*$
+
+.. zuul:rolevar:: tox_extra_args
+   :default: ''
+
+   String of extra command line options to pass to tox.
+
+   Here is an example of running tox with --sitepackages option:
+
+       ::
+           vars:
+             tox_extra_args: --sitepackages
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index e1e81da..06918b5 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -1,2 +1,5 @@
 devstack_base_dir: /opt/stack
-tox_venvlist: smoke
+tempest_test_regex: ''
+tox_envlist: smoke
+tempest_black_regex: ''
+tox_extra_args: ''
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index d079513..16086aa 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -20,8 +20,24 @@
     default_concurrency: "{{ num_cores|int // 2 }}"
   when: num_cores|int > 3
 
+- when:
+    - tempest_test_blacklist is defined
+  block:
+    - name: Check for test blacklist file
+      stat:
+        path: "{{ tempest_test_blacklist }}"
+      register:
+        blacklist_stat
+
+    - name: Build blacklist option
+      set_fact:
+        blacklist_option: "--blacklist-file={{ tempest_test_blacklist|quote }}"
+      when: blacklist_stat.stat.exists
+
 - name: Run Tempest
-  command: tox -e {{tox_venvlist}} -- --concurrency={{tempest_concurrency|default(default_concurrency)}}
+  command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} \
+            --concurrency={{tempest_concurrency|default(default_concurrency)}} \
+            --black-regex={{tempest_black_regex|quote}}
   args:
     chdir: "{{devstack_base_dir}}/tempest"
   become: true
diff --git a/setup.cfg b/setup.cfg
index 04bb29f..fcbe956 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,7 +4,7 @@
 description-file =
     README.rst
 author = OpenStack
-author-email = openstack-dev@lists.openstack.org
+author-email = openstack-discuss@lists.openstack.org
 home-page = https://docs.openstack.org/tempest/latest/
 classifier =
     Intended Audience :: Information Technology
@@ -47,11 +47,5 @@
 oslo.config.opts =
     tempest.config = tempest.config:list_opts
 
-[build_sphinx]
-all-files = 1
-warning-is-error = 1
-build-dir = doc/build
-source-dir = doc/source
-
 [wheel]
 universal = 1
diff --git a/tempest/README.rst b/tempest/README.rst
index 663653e..b345032 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -9,13 +9,15 @@
 OpenStack clouds.
 
 As such Tempest tests come in many flavors, each with their own rules
-and guidelines. Below is the overview of the Tempest respository structure
+and guidelines. Below is the overview of the Tempest repository structure
 to make this clear.
 
-| tempest/
-|    api/ - API tests
-|    scenario/ - complex scenario tests
-|    tests/ - unit tests for Tempest internals
+.. code-block:: console
+
+   tempest/
+      api/ - API tests
+      scenario/ - complex scenario tests
+      tests/ - unit tests for Tempest internals
 
 Each of these directories contains different types of tests. What
 belongs in each directory, the rules and examples for good tests, are
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 57d3983..7a3bfdf 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -25,17 +25,17 @@
 CONF = config.CONF
 
 
-class AggregatesAdminTestJSON(base.BaseV2ComputeAdminTest):
+class AggregatesAdminTestBase(base.BaseV2ComputeAdminTest):
     """Tests Aggregates API that require admin privileges"""
 
     @classmethod
     def setup_clients(cls):
-        super(AggregatesAdminTestJSON, cls).setup_clients()
+        super(AggregatesAdminTestBase, cls).setup_clients()
         cls.client = cls.os_admin.aggregates_client
 
     @classmethod
     def resource_setup(cls):
-        super(AggregatesAdminTestJSON, cls).resource_setup()
+        super(AggregatesAdminTestBase, cls).resource_setup()
         cls.aggregate_name_prefix = 'test_aggregate'
         cls.az_name_prefix = 'test_az'
 
@@ -48,11 +48,11 @@
                       if (hyper['hypervisor_type'] ==
                           CONF.compute.hypervisor_type)]
 
-        hosts_available = [hyper['service']['host'] for hyper in hypers
-                           if (hyper['state'] == 'up' and
-                               hyper['status'] == 'enabled')]
-        if hosts_available:
-            cls.host = hosts_available[0]
+        cls.hosts_available = [hyper['service']['host'] for hyper in hypers
+                               if (hyper['state'] == 'up' and
+                                   hyper['status'] == 'enabled')]
+        if cls.hosts_available:
+            cls.host = cls.hosts_available[0]
         else:
             msg = "no available compute node found"
             if CONF.compute.hypervisor_type:
@@ -69,6 +69,9 @@
 
         return aggregate
 
+
+class AggregatesAdminTestJSON(AggregatesAdminTestBase):
+
     @decorators.idempotent_id('0d148aa3-d54c-4317-aa8d-42040a475e20')
     def test_aggregate_create_delete(self):
         # Create and delete an aggregate.
@@ -206,11 +209,49 @@
         az_name = data_utils.rand_name(self.az_name_prefix)
         aggregate = self._create_test_aggregate(availability_zone=az_name)
 
-        self.client.add_host(aggregate['id'], host=self.host)
-        self.addCleanup(self.client.remove_host, aggregate['id'],
-                        host=self.host)
-        admin_servers_client = self.os_admin.servers_client
+        # Find a host that has not been added to other availability zone,
+        # for one host can't be added to different availability zones.
+        aggregates = self.client.list_aggregates()['aggregates']
+        hosts_in_zone = []
+        for agg in aggregates:
+            if agg['availability_zone']:
+                hosts_in_zone.extend(agg['hosts'])
+        hosts = [v for v in self.hosts_available if v not in hosts_in_zone]
+        if not hosts:
+            raise self.skipException("All hosts are already in other "
+                                     "availability zones, so can't add "
+                                     "host to aggregate. \nAggregates list: "
+                                     "%s" % aggregates)
+        host = hosts[0]
+
+        self.client.add_host(aggregate['id'], host=host)
+        self.addCleanup(self.client.remove_host, aggregate['id'], host=host)
         server = self.create_test_server(availability_zone=az_name,
                                          wait_until='ACTIVE')
-        body = admin_servers_client.show_server(server['id'])['server']
-        self.assertEqual(self.host, body['OS-EXT-SRV-ATTR:host'])
+        server_host = self.get_host_for_server(server['id'])
+        self.assertEqual(host, server_host)
+
+
+class AggregatesAdminTestV241(AggregatesAdminTestBase):
+    min_microversion = '2.41'
+
+    # NOTE(gmann): This test tests the Aggregate APIs response schema
+    # for 2.41 microversion. No specific assert or behaviour verification
+    # is needed.
+
+    @decorators.idempotent_id('fdf24d9e-8afa-4700-b6aa-9c498351504f')
+    def test_create_update_show_aggregate_add_remove_host(self):
+        # Update and add a host to the given aggregate and get details.
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        # Checking create aggregate API response schema
+        aggregate = self._create_test_aggregate()
+
+        new_aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        # Checking update aggregate API response schema
+        self.client.update_aggregate(aggregate['id'], name=new_aggregate_name)
+        # Checking show aggregate API response schema
+        self.client.show_aggregate(aggregate['id'])['aggregate']
+        # Checking add host to aggregate API response schema
+        self.client.add_host(aggregate['id'], host=self.host)
+        # Checking rempve host from aggregate API response schema
+        self.client.remove_host(aggregate['id'], host=self.host)
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
index 36ff09e..a6e0efa 100644
--- a/tempest/api/compute/admin/test_aggregates_negative.py
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -27,16 +27,17 @@
     def setup_clients(cls):
         super(AggregatesAdminNegativeTestJSON, cls).setup_clients()
         cls.client = cls.os_admin.aggregates_client
+        cls.services_client = cls.os_admin.services_client
 
     @classmethod
     def resource_setup(cls):
         super(AggregatesAdminNegativeTestJSON, cls).resource_setup()
         cls.aggregate_name_prefix = 'test_aggregate'
 
-        hosts_all = cls.os_admin.hosts_client.list_hosts()['hosts']
-        hosts = ([host['host_name']
-                 for host in hosts_all if host['service'] == 'compute'])
-        cls.host = hosts[0]
+        svc_list = cls.services_client.list_services(
+            binary='nova-compute')['services']
+        cls.hosts = [v['host'] for v in svc_list
+                     if v['status'] == 'enabled' and v['state'] == 'up']
 
     def _create_test_aggregate(self):
         aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -123,11 +124,9 @@
     @decorators.idempotent_id('0ef07828-12b4-45ba-87cc-41425faf5711')
     def test_aggregate_add_non_exist_host(self):
         # Adding a non-exist host to an aggregate should raise exceptions.
-        hosts_all = self.os_admin.hosts_client.list_hosts()['hosts']
-        hosts = map(lambda x: x['host_name'], hosts_all)
         while True:
             non_exist_host = data_utils.rand_name('nonexist_host')
-            if non_exist_host not in hosts:
+            if non_exist_host not in self.hosts:
                 break
         aggregate = self._create_test_aggregate()
         self.assertRaises(lib_exc.NotFound, self.client.add_host,
@@ -140,7 +139,7 @@
         aggregate = self._create_test_aggregate()
         self.assertRaises(lib_exc.Forbidden,
                           self.aggregates_client.add_host,
-                          aggregate['id'], host=self.host)
+                          aggregate['id'], host=self.hosts[0])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('19dd44e1-c435-4ee1-a402-88c4f90b5950')
@@ -148,12 +147,12 @@
         self.useFixture(fixtures.LockFixture('availability_zone'))
         aggregate = self._create_test_aggregate()
 
-        self.client.add_host(aggregate['id'], host=self.host)
+        self.client.add_host(aggregate['id'], host=self.hosts[0])
         self.addCleanup(self.client.remove_host, aggregate['id'],
-                        host=self.host)
+                        host=self.hosts[0])
 
         self.assertRaises(lib_exc.Conflict, self.client.add_host,
-                          aggregate['id'], host=self.host)
+                          aggregate['id'], host=self.hosts[0])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('7a53af20-137a-4e44-a4ae-e19260e626d9')
@@ -162,13 +161,13 @@
         self.useFixture(fixtures.LockFixture('availability_zone'))
         aggregate = self._create_test_aggregate()
 
-        self.client.add_host(aggregate['id'], host=self.host)
+        self.client.add_host(aggregate['id'], host=self.hosts[0])
         self.addCleanup(self.client.remove_host, aggregate['id'],
-                        host=self.host)
+                        host=self.hosts[0])
 
         self.assertRaises(lib_exc.Forbidden,
                           self.aggregates_client.remove_host,
-                          aggregate['id'], host=self.host)
+                          aggregate['id'], host=self.hosts[0])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('95d6a6fa-8da9-4426-84d0-eec0329f2e4d')
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
index a9772c4..e8011a6 100644
--- a/tempest/api/compute/admin/test_auto_allocate_network.py
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -84,8 +84,7 @@
         nets = cls.networks_client.list_networks(
             **search_opts).get('networks', [])
         if nets:
-            raise lib_excs.TempestException(
-                'Found shared networks: %s' % nets)
+            raise cls.skipException('Found shared networks: %s' % nets)
 
     @classmethod
     def resource_cleanup(cls):
@@ -149,9 +148,7 @@
     def test_server_create_no_allocate(self):
         """Tests that no networking is allocated for the server."""
         # create the server with no networking
-        server, _ = compute.create_test_server(
-            self.os_primary, networks='none', wait_until='ACTIVE')
-        self.addCleanup(self.delete_server, server['id'])
+        server = self.create_test_server(networks='none', wait_until='ACTIVE')
         # get the server ips
         addresses = self.servers_client.list_addresses(
             server['id'])['addresses']
diff --git a/tempest/api/compute/admin/test_create_server.py b/tempest/api/compute/admin/test_create_server.py
index 08b2d19..711b441 100644
--- a/tempest/api/compute/admin/test_create_server.py
+++ b/tempest/api/compute/admin/test_create_server.py
@@ -56,6 +56,18 @@
             # Create a flavor with ephemeral disk
             flavor = self.create_flavor(name=flavor_name, ram=ram, vcpus=vcpus,
                                         disk=disk, ephemeral=ephem_disk)
+
+            # Set extra specs same as self.flavor_ref for the created flavor,
+            # because the environment may need some special extra specs to
+            # create server which should have been contained in
+            # self.flavor_ref.
+            extra_spec_keys = \
+                self.admin_flavors_client.list_flavor_extra_specs(
+                    self.flavor_ref)['extra_specs']
+            if extra_spec_keys:
+                self.admin_flavors_client.set_flavor_extra_spec(
+                    flavor['id'], **extra_spec_keys)
+
             return flavor['id']
 
         flavor_with_eph_disk_id = create_flavor_with_ephemeral(ephem_disk=1)
diff --git a/tempest/api/compute/admin/test_delete_server.py b/tempest/api/compute/admin/test_delete_server.py
index 83444b9..58cac57 100644
--- a/tempest/api/compute/admin/test_delete_server.py
+++ b/tempest/api/compute/admin/test_delete_server.py
@@ -15,11 +15,8 @@
 
 from tempest.api.compute import base
 from tempest.common import waiters
-from tempest import config
 from tempest.lib import decorators
 
-CONF = config.CONF
-
 
 class DeleteServersAdminTestJSON(base.BaseV2ComputeAdminTest):
     # NOTE: Server creations of each test class should be under 10
diff --git a/tempest/api/compute/admin/test_flavors_microversions.py b/tempest/api/compute/admin/test_flavors_microversions.py
new file mode 100644
index 0000000..31b9217
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_microversions.py
@@ -0,0 +1,51 @@
+# Copyright 2018 NEC Corporation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+from tempest.api.compute import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+
+class FlavorsV255TestJSON(base.BaseV2ComputeAdminTest):
+    min_microversion = '2.55'
+    max_microversion = 'latest'
+
+    # NOTE(gmann): This class tests the flavors APIs
+    # response schema for the 2.55 microversion.
+
+    @decorators.idempotent_id('61976b25-488d-41dc-9dcb-cb9693a7b075')
+    def test_crud_flavor(self):
+        flavor_id = data_utils.rand_int_id(start=1000)
+        # Checking create API response schema
+        new_flavor_id = self.create_flavor(ram=512,
+                                           vcpus=1,
+                                           disk=10,
+                                           id=flavor_id)['id']
+        # Checking show API response schema
+        self.flavors_client.show_flavor(new_flavor_id)
+        # Checking update API response schema
+        self.admin_flavors_client.update_flavor(new_flavor_id,
+                                                description='new')
+        # Checking list details API response schema
+        self.flavors_client.list_flavors(detail=True)
+        # Checking list API response schema
+        self.flavors_client.list_flavors()
+
+
+class FlavorsV261TestJSON(FlavorsV255TestJSON):
+    min_microversion = '2.61'
+    max_microversion = 'latest'
+
+    # NOTE(gmann): This class tests the flavors APIs
+    # response schema for the 2.61 microversion.
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
index ba19937..2d7e1a7 100644
--- a/tempest/api/compute/admin/test_floating_ips_bulk.py
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -25,12 +25,16 @@
 CONF = config.CONF
 
 
+# TODO(stephenfin): Remove this test class once the nova queens branch goes
+# into extended maintenance mode.
 class FloatingIPsBulkAdminTestJSON(base.BaseV2ComputeAdminTest):
     """Tests Floating IPs Bulk APIs that require admin privileges.
 
     API documentation - http://docs.openstack.org/api/openstack-compute/2/
     content/ext-os-floating-ips-bulk.html
     """
+    max_microversion = '2.35'
+    depends_on_nova_network = True
 
     @classmethod
     def setup_clients(cls):
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 00f3256..c246685 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -20,6 +20,8 @@
 class HostsAdminTestJSON(base.BaseV2ComputeAdminTest):
     """Tests hosts API using admin privileges."""
 
+    max_microversion = '2.42'
+
     @classmethod
     def setup_clients(cls):
         super(HostsAdminTestJSON, cls).setup_clients()
diff --git a/tempest/api/compute/admin/test_hosts_negative.py b/tempest/api/compute/admin/test_hosts_negative.py
index 5bd8104..8a91ae2 100644
--- a/tempest/api/compute/admin/test_hosts_negative.py
+++ b/tempest/api/compute/admin/test_hosts_negative.py
@@ -20,6 +20,8 @@
 class HostsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
     """Tests hosts API using admin privileges."""
 
+    max_microversion = '2.42'
+
     @classmethod
     def setup_clients(cls):
         super(HostsAdminNegativeTestJSON, cls).setup_clients()
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 404fd94..9822c26 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -17,12 +17,12 @@
 from tempest.lib import decorators
 
 
-class HypervisorAdminTestJSON(base.BaseV2ComputeAdminTest):
+class HypervisorAdminTestBase(base.BaseV2ComputeAdminTest):
     """Tests Hypervisors API that require admin privileges"""
 
     @classmethod
     def setup_clients(cls):
-        super(HypervisorAdminTestJSON, cls).setup_clients()
+        super(HypervisorAdminTestBase, cls).setup_clients()
         cls.client = cls.os_admin.hypervisor_client
 
     def _list_hypervisors(self):
@@ -30,6 +30,10 @@
         hypers = self.client.list_hypervisors()['hypervisors']
         return hypers
 
+
+class HypervisorAdminTestJSON(HypervisorAdminTestBase):
+    """Tests Hypervisors API that require admin privileges"""
+
     @decorators.idempotent_id('7f0ceacd-c64d-4e96-b8ee-d02943142cc5')
     def test_get_hypervisor_list(self):
         # List of hypervisor and available hypervisors hostname
@@ -53,17 +57,6 @@
         self.assertEqual(details['hypervisor_hostname'],
                          hypers[0]['hypervisor_hostname'])
 
-    @decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862')
-    def test_get_hypervisor_show_servers(self):
-        # Show instances about the specific hypervisors
-        hypers = self._list_hypervisors()
-        self.assertNotEmpty(hypers, "No hypervisors found.")
-
-        hostname = hypers[0]['hypervisor_hostname']
-        hypervisors = (self.client.list_servers_on_hypervisor(hostname)
-                       ['hypervisors'])
-        self.assertNotEmpty(hypervisors)
-
     @decorators.idempotent_id('797e4f28-b6e0-454d-a548-80cc77c00816')
     def test_get_hypervisor_stats(self):
         # Verify the stats of the all hypervisor
@@ -86,7 +79,8 @@
         for hyper in hypers:
             details = (self.client.show_hypervisor(hyper['id'])
                        ['hypervisor'])
-            if details['hypervisor_type'] != 'ironic':
+            if (details['hypervisor_type'] != 'ironic' and
+                    details['state'] == 'up'):
                 hypers_without_ironic.append(hyper)
                 ironic_only = False
 
@@ -110,6 +104,34 @@
             has_valid_uptime,
             "None of the hypervisors had a valid uptime: %s" % hypers)
 
+
+class HypervisorAdminV228Test(HypervisorAdminTestBase):
+    min_microversion = '2.28'
+
+    @decorators.idempotent_id('d46bab64-0fbe-4eb8-9133-e6ee56188cc5')
+    def test_get_list_hypervisor_details(self):
+        # NOTE(zhufl): This test tests the hypervisor APIs response schema
+        # for 2.28 microversion. No specific assert or behaviour verification
+        # is needed.
+        hypers = self._list_hypervisors()
+        self.assertNotEmpty(hypers, "No hypervisors found.")
+        self.client.show_hypervisor(hypers[0]['id'])
+
+
+class HypervisorAdminUnderV252Test(HypervisorAdminTestBase):
+    max_microversion = '2.52'
+
+    @decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862')
+    def test_get_hypervisor_show_servers(self):
+        # Show instances about the specific hypervisors
+        hypers = self._list_hypervisors()
+        self.assertNotEmpty(hypers, "No hypervisors found.")
+
+        hostname = hypers[0]['hypervisor_hostname']
+        hypervisors = (self.client.list_servers_on_hypervisor(hostname)
+                       ['hypervisors'])
+        self.assertNotEmpty(hypervisors)
+
     @decorators.idempotent_id('d7e1805b-3b14-4a3b-b6fd-50ec6d9f361f')
     def test_search_hypervisor(self):
         hypers = self._list_hypervisors()
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index 431e823..0056376 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -19,12 +19,12 @@
 from tempest.lib import exceptions as lib_exc
 
 
-class HypervisorAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class HypervisorAdminNegativeTestBase(base.BaseV2ComputeAdminTest):
     """Tests Hypervisors API that require admin privileges"""
 
     @classmethod
     def setup_clients(cls):
-        super(HypervisorAdminNegativeTestJSON, cls).setup_clients()
+        super(HypervisorAdminNegativeTestBase, cls).setup_clients()
         cls.client = cls.os_admin.hypervisor_client
         cls.non_adm_client = cls.hypervisor_client
 
@@ -33,6 +33,10 @@
         hypers = self.client.list_hypervisors()['hypervisors']
         return hypers
 
+
+class HypervisorAdminNegativeTestJSON(HypervisorAdminNegativeTestBase):
+    """Tests Hypervisors API that require admin privileges"""
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('c136086a-0f67-4b2b-bc61-8482bd68989f')
     def test_show_nonexistent_hypervisor(self):
@@ -55,27 +59,6 @@
             hypers[0]['id'])
 
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
-    def test_show_servers_with_non_admin_user(self):
-        hypers = self._list_hypervisors()
-        self.assertNotEmpty(hypers)
-
-        self.assertRaises(
-            lib_exc.Forbidden,
-            self.non_adm_client.list_servers_on_hypervisor,
-            hypers[0]['id'])
-
-    @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
-    def test_show_servers_with_nonexistent_hypervisor(self):
-        nonexistent_hypervisor_id = data_utils.rand_uuid()
-
-        self.assertRaises(
-            lib_exc.NotFound,
-            self.client.list_servers_on_hypervisor,
-            nonexistent_hypervisor_id)
-
-    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('e2b061bb-13f9-40d8-9d6e-d5bf17595849')
     def test_get_hypervisor_stats_with_non_admin_user(self):
         self.assertRaises(
@@ -119,13 +102,30 @@
             lib_exc.Forbidden,
             self.non_adm_client.list_hypervisors, detail=True)
 
+
+class HypervisorAdminNegativeUnderV252Test(HypervisorAdminNegativeTestBase):
+    max_microversion = '2.52'
+
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
-    def test_search_nonexistent_hypervisor(self):
+    @decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
+    def test_show_servers_with_non_admin_user(self):
+        hypers = self._list_hypervisors()
+        self.assertNotEmpty(hypers)
+
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.non_adm_client.list_servers_on_hypervisor,
+            hypers[0]['id'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
+    def test_show_servers_with_nonexistent_hypervisor(self):
+        nonexistent_hypervisor_id = data_utils.rand_uuid()
+
         self.assertRaises(
             lib_exc.NotFound,
-            self.client.search_hypervisor,
-            'nonexistent_hypervisor_name')
+            self.client.list_servers_on_hypervisor,
+            nonexistent_hypervisor_id)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('5b6a6c79-5dc1-4fa5-9c58-9c8085948e74')
@@ -137,3 +137,11 @@
             lib_exc.Forbidden,
             self.non_adm_client.search_hypervisor,
             hypers[0]['hypervisor_hostname'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
+    def test_search_nonexistent_hypervisor(self):
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.client.search_hypervisor,
+            'nonexistent_hypervisor_name')
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 e4a2ffd..1b62249 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log.py
@@ -31,27 +31,11 @@
     @decorators.idempotent_id('25319919-33d9-424f-9f99-2c203ee48b9d')
     def test_list_instance_usage_audit_logs(self):
         # list instance usage audit logs
-        body = (self.adm_client.list_instance_usage_audit_logs()
-                ["instance_usage_audit_logs"])
-        expected_items = ['total_errors', 'total_instances', 'log',
-                          'num_hosts_running', 'num_hosts_done',
-                          'num_hosts', 'hosts_not_run', 'overall_status',
-                          'period_ending', 'period_beginning',
-                          'num_hosts_not_run']
-        for item in expected_items:
-            self.assertIn(item, body)
+        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
         now = datetime.datetime.now()
-        body = (self.adm_client.show_instance_usage_audit_log(
+        self.adm_client.show_instance_usage_audit_log(
             urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S")))
-            ["instance_usage_audit_log"])
-
-        expected_items = ['total_errors', 'total_instances', 'log',
-                          'num_hosts_running', 'num_hosts_done', 'num_hosts',
-                          'hosts_not_run', 'overall_status', 'period_ending',
-                          'period_beginning', 'num_hosts_not_run']
-        for item in expected_items:
-            self.assertIn(item, body)
diff --git a/tempest/api/compute/admin/test_keypairs_v210.py b/tempest/api/compute/admin/test_keypairs_v210.py
index e24c7c1..40ed532 100644
--- a/tempest/api/compute/admin/test_keypairs_v210.py
+++ b/tempest/api/compute/admin/test_keypairs_v210.py
@@ -34,7 +34,8 @@
             k_name = data_utils.rand_name('keypair')
             keypair = self.create_keypair(k_name,
                                           keypair_type='ssh',
-                                          user_id=user_id)
+                                          user_id=user_id,
+                                          client=self.client)
             self.assertEqual(k_name, keypair['name'],
                              "The created keypair name is not equal "
                              "to the requested name!")
@@ -55,8 +56,9 @@
         self.assertEqual(first_keyname, keypair_detail['name'])
         self.assertEqual(user_id, keypair_detail['user_id'],
                          "The fetched keypair is not for requested user!")
-        # Create a admin keypair
-        admin_keypair = self.create_keypair(keypair_type='ssh')
+        # Create an admin keypair
+        admin_keypair = self.create_keypair(keypair_type='ssh',
+                                            client=self.client)
         admin_keypair.pop('private_key', None)
         admin_keypair.pop('user_id')
 
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 411159b..b1a7c52 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -29,13 +29,11 @@
 LOG = logging.getLogger(__name__)
 
 
-class LiveMigrationTest(base.BaseV2ComputeAdminTest):
-    max_microversion = '2.24'
-    block_migration = None
+class LiveMigrationTestBase(base.BaseV2ComputeAdminTest):
 
     @classmethod
     def skip_checks(cls):
-        super(LiveMigrationTest, cls).skip_checks()
+        super(LiveMigrationTestBase, cls).skip_checks()
 
         if not CONF.compute_feature_enabled.live_migration:
             skip_msg = ("%s skipped as live-migration is "
@@ -52,21 +50,20 @@
         # a subnet so the instance being migrated has a single port, but
         # we need that to make sure we are properly updating the port
         # host bindings during the live migration.
-        # TODO(mriedem): SSH validation before and after the instance is
-        # live migrated would be a nice test wrinkle addition.
         cls.set_network_resources(network=True, subnet=True)
-        super(LiveMigrationTest, cls).setup_credentials()
+        super(LiveMigrationTestBase, cls).setup_credentials()
 
     @classmethod
     def setup_clients(cls):
-        super(LiveMigrationTest, cls).setup_clients()
+        super(LiveMigrationTestBase, cls).setup_clients()
         cls.admin_migration_client = cls.os_admin.migrations_client
 
     def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
         kwargs = dict()
         block_migration = getattr(self, 'block_migration', None)
         if self.block_migration is None:
-            kwargs['disk_over_commit'] = False
+            if self.is_requested_microversion_compatible('2.24'):
+                kwargs['disk_over_commit'] = False
             block_migration = (CONF.compute_feature_enabled.
                                block_migration_for_live_migration and
                                not volume_backed)
@@ -90,6 +87,11 @@
         self.assertEqual(target_host, self.get_host_for_server(server_id),
                          msg)
 
+
+class LiveMigrationTest(LiveMigrationTestBase):
+    max_microversion = '2.24'
+    block_migration = None
+
     def _test_live_migration(self, state='ACTIVE', volume_backed=False):
         """Tests live migration between two hosts.
 
@@ -132,7 +134,9 @@
     def test_live_block_migration_paused(self):
         self._test_live_migration(state='PAUSED')
 
-    @decorators.skip_because(bug="1524898")
+    @testtools.skipUnless(CONF.compute_feature_enabled.
+                          volume_backed_live_migration,
+                          'Volume-backed live migration not available')
     @decorators.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
     @utils.services('volume')
     def test_volume_backed_live_migration(self):
@@ -145,6 +149,7 @@
     @testtools.skipIf(not CONF.compute_feature_enabled.
                       block_migrate_cinder_iscsi,
                       'Block Live migration not configured for iSCSI')
+    @utils.services('volume')
     def test_iscsi_volume(self):
         server = self.create_test_server(wait_until="ACTIVE")
         server_id = server['id']
@@ -156,18 +161,15 @@
         self.attach_volume(server, volume, device='/dev/xvdb')
         server = self.admin_servers_client.show_server(server_id)['server']
         volume_id1 = server["os-extended-volumes:volumes_attached"][0]["id"]
-        self._migrate_server_to(server_id, target_host)
-        waiters.wait_for_server_status(self.servers_client,
-                                       server_id, 'ACTIVE')
+        self._live_migrate(server_id, target_host, 'ACTIVE')
 
         server = self.admin_servers_client.show_server(server_id)['server']
         volume_id2 = server["os-extended-volumes:volumes_attached"][0]["id"]
 
-        self.assertEqual(target_host, self.get_host_for_server(server_id))
         self.assertEqual(volume_id1, volume_id2)
 
 
-class LiveMigrationRemoteConsolesV26Test(LiveMigrationTest):
+class LiveMigrationRemoteConsolesV26Test(LiveMigrationTestBase):
     min_microversion = '2.6'
     max_microversion = 'latest'
 
@@ -202,10 +204,7 @@
         self._verify_console_interaction(server01_id)
         self._verify_console_interaction(server02_id)
 
-        self._migrate_server_to(server01_id, host02_id)
-        waiters.wait_for_server_status(self.servers_client,
-                                       server01_id, 'ACTIVE')
-        self.assertEqual(host02_id, self.get_host_for_server(server01_id))
+        self._live_migrate(server01_id, host02_id, 'ACTIVE')
         self._verify_console_interaction(server01_id)
         # At this point, both instances have a valid serial console
         # connection, which means the ports got updated.
@@ -228,8 +227,8 @@
             while data not in console_output and t <= 120.0:
                 try:
                     ws.send_frame(data)
-                    recieved = ws.receive_frame()
-                    console_output += recieved
+                    received = ws.receive_frame()
+                    console_output += received
                 except Exception:
                     # In case we had an issue with send/receive on the
                     # websocket connection, we create a new one.
diff --git a/tempest/api/compute/admin/test_live_migration_negative.py b/tempest/api/compute/admin/test_live_migration_negative.py
index deabbc2..8327a3b 100644
--- a/tempest/api/compute/admin/test_live_migration_negative.py
+++ b/tempest/api/compute/admin/test_live_migration_negative.py
@@ -32,9 +32,10 @@
 
     def _migrate_server_to(self, server_id, dest_host):
         bmflm = CONF.compute_feature_enabled.block_migration_for_live_migration
-        self.admin_servers_client.live_migrate_server(
-            server_id, host=dest_host, block_migration=bmflm,
-            disk_over_commit=False)
+        kwargs = dict(host=dest_host, block_migration=bmflm)
+        if self.is_requested_microversion_compatible('2.24'):
+            kwargs['disk_over_commit'] = False
+        self.admin_servers_client.live_migrate_server(server_id, **kwargs)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('7fb7856e-ae92-44c9-861a-af62d7830bcb')
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index a626ebb..83f2e61 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -77,6 +77,16 @@
         )['flavor']
         self.addCleanup(self._flavor_clean_up, flavor['id'])
 
+        # Set extra specs same as self.flavor_ref for the created flavor,
+        # because the environment may need some special extra specs to
+        # create server which should have been contained in
+        # self.flavor_ref.
+        extra_spec_keys = self.admin_flavors_client.list_flavor_extra_specs(
+            self.flavor_ref)['extra_specs']
+        if extra_spec_keys:
+            self.admin_flavors_client.set_flavor_extra_spec(
+                flavor['id'], **extra_spec_keys)
+
         # Now boot a server with the copied flavor.
         server = self.create_test_server(
             wait_until='ACTIVE', flavor=flavor['id'])
@@ -96,7 +106,7 @@
                                        'ACTIVE')
 
         server = self.servers_client.show_server(server['id'])['server']
-        self.assertEqual(flavor['id'], server['flavor']['id'])
+        self.assert_flavor_equal(flavor['id'], server['flavor'])
 
     def _test_cold_migrate_server(self, revert=False):
         if CONF.compute.min_compute_nodes < 2:
@@ -104,8 +114,7 @@
             raise self.skipException(msg)
 
         server = self.create_test_server(wait_until="ACTIVE")
-        src_host = self.admin_servers_client.show_server(
-            server['id'])['server']['OS-EXT-SRV-ATTR:host']
+        src_host = self.get_host_for_server(server['id'])
 
         self.admin_servers_client.migrate_server(server['id'])
 
@@ -121,8 +130,7 @@
 
         waiters.wait_for_server_status(self.servers_client,
                                        server['id'], 'ACTIVE')
-        dst_host = self.admin_servers_client.show_server(
-            server['id'])['server']['OS-EXT-SRV-ATTR:host']
+        dst_host = self.get_host_for_server(server['id'])
         assert_func(src_host, dst_host)
 
     @decorators.idempotent_id('4bf0be52-3b6f-4746-9a27-3143636fe30d')
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
index acb0d90..99907a8 100644
--- a/tempest/api/compute/admin/test_networks.py
+++ b/tempest/api/compute/admin/test_networks.py
@@ -24,8 +24,9 @@
     """Tests Nova Networks API that usually requires admin privileges.
 
     API docs:
-    http://developer.openstack.org/api-ref-compute-v2-ext.html#ext-os-networks
+    https://developer.openstack.org/api-ref/compute/#networks-os-networks-deprecated
     """
+    max_microversion = '2.35'
 
     @classmethod
     def setup_clients(cls):
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index c2bdf7e..0060ffe 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -25,72 +25,20 @@
 LOG = logging.getLogger(__name__)
 
 
-class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest):
+class QuotasAdminTestBase(base.BaseV2ComputeAdminTest):
     force_tenant_isolation = True
 
     def setUp(self):
         # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
         self.useFixture(fixtures.LockFixture('compute_quotas'))
-        super(QuotasAdminTestJSON, self).setUp()
+        super(QuotasAdminTestBase, self).setUp()
 
     @classmethod
     def setup_clients(cls):
-        super(QuotasAdminTestJSON, cls).setup_clients()
+        super(QuotasAdminTestBase, cls).setup_clients()
         cls.adm_client = cls.os_admin.quotas_client
 
-    @classmethod
-    def resource_setup(cls):
-        super(QuotasAdminTestJSON, cls).resource_setup()
-
-        # NOTE(afazekas): these test cases should always create and use a new
-        # tenant most of them should be skipped if we can't do that
-        cls.demo_tenant_id = cls.quotas_client.tenant_id
-
-        cls.default_quota_set = set(('injected_file_content_bytes',
-                                     'metadata_items', 'injected_files',
-                                     'ram', 'floating_ips',
-                                     'fixed_ips', 'key_pairs',
-                                     'injected_file_path_bytes',
-                                     'instances', 'security_group_rules',
-                                     'cores', 'security_groups'))
-
-    @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
-        expected_quota_set = self.default_quota_set | set(['id'])
-        quota_set = self.adm_client.show_default_quota_set(
-            self.demo_tenant_id)['quota_set']
-        self.assertEqual(quota_set['id'], self.demo_tenant_id)
-        for quota in expected_quota_set:
-            self.assertIn(quota, quota_set.keys())
-
-    @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
-        default_quota_set = self.adm_client.show_default_quota_set(
-            self.demo_tenant_id)['quota_set']
-        new_quota_set = {'injected_file_content_bytes': 20480,
-                         'metadata_items': 256, 'injected_files': 10,
-                         'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10,
-                         'key_pairs': 200, 'injected_file_path_bytes': 512,
-                         'instances': 20, 'security_group_rules': 20,
-                         'cores': 2, 'security_groups': 20,
-                         'server_groups': 20, 'server_group_members': 20}
-        # Update limits for all quota resources
-        quota_set = self.adm_client.update_quota_set(
-            self.demo_tenant_id,
-            force=True,
-            **new_quota_set)['quota_set']
-
-        default_quota_set.pop('id')
-        self.addCleanup(self.adm_client.update_quota_set,
-                        self.demo_tenant_id, **default_quota_set)
-        for quota in new_quota_set:
-            self.assertIn(quota, quota_set.keys())
-
-    # TODO(afazekas): merge these test cases
-    @decorators.idempotent_id('ce9e0815-8091-4abd-8345-7fe5b85faa1d')
-    def test_get_updated_quotas(self):
+    def _get_updated_quotas(self):
         # Verify that GET shows the updated quota set of project
         project_name = data_utils.rand_name('cpu_quota_project')
         project_desc = project_name + '-desc'
@@ -101,8 +49,11 @@
                         project_id)
 
         self.adm_client.update_quota_set(project_id, ram='5120')
-        quota_set = self.adm_client.show_quota_set(project_id)['quota_set']
-        self.assertEqual(5120, quota_set['ram'])
+        # Call show_quota_set with detail=true to cover the
+        # get_quota_set_details response schema for microversion tests
+        quota_set = self.adm_client.show_quota_set(
+            project_id, detail=True)['quota_set']
+        self.assertEqual(5120, quota_set['ram']['limit'])
 
         # Verify that GET shows the updated quota set of user
         user_name = data_utils.rand_name('cpu_quota_user')
@@ -122,6 +73,75 @@
             project_id, user_id=user_id)['quota_set']
         self.assertEqual(2048, quota_set['ram'])
 
+    @classmethod
+    def resource_setup(cls):
+        super(QuotasAdminTestBase, cls).resource_setup()
+
+        # NOTE(afazekas): these test cases should always create and use a new
+        # tenant most of them should be skipped if we can't do that
+        cls.demo_tenant_id = cls.quotas_client.tenant_id
+
+        cls.default_quota_set = set(('metadata_items', 'ram', 'key_pairs',
+                                     'instances', 'cores',
+                                     'server_group_members', 'server_groups'))
+        if cls.is_requested_microversion_compatible('2.35'):
+            cls.default_quota_set = \
+                cls.default_quota_set | set(['fixed_ips', 'floating_ips',
+                                             'security_group_rules',
+                                             'security_groups'])
+        if cls.is_requested_microversion_compatible('2.56'):
+            cls.default_quota_set = \
+                cls.default_quota_set | set(['injected_file_content_bytes',
+                                             'injected_file_path_bytes',
+                                             'injected_files'])
+
+
+class QuotasAdminTestJSON(QuotasAdminTestBase):
+    @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
+        expected_quota_set = self.default_quota_set | set(['id'])
+        quota_set = self.adm_client.show_default_quota_set(
+            self.demo_tenant_id)['quota_set']
+        self.assertEqual(quota_set['id'], self.demo_tenant_id)
+        for quota in expected_quota_set:
+            self.assertIn(quota, quota_set.keys())
+
+    @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
+        default_quota_set = self.adm_client.show_default_quota_set(
+            self.demo_tenant_id)['quota_set']
+        new_quota_set = {'metadata_items': 256, 'ram': 10240,
+                         'key_pairs': 200, 'instances': 20,
+                         'server_groups': 20,
+                         'server_group_members': 20, 'cores': 2}
+        if self.is_requested_microversion_compatible('2.35'):
+            new_quota_set.update({'fixed_ips': 10, 'floating_ips': 20,
+                                  'security_group_rules': 20,
+                                  'security_groups': 20})
+        if self.is_requested_microversion_compatible('2.56'):
+            new_quota_set.update({'injected_file_content_bytes': 20480,
+                                  'injected_file_path_bytes': 512,
+                                  'injected_files': 10})
+
+        # Update limits for all quota resources
+        quota_set = self.adm_client.update_quota_set(
+            self.demo_tenant_id,
+            force=True,
+            **new_quota_set)['quota_set']
+
+        default_quota_set.pop('id')
+        self.addCleanup(self.adm_client.update_quota_set,
+                        self.demo_tenant_id, **default_quota_set)
+        for quota in new_quota_set:
+            self.assertIn(quota, quota_set.keys())
+
+    # TODO(afazekas): merge these test cases
+    @decorators.idempotent_id('ce9e0815-8091-4abd-8345-7fe5b85faa1d')
+    def test_get_updated_quotas(self):
+        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
@@ -144,6 +164,30 @@
         self.assertEqual(ram_default, quota_set_new['ram'])
 
 
+class QuotasAdminTestV236(QuotasAdminTestBase):
+    min_microversion = '2.36'
+    # NOTE(gmann): This test tests the Quota APIs response schema
+    # for 2.36 microversion. No specific assert or behaviour verification
+    # is needed.
+
+    @decorators.idempotent_id('4268b5c9-92e5-4adc-acf1-3a2798f3d803')
+    def test_get_updated_quotas(self):
+        # Checking Quota update, get, get details APIs response schema
+        self._get_updated_quotas()
+
+
+class QuotasAdminTestV257(QuotasAdminTestBase):
+    min_microversion = '2.57'
+    # NOTE(gmann): This test tests the Quota APIs response schema
+    # for 2.57 microversion. No specific assert or behaviour verification
+    # is needed.
+
+    @decorators.idempotent_id('e641e6c6-e86c-41a4-9e5c-9493c0ae47ad')
+    def test_get_updated_quotas(self):
+        # Checking Quota update, get, get details APIs response schema
+        self._get_updated_quotas()
+
+
 class QuotaClassesAdminTestJSON(base.BaseV2ComputeAdminTest):
     """Tests the os-quota-class-sets API to update default quotas."""
 
@@ -168,7 +212,7 @@
     # 'danger' flag.
     @decorators.idempotent_id('7932ab0f-5136-4075-b201-c0e2338df51a')
     def test_update_default_quotas(self):
-        LOG.debug("get the current 'default' quota class values")
+        # get the current 'default' quota class values
         body = (self.adm_client.show_quota_class_set('default')
                 ['quota_class_set'])
         self.assertEqual('default', body.pop('id'))
@@ -180,9 +224,14 @@
             # there is a real chance that we go from -1 (unlimited)
             # to a very small number which causes issues.
             body[quota] = default + 100
-        LOG.debug("update limits for the default quota class set")
+        # update limits for the default quota class set
         update_body = self.adm_client.update_quota_class_set(
             'default', **body)['quota_class_set']
-        LOG.debug("assert that the response has all of the changed values")
+        # assert that the response has all of the changed values
         self.assertThat(update_body.items(),
                         matchers.ContainsAll(body.items()))
+        # check quota values are changed
+        show_body = self.adm_client.show_quota_class_set(
+            'default')['quota_class_set']
+        self.assertThat(show_body.items(),
+                        matchers.ContainsAll(body.items()))
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index 5ef7ee4..f90ff92 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -22,12 +22,12 @@
 CONF = config.CONF
 
 
-class QuotasAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class QuotasAdminNegativeTestBase(base.BaseV2ComputeAdminTest):
     force_tenant_isolation = True
 
     @classmethod
     def setup_clients(cls):
-        super(QuotasAdminNegativeTestJSON, cls).setup_clients()
+        super(QuotasAdminNegativeTestBase, cls).setup_clients()
         cls.client = cls.os_primary.quotas_client
         cls.adm_client = cls.os_admin.quotas_client
         cls.sg_client = cls.security_groups_client
@@ -35,7 +35,7 @@
 
     @classmethod
     def resource_setup(cls):
-        super(QuotasAdminNegativeTestJSON, cls).resource_setup()
+        super(QuotasAdminNegativeTestBase, cls).resource_setup()
         # NOTE(afazekas): these test cases should always create and use a new
         # tenant most of them should be skipped if we can't do that
         cls.demo_tenant_id = cls.client.tenant_id
@@ -51,6 +51,9 @@
         self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
                         **{quota_item: default_quota_value})
 
+
+class QuotasAdminNegativeTest(QuotasAdminNegativeTestBase):
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('733abfe8-166e-47bb-8363-23dbd7ff3476')
     def test_update_quota_normal_user(self):
@@ -85,6 +88,10 @@
         self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
                           self.create_test_server)
 
+
+class QuotasSecurityGroupAdminNegativeTest(QuotasAdminNegativeTestBase):
+    max_microversion = '2.35'
+
     @decorators.skip_because(bug="1186354",
                              condition=CONF.service_available.neutron)
     @decorators.attr(type=['negative'])
diff --git a/tempest/api/compute/admin/test_security_group_default_rules.py b/tempest/api/compute/admin/test_security_group_default_rules.py
index f2f3b57..bca6a22 100644
--- a/tempest/api/compute/admin/test_security_group_default_rules.py
+++ b/tempest/api/compute/admin/test_security_group_default_rules.py
@@ -23,6 +23,7 @@
 
 
 class SecurityGroupDefaultRulesTest(base.BaseV2ComputeAdminTest):
+    max_microversion = '2.35'
 
     @classmethod
     # TODO(GMann): Once Bug# 1311500 is fixed, these test can run
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
index ff9caa3..dfa801b 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -20,6 +20,7 @@
 
 
 class SecurityGroupsTestAdminJSON(base.BaseV2ComputeAdminTest):
+    max_microversion = '2.35'
 
     @classmethod
     def setup_clients(cls):
@@ -72,8 +73,17 @@
         # search filter
         fetched_list = (self.client.list_security_groups(all_tenants='true')
                         ['security_groups'])
-        # Now check if all created Security Groups are present in fetched list
-        for sec_group in fetched_list:
-            self.assertEqual(sec_group['tenant_id'], client_tenant_id,
-                             "Failed to get all security groups for "
-                             "non admin user.")
+        sec_group_id_list = [sg['id'] for sg in fetched_list]
+        # Now check that 'all_tenants='true' filter for non-admin user only
+        # provide the requested non-admin user's created security groups,
+        # not all security groups which include security groups created by
+        # other users.
+        for sec_group in security_group_list:
+            if sec_group['tenant_id'] == client_tenant_id:
+                self.assertIn(sec_group['id'], sec_group_id_list,
+                              "Failed to get all security groups for "
+                              "non admin user.")
+            else:
+                self.assertNotIn(sec_group['id'], sec_group_id_list,
+                                 "Non admin user shouldn't get other user's "
+                                 "security groups.")
diff --git a/tempest/api/compute/admin/test_server_diagnostics_negative.py b/tempest/api/compute/admin/test_server_diagnostics_negative.py
index d5b6674..6215c37 100644
--- a/tempest/api/compute/admin/test_server_diagnostics_negative.py
+++ b/tempest/api/compute/admin/test_server_diagnostics_negative.py
@@ -18,8 +18,6 @@
 
 
 class ServerDiagnosticsNegativeTest(base.BaseV2ComputeAdminTest):
-    min_microversion = None
-    max_microversion = '2.47'
 
     @classmethod
     def setup_clients(cls):
@@ -33,8 +31,3 @@
         server_id = self.create_test_server(wait_until='ACTIVE')['id']
         self.assertRaises(lib_exc.Forbidden,
                           self.client.show_server_diagnostics, server_id)
-
-
-class ServerDiagnosticsNegativeV248Test(ServerDiagnosticsNegativeTest):
-    min_microversion = '2.48'
-    max_microversion = 'latest'
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 3f06c4e..170b2cc 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -16,6 +16,7 @@
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
 
 
 class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -61,9 +62,13 @@
     @decorators.idempotent_id('d56e9540-73ed-45e0-9b88-98fc419087eb')
     def test_list_servers_detailed_filter_by_invalid_status(self):
         params = {'status': 'invalid_status'}
-        body = self.client.list_servers(detail=True, **params)
-        servers = body['servers']
-        self.assertEmpty(servers)
+        if self.is_requested_microversion_compatible('2.37'):
+            body = self.client.list_servers(detail=True, **params)
+            servers = body['servers']
+            self.assertEmpty(servers)
+        else:
+            self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
+                              detail=True, **params)
 
     @decorators.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
     def test_list_servers_by_admin(self):
@@ -171,7 +176,7 @@
         self.assertEqual(self.s1_id, rebuilt_server['id'])
         rebuilt_image_id = rebuilt_server['image']['id']
         self.assertEqual(self.image_ref_alt, rebuilt_image_id)
-        self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+        self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
         waiters.wait_for_server_status(self.non_admin_client,
                                        rebuilt_server['id'], 'ACTIVE',
                                        raise_on_error=False)
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index 2e7b07b..bebc8c5 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -28,7 +28,7 @@
     def resource_setup(cls):
         super(ServersOnMultiNodesTest, cls).resource_setup()
         cls.server01 = cls.create_test_server(wait_until='ACTIVE')['id']
-        cls.host01 = cls._get_host(cls.server01)
+        cls.host01 = cls.get_host_for_server(cls.server01)
 
     @classmethod
     def skip_checks(cls):
@@ -38,57 +38,8 @@
             raise cls.skipException(
                 "Less than 2 compute nodes, skipping multi-nodes test.")
 
-    @classmethod
-    def _get_host(cls, server_id):
-        return cls.os_admin.servers_client.show_server(
-            server_id)['server']['OS-EXT-SRV-ATTR:host']
-
-    @decorators.idempotent_id('26a9d5df-6890-45f2-abc4-a659290cb130')
-    @testtools.skipUnless(
-        compute.is_scheduler_filter_enabled("SameHostFilter"),
-        'SameHostFilter is not available.')
-    def test_create_servers_on_same_host(self):
-        hints = {'same_host': self.server01}
-        server02 = self.create_test_server(scheduler_hints=hints,
-                                           wait_until='ACTIVE')['id']
-        host02 = self._get_host(server02)
-        self.assertEqual(self.host01, host02)
-
-    @decorators.idempotent_id('cc7ca884-6e3e-42a3-a92f-c522fcf25e8e')
-    @testtools.skipUnless(
-        compute.is_scheduler_filter_enabled("DifferentHostFilter"),
-        'DifferentHostFilter is not available.')
-    def test_create_servers_on_different_hosts(self):
-        hints = {'different_host': self.server01}
-        server02 = self.create_test_server(scheduler_hints=hints,
-                                           wait_until='ACTIVE')['id']
-        host02 = self._get_host(server02)
-        self.assertNotEqual(self.host01, host02)
-
-    @decorators.idempotent_id('7869cc84-d661-4e14-9f00-c18cdc89cf57')
-    @testtools.skipUnless(
-        compute.is_scheduler_filter_enabled("DifferentHostFilter"),
-        'DifferentHostFilter is not available.')
-    def test_create_servers_on_different_hosts_with_list_of_servers(self):
-        # This scheduler-hint supports list of servers also.
-        hints = {'different_host': [self.server01]}
-        server02 = self.create_test_server(scheduler_hints=hints,
-                                           wait_until='ACTIVE')['id']
-        host02 = self._get_host(server02)
-        self.assertNotEqual(self.host01, host02)
-
-    @decorators.idempotent_id('f8bd0867-e459-45f5-ba53-59134552fe04')
-    @testtools.skipUnless(
-        compute.is_scheduler_filter_enabled("ServerGroupAntiAffinityFilter"),
-        'ServerGroupAntiAffinityFilter is not available.')
-    def test_create_server_with_scheduler_hint_group_anti_affinity(self):
-        """Tests the ServerGroupAntiAffinityFilter
-
-        Creates two servers in an anti-affinity server group and
-        asserts the servers are in the group and on different hosts.
-        """
-        group_id = self.create_test_server_group(
-            policy=['anti-affinity'])['id']
+    def _create_servers_with_group(self, policy):
+        group_id = self.create_test_server_group(policy=[policy])['id']
         hints = {'group': group_id}
         reservation_id = self.create_test_server(
             scheduler_hints=hints, wait_until='ACTIVE', min_count=2,
@@ -105,9 +56,70 @@
         hosts = {}
         for server in servers:
             self.assertIn(server['id'], server_group['members'])
-            hosts[server['id']] = self._get_host(server['id'])
+            hosts[server['id']] = self.get_host_for_server(server['id'])
 
-        # Assert the servers are on different hosts.
+        return hosts
+
+    @decorators.idempotent_id('26a9d5df-6890-45f2-abc4-a659290cb130')
+    @testtools.skipUnless(
+        compute.is_scheduler_filter_enabled("SameHostFilter"),
+        'SameHostFilter is not available.')
+    def test_create_servers_on_same_host(self):
+        hints = {'same_host': self.server01}
+        server02 = self.create_test_server(scheduler_hints=hints,
+                                           wait_until='ACTIVE')['id']
+        host02 = self.get_host_for_server(server02)
+        self.assertEqual(self.host01, host02)
+
+    @decorators.idempotent_id('cc7ca884-6e3e-42a3-a92f-c522fcf25e8e')
+    @testtools.skipUnless(
+        compute.is_scheduler_filter_enabled("DifferentHostFilter"),
+        'DifferentHostFilter is not available.')
+    def test_create_servers_on_different_hosts(self):
+        hints = {'different_host': self.server01}
+        server02 = self.create_test_server(scheduler_hints=hints,
+                                           wait_until='ACTIVE')['id']
+        host02 = self.get_host_for_server(server02)
+        self.assertNotEqual(self.host01, host02)
+
+    @decorators.idempotent_id('7869cc84-d661-4e14-9f00-c18cdc89cf57')
+    @testtools.skipUnless(
+        compute.is_scheduler_filter_enabled("DifferentHostFilter"),
+        'DifferentHostFilter is not available.')
+    def test_create_servers_on_different_hosts_with_list_of_servers(self):
+        # This scheduler-hint supports list of servers also.
+        hints = {'different_host': [self.server01]}
+        server02 = self.create_test_server(scheduler_hints=hints,
+                                           wait_until='ACTIVE')['id']
+        host02 = self.get_host_for_server(server02)
+        self.assertNotEqual(self.host01, host02)
+
+    @decorators.idempotent_id('f8bd0867-e459-45f5-ba53-59134552fe04')
+    @testtools.skipUnless(
+        compute.is_scheduler_filter_enabled("ServerGroupAntiAffinityFilter"),
+        'ServerGroupAntiAffinityFilter is not available.')
+    def test_create_server_with_scheduler_hint_group_anti_affinity(self):
+        """Tests the ServerGroupAntiAffinityFilter
+
+        Creates two servers in an anti-affinity server group and
+        asserts the servers are in the group and on different hosts.
+        """
+        hosts = self._create_servers_with_group('anti-affinity')
         hostnames = list(hosts.values())
         self.assertNotEqual(hostnames[0], hostnames[1],
                             'Servers are on the same host: %s' % hosts)
+
+    @decorators.idempotent_id('9d2e924a-baf4-11e7-b856-fa163e65f5ce')
+    @testtools.skipUnless(
+        compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"),
+        'ServerGroupAffinityFilter is not available.')
+    def test_create_server_with_scheduler_hint_group_affinity(self):
+        """Tests the ServerGroupAffinityFilter
+
+        Creates two servers in an affinity server group and
+        asserts the servers are in the group and on same host.
+        """
+        hosts = self._create_servers_with_group('affinity')
+        hostnames = list(hosts.values())
+        self.assertEqual(hostnames[0], hostnames[1],
+                         'Servers are on the different hosts: %s' % hosts)
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index f3eb597..73e191b 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -56,15 +56,3 @@
         # sort the lists before comparing, to take out dependency
         # on order.
         self.assertEqual(sorted(s1), sorted(s2))
-
-    @decorators.idempotent_id('39397f6f-37b8-4234-8671-281e44c74025')
-    def test_get_service_by_service_and_host_name(self):
-        services = self.client.list_services()['services']
-        host_name = services[0]['host']
-        binary_name = services[0]['binary']
-
-        services = self.client.list_services(host=host_name,
-                                             binary=binary_name)['services']
-        self.assertEqual(1, len(services))
-        self.assertEqual(host_name, services[0]['host'])
-        self.assertEqual(binary_name, services[0]['binary'])
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index 201670a..d264829 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -13,12 +13,14 @@
 #    under the License.
 
 from tempest.api.compute import base
+from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
 
 class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
     """Tests Services API. List and Enable/Disable require admin privileges."""
+    max_microversion = '2.52'
 
     @classmethod
     def setup_clients(cls):
@@ -35,7 +37,8 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('d0884a69-f693-4e79-a9af-232d15643bf7')
     def test_get_service_by_invalid_params(self):
-        # return all services if send the request with invalid parameter
+        # Expect all services to be returned when the request contains invalid
+        # parameters.
         services = self.client.list_services()['services']
         services_xxx = (self.client.list_services(xxx='nova-compute')
                         ['services'])
@@ -58,3 +61,43 @@
         services = self.client.list_services(host='xxx',
                                              binary=binary_name)['services']
         self.assertEmpty(services)
+
+
+class ServicesAdminNegativeV253TestJSON(ServicesAdminNegativeTestJSON):
+    min_microversion = '2.53'
+    max_microversion = 'latest'
+
+    # NOTE(felipemonteiro): This class tests the services APIs response schema
+    # for the 2.53 microversion. Schema testing is done for `list_services`
+    # tests.
+
+    @classmethod
+    def resource_setup(cls):
+        super(ServicesAdminNegativeV253TestJSON, cls).resource_setup()
+        cls.fake_service_id = data_utils.rand_uuid()
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('508671aa-c929-4479-bd10-8680d40dd0a6')
+    def test_enable_service_with_invalid_service_id(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.update_service,
+                          service_id=self.fake_service_id,
+                          status='enabled')
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('a9eeeade-42b3-419f-87aa-c9342aa068cf')
+    def test_disable_service_with_invalid_service_id(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.update_service,
+                          service_id=self.fake_service_id,
+                          status='disabled')
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('f46a9d91-1e85-4b96-8e7a-db7706fa2e9a')
+    def test_disable_log_reason_with_invalid_service_id(self):
+        # disabled_reason requires that status='disabled' be provided.
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.update_service,
+                          service_id=self.fake_service_id,
+                          status='disabled',
+                          disabled_reason='maintenance')
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index d715a42..371b506 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -22,30 +22,16 @@
 CONF = config.CONF
 
 
-class TestVolumeSwap(base.BaseV2ComputeAdminTest):
-    """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.
-    """
+class TestVolumeSwapBase(base.BaseV2ComputeAdminTest):
 
     @classmethod
     def skip_checks(cls):
-        super(TestVolumeSwap, cls).skip_checks()
+        super(TestVolumeSwapBase, cls).skip_checks()
         if not CONF.compute_feature_enabled.swap_volume:
             raise cls.skipException("Swapping volumes is not supported.")
 
-    def _wait_for_server_volume_swap(self, server_id, old_volume_id,
-                                     new_volume_id):
+    def wait_for_server_volume_swap(self, server_id, old_volume_id,
+                                    new_volume_id):
         """Waits for a server to swap the old volume to a new one."""
         volume_attachments = self.servers_client.list_volume_attachments(
             server_id)['volumeAttachments']
@@ -79,6 +65,29 @@
                             'timeout': self.servers_client.build_timeout})
                 raise lib_exc.TimeoutException(message)
 
+
+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.
+    """
+
+    # NOTE(mriedem): This is an uncommon scenario to call the compute API
+    # to swap volumes directly; swap volume is primarily only for volume
+    # live migration and retype callbacks from the volume service, and is slow
+    # so it's marked as such.
+    @decorators.attr(type='slow')
     @decorators.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813')
     @utils.services('volume')
     def test_volume_swap(self):
@@ -99,8 +108,8 @@
                                                 volume1['id'], 'available')
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume2['id'], 'in-use')
-        self._wait_for_server_volume_swap(server['id'], volume1['id'],
-                                          volume2['id'])
+        self.wait_for_server_volume_swap(server['id'], volume1['id'],
+                                         volume2['id'])
         # Verify "volume2" is attached to the server
         vol_attachments = self.servers_client.list_volume_attachments(
             server['id'])['volumeAttachments']
@@ -114,10 +123,95 @@
                                                 volume2['id'], 'available')
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume1['id'], 'in-use')
-        self._wait_for_server_volume_swap(server['id'], volume2['id'],
-                                          volume1['id'])
+        self.wait_for_server_volume_swap(server['id'], volume2['id'],
+                                         volume1['id'])
         # Verify "volume1" is attached to the server
         vol_attachments = self.servers_client.list_volume_attachments(
             server['id'])['volumeAttachments']
         self.assertEqual(1, len(vol_attachments))
         self.assertIn(volume1['id'], vol_attachments[0]['volumeId'])
+
+
+class TestMultiAttachVolumeSwap(TestVolumeSwapBase):
+    min_microversion = '2.60'
+    max_microversion = 'latest'
+
+    @classmethod
+    def skip_checks(cls):
+        super(TestMultiAttachVolumeSwap, cls).skip_checks()
+        if not CONF.compute_feature_enabled.volume_multiattach:
+            raise cls.skipException('Volume multi-attach is not available.')
+
+    @classmethod
+    def setup_clients(cls):
+        super(TestMultiAttachVolumeSwap, cls).setup_clients()
+        # Need this to set readonly volumes.
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
+
+    # NOTE(mriedem): This is an uncommon scenario to call the compute API
+    # to swap volumes directly; swap volume is primarily only for volume
+    # live migration and retype callbacks from the volume service, and is slow
+    # so it's marked as such.
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('e8f8f9d1-d7b7-4cd2-8213-ab85ef697b6e')
+    # For some reason this test intermittently fails on teardown when there are
+    # multiple compute nodes and the servers are split across the computes.
+    # For now, just skip this test if there are multiple computes.
+    # Alternatively we could put the servers in an affinity group if there are
+    # multiple computes but that would just side-step the underlying bug.
+    @decorators.skip_because(bug='1807723',
+                             condition=CONF.compute.min_compute_nodes > 1)
+    @utils.services('volume')
+    def test_volume_swap_with_multiattach(self):
+        # Create two volumes.
+        # NOTE(gmann): Volumes are created before server creation so that
+        # volumes cleanup can happen successfully irrespective of which volume
+        # is attached to server.
+        volume1 = self.create_volume(multiattach=True)
+        # Make volume1 read-only since you can't swap from a volume with
+        # multiple read/write attachments, and you can't change the readonly
+        # flag on an in-use volume so we have to do this before attaching
+        # volume1 to anything. If the compute API ever supports per-attachment
+        # attach modes, then we can handle this differently.
+        self.admin_volumes_client.update_volume_readonly(
+            volume1['id'], readonly=True)
+        volume2 = self.create_volume(multiattach=True)
+
+        # Create two servers and wait for them to be ACTIVE.
+        reservation_id = self.create_test_server(
+            wait_until='ACTIVE', min_count=2,
+            return_reservation_id=True)['reservation_id']
+        # Get the servers using the reservation_id.
+        servers = self.servers_client.list_servers(
+            reservation_id=reservation_id)['servers']
+        self.assertEqual(2, len(servers))
+        # Attach volume1 to server1
+        server1 = servers[0]
+        self.attach_volume(server1, volume1)
+        # Attach volume1 to server2
+        server2 = servers[1]
+        self.attach_volume(server2, volume1)
+
+        # Swap volume1 to volume2 on server1, volume1 should remain attached
+        # to server 2
+        self.admin_servers_client.update_attached_volume(
+            server1['id'], volume1['id'], volumeId=volume2['id'])
+        # volume1 will return to in-use after the swap
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume1['id'], 'in-use')
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume2['id'], 'in-use')
+        self.wait_for_server_volume_swap(server1['id'], volume1['id'],
+                                         volume2['id'])
+
+        # Verify volume2 is attached to server1
+        vol_attachments = self.servers_client.list_volume_attachments(
+            server1['id'])['volumeAttachments']
+        self.assertEqual(1, len(vol_attachments))
+        self.assertIn(volume2['id'], vol_attachments[0]['volumeId'])
+
+        # Verify volume1 is still attached to server2
+        vol_attachments = self.servers_client.list_volume_attachments(
+            server2['id'])['volumeAttachments']
+        self.assertEqual(1, len(vol_attachments))
+        self.assertIn(volume1['id'], vol_attachments[0]['volumeId'])
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 705814c..e71e642 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -17,11 +17,11 @@
 
 from oslo_log import log as logging
 
-from tempest.api.compute import api_microversion_fixture
 from tempest.common import compute
 from tempest.common import waiters
 from tempest import config
 from tempest import exceptions
+from tempest.lib.common import api_microversion_fixture
 from tempest.lib.common import api_version_request
 from tempest.lib.common import api_version_utils
 from tempest.lib.common.utils import data_utils
@@ -99,6 +99,44 @@
         cls.versions_client = cls.os_primary.compute_versions_client
         if CONF.service_available.cinder:
             cls.volumes_client = cls.os_primary.volumes_client_latest
+        if CONF.service_available.glance:
+            if CONF.image_feature_enabled.api_v1:
+                cls.images_client = cls.os_primary.image_client
+            elif CONF.image_feature_enabled.api_v2:
+                cls.images_client = cls.os_primary.image_client_v2
+            else:
+                raise lib_exc.InvalidConfiguration(
+                    'Either api_v1 or api_v2 must be True in '
+                    '[image-feature-enabled].')
+        cls._check_depends_on_nova_network()
+
+    @classmethod
+    def _check_depends_on_nova_network(cls):
+        # Since nova-network APIs were removed from Nova in the Rocky release,
+        # determine, based on the max version from the version document, if
+        # the compute API is >Queens and if so, skip tests that rely on
+        # nova-network.
+        if not getattr(cls, 'depends_on_nova_network', False):
+            return
+        versions = cls.versions_client.list_versions()['versions']
+        # Find the v2.1 version which will tell us our max version for the
+        # compute API we're testing against.
+        for version in versions:
+            if version['id'] == 'v2.1':
+                max_version = api_version_request.APIVersionRequest(
+                    version['version'])
+                break
+        else:
+            LOG.warning(
+                'Unable to determine max v2.1 compute API version: %s',
+                versions)
+            return
+
+        # The max compute API version in Queens is 2.60 so we cap
+        # at that version.
+        queens = api_version_request.APIVersionRequest('2.60')
+        if max_version > queens:
+            raise cls.skipException('nova-network is gone')
 
     @classmethod
     def resource_setup(cls):
@@ -118,6 +156,36 @@
         cls.image_ssh_password = CONF.validation.image_ssh_password
 
     @classmethod
+    def is_requested_microversion_compatible(cls, max_version):
+        """Check the compatibility of selected request microversion
+
+        This method will check if selected request microversion
+        (cls.request_microversion) for test is compatible with respect
+        to 'max_version'. Compatible means if selected request microversion
+        is in the range(<=) of 'max_version'.
+
+        :param max_version: maximum microversion to compare for compatibility.
+            Example: '2.30'
+        :returns: True if selected request microversion is compatible with
+            'max_version'. False in other case.
+        """
+        try:
+            req_version_obj = api_version_request.APIVersionRequest(
+                cls.request_microversion)
+        # NOTE(gmann): This is case where this method is used before calling
+        # resource_setup(), where cls.request_microversion is set. There may
+        # not be any such case but still we can handle this case.
+        except AttributeError:
+            request_microversion = (
+                api_version_utils.select_request_microversion(
+                    cls.min_microversion,
+                    CONF.compute.min_microversion))
+            req_version_obj = api_version_request.APIVersionRequest(
+                request_microversion)
+        max_version_obj = api_version_request.APIVersionRequest(max_version)
+        return req_version_obj <= max_version_obj
+
+    @classmethod
     def server_check_teardown(cls):
         """Checks is the shared server clean enough for subsequent test.
 
@@ -140,19 +208,6 @@
                 raise
 
     @classmethod
-    def clear_resources(cls, resource_name, resources, resource_del_func):
-        LOG.debug('Clearing %s: %s', resource_name,
-                  ','.join(map(str, resources)))
-        for res_id in resources:
-            try:
-                test_utils.call_and_ignore_notfound_exc(
-                    resource_del_func, res_id)
-            except Exception as exc:
-                LOG.exception('Exception raised deleting %s: %s',
-                              resource_name, res_id)
-                LOG.exception(exc)
-
-    @classmethod
     def create_test_server(cls, validatable=False, volume_backed=False,
                            validation_resources=None, **kwargs):
         """Wrapper utility that returns a test server.
@@ -176,11 +231,12 @@
             cls.request_microversion)
         v2_37_version = api_version_request.APIVersionRequest('2.37')
 
+        tenant_network = cls.get_tenant_network()
         # NOTE(snikitin): since microversion v2.37 'networks' field is required
-        if request_version >= v2_37_version and 'networks' not in kwargs:
+        if (request_version >= v2_37_version and 'networks' not in kwargs and
+            not tenant_network):
             kwargs['networks'] = 'none'
 
-        tenant_network = cls.get_tenant_network()
         body, servers = compute.create_test_server(
             cls.os_primary,
             validatable,
@@ -254,7 +310,11 @@
 
     @classmethod
     def create_image_from_server(cls, server_id, **kwargs):
-        """Wrapper utility that returns an image created from the server."""
+        """Wrapper utility that returns an image created from the server.
+
+        If compute microversion >= 2.36, the returned image response will
+        be from the image service API rather than the compute image proxy API.
+        """
         name = kwargs.pop('name',
                           data_utils.rand_name(cls.__name__ + "-image"))
         wait_until = kwargs.pop('wait_until', None)
@@ -267,14 +327,23 @@
             image_id = image['image_id']
         else:
             image_id = data_utils.parse_image_id(image.response['location'])
+
+        # The compute image proxy APIs were deprecated in 2.35 so
+        # use the images client directly if the API microversion being
+        # used is >=2.36.
+        if not cls.is_requested_microversion_compatible('2.35'):
+            client = cls.images_client
+        else:
+            client = cls.compute_images_client
         cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
-                                    cls.compute_images_client.delete_image,
-                                    image_id)
+                                    client.delete_image, image_id)
 
         if wait_until is not None:
             try:
-                waiters.wait_for_image_status(cls.compute_images_client,
-                                              image_id, wait_until)
+                wait_until = wait_until.upper()
+                if not cls.is_requested_microversion_compatible('2.35'):
+                    wait_until = wait_until.lower()
+                waiters.wait_for_image_status(client, image_id, wait_until)
             except lib_exc.NotFound:
                 if wait_until.upper() == 'ACTIVE':
                     # If the image is not found after create_image returned
@@ -292,7 +361,11 @@
                             image_id=image_id)
                 else:
                     raise
-            image = cls.compute_images_client.show_image(image_id)['image']
+            image = client.show_image(image_id)
+            # Compute image client returns response wrapped in 'image' element
+            # which is not the case with Glance image client.
+            if 'image' in image:
+                image = image['image']
 
             if wait_until.upper() == 'ACTIVE':
                 if wait_for_server:
@@ -344,14 +417,16 @@
         except Exception:
             LOG.exception('Failed to delete server %s', server_id)
 
-    @classmethod
-    def resize_server(cls, server_id, new_flavor_id, **kwargs):
+    def resize_server(self, server_id, new_flavor_id, **kwargs):
         """resize and confirm_resize an server, waits for it to be ACTIVE."""
-        cls.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
-        waiters.wait_for_server_status(cls.servers_client, server_id,
+        self.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
+        waiters.wait_for_server_status(self.servers_client, server_id,
                                        'VERIFY_RESIZE')
-        cls.servers_client.confirm_resize_server(server_id)
-        waiters.wait_for_server_status(cls.servers_client, server_id, 'ACTIVE')
+        self.servers_client.confirm_resize_server(server_id)
+        waiters.wait_for_server_status(
+            self.servers_client, server_id, 'ACTIVE')
+        server = self.servers_client.show_server(server_id)['server']
+        self.assert_flavor_equal(new_flavor_id, server['flavor'])
 
     @classmethod
     def delete_volume(cls, volume_id):
@@ -382,7 +457,7 @@
             else:
                 msg = ('When validation.connect_method equals floating, '
                        'validation_resources cannot be None')
-                raise exceptions.InvalidParam(invalid_param=msg)
+                raise lib_exc.InvalidParam(invalid_param=msg)
         elif CONF.validation.connect_method == 'fixed':
             addresses = server['addresses'][CONF.validation.network_for_ssh]
             for address in addresses:
@@ -395,14 +470,14 @@
     def setUp(self):
         super(BaseV2ComputeTest, self).setUp()
         self.useFixture(api_microversion_fixture.APIMicroversionFixture(
-            self.request_microversion))
+            compute_microversion=self.request_microversion))
 
     @classmethod
     def create_volume(cls, image_ref=None, **kwargs):
         """Create a volume and wait for it to become 'available'.
 
         :param image_ref: Specify an image id to create a bootable volume.
-        :**kwargs: other parameters to create volume.
+        :param kwargs: other parameters to create volume.
         :returns: The available volume.
         """
         if 'size' not in kwargs:
@@ -434,12 +509,12 @@
             # the compute API will return a 400 response.
             if volume['status'] == 'in-use':
                 self.servers_client.detach_volume(server['id'], volume['id'])
-        except exceptions.NotFound:
+        except lib_exc.NotFound:
             # Ignore 404s on detach in case the server is deleted or the volume
             # is already detached.
             pass
 
-    def attach_volume(self, server, volume, device=None, check_reserved=False):
+    def attach_volume(self, server, volume, device=None, tag=None):
         """Attaches volume to server and waits for 'in-use' volume status.
 
         The volume will be detached when the test tears down.
@@ -448,14 +523,13 @@
         :param volume: The volume to attach.
         :param device: Optional mountpoint for the attached volume. Note that
             this is not guaranteed for all hypervisors and is not recommended.
-        :param check_reserved: Consider a status of reserved as valid for
-            completion. This is to handle new Cinder attach where we more
-            accurately use 'reserved' for things like attaching to a shelved
-            server.
+        :param tag: Optional device role tag to apply to the volume.
         """
         attach_kwargs = dict(volumeId=volume['id'])
         if device:
             attach_kwargs['device'] = device
+        if tag:
+            attach_kwargs['tag'] = tag
 
         attachment = self.servers_client.attach_volume(
             server['id'], **attach_kwargs)['volumeAttachment']
@@ -467,13 +541,31 @@
         # Ignore 404s on detach in case the server is deleted or the volume
         # is already detached.
         self.addCleanup(self._detach_volume, server, volume)
-        statuses = ['in-use']
-        if check_reserved:
-            statuses.append('reserved')
         waiters.wait_for_volume_resource_status(self.volumes_client,
-                                                volume['id'], statuses)
+                                                volume['id'], 'in-use')
         return attachment
 
+    def assert_flavor_equal(self, flavor_id, server_flavor):
+        """Check whether server_flavor equals to flavor.
+
+        :param flavor_id: flavor id
+        :param server_flavor: flavor info returned by show_server.
+        """
+        # Nova API > 2.46 no longer includes flavor.id, and schema check
+        # will cover whether 'id' should be in flavor
+        if server_flavor.get('id'):
+            msg = ('server flavor is not same as flavor!')
+            self.assertEqual(flavor_id, server_flavor['id'], msg)
+        else:
+            flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
+            self.assertEqual(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(flavor[key], server_flavor[key], msg)
+
 
 class BaseV2ComputeAdminTest(BaseV2ComputeTest):
     """Base test case class for Compute Admin API tests."""
@@ -501,17 +593,18 @@
         self.addCleanup(client.delete_flavor, flavor['id'])
         return flavor
 
-    def get_host_for_server(self, server_id):
-        server_details = self.admin_servers_client.show_server(server_id)
+    @classmethod
+    def get_host_for_server(cls, server_id):
+        server_details = cls.admin_servers_client.show_server(server_id)
         return server_details['server']['OS-EXT-SRV-ATTR:host']
 
     def get_host_other_than(self, server_id):
         source_host = self.get_host_for_server(server_id)
 
-        hypers = self.os_admin.hypervisor_client.list_hypervisors(
-            )['hypervisors']
-        hosts = [hyper['hypervisor_hostname'] for hyper in hypers
-                 if hyper['state'] == 'up' and hyper['status'] == 'enabled']
+        svcs = self.os_admin.services_client.list_services(
+            binary='nova-compute')['services']
+        hosts = [svc['host'] for svc in svcs
+                 if svc['state'] == 'up' and svc['status'] == 'enabled']
 
         for target_host in hosts:
             if source_host != target_host:
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index efd4f0e..3a474e6 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -30,18 +30,6 @@
 
 class FlavorsV2NegativeTest(base.BaseV2ComputeTest):
 
-    @classmethod
-    def setup_clients(cls):
-        super(FlavorsV2NegativeTest, cls).setup_clients()
-        if CONF.image_feature_enabled.api_v1:
-            cls.images_client = cls.os_primary.image_client
-        elif CONF.image_feature_enabled.api_v2:
-            cls.images_client = cls.os_primary.image_client_v2
-        else:
-            raise lib_exc.InvalidConfiguration(
-                'Either api_v1 or api_v2 must be True in '
-                '[image-feature-enabled].')
-
     @decorators.attr(type=['negative'])
     @utils.services('image')
     @decorators.idempotent_id('90f0d93a-91c1-450c-91e6-07d18172cefe')
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index b497626..1f3af5f 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -28,6 +28,7 @@
 
 
 class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.38'
 
     @classmethod
     def skip_checks(cls):
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index 03d0789..407fb08 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -20,6 +20,7 @@
 
 
 class ImagesMetadataNegativeTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.38'
 
     @classmethod
     def setup_clients(cls):
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 29bd6da..c8221c2 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -38,7 +38,10 @@
     @classmethod
     def setup_clients(cls):
         super(ImagesTestJSON, cls).setup_clients()
-        cls.client = cls.compute_images_client
+        if cls.is_requested_microversion_compatible('2.35'):
+            cls.client = cls.compute_images_client
+        else:
+            cls.client = cls.images_client
 
     @decorators.idempotent_id('aa06b52b-2db5-4807-b218-9441f75d74e3')
     def test_delete_saving_image(self):
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index e292389..2400348 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -22,11 +22,11 @@
 CONF = config.CONF
 
 
-class ImagesNegativeTestJSON(base.BaseV2ComputeTest):
+class ImagesNegativeTestBase(base.BaseV2ComputeTest):
 
     @classmethod
     def skip_checks(cls):
-        super(ImagesNegativeTestJSON, cls).skip_checks()
+        super(ImagesNegativeTestBase, cls).skip_checks()
         if not CONF.service_available.glance:
             skip_msg = ("%s skipped as glance is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
@@ -37,9 +37,12 @@
 
     @classmethod
     def setup_clients(cls):
-        super(ImagesNegativeTestJSON, cls).setup_clients()
+        super(ImagesNegativeTestBase, cls).setup_clients()
         cls.client = cls.compute_images_client
 
+
+class ImagesNegativeTestJSON(ImagesNegativeTestBase):
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('6cd5a89d-5b47-46a7-93bc-3916f0d84973')
     def test_create_image_from_deleted_server(self):
@@ -82,6 +85,10 @@
         self.assertRaises(lib_exc.NotFound, self.client.create_image,
                           test_uuid, name=snapshot_name)
 
+
+class ImagesDeleteNegativeTestJSON(ImagesNegativeTestBase):
+    max_microversion = '2.35'
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('381acb65-785a-4942-94ce-d8f8c84f1f0f')
     def test_delete_image_with_invalid_image_id(self):
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 058e7e6..3c152c9 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -44,7 +44,10 @@
     @classmethod
     def setup_clients(cls):
         super(ImagesOneServerTestJSON, cls).setup_clients()
-        cls.client = cls.compute_images_client
+        if cls.is_requested_microversion_compatible('2.35'):
+            cls.client = cls.compute_images_client
+        else:
+            cls.client = cls.images_client
 
     def _get_default_flavor_disk_size(self, flavor_id):
         flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
@@ -52,6 +55,13 @@
 
     @decorators.idempotent_id('3731d080-d4c5-4872-b41a-64d0d0021314')
     def test_create_delete_image(self):
+        if self.is_requested_microversion_compatible('2.35'):
+            MIN_DISK = 'minDisk'
+            MIN_RAM = 'minRam'
+        else:
+            MIN_DISK = 'min_disk'
+            MIN_RAM = 'min_ram'
+
         # Create a new image
         name = data_utils.rand_name('image')
         meta = {'image_type': 'test'}
@@ -61,17 +71,22 @@
 
         # Verify the image was created correctly
         self.assertEqual(name, image['name'])
-        self.assertEqual('test', image['metadata']['image_type'])
+        if self.is_requested_microversion_compatible('2.35'):
+            self.assertEqual('test', image['metadata']['image_type'])
+        else:
+            self.assertEqual('test', image['image_type'])
 
-        original_image = self.client.show_image(self.image_ref)['image']
+        original_image = self.client.show_image(self.image_ref)
+        if self.is_requested_microversion_compatible('2.35'):
+            original_image = original_image['image']
 
         # Verify minRAM is the same as the original image
-        self.assertEqual(image['minRam'], original_image['minRam'])
+        self.assertEqual(image[MIN_RAM], original_image[MIN_RAM])
 
         # Verify minDisk is the same as the original image or the flavor size
         flavor_disk_size = self._get_default_flavor_disk_size(self.flavor_ref)
-        self.assertIn(str(image['minDisk']),
-                      (str(original_image['minDisk']), str(flavor_disk_size)))
+        self.assertIn(str(image[MIN_DISK]),
+                      (str(original_image[MIN_DISK]), str(flavor_disk_size)))
 
         # Verify the image was deleted correctly
         self.client.delete_image(image['id'])
@@ -86,7 +101,8 @@
         # 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'))
-        body = self.client.create_image(self.server_id, name=utf8_name)
+        body = self.compute_images_client.create_image(
+            self.server_id, name=utf8_name)
         if api_version_utils.compare_version_header_to_response(
             "OpenStack-API-Version", "compute 2.45", body.response, "lt"):
             image_id = body['image_id']
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index a2e58c9..512c9d2 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -33,8 +33,11 @@
 
     def tearDown(self):
         """Terminate test instances created after a test is executed."""
-        self.server_check_teardown()
         super(ImagesOneServerNegativeTestJSON, self).tearDown()
+        # NOTE(zhufl): Because server_check_teardown will raise Exception
+        # which will prevent other cleanup steps from being executed, so
+        # server_check_teardown should be called after super's tearDown.
+        self.server_check_teardown()
 
     def setUp(self):
         # NOTE(afazekas): Normally we use the same server with all test cases,
@@ -69,7 +72,10 @@
     @classmethod
     def setup_clients(cls):
         super(ImagesOneServerNegativeTestJSON, cls).setup_clients()
-        cls.client = cls.compute_images_client
+        if cls.is_requested_microversion_compatible('2.35'):
+            cls.client = cls.compute_images_client
+        else:
+            cls.client = cls.images_client
 
     @classmethod
     def resource_setup(cls):
@@ -119,7 +125,8 @@
         # Return an error if snapshot name over 255 characters is passed
 
         snapshot_name = ('a' * 256)
-        self.assertRaises(lib_exc.BadRequest, self.client.create_image,
+        self.assertRaises(lib_exc.BadRequest,
+                          self.compute_images_client.create_image,
                           self.server_id, name=snapshot_name)
 
     @decorators.attr(type=['negative'])
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index d83d8df..2ac7de3 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -31,6 +31,7 @@
 
 
 class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.35'
 
     @classmethod
     def skip_checks(cls):
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 d37f8fc..81c59f9 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,7 @@
 
 
 class ListImageFiltersNegativeTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.35'
 
     @classmethod
     def skip_checks(cls):
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index e2dbd72..cbb65bb 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -21,6 +21,7 @@
 
 
 class ListImagesTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.35'
 
     @classmethod
     def skip_checks(cls):
diff --git a/tempest/api/compute/keypairs/base.py b/tempest/api/compute/keypairs/base.py
index 0051810..44da88c 100644
--- a/tempest/api/compute/keypairs/base.py
+++ b/tempest/api/compute/keypairs/base.py
@@ -20,17 +20,16 @@
 class BaseKeypairTest(base.BaseV2ComputeTest):
     """Base test case class for all keypair API tests."""
 
-    @classmethod
-    def setup_clients(cls):
-        super(BaseKeypairTest, cls).setup_clients()
-        cls.client = cls.keypairs_client
-
-    def _delete_keypair(self, keypair_name, **params):
-        self.client.delete_keypair(keypair_name, **params)
+    def _delete_keypair(self, keypair_name, client=None, **params):
+        if not client:
+            client = self.keypairs_client
+        client.delete_keypair(keypair_name, **params)
 
     def create_keypair(self, keypair_name=None,
                        pub_key=None, keypair_type=None,
-                       user_id=None):
+                       user_id=None, client=None):
+        if not client:
+            client = self.keypairs_client
         if keypair_name is None:
             keypair_name = data_utils.rand_name(
                 self.__class__.__name__ + '-keypair')
@@ -43,6 +42,7 @@
         if user_id:
             kwargs.update({'user_id': user_id})
             delete_params['user_id'] = user_id
-        body = self.client.create_keypair(**kwargs)['keypair']
-        self.addCleanup(self._delete_keypair, keypair_name, **delete_params)
+        body = client.create_keypair(**kwargs)['keypair']
+        self.addCleanup(self._delete_keypair, keypair_name,
+                        client, **delete_params)
         return body
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 3a54d51..66abb21 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -35,7 +35,7 @@
             key_list.append(keypair)
         # Fetch all keypairs and verify the list
         # has all created keypairs
-        fetched_list = self.client.list_keypairs()['keypairs']
+        fetched_list = self.keypairs_client.list_keypairs()['keypairs']
         new_list = list()
         for keypair in fetched_list:
             new_list.append(keypair['keypair'])
@@ -61,7 +61,7 @@
         # Keypair should be created, Got details by name and deleted
         k_name = data_utils.rand_name('keypair')
         self.create_keypair(k_name)
-        keypair_detail = self.client.show_keypair(k_name)['keypair']
+        keypair_detail = self.keypairs_client.show_keypair(k_name)['keypair']
         self.assertEqual(keypair_detail['name'], k_name,
                          "The created keypair name is not equal "
                          "to requested name")
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index 205076c..81635ca 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -34,7 +34,8 @@
     def test_keypair_delete_nonexistent_key(self):
         # Non-existent key deletion should throw a proper error
         k_name = data_utils.rand_name("keypair-non-existent")
-        self.assertRaises(lib_exc.NotFound, self.client.delete_keypair,
+        self.assertRaises(lib_exc.NotFound,
+                          self.keypairs_client.delete_keypair,
                           k_name)
 
     @decorators.attr(type=['negative'])
@@ -58,11 +59,11 @@
     def test_create_keypair_with_duplicate_name(self):
         # Keypairs with duplicate names should not be created
         k_name = data_utils.rand_name('keypair')
-        self.client.create_keypair(name=k_name)
+        self.keypairs_client.create_keypair(name=k_name)
         # Now try the same keyname to create another key
         self.assertRaises(lib_exc.Conflict, self.create_keypair,
                           k_name)
-        self.client.delete_keypair(k_name)
+        self.keypairs_client.delete_keypair(k_name)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('1398abe1-4a84-45fb-9294-89f514daff00')
@@ -83,6 +84,6 @@
     @decorators.idempotent_id('45fbe5e0-acb5-49aa-837a-ff8d0719db91')
     def test_create_keypair_invalid_name(self):
         # Keypairs with name being an invalid name should not be created
-        k_name = 'key_/.\@:'
+        k_name = r'key_/.\@:'
         self.assertRaises(lib_exc.BadRequest, self.create_keypair,
                           k_name)
diff --git a/tempest/api/compute/keypairs/test_keypairs_v22.py b/tempest/api/compute/keypairs/test_keypairs_v22.py
index f39bb12..1aff262 100644
--- a/tempest/api/compute/keypairs/test_keypairs_v22.py
+++ b/tempest/api/compute/keypairs/test_keypairs_v22.py
@@ -32,9 +32,9 @@
         # Verify whether 'type' is present in keypair create response of
         # version 2.2 and it is with default value 'ssh'.
         self._check_keypair_type(keypair, keypair_type)
-        keypair_detail = self.client.show_keypair(k_name)['keypair']
+        keypair_detail = self.keypairs_client.show_keypair(k_name)['keypair']
         self._check_keypair_type(keypair_detail, keypair_type)
-        fetched_list = self.client.list_keypairs()['keypairs']
+        fetched_list = self.keypairs_client.list_keypairs()['keypairs']
         for keypair in fetched_list:
             # Verify whether 'type' is present in keypair list response of
             # version 2.2 and it is with default value 'ssh'.
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index 0585fec..8c2202e 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -18,6 +18,7 @@
 
 
 class AbsoluteLimitsTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.56'
 
     @classmethod
     def setup_clients(cls):
@@ -26,22 +27,14 @@
 
     @decorators.idempotent_id('b54c66af-6ab6-4cf0-a9e5-a0cb58d75e0b')
     def test_absLimits_get(self):
-        # To check if all limits are present in the response
-        limits = self.client.show_limits()['limits']
-        absolute_limits = limits['absolute']
-        expected_elements = ['maxImageMeta', 'maxPersonality',
-                             'maxPersonalitySize',
-                             'maxServerMeta', 'maxTotalCores',
-                             'maxTotalFloatingIps', 'maxSecurityGroups',
-                             'maxSecurityGroupRules', 'maxTotalInstances',
-                             'maxTotalKeypairs', 'maxTotalRAMSize',
-                             'maxServerGroups', 'maxServerGroupMembers',
-                             'totalCoresUsed', 'totalFloatingIpsUsed',
-                             'totalSecurityGroupsUsed', 'totalInstancesUsed',
-                             'totalRAMUsed', 'totalServerGroupsUsed']
-        # check whether all expected elements exist
-        missing_elements =\
-            [ele for ele in expected_elements if ele not in absolute_limits]
-        self.assertEmpty(missing_elements,
-                         "Failed to find element %s in absolute limits list"
-                         % ', '.join(ele for ele in missing_elements))
+        # To check if all limits are present in the response (will be checked
+        # by schema)
+        self.client.show_limits()
+
+
+class AbsoluteLimitsV257TestJSON(base.BaseV2ComputeTest):
+    min_microversion = '2.57'
+    max_microversion = 'latest'
+
+    # NOTE(felipemonteiro): This class tests the Absolute Limits APIs
+    # response schema for the 2.57 microversion.
diff --git a/tempest/api/compute/limits/test_absolute_limits_negative.py b/tempest/api/compute/limits/test_absolute_limits_negative.py
index bef4eb5..500638a 100644
--- a/tempest/api/compute/limits/test_absolute_limits_negative.py
+++ b/tempest/api/compute/limits/test_absolute_limits_negative.py
@@ -33,15 +33,15 @@
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('215cd465-d8ae-49c9-bf33-9c911913a5c8')
-    def test_max_image_meta_exceed_limit(self):
-        # We should not create vm with image meta over maxImageMeta limit
+    def test_max_metadata_exceed_limit(self):
+        # We should not create vm with metadata over maxServerMeta limit
         # Get max limit value
         limits = self.client.show_limits()['limits']
-        max_meta = limits['absolute']['maxImageMeta']
+        max_meta = limits['absolute']['maxServerMeta']
 
         # No point in running this test if there is no limit.
         if max_meta == -1:
-            raise self.skipException('no limit for maxImageMeta')
+            raise self.skipException('no limit for maxServerMeta')
 
         # Create server should fail, since we are passing > metadata Limit!
         max_meta_data = max_meta + 1
diff --git a/tempest/api/compute/security_groups/base.py b/tempest/api/compute/security_groups/base.py
index 54a6da8..49125d1 100644
--- a/tempest/api/compute/security_groups/base.py
+++ b/tempest/api/compute/security_groups/base.py
@@ -22,6 +22,7 @@
 
 
 class BaseSecurityGroupsTest(base.BaseV2ComputeTest):
+    max_microversion = '2.35'
 
     @classmethod
     def skip_checks(cls):
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 0248c65..eeb58d6 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -23,34 +23,72 @@
 from tempest.common.utils import net_utils
 from tempest.common import waiters
 from tempest import config
+from tempest.lib.common.utils.linux import remote_client
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
 CONF = config.CONF
 
 
-class AttachInterfacesTestJSON(base.BaseV2ComputeTest):
+class AttachInterfacesTestBase(base.BaseV2ComputeTest):
 
     @classmethod
     def skip_checks(cls):
-        super(AttachInterfacesTestJSON, cls).skip_checks()
+        super(AttachInterfacesTestBase, cls).skip_checks()
         if not CONF.service_available.neutron:
             raise cls.skipException("Neutron is required")
         if not CONF.compute_feature_enabled.interface_attach:
             raise cls.skipException("Interface attachment is not available.")
+        if not CONF.validation.run_validation:
+            raise cls.skipException('Validation should be enabled to ensure '
+                                    'guest OS is running and capable of '
+                                    'processing ACPI events.')
 
     @classmethod
     def setup_credentials(cls):
         # This test class requires network and subnet
-        cls.set_network_resources(network=True, subnet=True)
-        super(AttachInterfacesTestJSON, cls).setup_credentials()
+        cls.set_network_resources(network=True, subnet=True, router=True,
+                                  dhcp=True)
+        super(AttachInterfacesTestBase, cls).setup_credentials()
 
     @classmethod
     def setup_clients(cls):
-        super(AttachInterfacesTestJSON, cls).setup_clients()
+        super(AttachInterfacesTestBase, cls).setup_clients()
         cls.subnets_client = cls.os_primary.subnets_client
         cls.ports_client = cls.os_primary.ports_client
 
+    def _wait_for_validation(self, server, validation_resources):
+        linux_client = remote_client.RemoteClient(
+            self.get_server_ip(server, validation_resources),
+            self.image_ssh_user,
+            self.image_ssh_password,
+            validation_resources['keypair']['private_key'],
+            server=server,
+            servers_client=self.servers_client)
+        linux_client.validate_authentication()
+
+    def _create_server_get_interfaces(self):
+        validation_resources = self.get_test_validation_resources(
+            self.os_primary)
+        server = self.create_test_server(
+            validatable=True,
+            validation_resources=validation_resources,
+            wait_until='ACTIVE')
+        # NOTE(artom) self.create_test_server adds cleanups, but this is
+        # apparently not enough? Add cleanup here.
+        self.addCleanup(self.delete_server, server['id'])
+        self._wait_for_validation(server, validation_resources)
+        ifs = (self.interfaces_client.list_interfaces(server['id'])
+               ['interfaceAttachments'])
+        body = waiters.wait_for_interface_status(
+            self.interfaces_client, server['id'], ifs[0]['port_id'], 'ACTIVE')
+        ifs[0]['port_state'] = body['port_state']
+        return server, ifs
+
+
+class AttachInterfacesTestJSON(AttachInterfacesTestBase):
+
     def wait_for_port_detach(self, port_id):
         """Waits for the port's device_id to be unset.
 
@@ -78,8 +116,11 @@
 
         return port
 
-    def _check_interface(self, iface, port_id=None, network_id=None,
-                         fixed_ip=None, mac_addr=None):
+    def _check_interface(self, iface, server_id=None, port_id=None,
+                         network_id=None, fixed_ip=None, mac_addr=None):
+        if server_id:
+            iface = waiters.wait_for_interface_status(
+                self.interfaces_client, server_id, iface['port_id'], 'ACTIVE')
         if port_id:
             self.assertEqual(iface['port_id'], port_id)
         if network_id:
@@ -89,15 +130,6 @@
         if mac_addr:
             self.assertEqual(iface['mac_addr'], mac_addr)
 
-    def _create_server_get_interfaces(self):
-        server = self.create_test_server(wait_until='ACTIVE')
-        ifs = (self.interfaces_client.list_interfaces(server['id'])
-               ['interfaceAttachments'])
-        body = waiters.wait_for_interface_status(
-            self.interfaces_client, server['id'], ifs[0]['port_id'], 'ACTIVE')
-        ifs[0]['port_state'] = body['port_state']
-        return server, ifs
-
     def _test_create_interface(self, server):
         iface = (self.interfaces_client.create_interface(server['id'])
                  ['interfaceAttachment'])
@@ -109,9 +141,8 @@
         network_id = ifs[0]['net_id']
         iface = self.interfaces_client.create_interface(
             server['id'], net_id=network_id)['interfaceAttachment']
-        iface = waiters.wait_for_interface_status(
-            self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
-        self._check_interface(iface, network_id=network_id)
+        self._check_interface(iface, server_id=server['id'],
+                              network_id=network_id)
         return iface
 
     def _test_create_interface_by_port_id(self, server, ifs):
@@ -121,9 +152,8 @@
         self.addCleanup(self.ports_client.delete_port, port_id)
         iface = self.interfaces_client.create_interface(
             server['id'], port_id=port_id)['interfaceAttachment']
-        iface = waiters.wait_for_interface_status(
-            self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
-        self._check_interface(iface, port_id=port_id)
+        self._check_interface(iface, server_id=server['id'], port_id=port_id,
+                              network_id=network_id)
         return iface
 
     def _test_create_interface_by_fixed_ips(self, server, ifs):
@@ -139,10 +169,11 @@
         iface = self.interfaces_client.create_interface(
             server['id'], net_id=network_id,
             fixed_ips=fixed_ips)['interfaceAttachment']
-        self.addCleanup(self.ports_client.delete_port, iface['port_id'])
-        iface = waiters.wait_for_interface_status(
-            self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
-        self._check_interface(iface, fixed_ip=ip_list[0])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port,
+                        iface['port_id'])
+        self._check_interface(iface, server_id=server['id'],
+                              fixed_ip=ip_list[0])
         return iface
 
     def _test_show_interface(self, server, ifs):
@@ -185,7 +216,7 @@
 
     @decorators.idempotent_id('73fe8f02-590d-4bf1-b184-e9ca81065051')
     @utils.services('network')
-    def test_create_list_show_delete_interfaces(self):
+    def test_create_list_show_delete_interfaces_by_network_port(self):
         server, ifs = self._create_server_get_interfaces()
         interface_count = len(ifs)
         self.assertGreater(interface_count, 0)
@@ -206,6 +237,32 @@
         iface = self._test_create_interface_by_port_id(server, ifs)
         ifs.append(iface)
 
+        _ifs = (self.interfaces_client.list_interfaces(server['id'])
+                ['interfaceAttachments'])
+        self._compare_iface_list(ifs, _ifs)
+
+        self._test_show_interface(server, ifs)
+
+        _ifs = self._test_delete_interface(server, ifs)
+        self.assertEqual(len(ifs) - 1, len(_ifs))
+
+    @decorators.idempotent_id('d290c06c-f5b3-11e7-8ec8-002293781009')
+    @utils.services('network')
+    def test_create_list_show_delete_interfaces_by_fixed_ip(self):
+        # NOTE(zhufl) By default only project that is admin or network owner
+        # or project with role advsvc is authorised to create interfaces with
+        # fixed-ip, so if we don't create network for each project, do not
+        # test _test_create_interface_by_fixed_ips.
+        if not (CONF.auth.use_dynamic_credentials and
+                CONF.auth.create_isolated_networks and
+                not CONF.network.shared_physical_network):
+            raise self.skipException("Only owner network supports "
+                                     "creating interface by fixed ip.")
+
+        server, ifs = self._create_server_get_interfaces()
+        interface_count = len(ifs)
+        self.assertGreater(interface_count, 0)
+
         iface = self._test_create_interface_by_fixed_ips(server, ifs)
         ifs.append(iface)
 
@@ -218,30 +275,6 @@
         _ifs = self._test_delete_interface(server, ifs)
         self.assertEqual(len(ifs) - 1, len(_ifs))
 
-    @decorators.attr(type='smoke')
-    @decorators.idempotent_id('c7e0e60b-ee45-43d0-abeb-8596fd42a2f9')
-    @utils.services('network')
-    def test_add_remove_fixed_ip(self):
-        # Add and Remove the fixed IP to server.
-        server, ifs = self._create_server_get_interfaces()
-        interface_count = len(ifs)
-        self.assertGreater(interface_count, 0)
-        network_id = ifs[0]['net_id']
-        self.servers_client.add_fixed_ip(server['id'], networkId=network_id)
-        # Remove the fixed IP from server.
-        server_detail = self.os_primary.servers_client.show_server(
-            server['id'])['server']
-        # Get the Fixed IP from server.
-        fixed_ip = None
-        for ip_set in server_detail['addresses']:
-            for ip in server_detail['addresses'][ip_set]:
-                if ip['OS-EXT-IPS:type'] == 'fixed':
-                    fixed_ip = ip['addr']
-                    break
-            if fixed_ip is not None:
-                break
-        self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
-
     @decorators.idempotent_id('2f3a0127-95c7-4977-92d2-bc5aec602fb4')
     def test_reassign_port_between_servers(self):
         """Tests the following:
@@ -259,21 +292,112 @@
         port_id = port['port']['id']
         self.addCleanup(self.ports_client.delete_port, port_id)
 
-        # create two servers
-        _, servers = compute.create_test_server(
-            self.os_primary, tenant_network=network,
-            wait_until='ACTIVE', min_count=2)
+        # NOTE(artom) We create two servers one at a time because
+        # create_test_server doesn't support multiple validatable servers.
+        validation_resources = self.get_test_validation_resources(
+            self.os_primary)
+
+        def _create_validatable_server():
+            _, servers = compute.create_test_server(
+                self.os_primary, tenant_network=network,
+                wait_until='ACTIVE', validatable=True,
+                validation_resources=validation_resources)
+            return servers[0]
+
+        servers = [_create_validatable_server(), _create_validatable_server()]
+
         # add our cleanups for the servers since we bypassed the base class
         for server in servers:
             self.addCleanup(self.delete_server, server['id'])
 
         for server in servers:
+            self._wait_for_validation(server, validation_resources)
             # attach the port to the server
             iface = self.interfaces_client.create_interface(
                 server['id'], port_id=port_id)['interfaceAttachment']
-            self._check_interface(iface, port_id=port_id)
+            self._check_interface(iface, server_id=server['id'],
+                                  port_id=port_id)
 
             # detach the port from the server; this is a cast in the compute
             # API so we have to poll the port until the device_id is unset.
             self.interfaces_client.delete_interface(server['id'], port_id)
             self.wait_for_port_detach(port_id)
+
+
+class AttachInterfacesUnderV243Test(AttachInterfacesTestBase):
+    max_microversion = '2.43'
+
+    @decorators.attr(type='smoke')
+    @decorators.idempotent_id('c7e0e60b-ee45-43d0-abeb-8596fd42a2f9')
+    @utils.services('network')
+    def test_add_remove_fixed_ip(self):
+        # NOTE(zhufl) By default only project that is admin or network owner
+        # or project with role advsvc is authorised to add interfaces with
+        # fixed-ip, so if we don't create network for each project, do not
+        # test
+        if not (CONF.auth.use_dynamic_credentials and
+                CONF.auth.create_isolated_networks and
+                not CONF.network.shared_physical_network):
+            raise self.skipException("Only owner network supports "
+                                     "creating interface by fixed ip.")
+
+        # Add and Remove the fixed IP to server.
+        server, ifs = self._create_server_get_interfaces()
+        original_interface_count = len(ifs)  # This is the number of ports.
+        self.assertGreater(original_interface_count, 0)
+        # Get the starting list of IPs on the server.
+        addresses = self.os_primary.servers_client.list_addresses(
+            server['id'])['addresses']
+        # There should be one entry for the single network mapped to a list of
+        # addresses, which at this point should have at least one entry.
+        # Note that we could start with two addresses depending on how tempest
+        # is configured for using floating IPs.
+        self.assertEqual(1, len(addresses), addresses)  # number of networks
+        # Keep track of the original addresses so we can know which IP is new.
+        original_ips = [addr['addr'] for addr in list(addresses.values())[0]]
+        original_ip_count = len(original_ips)
+        self.assertGreater(original_ip_count, 0, addresses)  # at least 1
+        network_id = ifs[0]['net_id']
+        # Add another fixed IP to the server. This should result in another
+        # fixed IP on the same network (and same port since we only have one
+        # port).
+        self.servers_client.add_fixed_ip(server['id'], networkId=network_id)
+        # Wait for the ips count to increase by one.
+
+        def _wait_for_ip_increase():
+            _addresses = self.os_primary.servers_client.list_addresses(
+                server['id'])['addresses']
+            return len(list(_addresses.values())[0]) == original_ip_count + 1
+
+        if not test_utils.call_until_true(
+                _wait_for_ip_increase, CONF.compute.build_timeout,
+                CONF.compute.build_interval):
+            raise lib_exc.TimeoutException(
+                'Timed out while waiting for IP count to increase.')
+
+        # Remove the fixed IP that we just added.
+        server_detail = self.os_primary.servers_client.show_server(
+            server['id'])['server']
+        # Get the Fixed IP from server.
+        fixed_ip = None
+        for ip_set in server_detail['addresses']:
+            for ip in server_detail['addresses'][ip_set]:
+                if (ip['OS-EXT-IPS:type'] == 'fixed' and
+                        ip['addr'] not in original_ips):
+                    fixed_ip = ip['addr']
+                    break
+            if fixed_ip is not None:
+                break
+        self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
+        # Wait for the interface count to decrease by one.
+
+        def _wait_for_ip_decrease():
+            _addresses = self.os_primary.servers_client.list_addresses(
+                server['id'])['addresses']
+            return len(list(_addresses.values())[0]) == original_ip_count
+
+        if not test_utils.call_until_true(
+                _wait_for_ip_decrease, CONF.compute.build_timeout,
+                CONF.compute.build_interval):
+            raise lib_exc.TimeoutException(
+                'Timed out while waiting for IP count to decrease.')
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index c660821..4f0dbad 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -80,7 +80,7 @@
             self.assertEqual("", self.server['image'])
         else:
             self.assertEqual(self.image_ref, self.server['image']['id'])
-        self.assertEqual(self.flavor_ref, self.server['flavor']['id'])
+        self.assert_flavor_equal(self.flavor_ref, self.server['flavor'])
         self.assertEqual(self.meta, self.server['metadata'])
 
     @decorators.attr(type='smoke')
@@ -135,8 +135,13 @@
             servers_client=self.client)
         hostname = linux_client.exec_command("hostname").rstrip()
         msg = ('Failed while verifying servername equals hostname. Expected '
-               'hostname "%s" but got "%s".' % (self.name, hostname))
-        self.assertEqual(self.name.lower(), hostname, msg)
+               'hostname "%s" but got "%s".' %
+               (self.name, hostname.split(".")[0]))
+        # NOTE(zhufl): Some images will add postfix for the hostname, e.g.,
+        # if hostname is "aaa", postfix ".novalocal" may be added to it, and
+        # the hostname will be "aaa.novalocal" then, so we should ignore the
+        # postfix when checking whether hostname equals self.name.
+        self.assertEqual(self.name.lower(), hostname.split(".")[0], msg)
 
 
 class ServersTestManualDisk(ServersTestJSON):
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 0093752..0263b81 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -107,11 +107,10 @@
     @utils.services('volume')
     def test_delete_server_while_in_attached_volume(self):
         # Delete a server while a volume is attached to it
-        device = '/dev/%s' % CONF.compute.volume_device_name
         server = self.create_test_server(wait_until='ACTIVE')
 
         volume = self.create_volume()
-        self.attach_volume(server, volume, device=device)
+        self.attach_volume(server, volume)
 
         self.client.delete_server(server['id'])
         waiters.wait_for_server_termination(self.client, server['id'])
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index a126fd6..e817587 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -12,13 +12,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import json
-
 from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
 
 from tempest.api.compute import base
 from tempest.common import utils
 from tempest.common.utils.linux import remote_client
+from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
@@ -31,30 +31,23 @@
 LOG = logging.getLogger(__name__)
 
 
-class DeviceTaggingTest(base.BaseV2ComputeTest):
-
-    min_microversion = '2.32'
-    # NOTE(mriedem): max_version looks odd but it's actually correct. Due to a
-    # bug in the 2.32 microversion, tags on block devices only worked with the
-    # 2.32 microversion specifically. And tags on networks only worked between
-    # 2.32 and 2.36 inclusive; the 2.37 microversion broke tags for networks.
-    max_microversion = '2.32'
+class DeviceTaggingBase(base.BaseV2ComputeTest):
 
     @classmethod
     def skip_checks(cls):
-        super(DeviceTaggingTest, cls).skip_checks()
+        super(DeviceTaggingBase, cls).skip_checks()
         if not CONF.service_available.neutron:
             raise cls.skipException('Neutron is required')
         if not CONF.validation.run_validation:
             raise cls.skipException('Validation must be enabled')
-        if (not CONF.compute_feature_enabled.config_drive
-            and not CONF.compute_feature_enabled.metadata_service):
+        if (not CONF.compute_feature_enabled.config_drive and
+                not CONF.compute_feature_enabled.metadata_service):
             raise cls.skipException('One of metadata or config drive must be '
                                     'enabled')
 
     @classmethod
     def setup_clients(cls):
-        super(DeviceTaggingTest, cls).setup_clients()
+        super(DeviceTaggingBase, cls).setup_clients()
         cls.networks_client = cls.os_primary.networks_client
         cls.ports_client = cls.os_primary.ports_client
         cls.subnets_client = cls.os_primary.subnets_client
@@ -64,7 +57,57 @@
     def setup_credentials(cls):
         cls.set_network_resources(network=True, subnet=True, router=True,
                                   dhcp=True)
-        super(DeviceTaggingTest, cls).setup_credentials()
+        super(DeviceTaggingBase, cls).setup_credentials()
+
+    def verify_metadata_from_api(self, server, ssh_client, verify_method):
+        md_url = 'http://169.254.169.254/openstack/latest/meta_data.json'
+        LOG.info('Attempting to verify tagged devices in server %s via '
+                 'the metadata service: %s', server['id'], md_url)
+
+        def get_and_verify_metadata():
+            try:
+                ssh_client.exec_command('curl -V')
+            except exceptions.SSHExecCommandFailed:
+                if not CONF.compute_feature_enabled.config_drive:
+                    raise self.skipException('curl not found in guest '
+                                             'and config drive is '
+                                             'disabled')
+                LOG.warning('curl was not found in the guest, device '
+                            'tagging metadata was not checked in the '
+                            'metadata API')
+                return True
+            cmd = 'curl %s' % md_url
+            md_json = ssh_client.exec_command(cmd)
+            return verify_method(md_json)
+        # NOTE(gmann) Keep refreshing the metadata info until the metadata
+        # cache is refreshed. For safer side, we will go with wait loop of
+        # build_interval till build_timeout. verify_method() above will return
+        # True if all metadata verification is done as expected.
+        if not test_utils.call_until_true(get_and_verify_metadata,
+                                          CONF.compute.build_timeout,
+                                          CONF.compute.build_interval):
+            raise exceptions.TimeoutException('Timeout while verifying '
+                                              'metadata on server.')
+
+    def verify_metadata_on_config_drive(self, server, ssh_client,
+                                        verify_method):
+        LOG.info('Attempting to verify tagged devices in server %s via '
+                 'the config drive.', server['id'])
+        ssh_client.mount_config_drive()
+        cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
+        md_json = ssh_client.exec_command(cmd_md)
+        verify_method(md_json)
+        ssh_client.unmount_config_drive()
+
+
+class TaggedBootDevicesTest(DeviceTaggingBase):
+
+    min_microversion = '2.32'
+    # NOTE(mriedem): max_version looks odd but it's actually correct. Due to a
+    # bug in the 2.32 microversion, tags on block devices only worked with the
+    # 2.32 microversion specifically. And tags on networks only worked between
+    # 2.32 and 2.36 inclusive; the 2.37 microversion broke tags for networks.
+    max_microversion = '2.32'
 
     def verify_device_metadata(self, md_json):
         md_dict = json.loads(md_json)
@@ -79,18 +122,27 @@
                 if d['mac'] == self.net_2_200_mac:
                     self.assertEqual(d['tags'], ['net-2-200'])
 
-            # A hypervisor may present multiple paths to a tagged disk, so
-            # there may be duplicated tags in the metadata, use set() to
-            # remove duplicated tags.
-            found_devices = [d['tags'][0] for d in md_dict['devices']]
+        # A hypervisor may present multiple paths to a tagged disk, so
+        # there may be duplicated tags in the metadata, use set() to
+        # remove duplicated tags.
+        # Some hypervisors might report devices with no tags as well.
+        found_devices = [d['tags'][0] for d in md_dict['devices']
+                         if d.get('tags')]
+        try:
             self.assertEqual(set(found_devices), set(['port-1', 'port-2',
                                                       'net-1', 'net-2-100',
                                                       'net-2-200', 'boot',
                                                       'other']))
+            return True
+        except Exception:
+            return False
 
+    # NOTE(mriedem): This is really more like a scenario test and is slow so
+    # it's marked as such.
+    @decorators.attr(type='slow')
     @decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96')
     @utils.services('network', 'volume', 'image')
-    def test_device_tagging(self):
+    def test_tagged_boot_devices(self):
         # Create volumes
         # The create_volume methods waits for the volumes to be available and
         # the base class will clean them up on tearDown.
@@ -132,16 +184,15 @@
         self.addCleanup(self.ports_client.delete_port, self.port2['id'])
 
         # Create server
-        admin_pass = data_utils.rand_password()
         config_drive_enabled = CONF.compute_feature_enabled.config_drive
         validation_resources = self.get_test_validation_resources(
             self.os_primary)
 
         server = self.create_test_server(
             validatable=True,
+            wait_until='ACTIVE',
             validation_resources=validation_resources,
             config_drive=config_drive_enabled,
-            adminPass=admin_pass,
             name=data_utils.rand_name('device-tagging-server'),
             networks=[
                 # Validation network for ssh
@@ -205,11 +256,11 @@
 
         self.addCleanup(self.delete_server, server['id'])
 
-        self.ssh_client = remote_client.RemoteClient(
+        server = self.servers_client.show_server(server['id'])['server']
+        ssh_client = remote_client.RemoteClient(
             self.get_server_ip(server, validation_resources),
             CONF.validation.image_ssh_user,
-            admin_pass,
-            validation_resources['keypair']['private_key'],
+            pkey=validation_resources['keypair']['private_key'],
             server=server,
             servers_client=self.servers_client)
 
@@ -229,58 +280,112 @@
         self.assertTrue(self.net_2_100_mac)
         self.assertTrue(self.net_2_200_mac)
 
-        # Verify metadata from metadata service
+        # Verify metadata from metadata API
         if CONF.compute_feature_enabled.metadata_service:
-            md_url = 'http://169.254.169.254/openstack/latest/meta_data.json'
-            LOG.info('Attempting to verify tagged devices in server %s via '
-                     'the metadata service: %s', server['id'], md_url)
-
-            def get_and_verify_metadata():
-                try:
-                    self.ssh_client.exec_command('curl -V')
-                except exceptions.SSHExecCommandFailed:
-                    if not CONF.compute_feature_enabled.config_drive:
-                        raise self.skipException('curl not found in guest '
-                                                 'and config drive is '
-                                                 'disabled')
-                    LOG.warning('curl was not found in the guest, device '
-                                'tagging metadata was not checked in the '
-                                'metadata API')
-                    return True
-                cmd = 'curl %s' % md_url
-                md_json = self.ssh_client.exec_command(cmd)
-                self.verify_device_metadata(md_json)
-                return True
-
-            if not test_utils.call_until_true(get_and_verify_metadata,
-                                              CONF.compute.build_timeout,
-                                              CONF.compute.build_interval):
-                raise exceptions.TimeoutException('Timeout while verifying '
-                                                  'metadata on server.')
+            self.verify_metadata_from_api(server, ssh_client,
+                                          self.verify_device_metadata)
 
         # Verify metadata on config drive
         if CONF.compute_feature_enabled.config_drive:
-            cmd_blkid = 'blkid -t LABEL=config-2 -o device'
-            LOG.info('Attempting to verify tagged devices in server %s via '
-                     'the config drive.', server['id'])
-            dev_name = self.ssh_client.exec_command(cmd_blkid)
-            dev_name = dev_name.rstrip()
-            try:
-                self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
-            except exceptions.SSHExecCommandFailed:
-                # So the command failed, let's try to know why and print some
-                # useful information.
-                lsblk = self.ssh_client.exec_command('sudo lsblk --fs --ascii')
-                LOG.error("Mounting %s on /mnt failed. Right after the "
-                          "failure 'lsblk' in the guest reported:\n%s",
-                          dev_name, lsblk)
-                raise
-
-            cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
-            md_json = self.ssh_client.exec_command(cmd_md)
-            self.verify_device_metadata(md_json)
+            self.verify_metadata_on_config_drive(server, ssh_client,
+                                                 self.verify_device_metadata)
 
 
-class DeviceTaggingTestV2_42(DeviceTaggingTest):
+class TaggedBootDevicesTest_v242(TaggedBootDevicesTest):
     min_microversion = '2.42'
     max_microversion = 'latest'
+
+
+class TaggedAttachmentsTest(DeviceTaggingBase):
+
+    min_microversion = '2.49'
+    max_microversion = 'latest'
+
+    @classmethod
+    def skip_checks(cls):
+        super(TaggedAttachmentsTest, cls).skip_checks()
+        if not CONF.compute_feature_enabled.metadata_service:
+            raise cls.skipException('Metadata API must be enabled')
+
+    def verify_device_metadata(self, md_json):
+        md_dict = json.loads(md_json)
+        found_devices = [d['tags'][0] for d in md_dict['devices']
+                         if d.get('tags')]
+        try:
+            self.assertItemsEqual(found_devices, ['nic-tag', 'volume-tag'])
+            return True
+        except Exception:
+            return False
+
+    def verify_empty_devices(self, md_json):
+        md_dict = json.loads(md_json)
+        try:
+            self.assertEmpty(md_dict['devices'])
+            return True
+        except AssertionError:
+            LOG.debug("Related bug 1775947. Devices dict is not empty: %s",
+                      md_dict['devices'])
+            return False
+
+    @decorators.idempotent_id('3e41c782-2a89-4922-a9d2-9a188c4e7c7c')
+    @utils.services('network', 'volume', 'image')
+    def test_tagged_attachment(self):
+        # Create network
+        net = self.networks_client.create_network(
+            name=data_utils.rand_name(
+                'tagged-attachments-test-net'))['network']
+        self.addCleanup(self.networks_client.delete_network, net['id'])
+
+        # Create subnet
+        subnet = self.subnets_client.create_subnet(
+            network_id=net['id'],
+            cidr='10.10.10.0/24',
+            ip_version=4)['subnet']
+        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+
+        # Create volume
+        volume = self.create_volume()
+
+        # Boot test server
+        config_drive_enabled = CONF.compute_feature_enabled.config_drive
+        validation_resources = self.get_test_validation_resources(
+            self.os_primary)
+
+        server = self.create_test_server(
+            validatable=True,
+            validation_resources=validation_resources,
+            config_drive=config_drive_enabled,
+            name=data_utils.rand_name('device-tagging-server'),
+            networks=[{'uuid': self.get_tenant_network()['id']}])
+        self.addCleanup(self.delete_server, server['id'])
+
+        # Attach tagged nic and volume
+        interface = self.interfaces_client.create_interface(
+            server['id'], net_id=net['id'],
+            tag='nic-tag')['interfaceAttachment']
+        self.attach_volume(server, volume, tag='volume-tag')
+
+        ssh_client = remote_client.RemoteClient(
+            self.get_server_ip(server, validation_resources),
+            CONF.validation.image_ssh_user,
+            pkey=validation_resources['keypair']['private_key'],
+            server=server,
+            servers_client=self.servers_client)
+
+        self.verify_metadata_from_api(server, ssh_client,
+                                      self.verify_device_metadata)
+
+        # Detach tagged nic and volume
+        self.servers_client.detach_volume(server['id'], volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
+        self.interfaces_client.delete_interface(server['id'],
+                                                interface['port_id'])
+        waiters.wait_for_interface_detach(self.interfaces_client,
+                                          server['id'],
+                                          interface['port_id'])
+        # FIXME(mriedem): The assertion that the tagged devices are removed
+        # from the metadata for the server is being skipped until bug 1775947
+        # is fixed.
+        # self.verify_metadata_from_api(server, ssh_client,
+        #                               self.verify_empty_devices)
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 14aecfd..3dffd01 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -227,7 +227,7 @@
     @decorators.idempotent_id('24a89b0c-0d55-4a28-847f-45075f19b27b')
     def test_list_servers_filtered_by_name_regex(self):
         # list of regex that should match s1, s2 and s3
-        regexes = ['^.*\-instance\-[0-9]+$', '^.*\-instance\-.*$']
+        regexes = [r'^.*\-instance\-[0-9]+$', r'^.*\-instance\-.*$']
         for regex in regexes:
             params = {'name': regex}
             body = self.client.list_servers(**params)
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 6c9b287..f0915de 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -79,10 +79,16 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
     def test_list_servers_status_non_existing(self):
-        # Return an empty list when invalid status is specified
-        body = self.client.list_servers(status='non_existing_status')
-        servers = body['servers']
-        self.assertEmpty(servers)
+        # 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']
+            self.assertEmpty(servers)
+        else:
+            self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
+                              status='non_existing_status')
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
@@ -119,8 +125,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
-        changes_since = {'changes-since': '2051-01-01T12:34:00Z'}
+        # Return an empty list when a date in the future is passed.
+        # 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.
+        changes_since = {'changes-since': '2051-01-01T12:34:00Z',
+                         'status': 'ACTIVE'}
         body = self.client.list_servers(**changes_since)
         self.assertEmpty(body['servers'])
 
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index d9581e3..daf6a06 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -44,10 +44,13 @@
         self._websocket = None
 
     def tearDown(self):
-        self.server_check_teardown()
         super(NoVNCConsoleTestJSON, self).tearDown()
         if self._websocket is not None:
             self._websocket.close()
+        # NOTE(zhufl): Because server_check_teardown will raise Exception
+        # which will prevent other cleanup steps from being executed, so
+        # server_check_teardown should be called after super's tearDown.
+        self.server_check_teardown()
 
     @classmethod
     def setup_clients(cls):
@@ -58,6 +61,9 @@
     def resource_setup(cls):
         super(NoVNCConsoleTestJSON, cls).resource_setup()
         cls.server = cls.create_test_server(wait_until="ACTIVE")
+        cls.use_get_remote_console = False
+        if not cls.is_requested_microversion_compatible('2.5'):
+            cls.use_get_remote_console = True
 
     def _validate_novnc_html(self, vnc_url):
         """Verify we can connect to novnc and get back the javascript."""
@@ -137,7 +143,7 @@
         data_length = len(data) if data is not None else 0
         self.assertFalse(data_length <= 24 or
                          data_length != (struct.unpack(">L",
-                                         data[20:24])[0] + 24),
+                                                       data[20:24])[0] + 24),
                          'Server initialization was not the right format.')
         # Since the rest of the data on the screen is arbitrary, we will
         # close the socket and end our validation of the data at this point
@@ -145,22 +151,38 @@
         # initialization was the right format
         self.assertFalse(data_length <= 24 or
                          data_length != (struct.unpack(">L",
-                                         data[20:24])[0] + 24))
+                                                       data[20:24])[0] + 24))
 
     def _validate_websocket_upgrade(self):
         self.assertTrue(
             self._websocket.response.startswith(b'HTTP/1.1 101 Switching '
                                                 b'Protocols\r\n'),
-            'Did not get the expected 101 on the websockify call: '
-            + six.text_type(self._websocket.response))
-        self.assertTrue(
-            self._websocket.response.find(b'Server: WebSockify') > 0,
-            'Did not get the expected WebSocket HTTP Response.')
+            'Did not get the expected 101 on the {} call: {}'.format(
+                CONF.compute_feature_enabled.vnc_server_header,
+                six.text_type(self._websocket.response)
+            )
+        )
+        # Since every other server type returns Headers with different case
+        # (for example 'nginx'), lowercase must be applied to eliminate issues.
+        _desired_header = "server: {0}".format(
+            CONF.compute_feature_enabled.vnc_server_header
+        ).lower()
+        _response = six.text_type(self._websocket.response).lower()
+        self.assertIn(
+            _desired_header,
+            _response,
+            'Did not get the expected WebSocket HTTP Response.'
+        )
 
     @decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
     def test_novnc(self):
-        body = self.client.get_vnc_console(self.server['id'],
-                                           type='novnc')['console']
+        if self.use_get_remote_console:
+            body = self.client.get_remote_console(
+                self.server['id'], console_type='novnc',
+                protocol='vnc')['remote_console']
+        else:
+            body = self.client.get_vnc_console(self.server['id'],
+                                               type='novnc')['console']
         self.assertEqual('novnc', body['type'])
         # Do the initial HTTP Request to novncproxy to get the NoVNC JavaScript
         self._validate_novnc_html(body['url'])
@@ -173,8 +195,13 @@
 
     @decorators.idempotent_id('f9c79937-addc-4aaa-9e0e-841eef02aeb7')
     def test_novnc_bad_token(self):
-        body = self.client.get_vnc_console(self.server['id'],
-                                           type='novnc')['console']
+        if self.use_get_remote_console:
+            body = self.client.get_remote_console(
+                self.server['id'], console_type='novnc',
+                protocol='vnc')['remote_console']
+        else:
+            body = self.client.get_vnc_console(self.server['id'],
+                                               type='novnc')['console']
         self.assertEqual('novnc', body['type'])
         # Do the WebSockify HTTP Request to novncproxy with a bad token
         url = body['url'].replace('token=', 'token=bad')
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index bce7524..f6c3e73 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -59,8 +59,11 @@
                 self.server_id, validatable=True)
 
     def tearDown(self):
-        self.server_check_teardown()
         super(ServerActionsTestJSON, self).tearDown()
+        # NOTE(zhufl): Because server_check_teardown will raise Exception
+        # which will prevent other cleanup steps from being executed, so
+        # server_check_teardown should be called after super's tearDown.
+        self.server_check_teardown()
 
     @classmethod
     def setup_credentials(cls):
@@ -89,6 +92,7 @@
             validatable=True,
             validation_resources=validation_resources,
             wait_until='ACTIVE')
+        self.addCleanup(self.delete_server, newserver['id'])
         # The server's password should be set to the provided password
         new_password = 'Newpass1234'
         self.client.change_password(newserver['id'], adminPass=new_password)
@@ -197,7 +201,7 @@
         self.assertEqual(self.server_id, rebuilt_server['id'])
         rebuilt_image_id = rebuilt_server['image']['id']
         self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
-        self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+        self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
 
         # Verify the server properties after the rebuild completes
         waiters.wait_for_server_status(self.client,
@@ -251,7 +255,7 @@
         self.assertEqual(self.server_id, rebuilt_server['id'])
         rebuilt_image_id = rebuilt_server['image']['id']
         self.assertEqual(new_image, rebuilt_image_id)
-        self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+        self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
 
         # Verify the server properties after the rebuild completes
         waiters.wait_for_server_status(self.client,
@@ -262,6 +266,11 @@
 
         self.client.start_server(self.server_id)
 
+    # NOTE(mriedem): Marked as slow because while rebuild and volume-backed is
+    # common, we don't actually change the image (you can't with volume-backed
+    # rebuild) so this isn't testing much outside normal rebuild
+    # (and it's slow).
+    @decorators.attr(type='slow')
     @decorators.idempotent_id('b68bd8d6-855d-4212-b59b-2e704044dace')
     @utils.services('volume')
     def test_rebuild_server_with_volume_attached(self):
@@ -280,6 +289,17 @@
         self.assertEqual('in-use', vol_after_rebuild['status'])
         self.assertEqual(self.server_id,
                          vol_after_rebuild['attachments'][0]['server_id'])
+        if CONF.validation.run_validation:
+            validation_resources = self.get_class_validation_resources(
+                self.os_primary)
+            linux_client = remote_client.RemoteClient(
+                self.get_server_ip(server, validation_resources),
+                self.ssh_user,
+                password=None,
+                pkey=validation_resources['keypair']['private_key'],
+                server=server,
+                servers_client=self.client)
+            linux_client.validate_authentication()
 
     def _test_resize_server_confirm(self, server_id, stop=False):
         # The server's RAM and disk space should be modified to that of
@@ -303,7 +323,7 @@
                                        expected_status)
 
         server = self.client.show_server(server_id)['server']
-        self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
+        self.assert_flavor_equal(self.flavor_ref_alt, server['flavor'])
 
         if stop:
             # NOTE(mriedem): tearDown requires the server to be started.
@@ -326,10 +346,22 @@
         server = self.create_test_server(
             volume_backed=True, wait_until='ACTIVE')
         self._test_resize_server_confirm(server['id'])
-        # Now do something interactive with the guest like get its console
-        # output; we don't actually care about the output, just that it doesn't
-        # raise an error.
-        self.client.get_console_output(server['id'])
+        if CONF.compute_feature_enabled.console_output:
+            # Now do something interactive with the guest like get its console
+            # output; we don't actually care about the output,
+            # just that it doesn't raise an error.
+            self.client.get_console_output(server['id'])
+        if CONF.validation.run_validation:
+            validation_resources = self.get_class_validation_resources(
+                self.os_primary)
+            linux_client = remote_client.RemoteClient(
+                self.get_server_ip(server, validation_resources),
+                self.ssh_user,
+                password=None,
+                pkey=validation_resources['keypair']['private_key'],
+                server=server,
+                servers_client=self.client)
+            linux_client.validate_authentication()
 
     @decorators.idempotent_id('138b131d-66df-48c9-a171-64f45eb92962')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
@@ -355,7 +387,43 @@
         waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
 
         server = self.client.show_server(self.server_id)['server']
-        self.assertEqual(self.flavor_ref, server['flavor']['id'])
+        self.assert_flavor_equal(self.flavor_ref, server['flavor'])
+
+    @decorators.idempotent_id('fbbf075f-a812-4022-bc5c-ccb8047eef12')
+    @decorators.related_bug('1737599')
+    @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+                          '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.
+
+        # Create a blank volume and attach it to the server created in setUp.
+        volume = self.create_volume()
+        server = self.client.show_server(self.server_id)['server']
+        self.attach_volume(server, volume)
+        # Now resize the server with the blank volume attached.
+        self.client.resize_server(self.server_id, self.flavor_ref_alt)
+        # Explicitly delete the server to get a new one for later
+        # tests. Avoids resize down race issues.
+        self.addCleanup(self.delete_server, self.server_id)
+        waiters.wait_for_server_status(
+            self.client, self.server_id, 'VERIFY_RESIZE')
+        # Now revert the resize which should move the instance and it's volume
+        # attachment back to the original source compute host.
+        self.client.revert_resize_server(self.server_id)
+        waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+        # Make sure everything still looks OK.
+        server = self.client.show_server(self.server_id)['server']
+        # The flavor id is not returned in the server response after
+        # microversion 2.46 so handle that gracefully.
+        if server['flavor'].get('id'):
+            self.assertEqual(self.flavor_ref, server['flavor']['id'])
+        attached_volumes = server['os-extended-volumes:volumes_attached']
+        self.assertEqual(1, len(attached_volumes))
+        self.assertEqual(volume['id'], attached_volumes[0]['id'])
 
     @decorators.idempotent_id('b963d4f1-94b3-4c40-9e97-7b583f46e470')
     @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -381,7 +449,7 @@
         resp = self.client.create_backup(self.server_id,
                                          backup_type='daily',
                                          rotation=2,
-                                         name=backup1).response
+                                         name=backup1)
         oldest_backup_exist = True
 
         # the oldest one should be deleted automatically in this test
@@ -397,10 +465,10 @@
                                 "deleted during rotation.", oldest_backup)
 
         if api_version_utils.compare_version_header_to_response(
-                "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+                "OpenStack-API-Version", "compute 2.45", resp.response, "lt"):
             image1_id = resp['image_id']
         else:
-            image1_id = data_utils.parse_image_id(resp['location'])
+            image1_id = data_utils.parse_image_id(resp.response['location'])
         self.addCleanup(_clean_oldest_backup, image1_id)
         waiters.wait_for_image_status(glance_client,
                                       image1_id, 'active')
@@ -410,12 +478,12 @@
         resp = self.client.create_backup(self.server_id,
                                          backup_type='daily',
                                          rotation=2,
-                                         name=backup2).response
+                                         name=backup2)
         if api_version_utils.compare_version_header_to_response(
-                "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+                "OpenStack-API-Version", "compute 2.45", resp.response, "lt"):
             image2_id = resp['image_id']
         else:
-            image2_id = data_utils.parse_image_id(resp['location'])
+            image2_id = data_utils.parse_image_id(resp.response['location'])
         self.addCleanup(glance_client.delete_image, image2_id)
         waiters.wait_for_image_status(glance_client,
                                       image2_id, 'active')
@@ -453,12 +521,12 @@
         resp = self.client.create_backup(self.server_id,
                                          backup_type='daily',
                                          rotation=2,
-                                         name=backup3).response
+                                         name=backup3)
         if api_version_utils.compare_version_header_to_response(
-                "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+                "OpenStack-API-Version", "compute 2.45", resp.response, "lt"):
             image3_id = resp['image_id']
         else:
-            image3_id = data_utils.parse_image_id(resp['location'])
+            image3_id = data_utils.parse_image_id(resp.response['location'])
         self.addCleanup(glance_client.delete_image, image3_id)
         # the first back up should be deleted
         waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
@@ -479,10 +547,10 @@
 
     def _get_output(self):
         output = self.client.get_console_output(
-            self.server_id, length=10)['output']
+            self.server_id, length=3)['output']
         self.assertTrue(output, "Console output was empty.")
         lines = len(output.split('\n'))
-        self.assertEqual(lines, 10)
+        self.assertEqual(lines, 3)
 
     @decorators.idempotent_id('4b8867e6-fffa-4d54-b1d1-6fdda57be2f3')
     @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
@@ -513,8 +581,8 @@
 
             # NOTE: This test tries to get full length console log, and the
             # length should be bigger than the one of test_get_console_output.
-            self.assertGreater(lines, 10, "Cannot get enough console log "
-                                          "length. (lines: %s)" % lines)
+            self.assertGreater(lines, 3, "Cannot get enough console log "
+                                         "length. (lines: %s)" % lines)
 
         self.wait_for(_check_full_length_console_log)
 
@@ -570,6 +638,12 @@
         compute.shelve_server(self.client, self.server_id,
                               force_shelve_offload=True)
 
+        def _unshelve_server():
+            server_info = self.client.show_server(self.server_id)['server']
+            if 'SHELVED' in server_info['status']:
+                self.client.unshelve_server(self.server_id)
+        self.addCleanup(_unshelve_server)
+
         server = self.client.show_server(self.server_id)['server']
         image_name = server['name'] + '-shelved'
         params = {'name': image_name}
@@ -585,6 +659,20 @@
         waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
         glance_client.wait_for_resource_deletion(images[0]['id'])
 
+    @decorators.idempotent_id('8cf9f450-a871-42cf-9bef-77eba189c0b0')
+    @decorators.related_bug('1745529')
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+                          'Shelve is not available.')
+    @testtools.skipUnless(CONF.compute_feature_enabled.pause,
+                          'Pause is not available.')
+    def test_shelve_paused_server(self):
+        server = self.create_test_server(wait_until='ACTIVE')
+        self.client.pause_server(server['id'])
+        waiters.wait_for_server_status(self.client, server['id'], 'PAUSED')
+        # Check if Shelve operation is successful on paused server.
+        compute.shelve_server(self.client, server['id'],
+                              force_shelve_offload=True)
+
     @decorators.idempotent_id('af8eafd4-38a7-4a4b-bdbc-75145a580560')
     def test_stop_start_server(self):
         self.client.stop_server(self.server_id)
@@ -622,8 +710,13 @@
         # Get the VNC console of type 'novnc' and 'xvpvnc'
         console_types = ['novnc', 'xvpvnc']
         for console_type in console_types:
-            body = self.client.get_vnc_console(self.server_id,
-                                               type=console_type)['console']
+            if self.is_requested_microversion_compatible('2.5'):
+                body = self.client.get_vnc_console(
+                    self.server_id, type=console_type)['console']
+            else:
+                body = self.client.get_remote_console(
+                    self.server_id, console_type=console_type,
+                    protocol='vnc')['remote_console']
             self.assertEqual(console_type, body['type'])
             self.assertNotEqual('', body['url'])
             self._validate_url(body['url'])
diff --git a/tempest/api/compute/servers/test_server_group.py b/tempest/api/compute/servers/test_server_group.py
index 5286c8f..1b7cb96 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -47,8 +47,16 @@
         super(ServerGroupTestJSON, cls).resource_setup()
         cls.policy = ['affinity']
 
-        cls.created_server_group = cls.create_test_server_group(
-            policy=cls.policy)
+    def setUp(self):
+        super(ServerGroupTestJSON, self).setUp()
+        # TODO(zhufl): After microversion 2.13 project_id and user_id are
+        # added to the body of server_group, and microversion is not used
+        # in resource_setup for now, so we should create server group in setUp
+        # in order to use the same microversion as in testcases till
+        # microversion support in resource_setup is fulfilled.
+        if not hasattr(self, 'created_server_group'):
+            self.__class__.created_server_group = \
+                self.create_test_server_group(policy=self.policy)
 
     def _create_server_group(self, name, policy):
         # create the test server-group with given policy
diff --git a/tempest/api/compute/servers/test_server_password.py b/tempest/api/compute/servers/test_server_password.py
index e7591a5..e6a668a 100644
--- a/tempest/api/compute/servers/test_server_password.py
+++ b/tempest/api/compute/servers/test_server_password.py
@@ -21,19 +21,14 @@
 class ServerPasswordTestJSON(base.BaseV2ComputeTest):
 
     @classmethod
-    def setup_clients(cls):
-        super(ServerPasswordTestJSON, cls).setup_clients()
-        cls.client = cls.servers_client
-
-    @classmethod
     def resource_setup(cls):
         super(ServerPasswordTestJSON, cls).resource_setup()
         cls.server = cls.create_test_server(wait_until="ACTIVE")
 
     @decorators.idempotent_id('f83b582f-62a8-4f22-85b0-0dee50ff783a')
     def test_get_server_password(self):
-        self.client.show_password(self.server['id'])
+        self.servers_client.show_password(self.server['id'])
 
     @decorators.idempotent_id('f8229e8b-b625-4493-800a-bde86ac611ea')
     def test_delete_server_password(self):
-        self.client.delete_password(self.server['id'])
+        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 6f32b46..4f484e2 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -45,6 +45,10 @@
         super(ServerPersonalityTestJSON, cls).setup_clients()
         cls.client = cls.servers_client
 
+    # NOTE(mriedem): Marked as slow because personality (file injection) is
+    # deprecated in nova so we don't care as much about running this all the
+    # time (and it's slow).
+    @decorators.attr(type='slow')
     @decorators.idempotent_id('3cfe87fd-115b-4a02-b942-7dc36a337fdf')
     def test_create_server_with_personality(self):
         file_contents = 'This is a test file.'
@@ -75,6 +79,10 @@
                              linux_client.exec_command(
                                  'sudo cat %s' % file_path))
 
+    # NOTE(mriedem): Marked as slow because personality (file injection) is
+    # deprecated in nova so we don't care as much about running this all the
+    # time (and it's slow).
+    @decorators.attr(type='slow')
     @decorators.idempotent_id('128966d8-71fc-443c-8cab-08e24114ecc9')
     def test_rebuild_server_with_personality(self):
         validation_resources = self.get_test_validation_resources(
@@ -117,6 +125,10 @@
         self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
                           self.create_test_server, personality=personality)
 
+    # NOTE(mriedem): Marked as slow because personality (file injection) is
+    # deprecated in nova so we don't care as much about running this all the
+    # time (and it's slow).
+    @decorators.attr(type='slow')
     @decorators.idempotent_id('52f12ee8-5180-40cc-b417-31572ea3d555')
     def test_can_create_server_with_max_number_personality_files(self):
         # Server should be created successfully if maximum allowed number of
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index b0ef3bc..6629794 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -24,11 +24,11 @@
 CONF = config.CONF
 
 
-class ServerRescueTestJSON(base.BaseV2ComputeTest):
+class ServerRescueTestBase(base.BaseV2ComputeTest):
 
     @classmethod
     def skip_checks(cls):
-        super(ServerRescueTestJSON, cls).skip_checks()
+        super(ServerRescueTestBase, cls).skip_checks()
         if not CONF.compute_feature_enabled.rescue:
             msg = "Server rescue not available."
             raise cls.skipException(msg)
@@ -36,11 +36,11 @@
     @classmethod
     def setup_credentials(cls):
         cls.set_network_resources(network=True, subnet=True, router=True)
-        super(ServerRescueTestJSON, cls).setup_credentials()
+        super(ServerRescueTestBase, cls).setup_credentials()
 
     @classmethod
     def resource_setup(cls):
-        super(ServerRescueTestJSON, cls).resource_setup()
+        super(ServerRescueTestBase, cls).resource_setup()
 
         password = data_utils.rand_password()
         server = cls.create_test_server(adminPass=password,
@@ -50,6 +50,9 @@
                                        'RESCUE')
         cls.rescued_server_id = server['id']
 
+
+class ServerRescueTestJSON(ServerRescueTestBase):
+
     @decorators.idempotent_id('fd032140-714c-42e4-a8fd-adcd8df06be6')
     def test_rescue_unrescue_instance(self):
         password = data_utils.rand_password()
@@ -62,6 +65,15 @@
         waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'ACTIVE')
 
+
+class ServerRescueTestJSONUnderV235(ServerRescueTestBase):
+
+    max_microversion = '2.35'
+
+    # TODO(zhufl): After 2.35 we should switch to neutron client to create
+    # floating ip, but that will need admin credential, so the testcases will
+    # have to be added in somewhere in admin directory.
+
     @decorators.idempotent_id('4842e0cf-e87d-4d9d-b61f-f4791da3cacc')
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 1260c6b..caceb64 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -43,7 +43,6 @@
     @classmethod
     def resource_setup(cls):
         super(ServerRescueNegativeTestJSON, cls).resource_setup()
-        cls.device = CONF.compute.volume_device_name
         cls.password = data_utils.rand_password()
         rescue_password = data_utils.rand_password()
         # Server for negative tests
@@ -125,8 +124,7 @@
         self.assertRaises(lib_exc.Conflict,
                           self.servers_client.attach_volume,
                           self.server_id,
-                          volumeId=volume['id'],
-                          device='/dev/%s' % self.device)
+                          volumeId=volume['id'])
 
     @decorators.idempotent_id('f56e465b-fe10-48bf-b75d-646cda3a8bc9')
     @utils.services('volume')
@@ -136,7 +134,7 @@
 
         # Attach the volume to the server
         server = self.servers_client.show_server(self.server_id)['server']
-        self.attach_volume(server, volume, device='/dev/%s' % self.device)
+        self.attach_volume(server, volume)
 
         # Rescue the server
         self.servers_client.rescue_server(self.server_id,
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index c9ee671..e8b1161 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -19,7 +19,6 @@
 from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
@@ -40,11 +39,7 @@
         # 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(waiters.wait_for_server_termination,
-                        self.servers_client, server['id'])
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.servers_client.delete_server, server['id'])
+        self.addCleanup(self.delete_server, server['id'])
 
         # Verify the password is set correctly in the response
         self.assertEqual('testpassword', server['adminPass'])
@@ -59,19 +54,11 @@
         server = self.create_test_server(name=server_name,
                                          wait_until='ACTIVE')
         id1 = server['id']
-        self.addCleanup(waiters.wait_for_server_termination,
-                        self.servers_client, id1)
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.servers_client.delete_server, id1)
+        self.addCleanup(self.delete_server, id1)
         server = self.create_test_server(name=server_name,
                                          wait_until='ACTIVE')
         id2 = server['id']
-        self.addCleanup(waiters.wait_for_server_termination,
-                        self.servers_client, id2)
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.servers_client.delete_server, id2)
+        self.addCleanup(self.delete_server, id2)
         self.assertNotEqual(id1, id2, "Did not create a new server")
         server = self.client.show_server(id1)['server']
         name1 = server['name']
@@ -87,13 +74,9 @@
         self.keypairs_client.create_keypair(name=key_name)
         self.addCleanup(self.keypairs_client.delete_keypair, key_name)
         self.keypairs_client.list_keypairs()
-        server = self.create_test_server(key_name=key_name)
-        self.addCleanup(waiters.wait_for_server_termination,
-                        self.servers_client, server['id'])
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.servers_client.delete_server, server['id'])
-        waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
+        server = self.create_test_server(key_name=key_name,
+                                         wait_until='ACTIVE')
+        self.addCleanup(self.delete_server, server['id'])
         server = self.client.show_server(server['id'])['server']
         self.assertEqual(key_name, server['key_name'])
 
@@ -115,11 +98,7 @@
     def test_update_server_name(self):
         # The server name should be changed to the provided value
         server = self.create_test_server(wait_until='ACTIVE')
-        self.addCleanup(waiters.wait_for_server_termination,
-                        self.servers_client, server['id'])
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.servers_client.delete_server, server['id'])
+        self.addCleanup(self.delete_server, server['id'])
         # Update instance name with non-ASCII characters
         prefix_name = u'\u00CD\u00F1st\u00E1\u00F1c\u00E9'
         self._update_server_name(server['id'], 'ACTIVE', prefix_name)
@@ -137,11 +116,7 @@
     def test_update_access_server_address(self):
         # The server's access addresses should reflect the provided values
         server = self.create_test_server(wait_until='ACTIVE')
-        self.addCleanup(waiters.wait_for_server_termination,
-                        self.servers_client, server['id'])
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.servers_client.delete_server, server['id'])
+        self.addCleanup(self.delete_server, server['id'])
 
         # Update the IPv4 and IPv6 access addresses
         self.client.update_server(server['id'],
@@ -157,23 +132,82 @@
     @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).
-        server = self.create_test_server(accessIPv6='2001:2001::3')
-        self.addCleanup(waiters.wait_for_server_termination,
-                        self.servers_client, server['id'])
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.servers_client.delete_server, server['id'])
-        waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
+        server = self.create_test_server(accessIPv6='2001:2001::3',
+                                         wait_until='ACTIVE')
+        self.addCleanup(self.delete_server, server['id'])
         server = self.client.show_server(server['id'])['server']
         self.assertEqual('2001:2001::3', server['accessIPv6'])
 
+    @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
+
+        # 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):
     min_microversion = '2.47'
     max_microversion = 'latest'
 
+    # NOTE(gmann): This test tests the server APIs response schema
+    # Along with 2.47 microversion schema this test class tests the
+    # other microversions 2.9, 2.19 and 2.26 server APIs response schema
+    # also. 2.47 APIs schema are on top of 2.9->2.19->2.26 schema so
+    # below tests cover all of the schema.
+
     @decorators.idempotent_id('88b0bdb2-494c-11e7-a919-92ebcb67fe33')
     def test_show_server(self):
         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):
+        server = self.create_test_server()
+        # Checking update API response schema
+        self.servers_client.update_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client, server['id'],
+                                       'ACTIVE')
+        # Checking rebuild API response schema
+        self.servers_client.rebuild_server(server['id'], self.image_ref_alt)
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+        # Checking list details API response schema
+        self.servers_client.list_servers(detail=True)
+
+
+class ServerShowV263Test(base.BaseV2ComputeTest):
+    min_microversion = '2.63'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('71b8e3d5-11d2-494f-b917-b094a4afed3c')
+    def test_show_update_rebuild_list_server(self):
+        trusted_certs = ['test-cert-1', 'test-cert-2']
+        server = self.create_test_server(
+            trusted_image_certificates=trusted_certs,
+            wait_until='ACTIVE')
+
+        # Check show API response schema
+        self.servers_client.show_server(server['id'])['server']
+
+        # Check update API response schema
+        self.servers_client.update_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+
+        # Check rebuild API response schema
+        self.servers_client.rebuild_server(server['id'], self.image_ref_alt)
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+
+        # Check list details API response schema
+        params = {'trusted_image_certificates': trusted_certs}
+        servers = self.servers_client.list_servers(
+            detail=True, **params)['servers']
+        self.assertNotEmpty(servers)
diff --git a/tempest/api/compute/servers/test_servers_microversions.py b/tempest/api/compute/servers/test_servers_microversions.py
new file mode 100644
index 0000000..2434884
--- /dev/null
+++ b/tempest/api/compute/servers/test_servers_microversions.py
@@ -0,0 +1,66 @@
+# Copyright 2018 NEC Corporation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.compute import base
+from tempest.common import waiters
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+# NOTE(gmann): This file is to write the tests which mainly
+# tests newly added microversion schema related to servers APIs.
+# As per (https://docs.openstack.org/tempest/latest/microversion_testing.
+# html#tempest-scope-for-microversion-testing),
+# we need to fill the API response schema gaps which gets modified
+# during microversion change. To cover the testing of such schema
+# we need to have operation schema test which just test
+# the microversion schemas.
+# If you are adding server APIs microversion schema file without
+# their integration tests, you can add tests to cover those schema
+# in this file.
+
+
+class ServerShowV254Test(base.BaseV2ComputeTest):
+    min_microversion = '2.54'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('09170a98-4940-4637-add7-1a35121f1a5a')
+    def test_rebuild_server(self):
+        server = self.create_test_server(wait_until='ACTIVE')
+        keypair_name = data_utils.rand_name(
+            self.__class__.__name__ + '-keypair')
+        kwargs = {'name': keypair_name}
+        self.keypairs_client.create_keypair(**kwargs)
+        self.addCleanup(self.keypairs_client.delete_keypair,
+                        keypair_name)
+        # Checking rebuild API response schema
+        self.servers_client.rebuild_server(server['id'], self.image_ref_alt,
+                                           key_name=keypair_name)
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+
+
+class ServerShowV257Test(base.BaseV2ComputeTest):
+    min_microversion = '2.57'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('803df848-080a-4261-8f11-b020cd9b6f60')
+    def test_rebuild_server(self):
+        server = self.create_test_server(wait_until='ACTIVE')
+        user_data = "ZWNobyAiaGVsbG8gd29ybGQi"
+        # Checking rebuild API response schema
+        self.servers_client.rebuild_server(server['id'], self.image_ref_alt,
+                                           user_data=user_data)
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index d067bb3..6cabf65 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -40,8 +40,11 @@
             self.__class__.server_id = self.recreate_server(self.server_id)
 
     def tearDown(self):
-        self.server_check_teardown()
         super(ServersNegativeTestJSON, self).tearDown()
+        # NOTE(zhufl): Because server_check_teardown will raise Exception
+        # which will prevent other cleanup steps from being executed, so
+        # server_check_teardown should be called after super's tearDown.
+        self.server_check_teardown()
 
     @classmethod
     def setup_clients(cls):
@@ -477,10 +480,19 @@
         # shelve a shelved server.
         compute.shelve_server(self.client, self.server_id)
 
+        def _unshelve_server():
+            server_info = self.client.show_server(self.server_id)['server']
+            if 'SHELVED' in server_info['status']:
+                self.client.unshelve_server(self.server_id)
+        self.addCleanup(_unshelve_server)
+
         server = self.client.show_server(self.server_id)['server']
         image_name = server['name'] + '-shelved'
-        params = {'name': image_name}
-        images = self.compute_images_client.list_images(**params)['images']
+        if CONF.image_feature_enabled.api_v1:
+            kwargs = {'name': image_name}
+        else:
+            kwargs = {'params': {'name': image_name}}
+        images = self.images_client.list_images(**kwargs)['images']
         self.assertEqual(1, len(images))
         self.assertEqual(image_name, images[0]['name'])
 
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 90f04ff..f810ec5 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -25,7 +25,12 @@
 CONF = config.CONF
 
 
+# TODO(mriedem): Remove this test class once the nova queens branch goes into
+# extended maintenance mode.
 class VirtualInterfacesTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.43'
+
+    depends_on_nova_network = True
 
     @classmethod
     def setup_credentials(cls):
@@ -50,8 +55,6 @@
         # for a given server_id
 
         if CONF.service_available.neutron:
-            # TODO(mriedem): After a microversion implements the API for
-            # neutron, a 400 should be a failure for nova-network and neutron.
             with testtools.ExpectedException(exceptions.BadRequest):
                 self.client.list_virtual_interfaces(self.server['id'])
         else:
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
index 20923a8..f6e8bc9 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -20,7 +20,12 @@
 from tempest.lib import exceptions as lib_exc
 
 
+# TODO(mriedem): Remove this test class once the nova queens branch goes into
+# extended maintenance mode.
 class VirtualInterfacesNegativeTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.43'
+
+    depends_on_nova_network = True
 
     @classmethod
     def setup_credentials(cls):
@@ -28,11 +33,6 @@
         cls.set_network_resources()
         super(VirtualInterfacesNegativeTestJSON, cls).setup_credentials()
 
-    @classmethod
-    def setup_clients(cls):
-        super(VirtualInterfacesNegativeTestJSON, cls).setup_clients()
-        cls.client = cls.servers_client
-
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('64ebd03c-1089-4306-93fa-60f5eb5c803c')
     @utils.services('network')
@@ -41,5 +41,5 @@
         # for an invalid server_id
         invalid_server_id = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound,
-                          self.client.list_virtual_interfaces,
+                          self.servers_client.list_virtual_interfaces,
                           invalid_server_id)
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index 34faf5f..12e7fea 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -37,7 +37,7 @@
         ext = CONF.compute_feature_enabled.api_extensions[0]
 
         # Log extensions list
-        extension_list = map(lambda x: x['alias'], extensions)
+        extension_list = [x['alias'] for x in extensions]
         LOG.debug("Nova extensions: %s", ','.join(extension_list))
 
         if ext == 'all':
diff --git a/tempest/api/compute/test_networks.py b/tempest/api/compute/test_networks.py
index b8c79d7..76131e2 100644
--- a/tempest/api/compute/test_networks.py
+++ b/tempest/api/compute/test_networks.py
@@ -20,6 +20,8 @@
 
 
 class ComputeNetworksTest(base.BaseV2ComputeTest):
+    max_microversion = '2.35'
+
     @classmethod
     def skip_checks(cls):
         super(ComputeNetworksTest, cls).skip_checks()
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index 7cf90ae..a62492d 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -43,14 +43,19 @@
         super(QuotasTestJSON, cls).resource_setup()
         cls.tenant_id = cls.client.tenant_id
         cls.user_id = cls.client.user_id
-        cls.default_quota_set = set(('injected_file_content_bytes',
-                                     'metadata_items', 'injected_files',
-                                     'ram', 'floating_ips',
-                                     'fixed_ips', 'key_pairs',
-                                     'injected_file_path_bytes',
-                                     'instances', 'security_group_rules',
-                                     'cores', 'security_groups',
+        cls.default_quota_set = set(('metadata_items', 'ram', 'key_pairs',
+                                     'instances', 'cores',
                                      'server_group_members', 'server_groups'))
+        if cls.is_requested_microversion_compatible('2.35'):
+            cls.default_quota_set = \
+                cls.default_quota_set | set(['fixed_ips', 'floating_ips',
+                                             'security_group_rules',
+                                             'security_groups'])
+        if cls.is_requested_microversion_compatible('2.56'):
+            cls.default_quota_set = \
+                cls.default_quota_set | set(['injected_file_content_bytes',
+                                             'injected_file_path_bytes',
+                                             'injected_files'])
 
     @decorators.idempotent_id('f1ef0a97-dbbb-4cca-adc5-c9fbc4f76107')
     def test_get_quotas(self):
diff --git a/tempest/api/compute/test_tenant_networks.py b/tempest/api/compute/test_tenant_networks.py
index b55e2c0..f4eada0 100644
--- a/tempest/api/compute/test_tenant_networks.py
+++ b/tempest/api/compute/test_tenant_networks.py
@@ -18,6 +18,7 @@
 
 
 class ComputeTenantNetworksTest(base.BaseV2ComputeTest):
+    max_microversion = '2.35'
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 9bef80f..f83e62c 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -13,8 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.api.compute import base
 from tempest.common import compute
+from tempest.common import utils
 from tempest.common.utils.linux import remote_client
 from tempest.common import waiters
 from tempest import config
@@ -23,12 +26,12 @@
 CONF = config.CONF
 
 
-class AttachVolumeTestJSON(base.BaseV2ComputeTest):
-    max_microversion = '2.19'
+class BaseAttachVolumeTest(base.BaseV2ComputeTest):
+    """Base class for the attach volume tests in this module."""
 
     @classmethod
     def skip_checks(cls):
-        super(AttachVolumeTestJSON, cls).skip_checks()
+        super(BaseAttachVolumeTest, cls).skip_checks()
         if not CONF.service_available.cinder:
             skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
@@ -36,11 +39,11 @@
     @classmethod
     def setup_credentials(cls):
         cls.prepare_instance_network()
-        super(AttachVolumeTestJSON, cls).setup_credentials()
+        super(BaseAttachVolumeTest, cls).setup_credentials()
 
     @classmethod
     def resource_setup(cls):
-        super(AttachVolumeTestJSON, cls).resource_setup()
+        super(BaseAttachVolumeTest, cls).resource_setup()
         cls.device = CONF.compute.volume_device_name
 
     def _create_server(self):
@@ -58,7 +61,12 @@
             server['id'])['addresses']
         return server, validation_resources
 
+
+class AttachVolumeTestJSON(BaseAttachVolumeTest):
+
     @decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
+    # This test is conditionally marked slow if SSH validation is enabled.
+    @decorators.attr(type='slow', condition=CONF.validation.run_validation)
     def test_attach_detach_volume(self):
         # Stop and Start a server with an attached volume, ensuring that
         # the volume remains attached.
@@ -78,6 +86,11 @@
             linux_client.validate_authentication()
 
         volume = self.create_volume()
+
+        # NOTE: As of the 12.0.0 Liberty release, the Nova libvirt driver
+        # no longer honors a user-supplied device name, in that case
+        # CONF.compute.volume_device_name must be set the equal value as
+        # the libvirt auto-assigned one
         attachment = self.attach_volume(server, volume,
                                         device=('/dev/%s' % self.device))
 
@@ -113,10 +126,9 @@
     @decorators.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513')
     def test_list_get_volume_attachments(self):
         # List volume attachment of the server
-        server, _ = self._create_server()
+        server, validation_resources = self._create_server()
         volume_1st = self.create_volume()
-        attachment_1st = self.attach_volume(server, volume_1st,
-                                            device=('/dev/%s' % self.device))
+        attachment_1st = self.attach_volume(server, volume_1st)
         body = self.servers_client.list_volume_attachments(
             server['id'])['volumeAttachments']
         self.assertEqual(1, len(body))
@@ -137,6 +149,16 @@
             server['id'])['volumeAttachments']
         self.assertEqual(2, len(body))
 
+        if CONF.validation.run_validation:
+            linux_client = remote_client.RemoteClient(
+                self.get_server_ip(server, validation_resources),
+                self.image_ssh_user,
+                self.image_ssh_password,
+                validation_resources['keypair']['private_key'],
+                server=server,
+                servers_client=self.servers_client)
+            linux_client.validate_authentication()
+
         for attachment in [attachment_1st, attachment_2nd]:
             body = self.servers_client.show_volume_attachment(
                 server['id'], attachment['id'])['volumeAttachment']
@@ -149,11 +171,14 @@
                 self.volumes_client, attachment['volumeId'], 'available')
 
 
-class AttachVolumeShelveTestJSON(AttachVolumeTestJSON):
+class AttachVolumeShelveTestJSON(BaseAttachVolumeTest):
     """Testing volume with shelved instance.
 
     This test checks the attaching and detaching volumes from
     a shelved or shelved offload instance.
+
+    Note that these are uncommon scenarios until blueprint detach-boot-volume
+    is implemented in the compute service.
     """
 
     min_microversion = '2.20'
@@ -214,6 +239,9 @@
                 server, validation_resources)
             self.assertEqual(number_of_volumes, counted_volumes)
 
+    # NOTE(mriedem): Marked as slow since this is an uncommon scenario until
+    # attach/detach root volume is supported in nova, and it's slow.
+    @decorators.attr(type='slow')
     @decorators.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee')
     def test_attach_volume_shelved_or_offload_server(self):
         # Create server, count number of volumes on it, shelve
@@ -222,9 +250,7 @@
         volume = self.create_volume()
         num_vol = self._count_volumes(server, validation_resources)
         self._shelve_server(server, validation_resources)
-        attachment = self.attach_volume(server, volume,
-                                        device=('/dev/%s' % self.device),
-                                        check_reserved=True)
+        attachment = self.attach_volume(server, volume)
 
         # Unshelve the instance and check that attached volume exists
         self._unshelve_server_and_check_volumes(
@@ -240,6 +266,9 @@
         # case of shelved_offloaded.
         self.assertIsNotNone(volume_attachment['device'])
 
+    # NOTE(mriedem): Marked as slow since this is an uncommon scenario until
+    # attach/detach root volume is supported in nova, and it's slow.
+    @decorators.attr(type='slow')
     @decorators.idempotent_id('b54e86dd-a070-49c4-9c07-59ae6dae15aa')
     def test_detach_volume_shelved_or_offload_server(self):
         # Count number of volumes on instance, shelve
@@ -250,8 +279,7 @@
         self._shelve_server(server, validation_resources)
 
         # Attach and then detach the volume
-        self.attach_volume(server, volume, device=('/dev/%s' % self.device),
-                           check_reserved=True)
+        self.attach_volume(server, volume)
         self.servers_client.detach_volume(server['id'], volume['id'])
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume['id'], 'available')
@@ -260,3 +288,177 @@
         # volume(s)
         self._unshelve_server_and_check_volumes(
             server, validation_resources, num_vol)
+
+
+class AttachVolumeMultiAttachTest(BaseAttachVolumeTest):
+    min_microversion = '2.60'
+    max_microversion = 'latest'
+
+    @classmethod
+    def skip_checks(cls):
+        super(AttachVolumeMultiAttachTest, cls).skip_checks()
+        if not CONF.compute_feature_enabled.volume_multiattach:
+            raise cls.skipException('Volume multi-attach is not available.')
+
+    def _attach_volume_to_servers(self, volume, servers):
+        """Attaches the given volume to the list of servers.
+
+        :param volume: The multiattach volume to use.
+        :param servers: list of server instances on which the volume will be
+                        attached
+        :returns: dict of server ID to volumeAttachment dict entries
+        """
+        attachments = {}
+        for server in servers:
+            # map the server id to the volume attachment
+            attachments[server['id']] = self.attach_volume(server, volume)
+            # NOTE(mriedem): In the case of multi-attach, after the first
+            # attach the volume will be in-use. On the second attach, nova will
+            # 'reserve' the volume which puts it back into 'attaching' status
+            # and then the volume shouldn't go back to in-use until the compute
+            # actually attaches the server to the volume.
+        return attachments
+
+    def _detach_multiattach_volume(self, volume_id, server_id):
+        """Detaches a multiattach volume from the given server.
+
+        Depending on the number of attachments the volume has, this method
+        will wait for the volume to go to back to 'in-use' status if there are
+        more attachments or 'available' state if there are no more attachments.
+        """
+        # Count the number of attachments before starting the detach.
+        volume = self.volumes_client.show_volume(volume_id)['volume']
+        attachments = volume['attachments']
+        wait_status = 'in-use' if len(attachments) > 1 else 'available'
+        # Now detach the volume from the given server.
+        self.servers_client.detach_volume(server_id, volume_id)
+        # Now wait for the volume status to change.
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, volume_id, wait_status)
+
+    def _create_multiattach_volume(self, bootable=False):
+        kwargs = {}
+        if bootable:
+            kwargs['image_ref'] = CONF.compute.image_ref
+        return self.create_volume(multiattach=True, **kwargs)
+
+    def _create_and_multiattach(self):
+        """Creates two server instances and a volume and attaches to both.
+
+        :returns: A three-item tuple of the list of created servers,
+                  the created volume, and dict of server ID to volumeAttachment
+                  dict entries
+        """
+        servers = []
+        for x in range(2):
+            name = 'multiattach-server-%i' % x
+            servers.append(self.create_test_server(name=name))
+
+        # Now wait for the servers to be ACTIVE.
+        for server in servers:
+            waiters.wait_for_server_status(self.servers_client, server['id'],
+                                           'ACTIVE')
+
+        volume = self._create_multiattach_volume()
+
+        # Attach the volume to the servers
+        attachments = self._attach_volume_to_servers(volume, servers)
+        return servers, volume, attachments
+
+    @decorators.idempotent_id('8d5853f7-56e7-4988-9b0c-48cea3c7049a')
+    def test_list_get_volume_attachments_multiattach(self):
+        # Attach a single volume to two servers.
+        servers, volume, attachments = self._create_and_multiattach()
+
+        # List attachments from the volume and make sure the server uuids
+        # are in that list.
+        vol_attachments = self.volumes_client.show_volume(
+            volume['id'])['volume']['attachments']
+        attached_server_ids = [attachment['server_id']
+                               for attachment in vol_attachments]
+        self.assertEqual(2, len(attached_server_ids))
+
+        # List Volume attachment of the servers
+        for server in servers:
+            self.assertIn(server['id'], attached_server_ids)
+            vol_attachments = self.servers_client.list_volume_attachments(
+                server['id'])['volumeAttachments']
+            self.assertEqual(1, len(vol_attachments))
+            attachment = attachments[server['id']]
+            self.assertDictEqual(attachment, vol_attachments[0])
+            # Detach the volume from this server.
+            self._detach_multiattach_volume(volume['id'], server['id'])
+
+    def _boot_from_multiattach_volume(self):
+        """Boots a server from a multiattach volume.
+
+        The volume will not be deleted when the server is deleted.
+
+        :returns: 2-item tuple of (server, volume)
+        """
+        volume = self._create_multiattach_volume(bootable=True)
+        # Now create a server from the bootable volume.
+        bdm = [{
+            'uuid': volume['id'],
+            'source_type': 'volume',
+            'destination_type': 'volume',
+            'boot_index': 0,
+            'delete_on_termination': False}]
+        server = self.create_test_server(
+            image_id='', block_device_mapping_v2=bdm, wait_until='ACTIVE')
+        # Assert the volume is attached to the server.
+        attachments = self.servers_client.list_volume_attachments(
+            server['id'])['volumeAttachments']
+        self.assertEqual(1, len(attachments))
+        self.assertEqual(volume['id'], attachments[0]['volumeId'])
+        return server, volume
+
+    @decorators.idempotent_id('65e33aa2-185b-44c8-b22e-e524973ed625')
+    def test_boot_from_multiattach_volume(self):
+        """Simple test to boot an instance from a multiattach volume."""
+        self._boot_from_multiattach_volume()
+
+    @utils.services('image')
+    @decorators.idempotent_id('885ac48a-2d7a-40c5-ae8b-1993882d724c')
+    def test_snapshot_volume_backed_multiattach(self):
+        """Boots a server from a multiattach volume and snapshots the server.
+
+        Creating the snapshot of the server will also create a snapshot of
+        the volume.
+        """
+        server, volume = self._boot_from_multiattach_volume()
+        # Create a snapshot of the server (and volume implicitly).
+        self.create_image_from_server(
+            server['id'], name='multiattach-snapshot',
+            wait_until='active', wait_for_server=True)
+        # TODO(mriedem): Make sure the volume snapshot exists. This requires
+        # adding the volume snapshots client to BaseV2ComputeTest.
+        # Delete the server, wait for it to be gone, and make sure the volume
+        # still exists.
+        self.servers_client.delete_server(server['id'])
+        waiters.wait_for_server_termination(self.servers_client, server['id'])
+        # Delete the volume and cascade the delete of the volume snapshot.
+        self.volumes_client.delete_volume(volume['id'], cascade=True)
+        # Now we have to wait for the volume to be gone otherwise the normal
+        # teardown will fail since it will race with our call and the snapshot
+        # might still exist.
+        self.volumes_client.wait_for_resource_deletion(volume['id'])
+
+    @decorators.idempotent_id('f01c7169-a124-4fc7-ae60-5e380e247c9c')
+    @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+                          'Resize not available.')
+    def test_resize_server_with_multiattached_volume(self):
+        # Attach a single volume to multiple servers, then resize the servers
+        servers, volume, _ = self._create_and_multiattach()
+
+        for server in servers:
+            self.resize_server(server['id'], self.flavor_ref_alt)
+
+        for server in servers:
+            self._detach_multiattach_volume(volume['id'], server['id'])
+
+    # TODO(mriedem): Might be interesting to create a bootable multiattach
+    # volume with delete_on_termination=True, create server1 from the
+    # volume, then attach it to server2, and then delete server1 in which
+    # case the volume won't be deleted because it's still attached to
+    # server2 and make sure the volume is still attached to server2.
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index eabb907..6d08f90 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -35,9 +35,35 @@
     def test_delete_attached_volume(self):
         server = self.create_test_server(wait_until='ACTIVE')
         volume = self.create_volume()
-
-        path = "/dev/%s" % CONF.compute.volume_device_name
-        self.attach_volume(server, volume, device=path)
+        self.attach_volume(server, volume)
 
         self.assertRaises(lib_exc.BadRequest,
                           self.delete_volume, volume['id'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('aab919e2-d992-4cbb-a4ed-745c2475398c')
+    def test_attach_attached_volume_to_same_server(self):
+        # Test attaching the same volume to the same instance once
+        # it's already attached. The nova/cinder validation for this differs
+        # depending on whether or not cinder v3.27 is being used to attach
+        # the volume to the instance.
+        server = self.create_test_server(wait_until='ACTIVE')
+        volume = self.create_volume()
+
+        self.attach_volume(server, volume)
+
+        self.assertRaises(lib_exc.BadRequest,
+                          self.attach_volume, server, volume)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('ee37a796-2afb-11e7-bc0f-fa163e65f5ce')
+    def test_attach_attached_volume_to_different_server(self):
+        server1 = self.create_test_server(wait_until='ACTIVE')
+        volume = self.create_volume()
+
+        self.attach_volume(server1, volume)
+
+        # Create server2 and attach in-use volume
+        server2 = self.create_test_server(wait_until='ACTIVE')
+        self.assertRaises(lib_exc.BadRequest,
+                          self.attach_volume, server2, volume)
diff --git a/tempest/api/identity/admin/v2/test_roles.py b/tempest/api/identity/admin/v2/test_roles.py
index 124bb5f..9736a76 100644
--- a/tempest/api/identity/admin/v2/test_roles.py
+++ b/tempest/api/identity/admin/v2/test_roles.py
@@ -28,14 +28,11 @@
         for _ in range(5):
             role_name = data_utils.rand_name(name='role')
             role = cls.roles_client.create_role(name=role_name)['role']
+            cls.addClassResourceCleanup(
+                test_utils.call_and_ignore_notfound_exc,
+                cls.roles_client.delete_role, role['id'])
             cls.roles.append(role)
 
-    @classmethod
-    def resource_cleanup(cls):
-        super(RolesTestJSON, cls).resource_cleanup()
-        for role in cls.roles:
-            cls.roles_client.delete_role(role['id'])
-
     def _get_role_params(self):
         user = self.setup_test_user()
         tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index e2ed5ef..03543ac 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -89,14 +89,10 @@
             service = self.services_client.create_service(
                 name=name, type=s_type,
                 description=description)['OS-KSADM:service']
+            self.addCleanup(self.services_client.delete_service, service['id'])
             services.append(service)
         service_ids = [svc['id'] for svc in services]
 
-        def delete_services():
-            for service_id in service_ids:
-                self.services_client.delete_service(service_id)
-
-        self.addCleanup(delete_services)
         # List and Verify Services
         body = self.services_client.list_services()['OS-KSADM:services']
         found = [serv for serv in body if serv['id'] in service_ids]
diff --git a/tempest/api/identity/admin/v2/test_tenants.py b/tempest/api/identity/admin/v2/test_tenants.py
index 0f955bf..f68754e 100644
--- a/tempest/api/identity/admin/v2/test_tenants.py
+++ b/tempest/api/identity/admin/v2/test_tenants.py
@@ -50,7 +50,7 @@
                          'been sent in response for create')
         body = self.tenants_client.show_tenant(tenant_id)['tenant']
         desc2 = body['description']
-        self.assertEqual(desc2, tenant_desc, 'Description does not appear'
+        self.assertEqual(desc2, tenant_desc, 'Description does not appear '
                          'to be set')
         self.tenants_client.delete_tenant(tenant_id)
 
@@ -59,11 +59,9 @@
         # Create a tenant that is enabled
         tenant = self.setup_test_tenant(enabled=True)
         tenant_id = tenant['id']
-        en1 = tenant['enabled']
-        self.assertTrue(en1, 'Enable should be True in response')
+        self.assertTrue(tenant['enabled'], 'Enable should be True in response')
         body = self.tenants_client.show_tenant(tenant_id)['tenant']
-        en2 = body['enabled']
-        self.assertTrue(en2, 'Enable should be True in lookup')
+        self.assertTrue(body['enabled'], 'Enable should be True in lookup')
         self.tenants_client.delete_tenant(tenant_id)
 
     @decorators.idempotent_id('3be22093-b30f-499d-b772-38340e5e16fb')
@@ -71,12 +69,10 @@
         # Create a tenant that is not enabled
         tenant = self.setup_test_tenant(enabled=False)
         tenant_id = tenant['id']
-        en1 = tenant['enabled']
-        self.assertEqual('false', str(en1).lower(),
+        self.assertFalse(tenant['enabled'],
                          'Enable should be False in response')
         body = self.tenants_client.show_tenant(tenant_id)['tenant']
-        en2 = body['enabled']
-        self.assertEqual('false', str(en2).lower(),
+        self.assertFalse(body['enabled'],
                          'Enable should be False in lookup')
         self.tenants_client.delete_tenant(tenant_id)
 
@@ -143,7 +139,7 @@
         resp3_en = body['enabled']
 
         self.assertNotEqual(resp1_en, resp3_en)
-        self.assertEqual('false', str(resp1_en).lower())
+        self.assertFalse(tenant['enabled'])
         self.assertEqual(resp2_en, resp3_en)
 
         self.tenants_client.delete_tenant(t_id)
diff --git a/tempest/api/identity/admin/v2/test_tokens.py b/tempest/api/identity/admin/v2/test_tokens.py
index 6b30d23..6ce1a8b 100644
--- a/tempest/api/identity/admin/v2/test_tokens.py
+++ b/tempest/api/identity/admin/v2/test_tokens.py
@@ -112,6 +112,8 @@
 
     @decorators.idempotent_id('ca3ea6f7-ed08-4a61-adbd-96906456ad31')
     def test_list_endpoints_for_token(self):
+        tempest_services = ['keystone', 'nova', 'neutron', 'swift', 'cinder',
+                            'neutron']
         # get a token for the user
         creds = self.os_primary.credentials
         username = creds.username
@@ -125,9 +127,10 @@
         self.assertIsInstance(endpoints, list)
         # Store list of service names
         service_names = [e['name'] for e in endpoints]
-        # Get the list of available services.
+        # Get the list of available services. Keystone is always available.
         available_services = [s[0] for s in list(
-            CONF.service_available.items()) if s[1] is True]
+            CONF.service_available.items()) if s[1] is True] + ['keystone']
         # Verify that all available services are present.
-        for service in available_services:
-            self.assertIn(service, service_names)
+        for service in tempest_services:
+            if service in available_services:
+                self.assertIn(service, service_names)
diff --git a/tempest/api/identity/admin/v3/test_application_credentials.py b/tempest/api/identity/admin/v3/test_application_credentials.py
new file mode 100644
index 0000000..7e802c6
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_application_credentials.py
@@ -0,0 +1,44 @@
+# Copyright 2018 SUSE Linux GmbH
+#
+# 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.lib import decorators
+
+
+class ApplicationCredentialsV3AdminTest(base.BaseApplicationCredentialsV3Test,
+                                        base.BaseIdentityV3AdminTest):
+
+    @decorators.idempotent_id('3b3dd48f-3388-406a-a9e6-4d078a552d0e')
+    def test_create_application_credential_with_roles(self):
+        role = self.setup_test_role()
+        self.os_admin.roles_v3_client.create_user_role_on_project(
+            self.project_id,
+            self.user_id,
+            role['id']
+        )
+
+        app_cred = self.create_application_credential(
+            roles=[{'id': role['id']}])
+        secret = app_cred['secret']
+
+        # Check that the application credential is functional
+        token_id, resp = self.non_admin_token.get_token(
+            app_cred_id=app_cred['id'],
+            app_cred_secret=secret,
+            auth_data=True
+        )
+        self.assertEqual(resp['project']['id'], self.project_id)
+        self.assertEqual(resp['roles'][0]['id'], role['id'])
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index 15b2008..23fe788 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -20,6 +20,10 @@
 
 
 class CredentialsTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def resource_setup(cls):
@@ -27,26 +31,15 @@
         cls.projects = list()
         cls.creds_list = [['project_id', 'user_id', 'id'],
                           ['access', 'secret']]
-        u_name = data_utils.rand_name('user')
-        u_desc = '%s description' % u_name
-        u_email = '%s@testmail.tm' % u_name
-        u_password = data_utils.rand_password()
         for _ in range(2):
-            cls.project = cls.projects_client.create_project(
+            project = cls.projects_client.create_project(
                 data_utils.rand_name('project'),
                 description=data_utils.rand_name('project-desc'))['project']
-            cls.projects.append(cls.project['id'])
-
-        cls.user_body = cls.users_client.create_user(
-            name=u_name, description=u_desc, password=u_password,
-            email=u_email, project_id=cls.projects[0])['user']
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.users_client.delete_user(cls.user_body['id'])
-        for p in cls.projects:
-            cls.projects_client.delete_project(p)
-        super(CredentialsTestJSON, cls).resource_cleanup()
+            cls.addClassResourceCleanup(
+                cls.projects_client.delete_project, project['id'])
+            cls.projects.append(project['id'])
+        cls.user_body = cls.users_client.show_user(
+            cls.os_primary.credentials.user_id)['user']
 
     def _delete_credential(self, cred_id):
         self.creds_client.delete_credential(cred_id)
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index 302a0e5..73fddb7 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -9,6 +9,8 @@
 #    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.identity import base
 from tempest import clients
 from tempest import config
@@ -19,7 +21,7 @@
 CONF = config.CONF
 
 
-class TestDefaultProjectId (base.BaseIdentityV3AdminTest):
+class TestDefaultProjectId(base.BaseIdentityV3AdminTest):
 
     @classmethod
     def setup_credentials(cls):
@@ -32,6 +34,10 @@
         self.domains_client.update_domain(domain_id, enabled=False)
         self.domains_client.delete_domain(domain_id)
 
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an '
+                      'immutable user source and solely '
+                      'provides read-only access to users.')
     @decorators.idempotent_id('d6110661-6a71-49a7-a453-b5e26640ff6d')
     def test_default_project_id(self):
         # create a domain
@@ -51,9 +57,10 @@
         # create a user in the domain, with the previous project as his
         # default project
         user_name = data_utils.rand_name('user')
+        user_pass = data_utils.rand_password()
         user_body = self.users_client.create_user(
             name=user_name,
-            password=user_name,
+            password=user_pass,
             domain_id=dom_id,
             default_project_id=proj_id)['user']
         user_id = user_body['id']
@@ -72,7 +79,7 @@
 
         # create a new client with user's credentials (NOTE: unscoped token!)
         creds = auth.KeystoneV3Credentials(username=user_name,
-                                           password=user_name,
+                                           password=user_pass,
                                            user_domain_name=dom_name)
         auth_provider = clients.get_auth_provider(creds)
         creds = auth_provider.fill_credentials()
diff --git a/tempest/api/identity/admin/v3/test_domain_configuration.py b/tempest/api/identity/admin/v3/test_domain_configuration.py
index f731697..c0b18ca 100644
--- a/tempest/api/identity/admin/v3/test_domain_configuration.py
+++ b/tempest/api/identity/admin/v3/test_domain_configuration.py
@@ -21,6 +21,10 @@
 
 
 class DomainConfigurationTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     custom_config = {
         "identity": {
@@ -37,18 +41,6 @@
         super(DomainConfigurationTestJSON, cls).setup_clients()
         cls.client = cls.domain_config_client
 
-    @classmethod
-    def resource_setup(cls):
-        super(DomainConfigurationTestJSON, cls).resource_setup()
-        cls.group = cls.groups_client.create_group(
-            name=data_utils.rand_name('group'),
-            description=data_utils.rand_name('group-desc'))['group']
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.groups_client.delete_group(cls.group['id'])
-        super(DomainConfigurationTestJSON, cls).resource_cleanup()
-
     def _create_domain_and_config(self, config):
         domain = self.setup_test_domain()
         config = self.client.create_domain_config(domain['id'], **config)[
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index bf04ede..07175f4 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -18,6 +18,7 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions
 
 CONF = config.CONF
 
@@ -34,19 +35,6 @@
             domain = cls.create_domain(enabled=i < 2)
             cls.setup_domains.append(domain)
 
-    @classmethod
-    def resource_cleanup(cls):
-        for domain in cls.setup_domains:
-            cls._delete_domain(domain['id'])
-        super(DomainsTestJSON, cls).resource_cleanup()
-
-    @classmethod
-    def _delete_domain(cls, domain_id):
-        # It is necessary to disable the domain before deleting,
-        # or else it would result in unauthorized error
-        cls.domains_client.update_domain(domain_id, enabled=False)
-        cls.domains_client.delete_domain(domain_id)
-
     @decorators.idempotent_id('8cf516ef-2114-48f1-907b-d32726c734d4')
     def test_list_domains(self):
         # Test to list domains
@@ -92,7 +80,7 @@
         domain = self.domains_client.create_domain(
             name=d_name, description=d_desc)['domain']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self._delete_domain, domain['id'])
+                        self.delete_domain, domain['id'])
         self.assertIn('description', domain)
         self.assertIn('name', domain)
         self.assertIn('enabled', domain)
@@ -128,6 +116,22 @@
         domains_list = [d['id'] for d in body]
         self.assertNotIn(domain['id'], domains_list)
 
+    @decorators.idempotent_id('d8d318b7-d1b3-4c37-94c5-3c5ba0b121ea')
+    def test_domain_delete_cascades_content(self):
+        # Create a domain with a user and a group in it
+        domain = self.setup_test_domain()
+        user = self.create_test_user(domain_id=domain['id'])
+        group = self.setup_test_group(domain_id=domain['id'])
+        # Delete the domain
+        self.delete_domain(domain['id'])
+        # Check the domain, its users and groups are gone
+        self.assertRaises(exceptions.NotFound,
+                          self.domains_client.show_domain, domain['id'])
+        self.assertRaises(exceptions.NotFound,
+                          self.users_client.show_user, user['id'])
+        self.assertRaises(exceptions.NotFound,
+                          self.groups_client.show_group, group['id'])
+
     @decorators.idempotent_id('036df86e-bb5d-42c0-a7c2-66b9db3a6046')
     def test_create_domain_with_disabled_status(self):
         # Create domain with enabled status as false
@@ -145,22 +149,7 @@
         # Create domain only with name
         d_name = data_utils.rand_name('domain')
         domain = self.domains_client.create_domain(name=d_name)['domain']
-        self.addCleanup(self._delete_domain, domain['id'])
+        self.addCleanup(self.delete_domain, domain['id'])
         expected_data = {'name': d_name, 'enabled': True}
         self.assertEqual('', domain['description'])
         self.assertDictContainsSubset(expected_data, domain)
-
-
-class DefaultDomainTestJSON(base.BaseIdentityV3AdminTest):
-
-    @classmethod
-    def resource_setup(cls):
-        cls.domain_id = CONF.identity.default_domain_id
-        super(DefaultDomainTestJSON, cls).resource_setup()
-
-    @decorators.attr(type='smoke')
-    @decorators.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
-    def test_default_domain_exists(self):
-        domain = self.domains_client.show_domain(self.domain_id)['domain']
-
-        self.assertTrue(domain['enabled'])
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index 56f7d32..b3c68fb 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -20,6 +20,10 @@
 
 
 class DomainsNegativeTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @decorators.attr(type=['negative', 'gate'])
     @decorators.idempotent_id('1f3fbff5-4e44-400d-9ca1-d953f05f609b')
diff --git a/tempest/api/identity/admin/v3/test_endpoint_groups.py b/tempest/api/identity/admin/v3/test_endpoint_groups.py
index 49dbba1..7d85dc9 100644
--- a/tempest/api/identity/admin/v3/test_endpoint_groups.py
+++ b/tempest/api/identity/admin/v3/test_endpoint_groups.py
@@ -15,10 +15,15 @@
 
 from tempest.api.identity import base
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 
 class EndPointGroupsTest(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def setup_clients(cls):
@@ -28,11 +33,12 @@
     @classmethod
     def resource_setup(cls):
         super(EndPointGroupsTest, cls).resource_setup()
-        cls.service_ids = list()
         cls.endpoint_groups = list()
 
         # Create endpoint group so as to use it for LIST test
         service_id = cls._create_service()
+        cls.addClassResourceCleanup(
+            cls.services_client.delete_service, service_id)
 
         name = data_utils.rand_name('service_group')
         description = data_utils.rand_name('description')
@@ -42,18 +48,12 @@
             name=name,
             description=description,
             filters=filters)['endpoint_group']
+        cls.addClassResourceCleanup(
+            cls.client.delete_endpoint_group, endpoint_group['id'])
 
         cls.endpoint_groups.append(endpoint_group)
 
     @classmethod
-    def resource_cleanup(cls):
-        for e in cls.endpoint_groups:
-            cls.client.delete_endpoint_group(e['id'])
-        for s in cls.service_ids:
-            cls.services_client.delete_service(s)
-        super(EndPointGroupsTest, cls).resource_cleanup()
-
-    @classmethod
     def _create_service(cls):
         s_name = data_utils.rand_name('service')
         s_type = data_utils.rand_name('type')
@@ -64,12 +64,12 @@
                                                description=s_description))
 
         service_id = service_data['service']['id']
-        cls.service_ids.append(service_id)
         return service_id
 
     @decorators.idempotent_id('7c69e7a1-f865-402d-a2ea-44493017315a')
     def test_create_list_show_check_delete_endpoint_group(self):
         service_id = self._create_service()
+        self.addCleanup(self.services_client.delete_service, service_id)
         name = data_utils.rand_name('service_group')
         description = data_utils.rand_name('description')
         filters = {'service_id': service_id}
@@ -78,6 +78,9 @@
             name=name,
             description=description,
             filters=filters)['endpoint_group']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.client.delete_endpoint_group, endpoint_group['id'])
 
         self.endpoint_groups.append(endpoint_group)
 
@@ -115,7 +118,6 @@
 
         # Deleting the endpoint group created in this method
         self.client.delete_endpoint_group(endpoint_group['id'])
-        self.endpoint_groups.remove(endpoint_group)
 
         # Checking whether endpoint group is deleted successfully
         fetched_endpoints = \
@@ -128,6 +130,7 @@
         # Creating an endpoint group so as to check update endpoint group
         # with new values
         service1_id = self._create_service()
+        self.addCleanup(self.services_client.delete_service, service1_id)
         name = data_utils.rand_name('service_group')
         description = data_utils.rand_name('description')
         filters = {'service_id': service1_id}
@@ -136,10 +139,12 @@
             name=name,
             description=description,
             filters=filters)['endpoint_group']
-        self.endpoint_groups.append(endpoint_group)
+        self.addCleanup(self.client.delete_endpoint_group,
+                        endpoint_group['id'])
 
         # Creating new attr values to update endpoint group
         service2_id = self._create_service()
+        self.addCleanup(self.services_client.delete_service, service2_id)
         name2 = data_utils.rand_name('service_group2')
         description2 = data_utils.rand_name('description2')
         filters = {'service_id': service2_id}
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 5d48f68..2cd8906 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -15,10 +15,15 @@
 
 from tempest.api.identity import base
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 
 class EndPointsTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def setup_clients(cls):
@@ -34,12 +39,18 @@
         interfaces = ['public', 'internal']
         cls.setup_endpoint_ids = list()
         for i in range(2):
-            cls._create_service()
+            service = cls._create_service()
+            cls.service_ids.append(service['id'])
+            cls.addClassResourceCleanup(
+                cls.services_client.delete_service, service['id'])
+
             region = data_utils.rand_name('region')
             url = data_utils.rand_url()
             endpoint = cls.client.create_endpoint(
                 service_id=cls.service_ids[i], interface=interfaces[i],
                 url=url, region=region, enabled=True)['endpoint']
+            cls.addClassResourceCleanup(
+                cls.client.delete_endpoint, endpoint['id'])
             cls.setup_endpoint_ids.append(endpoint['id'])
 
     @classmethod
@@ -53,17 +64,7 @@
         service_data = (
             cls.services_client.create_service(name=s_name, type=s_type,
                                                description=s_description))
-        service = service_data['service']
-        cls.service_ids.append(service['id'])
-        return service
-
-    @classmethod
-    def resource_cleanup(cls):
-        for e in cls.setup_endpoint_ids:
-            cls.client.delete_endpoint(e)
-        for s in cls.service_ids:
-            cls.services_client.delete_service(s)
-        super(EndPointsTestJSON, cls).resource_cleanup()
+        return service_data['service']
 
     @decorators.idempotent_id('c19ecf90-240e-4e23-9966-21cee3f6a618')
     def test_list_endpoints(self):
@@ -114,8 +115,8 @@
                                                interface=interface,
                                                url=url, region=region,
                                                enabled=True)['endpoint']
-
-        self.setup_endpoint_ids.append(endpoint['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.client.delete_endpoint, endpoint['id'])
         # Asserting Create Endpoint response body
         self.assertEqual(region, endpoint['region'])
         self.assertEqual(url, endpoint['url'])
@@ -137,7 +138,6 @@
 
         # Deleting the endpoint created in this method
         self.client.delete_endpoint(endpoint['id'])
-        self.setup_endpoint_ids.remove(endpoint['id'])
 
         # Checking whether endpoint is deleted successfully
         fetched_endpoints = self.client.list_endpoints()['endpoints']
@@ -147,8 +147,20 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('37e8f15e-ee7c-4657-a1e7-f6b61e375eff')
     def test_update_endpoint(self):
-        # Creating an endpoint so as to check update endpoint
-        # with new values
+        # NOTE(zhufl) Service2 should be created before endpoint_for_update
+        # is created, because Service2 must be deleted after
+        # endpoint_for_update is deleted, otherwise we will get a 404 error
+        # when deleting endpoint_for_update if endpoint's service is deleted.
+
+        # Creating service for updating endpoint with new service ID
+        s_name = data_utils.rand_name('service')
+        s_type = data_utils.rand_name('type')
+        s_description = data_utils.rand_name('description')
+        service2 = self._create_service(s_name=s_name, s_type=s_type,
+                                        s_description=s_description)
+        self.addCleanup(self.services_client.delete_service, service2['id'])
+
+        # Creating an endpoint so as to check update endpoint with new values
         region1 = data_utils.rand_name('region')
         url1 = data_utils.rand_url()
         interface1 = 'public'
@@ -158,12 +170,7 @@
                                         url=url1, region=region1,
                                         enabled=True)['endpoint'])
         self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
-        # Creating service so as update endpoint with new service ID
-        s_name = data_utils.rand_name('service')
-        s_type = data_utils.rand_name('type')
-        s_description = data_utils.rand_name('description')
-        service2 = self._create_service(s_name=s_name, s_type=s_type,
-                                        s_description=s_description)
+
         # Updating endpoint with new values
         region2 = data_utils.rand_name('region')
         url2 = data_utils.rand_url()
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 70dd7b5..4c3eb1c 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -1,4 +1,3 @@
-
 # Copyright 2013 IBM Corp.
 # All Rights Reserved.
 #
@@ -21,6 +20,10 @@
 
 
 class EndpointsNegativeTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def setup_clients(cls):
@@ -30,7 +33,6 @@
     @classmethod
     def resource_setup(cls):
         super(EndpointsNegativeTestJSON, cls).resource_setup()
-        cls.service_ids = list()
         s_name = data_utils.rand_name('service')
         s_type = data_utils.rand_name('type')
         s_description = data_utils.rand_name('description')
@@ -38,14 +40,10 @@
             cls.services_client.create_service(name=s_name, type=s_type,
                                                description=s_description)
             ['service'])
-        cls.service_id = service_data['id']
-        cls.service_ids.append(cls.service_id)
+        cls.addClassResourceCleanup(cls.services_client.delete_service,
+                                    service_data['id'])
 
-    @classmethod
-    def resource_cleanup(cls):
-        for s in cls.service_ids:
-            cls.services_client.delete_service(s)
-        super(EndpointsNegativeTestJSON, cls).resource_cleanup()
+        cls.service_id = service_data['id']
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('ac6c137e-4d3d-448f-8c83-4f13d0942651')
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 17db3ea..df0d79d 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -12,6 +12,7 @@
 #    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.identity import base
 from tempest import config
@@ -22,65 +23,62 @@
 
 
 class GroupsV3TestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def resource_setup(cls):
         super(GroupsV3TestJSON, cls).resource_setup()
         cls.domain = cls.create_domain()
 
-    @classmethod
-    def resource_cleanup(cls):
-        # Cleanup the domains created in the setup
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
-        super(GroupsV3TestJSON, cls).resource_cleanup()
-
     @decorators.idempotent_id('2e80343b-6c81-4ac3-88c7-452f3e9d5129')
     def test_group_create_update_get(self):
+        # Verify group creation works.
         name = data_utils.rand_name('Group')
         description = data_utils.rand_name('Description')
-        group = self.groups_client.create_group(
-            name=name, domain_id=self.domain['id'],
-            description=description)['group']
-        self.addCleanup(self.groups_client.delete_group, group['id'])
+        group = self.setup_test_group(name=name, domain_id=self.domain['id'],
+                                      description=description)
         self.assertEqual(group['name'], name)
         self.assertEqual(group['description'], description)
+        self.assertEqual(self.domain['id'], group['domain_id'])
 
-        new_name = data_utils.rand_name('UpdateGroup')
-        new_desc = data_utils.rand_name('UpdateDescription')
+        # Verify updating name and description works.
+        first_name_update = data_utils.rand_name('UpdateGroup')
+        first_desc_update = data_utils.rand_name('UpdateDescription')
         updated_group = self.groups_client.update_group(
-            group['id'], name=new_name, description=new_desc)['group']
-        self.assertEqual(updated_group['name'], new_name)
-        self.assertEqual(updated_group['description'], new_desc)
+            group['id'], name=first_name_update,
+            description=first_desc_update)['group']
+        self.assertEqual(updated_group['name'], first_name_update)
+        self.assertEqual(updated_group['description'], first_desc_update)
 
+        # Verify that the updated values are reflected after performing show.
         new_group = self.groups_client.show_group(group['id'])['group']
         self.assertEqual(group['id'], new_group['id'])
-        self.assertEqual(new_name, new_group['name'])
-        self.assertEqual(new_desc, new_group['description'])
+        self.assertEqual(first_name_update, new_group['name'])
+        self.assertEqual(first_desc_update, new_group['description'])
 
-    @decorators.idempotent_id('b66eb441-b08a-4a6d-81ab-fef71baeb26c')
-    def test_group_update_with_few_fields(self):
-        name = data_utils.rand_name('Group')
-        old_description = data_utils.rand_name('Description')
-        group = self.groups_client.create_group(
-            name=name, domain_id=self.domain['id'],
-            description=old_description)['group']
-        self.addCleanup(self.groups_client.delete_group, group['id'])
-
-        new_name = data_utils.rand_name('UpdateGroup')
+        # Verify that updating a single field for a group (name) leaves the
+        # other fields (description, domain_id) unchanged.
+        second_name_update = data_utils.rand_name(
+            self.__class__.__name__ + 'UpdateGroup')
         updated_group = self.groups_client.update_group(
-            group['id'], name=new_name)['group']
-        self.assertEqual(new_name, updated_group['name'])
-        # Verify that 'description' is not being updated or deleted.
-        self.assertEqual(old_description, updated_group['description'])
+            group['id'], name=second_name_update)['group']
+        self.assertEqual(second_name_update, updated_group['name'])
+        # Verify that 'description' and 'domain_id' were not updated or
+        # deleted.
+        self.assertEqual(first_desc_update, updated_group['description'])
+        self.assertEqual(self.domain['id'], updated_group['domain_id'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('1598521a-2f36-4606-8df9-30772bd51339')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an '
+                      'immutable user source and solely '
+                      'provides read-only access to users.')
     def test_group_users_add_list_delete(self):
-        name = data_utils.rand_name('Group')
-        group = self.groups_client.create_group(
-            name=name, domain_id=self.domain['id'])['group']
-        self.addCleanup(self.groups_client.delete_group, group['id'])
+        group = self.setup_test_group(domain_id=self.domain['id'])
         # add user into group
         users = []
         for _ in range(3):
@@ -101,17 +99,18 @@
         self.assertEqual(len(group_users), 0)
 
     @decorators.idempotent_id('64573281-d26a-4a52-b899-503cb0f4e4ec')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an '
+                      'immutable user source and solely '
+                      'provides read-only access to users.')
     def test_list_user_groups(self):
         # create a user
         user = self.create_test_user()
         # create two groups, and add user into them
         groups = []
         for _ in range(2):
-            name = data_utils.rand_name('Group')
-            group = self.groups_client.create_group(
-                name=name, domain_id=self.domain['id'])['group']
+            group = self.setup_test_group(domain_id=self.domain['id'])
             groups.append(group)
-            self.addCleanup(self.groups_client.delete_group, group['id'])
             self.groups_client.add_group_user(group['id'], user['id'])
         # list groups which user belongs to
         user_groups = self.users_client.list_user_groups(user['id'])['groups']
@@ -125,12 +124,7 @@
         group_ids = list()
         fetched_ids = list()
         for _ in range(3):
-            name = data_utils.rand_name('Group')
-            description = data_utils.rand_name('Description')
-            group = self.groups_client.create_group(
-                name=name, domain_id=self.domain['id'],
-                description=description)['group']
-            self.addCleanup(self.groups_client.delete_group, group['id'])
+            group = self.setup_test_group(domain_id=self.domain['id'])
             group_ids.append(group['id'])
         # List and Verify Groups
         # When domain specific drivers are enabled the operations
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index 8b687cd..2672f71 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -9,14 +9,22 @@
 #    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.identity import base
 from tempest.common import utils
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
+CONF = config.CONF
+
 
 class InheritsV3TestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def skip_checks(cls):
@@ -30,34 +38,39 @@
         u_name = data_utils.rand_name('user-')
         u_desc = '%s description' % u_name
         u_email = '%s@testmail.tm' % u_name
-        u_password = data_utils.rand_name('pass-')
+        u_password = data_utils.rand_password()
         cls.domain = cls.create_domain()
         cls.project = cls.projects_client.create_project(
             data_utils.rand_name('project-'),
             description=data_utils.rand_name('project-desc-'),
             domain_id=cls.domain['id'])['project']
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.project['id'])
         cls.group = cls.groups_client.create_group(
             name=data_utils.rand_name('group-'), project_id=cls.project['id'],
             domain_id=cls.domain['id'])['group']
-        cls.user = cls.users_client.create_user(
-            name=u_name, description=u_desc, password=u_password,
-            email=u_email, project_id=cls.project['id'],
-            domain_id=cls.domain['id'])['user']
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.groups_client.delete_group(cls.group['id'])
-        cls.users_client.delete_user(cls.user['id'])
-        cls.projects_client.delete_project(cls.project['id'])
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
-        super(InheritsV3TestJSON, cls).resource_cleanup()
+        cls.addClassResourceCleanup(cls.groups_client.delete_group,
+                                    cls.group['id'])
+        if not CONF.identity_feature_enabled.immutable_user_source:
+            cls.user = cls.users_client.create_user(
+                name=u_name,
+                description=u_desc,
+                password=u_password,
+                email=u_email,
+                project_id=cls.project['id'],
+                domain_id=cls.domain['id']
+            )['user']
+            cls.addClassResourceCleanup(cls.users_client.delete_user,
+                                        cls.user['id'])
 
     def _list_assertions(self, body, fetched_role_ids, role_id):
         self.assertEqual(len(body), 1)
         self.assertIn(role_id, fetched_role_ids)
 
     @decorators.idempotent_id('4e6f0366-97c8-423c-b2be-41eae6ac91c8')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_inherit_assign_list_check_revoke_roles_on_domains_user(self):
         # Create role
         src_role = self.setup_test_role()
@@ -106,6 +119,9 @@
             self.domain['id'], self.group['id'], src_role['id'])
 
     @decorators.idempotent_id('18b70e45-7687-4b72-8277-b8f1a47d7591')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_inherit_assign_check_revoke_roles_on_projects_user(self):
         # Create role
         src_role = self.setup_test_role()
@@ -137,6 +153,9 @@
              self.project['id'], self.group['id'], src_role['id']))
 
     @decorators.idempotent_id('3acf666e-5354-42ac-8e17-8b68893bcd36')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_inherit_assign_list_revoke_user_roles_on_domain(self):
         # Create role
         src_role = self.setup_test_role()
@@ -181,6 +200,9 @@
         self.assertEmpty(assignments)
 
     @decorators.idempotent_id('9f02ccd9-9b57-46b4-8f77-dd5a736f3a06')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_inherit_assign_list_revoke_user_roles_on_project_tree(self):
         # Create role
         src_role = self.setup_test_role()
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 7e70c14..299a618 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -13,73 +13,66 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from oslo_log import log as logging
+
 from tempest.api.identity import base
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
 
-class ListProjectsTestJSON(base.BaseIdentityV3AdminTest):
+
+class BaseListProjectsTestJSON(base.BaseIdentityV3AdminTest):
+
+    def _list_projects_with_params(self, included, excluded, params, key):
+        # Validate that projects in ``included`` belongs to the projects
+        # returned that match ``params`` but not projects in ``excluded``
+        all_projects = self.projects_client.list_projects()['projects']
+        LOG.debug("Complete list of projects available in keystone: %s",
+                  all_projects)
+        body = self.projects_client.list_projects(params)['projects']
+        for p in included:
+            self.assertIn(p[key], map(lambda x: x[key], body))
+        for p in excluded:
+            self.assertNotIn(p[key], map(lambda x: x[key], body))
+
+
+class ListProjectsTestJSON(BaseListProjectsTestJSON):
 
     @classmethod
     def resource_setup(cls):
         super(ListProjectsTestJSON, cls).resource_setup()
         cls.project_ids = list()
-        # Create a domain
-        cls.domain = cls.create_domain()
+        cls.domain_id = cls.os_admin.credentials.domain_id
         # Create project with domain
-        cls.projects = list()
         cls.p1_name = data_utils.rand_name('project')
         cls.p1 = cls.projects_client.create_project(
             cls.p1_name, enabled=False,
-            domain_id=cls.domain['id'])['project']
-        cls.projects.append(cls.p1)
+            domain_id=cls.domain_id)['project']
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.p1['id'])
         cls.project_ids.append(cls.p1['id'])
         # Create default project
         p2_name = data_utils.rand_name('project')
         cls.p2 = cls.projects_client.create_project(p2_name)['project']
-        cls.projects.append(cls.p2)
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.p2['id'])
         cls.project_ids.append(cls.p2['id'])
         # Create a new project (p3) using p2 as parent project
         p3_name = data_utils.rand_name('project')
         cls.p3 = cls.projects_client.create_project(
             p3_name, parent_id=cls.p2['id'])['project']
-        cls.projects.append(cls.p3)
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.p3['id'])
         cls.project_ids.append(cls.p3['id'])
 
-    @classmethod
-    def resource_cleanup(cls):
-        # Cleanup the projects created during setup in inverse order
-        for project in reversed(cls.projects):
-            cls.projects_client.delete_project(project['id'])
-        # Cleanup the domain created during setup
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
-        super(ListProjectsTestJSON, cls).resource_cleanup()
-
-    @decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
-    def test_list_projects(self):
-        # List projects
-        list_projects = self.projects_client.list_projects()['projects']
-
-        for p in self.project_ids:
-            show_project = self.projects_client.show_project(p)['project']
-            self.assertIn(show_project, list_projects)
-
-    @decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10')
-    def test_list_projects_with_domains(self):
-        # List projects with domain
-        self._list_projects_with_params(
-            {'domain_id': self.domain['id']}, 'domain_id')
-
     @decorators.idempotent_id('0fe7a334-675a-4509-b00e-1c4b95d5dae8')
     def test_list_projects_with_enabled(self):
         # List the projects with enabled
-        self._list_projects_with_params({'enabled': False}, 'enabled')
-
-    @decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26')
-    def test_list_projects_with_name(self):
-        # List projects with name
-        self._list_projects_with_params({'name': self.p1_name}, 'name')
+        self._list_projects_with_params(
+            [self.p1], [self.p2, self.p3], {'enabled': False}, 'enabled')
 
     @decorators.idempotent_id('6edc66f5-2941-4a17-9526-4073311c1fac')
     def test_list_projects_with_parent(self):
@@ -91,7 +84,50 @@
         for project in fetched_projects:
             self.assertEqual(self.p3['parent_id'], project['parent_id'])
 
-    def _list_projects_with_params(self, params, key):
-        body = self.projects_client.list_projects(params)['projects']
-        self.assertIn(self.p1[key], map(lambda x: x[key], body))
-        self.assertNotIn(self.p2[key], map(lambda x: x[key], body))
+
+class ListProjectsStaticTestJSON(BaseListProjectsTestJSON):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
+
+    @classmethod
+    def resource_setup(cls):
+        super(ListProjectsStaticTestJSON, cls).resource_setup()
+        cls.domain_id = CONF.identity.default_domain_id
+        cls.project_ids = list()
+        cls.p1_name = cls.os_primary.credentials.project_name
+        cls.p1 = cls.projects_client.show_project(
+            cls.os_primary.credentials.project_id)['project']
+        cls.project_ids.append(cls.p1['id'])
+        p2_name = data_utils.rand_name('project')
+        cls.p2 = cls.projects_client.create_project(
+            p2_name, domain_id=cls.domain_id)['project']
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.p2['id'])
+        cls.project_ids.append(cls.p2['id'])
+
+    @decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
+    def test_list_projects(self):
+        # List projects
+        list_projects = self.projects_client.list_projects()['projects']
+
+        for p in self.project_ids:
+            show_project = self.projects_client.show_project(p)['project']
+            self.assertIn(show_project, list_projects)
+
+    @decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26')
+    def test_list_projects_with_name(self):
+        # List projects with name
+        self._list_projects_with_params(
+            [self.p1], [self.p2], {'name': self.p1_name}, 'name')
+
+    @decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10')
+    def test_list_projects_with_domains(self):
+        # List projects with domain
+        key = 'domain_id'
+        params = {key: self.domain_id}
+        # Verify both projects are in the self.domain_id which is the default
+        # domain
+        self._list_projects_with_params(
+            [self.p1, self.p2], [], params, key)
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index 506c729..c69e4c8 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -47,24 +47,18 @@
         cls.domain_enabled_user = cls.users_client.create_user(
             name=u1_name, password=alt_password,
             email=cls.alt_email, domain_id=cls.domain['id'])['user']
+        cls.addClassResourceCleanup(cls.users_client.delete_user,
+                                    cls.domain_enabled_user['id'])
         cls.users.append(cls.domain_enabled_user)
         # Create default not enabled user
         u2_name = data_utils.rand_name('test_user')
         cls.non_domain_enabled_user = cls.users_client.create_user(
             name=u2_name, password=alt_password,
             email=cls.alt_email, enabled=False)['user']
+        cls.addClassResourceCleanup(cls.users_client.delete_user,
+                                    cls.non_domain_enabled_user['id'])
         cls.users.append(cls.non_domain_enabled_user)
 
-    @classmethod
-    def resource_cleanup(cls):
-        # Cleanup the users created during setup
-        for user in cls.users:
-            cls.users_client.delete_user(user['id'])
-        # Cleanup the domain created during setup
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
-        super(UsersV3TestJSON, cls).resource_cleanup()
-
     @decorators.idempotent_id('08f9aabb-dcfe-41d0-8172-82b5fa0bd73d')
     def test_list_user_domains(self):
         # List users with domain
diff --git a/tempest/api/identity/admin/v3/test_oauth_consumers.py b/tempest/api/identity/admin/v3/test_oauth_consumers.py
index 062cce5..7a85f84 100644
--- a/tempest/api/identity/admin/v3/test_oauth_consumers.py
+++ b/tempest/api/identity/admin/v3/test_oauth_consumers.py
@@ -21,6 +21,10 @@
 
 
 class OAUTHConsumersV3Test(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     def _create_consumer(self):
         """Creates a consumer with a random description."""
diff --git a/tempest/api/identity/admin/v3/test_project_tags.py b/tempest/api/identity/admin/v3/test_project_tags.py
new file mode 100644
index 0000000..b7878a8
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_project_tags.py
@@ -0,0 +1,70 @@
+# Copyright 2018 AT&T Corporation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import testtools
+
+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 IdentityV3ProjectTagsTest(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
+
+    @decorators.idempotent_id('7c123aac-999d-416a-a0fb-84b915ab10de')
+    @testtools.skipUnless(CONF.identity_feature_enabled.project_tags,
+                          'Project tags not available.')
+    def test_list_update_delete_project_tags(self):
+        project = self.setup_test_project()
+
+        # Create a tag for testing.
+        tag = data_utils.rand_name('tag')
+        # NOTE(felipemonteiro): The response body for create is empty.
+        self.project_tags_client.update_project_tag(project['id'], tag)
+
+        # Verify that the tag was created.
+        self.project_tags_client.check_project_tag_existence(
+            project['id'], tag)
+
+        # Verify that updating the project tags works.
+        tags_to_update = [data_utils.rand_name('tag') for _ in range(3)]
+        updated_tags = self.project_tags_client.update_all_project_tags(
+            project['id'], tags_to_update)['tags']
+        self.assertEqual(sorted(tags_to_update), sorted(updated_tags))
+
+        # Verify that listing project tags works.
+        retrieved_tags = self.project_tags_client.list_project_tags(
+            project['id'])['tags']
+        self.assertEqual(sorted(tags_to_update), sorted(retrieved_tags))
+
+        # Verify that deleting a project tag works.
+        self.project_tags_client.delete_project_tag(
+            project['id'], tags_to_update[0])
+        self.assertRaises(lib_exc.NotFound,
+                          self.project_tags_client.check_project_tag_existence,
+                          project['id'], tags_to_update[0])
+
+        # Verify that deleting all project tags works.
+        self.project_tags_client.delete_all_project_tags(project['id'])
+        retrieved_tags = self.project_tags_client.list_project_tags(
+            project['id'])['tags']
+        self.assertEmpty(retrieved_tags)
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index ac23067..0b85b19 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -12,6 +12,7 @@
 #    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.identity import base
 from tempest import config
@@ -22,6 +23,10 @@
 
 
 class ProjectsTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @decorators.idempotent_id('0ecf465c-0dc4-4532-ab53-91ffeb74d12d')
     def test_project_create_with_description(self):
@@ -34,7 +39,7 @@
                          'been sent in response for create')
         body = self.projects_client.show_project(project_id)['project']
         desc2 = body['description']
-        self.assertEqual(desc2, project_desc, 'Description does not appear'
+        self.assertEqual(desc2, project_desc, 'Description does not appear '
                          'to be set')
 
     @decorators.idempotent_id('5f50fe07-8166-430b-a882-3b2ee0abe26f')
@@ -101,22 +106,19 @@
         # Create a project that is enabled
         project = self.setup_test_project(enabled=True)
         project_id = project['id']
-        en1 = project['enabled']
-        self.assertTrue(en1, 'Enable should be True in response')
+        self.assertTrue(project['enabled'],
+                        'Enable should be True in response')
         body = self.projects_client.show_project(project_id)['project']
-        en2 = body['enabled']
-        self.assertTrue(en2, 'Enable should be True in lookup')
+        self.assertTrue(body['enabled'], 'Enable should be True in lookup')
 
     @decorators.idempotent_id('78f96a9c-e0e0-4ee6-a3ba-fbf6dfd03207')
     def test_project_create_not_enabled(self):
         # Create a project that is not enabled
         project = self.setup_test_project(enabled=False)
-        en1 = project['enabled']
-        self.assertEqual('false', str(en1).lower(),
+        self.assertFalse(project['enabled'],
                          'Enable should be False in response')
         body = self.projects_client.show_project(project['id'])['project']
-        en2 = body['enabled']
-        self.assertEqual('false', str(en2).lower(),
+        self.assertFalse(body['enabled'],
                          'Enable should be False in lookup')
 
     @decorators.idempotent_id('f608f368-048c-496b-ad63-d286c26dab6b')
@@ -178,10 +180,14 @@
         resp3_en = body['enabled']
 
         self.assertNotEqual(resp1_en, resp3_en)
-        self.assertEqual('false', str(resp1_en).lower())
+        self.assertFalse(project['enabled'])
         self.assertEqual(resp2_en, resp3_en)
 
     @decorators.idempotent_id('59398d4a-5dc5-4f86-9a4c-c26cc804d6c6')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an '
+                      'immutable user source and solely '
+                      'provides read-only access to users.')
     def test_associate_user_to_project(self):
         # Associate a user to a project
         # Create a Project
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index 33a9c8c..12f1d4a 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -22,6 +22,22 @@
 class ProjectsNegativeTestJSON(base.BaseIdentityV3AdminTest):
 
     @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97')
+    def test_project_delete_by_unauthorized_user(self):
+        # Non-admin user should not be able to delete a project
+        project = self.setup_test_project()
+        self.assertRaises(
+            lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
+            project['id'])
+
+
+class ProjectsNegativeStaticTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
+
+    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('24c49279-45dd-4155-887a-cb738c2385aa')
     def test_list_projects_by_unauthorized_user(self):
         # Non-admin user should not be able to list projects
@@ -63,15 +79,6 @@
                           self.projects_client.create_project, project_name)
 
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97')
-    def test_project_delete_by_unauthorized_user(self):
-        # Non-admin user should not be able to delete a project
-        project = self.setup_test_project()
-        self.assertRaises(
-            lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
-            project['id'])
-
-    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('7965b581-60c1-43b7-8169-95d4ab7fc6fb')
     def test_delete_non_existent_project(self):
         # Attempt to delete a non existent project should fail
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index d00e408..c8c0151 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -20,6 +20,10 @@
 
 
 class RegionsTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def setup_clients(cls):
@@ -34,14 +38,10 @@
             r_description = data_utils.rand_name('description')
             region = cls.client.create_region(
                 description=r_description)['region']
+            cls.addClassResourceCleanup(
+                cls.client.delete_region, region['id'])
             cls.setup_regions.append(region)
 
-    @classmethod
-    def resource_cleanup(cls):
-        for r in cls.setup_regions:
-            cls.client.delete_region(r['id'])
-        super(RegionsTestJSON, cls).resource_cleanup()
-
     @decorators.idempotent_id('56186092-82e4-43f2-b954-91013218ba42')
     def test_create_update_get_delete_region(self):
         # Create region
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index ec904e6..5ba4c9f 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -12,6 +12,7 @@
 #    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.identity import base
 from tempest import config
@@ -24,6 +25,10 @@
 
 
 class RolesV3TestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def resource_setup(cls):
@@ -32,6 +37,8 @@
         for _ in range(3):
             role_name = data_utils.rand_name(name='role')
             role = cls.roles_client.create_role(name=role_name)['role']
+            cls.addClassResourceCleanup(cls.roles_client.delete_role,
+                                        role['id'])
             cls.roles.append(role)
         u_name = data_utils.rand_name('user')
         u_desc = '%s description' % u_name
@@ -42,29 +49,28 @@
             data_utils.rand_name('project'),
             description=data_utils.rand_name('project-desc'),
             domain_id=cls.domain['id'])['project']
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.project['id'])
         cls.group_body = cls.groups_client.create_group(
             name=data_utils.rand_name('Group'), project_id=cls.project['id'],
             domain_id=cls.domain['id'])['group']
-        cls.user_body = cls.users_client.create_user(
-            name=u_name, description=u_desc, password=cls.u_password,
-            email=u_email, project_id=cls.project['id'],
-            domain_id=cls.domain['id'])['user']
+        cls.addClassResourceCleanup(cls.groups_client.delete_group,
+                                    cls.group_body['id'])
         cls.role = cls.roles_client.create_role(
             name=data_utils.rand_name('Role'))['role']
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.roles_client.delete_role(cls.role['id'])
-        cls.groups_client.delete_group(cls.group_body['id'])
-        cls.users_client.delete_user(cls.user_body['id'])
-        cls.projects_client.delete_project(cls.project['id'])
-        # NOTE(harika-vakadi): It is necessary to disable the domain
-        # before deleting,or else it would result in unauthorized error
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
-        for role in cls.roles:
-            cls.roles_client.delete_role(role['id'])
-        super(RolesV3TestJSON, cls).resource_cleanup()
+        cls.addClassResourceCleanup(cls.roles_client.delete_role,
+                                    cls.role['id'])
+        if not CONF.identity_feature_enabled.immutable_user_source:
+            cls.user_body = cls.users_client.create_user(
+                name=u_name,
+                description=u_desc,
+                email=u_email,
+                password=cls.u_password,
+                domain_id=cls.domain['id'],
+                project_id=cls.project['id']
+            )['user']
+            cls.addClassResourceCleanup(cls.users_client.delete_user,
+                                        cls.user_body['id'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a')
@@ -91,6 +97,9 @@
         self.assertIn(role['id'], [r['id'] for r in roles])
 
     @decorators.idempotent_id('c6b80012-fe4a-498b-9ce8-eb391c05169f')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_grant_list_revoke_role_to_user_on_project(self):
         self.roles_client.create_user_role_on_project(self.project['id'],
                                                       self.user_body['id'],
@@ -109,6 +118,9 @@
             self.project['id'], self.user_body['id'], self.role['id'])
 
     @decorators.idempotent_id('6c9a2940-3625-43a3-ac02-5dcec62ef3bd')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_grant_list_revoke_role_to_user_on_domain(self):
         self.roles_client.create_user_role_on_domain(
             self.domain['id'], self.user_body['id'], self.role['id'])
@@ -126,6 +138,9 @@
             self.domain['id'], self.user_body['id'], self.role['id'])
 
     @decorators.idempotent_id('cbf11737-1904-4690-9613-97bcbb3df1c4')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_grant_list_revoke_role_to_group_on_project(self):
         # Grant role to group on project
         self.roles_client.create_group_role_on_project(
@@ -261,6 +276,9 @@
         self.assertIn(self.roles[2]['id'], implies_ids)
 
     @decorators.idempotent_id('c8828027-df48-4021-95df-b65b92c7429e')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_assignments_for_implied_roles_create_delete(self):
         # Create a grant using "roles[0]"
         self.roles_client.create_user_role_on_project(
@@ -342,16 +360,18 @@
         # domain role to a global one
         self._create_implied_role(domain_role1['id'], self.role['id'])
 
-        if CONF.identity_feature_enabled.forbid_global_implied_dsr:
-            # The contrary is not true: we can't create an inference rule
-            # from a global role to a domain role
-            self.assertRaises(
-                lib_exc.Forbidden,
-                self.roles_client.create_role_inference_rule,
-                self.role['id'],
-                domain_role1['id'])
+        # The contrary is not true: we can't create an inference rule
+        # from a global role to a domain role
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.roles_client.create_role_inference_rule,
+            self.role['id'],
+            domain_role1['id'])
 
     @decorators.idempotent_id('3859df7e-5b78-4e4d-b10e-214c8953842a')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an immutable user '
+                      'source and solely provides read-only access to users.')
     def test_assignments_for_domain_roles(self):
         domain_role = self.setup_test_role(domain_id=self.domain['id'])
 
@@ -389,12 +409,23 @@
 
         rules = self.roles_client.list_all_role_inference_rules()[
             'role_inferences']
+
+        # NOTE(jaosorior): With the work related to the define-default-roles
+        # blueprint, we now have 'admin', 'member' and 'reader' by default. So
+        # we filter every other implied role to only take into account the ones
+        # relates to this test.
+        relevant_roles = (self.roles[0]['id'], self.roles[1]['id'],
+                          self.roles[2]['id'], self.role['id'])
+
+        def is_implied_role_relevant(rule):
+            return any(r for r in rule['implies'] if r['id'] in relevant_roles)
+
+        relevant_rules = filter(is_implied_role_relevant, rules)
         # Sort the rules by the number of inferences, since there should be 1
         # inference between "roles[2]" and "role" and 2 inferences for
         # "roles[0]": between "roles[1]" and "roles[2]".
-        sorted_rules = sorted(rules, key=lambda r: len(r['implies']))
+        sorted_rules = sorted(relevant_rules, key=lambda r: len(r['implies']))
 
-        # Check that 2 sets of rules are returned.
         self.assertEqual(2, len(sorted_rules))
         # Check that only 1 inference rule exists between "roles[2]" and "role"
         self.assertEqual(1, len(sorted_rules[0]['implies']))
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 0845407..5f1b58d 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -19,7 +19,6 @@
 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
 
@@ -28,30 +27,6 @@
 
     credentials = ['primary', 'admin', 'alt']
 
-    @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
-    def test_tokens(self):
-        # Valid user's token is authenticated
-        # Create a User
-        u_name = data_utils.rand_name('user')
-        u_desc = '%s-description' % u_name
-        u_password = data_utils.rand_password()
-        user = self.create_test_user(
-            name=u_name, description=u_desc, password=u_password)
-        # Perform Authentication
-        resp = self.token.auth(user_id=user['id'],
-                               password=u_password).response
-        subject_token = resp['x-subject-token']
-        self.client.check_token_existence(subject_token)
-        # Perform GET Token
-        token_details = self.client.show_token(subject_token)['token']
-        self.assertEqual(resp['x-subject-token'], subject_token)
-        self.assertEqual(token_details['user']['id'], user['id'])
-        self.assertEqual(token_details['user']['name'], u_name)
-        # Perform Delete Token
-        self.client.delete_token(subject_token)
-        self.assertRaises(lib_exc.NotFound, self.client.check_token_existence,
-                          subject_token)
-
     @decorators.idempotent_id('565fa210-1da1-4563-999b-f7b5b67cf112')
     def test_rescope_token(self):
         """Rescope a token.
@@ -67,10 +42,10 @@
         user = self.create_test_user(password=user_password)
 
         # Create a couple projects
-        project1_name = data_utils.rand_name(name='project')
+        project1_name = data_utils.rand_name(name=self.__class__.__name__)
         project1 = self.setup_test_project(name=project1_name)
 
-        project2_name = data_utils.rand_name(name='project')
+        project2_name = data_utils.rand_name(name=self.__class__.__name__)
         project2 = self.setup_test_project(name=project2_name)
         self.addCleanup(self.projects_client.delete_project, project2['id'])
 
@@ -201,10 +176,7 @@
             role_id = self.setup_test_role()['id']
 
             # Create a group.
-            group_name = data_utils.rand_name('Group')
-            group_id = self.groups_client.create_group(
-                name=group_name, domain_id=domain_id)['group']['id']
-            self.addCleanup(self.groups_client.delete_group, group_id)
+            group_id = self.setup_test_group(domain_id=domain_id)['id']
 
             # Add the alt user to the group.
             self.groups_client.add_group_user(group_id, alt_user_id)
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 2530072..54a5ab7 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -39,7 +39,6 @@
         # Use alt_username as the trustee
         self.trust_id = None
         self.create_trustor_and_roles()
-        self.addCleanup(self.cleanup_user_and_roles)
 
     def tearDown(self):
         if self.trust_id:
@@ -50,11 +49,13 @@
 
     def create_trustor_and_roles(self):
         # create a project that trusts will be granted on
-        trustor_project_name = data_utils.rand_name(name='project')
+        trustor_project_name = data_utils.rand_name(
+            name=self.__class__.__name__)
         project = self.projects_client.create_project(
             trustor_project_name,
             domain_id=CONF.identity.default_domain_id)['project']
         self.trustor_project_id = project['id']
+        self.addCleanup(self.projects_client.delete_project, project['id'])
         self.assertIsNotNone(self.trustor_project_id)
 
         # Create a trustor User
@@ -69,6 +70,7 @@
             email=u_email,
             project_id=self.trustor_project_id,
             domain_id=CONF.identity.default_domain_id)['user']
+        self.addCleanup(self.users_client.delete_user, user['id'])
         self.trustor_user_id = user['id']
 
         # And two roles, one we'll delegate and one we won't
@@ -76,10 +78,12 @@
         self.not_delegated_role = data_utils.rand_name('NotDelegatedRole')
 
         role = self.roles_client.create_role(name=self.delegated_role)['role']
+        self.addCleanup(self.roles_client.delete_role, role['id'])
         self.delegated_role_id = role['id']
 
         role = self.roles_client.create_role(
             name=self.not_delegated_role)['role']
+        self.addCleanup(self.roles_client.delete_role, role['id'])
         self.not_delegated_role_id = role['id']
 
         # Assign roles to trustor
@@ -109,16 +113,6 @@
         os = clients.Manager(credentials=creds)
         self.trustor_client = os.trusts_client
 
-    def cleanup_user_and_roles(self):
-        if self.trustor_user_id:
-            self.users_client.delete_user(self.trustor_user_id)
-        if self.trustor_project_id:
-            self.projects_client.delete_project(self.trustor_project_id)
-        if self.delegated_role_id:
-            self.roles_client.delete_role(self.delegated_role_id)
-        if self.not_delegated_role_id:
-            self.roles_client.delete_role(self.not_delegated_role_id)
-
     def create_trust(self, impersonate=True, expires=None):
 
         trust_create = self.trustor_client.create_trust(
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 3813568..8955a93 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -28,6 +28,14 @@
 
 class UsersV3TestJSON(base.BaseIdentityV3AdminTest):
 
+    @classmethod
+    def skip_checks(cls):
+        super(UsersV3TestJSON, cls).skip_checks()
+        if CONF.identity_feature_enabled.immutable_user_source:
+            raise cls.skipException('Skipped because environment has an '
+                                    'immutable user source and solely '
+                                    'provides read-only access to users.')
+
     @decorators.idempotent_id('b537d090-afb9-4519-b95d-270b0708e87e')
     def test_user_update(self):
         # Test case to check if updating of user attributes is successful.
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 30d2a36..282343c 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -190,6 +190,8 @@
         cls.non_admin_catalog_client = cls.os_primary.catalog_client
         cls.non_admin_versions_client =\
             cls.os_primary.identity_versions_v3_client
+        cls.non_admin_app_creds_client = \
+            cls.os_primary.application_credentials_client
 
 
 class BaseIdentityV3AdminTest(BaseIdentityV3Test):
@@ -228,6 +230,7 @@
         cls.domain_config_client = cls.os_admin.domain_config_client
         cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client
         cls.endpoint_groups_client = cls.os_admin.endpoint_groups_client
+        cls.project_tags_client = cls.os_admin.project_tags_client
 
         if CONF.identity.admin_domain_scope:
             # NOTE(andreaf) When keystone policy requires it, the identity
@@ -249,13 +252,16 @@
         if 'description' not in kwargs:
             kwargs['description'] = data_utils.rand_name('desc')
         domain = cls.domains_client.create_domain(**kwargs)['domain']
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.delete_domain, domain['id'])
         return domain
 
-    def delete_domain(self, domain_id):
+    @classmethod
+    def delete_domain(cls, domain_id):
         # NOTE(mpavlase) It is necessary to disable the domain before deleting
         # otherwise it raises Forbidden exception
-        self.domains_client.update_domain(domain_id, enabled=False)
-        self.domains_client.delete_domain(domain_id)
+        cls.domains_client.update_domain(domain_id, enabled=False)
+        cls.domains_client.delete_domain(domain_id)
 
     def setup_test_user(self, password=None):
         """Set up a test user."""
@@ -285,3 +291,44 @@
             test_utils.call_and_ignore_notfound_exc,
             self.delete_domain, domain['id'])
         return domain
+
+    def setup_test_group(self, **kwargs):
+        """Set up a test group."""
+        if 'name' not in kwargs:
+            kwargs['name'] = data_utils.rand_name(
+                self.__class__.__name__ + '_test_project')
+        if 'description' not in kwargs:
+            kwargs['description'] = data_utils.rand_name(
+                self.__class__.__name__ + '_test_description')
+        group = self.groups_client.create_group(**kwargs)['group']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.groups_client.delete_group, group['id'])
+        return group
+
+
+class BaseApplicationCredentialsV3Test(BaseIdentityV3Test):
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaseApplicationCredentialsV3Test, cls).skip_checks()
+        if not CONF.identity_feature_enabled.application_credentials:
+            raise cls.skipException("Application credentials are not available"
+                                    " in this environment")
+
+    @classmethod
+    def resource_setup(cls):
+        super(BaseApplicationCredentialsV3Test, cls).resource_setup()
+        cls.user_id = cls.os_primary.credentials.user_id
+        cls.project_id = cls.os_primary.credentials.project_id
+
+    def create_application_credential(self, name=None, **kwargs):
+        name = name or data_utils.rand_name('application_credential')
+        application_credential = (
+            self.non_admin_app_creds_client.create_application_credential(
+                self.user_id, name=name, **kwargs))['application_credential']
+        self.addCleanup(
+            self.non_admin_app_creds_client.delete_application_credential,
+            self.user_id,
+            application_credential['id'])
+        return application_credential
diff --git a/tempest/api/identity/v2/test_ec2_credentials.py b/tempest/api/identity/v2/test_ec2_credentials.py
index 237e728..9981ef8 100644
--- a/tempest/api/identity/v2/test_ec2_credentials.py
+++ b/tempest/api/identity/v2/test_ec2_credentials.py
@@ -57,18 +57,19 @@
             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'])
-        # add credentials to be cleaned up
-        self.addCleanup(
-            self.non_admin_users_client.delete_user_ec2_credential,
-            self.creds.user_id, creds1['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"]
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 9c77fff..158dfb3 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -84,7 +84,7 @@
         new_pass = data_utils.rand_password()
         user_id = self.creds.user_id
 
-        # to change password back. important for allow_tenant_isolation = false
+        # to change password back. important for use_dynamic_credentials=false
         self.addCleanup(self._restore_password, user_id, old_pass, new_pass)
 
         # user updates own password
diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py
new file mode 100644
index 0000000..1cee902
--- /dev/null
+++ b/tempest/api/identity/v3/test_application_credentials.py
@@ -0,0 +1,81 @@
+# Copyright 2018 SUSE Linux GmbH
+#
+# 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.
+
+import datetime
+
+from oslo_utils import timeutils
+
+from tempest.api.identity import base
+from tempest.lib import decorators
+
+
+class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test):
+
+    def _list_app_creds(self, name=None):
+        kwargs = dict(user_id=self.user_id)
+        if name:
+            kwargs.update(name=name)
+        return self.non_admin_app_creds_client.list_application_credentials(
+            **kwargs)['application_credentials']
+
+    @decorators.idempotent_id('8080c75c-eddc-4786-941a-c2da7039ae61')
+    def test_create_application_credential(self):
+        app_cred = self.create_application_credential()
+
+        # Check that the secret appears in the create response
+        secret = app_cred['secret']
+
+        # Check that the secret is not retrievable after initial create
+        app_cred = self.non_admin_app_creds_client.show_application_credential(
+            user_id=self.user_id,
+            application_credential_id=app_cred['id']
+        )['application_credential']
+        self.assertNotIn('secret', app_cred)
+
+        # Check that the application credential is functional
+        token_id, resp = self.non_admin_token.get_token(
+            app_cred_id=app_cred['id'],
+            app_cred_secret=secret,
+            auth_data=True
+        )
+        self.assertEqual(resp['project']['id'], self.project_id)
+
+    @decorators.idempotent_id('852daf0c-42b5-4239-8466-d193d0543ed3')
+    def test_create_application_credential_expires(self):
+        expires_at = timeutils.utcnow() + datetime.timedelta(hours=1)
+
+        app_cred = self.create_application_credential(expires_at=expires_at)
+
+        expires_str = expires_at.isoformat()
+        self.assertEqual(expires_str, app_cred['expires_at'])
+
+    @decorators.idempotent_id('ff0cd457-6224-46e7-b79e-0ada4964a8a6')
+    def test_list_application_credentials(self):
+        self.create_application_credential()
+        self.create_application_credential()
+
+        app_creds = self._list_app_creds()
+        self.assertEqual(2, len(app_creds))
+
+    @decorators.idempotent_id('9bb5e5cc-5250-493a-8869-8b665f6aa5f6')
+    def test_query_application_credentials(self):
+        self.create_application_credential()
+        app_cred_two = self.create_application_credential()
+        app_cred_two_name = app_cred_two['name']
+
+        app_creds = self._list_app_creds(name=app_cred_two_name)
+        self.assertEqual(1, len(app_creds))
+        self.assertEqual(app_cred_two_name, app_creds[0]['name'])
diff --git a/tempest/api/identity/v3/test_catalog.py b/tempest/api/identity/v3/test_catalog.py
index deec2dc..bc95f0d 100644
--- a/tempest/api/identity/v3/test_catalog.py
+++ b/tempest/api/identity/v3/test_catalog.py
@@ -22,8 +22,8 @@
 
     @decorators.idempotent_id('56b57ced-22b8-4127-9b8a-565dfb0207e2')
     def test_catalog_standardization(self):
-        # http://git.openstack.org/cgit/openstack/service-types-authority
-        # /tree/service-types.yaml
+        # https://opendev.org/openstack/service-types-authority
+        # /src/branch/master/service-types.yaml
         standard_service_values = [{'name': 'keystone', 'type': 'identity'},
                                    {'name': 'nova', 'type': 'compute'},
                                    {'name': 'glance', 'type': 'image'},
diff --git a/tempest/api/identity/v3/test_domains.py b/tempest/api/identity/v3/test_domains.py
new file mode 100644
index 0000000..9f132dd
--- /dev/null
+++ b/tempest/api/identity/v3/test_domains.py
@@ -0,0 +1,39 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.identity import base
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class DefaultDomainTestJSON(base.BaseIdentityV3Test):
+
+    @classmethod
+    def setup_clients(cls):
+        super(DefaultDomainTestJSON, cls).setup_clients()
+        cls.domains_client = cls.os_primary.domains_client
+
+    @classmethod
+    def resource_setup(cls):
+        super(DefaultDomainTestJSON, cls).resource_setup()
+        cls.domain_id = CONF.identity.default_domain_id
+
+    @decorators.attr(type='smoke')
+    @decorators.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
+    def test_default_domain_exists(self):
+        domain = self.domains_client.show_domain(self.domain_id)['domain']
+        self.assertTrue(domain['enabled'])
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index 4c72d82..f13aa10 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -91,3 +91,28 @@
             self.assertIsNotNone(subject_name, 'Expected user name in token.')
 
         self.assertEqual(resp['methods'][0], 'password')
+
+    @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
+    def test_token_auth_creation_existence_deletion(self):
+        # Tests basic token auth functionality in a way that is compatible with
+        # pre-provisioned credentials. The default user is used for token
+        # authentication.
+
+        # Valid user's token is authenticated
+        user = self.os_primary.credentials
+        # Perform Authentication
+        resp = self.non_admin_token.auth(
+            user_id=user.user_id, password=user.password).response
+        subject_token = resp['x-subject-token']
+        self.non_admin_client.check_token_existence(subject_token)
+        # Perform GET Token
+        token_details = self.non_admin_client.show_token(
+            subject_token)['token']
+        self.assertEqual(resp['x-subject-token'], subject_token)
+        self.assertEqual(token_details['user']['id'], user.user_id)
+        self.assertEqual(token_details['user']['name'], user.username)
+        # Perform Delete Token
+        self.non_admin_client.delete_token(subject_token)
+        self.assertRaises(lib_exc.NotFound,
+                          self.non_admin_client.check_token_existence,
+                          subject_token)
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 1f099df..13b5161 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -82,7 +82,7 @@
         old_token = self.non_admin_client.token
         new_pass = data_utils.rand_password()
 
-        # to change password back. important for allow_tenant_isolation = false
+        # to change password back. important for use_dynamic_credentials=false
         self.addCleanup(self._restore_password, old_pass, new_pass)
 
         # user updates own password
@@ -133,6 +133,13 @@
                           'Security compliance not available.')
     @decorators.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658')
     def test_user_account_lockout(self):
+        if (CONF.identity.user_lockout_failure_attempts <= 0 or
+                CONF.identity.user_lockout_duration <= 0):
+            raise self.skipException(
+                "Both CONF.identity.user_lockout_failure_attempts and "
+                "CONF.identity.user_lockout_duration should be greater than "
+                "zero to test this feature")
+
         password = self.creds.password
 
         # First, we login using the correct credentials
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 70ba2fe..ae7b3e4 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -46,16 +46,6 @@
         cls.created_images = []
 
     @classmethod
-    def resource_cleanup(cls):
-        for image_id in cls.created_images:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.client.delete_image, image_id)
-
-        for image_id in cls.created_images:
-                cls.client.wait_for_resource_deletion(image_id)
-        super(BaseImageTest, cls).resource_cleanup()
-
-    @classmethod
     def create_image(cls, data=None, **kwargs):
         """Wrapper that returns a test image."""
 
@@ -75,6 +65,10 @@
         if 'image' in image:
             image = image['image']
         cls.created_images.append(image['id'])
+        cls.addClassResourceCleanup(cls.client.wait_for_resource_deletion,
+                                    image['id'])
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.client.delete_image, image['id'])
         return image
 
     @classmethod
@@ -148,16 +142,17 @@
         cls.schemas_client = cls.os_primary.schemas_client
         cls.versions_client = cls.os_primary.image_versions_client
 
-    def create_namespace(cls, namespace_name=None, visibility='public',
+    def create_namespace(self, namespace_name=None, visibility='public',
                          description='Tempest', protected=False,
                          **kwargs):
         if not namespace_name:
             namespace_name = data_utils.rand_name('test-ns')
         kwargs.setdefault('display_name', namespace_name)
-        namespace = cls.namespaces_client.create_namespace(
+        namespace = self.namespaces_client.create_namespace(
             namespace=namespace_name, visibility=visibility,
             description=description, protected=protected, **kwargs)
-        cls.addCleanup(cls.namespaces_client.delete_namespace, namespace_name)
+        self.addCleanup(self.namespaces_client.delete_namespace,
+                        namespace_name)
         return namespace
 
 
@@ -191,21 +186,11 @@
         return image['id']
 
 
-class BaseV1ImageAdminTest(BaseImageTest):
-    credentials = ['admin', 'primary']
+class BaseV2ImageAdminTest(BaseV2ImageTest):
 
-    @classmethod
-    def setup_clients(cls):
-        super(BaseV1ImageAdminTest, cls).setup_clients()
-        cls.client = cls.os_primary.image_client
-        cls.admin_client = cls.os_admin.image_client
-
-
-class BaseV2ImageAdminTest(BaseImageTest):
     credentials = ['admin', 'primary']
 
     @classmethod
     def setup_clients(cls):
         super(BaseV2ImageAdminTest, cls).setup_clients()
-        cls.client = cls.os_primary.image_client_v2
         cls.admin_client = cls.os_admin.image_client_v2
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 76723f4..2432c8b 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -132,8 +132,8 @@
     @classmethod
     def skip_checks(cls):
         super(ListImagesTest, cls).skip_checks()
-        if (len(CONF.image.container_formats) < 2
-           or len(CONF.image.disk_formats) < 2):
+        if (len(CONF.image.container_formats) < 2 or
+                len(CONF.image.disk_formats) < 2):
             skip_msg = ("%s skipped as multiple container formats "
                         "or disk formats are not available." % cls.__name__)
             raise cls.skipException(skip_msg)
@@ -227,8 +227,8 @@
             self.assertEqual(image['disk_format'], self.disk_format_alt)
         result_set = set(map(lambda x: x['id'], images_list))
         self.assertTrue(self.same_disk_format_set <= result_set)
-        self.assertFalse(self.created_set - self.same_disk_format_set
-                         <= result_set)
+        self.assertFalse(self.created_set - self.same_disk_format_set <=
+                         result_set)
 
     @decorators.idempotent_id('2143655d-96d9-4bec-9188-8674206b4b3b')
     def test_index_container_format(self):
@@ -238,8 +238,8 @@
             self.assertEqual(image['container_format'], self.container_format)
         result_set = set(map(lambda x: x['id'], images_list))
         self.assertTrue(self.same_container_format_set <= result_set)
-        self.assertFalse(self.created_set - self.same_container_format_set
-                         <= result_set)
+        self.assertFalse(self.created_set - self.same_container_format_set <=
+                         result_set)
 
     @decorators.idempotent_id('feb32ac6-22bb-4a16-afd8-9454bb714b14')
     def test_index_max_size(self):
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/api/image/v2/admin/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/api/image/v2/admin/__init__.py
diff --git a/tempest/api/image/v2/admin/test_images.py b/tempest/api/image/v2/admin/test_images.py
new file mode 100644
index 0000000..dbb8c58
--- /dev/null
+++ b/tempest/api/image/v2/admin/test_images.py
@@ -0,0 +1,51 @@
+# Copyright 2018 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.image import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+
+class BasicOperationsImagesAdminTest(base.BaseV2ImageAdminTest):
+
+    @decorators.related_bug('1420008')
+    @decorators.idempotent_id('646a6eaa-135f-4493-a0af-12583021224e')
+    def test_create_image_owner_param(self):
+        # NOTE: Create image with owner different from tenant owner by
+        # using "owner" parameter requires an admin privileges.
+        random_id = data_utils.rand_uuid_hex()
+        image = self.admin_client.create_image(
+            container_format='bare', disk_format='raw', owner=random_id)
+        self.addCleanup(self.admin_client.delete_image, image['id'])
+        image_info = self.admin_client.show_image(image['id'])
+        self.assertEqual(random_id, image_info['owner'])
+
+    @decorators.related_bug('1420008')
+    @decorators.idempotent_id('525ba546-10ef-4aad-bba1-1858095ce553')
+    def test_update_image_owner_param(self):
+        random_id_1 = data_utils.rand_uuid_hex()
+        image = self.admin_client.create_image(
+            container_format='bare', disk_format='raw', owner=random_id_1)
+        self.addCleanup(self.admin_client.delete_image, image['id'])
+        created_image_info = self.admin_client.show_image(image['id'])
+
+        random_id_2 = data_utils.rand_uuid_hex()
+        self.admin_client.update_image(
+            image['id'], [dict(replace="/owner", value=random_id_2)])
+        updated_image_info = self.admin_client.show_image(image['id'])
+
+        self.assertEqual(random_id_2, updated_image_info['owner'])
+        self.assertNotEqual(created_image_info['owner'],
+                            updated_image_info['owner'])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index c846f88..c938cee 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -18,8 +18,6 @@
 
 import six
 
-import testtools
-
 from oslo_log import log as logging
 from tempest.api.image import base
 from tempest import config
@@ -73,8 +71,12 @@
         self.assertEqual(1024, body.get('size'))
 
         # Now try get image file
+        # NOTE: This Glance API returns different status codes for image
+        # condition. In this non-empty data case, Glance should return 200,
+        # so here should check the status code.
         body = self.client.show_image_file(image['id'])
         self.assertEqual(file_content, body.data)
+        self.assertEqual(200, body.response.status)
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('f848bb94-1c6e-45a4-8726-39e3a5b23535')
@@ -113,6 +115,13 @@
                                   visibility='private')
         self.assertEqual('queued', image['status'])
 
+        # NOTE: This Glance API returns different status codes for image
+        # condition. In this empty data case, Glance should return 204,
+        # so here should check the status code.
+        image_file = self.client.show_image_file(image['id'])
+        self.assertEqual(0, len(image_file.data))
+        self.assertEqual(204, image_file.response.status)
+
         # Now try uploading an image file
         image_file = six.BytesIO(data_utils.random_bytes())
         self.client.store_image_file(image['id'], image_file)
@@ -128,8 +137,6 @@
         self.assertEqual(image['id'], body['id'])
         self.assertEqual(new_image_name, body['name'])
 
-    @testtools.skipUnless(CONF.image_feature_enabled.deactivate_image,
-                          'deactivate-image is not available.')
     @decorators.idempotent_id('951ebe01-969f-4ea9-9898-8a3f1f442ab0')
     def test_deactivate_reactivate_image(self):
         # Create image
@@ -178,7 +185,7 @@
                      for disk_fmt in disk_fmts]
 
         for (container_fmt, disk_fmt) in all_pairs[:6]:
-            LOG.debug("Creating an image"
+            LOG.debug("Creating an image "
                       "(Container format: %s, Disk format: %s).",
                       container_fmt, disk_fmt)
             cls._create_standard_image(container_fmt, disk_fmt)
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index 0208780..e19d8c8 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -33,8 +33,8 @@
         self.assertIn(image_id, self._list_image_ids_as_alt())
         body = self.image_member_client.list_image_members(image_id)
         members = body['members']
-        member = members[0]
         self.assertEqual(len(members), 1, str(members))
+        member = members[0]
         self.assertEqual(member['member_id'], self.alt_tenant_id)
         self.assertEqual(member['image_id'], image_id)
         self.assertEqual(member['status'], 'accepted')
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
index 69bebfe..482e808 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
@@ -49,7 +49,7 @@
         # List namespace tags
         body = self.namespace_tags_client.list_namespace_tags(
             namespace['namespace'])
-        self.assertTrue(3, len(body['tags']))
+        self.assertEqual(3, len(body['tags']))
         self.assertIn(body['tags'][0]['name'], self.tag_list)
         self.assertIn(body['tags'][1]['name'], self.tag_list)
         self.assertIn(body['tags'][2]['name'], self.tag_list)
diff --git a/tempest/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
index 5068fc4..eaf477c 100644
--- a/tempest/api/network/admin/test_agent_management.py
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -15,7 +15,9 @@
 from tempest.api.network import base
 from tempest.common import tempest_fixtures as fixtures
 from tempest.common import utils
+from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
 
 
 class AgentManagementTestJSON(base.BaseAdminNetworkTest):
@@ -46,11 +48,6 @@
             agent.pop('configurations', None)
         self.assertIn(self.agent, agents)
 
-    @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
-    def test_list_agents_non_admin(self):
-        body = self.agents_client.list_agents()
-        self.assertEmpty(body["agents"])
-
     @decorators.idempotent_id('869bc8e8-0fda-4a30-9b71-f8a7cf58ca9f')
     def test_show_agent(self):
         body = self.admin_agents_client.show_agent(self.agent['id'])
@@ -86,3 +83,11 @@
         origin_agent = {'description': description}
         self.admin_agents_client.update_agent(agent_id=self.agent['id'],
                                               agent=origin_agent)
+
+    @decorators.idempotent_id('b33af888-b6ac-4e68-a0ca-0444c2696cf9')
+    @decorators.attr(type=['negative'])
+    def test_delete_agent_negative(self):
+        non_existent_id = data_utils.rand_uuid()
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.admin_agents_client.delete_agent, non_existent_id)
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index 8315c5d..4631ea9 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -63,7 +63,6 @@
     def test_add_remove_network_from_dhcp_agent(self):
         # The agent is now bound to the network, we can free the port
         self.ports_client.delete_port(self.port['id'])
-        self.ports.remove(self.port)
         agent = dict()
         agent['agent_type'] = None
         body = self.admin_agents_client.list_agents()
diff --git a/tempest/api/network/admin/test_external_network_extension.py b/tempest/api/network/admin/test_external_network_extension.py
index 4d41e33..5bd3fce 100644
--- a/tempest/api/network/admin/test_external_network_extension.py
+++ b/tempest/api/network/admin/test_external_network_extension.py
@@ -13,6 +13,7 @@
 import testtools
 
 from tempest.api.network import base
+from tempest.common import utils
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
@@ -35,6 +36,7 @@
         body = self.admin_networks_client.create_network(**post_body)
         network = body['network']
         self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
             self.admin_networks_client.delete_network, network['id'])
         return network
 
@@ -117,8 +119,15 @@
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.admin_floating_ips_client.delete_floatingip,
                         created_floating_ip['id'])
-        floatingip_list = self.admin_floating_ips_client.list_floatingips(
-            network=external_network['id'])
+        if utils.is_extension_enabled('filter-validation', 'network'):
+            floatingip_list = self.admin_floating_ips_client.list_floatingips(
+                floating_network_id=external_network['id'])
+        else:
+            # NOTE(hongbin): This is for testing the backward-compatibility
+            # of neutron API although the parameter is a wrong filter
+            # for listing floating IPs.
+            floatingip_list = self.admin_floating_ips_client.list_floatingips(
+                invalid_filter=external_network['id'])
         self.assertIn(created_floating_ip['id'],
                       (f['id'] for f in floatingip_list['floatingips']))
         self.admin_networks_client.delete_network(external_network['id'])
@@ -130,5 +139,3 @@
         subnet_list = self.admin_subnets_client.list_subnets()
         self.assertNotIn(subnet['id'],
                          (s['id'] for s in subnet_list))
-        # Removes subnet from the cleanup list
-        self.subnets.remove(subnet)
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 5aa337c..adc4dda 100644
--- a/tempest/api/network/admin/test_floating_ips_admin_actions.py
+++ b/tempest/api/network/admin/test_floating_ips_admin_actions.py
@@ -16,13 +16,13 @@
 from tempest.api.network import base
 from tempest.common import utils
 from tempest import config
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
 
 
 class FloatingIPAdminTestJSON(base.BaseAdminNetworkTest):
-    force_tenant_isolation = True
     credentials = ['primary', 'alt', 'admin']
 
     @classmethod
@@ -58,14 +58,18 @@
         # Create floating ip from admin user
         floating_ip_admin = self.admin_floating_ips_client.create_floatingip(
             floating_network_id=self.ext_net_id)
-        self.addCleanup(self.admin_floating_ips_client.delete_floatingip,
-                        floating_ip_admin['floatingip']['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.admin_floating_ips_client.delete_floatingip,
+            floating_ip_admin['floatingip']['id'])
         # Create floating ip from alt user
         body = self.alt_floating_ips_client.create_floatingip(
             floating_network_id=self.ext_net_id)
         floating_ip_alt = body['floatingip']
-        self.addCleanup(self.alt_floating_ips_client.delete_floatingip,
-                        floating_ip_alt['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.alt_floating_ips_client.delete_floatingip,
+            floating_ip_alt['id'])
         # List floating ips from admin
         body = self.admin_floating_ips_client.list_floatingips()
         floating_ip_ids_admin = [f['id'] for f in body['floatingips']]
@@ -92,8 +96,10 @@
             tenant_id=self.network['tenant_id'],
             port_id=self.port['id'])
         created_floating_ip = body['floatingip']
-        self.addCleanup(self.floating_ips_client.delete_floatingip,
-                        created_floating_ip['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.floating_ips_client.delete_floatingip,
+            created_floating_ip['id'])
         self.assertIsNotNone(created_floating_ip['id'])
         self.assertIsNotNone(created_floating_ip['tenant_id'])
         self.assertIsNotNone(created_floating_ip['floating_ip_address'])
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
deleted file mode 100644
index 1a7b0ec..0000000
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright 2013 IBM Corp.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from tempest.api.network import base
-from tempest.common import utils
-from tempest import config
-from tempest.lib import decorators
-from tempest.lib import exceptions
-
-CONF = config.CONF
-AGENT_TYPE = 'L3 agent'
-AGENT_MODES = (
-    'legacy',
-    'dvr_snat'
-)
-
-
-class L3AgentSchedulerTestJSON(base.BaseAdminNetworkTest):
-    """Tests the following operations in the Neutron API:
-
-        List routers that the given L3 agent is hosting.
-        List L3 agents hosting the given router.
-        Add and Remove Router to L3 agent
-
-    v2.0 of the Neutron API is assumed.
-
-    The l3_agent_scheduler extension is required for these tests.
-    """
-
-    @classmethod
-    def skip_checks(cls):
-        super(L3AgentSchedulerTestJSON, cls).skip_checks()
-        if not utils.is_extension_enabled('l3_agent_scheduler', 'network'):
-            msg = "L3 Agent Scheduler Extension not enabled."
-            raise cls.skipException(msg)
-
-    @classmethod
-    def resource_setup(cls):
-        super(L3AgentSchedulerTestJSON, cls).resource_setup()
-        agents = cls.admin_agents_client.list_agents(
-            agent_type=AGENT_TYPE)['agents']
-        for agent in agents:
-            if agent['configurations']['agent_mode'] in AGENT_MODES:
-                cls.agent = agent
-                break
-        else:
-            msg = "L3 Agent Scheduler enabled in conf, but L3 Agent not found"
-            raise exceptions.InvalidConfiguration(msg)
-        cls.router = cls.create_router()
-
-    @decorators.idempotent_id('b7ce6e89-e837-4ded-9b78-9ed3c9c6a45a')
-    def test_list_routers_on_l3_agent(self):
-        self.admin_agents_client.list_routers_on_l3_agent(self.agent['id'])
-
-    @decorators.idempotent_id('9464e5e7-8625-49c3-8fd1-89c52be59d66')
-    def test_add_list_remove_router_on_l3_agent(self):
-        l3_agent_ids = list()
-        self.admin_agents_client.create_router_on_l3_agent(
-            self.agent['id'],
-            router_id=self.router['id'])
-        body = (
-            self.admin_routers_client.list_l3_agents_hosting_router(
-                self.router['id']))
-        for agent in body['agents']:
-            l3_agent_ids.append(agent['id'])
-            self.assertIn('agent_type', agent)
-            self.assertEqual('L3 agent', agent['agent_type'])
-        self.assertIn(self.agent['id'], l3_agent_ids)
-        body = self.admin_agents_client.delete_router_from_l3_agent(
-            self.agent['id'],
-            self.router['id'])
-        # NOTE(afazekas): The deletion not asserted, because neutron
-        # is not forbidden to reschedule the router to the same agent
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 6849653..0db038d 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -14,7 +14,10 @@
 #    under the License.
 
 from tempest.api.network import base
+from tempest.common import identity
 from tempest.common import utils
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
@@ -28,9 +31,8 @@
     It is also assumed that the per-project quota extension API is configured
     in /etc/neutron/neutron.conf as follows:
 
-        quota_driver = neutron.db.quota_db.DbQuotaDriver
+        quota_driver = neutron.db.quota.driver.DbQuotaDriver
     """
-    force_tenant_isolation = True
 
     @classmethod
     def skip_checks(cls):
@@ -39,27 +41,39 @@
             msg = "quotas extension not enabled."
             raise cls.skipException(msg)
 
+    def setUp(self):
+        super(QuotasNegativeTest, self).setUp()
+        name = data_utils.rand_name('test_project_')
+        description = data_utils.rand_name('desc_')
+        self.project = identity.identity_utils(self.os_admin).create_project(
+            name=name, description=description)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+                        self.project['id'])
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('644f4e1b-1bf9-4af0-9fd8-eb56ac0f51cf')
     def test_network_quota_exceeding(self):
         # Set the network quota to two
-        self.admin_quotas_client.update_quotas(self.networks_client.tenant_id,
-                                               network=2)
-        self.addCleanup(self.admin_quotas_client.reset_quotas,
-                        self.networks_client.tenant_id)
+        self.admin_quotas_client.update_quotas(self.project['id'], network=2)
 
         # Create two networks
-        n1 = self.networks_client.create_network()
-        self.addCleanup(self.networks_client.delete_network,
+        n1 = self.admin_networks_client.create_network(
+            tenant_id=self.project['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_networks_client.delete_network,
                         n1['network']['id'])
-        n2 = self.networks_client.create_network()
-        self.addCleanup(self.networks_client.delete_network,
+        n2 = self.admin_networks_client.create_network(
+            tenant_id=self.project['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_networks_client.delete_network,
                         n2['network']['id'])
 
         # Try to create a third network while the quota is two
         with self.assertRaisesRegex(
                 lib_exc.Conflict,
-                "Quota exceeded for resources: \['network'\].*"):
-            n3 = self.networks_client.create_network()
-            self.addCleanup(self.networks_client.delete_network,
+                r"Quota exceeded for resources: \['network'\].*"):
+            n3 = self.admin_networks_client.create_network(
+                tenant_id=self.project['id'])
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self.admin_networks_client.delete_network,
                             n3['network']['id'])
diff --git a/tempest/api/network/admin/test_ports.py b/tempest/api/network/admin/test_ports.py
index 807994b..edfda6e 100644
--- a/tempest/api/network/admin/test_ports.py
+++ b/tempest/api/network/admin/test_ports.py
@@ -13,22 +13,24 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import socket
-
 from tempest.api.network import base
-from tempest import config
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
-CONF = config.CONF
-
 
 class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest):
 
     @classmethod
+    def setup_clients(cls):
+        super(PortsAdminExtendedAttrsTestJSON, cls).setup_clients()
+        cls.hyper_client = cls.os_admin.hypervisor_client
+
+    @classmethod
     def resource_setup(cls):
         super(PortsAdminExtendedAttrsTestJSON, cls).resource_setup()
         cls.network = cls.create_network()
-        cls.host_id = socket.gethostname()
+        hyper_list = cls.hyper_client.list_hypervisors()
+        cls.host_id = hyper_list['hypervisors'][0]['hypervisor_hostname']
 
     @decorators.idempotent_id('8e8569c1-9ac7-44db-8bc1-f5fb2814f29b')
     def test_create_port_binding_ext_attr(self):
@@ -36,7 +38,9 @@
                      "binding:host_id": self.host_id}
         body = self.admin_ports_client.create_port(**post_body)
         port = body['port']
-        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.admin_ports_client.delete_port, port['id'])
         host_id = port['binding:host_id']
         self.assertIsNotNone(host_id)
         self.assertEqual(self.host_id, host_id)
@@ -46,7 +50,9 @@
         post_body = {"network_id": self.network['id']}
         body = self.admin_ports_client.create_port(**post_body)
         port = body['port']
-        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.admin_ports_client.delete_port, port['id'])
         update_body = {"binding:host_id": self.host_id}
         body = self.admin_ports_client.update_port(port['id'], **update_body)
         updated_port = body['port']
@@ -60,7 +66,9 @@
         post_body = {"network_id": self.network['id']}
         body = self.admin_ports_client.create_port(**post_body)
         port = body['port']
-        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.admin_ports_client.delete_port, port['id'])
 
         # Update the port's binding attributes so that is now 'bound'
         # to a host
@@ -84,7 +92,8 @@
         body = self.admin_ports_client.create_port(
             network_id=self.network['id'])
         port = body['port']
-        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_ports_client.delete_port, port['id'])
         body = self.admin_ports_client.show_port(port['id'])
         show_port = body['port']
         self.assertEqual(port['binding:host_id'],
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index cf4236d..ef5ebb6 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.api.network import base
 from tempest.common import identity
 from tempest.common import utils
@@ -33,7 +35,7 @@
     It is also assumed that the per-project quota extension API is configured
     in /etc/neutron/neutron.conf as follows:
 
-        quota_driver = neutron.db.quota_db.DbQuotaDriver
+        quota_driver = neutron.db.quota.driver.DbQuotaDriver
     """
 
     @classmethod
@@ -80,8 +82,24 @@
         non_default_quotas = self.admin_quotas_client.list_quotas()
         for q in non_default_quotas['quotas']:
             self.assertNotEqual(project_id, q['tenant_id'])
+        quota_set = self.admin_quotas_client.show_quotas(project_id)['quota']
+        default_quotas = self.admin_quotas_client.show_default_quotas(
+            project_id)['quota']
+        self.assertEqual(default_quotas, quota_set)
 
     @decorators.idempotent_id('2390f766-836d-40ef-9aeb-e810d78207fb')
     def test_quotas(self):
         new_quotas = {'network': 0, 'port': 0}
         self._check_quotas(new_quotas)
+
+    @testtools.skipUnless(utils.is_extension_enabled(
+        'quota_details', 'network'), 'Quota details extension not enabled.')
+    @decorators.idempotent_id('7b05ec5f-bf44-43cb-b28f-ddd72a824288')
+    def test_show_quota_details(self):
+        # Show quota details for an existing project
+        quota_details = self.admin_quotas_client.show_quota_details(
+            self.admin_quotas_client.tenant_id)['quota']
+        expected_keys = ['used', 'limit', 'reserved']
+        for resource_type in quota_details:
+            for key in expected_keys:
+                self.assertIn(key, quota_details[resource_type])
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
index 8cdb41e..a4a057c 100644
--- a/tempest/api/network/admin/test_routers.py
+++ b/tempest/api/network/admin/test_routers.py
@@ -20,6 +20,7 @@
 from tempest.common import utils
 from tempest import config
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
@@ -32,14 +33,14 @@
 
     def _cleanup_router(self, router):
         self.delete_router(router)
-        self.routers.remove(router)
 
     def _create_router(self, name=None, admin_state_up=False,
                        external_network_id=None, enable_snat=None):
         # associate a cleanup with created routers to avoid quota limits
         router = self.create_router(name, admin_state_up,
                                     external_network_id, enable_snat)
-        self.addCleanup(self._cleanup_router, router)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self._cleanup_router, router)
         return router
 
     @classmethod
@@ -63,7 +64,8 @@
         name = data_utils.rand_name('router-')
         create_body = self.admin_routers_client.create_router(
             name=name, tenant_id=project_id)
-        self.addCleanup(self.admin_routers_client.delete_router,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_routers_client.delete_router,
                         create_body['router']['id'])
         self.assertEqual(project_id, create_body['router']['tenant_id'])
 
@@ -93,7 +95,8 @@
                 'enable_snat': enable_snat}
             create_body = self.admin_routers_client.create_router(
                 name=name, external_gateway_info=external_gateway_info)
-            self.addCleanup(self.admin_routers_client.delete_router,
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self.admin_routers_client.delete_router,
                             create_body['router']['id'])
             # Verify snat attributes after router creation
             self._verify_router_gateway(create_body['router']['id'],
@@ -112,7 +115,8 @@
     def _verify_gateway_port(self, router_id):
         list_body = self.admin_ports_client.list_ports(
             network_id=CONF.network.public_network_id,
-            device_id=router_id)
+            device_id=router_id,
+            device_owner="network:router_gateway")
         self.assertEqual(len(list_body['ports']), 1)
         gw_port = list_body['ports'][0]
         fixed_ips = gw_port['fixed_ips']
diff --git a/tempest/api/network/admin/test_routers_dvr.py b/tempest/api/network/admin/test_routers_dvr.py
index 93478e6..270f802 100644
--- a/tempest/api/network/admin/test_routers_dvr.py
+++ b/tempest/api/network/admin/test_routers_dvr.py
@@ -18,6 +18,7 @@
 from tempest.api.network import base
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 
@@ -62,7 +63,8 @@
         name = data_utils.rand_name('router')
         router = self.admin_routers_client.create_router(name=name,
                                                          distributed=True)
-        self.addCleanup(self.admin_routers_client.delete_router,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_routers_client.delete_router,
                         router['router']['id'])
         self.assertTrue(router['router']['distributed'])
 
@@ -82,7 +84,8 @@
         name = data_utils.rand_name('router')
         router = self.admin_routers_client.create_router(name=name,
                                                          distributed=False)
-        self.addCleanup(self.admin_routers_client.delete_router,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_routers_client.delete_router,
                         router['router']['id'])
         self.assertFalse(router['router']['distributed'])
 
@@ -112,8 +115,8 @@
                                                          ha=False,
                                                          tenant_id=tenant_id)
         router_id = router['router']['id']
-        self.addCleanup(self.admin_routers_client.delete_router,
-                        router_id)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_routers_client.delete_router, router_id)
         self.assertFalse(router['router']['distributed'])
         router = self.admin_routers_client.update_router(
             router_id, distributed=True)
diff --git a/tempest/api/network/admin/test_routers_negative.py b/tempest/api/network/admin/test_routers_negative.py
index 9356bcc..fdcc977 100644
--- a/tempest/api/network/admin/test_routers_negative.py
+++ b/tempest/api/network/admin/test_routers_negative.py
@@ -18,6 +18,7 @@
 from tempest.api.network import base
 from tempest.common import utils
 from tempest import config
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
@@ -42,7 +43,8 @@
         # At first create a address from public_network_id
         port = self.admin_ports_client.create_port(
             network_id=CONF.network.public_network_id)['port']
-        self.addCleanup(self.admin_ports_client.delete_port,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_ports_client.delete_port,
                         port_id=port['id'])
         # Add used ip and subnet_id in external_fixed_ips
         fixed_ip = {
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index c2a67e3..9032fdc 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -88,11 +88,6 @@
     @classmethod
     def resource_setup(cls):
         super(BaseNetworkTest, cls).resource_setup()
-        cls.networks = []
-        cls.subnets = []
-        cls.ports = []
-        cls.routers = []
-        cls.floating_ips = []
         cls.ethertype = "IPv" + str(cls._ip_version)
         if cls._ip_version == 4:
             cls.cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
@@ -102,32 +97,6 @@
             cls.mask_bits = CONF.network.project_network_v6_mask_bits
 
     @classmethod
-    def resource_cleanup(cls):
-        if CONF.service_available.neutron:
-            # Clean up floating IPs
-            for floating_ip in cls.floating_ips:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.floating_ips_client.delete_floatingip,
-                    floating_ip['id'])
-            # Clean up ports
-            for port in cls.ports:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.ports_client.delete_port, port['id'])
-            # Clean up routers
-            for router in cls.routers:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.delete_router, router)
-            # Clean up subnets
-            for subnet in cls.subnets:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.subnets_client.delete_subnet, subnet['id'])
-            # Clean up networks
-            for network in cls.networks:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.networks_client.delete_network, network['id'])
-        super(BaseNetworkTest, cls).resource_cleanup()
-
-    @classmethod
     def create_network(cls, network_name=None, **kwargs):
         """Wrapper utility that returns a test network."""
         network_name = network_name or data_utils.rand_name(
@@ -135,7 +104,9 @@
 
         body = cls.networks_client.create_network(name=network_name, **kwargs)
         network = body['network']
-        cls.networks.append(network)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.networks_client.delete_network,
+                                    network['id'])
         return network
 
     @classmethod
@@ -178,7 +149,9 @@
             message = 'Available CIDR for subnet creation could not be found'
             raise exceptions.BuildErrorException(message)
         subnet = body['subnet']
-        cls.subnets.append(subnet)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.subnets_client.delete_subnet,
+                                    subnet['id'])
         return subnet
 
     @classmethod
@@ -187,7 +160,8 @@
         body = cls.ports_client.create_port(network_id=network['id'],
                                             **kwargs)
         port = body['port']
-        cls.ports.append(port)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.ports_client.delete_port, port['id'])
         return port
 
     @classmethod
@@ -213,7 +187,8 @@
             name=router_name, external_gateway_info=ext_gw_info,
             admin_state_up=admin_state_up, **kwargs)
         router = body['router']
-        cls.routers.append(router)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.delete_router, router)
         return router
 
     @classmethod
@@ -222,7 +197,9 @@
         body = cls.floating_ips_client.create_floatingip(
             floating_network_id=external_network_id)
         fip = body['floatingip']
-        cls.floating_ips.append(fip)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.floating_ips_client.delete_floatingip,
+                                    fip['id'])
         return fip
 
     @classmethod
diff --git a/tempest/api/network/base_security_groups.py b/tempest/api/network/base_security_groups.py
index b8d677a..32f2cdd 100644
--- a/tempest/api/network/base_security_groups.py
+++ b/tempest/api/network/base_security_groups.py
@@ -15,6 +15,7 @@
 
 from tempest.api.network import base
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 
 
 class BaseSecGroupTest(base.BaseNetworkTest):
@@ -24,7 +25,8 @@
         name = data_utils.rand_name('secgroup-')
         group_create_body = (
             self.security_groups_client.create_security_group(name=name))
-        self.addCleanup(self._delete_security_group,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self._delete_security_group,
                         group_create_body['security_group']['id'])
         self.assertEqual(group_create_body['security_group']['name'], name)
         return group_create_body, name
diff --git a/tempest/api/network/test_agent_management_negative.py b/tempest/api/network/test_agent_management_negative.py
new file mode 100644
index 0000000..d1c02ce
--- /dev/null
+++ b/tempest/api/network/test_agent_management_negative.py
@@ -0,0 +1,28 @@
+# Copyright 2018 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.network import base
+from tempest.lib import decorators
+
+
+class AgentManagementNegativeTest(base.BaseNetworkTest):
+
+    @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
+    @decorators.attr(type=['negative'])
+    def test_list_agents_non_admin(self):
+        """Validate that non-admin user cannot list agents."""
+        # Listing agents requires admin_only permissions.
+        body = self.agents_client.list_agents()
+        self.assertEmpty(body["agents"])
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index 3075047..d393207 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -17,11 +17,9 @@
 
 from tempest.api.network import base
 from tempest.common import utils
-from tempest import config
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
-CONF = config.CONF
-
 
 class AllowedAddressPairTestJSON(base.BaseNetworkTest):
     """Tests the Neutron Allowed Address Pair API extension
@@ -65,7 +63,8 @@
             network_id=self.network['id'],
             allowed_address_pairs=allowed_address_pairs)
         port_id = body['port']['id']
-        self.addCleanup(self.ports_client.delete_port, port_id)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port_id)
 
         # Confirm port was created with allowed address pair attribute
         body = self.ports_client.list_ports()
@@ -79,7 +78,8 @@
         # Create a port without allowed address pair
         body = self.ports_client.create_port(network_id=self.network['id'])
         port_id = body['port']['id']
-        self.addCleanup(self.ports_client.delete_port, port_id)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port_id)
         if mac_address is None:
             mac_address = self.mac_address
 
@@ -109,7 +109,8 @@
         # Create an ip _address and mac_address through port create
         resp = self.ports_client.create_port(network_id=self.network['id'])
         newportid = resp['port']['id']
-        self.addCleanup(self.ports_client.delete_port, newportid)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, newportid)
         ipaddress = resp['port']['fixed_ips'][0]['ip_address']
         macaddress = resp['port']['mac_address']
 
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index 9d6d700..eb31ed3 100644
--- a/tempest/api/network/test_dhcp_ipv6.py
+++ b/tempest/api/network/test_dhcp_ipv6.py
@@ -56,6 +56,9 @@
     def resource_setup(cls):
         super(NetworksTestDHCPv6, cls).resource_setup()
         cls.network = cls.create_network()
+        cls.ports = []
+        cls.subnets = []
+        cls.routers = []
 
     def _remove_from_list_by_index(self, things_list, elem):
         for index, i in enumerate(things_list):
@@ -90,8 +93,10 @@
 
     def _get_ips_from_subnet(self, **kwargs):
         subnet = self.create_subnet(self.network, **kwargs)
+        self.subnets.append(subnet)
         port_mac = data_utils.rand_mac_address()
         port = self.create_port(self.network, mac_address=port_mac)
+        self.ports.append(port)
         real_ip = next(iter(port['fixed_ips']), None)['ip_address']
         eui_ip = str(netutils.get_ipv6_addr_by_EUI64(
             subnet['cidr'], port_mac))
@@ -130,7 +135,7 @@
             real_ip, eui_ip = self._get_ips_from_subnet(**kwargs)
             self._clean_network()
             self.assertEqual(eui_ip, real_ip,
-                             ('Real port IP %s shall be equal to EUI-64 %s'
+                             ('Real port IP %s shall be equal to EUI-64 %s '
                               'when ipv6_ra_mode=%s,ipv6_address_mode=%s') % (
                                  real_ip, eui_ip,
                                  ra_mode if ra_mode else "Off",
@@ -162,7 +167,7 @@
         self._clean_network()
         self.assertNotEqual(eui_ip, real_ip,
                             ('Real port IP %s equal to EUI-64 %s when '
-                             'ipv6_ra_mode=Off and ipv6_address_mode=Off,'
+                             'ipv6_ra_mode=Off and ipv6_address_mode=Off, '
                              'but shall be taken from fixed IPs') % (
                                 real_ip, eui_ip))
 
@@ -182,21 +187,26 @@
                 kwargs_dhcp = {'ipv6_address_mode': 'dhcpv6-stateful'}
                 if order == "slaac_first":
                     subnet_slaac = self.create_subnet(self.network, **kwargs)
+                    self.subnets.append(subnet_slaac)
                     subnet_dhcp = self.create_subnet(
                         self.network, **kwargs_dhcp)
+                    self.subnets.append(subnet_dhcp)
                 else:
                     subnet_dhcp = self.create_subnet(
                         self.network, **kwargs_dhcp)
+                    self.subnets.append(subnet_dhcp)
                     subnet_slaac = self.create_subnet(self.network, **kwargs)
+                    self.subnets.append(subnet_slaac)
                 port_mac = data_utils.rand_mac_address()
                 eui_ip = str(netutils.get_ipv6_addr_by_EUI64(
                     subnet_slaac['cidr'], port_mac))
                 port = self.create_port(self.network, mac_address=port_mac)
+                self.ports.append(port)
                 real_ips = dict([(k['subnet_id'], k['ip_address'])
                                  for k in port['fixed_ips']])
                 real_dhcp_ip, real_eui_ip = [real_ips[sub['id']]
                                              for sub in [subnet_dhcp,
-                                             subnet_slaac]]
+                                                         subnet_slaac]]
                 self.ports_client.delete_port(port['id'])
                 self.ports.pop()
                 body = self.ports_client.list_ports()
@@ -228,21 +238,26 @@
                           'ipv6_address_mode': add_mode}
                 if order == "slaac_first":
                     subnet_slaac = self.create_subnet(self.network, **kwargs)
+                    self.subnets.append(subnet_slaac)
                     subnet_dhcp = self.create_subnet(
                         self.network, ip_version=4)
+                    self.subnets.append(subnet_dhcp)
                 else:
                     subnet_dhcp = self.create_subnet(
                         self.network, ip_version=4)
+                    self.subnets.append(subnet_dhcp)
                     subnet_slaac = self.create_subnet(self.network, **kwargs)
+                    self.subnets.append(subnet_slaac)
                 port_mac = data_utils.rand_mac_address()
                 eui_ip = str(netutils.get_ipv6_addr_by_EUI64(
                     subnet_slaac['cidr'], port_mac))
                 port = self.create_port(self.network, mac_address=port_mac)
+                self.ports.append(port)
                 real_ips = dict([(k['subnet_id'], k['ip_address'])
                                  for k in port['fixed_ips']])
                 real_dhcp_ip, real_eui_ip = [real_ips[sub['id']]
                                              for sub in [subnet_dhcp,
-                                             subnet_slaac]]
+                                                         subnet_slaac]]
                 self._clean_network()
                 self.assertEqual(real_eui_ip,
                                  eui_ip,
@@ -267,7 +282,9 @@
                       'ipv6_address_mode': add_mode}
             kwargs = dict((k, v) for k, v in kwargs.items() if v)
             subnet = self.create_subnet(self.network, **kwargs)
+            self.subnets.append(subnet)
             port = self.create_port(self.network)
+            self.ports.append(port)
             port_ip = next(iter(port['fixed_ips']), None)['ip_address']
             self._clean_network()
             msg = ('Real IP address is {0} and it is NOT on '
@@ -289,6 +306,7 @@
                       'ipv6_address_mode': add_mode}
             kwargs = dict((k, v) for k, v in kwargs.items() if v)
             subnet = self.create_subnet(self.network, **kwargs)
+            self.subnets.append(subnet)
             ip_range = netaddr.IPRange(subnet["allocation_pools"][0]["start"],
                                        subnet["allocation_pools"][0]["end"])
             ip = netaddr.IPAddress(random.randrange(ip_range.first,
@@ -296,6 +314,7 @@
             port = self.create_port(self.network,
                                     fixed_ips=[{'subnet_id': subnet['id'],
                                                 'ip_address': ip}])
+            self.ports.append(port)
             port_ip = next(iter(port['fixed_ips']), None)['ip_address']
             self._clean_network()
             self.assertEqual(port_ip, ip,
@@ -310,6 +329,7 @@
         kwargs = {'ipv6_ra_mode': 'dhcpv6-stateful',
                   'ipv6_address_mode': 'dhcpv6-stateful'}
         subnet = self.create_subnet(self.network, **kwargs)
+        self.subnets.append(subnet)
         ip_range = netaddr.IPRange(subnet["allocation_pools"][0]["start"],
                                    subnet["allocation_pools"][0]["end"])
         ip = netaddr.IPAddress(random.randrange(
@@ -327,14 +347,16 @@
         kwargs = {'ipv6_ra_mode': 'dhcpv6-stateful',
                   'ipv6_address_mode': 'dhcpv6-stateful'}
         subnet = self.create_subnet(self.network, **kwargs)
+        self.subnets.append(subnet)
         ip_range = netaddr.IPRange(subnet["allocation_pools"][0]["start"],
                                    subnet["allocation_pools"][0]["end"])
         ip = netaddr.IPAddress(random.randrange(
             ip_range.first, ip_range.last)).format()
-        self.create_port(self.network,
-                         fixed_ips=[
-                             {'subnet_id': subnet['id'],
-                              'ip_address': ip}])
+        port = self.create_port(self.network,
+                                fixed_ips=[
+                                    {'subnet_id': subnet['id'],
+                                     'ip_address': ip}])
+        self.ports.append(port)
         self.assertRaisesRegex(lib_exc.Conflict,
                                "IpAddressAlreadyAllocated|IpAddressInUse",
                                self.create_port,
@@ -344,7 +366,9 @@
 
     def _create_subnet_router(self, kwargs):
         subnet = self.create_subnet(self.network, **kwargs)
+        self.subnets.append(subnet)
         router = self.create_router(admin_state_up=True)
+        self.routers.append(router)
         port = self.create_router_interface(router['id'],
                                             subnet['id'])
         body = self.ports_client.show_port(port['port_id'])
diff --git a/tempest/api/network/test_extra_dhcp_options.py b/tempest/api/network/test_extra_dhcp_options.py
index 0d42033..8e94429 100644
--- a/tempest/api/network/test_extra_dhcp_options.py
+++ b/tempest/api/network/test_extra_dhcp_options.py
@@ -16,6 +16,7 @@
 from tempest.api.network import base
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 
@@ -62,7 +63,8 @@
             network_id=self.network['id'],
             extra_dhcp_opts=self.extra_dhcp_opts)
         port_id = body['port']['id']
-        self.addCleanup(self.ports_client.delete_port, port_id)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port_id)
 
         # Confirm port created has Extra DHCP Options
         body = self.ports_client.list_ports()
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index ef4a23a..9704c73 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -15,8 +15,10 @@
 
 from tempest.api.network import base
 from tempest.common import utils
+from tempest.common.utils import data_utils
 from tempest.common.utils import net_utils
 from tempest import config
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
@@ -63,8 +65,10 @@
         cls.router = cls.create_router(external_network_id=cls.ext_net_id)
         cls.create_router_interface(cls.router['id'], cls.subnet['id'])
         # Create two ports one each for Creation and Updating of floatingIP
+        cls.ports = []
         for i in range(2):
-            cls.create_port(cls.network)
+            port = cls.create_port(cls.network)
+            cls.ports.append(port)
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('62595970-ab1c-4b7f-8fcc-fddfe55e8718')
@@ -74,8 +78,10 @@
             floating_network_id=self.ext_net_id,
             port_id=self.ports[0]['id'])
         created_floating_ip = body['floatingip']
-        self.addCleanup(self.floating_ips_client.delete_floatingip,
-                        created_floating_ip['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.floating_ips_client.delete_floatingip,
+            created_floating_ip['id'])
         self.assertIsNotNone(created_floating_ip['id'])
         self.assertIsNotNone(created_floating_ip['tenant_id'])
         self.assertIsNotNone(created_floating_ip['floating_ip_address'])
@@ -122,14 +128,19 @@
         self.assertIsNone(updated_floating_ip['fixed_ip_address'])
         self.assertIsNone(updated_floating_ip['router_id'])
 
+        # Explicity test deletion of floating IP
+        self.floating_ips_client.delete_floatingip(created_floating_ip['id'])
+
     @decorators.idempotent_id('e1f6bffd-442f-4668-b30e-df13f2705e77')
     def test_floating_ip_delete_port(self):
         # Create a floating IP
         body = self.floating_ips_client.create_floatingip(
             floating_network_id=self.ext_net_id)
         created_floating_ip = body['floatingip']
-        self.addCleanup(self.floating_ips_client.delete_floatingip,
-                        created_floating_ip['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.floating_ips_client.delete_floatingip,
+            created_floating_ip['id'])
         # Create a port
         port = self.ports_client.create_port(network_id=self.network['id'])
         created_port = port['port']
@@ -155,14 +166,36 @@
             floating_network_id=self.ext_net_id,
             port_id=self.ports[1]['id'])
         created_floating_ip = body['floatingip']
-        self.addCleanup(self.floating_ips_client.delete_floatingip,
-                        created_floating_ip['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.floating_ips_client.delete_floatingip,
+            created_floating_ip['id'])
         self.assertEqual(created_floating_ip['router_id'], self.router['id'])
-        network2 = self.create_network()
+        network_name = data_utils.rand_name(self.__class__.__name__)
+        network2 = self.networks_client.create_network(
+            name=network_name)['network']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.networks_client.delete_network,
+            network2['id'])
         subnet2 = self.create_subnet(network2)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.subnets_client.delete_subnet, subnet2['id'])
         router2 = self.create_router(external_network_id=self.ext_net_id)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.routers_client.delete_router, router2['id'])
         self.create_router_interface(router2['id'], subnet2['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.routers_client.remove_router_interface,
+            router2['id'], subnet_id=subnet2['id'])
         port_other_router = self.create_port(network2)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ports_client.delete_port,
+            port_other_router['id'])
         # Associate floating IP to the other port on another router
         floating_ip = self.floating_ips_client.update_floatingip(
             created_floating_ip['id'],
@@ -181,8 +214,10 @@
             port_id=self.ports[1]['id'],
             fixed_ip_address=self.ports[1]['fixed_ips'][0]['ip_address'])
         created_floating_ip = body['floatingip']
-        self.addCleanup(self.floating_ips_client.delete_floatingip,
-                        created_floating_ip['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.floating_ips_client.delete_floatingip,
+            created_floating_ip['id'])
         self.assertIsNotNone(created_floating_ip['id'])
         self.assertEqual(created_floating_ip['fixed_ip_address'],
                          self.ports[1]['fixed_ips'][0]['ip_address'])
@@ -205,14 +240,16 @@
         body = self.ports_client.create_port(network_id=self.network['id'],
                                              fixed_ips=fixed_ips)
         port = body['port']
-        self.addCleanup(self.ports_client.delete_port, port['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port['id'])
         # Create floating ip
         body = self.floating_ips_client.create_floatingip(
             floating_network_id=self.ext_net_id,
             port_id=port['id'],
             fixed_ip_address=list_ips[0])
         floating_ip = body['floatingip']
-        self.addCleanup(self.floating_ips_client.delete_floatingip,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.floating_ips_client.delete_floatingip,
                         floating_ip['id'])
         self.assertIsNotNone(floating_ip['id'])
         self.assertEqual(floating_ip['fixed_ip_address'], list_ips[0])
diff --git a/tempest/api/network/test_floating_ips_negative.py b/tempest/api/network/test_floating_ips_negative.py
index e904a81..1688c9d 100644
--- a/tempest/api/network/test_floating_ips_negative.py
+++ b/tempest/api/network/test_floating_ips_negative.py
@@ -17,6 +17,7 @@
 from tempest.api.network import base
 from tempest.common import utils
 from tempest import config
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
@@ -81,6 +82,7 @@
             floating_network_id=self.ext_net_id)
         floating_ip = body['floatingip']
         self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
             self.floating_ips_client.delete_floatingip, floating_ip['id'])
         # Associate floating IP to the other port
         self.assertRaises(
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 1c59556..eba1f6c 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -104,15 +104,6 @@
         self.assertThat(actual, custom_matchers.MatchesDictExceptForKeys(
                         expected, exclude_keys))
 
-    def _delete_network(self, network):
-        # Deleting network also deletes its subnets if exists
-        self.networks_client.delete_network(network['id'])
-        if network in self.networks:
-            self.networks.remove(network)
-        for subnet in self.subnets:
-            if subnet['network_id'] == network['id']:
-                self.subnets.remove(subnet)
-
     def _create_verify_delete_subnet(self, cidr=None, mask_bits=None,
                                      **kwargs):
         network = self.create_network()
@@ -132,8 +123,6 @@
 
         self._compare_resource_attrs(subnet, compare_args)
         self.networks_client.delete_network(net_id)
-        self.networks.pop()
-        self.subnets.pop()
 
 
 class NetworksTest(BaseNetworkTestResources):
@@ -171,7 +160,8 @@
     def test_create_update_delete_network_subnet(self):
         # Create a network
         network = self.create_network()
-        self.addCleanup(self._delete_network, network)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.networks_client.delete_network, network['id'])
         net_id = network['id']
         self.assertEqual('ACTIVE', network['status'])
         # Verify network update
@@ -187,6 +177,8 @@
         body = self.subnets_client.update_subnet(subnet_id, name=new_name)
         updated_subnet = body['subnet']
         self.assertEqual(updated_subnet['name'], new_name)
+        # Verify network delete
+        self.networks_client.delete_network(network['id'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('2bf13842-c93f-4a69-83ed-717d2ec3b44e')
@@ -280,7 +272,7 @@
         network = self.create_network()
         net_id = network['id']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self._delete_network, network)
+                        self.networks_client.delete_network, network['id'])
 
         # Find a cidr that is not in use yet and create a subnet with it
         subnet = self.create_subnet(network)
@@ -324,11 +316,12 @@
     @decorators.idempotent_id('3d3852eb-3009-49ec-97ac-5ce83b73010a')
     def test_update_subnet_gw_dns_host_routes_dhcp(self):
         network = self.create_network()
-        self.addCleanup(self._delete_network, network)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.networks_client.delete_network, network['id'])
 
         subnet = self.create_subnet(
             network, **self.subnet_dict(['gateway', 'host_routes',
-                                        'dns_nameservers',
+                                         'dns_nameservers',
                                          'allocation_pools']))
         subnet_id = subnet['id']
         new_gateway = str(netaddr.IPAddress(
@@ -622,7 +615,6 @@
         port = self.create_port(slaac_network)
         self.assertIsNotNone(port['fixed_ips'][0]['ip_address'])
         self.subnets_client.delete_subnet(subnet_slaac['id'])
-        self.subnets.pop()
         subnets = self.subnets_client.list_subnets()
         subnet_ids = [subnet['id'] for subnet in subnets['subnets']]
         self.assertNotIn(subnet_slaac['id'], subnet_ids,
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index eb53fbb..93a4631 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -13,19 +13,20 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import ipaddress
+
 import netaddr
+import six
 import testtools
 
 from tempest.api.network import base_security_groups as sec_base
 from tempest.common import custom_matchers
 from tempest.common import utils
-from tempest import config
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions
 
-CONF = config.CONF
-
 
 class PortsTestJSON(sec_base.BaseSecGroupTest):
     """Test the following operations for ports:
@@ -49,6 +50,23 @@
         ports_list = body['ports']
         self.assertFalse(port_id in [n['id'] for n in ports_list])
 
+    def _create_subnet(self, network, gateway='',
+                       cidr=None, mask_bits=None, **kwargs):
+        subnet = self.create_subnet(network, gateway, cidr, mask_bits)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.subnets_client.delete_subnet, subnet['id'])
+        return subnet
+
+    def _create_network(self, network_name=None, **kwargs):
+        network_name = network_name or data_utils.rand_name(
+            self.__class__.__name__)
+        network = self.networks_client.create_network(
+            name=network_name, **kwargs)['network']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.networks_client.delete_network,
+                        network['id'])
+        return network
+
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('c72c1c0c-2193-4aca-aaa4-b1442640f51c')
     def test_create_update_delete_port(self):
@@ -70,7 +88,7 @@
     @decorators.idempotent_id('67f1b811-f8db-43e2-86bd-72c074d4a42c')
     def test_create_bulk_port(self):
         network1 = self.network
-        network2 = self.create_network()
+        network2 = self._create_network()
         network_list = [network1['id'], network2['id']]
         port_list = [{'network_id': net_id} for net_id in network_list]
         body = self.ports_client.create_bulk_ports(ports=port_list)
@@ -87,28 +105,29 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('0435f278-40ae-48cb-a404-b8a087bc09b1')
     def test_create_port_in_allowed_allocation_pools(self):
-        network = self.create_network()
+        network = self._create_network()
         net_id = network['id']
         address = self.cidr
         address.prefixlen = self.mask_bits
         if ((address.version == 4 and address.prefixlen >= 30) or
-           (address.version == 6 and address.prefixlen >= 126)):
+            (address.version == 6 and address.prefixlen >= 126)):
             msg = ("Subnet %s isn't large enough for the test" % address.cidr)
             raise exceptions.InvalidConfiguration(msg)
         allocation_pools = {'allocation_pools': [{'start': str(address[2]),
                                                   'end': str(address[-2])}]}
-        subnet = self.create_subnet(network, cidr=address,
-                                    mask_bits=address.prefixlen,
-                                    **allocation_pools)
-        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+        self._create_subnet(network, cidr=address,
+                            mask_bits=address.prefixlen,
+                            **allocation_pools)
         body = self.ports_client.create_port(network_id=net_id)
-        self.addCleanup(self.ports_client.delete_port, body['port']['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, body['port']['id'])
         port = body['port']
         ip_address = port['fixed_ips'][0]['ip_address']
         start_ip_address = allocation_pools['allocation_pools'][0]['start']
         end_ip_address = allocation_pools['allocation_pools'][0]['end']
         ip_range = netaddr.IPRange(start_ip_address, end_ip_address)
         self.assertIn(ip_address, ip_range)
+        self.ports_client.delete_port(port['id'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('c9a685bd-e83f-499c-939f-9f7863ca259f')
@@ -150,14 +169,15 @@
     @decorators.idempotent_id('e7fe260b-1e79-4dd3-86d9-bec6a7959fc5')
     def test_port_list_filter_by_ip(self):
         # Create network and subnet
-        network = self.create_network()
-        subnet = self.create_subnet(network)
-        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+        network = self._create_network()
+        self._create_subnet(network)
         # Create two ports
         port_1 = self.ports_client.create_port(network_id=network['id'])
-        self.addCleanup(self.ports_client.delete_port, port_1['port']['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port_1['port']['id'])
         port_2 = self.ports_client.create_port(network_id=network['id'])
-        self.addCleanup(self.ports_client.delete_port, port_2['port']['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port_2['port']['id'])
         # List ports filtered by fixed_ips
         port_1_fixed_ip = port_1['port']['fixed_ips'][0]['ip_address']
         fixed_ips = 'ip_address=' + port_1_fixed_ip
@@ -178,20 +198,97 @@
         self.assertIn(port_1_fixed_ip, port_ips)
         self.assertIn(network['id'], port_net_ids)
 
+    @decorators.idempotent_id('79895408-85d5-460d-94e7-9531c5fd9123')
+    @testtools.skipUnless(
+        utils.is_extension_enabled('ip-substring-filtering', 'network'),
+        'ip-substring-filtering extension not enabled.')
+    def test_port_list_filter_by_ip_substr(self):
+        # Create network and subnet
+        network = self._create_network()
+        subnet = self._create_subnet(network)
+        # Get two IP addresses
+        ip_address_1 = None
+        ip_address_2 = None
+        ip_network = ipaddress.ip_network(six.text_type(subnet['cidr']))
+        for ip in ip_network:
+            if ip == ip_network.network_address:
+                continue
+            if ip_address_1 is None:
+                ip_address_1 = six.text_type(ip)
+            else:
+                ip_address_2 = ip_address_1
+                ip_address_1 = six.text_type(ip)
+                # Make sure these two IP addresses have different substring
+                if ip_address_1[:-1] != ip_address_2[:-1]:
+                    break
+
+        # Create two ports
+        fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': ip_address_1}]
+        port_1 = self.ports_client.create_port(network_id=network['id'],
+                                               fixed_ips=fixed_ips)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port_1['port']['id'])
+        fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': ip_address_2}]
+        port_2 = self.ports_client.create_port(network_id=network['id'],
+                                               fixed_ips=fixed_ips)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port_2['port']['id'])
+
+        # Scenario 1: List port1 (port2 is filtered out)
+        if ip_address_1[:-1] != ip_address_2[:-1]:
+            ips_filter = 'ip_address_substr=' + ip_address_1[:-1]
+        else:
+            ips_filter = 'ip_address_substr=' + ip_address_1
+        ports = self.ports_client.list_ports(fixed_ips=ips_filter)['ports']
+        # Check that we got the desired port
+        port_ids = [port['id'] for port in ports]
+        fixed_ips = [port['fixed_ips'] for port in ports]
+        port_ips = []
+        for addr in fixed_ips:
+            port_ips.extend([a['ip_address'] for a in addr])
+
+        port_net_ids = [port['network_id'] for port in ports]
+        self.assertIn(network['id'], port_net_ids)
+        self.assertIn(port_1['port']['id'], port_ids)
+        self.assertIn(port_1['port']['fixed_ips'][0]['ip_address'], port_ips)
+        self.assertNotIn(port_2['port']['id'], port_ids)
+        self.assertNotIn(
+            port_2['port']['fixed_ips'][0]['ip_address'], port_ips)
+
+        # Scenario 2: List both port1 and port2
+        substr = ip_address_1
+        while substr not in ip_address_2:
+            substr = substr[:-1]
+        ips_filter = 'ip_address_substr=' + substr
+        ports = self.ports_client.list_ports(fixed_ips=ips_filter)['ports']
+        # Check that we got both port
+        port_ids = [port['id'] for port in ports]
+        fixed_ips = [port['fixed_ips'] for port in ports]
+        port_ips = []
+        for addr in fixed_ips:
+            port_ips.extend([a['ip_address'] for a in addr])
+
+        port_net_ids = [port['network_id'] for port in ports]
+        self.assertIn(network['id'], port_net_ids)
+        self.assertIn(port_1['port']['id'], port_ids)
+        self.assertIn(port_1['port']['fixed_ips'][0]['ip_address'], port_ips)
+        self.assertIn(port_2['port']['id'], port_ids)
+        self.assertIn(port_2['port']['fixed_ips'][0]['ip_address'], port_ips)
+
     @decorators.idempotent_id('5ad01ed0-0e6e-4c5d-8194-232801b15c72')
     def test_port_list_filter_by_router_id(self):
         # Create a router
-        network = self.create_network()
-        self.addCleanup(self.networks_client.delete_network, network['id'])
-        subnet = self.create_subnet(network)
-        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+        network = self._create_network()
+        self._create_subnet(network)
         router = self.create_router()
-        self.addCleanup(self.routers_client.delete_router, router['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.routers_client.delete_router, router['id'])
         port = self.ports_client.create_port(network_id=network['id'])
         # Add router interface to port created above
         self.routers_client.add_router_interface(router['id'],
                                                  port_id=port['port']['id'])
-        self.addCleanup(self.routers_client.remove_router_interface,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.routers_client.remove_router_interface,
                         router['id'], port_id=port['port']['id'])
         # List ports filtered by router_id
         port_list = self.ports_client.list_ports(device_id=router['id'])
@@ -214,12 +311,9 @@
     @decorators.idempotent_id('63aeadd4-3b49-427f-a3b1-19ca81f06270')
     def test_create_update_port_with_second_ip(self):
         # Create a network with two subnets
-        network = self.create_network()
-        self.addCleanup(self.networks_client.delete_network, network['id'])
-        subnet_1 = self.create_subnet(network)
-        self.addCleanup(self.subnets_client.delete_subnet, subnet_1['id'])
-        subnet_2 = self.create_subnet(network)
-        self.addCleanup(self.subnets_client.delete_subnet, subnet_2['id'])
+        network = self._create_network()
+        subnet_1 = self._create_subnet(network)
+        subnet_2 = self._create_subnet(network)
         fixed_ip_1 = [{'subnet_id': subnet_1['id']}]
         fixed_ip_2 = [{'subnet_id': subnet_2['id']}]
 
@@ -228,7 +322,8 @@
         # Create a port with multiple IP addresses
         port = self.create_port(network,
                                 fixed_ips=fixed_ips)
-        self.addCleanup(self.ports_client.delete_port, port['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port['id'])
         self.assertEqual(2, len(port['fixed_ips']))
         check_fixed_ips = [subnet_1['id'], subnet_2['id']]
         for item in port['fixed_ips']:
@@ -243,8 +338,7 @@
         self.assertEqual(2, len(port['fixed_ips']))
 
     def _update_port_with_security_groups(self, security_groups_names):
-        subnet_1 = self.create_subnet(self.network)
-        self.addCleanup(self.subnets_client.delete_subnet, subnet_1['id'])
+        subnet_1 = self._create_subnet(self.network)
         fixed_ip_1 = [{'subnet_id': subnet_1['id']}]
 
         security_groups_list = list()
@@ -252,7 +346,8 @@
         for name in security_groups_names:
             group_create_body = sec_grps_client.create_security_group(
                 name=name)
-            self.addCleanup(self.security_groups_client.delete_security_group,
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self.security_groups_client.delete_security_group,
                             group_create_body['security_group']['id'])
             security_groups_list.append(group_create_body['security_group']
                                         ['id'])
@@ -260,7 +355,8 @@
         sec_grp_name = data_utils.rand_name('secgroup')
         security_group = sec_grps_client.create_security_group(
             name=sec_grp_name)
-        self.addCleanup(self.security_groups_client.delete_security_group,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.security_groups_client.delete_security_group,
                         security_group['security_group']['id'])
         post_body = {
             "name": data_utils.rand_name('port-'),
@@ -269,7 +365,8 @@
             "admin_state_up": True,
             "fixed_ips": fixed_ip_1}
         body = self.ports_client.create_port(**post_body)
-        self.addCleanup(self.ports_client.delete_port, body['port']['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, body['port']['id'])
         port = body['port']
 
         # Update the port with security groups
@@ -320,7 +417,8 @@
         # Create a new port with user defined mac
         body = self.ports_client.create_port(network_id=self.network['id'],
                                              mac_address=free_mac_address)
-        self.addCleanup(self.ports_client.delete_port, body['port']['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, body['port']['id'])
         port = body['port']
         body = self.ports_client.show_port(port['id'])
         show_port = body['port']
@@ -333,12 +431,11 @@
         utils.is_extension_enabled('security-group', 'network'),
         'security-group extension not enabled.')
     def test_create_port_with_no_securitygroups(self):
-        network = self.create_network()
-        self.addCleanup(self.networks_client.delete_network, network['id'])
-        subnet = self.create_subnet(network)
-        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+        network = self._create_network()
+        self._create_subnet(network)
         port = self.create_port(network, security_groups=[])
-        self.addCleanup(self.ports_client.delete_port, port['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port, port['id'])
         self.assertIsNotNone(port['security_groups'])
         self.assertEmpty(port['security_groups'])
 
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index abbb779..f223fa4 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -20,6 +20,7 @@
 from tempest.common import utils
 from tempest import config
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
@@ -27,18 +28,6 @@
 
 class RoutersTest(base.BaseNetworkTest):
 
-    def _cleanup_router(self, router):
-        self.delete_router(router)
-        self.routers.remove(router)
-
-    def _create_router(self, name=None, admin_state_up=False,
-                       external_network_id=None, enable_snat=None):
-        # associate a cleanup with created routers to avoid quota limits
-        router = self.create_router(name, admin_state_up,
-                                    external_network_id, enable_snat)
-        self.addCleanup(self._cleanup_router, router)
-        return router
-
     def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
         interface = self.routers_client.add_router_interface(
             router_id, subnet_id=subnet_id)
@@ -65,12 +54,13 @@
                           'The public_network_id option must be specified.')
     def test_create_show_list_update_delete_router(self):
         # Create a router
-        name = data_utils.rand_name(self.__class__.__name__ + '-router')
-        router = self._create_router(
-            name=name,
+        router_name = data_utils.rand_name(self.__class__.__name__ + '-router')
+        router = self.create_router(
+            router_name,
             admin_state_up=False,
             external_network_id=CONF.network.public_network_id)
-        self.assertEqual(router['name'], name)
+        self.addCleanup(self.delete_router, router)
+        self.assertEqual(router['name'], router_name)
         self.assertEqual(router['admin_state_up'], False)
         self.assertEqual(
             router['external_gateway_info']['network_id'],
@@ -97,9 +87,16 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('b42e6e39-2e37-49cc-a6f4-8467e940900a')
     def test_add_remove_router_interface_with_subnet_id(self):
-        network = self.create_network()
+        network_name = data_utils.rand_name(self.__class__.__name__)
+        network = self.networks_client.create_network(
+            name=network_name)['network']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.networks_client.delete_network, network['id'])
         subnet = self.create_subnet(network)
-        router = self._create_router()
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.subnets_client.delete_subnet, subnet['id'])
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         # Add router interface with subnet id
         interface = self.routers_client.add_router_interface(
             router['id'], subnet_id=subnet['id'])
@@ -116,16 +113,23 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('2b7d2f37-6748-4d78-92e5-1d590234f0d5')
     def test_add_remove_router_interface_with_port_id(self):
-        network = self.create_network()
-        self.create_subnet(network)
-        router = self._create_router()
+        network_name = data_utils.rand_name(self.__class__.__name__)
+        network = self.networks_client.create_network(
+            name=network_name)['network']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.networks_client.delete_network, network['id'])
+        subnet = self.create_subnet(network)
+        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         port_body = self.ports_client.create_port(
             network_id=network['id'])
         # add router interface to port created above
         interface = self.routers_client.add_router_interface(
             router['id'],
             port_id=port_body['port']['id'])
-        self.addCleanup(self.routers_client.remove_router_interface,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.routers_client.remove_router_interface,
                         router['id'], port_id=port_body['port']['id'])
         self.assertIn('subnet_id', interface.keys())
         self.assertIn('port_id', interface.keys())
@@ -134,6 +138,8 @@
             interface['port_id'])
         self.assertEqual(show_port_body['port']['device_id'],
                          router['id'])
+        self.routers_client.remove_router_interface(
+            router['id'], port_id=port_body['port']['id'])
 
     @decorators.idempotent_id('cbe42f84-04c2-11e7-8adb-fa163e4fa634')
     @utils.requires_ext(extension='ext-gw-mode', service='network')
@@ -159,7 +165,8 @@
         # Create a router and set gateway to fixed_ip
         router = self.admin_routers_client.create_router(
             external_gateway_info=external_gateway_info)['router']
-        self.addCleanup(self.admin_routers_client.delete_router,
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.admin_routers_client.delete_router,
                         router_id=router['id'])
         # Examine router's gateway is equal to fixed_ip
         self.assertEqual(router['external_gateway_info'][
@@ -176,20 +183,29 @@
         test_routes = []
         routes_num = 4
         # Create a router
-        router = self._create_router(admin_state_up=True)
+        router = self.create_router(admin_state_up=True)
+        self.addCleanup(self.delete_router, router)
         self.addCleanup(
             self._delete_extra_routes,
             router['id'])
         # Update router extra route, second ip of the range is
         # used as next hop
         for i in range(routes_num):
-            network = self.create_network()
+            network_name = data_utils.rand_name(self.__class__.__name__)
+            network = self.networks_client.create_network(
+                name=network_name)['network']
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self.networks_client.delete_network,
+                            network['id'])
             subnet = self.create_subnet(network, cidr=next_cidr)
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self.subnets_client.delete_subnet, subnet['id'])
             next_cidr = next_cidr.next()
 
             # Add router interface with subnet id
             self.create_router_interface(router['id'], subnet['id'])
-
+            self.addCleanup(self._remove_router_interface_with_subnet_id,
+                            router['id'], subnet['id'])
             cidr = netaddr.IPNetwork(subnet['cidr'])
             next_hop = str(cidr[2])
             destination = str(subnet['cidr'])
@@ -230,7 +246,8 @@
 
     @decorators.idempotent_id('a8902683-c788-4246-95c7-ad9c6d63a4d9')
     def test_update_router_admin_state(self):
-        router = self._create_router()
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         self.assertFalse(router['admin_state_up'])
         # Update router admin state
         update_body = self.routers_client.update_router(router['id'],
@@ -242,14 +259,25 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('802c73c9-c937-4cef-824b-2191e24a6aab')
     def test_add_multiple_router_interfaces(self):
-        network01 = self.create_network(
-            network_name=data_utils.rand_name('router-network01-'))
-        network02 = self.create_network(
-            network_name=data_utils.rand_name('router-network02-'))
+        network_name = data_utils.rand_name(self.__class__.__name__)
+        network01 = self.networks_client.create_network(
+            name=network_name)['network']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.networks_client.delete_network, network01['id'])
+        network_name = data_utils.rand_name(self.__class__.__name__)
+        network02 = self.networks_client.create_network(
+            name=network_name)['network']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.networks_client.delete_network, network02['id'])
         subnet01 = self.create_subnet(network01)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.subnets_client.delete_subnet, subnet01['id'])
         sub02_cidr = self.cidr.next()
         subnet02 = self.create_subnet(network02, cidr=sub02_cidr)
-        router = self._create_router()
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.subnets_client.delete_subnet, subnet02['id'])
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         interface01 = self._add_router_interface_with_subnet_id(router['id'],
                                                                 subnet01['id'])
         self._verify_router_interface(router['id'], subnet01['id'],
@@ -261,9 +289,16 @@
 
     @decorators.idempotent_id('96522edf-b4b5-45d9-8443-fa11c26e6eff')
     def test_router_interface_port_update_with_fixed_ip(self):
-        network = self.create_network()
+        network_name = data_utils.rand_name(self.__class__.__name__)
+        network = self.networks_client.create_network(
+            name=network_name)['network']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.networks_client.delete_network, network['id'])
         subnet = self.create_subnet(network)
-        router = self._create_router()
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.subnets_client.delete_subnet, subnet['id'])
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         fixed_ip = [{'subnet_id': subnet['id']}]
         interface = self._add_router_interface_with_subnet_id(router['id'],
                                                               subnet['id'])
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index c9ce55c..0b61860 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -15,13 +15,10 @@
 
 from tempest.api.network import base
 from tempest.common import utils
-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 RoutersNegativeTest(base.BaseNetworkTest):
 
@@ -84,6 +81,8 @@
     def test_router_remove_interface_in_use_returns_409(self):
         self.routers_client.add_router_interface(self.router['id'],
                                                  subnet_id=self.subnet['id'])
+        self.addCleanup(self.routers_client.remove_router_interface,
+                        self.router['id'], subnet_id=self.subnet['id'])
         self.assertRaises(lib_exc.Conflict,
                           self.routers_client.delete_router,
                           self.router['id'])
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 24bd8ea..ea68005 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -15,12 +15,10 @@
 
 from tempest.api.network import base_security_groups as base
 from tempest.common import utils
-from tempest import config
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
-CONF = config.CONF
-
 
 class SecGroupTest(base.BaseSecGroupTest):
 
@@ -52,8 +50,8 @@
         )
 
         sec_group_rule = rule_create_body['security_group_rule']
-        self.addCleanup(self._delete_security_group_rule,
-                        sec_group_rule['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self._delete_security_group_rule, sec_group_rule['id'])
 
         expected = {'direction': direction, 'protocol': protocol,
                     'ethertype': ethertype, 'port_range_min': port_range_min,
@@ -107,6 +105,8 @@
         self.assertEqual(show_body['security_group']['name'], new_name)
         self.assertEqual(show_body['security_group']['description'],
                          new_description)
+        # Delete security group
+        self._delete_security_group(group_create_body['security_group']['id'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('cfb99e0e-7410-4a3d-8a0c-959a63ee77e9')
@@ -141,6 +141,8 @@
                          for rule in rule_list_body['security_group_rules']]
             self.assertIn(rule_create_body['security_group_rule']['id'],
                           rule_list)
+            self._delete_security_group_rule(
+                rule_create_body['security_group_rule']['id'])
 
     @decorators.idempotent_id('87dfbcf9-1849-43ea-b1e4-efa3eeae9f71')
     def test_create_security_group_rule_with_additional_args(self):
diff --git a/tempest/api/network/test_versions.py b/tempest/api/network/test_versions.py
index 2f01e50..020cb5c 100644
--- a/tempest/api/network/test_versions.py
+++ b/tempest/api/network/test_versions.py
@@ -29,7 +29,7 @@
         """
 
         result = self.network_versions_client.list_versions()
-        expected_versions = ('v2.0')
+        expected_versions = ('v2.0',)
         expected_resources = ('id', 'links', 'status')
         received_list = result.values()
 
@@ -38,3 +38,14 @@
                 for resource in expected_resources:
                     self.assertIn(resource, version)
                 self.assertIn(version['id'], expected_versions)
+
+    @decorators.attr(type='smoke')
+    @decorators.idempotent_id('e64b7216-3178-4263-967c-d389290988bf')
+    def test_show_api_v2_details(self):
+        """Test that GET /v2.0/ returns expected resources."""
+        current_version = 'v2.0'
+        expected_resources = ('subnet', 'network', 'port')
+        result = self.network_versions_client.show_version(current_version)
+        actual_resources = [r['name'] for r in result['resources']]
+        for resource in expected_resources:
+            self.assertIn(resource, actual_resources)
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index ee72163..e8f3f8b 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -36,10 +36,14 @@
     using HA proxy sync the deletion properly, otherwise, the container
     might fail to be deleted because it's not empty.
 
-    :param containers: List of containers to be deleted
+    :param containers: List of containers(or string of a container)
+                       to be deleted
     :param container_client: Client to be used to delete containers
     :param object_client: Client to be used to delete objects
     """
+    if isinstance(containers, str):
+        containers = [containers]
+
     for cont in containers:
         try:
             params = {'limit': 9999, 'format': 'json'}
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index d7c85a2..c5c30e3 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -44,14 +44,13 @@
         for i in range(ord('a'), ord('f') + 1):
             name = data_utils.rand_name(name='%s-' % six.int2byte(i))
             cls.container_client.update_container(name)
+            cls.addClassResourceCleanup(base.delete_containers,
+                                        [name],
+                                        cls.container_client,
+                                        cls.object_client)
             cls.containers.append(name)
         cls.containers_count = len(cls.containers)
 
-    @classmethod
-    def resource_cleanup(cls):
-        cls.delete_containers()
-        super(AccountTest, cls).resource_cleanup()
-
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('3499406a-ae53-4f8c-b43a-133d4dc6fe3f')
     def test_list_containers(self):
@@ -242,7 +241,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
-        prefix = '{0}-a'.format(CONF.resources_prefix)
+        prefix = 'tempest-a'
         params = {'prefix': prefix}
         resp, container_list = self.account_client.list_account_containers(
             params=params)
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index 765bc6d..e9ca0b1 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -38,9 +38,9 @@
     def test_read_object_with_rights(self):
         # attempt to read object using authorized user
         # update X-Container-Read metadata ACL
-        tenant_name = self.os_roles_operator_alt.credentials.tenant_name
-        username = self.os_roles_operator_alt.credentials.username
-        cont_headers = {'X-Container-Read': tenant_name + ':' + username}
+        tenant_id = self.os_roles_operator_alt.credentials.tenant_id
+        user_id = self.os_roles_operator_alt.credentials.user_id
+        cont_headers = {'X-Container-Read': tenant_id + ':' + user_id}
         container_client = self.os_roles_operator.container_client
         resp_meta, _ = (
             container_client.create_update_or_delete_container_metadata(
@@ -66,9 +66,9 @@
     def test_write_object_with_rights(self):
         # attempt to write object using authorized user
         # update X-Container-Write metadata ACL
-        tenant_name = self.os_roles_operator_alt.credentials.tenant_name
-        username = self.os_roles_operator_alt.credentials.username
-        cont_headers = {'X-Container-Write': tenant_name + ':' + username}
+        tenant_id = self.os_roles_operator_alt.credentials.tenant_id
+        user_id = self.os_roles_operator_alt.credentials.user_id
+        cont_headers = {'X-Container-Write': tenant_id + ':' + user_id}
         container_client = self.os_roles_operator.container_client
         resp_meta, _ = (
             container_client.create_update_or_delete_container_metadata(
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 982c4a1..fcd9a7c 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -31,9 +31,10 @@
 
         Quotas are set by adding meta values to the container,
         and are validated when set:
-          - X-Container-Meta-Quota-Bytes:
+
+        - X-Container-Meta-Quota-Bytes:
                      Maximum size of the container, in bytes.
-          - X-Container-Meta-Quota-Count:
+        - X-Container-Meta-Quota-Count:
                      Maximum object count of the container.
         """
         super(ContainerQuotasTest, self).setUp()
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 042d288..322579c 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -33,8 +33,6 @@
 
 
 class ContainerSyncTest(base.BaseObjectTest):
-    clients = {}
-
     credentials = [['operator', CONF.object_storage.operator_role],
                    ['operator_alt', CONF.object_storage.operator_role]]
 
@@ -54,6 +52,7 @@
         super(ContainerSyncTest, cls).resource_setup()
         cls.containers = []
         cls.objects = []
+        cls.clients = {}
 
         # Default container-server config only allows localhost
         cls.local_ip = '127.0.0.1'
@@ -72,14 +71,12 @@
             (cls.container_client_alt, cls.object_client_alt)
         for cont_name, client in cls.clients.items():
             client[0].create_container(cont_name)
+            cls.addClassResourceCleanup(base.delete_containers,
+                                        cont_name,
+                                        client[0],
+                                        client[1])
             cls.containers.append(cont_name)
 
-    @classmethod
-    def resource_cleanup(cls):
-        for client in cls.clients.values():
-            cls.delete_containers(client[0], client[1])
-        super(ContainerSyncTest, cls).resource_cleanup()
-
     def _test_container_synchronization(self, make_headers):
         # container to container synchronization
         # to allow/accept sync requests to/from other accounts
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index 51b0a1d..75111b6 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -24,16 +24,6 @@
 
 
 class ContainerTest(base.BaseObjectTest):
-    @classmethod
-    def resource_setup(cls):
-        super(ContainerTest, cls).resource_setup()
-        cls.containers = []
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.delete_containers()
-        super(ContainerTest, cls).resource_cleanup()
-
     def assertContainer(self, container, count, byte, versioned):
         resp, _ = self.container_client.list_container_metadata(container)
         self.assertHeaders(resp, 'Container', 'HEAD')
@@ -52,7 +42,10 @@
         # create container
         vers_container_name = data_utils.rand_name(name='TestVersionContainer')
         resp, _ = self.container_client.update_container(vers_container_name)
-        self.containers.append(vers_container_name)
+        self.addCleanup(base.delete_containers,
+                        [vers_container_name],
+                        self.container_client,
+                        self.object_client)
         self.assertHeaders(resp, 'Container', 'PUT')
         self.assertContainer(vers_container_name, '0', '0', 'Missing Header')
 
@@ -61,7 +54,10 @@
         resp, _ = self.container_client.update_container(
             base_container_name,
             **headers)
-        self.containers.append(base_container_name)
+        self.addCleanup(base.delete_containers,
+                        [base_container_name],
+                        self.container_client,
+                        self.object_client)
         self.assertHeaders(resp, 'Container', 'PUT')
         self.assertContainer(base_container_name, '0', '0',
                              vers_container_name)
diff --git a/tempest/api/volume/admin/test_backends_capabilities.py b/tempest/api/volume/admin/test_backends_capabilities.py
index 607fc43..affed6b 100644
--- a/tempest/api/volume/admin/test_backends_capabilities.py
+++ b/tempest/api/volume/admin/test_backends_capabilities.py
@@ -72,8 +72,8 @@
         ]
 
         # Returns a tuple of VOLUME_STATS values
-        expected_list = list(map(operator.itemgetter(*VOLUME_STATS),
-                             cinder_pools))
-        observed_list = list(map(operator.itemgetter(*VOLUME_STATS),
-                             capabilities))
+        expected_list = sorted(list(map(operator.itemgetter(*VOLUME_STATS),
+                                        cinder_pools)))
+        observed_list = sorted(list(map(operator.itemgetter(*VOLUME_STATS),
+                                        capabilities)))
         self.assertEqual(expected_list, observed_list)
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
new file mode 100644
index 0000000..f695f51
--- /dev/null
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -0,0 +1,254 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# Copyright (C) 2017 Dell Inc. or its subsidiaries.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.volume import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class BaseGroupSnapshotsTest(base.BaseVolumeAdminTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaseGroupSnapshotsTest, cls).skip_checks()
+        if not CONF.volume_feature_enabled.snapshot:
+            raise cls.skipException("Cinder volume snapshots are disabled")
+
+    def _create_group_snapshot(self, **kwargs):
+        if 'name' not in kwargs:
+            kwargs['name'] = data_utils.rand_name(
+                self.__class__.__name__ + '-Group_Snapshot')
+
+        group_snapshot = self.group_snapshots_client.create_group_snapshot(
+            **kwargs)['group_snapshot']
+        group_snapshot['group_id'] = kwargs['group_id']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self._delete_group_snapshot, group_snapshot)
+        waiters.wait_for_volume_resource_status(
+            self.group_snapshots_client, group_snapshot['id'], 'available')
+        return group_snapshot
+
+    def _delete_group_snapshot(self, group_snapshot):
+        self.group_snapshots_client.delete_group_snapshot(group_snapshot['id'])
+        vols = self.volumes_client.list_volumes(detail=True)['volumes']
+        snapshots = self.snapshots_client.list_snapshots(
+            detail=True)['snapshots']
+        for vol in vols:
+            for snap in snapshots:
+                if (vol['group_id'] == group_snapshot['group_id'] and
+                        vol['id'] == snap['volume_id']):
+                    self.snapshots_client.wait_for_resource_deletion(
+                        snap['id'])
+        self.group_snapshots_client.wait_for_resource_deletion(
+            group_snapshot['id'])
+
+
+class GroupSnapshotsTest(BaseGroupSnapshotsTest):
+    _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):
+        # Create volume type
+        volume_type = self.create_volume_type()
+
+        # Create group type
+        group_type = self.create_group_type()
+
+        # Create group
+        grp = self.create_group(group_type=group_type['id'],
+                                volume_types=[volume_type['id']])
+
+        # Create volume
+        vol = self.create_volume(volume_type=volume_type['id'],
+                                 group_id=grp['id'])
+
+        # Create group snapshot
+        group_snapshot_name = data_utils.rand_name('group_snapshot')
+        group_snapshot = self._create_group_snapshot(
+            group_id=grp['id'], name=group_snapshot_name)
+        snapshots = self.snapshots_client.list_snapshots(
+            detail=True)['snapshots']
+        for snap in snapshots:
+            if vol['id'] == snap['volume_id']:
+                waiters.wait_for_volume_resource_status(
+                    self.snapshots_client, snap['id'], 'available')
+        self.assertEqual(group_snapshot_name, group_snapshot['name'])
+
+        # Get a given group snapshot
+        group_snapshot = self.group_snapshots_client.show_group_snapshot(
+            group_snapshot['id'])['group_snapshot']
+        self.assertEqual(group_snapshot_name, group_snapshot['name'])
+
+        # Get all group snapshots with details, check some detail-specific
+        # elements, and look for the created group snapshot
+        group_snapshots = self.group_snapshots_client.list_group_snapshots(
+            detail=True)['group_snapshots']
+        for grp_snapshot in group_snapshots:
+            self.assertIn('created_at', grp_snapshot)
+            self.assertIn('group_id', grp_snapshot)
+        self.assertIn((group_snapshot['name'], group_snapshot['id']),
+                      [(m['name'], m['id']) for m in group_snapshots])
+
+        # Delete group snapshot
+        self._delete_group_snapshot(group_snapshot)
+        group_snapshots = self.group_snapshots_client.list_group_snapshots()[
+            'group_snapshots']
+        self.assertEmpty(group_snapshots)
+
+    @decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
+    def test_create_group_from_group_snapshot(self):
+        # Create volume type
+        volume_type = self.create_volume_type()
+
+        # Create group type
+        group_type = self.create_group_type()
+
+        # Create Group
+        grp = self.create_group(group_type=group_type['id'],
+                                volume_types=[volume_type['id']])
+
+        # Create volume
+        vol = self.create_volume(volume_type=volume_type['id'],
+                                 group_id=grp['id'])
+
+        # Create group_snapshot
+        group_snapshot_name = data_utils.rand_name('group_snapshot')
+        group_snapshot = self._create_group_snapshot(
+            group_id=grp['id'], name=group_snapshot_name)
+        self.assertEqual(group_snapshot_name, group_snapshot['name'])
+        snapshots = self.snapshots_client.list_snapshots(
+            detail=True)['snapshots']
+        for snap in snapshots:
+            if vol['id'] == snap['volume_id']:
+                waiters.wait_for_volume_resource_status(
+                    self.snapshots_client, snap['id'], 'available')
+
+        # Create Group from Group snapshot
+        grp_name2 = data_utils.rand_name('Group_from_snap')
+        grp2 = self.groups_client.create_group_from_source(
+            group_snapshot_id=group_snapshot['id'], name=grp_name2)['group']
+        self.addCleanup(self.delete_group, grp2['id'])
+        self.assertEqual(grp_name2, grp2['name'])
+        vols = self.volumes_client.list_volumes(detail=True)['volumes']
+        for vol in vols:
+            if vol['group_id'] == grp2['id']:
+                waiters.wait_for_volume_resource_status(
+                    self.volumes_client, vol['id'], 'available')
+        waiters.wait_for_volume_resource_status(
+            self.groups_client, grp2['id'], 'available')
+
+    @decorators.idempotent_id('7d7fc000-0b4c-4376-a372-544116d2e127')
+    @decorators.related_bug('1739031')
+    def test_delete_group_snapshots_following_updated_volumes(self):
+        volume_type = self.create_volume_type()
+
+        group_type = self.create_group_type()
+
+        # Create a volume group
+        grp = self.create_group(group_type=group_type['id'],
+                                volume_types=[volume_type['id']])
+
+        # Note: When dealing with consistency groups all volumes must
+        # reside on the same backend. Adding volumes to the same consistency
+        # group from multiple backends isn't supported. In order to ensure all
+        # volumes share the same backend, all volumes must share same
+        # volume-type and group id.
+        volume_list = []
+        for _ in range(2):
+            volume = self.create_volume(volume_type=volume_type['id'],
+                                        group_id=grp['id'])
+            volume_list.append(volume['id'])
+
+        for vol in volume_list:
+            self.groups_client.update_group(grp['id'],
+                                            remove_volumes=vol)
+            waiters.wait_for_volume_resource_status(
+                self.groups_client, grp['id'], 'available')
+
+            self.groups_client.update_group(grp['id'],
+                                            add_volumes=vol)
+            waiters.wait_for_volume_resource_status(
+                self.groups_client, grp['id'], 'available')
+
+        # Verify the created volumes are associated with consistency group
+        vols = self.volumes_client.list_volumes(detail=True)['volumes']
+        grp_vols = [v for v in vols if v['group_id'] == grp['id']]
+        self.assertEqual(2, len(grp_vols))
+
+        # Create a snapshot group
+        group_snapshot = self._create_group_snapshot(group_id=grp['id'])
+        snapshots = self.snapshots_client.list_snapshots(
+            detail=True)['snapshots']
+
+        for snap in snapshots:
+            if snap['volume_id'] in volume_list:
+                waiters.wait_for_volume_resource_status(
+                    self.snapshots_client, snap['id'], 'available')
+
+        # Delete a snapshot group
+        self._delete_group_snapshot(group_snapshot)
+
+
+class GroupSnapshotsV319Test(BaseGroupSnapshotsTest):
+    _api_version = 3
+    min_microversion = '3.19'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
+    @decorators.skip_because(bug='1770179')
+    def test_reset_group_snapshot_status(self):
+        # Create volume type
+        volume_type = self.create_volume_type()
+
+        # Create group type
+        group_type = self.create_group_type()
+
+        # Create group
+        group = self.create_group(group_type=group_type['id'],
+                                  volume_types=[volume_type['id']])
+
+        # Create volume
+        volume = self.create_volume(volume_type=volume_type['id'],
+                                    group_id=group['id'])
+
+        # Create group snapshot
+        group_snapshot = self._create_group_snapshot(group_id=group['id'])
+        snapshots = self.snapshots_client.list_snapshots(
+            detail=True)['snapshots']
+        for snap in snapshots:
+            if volume['id'] == snap['volume_id']:
+                waiters.wait_for_volume_resource_status(
+                    self.snapshots_client, snap['id'], 'available')
+
+        # Reset group snapshot status
+        self.addCleanup(waiters.wait_for_volume_resource_status,
+                        self.group_snapshots_client,
+                        group_snapshot['id'], 'available')
+        self.addCleanup(
+            self.admin_group_snapshots_client.reset_group_snapshot_status,
+            group_snapshot['id'], 'available')
+        for status in ['creating', 'available', 'error']:
+            self.admin_group_snapshots_client.reset_group_snapshot_status(
+                group_snapshot['id'], status)
+            waiters.wait_for_volume_resource_status(
+                self.group_snapshots_client, group_snapshot['id'], status)
diff --git a/tempest/api/volume/admin/test_group_type_specs.py b/tempest/api/volume/admin/test_group_type_specs.py
new file mode 100644
index 0000000..c5e6d1a
--- /dev/null
+++ b/tempest/api/volume/admin/test_group_type_specs.py
@@ -0,0 +1,80 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.volume import base
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class GroupTypeSpecsTest(base.BaseVolumeAdminTest):
+    _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):
+        # Create new group type
+        group_type = self.create_group_type()
+
+        # Create new group type specs
+        create_specs = {
+            "key1": "value1",
+            "key2": "value2"
+        }
+        body = self.admin_group_types_client.create_or_update_group_type_specs(
+            group_type['id'], create_specs)['group_specs']
+        self.assertEqual(create_specs, body)
+
+        # Create a new group type spec and update an existing group type spec
+        update_specs = {
+            "key2": "value2-updated",
+            "key3": "value3"
+        }
+        body = self.admin_group_types_client.create_or_update_group_type_specs(
+            group_type['id'], update_specs)['group_specs']
+        self.assertEqual(update_specs, body)
+
+        # Show specified item of group type specs
+        spec_keys = ['key2', 'key3']
+        for key in spec_keys:
+            body = self.admin_group_types_client.show_group_type_specs_item(
+                group_type['id'], key)
+            self.assertIn(key, body)
+            self.assertEqual(update_specs[key], body[key])
+
+        # Update specified item of group type specs
+        update_key = 'key3'
+        update_spec = {update_key: "value3-updated"}
+        body = self.admin_group_types_client.update_group_type_specs_item(
+            group_type['id'], update_key, update_spec)
+        self.assertEqual(update_spec, body)
+
+        # List all group type specs that created or updated above
+        list_specs = {}
+        list_specs.update(create_specs)
+        list_specs.update(update_specs)
+        list_specs.update(update_spec)
+        body = self.admin_group_types_client.list_group_type_specs(
+            group_type['id'])['group_specs']
+        self.assertEqual(list_specs, body)
+
+        # Delete specified item of group type specs
+        delete_key = 'key1'
+        self.admin_group_types_client.delete_group_type_specs_item(
+            group_type['id'], delete_key)
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.admin_group_types_client.show_group_type_specs_item,
+            group_type['id'], delete_key)
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
index 0df5fbd..6723207 100644
--- a/tempest/api/volume/admin/test_group_types.py
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -24,7 +24,7 @@
     max_microversion = 'latest'
 
     @decorators.idempotent_id('dd71e5f9-393e-4d4f-90e9-fa1b8d278864')
-    def test_group_type_create_list_show(self):
+    def test_group_type_create_list_update_show(self):
         # Create/list/show group type.
         name = data_utils.rand_name(self.__class__.__name__ + '-group-type')
         description = data_utils.rand_name("group-type-description")
@@ -46,8 +46,19 @@
         self.assertIsInstance(group_list, list)
         self.assertNotEmpty(group_list)
 
+        update_params = {
+            'name': data_utils.rand_name(
+                self.__class__.__name__ + '-updated-group-type'),
+            'description': 'updated-group-type-desc'
+        }
+        updated_group_type = self.admin_group_types_client.update_group_type(
+            body['id'], **update_params)['group_type']
+        for key, expected_val in update_params.items():
+            self.assertEqual(expected_val, updated_group_type[key])
+
         fetched_group_type = self.admin_group_types_client.show_group_type(
             body['id'])['group_type']
+        params.update(update_params)  # Add updated params to original params.
         for key in params.keys():
             self.assertEqual(params[key], fetched_group_type[key],
                              '%s of the fetched group_type is different '
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
index 6b53d85..2f6eb6b 100644
--- a/tempest/api/volume/admin/test_groups.py
+++ b/tempest/api/volume/admin/test_groups.py
@@ -17,54 +17,14 @@
 from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
 
 
-class BaseGroupsTest(base.BaseVolumeAdminTest):
-
-    def _delete_group(self, grp_id, delete_volumes=True):
-        self.groups_client.delete_group(grp_id, delete_volumes)
-        vols = self.volumes_client.list_volumes(detail=True)['volumes']
-        for vol in vols:
-            if vol['group_id'] == grp_id:
-                self.volumes_client.wait_for_resource_deletion(vol['id'])
-        self.groups_client.wait_for_resource_deletion(grp_id)
-
-    def _delete_group_snapshot(self, group_snapshot_id, grp_id):
-        self.group_snapshots_client.delete_group_snapshot(group_snapshot_id)
-        vols = self.volumes_client.list_volumes(detail=True)['volumes']
-        snapshots = self.snapshots_client.list_snapshots(
-            detail=True)['snapshots']
-        for vol in vols:
-            for snap in snapshots:
-                if (vol['group_id'] == grp_id and
-                        vol['id'] == snap['volume_id']):
-                    self.snapshots_client.wait_for_resource_deletion(
-                        snap['id'])
-        self.group_snapshots_client.wait_for_resource_deletion(
-            group_snapshot_id)
-
-    def _create_group(self, group_type, volume_type, grp_name=None):
-        if not grp_name:
-            grp_name = data_utils.rand_name('Group')
-        grp = self.groups_client.create_group(
-            group_type=group_type['id'],
-            volume_types=[volume_type['id']],
-            name=grp_name)['group']
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self._delete_group, grp['id'])
-        waiters.wait_for_volume_resource_status(
-            self.groups_client, grp['id'], 'available')
-        self.assertEqual(grp_name, grp['name'])
-        return grp
-
-
-class GroupsTest(BaseGroupsTest):
+class GroupsTest(base.BaseVolumeAdminTest):
     _api_version = 3
-    min_microversion = '3.14'
+    min_microversion = '3.13'
     max_microversion = 'latest'
 
     @decorators.idempotent_id('4b111d28-b73d-4908-9bd2-03dc2992e4d4')
@@ -77,13 +37,15 @@
 
         # Create group
         grp1_name = data_utils.rand_name('Group1')
-        grp1 = self._create_group(group_type, volume_type,
-                                  grp_name=grp1_name)
+        grp1 = self.create_group(group_type=group_type['id'],
+                                 volume_types=[volume_type['id']],
+                                 name=grp1_name)
         grp1_id = grp1['id']
 
         grp2_name = data_utils.rand_name('Group2')
-        grp2 = self._create_group(group_type, volume_type,
-                                  grp_name=grp2_name)
+        grp2 = self.create_group(group_type=group_type['id'],
+                                 volume_types=[volume_type['id']],
+                                 name=grp2_name)
         grp2_id = grp2['id']
 
         # Create volume
@@ -125,143 +87,12 @@
 
         # Delete group
         # grp1 has a volume so delete_volumes flag is set to True by default
-        self._delete_group(grp1_id)
+        self.delete_group(grp1_id)
         # grp2 is empty so delete_volumes flag can be set to False
-        self._delete_group(grp2_id, delete_volumes=False)
+        self.delete_group(grp2_id, delete_volumes=False)
         grps = self.groups_client.list_groups(detail=True)['groups']
         self.assertEmpty(grps)
 
-    @decorators.idempotent_id('1298e537-f1f0-47a3-a1dd-8adec8168897')
-    def test_group_snapshot_create_show_list_delete(self):
-        # Create volume type
-        volume_type = self.create_volume_type()
-
-        # Create group type
-        group_type = self.create_group_type()
-
-        # Create group
-        grp = self._create_group(group_type, volume_type)
-
-        # Create volume
-        vol = self.create_volume(volume_type=volume_type['id'],
-                                 group_id=grp['id'])
-
-        # Create group snapshot
-        group_snapshot_name = data_utils.rand_name('group_snapshot')
-        group_snapshot = (
-            self.group_snapshots_client.create_group_snapshot(
-                group_id=grp['id'],
-                name=group_snapshot_name)['group_snapshot'])
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self._delete_group_snapshot,
-                        group_snapshot['id'], grp['id'])
-        snapshots = self.snapshots_client.list_snapshots(
-            detail=True)['snapshots']
-        for snap in snapshots:
-            if vol['id'] == snap['volume_id']:
-                waiters.wait_for_volume_resource_status(
-                    self.snapshots_client, snap['id'], 'available')
-        waiters.wait_for_volume_resource_status(
-            self.group_snapshots_client,
-            group_snapshot['id'], 'available')
-        self.assertEqual(group_snapshot_name, group_snapshot['name'])
-
-        # Get a given group snapshot
-        group_snapshot = self.group_snapshots_client.show_group_snapshot(
-            group_snapshot['id'])['group_snapshot']
-        self.assertEqual(group_snapshot_name, group_snapshot['name'])
-
-        # Get all group snapshots with details, check some detail-specific
-        # elements, and look for the created group snapshot
-        group_snapshots = (self.group_snapshots_client.list_group_snapshots(
-            detail=True)['group_snapshots'])
-        for grp_snapshot in group_snapshots:
-            self.assertIn('created_at', grp_snapshot)
-            self.assertIn('group_id', grp_snapshot)
-        self.assertIn((group_snapshot['name'], group_snapshot['id']),
-                      [(m['name'], m['id']) for m in group_snapshots])
-
-        # Delete group snapshot
-        self._delete_group_snapshot(group_snapshot['id'], grp['id'])
-        group_snapshots = (self.group_snapshots_client.list_group_snapshots()
-                           ['group_snapshots'])
-        self.assertEmpty(group_snapshots)
-
-    @decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
-    def test_create_group_from_group_snapshot(self):
-        # Create volume type
-        volume_type = self.create_volume_type()
-
-        # Create group type
-        group_type = self.create_group_type()
-
-        # Create Group
-        grp = self._create_group(group_type, volume_type)
-
-        # Create volume
-        vol = self.create_volume(volume_type=volume_type['id'],
-                                 group_id=grp['id'])
-
-        # Create group_snapshot
-        group_snapshot_name = data_utils.rand_name('group_snapshot')
-        group_snapshot = (
-            self.group_snapshots_client.create_group_snapshot(
-                group_id=grp['id'],
-                name=group_snapshot_name)['group_snapshot'])
-        self.addCleanup(self._delete_group_snapshot,
-                        group_snapshot['id'], grp['id'])
-        self.assertEqual(group_snapshot_name, group_snapshot['name'])
-        snapshots = self.snapshots_client.list_snapshots(
-            detail=True)['snapshots']
-        for snap in snapshots:
-            if vol['id'] == snap['volume_id']:
-                waiters.wait_for_volume_resource_status(
-                    self.snapshots_client, snap['id'], 'available')
-        waiters.wait_for_volume_resource_status(
-            self.group_snapshots_client, group_snapshot['id'], 'available')
-
-        # Create Group from Group snapshot
-        grp_name2 = data_utils.rand_name('Group_from_snap')
-        grp2 = self.groups_client.create_group_from_source(
-            group_snapshot_id=group_snapshot['id'], name=grp_name2)['group']
-        self.addCleanup(self._delete_group, grp2['id'])
-        self.assertEqual(grp_name2, grp2['name'])
-        vols = self.volumes_client.list_volumes(detail=True)['volumes']
-        for vol in vols:
-            if vol['group_id'] == grp2['id']:
-                waiters.wait_for_volume_resource_status(
-                    self.volumes_client, vol['id'], 'available')
-        waiters.wait_for_volume_resource_status(
-            self.groups_client, grp2['id'], 'available')
-
-    @decorators.idempotent_id('2424af8c-7851-4888-986a-794b10c3210e')
-    def test_create_group_from_group(self):
-        # Create volume type
-        volume_type = self.create_volume_type()
-
-        # Create group type
-        group_type = self.create_group_type()
-
-        # Create Group
-        grp = self._create_group(group_type, volume_type)
-
-        # Create volume
-        self.create_volume(volume_type=volume_type['id'], group_id=grp['id'])
-
-        # Create Group from Group
-        grp_name2 = data_utils.rand_name('Group_from_grp')
-        grp2 = self.groups_client.create_group_from_source(
-            source_group_id=grp['id'], name=grp_name2)['group']
-        self.addCleanup(self._delete_group, grp2['id'])
-        self.assertEqual(grp_name2, grp2['name'])
-        vols = self.volumes_client.list_volumes(detail=True)['volumes']
-        for vol in vols:
-            if vol['group_id'] == grp2['id']:
-                waiters.wait_for_volume_resource_status(
-                    self.volumes_client, vol['id'], 'available')
-        waiters.wait_for_volume_resource_status(
-            self.groups_client, grp2['id'], 'available')
-
     @decorators.idempotent_id('4a8a6fd2-8b3b-4641-8f54-6a6f99320006')
     def test_group_update(self):
         # Create volume type
@@ -271,7 +102,8 @@
         group_type = self.create_group_type()
 
         # Create Group
-        grp = self._create_group(group_type, volume_type)
+        grp = self.create_group(group_type=group_type['id'],
+                                volume_types=[volume_type['id']])
 
         # Create volumes
         grp_vols = []
@@ -317,56 +149,42 @@
         self.assertEqual(2, len(grp_vols))
 
 
-class GroupsV319Test(BaseGroupsTest):
+class GroupsV314Test(base.BaseVolumeAdminTest):
     _api_version = 3
-    min_microversion = '3.19'
+    min_microversion = '3.14'
     max_microversion = 'latest'
 
-    @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
-    def test_reset_group_snapshot_status(self):
+    @decorators.idempotent_id('2424af8c-7851-4888-986a-794b10c3210e')
+    def test_create_group_from_group(self):
         # Create volume type
         volume_type = self.create_volume_type()
 
         # Create group type
         group_type = self.create_group_type()
 
-        # Create group
-        group = self._create_group(group_type, volume_type)
+        # Create Group
+        grp = self.create_group(group_type=group_type['id'],
+                                volume_types=[volume_type['id']])
 
         # Create volume
-        volume = self.create_volume(volume_type=volume_type['id'],
-                                    group_id=group['id'])
+        self.create_volume(volume_type=volume_type['id'], group_id=grp['id'])
 
-        # Create group snapshot
-        group_snapshot_name = data_utils.rand_name('group_snapshot')
-        group_snapshot = (self.group_snapshots_client.create_group_snapshot(
-            group_id=group['id'], name=group_snapshot_name)['group_snapshot'])
-        self.addCleanup(self._delete_group_snapshot,
-                        group_snapshot['id'], group['id'])
-        snapshots = self.snapshots_client.list_snapshots(
-            detail=True)['snapshots']
-        for snap in snapshots:
-            if volume['id'] == snap['volume_id']:
+        # Create Group from Group
+        grp_name2 = data_utils.rand_name('Group_from_grp')
+        grp2 = self.groups_client.create_group_from_source(
+            source_group_id=grp['id'], name=grp_name2)['group']
+        self.addCleanup(self.delete_group, grp2['id'])
+        self.assertEqual(grp_name2, grp2['name'])
+        vols = self.volumes_client.list_volumes(detail=True)['volumes']
+        for vol in vols:
+            if vol['group_id'] == grp2['id']:
                 waiters.wait_for_volume_resource_status(
-                    self.snapshots_client, snap['id'], 'available')
+                    self.volumes_client, vol['id'], 'available')
         waiters.wait_for_volume_resource_status(
-            self.group_snapshots_client, group_snapshot['id'], 'available')
-
-        # Reset group snapshot status
-        self.addCleanup(waiters.wait_for_volume_resource_status,
-                        self.group_snapshots_client,
-                        group_snapshot['id'], 'available')
-        self.addCleanup(
-            self.admin_group_snapshots_client.reset_group_snapshot_status,
-            group_snapshot['id'], 'available')
-        for status in ['creating', 'available', 'error']:
-            self.admin_group_snapshots_client.reset_group_snapshot_status(
-                group_snapshot['id'], status)
-            waiters.wait_for_volume_resource_status(
-                self.group_snapshots_client, group_snapshot['id'], status)
+            self.groups_client, grp2['id'], 'available')
 
 
-class GroupsV320Test(BaseGroupsTest):
+class GroupsV320Test(base.BaseVolumeAdminTest):
     _api_version = 3
     min_microversion = '3.20'
     max_microversion = 'latest'
@@ -380,7 +198,8 @@
         group_type = self.create_group_type()
 
         # Create group
-        group = self._create_group(group_type, volume_type)
+        group = self.create_group(group_type=group_type['id'],
+                                  volume_types=[volume_type['id']])
 
         # Reset group status
         self.addCleanup(waiters.wait_for_volume_resource_status,
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index c0891e4..c5c70d2 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -29,6 +29,10 @@
         if not CONF.volume_feature_enabled.multi_backend:
             raise cls.skipException("Cinder multi-backend feature disabled")
 
+        if len(set(CONF.volume.backend_names)) < 2:
+            raise cls.skipException("Requires at least two different "
+                                    "backend names")
+
     @classmethod
     def resource_setup(cls):
         super(VolumeMultiBackendTest, cls).resource_setup()
@@ -41,9 +45,6 @@
 
         # Volume/Type creation (uses volume_backend_name)
         # It is not allowed to create the same backend name twice
-        if len(backend_names) < 2:
-            raise cls.skipException("Requires at least two different "
-                                    "backend names")
         for backend_name in backend_names:
             # Volume/Type creation (uses backend_name)
             cls._create_type_and_volume(backend_name, False)
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index 9ff7160..37a47ec 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -35,6 +35,9 @@
     def skip_checks(cls):
         super(SnapshotManageAdminTest, cls).skip_checks()
 
+        if not CONF.volume_feature_enabled.snapshot:
+            raise cls.skipException("Cinder volume snapshots are disabled")
+
         if not CONF.volume_feature_enabled.manage_snapshot:
             raise cls.skipException("Manage snapshot tests are disabled")
 
@@ -60,7 +63,7 @@
         # Verify the original snapshot does not exist in snapshot list
         params = {'all_tenants': 1}
         all_snapshots = self.admin_snapshots_client.list_snapshots(
-            detail=True, params=params)['snapshots']
+            detail=True, **params)['snapshots']
         self.assertNotIn(snapshot['id'], [v['id'] for v in all_snapshots])
 
         # Manage the snapshot
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 20c3538..9907497 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -62,8 +62,16 @@
         return message_id
 
     @decorators.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a')
-    def test_list_messages(self):
-        self._create_user_message()
+    def test_list_show_messages(self):
+        message_id = self._create_user_message()
+        self.addCleanup(self.messages_client.delete_message, message_id)
+
+        # show message
+        message = self.messages_client.show_message(message_id)['message']
+        for key in MESSAGE_KEYS:
+            self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
+
+        # list messages
         messages = self.messages_client.list_messages()['messages']
         self.assertIsInstance(messages, list)
         for message in messages:
@@ -71,16 +79,6 @@
                 self.assertIn(key, message.keys(),
                               'Missing expected key %s' % key)
 
-    @decorators.idempotent_id('55a4a61e-c7b2-4ba0-a05d-b914bdef3070')
-    def test_show_message(self):
-        message_id = self._create_user_message()
-        self.addCleanup(self.messages_client.delete_message, message_id)
-
-        message = self.messages_client.show_message(message_id)['message']
-
-        for key in MESSAGE_KEYS:
-            self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
-
     @decorators.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
     def test_delete_message(self):
         message_id = self._create_user_message()
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index ce0cbd2..7e53ce8 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -46,8 +46,8 @@
         # show host API should fail (return code: 404). The cinder-volume host
         # is presented in format: <host-name>@driver-name.
         c_vol_hosts = [host['host_name'] for host in hosts
-                       if (host['service'] == 'cinder-volume'
-                           and host['service-state'] == 'enabled')]
+                       if (host['service'] == 'cinder-volume' and
+                           host['service-state'] == 'enabled')]
         self.assertNotEmpty(c_vol_hosts,
                             "No available cinder-volume host is found, "
                             "all hosts that found are: %s" % hosts)
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 42bfcd6..053a7d9 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -13,10 +13,8 @@
 #    under the License.
 
 from tempest.api.volume import base
-from tempest.common import identity
 from tempest.common import tempest_fixtures as fixtures
 from tempest.common import waiters
-from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
 QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups',
@@ -24,27 +22,36 @@
 QUOTA_USAGE_KEYS = ['reserved', 'limit', 'in_use']
 
 
-class BaseVolumeQuotasAdminTestJSON(base.BaseVolumeAdminTest):
-    force_tenant_isolation = True
-
+class VolumeQuotasAdminTestJSON(base.BaseVolumeAdminTest):
     credentials = ['primary', 'alt', 'admin']
 
     def setUp(self):
         # NOTE(jeremy.zhang): Avoid conflicts with volume quota class tests.
         self.useFixture(fixtures.LockFixture('volume_quotas'))
-        super(BaseVolumeQuotasAdminTestJSON, self).setUp()
+        super(VolumeQuotasAdminTestJSON, self).setUp()
 
     @classmethod
     def setup_credentials(cls):
-        super(BaseVolumeQuotasAdminTestJSON, cls).setup_credentials()
+        super(VolumeQuotasAdminTestJSON, cls).setup_credentials()
         cls.demo_tenant_id = cls.os_primary.credentials.tenant_id
-        cls.alt_client = cls.os_alt.volumes_client_latest
 
     @classmethod
     def setup_clients(cls):
-        super(BaseVolumeQuotasAdminTestJSON, cls).setup_clients()
-        cls.transfer_client = cls.os_primary.volume_transfers_v2_client
-        cls.alt_transfer_client = cls.os_alt.volume_transfers_v2_client
+        super(VolumeQuotasAdminTestJSON, cls).setup_clients()
+        cls.transfer_client = cls.os_primary.volume_transfers_client_latest
+        cls.alt_transfer_client = cls.os_alt.volume_transfers_client_latest
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumeQuotasAdminTestJSON, cls).resource_setup()
+
+        # Save the current set of quotas so that some tests may use it
+        # to restore the quotas to their original values after they are
+        # done.
+        original_quota_set = (cls.admin_quotas_client.show_quota_set(
+            cls.demo_tenant_id)['quota_set'])
+        cls.cleanup_quota_set = dict(
+            (k, v) for k, v in original_quota_set.items() if k in QUOTA_KEYS)
 
     @decorators.idempotent_id('59eada70-403c-4cef-a2a3-a8ce2f1b07a0')
     def test_list_quotas(self):
@@ -63,8 +70,6 @@
     @decorators.idempotent_id('3d45c99e-cc42-4424-a56e-5cbd212b63a6')
     def test_update_all_quota_resources_for_tenant(self):
         # Admin can update all the resource quota limits for a tenant
-        default_quota_set = self.admin_quotas_client.show_default_quota_set(
-            self.demo_tenant_id)['quota_set']
         new_quota_set = {'gigabytes': 1009,
                          'volumes': 11,
                          'snapshots': 11,
@@ -77,11 +82,9 @@
             self.demo_tenant_id,
             **new_quota_set)['quota_set']
 
-        cleanup_quota_set = dict(
-            (k, v) for k, v in default_quota_set.items()
-            if k in QUOTA_KEYS)
         self.addCleanup(self.admin_quotas_client.update_quota_set,
-                        self.demo_tenant_id, **cleanup_quota_set)
+                        self.demo_tenant_id, **self.cleanup_quota_set)
+
         # test that the specific values we set are actually in
         # the final result. There is nothing here that ensures there
         # would be no other values in there.
@@ -97,6 +100,25 @@
             for usage_key in QUOTA_USAGE_KEYS:
                 self.assertIn(usage_key, quota_usage[key])
 
+    @decorators.idempotent_id('874b35a9-51f1-4258-bec5-cd561b6690d3')
+    def test_delete_quota(self):
+        # Admin can delete the resource quota set for a project
+
+        self.addCleanup(self.admin_quotas_client.update_quota_set,
+                        self.demo_tenant_id, **self.cleanup_quota_set)
+
+        quota_set_default = self.admin_quotas_client.show_default_quota_set(
+            self.demo_tenant_id)['quota_set']
+        volume_default = quota_set_default['volumes']
+
+        self.admin_quotas_client.update_quota_set(
+            self.demo_tenant_id, volumes=(volume_default + 5))
+
+        self.admin_quotas_client.delete_quota_set(self.demo_tenant_id)
+        quota_set_new = (self.admin_quotas_client.show_quota_set(
+            self.demo_tenant_id)['quota_set'])
+        self.assertEqual(volume_default, quota_set_new['volumes'])
+
     @decorators.idempotent_id('ae8b6091-48ad-4bfa-a188-bbf5cc02115f')
     def test_quota_usage(self):
         quota_usage = self.admin_quotas_client.show_quota_set(
@@ -116,28 +138,6 @@
                          volume["size"],
                          new_quota_usage['gigabytes']['in_use'])
 
-    @decorators.idempotent_id('874b35a9-51f1-4258-bec5-cd561b6690d3')
-    def test_delete_quota(self):
-        # Admin can delete the resource quota set for a project
-        project_name = data_utils.rand_name('quota_tenant')
-        description = data_utils.rand_name('desc_')
-        project = identity.identity_utils(self.os_admin).create_project(
-            project_name, description=description)
-        project_id = project['id']
-        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
-                        project_id)
-        quota_set_default = self.admin_quotas_client.show_default_quota_set(
-            project_id)['quota_set']
-        volume_default = quota_set_default['volumes']
-
-        self.admin_quotas_client.update_quota_set(
-            project_id, volumes=(volume_default + 5))
-
-        self.admin_quotas_client.delete_quota_set(project_id)
-        quota_set_new = (self.admin_quotas_client.show_quota_set(project_id)
-                         ['quota_set'])
-        self.assertEqual(volume_default, quota_set_new['volumes'])
-
     @decorators.idempotent_id('8911036f-9d54-4720-80cc-a1c9796a8805')
     def test_quota_usage_after_volume_transfer(self):
         # Create a volume for transfer
@@ -150,7 +150,8 @@
             self.demo_tenant_id, params={'usage': True})['quota_set']
 
         alt_quota = self.admin_quotas_client.show_quota_set(
-            self.alt_client.tenant_id, params={'usage': True})['quota_set']
+            self.os_alt.volumes_client_latest.tenant_id,
+            params={'usage': True})['quota_set']
 
         # Creates a volume transfer
         transfer = self.transfer_client.create_volume_transfer(
@@ -164,14 +165,15 @@
 
         # Verify volume transferred is available
         waiters.wait_for_volume_resource_status(
-            self.alt_client, volume['id'], 'available')
+            self.os_alt.volumes_client_latest, volume['id'], 'available')
 
         # List of tenants quota usage post transfer
         new_primary_quota = self.admin_quotas_client.show_quota_set(
             self.demo_tenant_id, params={'usage': True})['quota_set']
 
         new_alt_quota = self.admin_quotas_client.show_quota_set(
-            self.alt_client.tenant_id, params={'usage': True})['quota_set']
+            self.os_alt.volumes_client_latest.tenant_id,
+            params={'usage': True})['quota_set']
 
         # Verify tenants quota usage was updated
         self.assertEqual(primary_quota['volumes']['in_use'] -
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index d127b5f..5c7ab15 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -19,27 +19,31 @@
 from tempest.lib import exceptions as lib_exc
 
 CONF = config.CONF
+QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups',
+              'backup_gigabytes', 'per_volume_gigabytes']
 
 
-class BaseVolumeQuotasNegativeTestJSON(base.BaseVolumeAdminTest):
-    force_tenant_isolation = True
+class VolumeQuotasNegativeTestJSON(base.BaseVolumeAdminTest):
 
     @classmethod
     def setup_credentials(cls):
-        super(BaseVolumeQuotasNegativeTestJSON, cls).setup_credentials()
+        super(VolumeQuotasNegativeTestJSON, cls).setup_credentials()
         cls.demo_tenant_id = cls.os_primary.credentials.tenant_id
 
     @classmethod
     def resource_setup(cls):
-        super(BaseVolumeQuotasNegativeTestJSON, cls).resource_setup()
-        cls.shared_quota_set = {'gigabytes': 2 * CONF.volume.volume_size,
-                                'volumes': 1}
+        super(VolumeQuotasNegativeTestJSON, cls).resource_setup()
 
-        # NOTE(gfidente): no need to restore original quota set
-        # after the tests as they only work with dynamic credentials.
-        cls.admin_quotas_client.update_quota_set(
-            cls.demo_tenant_id,
-            **cls.shared_quota_set)
+        # Save the current set of quotas, then set up the cleanup method
+        # to restore the quotas to their original values after the tests
+        # from this class are done. This is needed just in case Tempest is
+        # configured to use pre-provisioned projects/user accounts.
+        original_quota_set = (cls.admin_quotas_client.show_quota_set(
+            cls.demo_tenant_id)['quota_set'])
+        cleanup_quota_set = dict(
+            (k, v) for k, v in original_quota_set.items() if k in QUOTA_KEYS)
+        cls.addClassResourceCleanup(cls.admin_quotas_client.update_quota_set,
+                                    cls.demo_tenant_id, **cleanup_quota_set)
 
         # NOTE(gfidente): no need to delete in tearDown as
         # they are created using utility wrapper methods.
@@ -48,6 +52,8 @@
     @decorators.attr(type='negative')
     @decorators.idempotent_id('bf544854-d62a-47f2-a681-90f7a47d86b6')
     def test_quota_volumes(self):
+        self.admin_quotas_client.update_quota_set(self.demo_tenant_id,
+                                                  volumes=1, gigabytes=-1)
         self.assertRaises(lib_exc.OverLimit,
                           self.volumes_client.create_volume,
                           size=CONF.volume.volume_size)
@@ -55,17 +61,18 @@
     @decorators.attr(type='negative')
     @decorators.idempotent_id('2dc27eee-8659-4298-b900-169d71a91374')
     def test_quota_volume_gigabytes(self):
-        # NOTE(gfidente): quota set needs to be changed for this test
-        # or we may be limited by the volumes or snaps quota number, not by
-        # actual gigs usage; next line ensures shared set is restored.
-        self.addCleanup(self.admin_quotas_client.update_quota_set,
-                        self.demo_tenant_id,
-                        **self.shared_quota_set)
-        new_quota_set = {'gigabytes': CONF.volume.volume_size,
-                         'volumes': 2, 'snapshots': 1}
         self.admin_quotas_client.update_quota_set(
-            self.demo_tenant_id,
-            **new_quota_set)
+            self.demo_tenant_id, gigabytes=CONF.volume.volume_size, volumes=-1)
         self.assertRaises(lib_exc.OverLimit,
                           self.volumes_client.create_volume,
-                          size=CONF.volume.volume_size)
+                          size=CONF.volume.volume_size * 2)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('d321dc21-d8c6-401f-95fe-49f4845f1a6d')
+    def test_volume_extend_gigabytes_quota_deviation(self):
+        self.admin_quotas_client.update_quota_set(
+            self.demo_tenant_id, gigabytes=CONF.volume.volume_size)
+        self.assertRaises(lib_exc.OverLimit,
+                          self.volumes_client.extend_volume,
+                          self.volume['id'],
+                          new_size=CONF.volume.volume_size * 2)
diff --git a/tempest/api/volume/admin/test_volume_retype.py b/tempest/api/volume/admin/test_volume_retype.py
new file mode 100644
index 0000000..9136139
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -0,0 +1,180 @@
+#    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 abc
+
+from oslo_log import log as logging
+
+from tempest.api.volume import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+class VolumeRetypeTest(base.BaseVolumeAdminTest):
+
+    def _wait_for_internal_volume_cleanup(self, vol):
+        # When retyping a volume, Cinder creates an internal volume in the
+        # target backend. The volume in the source backend is deleted after
+        # the migration, so we need to wait for Cinder delete this volume
+        # before deleting the types we've created.
+
+        # This list should return 2 volumes until the copy and cleanup
+        # process is finished.
+        fetched_list = self.admin_volume_client.list_volumes(
+            params={'all_tenants': True,
+                    'name': vol['name']})['volumes']
+
+        for fetched_vol in fetched_list:
+            if fetched_vol['id'] != vol['id']:
+                # This is the Cinder internal volume
+                LOG.debug('Waiting for internal volume %s deletion',
+                          fetched_vol['id'])
+                self.admin_volume_client.wait_for_resource_deletion(
+                    fetched_vol['id'])
+                break
+
+    @abc.abstractmethod
+    def _verify_migration(self, source_vol, dest_vol):
+        pass
+
+    def _create_volume_from_snapshot(self):
+        # Create a volume in the first backend
+        src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
+
+        # Create a volume snapshot
+        snapshot = self.create_snapshot(src_vol['id'])
+
+        # Create a volume from the snapshot
+        src_vol = self.create_volume(volume_type=self.src_vol_type['name'],
+                                     snapshot_id=snapshot['id'])
+
+        # Delete the snapshot
+        self.snapshots_client.delete_snapshot(snapshot['id'])
+        self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
+
+        return src_vol
+
+    def _retype_volume(self, volume, migration_policy):
+
+        volume_source = self.admin_volume_client.show_volume(
+            volume['id'])['volume']
+
+        self.volumes_client.retype_volume(
+            volume['id'],
+            new_type=self.dst_vol_type['name'],
+            migration_policy=migration_policy)
+        self.addCleanup(self._wait_for_internal_volume_cleanup, volume)
+        waiters.wait_for_volume_retype(self.volumes_client, volume['id'],
+                                       self.dst_vol_type['name'])
+
+        volume_dest = self.admin_volume_client.show_volume(
+            volume['id'])['volume']
+
+        self._verify_migration(volume_source, volume_dest)
+
+
+class VolumeRetypeWithMigrationTest(VolumeRetypeTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(VolumeRetypeTest, cls).skip_checks()
+
+        if not CONF.volume_feature_enabled.multi_backend:
+            raise cls.skipException("Cinder multi-backend feature disabled.")
+
+        if len(set(CONF.volume.backend_names)) < 2:
+            raise cls.skipException("Requires at least two different "
+                                    "backend names")
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumeRetypeWithMigrationTest, cls).resource_setup()
+        # read backend name from a list.
+        backend_src = CONF.volume.backend_names[0]
+        backend_dst = CONF.volume.backend_names[1]
+
+        extra_specs_src = {"volume_backend_name": backend_src}
+        extra_specs_dst = {"volume_backend_name": backend_dst}
+
+        cls.src_vol_type = cls.create_volume_type(extra_specs=extra_specs_src)
+        cls.dst_vol_type = cls.create_volume_type(extra_specs=extra_specs_dst)
+
+    def _verify_migration(self, volume_source, volume_dest):
+
+        keys_with_no_change = ('id', 'size', 'description', 'name',
+                               'user_id', 'os-vol-tenant-attr:tenant_id')
+        keys_with_change = ('volume_type', 'os-vol-host-attr:host')
+
+        # Check the volume information after the migration.
+        self.assertEqual('success',
+                         volume_dest['os-vol-mig-status-attr:migstat'])
+        self.assertEqual('success', volume_dest['migration_status'])
+
+        for key in keys_with_no_change:
+            self.assertEqual(volume_source[key], volume_dest[key])
+
+        for key in keys_with_change:
+            self.assertNotEqual(volume_source[key], volume_dest[key])
+
+        self.assertEqual(volume_dest['volume_type'], self.dst_vol_type['name'])
+
+    @decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd')
+    def test_available_volume_retype_with_migration(self):
+        src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
+        self._retype_volume(src_vol, migration_policy='on-demand')
+
+    @decorators.idempotent_id('d0d9554f-e7a5-4104-8973-f35b27ccb60d')
+    def test_volume_from_snapshot_retype_with_migration(self):
+        src_vol = self._create_volume_from_snapshot()
+
+        # Migrate the volume from snapshot to the second backend
+        self._retype_volume(src_vol, migration_policy='on-demand')
+
+
+class VolumeRetypeWithoutMigrationTest(VolumeRetypeTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumeRetypeWithoutMigrationTest, cls).resource_setup()
+        cls.src_vol_type = cls.create_volume_type('volume-type-1')
+        cls.dst_vol_type = cls.create_volume_type('volume-type-2')
+
+    def _verify_migration(self, volume_source, volume_dest):
+
+        keys_with_no_change = ('id', 'size', 'description', 'name',
+                               'user_id', 'os-vol-tenant-attr:tenant_id',
+                               'os-vol-host-attr:host')
+        keys_with_change = ('volume_type',)
+
+        # Check the volume information after the retype
+        self.assertIsNone(volume_dest['os-vol-mig-status-attr:migstat'])
+        self.assertIsNone(volume_dest['migration_status'])
+
+        for key in keys_with_no_change:
+            self.assertEqual(volume_source[key], volume_dest[key])
+
+        for key in keys_with_change:
+            self.assertNotEqual(volume_source[key], volume_dest[key])
+
+        self.assertEqual(volume_dest['volume_type'], self.dst_vol_type['name'])
+
+    @decorators.idempotent_id('b90412ee-465d-46e9-b249-ec84a47d5f25')
+    def test_available_volume_retype(self):
+        src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
+
+        # Retype the volume from snapshot
+        self._retype_volume(src_vol, migration_policy='never')
diff --git a/tempest/api/volume/admin/test_volume_retype_with_migration.py b/tempest/api/volume/admin/test_volume_retype_with_migration.py
deleted file mode 100644
index f0b3a4f..0000000
--- a/tempest/api/volume/admin/test_volume_retype_with_migration.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#    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_log import log as logging
-
-from tempest.api.volume import base
-from tempest.common import waiters
-from tempest import config
-from tempest.lib import decorators
-
-CONF = config.CONF
-
-LOG = logging.getLogger(__name__)
-
-
-class VolumeRetypeWithMigrationTest(base.BaseVolumeAdminTest):
-
-    @classmethod
-    def skip_checks(cls):
-        super(VolumeRetypeWithMigrationTest, cls).skip_checks()
-
-        if not CONF.volume_feature_enabled.multi_backend:
-            raise cls.skipException("Cinder multi-backend feature disabled.")
-
-        if len(set(CONF.volume.backend_names)) < 2:
-            raise cls.skipException("Requires at least two different "
-                                    "backend names")
-
-    @classmethod
-    def resource_setup(cls):
-        super(VolumeRetypeWithMigrationTest, cls).resource_setup()
-        # read backend name from a list.
-        backend_src = CONF.volume.backend_names[0]
-        backend_dst = CONF.volume.backend_names[1]
-
-        extra_specs_src = {"volume_backend_name": backend_src}
-        extra_specs_dst = {"volume_backend_name": backend_dst}
-
-        src_vol_type = cls.create_volume_type(extra_specs=extra_specs_src)
-        cls.dst_vol_type = cls.create_volume_type(extra_specs=extra_specs_dst)
-
-        cls.src_vol = cls.create_volume(volume_type=src_vol_type['name'])
-
-    @classmethod
-    def resource_cleanup(cls):
-        # When retyping a volume, Cinder creates an internal volume in the
-        # target backend. The volume in the source backend is deleted after
-        # the migration, so we need to wait for Cinder delete this volume
-        # before deleting the types we've created.
-
-        # This list should return 2 volumes until the copy and cleanup
-        # process is finished.
-        fetched_list = cls.admin_volume_client.list_volumes(
-            params={'all_tenants': True,
-                    'display_name': cls.src_vol['name']})['volumes']
-
-        for fetched_vol in fetched_list:
-            if fetched_vol['id'] != cls.src_vol['id']:
-                # This is the Cinder internal volume
-                LOG.debug('Waiting for internal volume %s deletion',
-                          fetched_vol['id'])
-                cls.admin_volume_client.wait_for_resource_deletion(
-                    fetched_vol['id'])
-                break
-
-        super(VolumeRetypeWithMigrationTest, cls).resource_cleanup()
-
-    @decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd')
-    def test_available_volume_retype_with_migration(self):
-
-        keys_with_no_change = ('id', 'size', 'description', 'name', 'user_id',
-                               'os-vol-tenant-attr:tenant_id')
-        keys_with_change = ('volume_type', 'os-vol-host-attr:host')
-
-        volume_source = self.admin_volume_client.show_volume(
-            self.src_vol['id'])['volume']
-
-        self.volumes_client.retype_volume(
-            self.src_vol['id'],
-            new_type=self.dst_vol_type['name'],
-            migration_policy='on-demand')
-
-        waiters.wait_for_volume_retype(self.volumes_client, self.src_vol['id'],
-                                       self.dst_vol_type['name'])
-        volume_dest = self.admin_volume_client.show_volume(
-            self.src_vol['id'])['volume']
-
-        # Check the volume information after the migration.
-        self.assertEqual('success',
-                         volume_dest['os-vol-mig-status-attr:migstat'])
-        self.assertEqual('success', volume_dest['migration_status'])
-
-        for key in keys_with_no_change:
-            self.assertEqual(volume_source[key], volume_dest[key])
-
-        for key in keys_with_change:
-            self.assertNotEqual(volume_source[key], volume_dest[key])
diff --git a/tempest/api/volume/admin/test_volume_services_negative.py b/tempest/api/volume/admin/test_volume_services_negative.py
new file mode 100644
index 0000000..3a863a1
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_services_negative.py
@@ -0,0 +1,64 @@
+# Copyright 2018 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.volume import base
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class VolumeServicesNegativeTest(base.BaseVolumeAdminTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumeServicesNegativeTest, cls).resource_setup()
+        services = cls.admin_volume_services_client.list_services()['services']
+        cls.host = services[0]['host']
+        cls.binary = services[0]['binary']
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('3246ce65-ba70-4159-aa3b-082c28e4b484')
+    def test_enable_service_with_invalid_host(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.admin_volume_services_client.enable_service,
+                          host='invalid_host', binary=self.binary)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('c571f179-c6e6-4c50-a0ab-368b628a8ac1')
+    def test_disable_service_with_invalid_binary(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.admin_volume_services_client.disable_service,
+                          host=self.host, binary='invalid_binary')
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('77767b36-5e8f-4c68-a0b5-2308cc21ec64')
+    def test_disable_log_reason_with_no_reason(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.admin_volume_services_client.disable_log_reason,
+                          host=self.host, binary=self.binary,
+                          disabled_reason=None)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('712bfab8-1f44-4eb5-a632-fa70bf78f05e')
+    def test_freeze_host_with_invalid_host(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.admin_volume_services_client.freeze_host,
+                          host='invalid_host')
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('7c6287c9-d655-47e1-9a11-76f6657a6dce')
+    def test_thaw_host_with_invalid_host(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.admin_volume_services_client.thaw_host,
+                          host='invalid_host')
diff --git a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
index 0f4e90f..ff5e7e2 100644
--- a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
@@ -19,10 +19,11 @@
 from tempest.lib import exceptions as lib_exc
 
 CONF = config.CONF
+QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups',
+              'backup_gigabytes', 'per_volume_gigabytes']
 
 
 class VolumeSnapshotQuotasNegativeTestJSON(base.BaseVolumeAdminTest):
-    force_tenant_isolation = True
 
     @classmethod
     def skip_checks(cls):
@@ -38,12 +39,22 @@
     @classmethod
     def resource_setup(cls):
         super(VolumeSnapshotQuotasNegativeTestJSON, cls).resource_setup()
+
+        # Save the current set of quotas, then set up the cleanup method
+        # to restore the quotas to their original values after the tests
+        # from this class are done. This is needed just in case Tempest is
+        # configured to use pre-provisioned projects/user accounts.
+        original_quota_set = (cls.admin_quotas_client.show_quota_set(
+            cls.demo_tenant_id)['quota_set'])
+        cleanup_quota_set = dict(
+            (k, v) for k, v in original_quota_set.items() if k in QUOTA_KEYS)
+        cls.addClassResourceCleanup(cls.admin_quotas_client.update_quota_set,
+                                    cls.demo_tenant_id, **cleanup_quota_set)
+
         cls.default_volume_size = CONF.volume.volume_size
         cls.shared_quota_set = {'gigabytes': 3 * cls.default_volume_size,
                                 'volumes': 1, 'snapshots': 1}
 
-        # NOTE(gfidente): no need to restore original quota set
-        # after the tests as they only work with tenant isolation.
         cls.admin_quotas_client.update_quota_set(
             cls.demo_tenant_id,
             **cls.shared_quota_set)
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
index e93bcb5..b64face 100644
--- a/tempest/api/volume/admin/test_volume_type_access.py
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -27,11 +27,6 @@
 
     credentials = ['primary', 'alt', 'admin']
 
-    @classmethod
-    def setup_clients(cls):
-        super(VolumeTypesAccessTest, cls).setup_clients()
-        cls.alt_client = cls.os_alt.volumes_client_latest
-
     @decorators.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184')
     def test_volume_type_access_add(self):
         # Creating a NON public volume type
@@ -70,10 +65,11 @@
 
         # Adding volume type access for alt tenant
         self.admin_volume_types_client.add_type_access(
-            volume_type['id'], project=self.alt_client.tenant_id)
+            volume_type['id'],
+            project=self.os_alt.volumes_client_latest.tenant_id)
         self.addCleanup(self.admin_volume_types_client.remove_type_access,
                         volume_type['id'],
-                        project=self.alt_client.tenant_id)
+                        project=self.os_alt.volumes_client_latest.tenant_id)
 
         # List tenant access for the given volume type
         type_access_list = self.admin_volume_types_client.list_type_access(
@@ -88,5 +84,5 @@
         # Validating the permitted tenants are the expected tenants
         self.assertIn(self.volumes_client.tenant_id,
                       map(operator.itemgetter('project_id'), type_access_list))
-        self.assertIn(self.alt_client.tenant_id,
+        self.assertIn(self.os_alt.volumes_client_latest.tenant_id,
                       map(operator.itemgetter('project_id'), type_access_list))
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index af1024c..9e24176 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -161,6 +161,12 @@
                              'The fetched encryption_type %s is different '
                              'from the updated encryption_type' % key)
 
+        # Get encryption specs item
+        key = 'cipher'
+        item = self.admin_encryption_types_client.show_encryption_specs_item(
+            encrypt_type_id, key)
+        self.assertEqual(update_kwargs[key], item[key])
+
         # Delete encryption type
         self.admin_encryption_types_client.delete_encryption_type(
             encrypt_type_id)
@@ -187,8 +193,13 @@
                   'is_public': is_public}
         updated_vol_type = self.admin_volume_types_client.update_volume_type(
             volume_type['id'], **kwargs)['volume_type']
-
-        # Verify volume type details were updated
         self.assertEqual(name, updated_vol_type['name'])
         self.assertEqual(description, updated_vol_type['description'])
         self.assertEqual(is_public, updated_vol_type['is_public'])
+
+        # Verify volume type details were updated
+        fetched_volume_type = self.admin_volume_types_client.show_volume_type(
+            volume_type['id'])['volume_type']
+        self.assertEqual(name, fetched_volume_type['name'])
+        self.assertEqual(description, fetched_volume_type['description'])
+        self.assertEqual(is_public, fetched_volume_type['is_public'])
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 375aacb..45060d0 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -59,9 +59,9 @@
         volume = self.create_volume()
         # Create backup
         backup_name = data_utils.rand_name(self.__class__.__name__ + '-Backup')
-        backup = (self.create_backup(backup_client=self.admin_backups_client,
-                                     volume_id=volume['id'],
-                                     name=backup_name))
+        backup = self.create_backup(volume_id=volume['id'], name=backup_name)
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
         self.assertEqual(backup_name, backup['name'])
 
         # Export Backup
@@ -103,21 +103,22 @@
         self.assertIn(new_id, [b['id'] for b in backups])
 
         # Restore backup
-        restore = self.admin_backups_client.restore_backup(
-            backup['id'])['restore']
-        self.addCleanup(self.admin_volume_client.delete_volume,
+        restore = self.backups_client.restore_backup(backup['id'])['restore']
+        self.addCleanup(self.volumes_client.delete_volume,
                         restore['volume_id'])
         self.assertEqual(backup['id'], restore['backup_id'])
-        waiters.wait_for_volume_resource_status(self.admin_volume_client,
-                                                restore['volume_id'],
-                                                'available')
+
+        # When restore operation is performed then, backup['id']
+        # goes to 'restoring' state so we need to wait for
+        # backup['id'] to become 'available'.
+        waiters.wait_for_volume_resource_status(
+            self.backups_client, backup['id'], 'available')
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, restore['volume_id'], 'available')
 
         # Verify if restored volume is there in volume list
-        volumes = self.admin_volume_client.list_volumes()['volumes']
+        volumes = self.volumes_client.list_volumes()['volumes']
         self.assertIn(restore['volume_id'], [v['id'] for v in volumes])
-        waiters.wait_for_volume_resource_status(self.admin_backups_client,
-                                                import_backup['id'],
-                                                'available')
 
     @decorators.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
     def test_volume_backup_reset_status(self):
@@ -126,12 +127,12 @@
         # Create a backup
         backup_name = data_utils.rand_name(
             self.__class__.__name__ + '-Backup')
-        backup = self.create_backup(backup_client=self.admin_backups_client,
-                                    volume_id=volume['id'],
-                                    name=backup_name)
+        backup = self.create_backup(volume_id=volume['id'], name=backup_name)
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
         self.assertEqual(backup_name, backup['name'])
         # Reset backup status to error
         self.admin_backups_client.reset_backup_status(backup_id=backup['id'],
                                                       status="error")
-        waiters.wait_for_volume_resource_status(self.admin_backups_client,
+        waiters.wait_for_volume_resource_status(self.backups_client,
                                                 backup['id'], 'error')
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 63ef85b..64fe29a 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -31,6 +31,11 @@
     """Base test case class for all Cinder API tests."""
 
     _api_version = 2
+    # if api_v2 is not enabled while api_v3 is enabled, the volume v2 classes
+    # should be transferred to volume v3 classes.
+    if (not CONF.volume_feature_enabled.api_v2 and
+        CONF.volume_feature_enabled.api_v3):
+        _api_version = 3
     credentials = ['primary']
 
     @classmethod
@@ -69,24 +74,19 @@
         if CONF.service_available.glance:
             cls.images_client = cls.os_primary.image_client_v2
 
-        if cls._api_version == 3:
-            cls.backups_client = cls.os_primary.backups_v3_client
-            cls.volumes_client = cls.os_primary.volumes_v3_client
-            cls.messages_client = cls.os_primary.volume_v3_messages_client
-            cls.versions_client = cls.os_primary.volume_v3_versions_client
-            cls.groups_client = cls.os_primary.groups_v3_client
-            cls.group_snapshots_client = (
-                cls.os_primary.group_snapshots_v3_client)
-        else:
-            cls.backups_client = cls.os_primary.backups_v2_client
-            cls.volumes_client = cls.os_primary.volumes_v2_client
-
-        cls.snapshots_client = cls.os_primary.snapshots_v2_client
+        cls.backups_client = cls.os_primary.backups_client_latest
+        cls.volumes_client = cls.os_primary.volumes_client_latest
+        cls.messages_client = cls.os_primary.volume_messages_client_latest
+        cls.versions_client = cls.os_primary.volume_versions_client_latest
+        cls.groups_client = cls.os_primary.groups_client_latest
+        cls.group_snapshots_client = (
+            cls.os_primary.group_snapshots_client_latest)
+        cls.snapshots_client = cls.os_primary.snapshots_client_latest
         cls.volumes_extension_client =\
-            cls.os_primary.volumes_v2_extension_client
+            cls.os_primary.volumes_extension_client_latest
         cls.availability_zone_client = (
-            cls.os_primary.volume_v2_availability_zone_client)
-        cls.volume_limits_client = cls.os_primary.volume_v2_limits_client
+            cls.os_primary.volume_availability_zone_client_latest)
+        cls.volume_limits_client = cls.os_primary.volume_limits_client_latest
 
     def setUp(self):
         super(BaseVolumeTest, self).setUp()
@@ -101,20 +101,12 @@
                 cls.min_microversion,
                 CONF.volume.min_microversion))
 
-        cls.snapshots = []
-        cls.volumes = []
         cls.image_ref = CONF.compute.image_ref
         cls.flavor_ref = CONF.compute.flavor_ref
         cls.build_interval = CONF.volume.build_interval
         cls.build_timeout = CONF.volume.build_timeout
 
     @classmethod
-    def resource_cleanup(cls):
-        cls.clear_snapshots()
-        cls.clear_volumes()
-        super(BaseVolumeTest, cls).resource_cleanup()
-
-    @classmethod
     def create_volume(cls, wait_until='available', **kwargs):
         """Wrapper utility that returns a test volume.
 
@@ -133,7 +125,9 @@
             kwargs['name'] = name
 
         volume = cls.volumes_client.create_volume(**kwargs)['volume']
-        cls.volumes.append(volume)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.delete_volume, cls.volumes_client,
+                                    volume['id'])
         waiters.wait_for_volume_resource_status(cls.volumes_client,
                                                 volume['id'], wait_until)
         return volume
@@ -147,7 +141,8 @@
 
         snapshot = cls.snapshots_client.create_snapshot(
             volume_id=volume_id, **kwargs)['snapshot']
-        cls.snapshots.append(snapshot['id'])
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.delete_snapshot, snapshot['id'])
         waiters.wait_for_volume_resource_status(cls.snapshots_client,
                                                 snapshot['id'], 'available')
         return snapshot
@@ -176,14 +171,13 @@
         client.delete_volume(volume_id)
         client.wait_for_resource_deletion(volume_id)
 
-    def delete_snapshot(self, snapshot_id, snapshots_client=None):
+    @classmethod
+    def delete_snapshot(cls, snapshot_id, snapshots_client=None):
         """Delete snapshot by the given client"""
         if snapshots_client is None:
-            snapshots_client = self.snapshots_client
+            snapshots_client = cls.snapshots_client
         snapshots_client.delete_snapshot(snapshot_id)
         snapshots_client.wait_for_resource_deletion(snapshot_id)
-        if snapshot_id in self.snapshots:
-            self.snapshots.remove(snapshot_id)
 
     def attach_volume(self, server_id, volume_id):
         """Attach a volume to a server"""
@@ -197,31 +191,6 @@
         self.addCleanup(self.servers_client.detach_volume, server_id,
                         volume_id)
 
-    @classmethod
-    def clear_volumes(cls):
-        for volume in cls.volumes:
-            try:
-                cls.volumes_client.delete_volume(volume['id'])
-            except Exception:
-                pass
-
-        for volume in cls.volumes:
-            try:
-                cls.volumes_client.wait_for_resource_deletion(volume['id'])
-            except Exception:
-                pass
-
-    @classmethod
-    def clear_snapshots(cls):
-        for snapshot in cls.snapshots:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.snapshots_client.delete_snapshot, snapshot)
-
-        for snapshot in cls.snapshots:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.snapshots_client.wait_for_resource_deletion,
-                snapshot)
-
     def create_server(self, wait_until='ACTIVE', **kwargs):
         name = kwargs.pop(
             'name',
@@ -242,6 +211,27 @@
                         self.servers_client.delete_server, body['id'])
         return body
 
+    def create_group(self, **kwargs):
+        if 'name' not in kwargs:
+            kwargs['name'] = data_utils.rand_name(
+                self.__class__.__name__ + '-Group')
+
+        group = self.groups_client.create_group(**kwargs)['group']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.delete_group, group['id'])
+        waiters.wait_for_volume_resource_status(
+            self.groups_client, group['id'], 'available')
+        return group
+
+    def delete_group(self, group_id, delete_volumes=True):
+        self.groups_client.delete_group(group_id, delete_volumes)
+        if delete_volumes:
+            vols = self.volumes_client.list_volumes(detail=True)['volumes']
+            for vol in vols:
+                if vol['group_id'] == group_id:
+                    self.volumes_client.wait_for_resource_deletion(vol['id'])
+        self.groups_client.wait_for_resource_deletion(group_id)
+
 
 class BaseVolumeAdminTest(BaseVolumeTest):
     """Base test case class for all Volume Admin API tests."""
@@ -252,47 +242,34 @@
     def setup_clients(cls):
         super(BaseVolumeAdminTest, cls).setup_clients()
 
-        cls.admin_volume_qos_client = cls.os_admin.volume_qos_v2_client
+        cls.admin_volume_qos_client = cls.os_admin.volume_qos_client_latest
         cls.admin_volume_services_client = \
-            cls.os_admin.volume_services_v2_client
-        cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client
-        cls.admin_volume_manage_client = cls.os_admin.volume_manage_v2_client
-        cls.admin_volume_client = cls.os_admin.volumes_v2_client
-        if cls._api_version == 3:
-            cls.admin_volume_client = cls.os_admin.volumes_v3_client
-            cls.admin_groups_client = cls.os_admin.groups_v3_client
-            cls.admin_messages_client = cls.os_admin.volume_v3_messages_client
-            cls.admin_group_snapshots_client = \
-                cls.os_admin.group_snapshots_v3_client
-            cls.admin_group_types_client = cls.os_admin.group_types_v3_client
-        cls.admin_hosts_client = cls.os_admin.volume_hosts_v2_client
+            cls.os_admin.volume_services_client_latest
+        cls.admin_volume_types_client = cls.os_admin.volume_types_client_latest
+        cls.admin_volume_manage_client = (
+            cls.os_admin.volume_manage_client_latest)
+        cls.admin_volume_client = cls.os_admin.volumes_client_latest
+        cls.admin_groups_client = cls.os_admin.groups_client_latest
+        cls.admin_messages_client = cls.os_admin.volume_messages_client_latest
+        cls.admin_group_snapshots_client = \
+            cls.os_admin.group_snapshots_client_latest
+        cls.admin_group_types_client = cls.os_admin.group_types_client_latest
+        cls.admin_hosts_client = cls.os_admin.volume_hosts_client_latest
         cls.admin_snapshot_manage_client = \
-            cls.os_admin.snapshot_manage_v2_client
-        cls.admin_snapshots_client = cls.os_admin.snapshots_v2_client
-        cls.admin_backups_client = cls.os_admin.backups_v2_client
+            cls.os_admin.snapshot_manage_client_latest
+        cls.admin_snapshots_client = cls.os_admin.snapshots_client_latest
+        cls.admin_backups_client = cls.os_admin.backups_client_latest
         cls.admin_encryption_types_client = \
-            cls.os_admin.encryption_types_v2_client
+            cls.os_admin.encryption_types_client_latest
         cls.admin_quota_classes_client = \
-            cls.os_admin.volume_quota_classes_v2_client
-        cls.admin_quotas_client = cls.os_admin.volume_quotas_v2_client
-        cls.admin_volume_limits_client = cls.os_admin.volume_v2_limits_client
+            cls.os_admin.volume_quota_classes_client_latest
+        cls.admin_quotas_client = cls.os_admin.volume_quotas_client_latest
+        cls.admin_volume_limits_client = (
+            cls.os_admin.volume_limits_client_latest)
         cls.admin_capabilities_client = \
-            cls.os_admin.volume_capabilities_v2_client
+            cls.os_admin.volume_capabilities_client_latest
         cls.admin_scheduler_stats_client = \
-            cls.os_admin.volume_scheduler_stats_v2_client
-
-    @classmethod
-    def resource_setup(cls):
-        super(BaseVolumeAdminTest, cls).resource_setup()
-
-        cls.qos_specs = []
-        cls.volume_types = []
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.clear_qos_specs()
-        super(BaseVolumeAdminTest, cls).resource_cleanup()
-        cls.clear_volume_types()
+            cls.os_admin.volume_scheduler_stats_client_latest
 
     @classmethod
     def create_test_qos_specs(cls, name=None, consumer=None, **kwargs):
@@ -301,7 +278,7 @@
         consumer = consumer or 'front-end'
         qos_specs = cls.admin_volume_qos_client.create_qos(
             name=name, consumer=consumer, **kwargs)['qos_specs']
-        cls.qos_specs.append(qos_specs['id'])
+        cls.addClassResourceCleanup(cls.clear_qos_spec, qos_specs['id'])
         return qos_specs
 
     @classmethod
@@ -310,7 +287,7 @@
         name = name or data_utils.rand_name(cls.__name__ + '-volume-type')
         volume_type = cls.admin_volume_types_client.create_volume_type(
             name=name, **kwargs)['volume_type']
-        cls.volume_types.append(volume_type['id'])
+        cls.addClassResourceCleanup(cls.clear_volume_type, volume_type['id'])
         return volume_type
 
     def create_group_type(self, name=None, **kwargs):
@@ -324,22 +301,18 @@
         return group_type
 
     @classmethod
-    def clear_qos_specs(cls):
-        for qos_id in cls.qos_specs:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.admin_volume_qos_client.delete_qos, qos_id)
+    def clear_qos_spec(cls, qos_id):
+        test_utils.call_and_ignore_notfound_exc(
+            cls.admin_volume_qos_client.delete_qos, qos_id)
 
-        for qos_id in cls.qos_specs:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
+        test_utils.call_and_ignore_notfound_exc(
+            cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
 
     @classmethod
-    def clear_volume_types(cls):
-        for vol_type in cls.volume_types:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.admin_volume_types_client.delete_volume_type, vol_type)
+    def clear_volume_type(cls, vol_type_id):
+        test_utils.call_and_ignore_notfound_exc(
+            cls.admin_volume_types_client.delete_volume_type, vol_type_id)
 
-        for vol_type in cls.volume_types:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.admin_volume_types_client.wait_for_resource_deletion,
-                vol_type)
+        test_utils.call_and_ignore_notfound_exc(
+            cls.admin_volume_types_client.wait_for_resource_deletion,
+            vol_type_id)
diff --git a/tempest/api/volume/test_volume_absolute_limits.py b/tempest/api/volume/test_volume_absolute_limits.py
index 4018468..00a3375 100644
--- a/tempest/api/volume/test_volume_absolute_limits.py
+++ b/tempest/api/volume/test_volume_absolute_limits.py
@@ -17,7 +17,6 @@
 from tempest import config
 from tempest.lib import decorators
 
-
 CONF = config.CONF
 
 
@@ -32,9 +31,16 @@
     @classmethod
     def resource_setup(cls):
         super(AbsoluteLimitsTests, cls).resource_setup()
+
         # Create a shared volume for tests
         cls.volume = cls.create_volume()
 
+    @classmethod
+    def skip_checks(cls):
+        super(AbsoluteLimitsTests, cls).skip_checks()
+        if not CONF.auth.use_dynamic_credentials:
+            raise cls.skipException("Must use dynamic credentials.")
+
     @decorators.idempotent_id('8e943f53-e9d6-4272-b2e9-adcf2f7c29ad')
     def test_get_volume_absolute_limits(self):
         # get volume limit for a tenant
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 4108da5..c85e0bc 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -15,6 +15,7 @@
 
 from tempest.api.volume import base
 from tempest.common import waiters
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 
@@ -26,10 +27,10 @@
     def setup_clients(cls):
         super(VolumesTransfersTest, cls).setup_clients()
 
-        cls.client = cls.os_primary.volume_transfers_v2_client
-        cls.alt_client = cls.os_alt.volume_transfers_v2_client
-        cls.alt_volumes_client = cls.os_alt.volumes_v2_client
-        cls.adm_volumes_client = cls.os_admin.volumes_v2_client
+        cls.client = cls.os_primary.volume_transfers_client_latest
+        cls.alt_client = cls.os_alt.volume_transfers_client_latest
+        cls.alt_volumes_client = cls.os_alt.volumes_client_latest
+        cls.adm_volumes_client = cls.os_admin.volumes_client_latest
 
     @decorators.idempotent_id('4d75b645-a478-48b1-97c8-503f64242f1a')
     def test_create_get_list_accept_volume_transfer(self):
@@ -43,6 +44,9 @@
         transfer = self.client.create_volume_transfer(
             volume_id=volume['id'])['transfer']
         transfer_id = transfer['id']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.client.delete_volume_transfer,
+                        transfer_id)
         auth_key = transfer['auth_key']
         waiters.wait_for_volume_resource_status(
             self.volumes_client, volume['id'], 'awaiting-transfer')
@@ -81,6 +85,9 @@
         # Create a volume transfer
         transfer_id = self.client.create_volume_transfer(
             volume_id=volume['id'])['transfer']['id']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.client.delete_volume_transfer,
+                        transfer_id)
         waiters.wait_for_volume_resource_status(
             self.volumes_client, volume['id'], 'awaiting-transfer')
 
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 1e240b8..6ce5d3e 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -40,7 +40,7 @@
             backup_id)['restore']
 
         # Delete backup
-        self.addCleanup(self.volumes_client.delete_volume,
+        self.addCleanup(self.delete_volume, self.volumes_client,
                         restored_volume['volume_id'])
         self.assertEqual(backup_id, restored_volume['backup_id'])
         waiters.wait_for_volume_resource_status(self.backups_client,
@@ -50,6 +50,7 @@
                                                 'available')
         return restored_volume
 
+    @decorators.skip_because(bug="1483434")
     @testtools.skipIf(CONF.volume.storage_protocol == 'ceph',
                       'ceph does not support arbitrary container names')
     @decorators.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
@@ -59,8 +60,7 @@
                     "vol-meta2": "value2",
                     "vol-meta3": "value3"}
         volume = self.create_volume(metadata=metadata)
-        self.addCleanup(self.volumes_client.delete_volume,
-                        volume['id'])
+        self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
 
         # Create a backup
         backup_name = data_utils.rand_name(
@@ -109,8 +109,7 @@
         """
         # Create a server
         volume = self.create_volume()
-        self.addCleanup(self.volumes_client.delete_volume,
-                        volume['id'])
+        self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
         server = self.create_server()
         # Attach volume to instance
         self.attach_volume(server['id'], volume['id'])
@@ -119,6 +118,8 @@
             self.__class__.__name__ + '-Backup')
         backup = self.create_backup(volume_id=volume['id'],
                                     name=backup_name, force=True)
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'in-use')
         self.assertEqual(backup_name, backup['name'])
 
     @decorators.idempotent_id('2a8ba340-dff2-4511-9db7-646f07156b15')
@@ -130,10 +131,12 @@
 
         volume_details = self.volumes_client.show_volume(
             volume['id'])['volume']
-        self.assertEqual('true', volume_details['bootable'])
+        self.assertTrue(volume_details['bootable'])
 
         # Create a backup
         backup = self.create_backup(volume_id=volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
 
         # Restore the backup
         restored_volume_id = self.restore_backup(backup['id'])['volume_id']
@@ -142,7 +145,7 @@
         restored_volume_info = self.volumes_client.show_volume(
             restored_volume_id)['volume']
 
-        self.assertEqual('true', restored_volume_info['bootable'])
+        self.assertTrue(restored_volume_info['bootable'])
 
 
 class VolumesBackupsV39Test(base.BaseVolumeTest):
@@ -162,6 +165,8 @@
         # Create volume and backup
         volume = self.create_volume()
         backup = self.create_backup(volume_id=volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
 
         # Update backup and assert response body for update_backup method
         update_kwargs = {
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index de28a30..ac9a9c7 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -18,6 +18,7 @@
 import testtools
 
 from tempest.api.volume import base
+from tempest.common import utils
 from tempest.common import waiters
 from tempest import config
 from tempest.lib import decorators
@@ -31,8 +32,8 @@
     @decorators.idempotent_id('9a36df71-a257-43a5-9555-dc7c88e66e0e')
     def test_volume_extend(self):
         # Extend Volume Test.
-        volume = self.create_volume()
-        extend_size = volume['size'] + 1
+        volume = self.create_volume(image_ref=self.image_ref)
+        extend_size = volume['size'] * 2
         self.volumes_client.extend_volume(volume['id'],
                                           new_size=extend_size)
         waiters.wait_for_volume_resource_status(self.volumes_client,
@@ -43,12 +44,11 @@
     @decorators.idempotent_id('86be1cba-2640-11e5-9c82-635fb964c912')
     @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
                           "Cinder volume snapshots are disabled")
-    @decorators.skip_because(bug='1687044')
     def test_volume_extend_when_volume_has_snapshot(self):
         volume = self.create_volume()
         self.create_snapshot(volume['id'])
 
-        extend_size = volume['size'] + 1
+        extend_size = volume['size'] * 2
         self.volumes_client.extend_volume(volume['id'], new_size=extend_size)
 
         waiters.wait_for_volume_resource_status(self.volumes_client,
@@ -79,11 +79,6 @@
     # is implicit - Cinder calls Nova at that microversion, Tempest does not.
     min_microversion = '3.42'
 
-    @classmethod
-    def setup_clients(cls):
-        super(VolumesExtendAttachedTest, cls).setup_clients()
-        cls.admin_servers_client = cls.os_admin.servers_client
-
     def _find_extend_volume_instance_action(self, server_id):
         actions = self.servers_client.list_instance_actions(
             server_id)['instanceActions']
@@ -94,7 +89,7 @@
     def _find_extend_volume_instance_action_finish_event(self, action):
         # This has to be called by an admin client otherwise
         # the events don't show up.
-        action = self.admin_servers_client.show_instance_action(
+        action = self.os_admin.servers_client.show_instance_action(
             action['instance_uuid'], action['request_id'])['instanceAction']
         for event in action['events']:
             if (event['event'] == 'compute_extend_volume' and
@@ -104,6 +99,7 @@
     @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):
         """This is a happy path test which does the following:
 
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index b5f98ea..d5358ab 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -26,15 +26,28 @@
 
 
 class VolumesListTestJSON(base.BaseVolumeTest):
-    # NOTE: This test creates a number of 1G volumes. To run successfully,
-    # ensure that the backing file for the volume group that Nova uses
+    # NOTE: This test creates a number of 1G volumes. To run it successfully,
+    # ensure that the backing file for the volume group that Cinder uses
     # has space for at least 3 1G volumes!
     # If you are running a Devstack environment, ensure that the
     # VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
 
     VOLUME_FIELDS = ('id', 'name')
 
-    def assertVolumesIn(self, fetched_list, expected_list, fields=None):
+    @classmethod
+    def _remove_volatile_fields(cls, fetched_list):
+        """Remove fields that should not be compared.
+
+        This method makes sure that Tempest does not compare e.g.
+        the volume's "updated_at" field that may change for any reason
+        internal to the operation of Cinder.
+        """
+        for volume in fetched_list:
+            for field in ('updated_at',):
+                if field in volume:
+                    del volume[field]
+
+    def _assert_volumes_in(self, fetched_list, expected_list, fields=None):
         """Check out the list.
 
         This function is aim at check out whether all of the volumes in
@@ -45,6 +58,8 @@
             expected_list = map(fieldsgetter, expected_list)
             fetched_list = [fieldsgetter(item) for item in fetched_list]
 
+        # Hopefully the expected_list has already been cleaned.
+        self._remove_volatile_fields(fetched_list)
         missing_vols = [v for v in expected_list if v not in fetched_list]
         if not missing_vols:
             return
@@ -72,6 +87,7 @@
             volume = cls.volumes_client.show_volume(volume['id'])['volume']
             cls.volume_list.append(volume)
             cls.volume_id_list.append(volume['id'])
+        cls._remove_volatile_fields(cls.volume_list)
 
     def _list_by_param_value_and_assert(self, params, with_detail=False):
         """list or list_details with given params and validates result"""
@@ -103,15 +119,15 @@
         # Get a list of Volumes
         # Fetch all volumes
         fetched_list = self.volumes_client.list_volumes()['volumes']
-        self.assertVolumesIn(fetched_list, self.volume_list,
-                             fields=self.VOLUME_FIELDS)
+        self._assert_volumes_in(fetched_list, self.volume_list,
+                                fields=self.VOLUME_FIELDS)
 
     @decorators.idempotent_id('adcbb5a7-5ad8-4b61-bd10-5380e111a877')
     def test_volume_list_with_details(self):
         # Get a list of Volumes with details
         # Fetch all Volumes
         fetched_list = self.volumes_client.list_volumes(detail=True)['volumes']
-        self.assertVolumesIn(fetched_list, self.volume_list)
+        self._assert_volumes_in(fetched_list, self.volume_list)
 
     @decorators.idempotent_id('a28e8da4-0b56-472f-87a8-0f4d3f819c02')
     def test_volume_list_by_name(self):
@@ -137,8 +153,8 @@
         fetched_list = self.volumes_client.list_volumes(
             params=params)['volumes']
         self._list_by_param_value_and_assert(params)
-        self.assertVolumesIn(fetched_list, self.volume_list,
-                             fields=self.VOLUME_FIELDS)
+        self._assert_volumes_in(fetched_list, self.volume_list,
+                                fields=self.VOLUME_FIELDS)
 
     @decorators.idempotent_id('2943f712-71ec-482a-bf49-d5ca06216b9f')
     def test_volumes_list_details_by_status(self):
@@ -147,7 +163,7 @@
             detail=True, params=params)['volumes']
         for volume in fetched_list:
             self.assertEqual('available', volume['status'])
-        self.assertVolumesIn(fetched_list, self.volume_list)
+        self._assert_volumes_in(fetched_list, self.volume_list)
 
     @decorators.idempotent_id('2016a942-3020-40d7-95ce-7613bf8407ce')
     def test_volumes_list_by_bootable(self):
@@ -160,8 +176,8 @@
         fetched_list = self.volumes_client.list_volumes(
             params=params)['volumes']
         self._list_by_param_value_and_assert(params)
-        self.assertVolumesIn(fetched_list, self.volume_list,
-                             fields=self.VOLUME_FIELDS)
+        self._assert_volumes_in(fetched_list, self.volume_list,
+                                fields=self.VOLUME_FIELDS)
 
     @decorators.idempotent_id('2016a939-72ec-482a-bf49-d5ca06216b9f')
     def test_volumes_list_details_by_bootable(self):
@@ -170,7 +186,7 @@
             detail=True, params=params)['volumes']
         for volume in fetched_list:
             self.assertEqual('false', volume['bootable'])
-        self.assertVolumesIn(fetched_list, self.volume_list)
+        self._assert_volumes_in(fetched_list, self.volume_list)
 
     @decorators.idempotent_id('c0cfa863-3020-40d7-b587-e35f597d5d87')
     def test_volumes_list_by_availability_zone(self):
@@ -180,8 +196,8 @@
         fetched_list = self.volumes_client.list_volumes(
             params=params)['volumes']
         self._list_by_param_value_and_assert(params)
-        self.assertVolumesIn(fetched_list, self.volume_list,
-                             fields=self.VOLUME_FIELDS)
+        self._assert_volumes_in(fetched_list, self.volume_list,
+                                fields=self.VOLUME_FIELDS)
 
     @decorators.idempotent_id('e1b80d13-94f0-4ba2-a40e-386af29f8db1')
     def test_volumes_list_details_by_availability_zone(self):
@@ -192,7 +208,7 @@
             detail=True, params=params)['volumes']
         for volume in fetched_list:
             self.assertEqual(zone, volume['availability_zone'])
-        self.assertVolumesIn(fetched_list, self.volume_list)
+        self._assert_volumes_in(fetched_list, self.volume_list)
 
     @decorators.idempotent_id('b5ebea1b-0603-40a0-bb41-15fcd0a53214')
     def test_volume_list_with_param_metadata(self):
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index f139283..866bd87 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -103,21 +103,24 @@
     def test_create_volume_with_nonexistent_volume_type(self):
         # Should not be able to create volume with non-existent volume type
         self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
-                          size='1', volume_type=data_utils.rand_uuid())
+                          size=CONF.volume.volume_size,
+                          volume_type=data_utils.rand_uuid())
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('0c36f6ae-4604-4017-b0a9-34fdc63096f9')
     def test_create_volume_with_nonexistent_snapshot_id(self):
         # Should not be able to create volume with non-existent snapshot
         self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
-                          size='1', snapshot_id=data_utils.rand_uuid())
+                          size=CONF.volume.volume_size,
+                          snapshot_id=data_utils.rand_uuid())
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('47c73e08-4be8-45bb-bfdf-0c4e79b88344')
     def test_create_volume_with_nonexistent_source_volid(self):
         # Should not be able to create volume with non-existent source volume
         self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
-                          size='1', source_volid=data_utils.rand_uuid())
+                          size=CONF.volume.volume_size,
+                          source_volid=data_utils.rand_uuid())
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('0186422c-999a-480e-a026-6a665744c30c')
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index dcd3518..1855386 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -15,6 +15,7 @@
 
 from tempest.api.volume import base
 from tempest.common import utils
+from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -41,16 +42,19 @@
     def test_snapshot_create_delete_with_volume_in_use(self):
         # Create a test instance
         server = self.create_server()
-        self.attach_volume(server['id'], self.volume_origin['id'])
+        # NOTE(zhufl) Here we create volume from self.image_ref for adding
+        # coverage for "creating snapshot from non-blank volume".
+        volume = self.create_volume(image_ref=self.image_ref)
+        self.attach_volume(server['id'], volume['id'])
 
         # Snapshot a volume which attached to an instance with force=False
         self.assertRaises(lib_exc.BadRequest, self.create_snapshot,
-                          self.volume_origin['id'], force=False)
+                          volume['id'], force=False)
 
         # Snapshot a volume attached to an instance
-        snapshot1 = self.create_snapshot(self.volume_origin['id'], force=True)
-        snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True)
-        snapshot3 = self.create_snapshot(self.volume_origin['id'], force=True)
+        snapshot1 = self.create_snapshot(volume['id'], force=True)
+        snapshot2 = self.create_snapshot(volume['id'], force=True)
+        snapshot3 = self.create_snapshot(volume['id'], force=True)
 
         # Delete the snapshots. Some snapshot implementations can take
         # different paths according to order they are deleted.
@@ -126,17 +130,15 @@
         # Delete the snapshot
         self.delete_snapshot(snapshot['id'])
 
-    @decorators.idempotent_id('677863d1-3142-456d-b6ac-9924f667a7f4')
-    def test_volume_from_snapshot(self):
-        # Creates a volume from a snapshot passing a size
-        # different from the source
+    def _create_volume_from_snapshot(self, extra_size=0):
         src_size = CONF.volume.volume_size
+        size = src_size + extra_size
 
         src_vol = self.create_volume(size=src_size)
         src_snap = self.create_snapshot(src_vol['id'])
-        # Destination volume bigger than source snapshot
+
         dst_vol = self.create_volume(snapshot_id=src_snap['id'],
-                                     size=src_size + 1)
+                                     size=size)
         # NOTE(zhufl): dst_vol is created based on snapshot, so dst_vol
         # should be deleted before deleting snapshot, otherwise deleting
         # snapshot will end with status 'error-deleting'. This depends on
@@ -149,7 +151,18 @@
         volume = self.volumes_client.show_volume(dst_vol['id'])['volume']
         # Should allow
         self.assertEqual(volume['snapshot_id'], src_snap['id'])
-        self.assertEqual(volume['size'], src_size + 1)
+        self.assertEqual(volume['size'], size)
+
+    @decorators.idempotent_id('677863d1-3142-456d-b6ac-9924f667a7f4')
+    def test_volume_from_snapshot(self):
+        # Creates a volume from a snapshot passing a size
+        # different from the source
+        self._create_volume_from_snapshot(extra_size=1)
+
+    @decorators.idempotent_id('053d8870-8282-4fff-9dbb-99cb58bb5e0a')
+    def test_volume_from_snapshot_no_size(self):
+        # Creates a volume from a snapshot defaulting to original size
+        self._create_volume_from_snapshot()
 
     @decorators.idempotent_id('bbcfa285-af7f-479e-8c1a-8c34fc16543c')
     @testtools.skipUnless(CONF.volume_feature_enabled.backup,
@@ -160,6 +173,8 @@
 
         backup = self.create_backup(volume_id=self.volume_origin['id'],
                                     snapshot_id=snapshot['id'])
+        waiters.wait_for_volume_resource_status(self.snapshots_client,
+                                                snapshot['id'], 'available')
         backup_info = self.backups_client.show_backup(backup['id'])['backup']
         self.assertEqual(self.volume_origin['id'], backup_info['volume_id'])
         self.assertEqual(snapshot['id'], backup_info['snapshot_id'])
diff --git a/tempest/api/volume/test_volumes_snapshots_list.py b/tempest/api/volume/test_volumes_snapshots_list.py
index 507df1f..8a416ea 100644
--- a/tempest/api/volume/test_volumes_snapshots_list.py
+++ b/tempest/api/volume/test_volumes_snapshots_list.py
@@ -28,13 +28,11 @@
     @classmethod
     def resource_setup(cls):
         super(VolumesSnapshotListTestJSON, cls).resource_setup()
-        cls.snapshot_id_list = []
         volume_origin = cls.create_volume()
 
         # Create snapshots with params
         for _ in range(3):
             snapshot = cls.create_snapshot(volume_origin['id'])
-            cls.snapshot_id_list.append(snapshot['id'])
         cls.snapshot = snapshot
 
     def _list_by_param_values_and_assert(self, with_detail=False, **params):
@@ -151,10 +149,22 @@
     @decorators.idempotent_id('05489dde-44bc-4961-a1f5-3ce7ee7824f7')
     def test_snapshot_list_param_marker(self):
         # The list of snapshots should end before the provided marker
-        params = {'marker': self.snapshot_id_list[1]}
+        snap_list = self.snapshots_client.list_snapshots()['snapshots']
+        # list_snapshots will take the reverse order as they are created.
+        snapshot_id_list = [snap['id'] for snap in snap_list][::-1]
+
+        params = {'marker': snapshot_id_list[1]}
         snap_list = self.snapshots_client.list_snapshots(**params)['snapshots']
         fetched_list_id = [snap['id'] for snap in snap_list]
         # Verify the list of snapshots ends before the provided
         # marker(second snapshot), therefore only the first snapshot
         # should displayed.
-        self.assertEqual(self.snapshot_id_list[:1], fetched_list_id)
+        self.assertEqual(snapshot_id_list[:1], fetched_list_id)
+
+    @decorators.idempotent_id('ca96d551-17c6-4e11-b0e8-52d3bb8a63c7')
+    def test_snapshot_list_param_offset(self):
+        params = {'offset': 2, 'limit': 3}
+        snap_list = self.snapshots_client.list_snapshots(**params)['snapshots']
+        # Verify the list of snapshots skip offset=2 from the first element
+        # (total 3 elements), therefore only one snapshot should display
+        self.assertEqual(1, len(snap_list))
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index ea5f036..0453c0a 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -50,7 +50,7 @@
     @decorators.idempotent_id('677863d1-34f9-456d-b6ac-9924f667a7f4')
     def test_volume_from_snapshot_decreasing_size(self):
         # Creates a volume a snapshot passing a size different from the source
-        src_size = CONF.volume.volume_size + 1
+        src_size = CONF.volume.volume_size * 2
 
         src_vol = self.create_volume(size=src_size)
         src_snap = self.create_snapshot(src_vol['id'])
@@ -58,7 +58,7 @@
         # Destination volume smaller than source
         self.assertRaises(lib_exc.BadRequest,
                           self.volumes_client.create_volume,
-                          size=src_size - 1,
+                          size=CONF.volume.volume_size,
                           snapshot_id=src_snap['id'])
 
     @decorators.attr(type=['negative'])
diff --git a/tempest/clients.py b/tempest/clients.py
index ca205c8..0506646 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -44,6 +44,7 @@
         self._set_object_storage_clients()
         self._set_image_clients()
         self._set_network_clients()
+        self.placement_client = self.placement.PlacementClient()
         # 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
@@ -82,10 +83,8 @@
             self.schemas_client = self.image_v2.SchemasClient()
             self.namespace_properties_client = \
                 self.image_v2.NamespacePropertiesClient()
-            self.namespace_tags_client = \
-                self.image_v2.NamespaceTagsClient()
-            self.image_versions_client = \
-                self.image_v2.VersionsClient()
+            self.namespace_tags_client = self.image_v2.NamespaceTagsClient()
+            self.image_versions_client = self.image_v2.VersionsClient()
 
     def _set_compute_clients(self):
         self.agents_client = self.compute.AgentsClient()
@@ -197,6 +196,10 @@
         self.endpoint_groups_client = self.identity_v3.EndPointGroupsClient(
             **params_v3)
         self.catalog_client = self.identity_v3.CatalogClient(**params_v3)
+        self.project_tags_client = self.identity_v3.ProjectTagsClient(
+            **params_v3)
+        self.application_credentials_client = \
+            self.identity_v3.ApplicationCredentialsClient(**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
@@ -218,53 +221,75 @@
 
     def _set_volume_clients(self):
 
-        if CONF.volume_feature_enabled.api_v1:
-            self.backups_client = self.volume_v1.BackupsClient()
-            self.encryption_types_client = \
-                self.volume_v1.EncryptionTypesClient()
-            self.snapshots_client = self.volume_v1.SnapshotsClient()
-            self.volume_availability_zone_client = \
-                self.volume_v1.AvailabilityZoneClient()
-            self.volume_hosts_client = self.volume_v1.HostsClient()
-            self.volume_limits_client = self.volume_v1.LimitsClient()
-            self.volume_qos_client = self.volume_v1.QosSpecsClient()
-            self.volume_quotas_client = self.volume_v1.QuotasClient()
-            self.volume_services_client = self.volume_v1.ServicesClient()
-            self.volume_types_client = self.volume_v1.TypesClient()
-            self.volumes_client = self.volume_v1.VolumesClient()
-            self.volumes_extension_client = self.volume_v1.ExtensionsClient()
+        # if only api_v3 is enabled, all these clients should be available
+        if (CONF.volume_feature_enabled.api_v2 or
+            CONF.volume_feature_enabled.api_v3):
+            self.backups_client_latest = self.volume_v3.BackupsClient()
+            self.encryption_types_client_latest = \
+                self.volume_v3.EncryptionTypesClient()
+            self.snapshot_manage_client_latest = \
+                self.volume_v3.SnapshotManageClient()
+            self.snapshots_client_latest = self.volume_v3.SnapshotsClient()
+            self.volume_capabilities_client_latest = \
+                self.volume_v3.CapabilitiesClient()
+            self.volume_manage_client_latest = (
+                self.volume_v3.VolumeManageClient())
+            self.volume_qos_client_latest = self.volume_v3.QosSpecsClient()
+            self.volume_services_client_latest = (
+                self.volume_v3.ServicesClient())
+            self.volume_types_client_latest = self.volume_v3.TypesClient()
+            self.volume_hosts_client_latest = self.volume_v3.HostsClient()
+            self.volume_quotas_client_latest = self.volume_v3.QuotasClient()
+            self.volume_quota_classes_client_latest = \
+                self.volume_v3.QuotaClassesClient()
+            self.volume_scheduler_stats_client_latest = \
+                self.volume_v3.SchedulerStatsClient()
+            self.volume_transfers_client_latest = \
+                self.volume_v3.TransfersClient()
+            self.volume_availability_zone_client_latest = \
+                self.volume_v3.AvailabilityZoneClient()
+            self.volume_limits_client_latest = self.volume_v3.LimitsClient()
+            self.volumes_client_latest = self.volume_v3.VolumesClient()
+            self.volumes_extension_client_latest = \
+                self.volume_v3.ExtensionsClient()
+            self.group_types_client_latest = self.volume_v3.GroupTypesClient()
+            self.groups_client_latest = self.volume_v3.GroupsClient()
+            self.group_snapshots_client_latest = \
+                self.volume_v3.GroupSnapshotsClient()
+            self.volume_messages_client_latest = (
+                self.volume_v3.MessagesClient())
+            self.volume_versions_client_latest = (
+                self.volume_v3.VersionsClient())
 
-        if CONF.volume_feature_enabled.api_v2:
-            self.backups_v2_client = self.volume_v2.BackupsClient()
+            # TODO(gmann): Below alias for service clients have been
+            # deprecated and will be removed in future. Start using the alias
+            # defined above with suffix _latest.
+            # ****************Deprecated alias start from here***************
+            self.backups_v2_client = self.volume_v3.BackupsClient()
             self.encryption_types_v2_client = \
-                self.volume_v2.EncryptionTypesClient()
+                self.volume_v3.EncryptionTypesClient()
             self.snapshot_manage_v2_client = \
-                self.volume_v2.SnapshotManageClient()
-            self.snapshots_v2_client = self.volume_v2.SnapshotsClient()
+                self.volume_v3.SnapshotManageClient()
+            self.snapshots_v2_client = self.volume_v3.SnapshotsClient()
             self.volume_capabilities_v2_client = \
-                self.volume_v2.CapabilitiesClient()
-            self.volume_manage_v2_client = self.volume_v2.VolumeManageClient()
-            self.volume_qos_v2_client = self.volume_v2.QosSpecsClient()
-            self.volume_services_v2_client = self.volume_v2.ServicesClient()
-            self.volume_types_v2_client = self.volume_v2.TypesClient()
-            self.volume_hosts_v2_client = self.volume_v2.HostsClient()
-            self.volume_quotas_v2_client = self.volume_v2.QuotasClient()
+                self.volume_v3.CapabilitiesClient()
+            self.volume_manage_v2_client = self.volume_v3.VolumeManageClient()
+            self.volume_qos_v2_client = self.volume_v3.QosSpecsClient()
+            self.volume_services_v2_client = self.volume_v3.ServicesClient()
+            self.volume_types_v2_client = self.volume_v3.TypesClient()
+            self.volume_hosts_v2_client = self.volume_v3.HostsClient()
+            self.volume_quotas_v2_client = self.volume_v3.QuotasClient()
             self.volume_quota_classes_v2_client = \
-                self.volume_v2.QuotaClassesClient()
+                self.volume_v3.QuotaClassesClient()
             self.volume_scheduler_stats_v2_client = \
-                self.volume_v2.SchedulerStatsClient()
-            self.volume_transfers_v2_client = \
-                self.volume_v2.TransfersClient()
+                self.volume_v3.SchedulerStatsClient()
+            self.volume_transfers_v2_client = self.volume_v3.TransfersClient()
             self.volume_v2_availability_zone_client = \
-                self.volume_v2.AvailabilityZoneClient()
-            self.volume_v2_limits_client = self.volume_v2.LimitsClient()
-            self.volumes_v2_client = self.volume_v2.VolumesClient()
+                self.volume_v3.AvailabilityZoneClient()
+            self.volume_v2_limits_client = self.volume_v3.LimitsClient()
+            self.volumes_v2_client = self.volume_v3.VolumesClient()
             self.volumes_v2_extension_client = \
-                self.volume_v2.ExtensionsClient()
-
-            # Set default client for users that don't need explicit version
-            self.volumes_client_latest = self.volumes_v2_client
-            self.snapshots_client_latest = self.snapshots_v2_client
+                self.volume_v3.ExtensionsClient()
 
         if CONF.volume_feature_enabled.api_v3:
             self.backups_v3_client = self.volume_v3.BackupsClient()
@@ -276,10 +301,7 @@
             self.volume_v3_messages_client = self.volume_v3.MessagesClient()
             self.volume_v3_versions_client = self.volume_v3.VersionsClient()
             self.volumes_v3_client = self.volume_v3.VolumesClient()
-
-            # Set default client for users that don't need explicit version
-            self.volumes_client_latest = self.volumes_v3_client
-            self.snapshots_client_latest = self.snapshots_v3_client
+            # ****************Deprecated alias end here***********************
 
     def _set_object_storage_clients(self):
         self.account_client = self.object_storage.AccountClient()
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 8636405..7ea0099 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -15,18 +15,18 @@
 #    under the License.
 
 """
-Utility for creating **accounts.yaml** file for concurrent test runs.
+Utility for creating ``accounts.yaml`` file for concurrent test runs.
 Creates one primary user, one alt user, one swift admin, one stack owner
 and one admin (optionally) for each concurrent thread. The utility creates
-user for each tenant. The **accounts.yaml** file will be valid and contain
+user for each tenant. The ``accounts.yaml`` file will be valid and contain
 credentials for created users, so each user will be in separate tenant and
 have the username, tenant_name, password and roles.
 
-**Usage:** ``tempest account-generator [-h] [OPTIONS] accounts_file.yaml``.
+**Usage:** ``tempest account-generator [-h] [OPTIONS] accounts_file.yaml``
 
 Positional Arguments
 --------------------
-**accounts_file.yaml** (Required) Provide an output accounts yaml file. Utility
+``accounts_file.yaml`` (Required) Provide an output accounts yaml file. Utility
 creates a .yaml file in the directory where the command is ran. The appropriate
 name for the file is *accounts.yaml* and it should be placed in *tempest/etc*
 directory.
@@ -40,55 +40,62 @@
 
 You're probably familiar with these, but just to remind:
 
-======== ======================== ====================
-Param    CLI                      Environment Variable
-======== ======================== ====================
-Username --os-username            OS_USERNAME
-Password --os-password            OS_PASSWORD
-Project  --os-project-name        OS_PROJECT_NAME
-Tenant   --os-tenant-name (depr.) OS_TENANT_NAME
-Domain   --os-domain-name         OS_DOMAIN_NAME
-======== ======================== ====================
+======== ============================ ====================
+Param    CLI                          Environment Variable
+======== ============================ ====================
+Username ``--os-username``            OS_USERNAME
+Password ``--os-password``            OS_PASSWORD
+Project  ``--os-project-name``        OS_PROJECT_NAME
+Tenant   ``--os-tenant-name`` (depr.) OS_TENANT_NAME
+Domain   ``--os-domain-name``         OS_DOMAIN_NAME
+======== ============================ ====================
 
 Optional Arguments
 ------------------
-**-h**, **--help** (Optional) Shows help message with the description of
-utility and its arguments, and exits.
+* ``-h, --help`` (Optional) Shows help message with the description of
+  utility and its arguments, and exits.
 
-**c /etc/tempest.conf**, **--config-file /etc/tempest.conf** (Optional) Path to
-tempest config file.
+* ``-c, --config-file /etc/tempest.conf`` (Optional) Path
+  to tempest config file. If not specified, it searches for tempest.conf in
+  these locations:
 
-**--os-username <auth-user-name>** (Optional) Name used for authentication with
-the OpenStack Identity service. Defaults to env[OS_USERNAME]. Note: User should
-have permissions to create new user accounts and tenants.
+  - ./etc/
+  - /etc/tempest
+  - ~/.tempest/
+  - ~/
+  - /etc/
 
-**--os-password <auth-password>** (Optional) Password used for authentication
-with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
+* ``--os-username <auth-user-name>`` (Optional) Name used for authentication
+  with the OpenStack Identity service. Defaults to env[OS_USERNAME]. Note: User
+  should have permissions to create new user accounts and tenants.
 
-**--os-project-name <auth-project-name>** (Optional) Project to request
-authorization on. Defaults to env[OS_PROJECT_NAME].
+* ``--os-password <auth-password>`` (Optional) Password used for authentication
+  with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
 
-**--os-tenant-name <auth-tenant-name>** (Optional, deprecated) Tenant to
-request authorization on. Defaults to env[OS_TENANT_NAME].
+* ``--os-project-name <auth-project-name>`` (Optional) Project to request
+  authorization on. Defaults to env[OS_PROJECT_NAME].
 
-**--os-domain-name <auth-domain-name>** (Optional) Domain the user and project
-belong to. Defaults to env[OS_DOMAIN_NAME].
+* ``--os-tenant-name <auth-tenant-name>`` (Optional, deprecated) Tenant to
+  request authorization on. Defaults to env[OS_TENANT_NAME].
 
-**--tag TAG** (Optional) Resources tag. Each created resource (user, project)
-will have the prefix with the given TAG in its name. Using tag is recommended
-for the further using, cleaning resources.
+* ``--os-domain-name <auth-domain-name>`` (Optional) Domain the user and
+  project belong to. Defaults to env[OS_DOMAIN_NAME].
 
-**-r CONCURRENCY**, **--concurrency CONCURRENCY** (Required) Concurrency count
-(default: 1). The number of accounts required can be estimated as
-CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in
-a different tenant. This is required to provide isolation between test for
-running in parallel.
+* ``--tag TAG`` (Optional) Resources tag. Each created resource (user, project)
+  will have the prefix with the given TAG in its name. Using tag is recommended
+  for the further using, cleaning resources.
 
-**--with-admin** (Optional) Creates admin for each concurrent group
-(default: False).
+* ``-r, --concurrency CONCURRENCY`` (Optional) Concurrency count
+  (default: 1). The number of accounts required can be estimated as
+  CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in
+  a different tenant. This is required to provide isolation between test for
+  running in parallel.
 
-**-i VERSION**, **--identity-version VERSION** (Optional) Provisions accounts
-using the specified version of the identity API. (default: '3').
+* ``--with-admin`` (Optional) Creates admin for each concurrent group
+  (default: False).
+
+* ``-i, --identity-version VERSION`` (Optional) Provisions accounts
+  using the specified version of the identity API. (default: '3').
 
 To see help on specific argument, please do: ``tempest account-generator
 [OPTIONS] <accounts_file.yaml> -h``.
@@ -155,9 +162,6 @@
     if CONF.service_available.swift:
         spec.append([CONF.object_storage.operator_role])
         spec.append([CONF.object_storage.reseller_admin_role])
-    if CONF.service_available.heat:
-        spec.append([CONF.orchestration.stack_owner_role,
-                     CONF.object_storage.operator_role])
     if admin:
         spec.append('admin')
     resources = []
@@ -190,7 +194,6 @@
 
         if test_resource.network:
             account['resources'] = {}
-        if test_resource.network:
             account['resources']['network'] = test_resource.network['name']
         accounts.append(account)
     if os.path.exists(account_file):
@@ -288,13 +291,16 @@
 
 
 def main(opts=None):
-    setup_logging()
+    log_warning = False
     if not opts:
-        LOG.warning("Use of: 'tempest-account-generator' is deprecated, "
-                    "please use: 'tempest account-generator'")
+        log_warning = True
         opts = get_options()
     if opts.config_file:
         config.CONF.set_config_path(opts.config_file)
+    setup_logging()
+    if log_warning:
+        LOG.warning("Use of: 'tempest-account-generator' is deprecated, "
+                    "please use: 'tempest account-generator'")
     if opts.os_tenant_name:
         LOG.warning("'os-tenant-name' and 'OS_TENANT_NAME' are both "
                     "deprecated, please use 'os-project-name' or "
@@ -306,5 +312,6 @@
         resources.extend(generate_resources(cred_provider, opts.admin))
     dump_accounts(resources, opts.identity_version, opts.accounts)
 
+
 if __name__ == "__main__":
     main()
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index d0aa7dc..e6db2e9 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -28,45 +28,48 @@
 Example Run
 -----------
 
-**WARNING: If step 1 is skipped in the example below, the cleanup procedure
-may delete resources that existed in the cloud before the test run. This
-may cause an unwanted destruction of cloud resources, so use caution with
-this command.**
+.. warning::
 
-``$ tempest cleanup --init-saved-state``
+    If step 1 is skipped in the example below, the cleanup procedure
+    may delete resources that existed in the cloud before the test run. This
+    may cause an unwanted destruction of cloud resources, so use caution with
+    this command.
 
-``$ # Actual running of Tempest tests``
+    Examples::
 
-``$ tempest cleanup``
+     $ tempest cleanup --init-saved-state
+     $ # Actual running of Tempest tests
+     $ tempest cleanup
 
 Runtime Arguments
 -----------------
 
-**--init-saved-state**: Initializes the saved state of the OpenStack deployment
-and will output a ``saved_state.json`` file containing resources from your
-deployment that will be preserved from the cleanup command. This should be
-done prior to running Tempest tests.
+* ``--init-saved-state``: Initializes the saved state of the OpenStack
+  deployment and will output a ``saved_state.json`` file containing resources
+  from your deployment that will be preserved from the cleanup command. This
+  should be done prior to running Tempest tests.
 
-**--delete-tempest-conf-objects**: If option is present, then the command will
-delete the admin project in addition to the resources associated with them on
-clean up. If option is not present, the command will delete the resources
-associated with the Tempest and alternate Tempest users and projects but will
-not delete the projects themselves.
+* ``--delete-tempest-conf-objects``: If option is present, then the command
+  will delete the admin project in addition to the resources associated with
+  them on clean up. If option is not present, the command will delete the
+  resources associated with the Tempest and alternate Tempest users and
+  projects but will not delete the projects themselves.
 
-**--dry-run**: Creates a report (``./dry_run.json``) of the projects that will
-be cleaned up (in the ``_projects_to_clean`` dictionary [1]_) and the global
-objects that will be removed (domains, flavors, images, roles, projects,
-and users). Once the cleanup command is executed (e.g. run without
-parameters), running it again with **--dry-run** should yield an empty report.
+* ``--dry-run``: Creates a report (``./dry_run.json``) of the projects that
+  will be cleaned up (in the ``_projects_to_clean`` dictionary [1]_) and the
+  global objects that will be removed (domains, flavors, images, roles,
+  projects, and users). Once the cleanup command is executed (e.g. run without
+  parameters), running it again with ``--dry-run`` should yield an empty
+  report.
 
-**--help**: Print the help text for the command and parameters.
+* ``--help``: Print the help text for the command and parameters.
 
 .. [1] The ``_projects_to_clean`` dictionary in ``dry_run.json`` lists the
     projects that ``tempest cleanup`` will loop through to delete child
     objects, but the command will, by default, not delete the projects
     themselves. This may differ from the ``projects`` list as you can clean
     the Tempest and alternate Tempest users and projects but they will not be
-    deleted unless the **--delete-tempest-conf-objects** flag is used to
+    deleted unless the ``--delete-tempest-conf-objects`` flag is used to
     force their deletion.
 
 """
@@ -194,7 +197,7 @@
             **kwargs))
         kwargs = {'data': project_data,
                   'is_dry_run': is_dry_run,
-                  'saved_state_json': None,
+                  'saved_state_json': self.json_data,
                   'is_preserve': is_preserve,
                   'is_save_state': False,
                   'project_id': project_id}
@@ -276,7 +279,7 @@
                                                        self.admin_id,
                                                        self.admin_role_id)
             except Exception as ex:
-                LOG.exception("Failed removing role from project which still"
+                LOG.exception("Failed removing role from project which still "
                               "exists, exception: %s", ex)
 
     def _project_exists(self, project_id):
@@ -302,13 +305,17 @@
             svc = service(admin_mgr, **kwargs)
             svc.run()
 
-        with open(SAVED_STATE_JSON, 'w+') as f:
-            f.write(json.dumps(data,
-                    sort_keys=True, indent=2, separators=(',', ': ')))
+        for service in self.project_services:
+            svc = service(admin_mgr, **kwargs)
+            svc.run()
 
-    def _load_json(self):
+        with open(SAVED_STATE_JSON, 'w+') as f:
+            f.write(json.dumps(data, sort_keys=True,
+                               indent=2, separators=(',', ': ')))
+
+    def _load_json(self, saved_state_json=SAVED_STATE_JSON):
         try:
-            with open(SAVED_STATE_JSON) as json_file:
+            with open(saved_state_json, 'rb') as json_file:
                 self.json_data = json.load(json_file)
 
         except IOError as ex:
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index e61f906..104958a 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -37,7 +37,6 @@
 
 IS_CINDER = None
 IS_GLANCE = None
-IS_HEAT = None
 IS_NEUTRON = None
 IS_NOVA = None
 
@@ -60,7 +59,6 @@
 
     IS_CINDER = CONF.service_available.cinder
     IS_GLANCE = CONF.service_available.glance
-    IS_HEAT = CONF.service_available.heat
     IS_NEUTRON = CONF.service_available.neutron
     IS_NOVA = CONF.service_available.nova
 
@@ -103,14 +101,14 @@
 
         self.tenant_filter = {}
         if hasattr(self, 'tenant_id'):
-            self.tenant_filter['tenant_id'] = self.tenant_id
+            self.tenant_filter['project_id'] = self.tenant_id
 
     def _filter_by_tenant_id(self, item_list):
-        if (item_list is None
-                or not item_list
-                or not hasattr(self, 'tenant_id')
-                or self.tenant_id is None
-                or 'tenant_id' not in item_list[0]):
+        if (item_list is None or
+                not item_list or
+                not hasattr(self, 'tenant_id') or
+                self.tenant_id is None or
+                'tenant_id' not in item_list[0]):
             return item_list
 
         return [item for item in item_list
@@ -141,11 +139,15 @@
 
     def __init__(self, manager, **kwargs):
         super(SnapshotService, self).__init__(kwargs)
-        self.client = manager.snapshots_client
+        self.client = manager.snapshots_client_latest
 
     def list(self):
         client = self.client
         snaps = client.list_snapshots()['snapshots']
+        if not self.is_save_state:
+            # recreate list removing saved snapshots
+            snaps = [snap for snap in snaps if snap['id']
+                     not in self.saved_state_json['snapshots'].keys()]
         LOG.debug("List count, %s Snapshots", len(snaps))
         return snaps
 
@@ -156,12 +158,18 @@
             try:
                 client.delete_snapshot(snap['id'])
             except Exception:
-                LOG.exception("Delete Snapshot exception.")
+                LOG.exception("Delete Snapshot %s exception.", snap['id'])
 
     def dry_run(self):
         snaps = self.list()
         self.data['snapshots'] = snaps
 
+    def save_state(self):
+        snaps = self.list()
+        self.data['snapshots'] = {}
+        for snap in snaps:
+            self.data['snapshots'][snap['id']] = snap['name']
+
 
 class ServerService(BaseService):
     def __init__(self, manager, **kwargs):
@@ -173,6 +181,10 @@
         client = self.client
         servers_body = client.list_servers()
         servers = servers_body['servers']
+        if not self.is_save_state:
+            # recreate list removing saved servers
+            servers = [server for server in servers if server['id']
+                       not in self.saved_state_json['servers'].keys()]
         LOG.debug("List count, %s Servers", len(servers))
         return servers
 
@@ -183,60 +195,49 @@
             try:
                 client.delete_server(server['id'])
             except Exception:
-                LOG.exception("Delete Server exception.")
+                LOG.exception("Delete Server %s exception.", server['id'])
 
     def dry_run(self):
         servers = self.list()
         self.data['servers'] = servers
 
+    def save_state(self):
+        servers = self.list()
+        self.data['servers'] = {}
+        for server in servers:
+            self.data['servers'][server['id']] = server['name']
+
 
 class ServerGroupService(ServerService):
 
     def list(self):
         client = self.server_groups_client
         sgs = client.list_server_groups()['server_groups']
+        if not self.is_save_state:
+            # recreate list removing saved server_groups
+            sgs = [sg for sg in sgs if sg['id']
+                   not in self.saved_state_json['server_groups'].keys()]
         LOG.debug("List count, %s Server Groups", len(sgs))
         return sgs
 
     def delete(self):
-        client = self.client
+        client = self.server_groups_client
         sgs = self.list()
         for sg in sgs:
             try:
                 client.delete_server_group(sg['id'])
             except Exception:
-                LOG.exception("Delete Server Group exception.")
+                LOG.exception("Delete Server Group %s exception.", sg['id'])
 
     def dry_run(self):
         sgs = self.list()
         self.data['server_groups'] = sgs
 
-
-class StackService(BaseService):
-    def __init__(self, manager, **kwargs):
-        super(StackService, self).__init__(kwargs)
-        params = config.service_client_config('orchestration')
-        self.client = manager.orchestration.OrchestrationClient(
-            manager.auth_provider, **params)
-
-    def list(self):
-        client = self.client
-        stacks = client.list_stacks()['stacks']
-        LOG.debug("List count, %s Stacks", len(stacks))
-        return stacks
-
-    def delete(self):
-        client = self.client
-        stacks = self.list()
-        for stack in stacks:
-            try:
-                client.delete_stack(stack['id'])
-            except Exception:
-                LOG.exception("Delete Stack exception.")
-
-    def dry_run(self):
-        stacks = self.list()
-        self.data['stacks'] = stacks
+    def save_state(self):
+        sgs = self.list()
+        self.data['server_groups'] = {}
+        for sg in sgs:
+            self.data['server_groups'][sg['id']] = sg['name']
 
 
 class KeyPairService(BaseService):
@@ -247,6 +248,11 @@
     def list(self):
         client = self.client
         keypairs = client.list_keypairs()['keypairs']
+        if not self.is_save_state:
+            # recreate list removing saved keypairs
+            keypairs = [keypair for keypair in keypairs
+                        if keypair['keypair']['name']
+                        not in self.saved_state_json['keypairs'].keys()]
         LOG.debug("List count, %s Keypairs", len(keypairs))
         return keypairs
 
@@ -254,76 +260,36 @@
         client = self.client
         keypairs = self.list()
         for k in keypairs:
+            name = k['keypair']['name']
             try:
-                name = k['keypair']['name']
                 client.delete_keypair(name)
             except Exception:
-                LOG.exception("Delete Keypairs exception.")
+                LOG.exception("Delete Keypair %s exception.", name)
 
     def dry_run(self):
         keypairs = self.list()
         self.data['keypairs'] = keypairs
 
-
-class SecurityGroupService(BaseService):
-    def __init__(self, manager, **kwargs):
-        super(SecurityGroupService, self).__init__(kwargs)
-        self.client = manager.compute_security_groups_client
-
-    def list(self):
-        client = self.client
-        secgrps = client.list_security_groups()['security_groups']
-        secgrp_del = [grp for grp in secgrps if grp['name'] != 'default']
-        LOG.debug("List count, %s Security Groups", len(secgrp_del))
-        return secgrp_del
-
-    def delete(self):
-        client = self.client
-        secgrp_del = self.list()
-        for g in secgrp_del:
-            try:
-                client.delete_security_group(g['id'])
-            except Exception:
-                LOG.exception("Delete Security Groups exception.")
-
-    def dry_run(self):
-        secgrp_del = self.list()
-        self.data['security_groups'] = secgrp_del
-
-
-class FloatingIpService(BaseService):
-    def __init__(self, manager, **kwargs):
-        super(FloatingIpService, self).__init__(kwargs)
-        self.client = manager.compute_floating_ips_client
-
-    def list(self):
-        client = self.client
-        floating_ips = client.list_floating_ips()['floating_ips']
-        LOG.debug("List count, %s Floating IPs", len(floating_ips))
-        return floating_ips
-
-    def delete(self):
-        client = self.client
-        floating_ips = self.list()
-        for f in floating_ips:
-            try:
-                client.delete_floating_ip(f['id'])
-            except Exception:
-                LOG.exception("Delete Floating IPs exception.")
-
-    def dry_run(self):
-        floating_ips = self.list()
-        self.data['floating_ips'] = floating_ips
+    def save_state(self):
+        keypairs = self.list()
+        self.data['keypairs'] = {}
+        for keypair in keypairs:
+            keypair = keypair['keypair']
+            self.data['keypairs'][keypair['name']] = keypair
 
 
 class VolumeService(BaseService):
     def __init__(self, manager, **kwargs):
         super(VolumeService, self).__init__(kwargs)
-        self.client = manager.volumes_client
+        self.client = manager.volumes_client_latest
 
     def list(self):
         client = self.client
         vols = client.list_volumes()['volumes']
+        if not self.is_save_state:
+            # recreate list removing saved volumes
+            vols = [vol for vol in vols if vol['id']
+                    not in self.saved_state_json['volumes'].keys()]
         LOG.debug("List count, %s Volumes", len(vols))
         return vols
 
@@ -334,28 +300,35 @@
             try:
                 client.delete_volume(v['id'])
             except Exception:
-                LOG.exception("Delete Volume exception.")
+                LOG.exception("Delete Volume %s exception.", v['id'])
 
     def dry_run(self):
         vols = self.list()
         self.data['volumes'] = vols
 
+    def save_state(self):
+        vols = self.list()
+        self.data['volumes'] = {}
+        for vol in vols:
+            self.data['volumes'][vol['id']] = vol['name']
+
 
 class VolumeQuotaService(BaseService):
     def __init__(self, manager, **kwargs):
         super(VolumeQuotaService, self).__init__(kwargs)
-        self.client = manager.volume_quotas_client
+        self.client = manager.volume_quotas_client_latest
 
     def delete(self):
         client = self.client
         try:
-            client.delete_quota_set(self.tenant_id)
+            client.delete_quota_set(self.project_id)
         except Exception:
-            LOG.exception("Delete Volume Quotas exception.")
+            LOG.exception("Delete Volume Quotas exception for 'project %s'.",
+                          self.project_id)
 
     def dry_run(self):
         quotas = self.client.show_quota_set(
-            self.tenant_id, params={'usage': True})['quota_set']
+            self.project_id, params={'usage': True})['quota_set']
         self.data['volume_quotas'] = quotas
 
 
@@ -368,9 +341,10 @@
     def delete(self):
         client = self.client
         try:
-            client.delete_quota_set(self.tenant_id)
+            client.delete_quota_set(self.project_id)
         except Exception:
-            LOG.exception("Delete Quotas exception.")
+            LOG.exception("Delete Quotas exception for 'project %s'.",
+                          self.project_id)
 
     def dry_run(self):
         client = self.limits_client
@@ -379,9 +353,9 @@
 
 
 # Begin network service classes
-class NetworkService(BaseService):
+class BaseNetworkService(BaseService):
     def __init__(self, manager, **kwargs):
-        super(NetworkService, self).__init__(kwargs)
+        super(BaseNetworkService, self).__init__(kwargs)
         self.networks_client = manager.networks_client
         self.subnets_client = manager.subnets_client
         self.ports_client = manager.ports_client
@@ -390,6 +364,7 @@
         self.metering_label_rules_client = manager.metering_label_rules_client
         self.security_groups_client = manager.security_groups_client
         self.routers_client = manager.routers_client
+        self.subnetpools_client = manager.subnetpools_client
 
     def _filter_by_conf_networks(self, item_list):
         if not item_list or not all(('network_id' in i for i in item_list)):
@@ -398,10 +373,18 @@
         return [item for item in item_list if item['network_id']
                 not in CONF_NETWORKS]
 
+
+class NetworkService(BaseNetworkService):
+
     def list(self):
         client = self.networks_client
         networks = client.list_networks(**self.tenant_filter)
         networks = networks['networks']
+
+        if not self.is_save_state:
+            # recreate list removing saved networks
+            networks = [network for network in networks if network['id']
+                        not in self.saved_state_json['networks'].keys()]
         # filter out networks declared in tempest.conf
         if self.is_preserve:
             networks = [network for network in networks
@@ -416,42 +399,65 @@
             try:
                 client.delete_network(n['id'])
             except Exception:
-                LOG.exception("Delete Network exception.")
+                LOG.exception("Delete Network %s exception.", n['id'])
 
     def dry_run(self):
         networks = self.list()
         self.data['networks'] = networks
 
+    def save_state(self):
+        networks = self.list()
+        self.data['networks'] = {}
+        for network in networks:
+            self.data['networks'][network['id']] = network
 
-class NetworkFloatingIpService(NetworkService):
+
+class NetworkFloatingIpService(BaseNetworkService):
 
     def list(self):
         client = self.floating_ips_client
         flips = client.list_floatingips(**self.tenant_filter)
         flips = flips['floatingips']
+
+        if not self.is_save_state:
+            # recreate list removing saved flips
+            flips = [flip for flip in flips if flip['id']
+                     not in self.saved_state_json['floatingips'].keys()]
         LOG.debug("List count, %s Network Floating IPs", len(flips))
         return flips
 
     def delete(self):
-        client = self.client
+        client = self.floating_ips_client
         flips = self.list()
         for flip in flips:
             try:
                 client.delete_floatingip(flip['id'])
             except Exception:
-                LOG.exception("Delete Network Floating IP exception.")
+                LOG.exception("Delete Network Floating IP %s exception.",
+                              flip['id'])
 
     def dry_run(self):
         flips = self.list()
-        self.data['floating_ips'] = flips
+        self.data['floatingips'] = flips
+
+    def save_state(self):
+        flips = self.list()
+        self.data['floatingips'] = {}
+        for flip in flips:
+            self.data['floatingips'][flip['id']] = flip
 
 
-class NetworkRouterService(NetworkService):
+class NetworkRouterService(BaseNetworkService):
 
     def list(self):
         client = self.routers_client
         routers = client.list_routers(**self.tenant_filter)
         routers = routers['routers']
+
+        if not self.is_save_state:
+            # recreate list removing saved routers
+            routers = [router for router in routers if router['id']
+                       not in self.saved_state_json['routers'].keys()]
         if self.is_preserve:
             routers = [router for router in routers
                        if router['id'] != CONF_PUB_ROUTER]
@@ -464,116 +470,30 @@
         ports_client = self.ports_client
         routers = self.list()
         for router in routers:
-            try:
-                rid = router['id']
-                ports = [port for port
-                         in ports_client.list_ports(device_id=rid)['ports']
-                         if net_info.is_router_interface_port(port)]
-                for port in ports:
+            rid = router['id']
+            ports = [port for port
+                     in ports_client.list_ports(device_id=rid)['ports']
+                     if net_info.is_router_interface_port(port)]
+            for port in ports:
+                try:
                     client.remove_router_interface(rid, port_id=port['id'])
+                except Exception:
+                    LOG.exception("Delete Router Interface exception for "
+                                  "'port %s' of 'router %s'.", port['id'], rid)
+            try:
                 client.delete_router(rid)
             except Exception:
-                LOG.exception("Delete Router exception.")
+                LOG.exception("Delete Router %s exception.", rid)
 
     def dry_run(self):
         routers = self.list()
         self.data['routers'] = routers
 
-
-class NetworkHealthMonitorService(NetworkService):
-
-    def list(self):
-        client = self.client
-        hms = client.list_health_monitors()
-        hms = hms['health_monitors']
-        hms = self._filter_by_tenant_id(hms)
-        LOG.debug("List count, %s Health Monitors", len(hms))
-        return hms
-
-    def delete(self):
-        client = self.client
-        hms = self.list()
-        for hm in hms:
-            try:
-                client.delete_health_monitor(hm['id'])
-            except Exception:
-                LOG.exception("Delete Health Monitor exception.")
-
-    def dry_run(self):
-        hms = self.list()
-        self.data['health_monitors'] = hms
-
-
-class NetworkMemberService(NetworkService):
-
-    def list(self):
-        client = self.client
-        members = client.list_members()
-        members = members['members']
-        members = self._filter_by_tenant_id(members)
-        LOG.debug("List count, %s Members", len(members))
-        return members
-
-    def delete(self):
-        client = self.client
-        members = self.list()
-        for member in members:
-            try:
-                client.delete_member(member['id'])
-            except Exception:
-                LOG.exception("Delete Member exception.")
-
-    def dry_run(self):
-        members = self.list()
-        self.data['members'] = members
-
-
-class NetworkVipService(NetworkService):
-
-    def list(self):
-        client = self.client
-        vips = client.list_vips()
-        vips = vips['vips']
-        vips = self._filter_by_tenant_id(vips)
-        LOG.debug("List count, %s VIPs", len(vips))
-        return vips
-
-    def delete(self):
-        client = self.client
-        vips = self.list()
-        for vip in vips:
-            try:
-                client.delete_vip(vip['id'])
-            except Exception:
-                LOG.exception("Delete VIP exception.")
-
-    def dry_run(self):
-        vips = self.list()
-        self.data['vips'] = vips
-
-
-class NetworkPoolService(NetworkService):
-
-    def list(self):
-        client = self.client
-        pools = client.list_pools()
-        pools = pools['pools']
-        pools = self._filter_by_tenant_id(pools)
-        LOG.debug("List count, %s Pools", len(pools))
-        return pools
-
-    def delete(self):
-        client = self.client
-        pools = self.list()
-        for pool in pools:
-            try:
-                client.delete_pool(pool['id'])
-            except Exception:
-                LOG.exception("Delete Pool exception.")
-
-    def dry_run(self):
-        pools = self.list()
-        self.data['pools'] = pools
+    def save_state(self):
+        routers = self.list()
+        self.data['routers'] = {}
+        for router in routers:
+            self.data['routers'][router['id']] = router['name']
 
 
 class NetworkMeteringLabelRuleService(NetworkService):
@@ -583,6 +503,11 @@
         rules = client.list_metering_label_rules()
         rules = rules['metering_label_rules']
         rules = self._filter_by_tenant_id(rules)
+
+        if not self.is_save_state:
+            saved_rules = self.saved_state_json['metering_label_rules'].keys()
+            # recreate list removing saved rules
+            rules = [rule for rule in rules if rule['id'] not in saved_rules]
         LOG.debug("List count, %s Metering Label Rules", len(rules))
         return rules
 
@@ -593,20 +518,32 @@
             try:
                 client.delete_metering_label_rule(rule['id'])
             except Exception:
-                LOG.exception("Delete Metering Label Rule exception.")
+                LOG.exception("Delete Metering Label Rule %s exception.",
+                              rule['id'])
 
     def dry_run(self):
         rules = self.list()
-        self.data['rules'] = rules
+        self.data['metering_label_rules'] = rules
+
+    def save_state(self):
+        rules = self.list()
+        self.data['metering_label_rules'] = {}
+        for rule in rules:
+            self.data['metering_label_rules'][rule['id']] = rule
 
 
-class NetworkMeteringLabelService(NetworkService):
+class NetworkMeteringLabelService(BaseNetworkService):
 
     def list(self):
         client = self.metering_labels_client
         labels = client.list_metering_labels()
         labels = labels['metering_labels']
         labels = self._filter_by_tenant_id(labels)
+
+        if not self.is_save_state:
+            # recreate list removing saved labels
+            labels = [label for label in labels if label['id']
+                      not in self.saved_state_json['metering_labels'].keys()]
         LOG.debug("List count, %s Metering Labels", len(labels))
         return labels
 
@@ -617,14 +554,21 @@
             try:
                 client.delete_metering_label(label['id'])
             except Exception:
-                LOG.exception("Delete Metering Label exception.")
+                LOG.exception("Delete Metering Label %s exception.",
+                              label['id'])
 
     def dry_run(self):
         labels = self.list()
-        self.data['labels'] = labels
+        self.data['metering_labels'] = labels
+
+    def save_state(self):
+        labels = self.list()
+        self.data['metering_labels'] = {}
+        for label in labels:
+            self.data['metering_labels'][label['id']] = label['name']
 
 
-class NetworkPortService(NetworkService):
+class NetworkPortService(BaseNetworkService):
 
     def list(self):
         client = self.ports_client
@@ -633,6 +577,10 @@
                  if port["device_owner"] == "" or
                  port["device_owner"].startswith("compute:")]
 
+        if not self.is_save_state:
+            # recreate list removing saved ports
+            ports = [port for port in ports if port['id']
+                     not in self.saved_state_json['ports'].keys()]
         if self.is_preserve:
             ports = self._filter_by_conf_networks(ports)
 
@@ -646,14 +594,20 @@
             try:
                 client.delete_port(port['id'])
             except Exception:
-                LOG.exception("Delete Port exception.")
+                LOG.exception("Delete Port %s exception.", port['id'])
 
     def dry_run(self):
         ports = self.list()
         self.data['ports'] = ports
 
+    def save_state(self):
+        ports = self.list()
+        self.data['ports'] = {}
+        for port in ports:
+            self.data['ports'][port['id']] = port['name']
 
-class NetworkSecGroupService(NetworkService):
+
+class NetworkSecGroupService(BaseNetworkService):
     def list(self):
         client = self.security_groups_client
         filter = self.tenant_filter
@@ -662,31 +616,49 @@
                      client.list_security_groups(**filter)['security_groups']
                      if secgroup['name'] != 'default']
 
+        if not self.is_save_state:
+            # recreate list removing saved security_groups
+            secgroups = [secgroup for secgroup in secgroups if secgroup['id']
+                         not in self.saved_state_json['security_groups'].keys()
+                         ]
         if self.is_preserve:
-            secgroups = self._filter_by_conf_networks(secgroups)
+            secgroups = [secgroup for secgroup in secgroups
+                         if secgroup['security_group_rules'][0]['project_id']
+                         not in CONF_PROJECTS]
         LOG.debug("List count, %s security_groups", len(secgroups))
         return secgroups
 
     def delete(self):
-        client = self.client
+        client = self.security_groups_client
         secgroups = self.list()
         for secgroup in secgroups:
             try:
-                client.delete_secgroup(secgroup['id'])
+                client.delete_security_group(secgroup['id'])
             except Exception:
-                LOG.exception("Delete security_group exception.")
+                LOG.exception("Delete security_group %s exception.",
+                              secgroup['id'])
 
     def dry_run(self):
         secgroups = self.list()
-        self.data['secgroups'] = secgroups
+        self.data['security_groups'] = secgroups
+
+    def save_state(self):
+        secgroups = self.list()
+        self.data['security_groups'] = {}
+        for secgroup in secgroups:
+            self.data['security_groups'][secgroup['id']] = secgroup['name']
 
 
-class NetworkSubnetService(NetworkService):
+class NetworkSubnetService(BaseNetworkService):
 
     def list(self):
         client = self.subnets_client
         subnets = client.list_subnets(**self.tenant_filter)
         subnets = subnets['subnets']
+        if not self.is_save_state:
+            # recreate list removing saved subnets
+            subnets = [subnet for subnet in subnets if subnet['id']
+                       not in self.saved_state_json['subnets'].keys()]
         if self.is_preserve:
             subnets = self._filter_by_conf_networks(subnets)
         LOG.debug("List count, %s Subnets", len(subnets))
@@ -699,12 +671,53 @@
             try:
                 client.delete_subnet(subnet['id'])
             except Exception:
-                LOG.exception("Delete Subnet exception.")
+                LOG.exception("Delete Subnet %s exception.", subnet['id'])
 
     def dry_run(self):
         subnets = self.list()
         self.data['subnets'] = subnets
 
+    def save_state(self):
+        subnets = self.list()
+        self.data['subnets'] = {}
+        for subnet in subnets:
+            self.data['subnets'][subnet['id']] = subnet['name']
+
+
+class NetworkSubnetPoolsService(BaseNetworkService):
+
+    def list(self):
+        client = self.subnetpools_client
+        pools = client.list_subnetpools(**self.tenant_filter)['subnetpools']
+        if not self.is_save_state:
+            # recreate list removing saved subnet pools
+            pools = [pool for pool in pools if pool['id']
+                     not in self.saved_state_json['subnetpools'].keys()]
+        if self.is_preserve:
+            pools = [pool for pool in pools if pool['project_id']
+                     not in CONF_PROJECTS]
+        LOG.debug("List count, %s Subnet Pools", len(pools))
+        return pools
+
+    def delete(self):
+        client = self.subnetpools_client
+        pools = self.list()
+        for pool in pools:
+            try:
+                client.delete_subnetpool(pool['id'])
+            except Exception:
+                LOG.exception("Delete Subnet Pool %s exception.", pool['id'])
+
+    def dry_run(self):
+        pools = self.list()
+        self.data['subnetpools'] = pools
+
+    def save_state(self):
+        pools = self.list()
+        self.data['subnetpools'] = {}
+        for pool in pools:
+            self.data['subnetpools'][pool['id']] = pool['name']
+
 
 # begin global services
 class FlavorService(BaseService):
@@ -733,7 +746,7 @@
             try:
                 client.delete_flavor(flavor['id'])
             except Exception:
-                LOG.exception("Delete Flavor exception.")
+                LOG.exception("Delete Flavor %s exception.", flavor['id'])
 
     def dry_run(self):
         flavors = self.list()
@@ -749,11 +762,11 @@
 class ImageService(BaseService):
     def __init__(self, manager, **kwargs):
         super(ImageService, self).__init__(kwargs)
-        self.client = manager.compute_images_client
+        self.client = manager.image_client_v2
 
     def list(self):
         client = self.client
-        images = client.list_images({"all_tenants": True})['images']
+        images = client.list_images(params={"all_tenants": True})['images']
         if not self.is_save_state:
             images = [image for image in images if image['id']
                       not in self.saved_state_json['images'].keys()]
@@ -770,7 +783,7 @@
             try:
                 client.delete_image(image['id'])
             except Exception:
-                LOG.exception("Delete Image exception.")
+                LOG.exception("Delete Image %s exception.", image['id'])
 
     def dry_run(self):
         images = self.list()
@@ -783,12 +796,6 @@
             self.data['images'][image['id']] = image['name']
 
 
-class IdentityService(BaseService):
-    def __init__(self, manager, **kwargs):
-        super(IdentityService, self).__init__(kwargs)
-        self.client = manager.identity_v3_client
-
-
 class UserService(BaseService):
 
     def __init__(self, manager, **kwargs):
@@ -819,7 +826,7 @@
             try:
                 self.client.delete_user(user['id'])
             except Exception:
-                LOG.exception("Delete User exception.")
+                LOG.exception("Delete User %s exception.", user['id'])
 
     def dry_run(self):
         users = self.list()
@@ -845,8 +852,8 @@
             if not self.is_save_state:
                 roles = [role for role in roles if
                          (role['id'] not in
-                          self.saved_state_json['roles'].keys()
-                          and role['name'] != CONF.identity.admin_role)]
+                          self.saved_state_json['roles'].keys() and
+                          role['name'] != CONF.identity.admin_role)]
                 LOG.debug("List count, %s Roles after reconcile", len(roles))
             return roles
         except Exception:
@@ -859,7 +866,7 @@
             try:
                 self.client.delete_role(role['id'])
             except Exception:
-                LOG.exception("Delete Role exception.")
+                LOG.exception("Delete Role %s exception.", role['id'])
 
     def dry_run(self):
         roles = self.list()
@@ -881,13 +888,16 @@
     def list(self):
         projects = self.client.list_projects()['projects']
         if not self.is_save_state:
-            projects = [project for project in projects if (project['id']
-                        not in self.saved_state_json['projects'].keys()
-                        and project['name'] != CONF.auth.admin_project_name)]
+            project_ids = self.saved_state_json['projects']
+            projects = [project
+                        for project in projects
+                        if (project['id'] not in project_ids and
+                            project['name'] != CONF.auth.admin_project_name)]
 
         if self.is_preserve:
-            projects = [project for project in projects if project['name']
-                        not in CONF_PROJECTS]
+            projects = [project
+                        for project in projects
+                        if project['name'] not in CONF_PROJECTS]
 
         LOG.debug("List count, %s Projects after reconcile", len(projects))
         return projects
@@ -898,7 +908,7 @@
             try:
                 self.client.delete_project(project['id'])
             except Exception:
-                LOG.exception("Delete project exception.")
+                LOG.exception("Delete project %s exception.", project['id'])
 
     def dry_run(self):
         projects = self.list()
@@ -935,7 +945,7 @@
                 client.update_domain(domain['id'], enabled=False)
                 client.delete_domain(domain['id'])
             except Exception:
-                LOG.exception("Delete Domain exception.")
+                LOG.exception("Delete Domain %s exception.", domain['id'])
 
     def dry_run(self):
         domains = self.list()
@@ -955,13 +965,8 @@
     if IS_NOVA:
         project_services.append(ServerService)
         project_services.append(KeyPairService)
-        project_services.append(SecurityGroupService)
         project_services.append(ServerGroupService)
-        if not IS_NEUTRON:
-            project_services.append(FloatingIpService)
         project_services.append(NovaQuotaService)
-    if IS_HEAT:
-        project_services.append(StackService)
     if IS_NEUTRON:
         project_services.append(NetworkFloatingIpService)
         if utils.is_extension_enabled('metering', 'network'):
@@ -972,6 +977,7 @@
         project_services.append(NetworkSubnetService)
         project_services.append(NetworkService)
         project_services.append(NetworkSecGroupService)
+        project_services.append(NetworkSubnetPoolsService)
     if IS_CINDER:
         project_services.append(SnapshotService)
         project_services.append(VolumeService)
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 7634d9e..d84f3a3 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -20,19 +20,15 @@
 from oslo_config import generator
 from oslo_log import log as logging
 from six import moves
-from testrepository import commands
+from stestr import commands
 
 from tempest.cmd import workspace
 
 LOG = logging.getLogger(__name__)
 
-TESTR_CONF = """[DEFAULT]
-test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \\
-    OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \\
-    OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \\
-    ${PYTHON:-python} -m subunit.run discover -t %s %s $LISTOPT $IDOPTION
-test_id_option=--load-list $IDFILE
-test_list_option=--list
+STESTR_CONF = r"""[DEFAULT]
+test_path=%s
+top_dir=%s
 group_regex=([^\.]*\.)*
 """
 
@@ -84,13 +80,13 @@
                                  "is ~/.tempest/workspace.yaml")
         return parser
 
-    def generate_testr_conf(self, local_path):
-        testr_conf_path = os.path.join(local_path, '.testr.conf')
+    def generate_stestr_conf(self, local_path):
+        stestr_conf_path = os.path.join(local_path, '.stestr.conf')
         top_level_path = os.path.dirname(os.path.dirname(__file__))
         discover_path = os.path.join(top_level_path, 'test_discover')
-        testr_conf = TESTR_CONF % (top_level_path, discover_path)
-        with open(testr_conf_path, 'w+') as testr_conf_file:
-            testr_conf_file.write(testr_conf)
+        stestr_conf = STESTR_CONF % (discover_path, top_level_path)
+        with open(stestr_conf_path, 'w+') as stestr_conf_file:
+            stestr_conf_file.write(stestr_conf)
 
     def get_configparser(self, conf_path):
         config_parse = moves.configparser.ConfigParser()
@@ -140,7 +136,7 @@
         if not os.path.isdir(local_dir):
             LOG.debug('Creating local working dir: %s', local_dir)
             os.mkdir(local_dir)
-        elif not os.listdir(local_dir) == []:
+        elif os.listdir(local_dir):
             raise OSError("Directory you are trying to initialize already "
                           "exists and is not empty: %s" % local_dir)
 
@@ -148,7 +144,7 @@
         etc_dir = os.path.join(local_dir, 'etc')
         config_path = os.path.join(etc_dir, 'tempest.conf')
         log_dir = os.path.join(local_dir, 'logs')
-        testr_dir = os.path.join(local_dir, '.testrepository')
+        stestr_dir = os.path.join(local_dir, '.stestr')
         # Create lock dir
         if not os.path.isdir(lock_dir):
             LOG.debug('Creating lock dir: %s', lock_dir)
@@ -163,12 +159,11 @@
         self.generate_sample_config(local_dir)
         # Update local confs to reflect local paths
         self.update_local_conf(config_path, lock_dir, log_dir)
-        # Generate a testr conf file
-        self.generate_testr_conf(local_dir)
-        # setup local testr working dir
-        if not os.path.isdir(testr_dir):
-            commands.run_argv(['testr', 'init', '-d', local_dir], sys.stdin,
-                              sys.stdout, sys.stderr)
+        # Generate a stestr conf file
+        self.generate_stestr_conf(local_dir)
+        # setup local stestr working dir
+        if not os.path.isdir(stestr_dir):
+            commands.init_command(repo_url=local_dir)
 
     def take_action(self, parsed_args):
         workspace_manager = workspace.WorkspaceManager(
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index f07f197..77d4496 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -19,11 +19,13 @@
 ==============
 Tempest run has several options:
 
- * **--regex/-r**: This is a selection regex like what testr uses. It will run
+ * **--regex/-r**: This is a selection regex like what stestr uses. It will run
                    any tests that match on re.match() with the regex
  * **--smoke/-s**: Run all the tests tagged as smoke
+ * **--black-regex**: It allows to do simple test exclusion via passing a
+                      rejection/black regexp
 
-There are also the **--blacklist-file** and **--whitelist-file** options that
+There are also the ``--blacklist-file`` and ``--whitelist-file`` options that
 let you pass a filepath to tempest run with the file format being a line
 separated regex, with '#' used to signify the start of a comment on a line.
 For example::
@@ -32,33 +34,25 @@
     ^regex1 # Match these tests
     .*regex2 # Match those tests
 
-The blacklist file will be used to construct a negative lookahead regex and
-the whitelist file will simply OR all the regexes in the file. The whitelist
-and blacklist file options are mutually exclusive so you can't use them
-together. However, you can combine either with a normal regex or the *--smoke*
-flag. When used with a blacklist file the generated regex will be combined to
-something like::
+These arguments are just passed into stestr, you can refer to the stestr
+selection docs for more details on how these operate:
+http://stestr.readthedocs.io/en/latest/MANUAL.html#test-selection
 
-    ^((?!black_regex1|black_regex2).)*$cli_regex1
-
-When combined with a whitelist file all the regexes from the file and the CLI
-regexes will be ORed.
-
-You can also use the **--list-tests** option in conjunction with selection
+You can also use the ``--list-tests`` option in conjunction with selection
 arguments to list which tests will be run.
 
-You can also use the **--load-list** option that lets you pass a filepath to
+You can also use the ``--load-list`` option that lets you pass a filepath to
 tempest run with the file format being in a non-regex format, similar to the
-tests generated by the **--list-tests** option. You can specify target tests
+tests generated by the ``--list-tests`` option. You can specify target tests
 by removing unnecessary tests from a list file which is generated from
-**--list-tests** option.
+``--list-tests`` option.
 
 Test Execution
 ==============
 There are several options to control how the tests are executed. By default
 tempest will run in parallel with a worker for each CPU present on the machine.
-If you want to adjust the number of workers use the **--concurrency** option
-and if you want to run tests serially use **--serial/-t**
+If you want to adjust the number of workers use the ``--concurrency`` option
+and if you want to run tests serially use ``--serial/-t``
 
 Running with Workspaces
 -----------------------
@@ -74,15 +68,15 @@
 ---------------------
 Tempest run provides you with an option to execute tempest from anywhere on
 your system. You are required to provide a config file in this case with the
-``--config-file`` option. When run tempest will create a .testrepository
-directory and a .testr.conf file in your current working directory. This way
-you can use testr commands directly to inspect the state of the previous run.
+``--config-file`` option. When run tempest will create a .stestr
+directory and a .stestr.conf file in your current working directory. This way
+you can use stestr commands directly to inspect the state of the previous run.
 
 Test Output
 ===========
 By default tempest run's output to STDOUT will be generated using the
 subunit-trace output filter. But, if you would prefer a subunit v2 stream be
-output to STDOUT use the **--subunit** flag
+output to STDOUT use the ``--subunit`` flag
 
 Combining Runs
 ==============
@@ -90,22 +84,17 @@
 There are certain situations in which you want to split a single run of tempest
 across 2 executions of tempest run. (for example to run part of the tests
 serially and others in parallel) To accomplish this but still treat the results
-as a single run you can leverage the **--combine** option which will append
+as a single run you can leverage the ``--combine`` option which will append
 the current run's results with the previous runs.
 """
 
-import io
 import os
 import sys
-import tempfile
-import threading
 
 from cliff import command
-from os_testr import regex_builder
-from os_testr import subunit_trace
 from oslo_serialization import jsonutils as json
 import six
-from testrepository.commands import run_argv
+from stestr import commands
 
 from tempest import clients
 from tempest.cmd import cleanup_service
@@ -114,6 +103,9 @@
 from tempest.common import credentials_factory as credentials
 from tempest import config
 
+if six.PY2:
+    # Python 2 has not FileNotFoundError exception
+    FileNotFoundError = IOError
 
 CONF = config.CONF
 SAVED_STATE_JSON = "saved_state.json"
@@ -123,36 +115,33 @@
 
     def _set_env(self, config_file=None):
         if config_file:
-            CONF.set_config_path(os.path.abspath(config_file))
-        # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
+            if os.path.exists(os.path.abspath(config_file)):
+                CONF.set_config_path(os.path.abspath(config_file))
+            else:
+                raise FileNotFoundError(
+                    "Config file: %s doesn't exist" % config_file)
+
+        # NOTE(mtreinish): This is needed so that stestr doesn't gobble up any
         # stacktraces on failure.
         if 'TESTR_PDB' in os.environ:
             return
         else:
             os.environ["TESTR_PDB"] = ""
-        # NOTE(dims): most of our .testr.conf try to test for PYTHON
+        # NOTE(dims): most of our .stestr.conf try to test for PYTHON
         # environment variable and fall back to "python", under python3
         # if it does not exist. we should set it to the python3 executable
         # to deal with this situation better for now.
         if six.PY3 and 'PYTHON' not in os.environ:
             os.environ['PYTHON'] = sys.executable
 
-    def _create_testrepository(self):
-        if not os.path.isdir('.testrepository'):
-            returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
-                                  sys.stderr)
-            if returncode:
-                sys.exit(returncode)
-
-    def _create_testr_conf(self):
+    def _create_stestr_conf(self):
         top_level_path = os.path.dirname(os.path.dirname(__file__))
         discover_path = os.path.join(top_level_path, 'test_discover')
-        file_contents = init.TESTR_CONF % (top_level_path, discover_path)
-        with open('.testr.conf', 'w+') as testr_conf_file:
-                testr_conf_file.write(file_contents)
+        file_contents = init.STESTR_CONF % (discover_path, top_level_path)
+        with open('.stestr.conf', 'w+') as stestr_conf_file:
+            stestr_conf_file.write(file_contents)
 
     def take_action(self, parsed_args):
-        returncode = 0
         if parsed_args.config_file:
             self._set_env(parsed_args.config_file)
         else:
@@ -169,52 +158,37 @@
                     "register the workspace." %
                     (parsed_args.workspace, workspace_mgr.path))
             os.chdir(path)
-            # NOTE(mtreinish): tempest init should create a .testrepository dir
-            # but since workspaces can be imported let's sanity check and
-            # ensure that one is created
-            self._create_testrepository()
-        # Local execution mode
-        elif os.path.isfile('.testr.conf'):
-            # If you're running in local execution mode and there is not a
-            # testrepository dir create one
-            self._create_testrepository()
+            if not os.path.isfile('.stestr.conf'):
+                self._create_stestr_conf()
         # local execution with config file mode
-        elif parsed_args.config_file:
-            self._create_testr_conf()
-            self._create_testrepository()
-        else:
-            print("No .testr.conf file was found for local execution")
+        elif parsed_args.config_file and not os.path.isfile('.stestr.conf'):
+            self._create_stestr_conf()
+        elif not os.path.isfile('.stestr.conf'):
+            print("No .stestr.conf file was found for local execution")
             sys.exit(2)
         if parsed_args.state:
             self._init_state()
-        else:
-            pass
-
-        if parsed_args.combine:
-            temp_stream = tempfile.NamedTemporaryFile()
-            return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
-                                   temp_stream, sys.stderr)
-            if return_code > 0:
-                sys.exit(return_code)
 
         regex = self._build_regex(parsed_args)
+        return_code = 0
         if parsed_args.list_tests:
-            argv = ['tempest', 'list-tests', regex]
-            returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
-        else:
-            options = self._build_options(parsed_args)
-            returncode = self._run(regex, options)
-            if returncode > 0:
-                sys.exit(returncode)
+            return_code = commands.list_command(
+                filters=regex, whitelist_file=parsed_args.whitelist_file,
+                blacklist_file=parsed_args.blacklist_file,
+                black_regex=parsed_args.black_regex)
 
-        if parsed_args.combine:
-            return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
-                                   temp_stream, sys.stderr)
+        else:
+            serial = not parsed_args.parallel
+            return_code = commands.run_command(
+                filters=regex, subunit_out=parsed_args.subunit,
+                serial=serial, concurrency=parsed_args.concurrency,
+                blacklist_file=parsed_args.blacklist_file,
+                whitelist_file=parsed_args.whitelist_file,
+                black_regex=parsed_args.black_regex,
+                load_list=parsed_args.load_list, combine=parsed_args.combine)
             if return_code > 0:
                 sys.exit(return_code)
-            returncode = run_argv(['tempest', 'load', temp_stream.name],
-                                  sys.stdin, sys.stdout, sys.stderr)
-        sys.exit(returncode)
+        return return_code
 
     def get_description(self):
         return 'Run tempest'
@@ -236,8 +210,8 @@
             svc.run()
 
         with open(SAVED_STATE_JSON, 'w+') as f:
-            f.write(json.dumps(data,
-                    sort_keys=True, indent=2, separators=(',', ': ')))
+            f.write(json.dumps(data, sort_keys=True,
+                               indent=2, separators=(',', ': ')))
 
     def get_parser(self, prog_name):
         parser = super(TempestRun, self).get_parser(prog_name)
@@ -262,29 +236,31 @@
         regex.add_argument('--smoke', '-s', action='store_true',
                            help="Run the smoke tests only")
         regex.add_argument('--regex', '-r', default='',
-                           help='A normal testr selection regex used to '
+                           help='A normal stestr selection regex used to '
                                 'specify a subset of tests to run')
-        list_selector = parser.add_mutually_exclusive_group()
-        list_selector.add_argument('--whitelist-file', '--whitelist_file',
-                                   help="Path to a whitelist file, this file "
-                                        "contains a separate regex on each "
-                                        "newline.")
-        list_selector.add_argument('--blacklist-file', '--blacklist_file',
-                                   help='Path to a blacklist file, this file '
-                                        'contains a separate regex exclude on '
-                                        'each newline')
-        list_selector.add_argument('--load-list', '--load_list',
-                                   help='Path to a non-regex whitelist file, '
-                                        'this file contains a seperate test '
-                                        'on each newline. This command'
-                                        'supports files created by the tempest'
-                                        'run ``--list-tests`` command')
+        parser.add_argument('--black-regex', dest='black_regex',
+                            help='A regex to exclude tests that match it')
+        parser.add_argument('--whitelist-file', '--whitelist_file',
+                            help="Path to a whitelist file, this file "
+                            "contains a separate regex on each "
+                            "newline.")
+        parser.add_argument('--blacklist-file', '--blacklist_file',
+                            help='Path to a blacklist file, this file '
+                                 'contains a separate regex exclude on '
+                                 'each newline')
+        parser.add_argument('--load-list', '--load_list',
+                            help='Path to a non-regex whitelist file, '
+                                 'this file contains a separate test '
+                                 'on each newline. This command '
+                                 'supports files created by the tempest '
+                                 'run ``--list-tests`` command')
         # list only args
         parser.add_argument('--list-tests', '-l', action='store_true',
                             help='List tests',
                             default=False)
         # execution args
         parser.add_argument('--concurrency', '-w',
+                            type=int, default=0,
                             help="The number of workers to use, defaults to "
                                  "the number of cpus")
         parallel = parser.add_mutually_exclusive_group()
@@ -305,62 +281,15 @@
         parser.add_argument("--combine", action='store_true',
                             help='Combine the output of this run with the '
                                  "previous run's as a combined stream in the "
-                                 "testr repository after it finish")
+                                 "stestr repository after it finish")
 
         parser.set_defaults(parallel=True)
         return parser
 
     def _build_regex(self, parsed_args):
-        regex = ''
+        regex = None
         if parsed_args.smoke:
-            regex = 'smoke'
+            regex = ['smoke']
         elif parsed_args.regex:
-            regex = parsed_args.regex
-        if parsed_args.whitelist_file or parsed_args.blacklist_file:
-            regex = regex_builder.construct_regex(parsed_args.blacklist_file,
-                                                  parsed_args.whitelist_file,
-                                                  regex, False)
+            regex = parsed_args.regex.split()
         return regex
-
-    def _build_options(self, parsed_args):
-        options = []
-        if parsed_args.subunit:
-            options.append("--subunit")
-        if parsed_args.parallel:
-            options.append("--parallel")
-        if parsed_args.concurrency:
-            options.append("--concurrency=%s" % parsed_args.concurrency)
-        if parsed_args.load_list:
-            options.append("--load-list=%s" % parsed_args.load_list)
-        return options
-
-    def _run(self, regex, options):
-        returncode = 0
-        argv = ['tempest', 'run', regex] + options
-        if '--subunit' in options:
-            returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
-        else:
-            argv.append('--subunit')
-            stdin = io.StringIO()
-            stdout_r, stdout_w = os.pipe()
-            subunit_w = os.fdopen(stdout_w, 'wt')
-            subunit_r = os.fdopen(stdout_r)
-            returncodes = {}
-
-            def run_argv_thread():
-                returncodes['testr'] = run_argv(argv, stdin, subunit_w,
-                                                sys.stderr)
-                subunit_w.close()
-
-            run_thread = threading.Thread(target=run_argv_thread)
-            run_thread.start()
-            returncodes['subunit-trace'] = subunit_trace.trace(
-                subunit_r, sys.stdout, post_fails=True, print_failures=True)
-            run_thread.join()
-            subunit_r.close()
-            # python version of pipefail
-            if returncodes['testr']:
-                returncode = returncodes['testr']
-            elif returncodes['subunit-trace']:
-                returncode = returncodes['subunit-trace']
-        return returncode
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index f9ebe20..081fa7a 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -21,17 +21,17 @@
 Runtime Arguments
 -----------------
 
-**--subunit, -s**: (Optional) The path to the subunit file being parsed,
-defaults to stdin
+* ``--subunit, -s``: (Optional) The path to the subunit file being parsed,
+  defaults to stdin
+* ``--non-subunit-name, -n``: (Optional) The file_name that the logs are being
+  stored in
+* ``--output-file, -o``: (Optional) The path where the JSON output will be
+  written to. This contains more information than is present in stdout.
+* ``--ports, -p``: (Optional) The path to a JSON file describing the ports
+  being used by different services
+* ``--verbose, -v``: (Optional) Print Request and Response Headers and Body
+  data to stdout
 
-**--non-subunit-name, -n**: (Optional) The file_name that the logs are being
-stored in
-
-**--output-file, -o**: (Optional) The path where the JSON output will be
-written to. This contains more information than is present in stdout.
-
-**--ports, -p**: (Optional) The path to a JSON file describing the ports being
-used by different services
 
 Usage
 -----
@@ -78,11 +78,11 @@
 import argparse
 import collections
 import io
-import json
 import os
 import re
 import sys
 
+from oslo_serialization import jsonutils as json
 import subunit
 import testtools
 
@@ -95,7 +95,7 @@
     ip_re = re.compile(r'(^|[^0-9])[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]'
                        '{1,3}([^0-9]|$)')
     url_re = re.compile(r'.*INFO.*Request \((?P<name>.*)\): (?P<code>[\d]{3}) '
-                        '(?P<verb>\w*) (?P<url>.*) .*')
+                        r'(?P<verb>\w*) (?P<url>.*) .*')
     port_re = re.compile(r'.*:(?P<port>\d+).*')
     path_re = re.compile(r'http[s]?://[^/]*/(?P<path>.*)')
     request_re = re.compile(r'.* Request - Headers: (?P<headers>.*)')
@@ -265,6 +265,10 @@
             "-p", "--ports", metavar="<ports file>", default=None,
             help="A JSON file describing the ports for each service.")
 
+        self.add_argument(
+            "-v", "--verbose", action='store_true', default=False,
+            help="Add Request and Response header and body data to stdout.")
+
 
 def parse(stream, non_subunit_name, ports):
     if ports is not None and os.path.exists(ports):
@@ -289,7 +293,7 @@
     return url_parser
 
 
-def output(url_parser, output_file):
+def output(url_parser, output_file, verbose):
     if output_file is not None:
         with open(output_file, "w") as outfile:
             outfile.write(json.dumps(url_parser.test_logs))
@@ -305,13 +309,22 @@
             sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format(
                 item.get('status_code'), item.get('verb'),
                 item.get('service'), item.get('url')))
+            if verbose:
+                sys.stdout.write('\t\t- request headers: {0}\n'.format(
+                    item.get('request_headers')))
+                sys.stdout.write('\t\t- request body: {0}\n'.format(
+                    item.get('request_body')))
+                sys.stdout.write('\t\t- response headers: {0}\n'.format(
+                    item.get('response_headers')))
+                sys.stdout.write('\t\t- response body: {0}\n'.format(
+                    item.get('response_body')))
         sys.stdout.write('\n')
 
 
 def entry_point():
     cl_args = ArgumentParser().parse_args()
     parser = parse(cl_args.subunit, cl_args.non_subunit_name, cl_args.ports)
-    output(parser, cl_args.output_file)
+    output(parser, cl_args.output_file, cl_args.verbose)
 
 
 if __name__ == "__main__":
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index fdf28d5..d25d3ca 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -205,10 +205,6 @@
 def verify_cinder_api_versions(os, update):
     # Check cinder api versions
     versions = _get_api_versions(os, 'cinder')
-    if (CONF.volume_feature_enabled.api_v1 !=
-            contains_version('v1.', versions)):
-        print_and_or_update('api_v1', 'volume-feature-enabled',
-                            not CONF.volume_feature_enabled.api_v1, update)
     if (CONF.volume_feature_enabled.api_v2 !=
             contains_version('v2.', versions)):
         print_and_or_update('api_v2', 'volume-feature-enabled',
@@ -283,6 +279,9 @@
     if not results.get(service):
         results[service] = {}
     extensions_opt = get_enabled_extensions(service)
+    if not extensions_opt:
+        LOG.info("'%s' has no api_extensions set.", service)
+        return results
     if extensions_opt[0] == 'all':
         results[service]['extensions'] = extensions
         return results
@@ -349,7 +348,6 @@
         'image': 'glance',
         'object_storage': 'swift',
         'compute': 'nova',
-        'orchestration': 'heat',
         'baremetal': 'ironic',
         'identity': 'keystone',
     }
@@ -367,11 +365,11 @@
         catalog_type = getattr(cfg, 'catalog_type', None)
         if not catalog_type:
             continue
-        else:
-            if cfgname == 'identity':
-                # Keystone is a required service for tempest
-                continue
-            if catalog_type not in services:
+        if cfgname == 'identity':
+            # Keystone is a required service for tempest
+            continue
+        if catalog_type not in services:
+            try:
                 if getattr(CONF.service_available, codename_match[cfgname]):
                     print('Endpoint type %s not found either disable service '
                           '%s or fix the catalog_type in the config file' % (
@@ -379,7 +377,13 @@
                     if update:
                         change_option(codename_match[cfgname],
                                       'service_available', False)
-            else:
+            except KeyError:
+                print('%s is a third party plugin, cannot be verified '
+                      'automatically, but it is suggested that it is set to '
+                      'False because %s service is not available ' % (
+                          cfgname, catalog_type))
+        else:
+            try:
                 if not getattr(CONF.service_available,
                                codename_match[cfgname]):
                     print('Endpoint type %s is available, service %s should be'
@@ -393,6 +397,11 @@
                         avail_services.append(codename_match[cfgname])
                 else:
                     avail_services.append(codename_match[cfgname])
+            except KeyError:
+                print('%s is a third party plugin, cannot be verified '
+                      'automatically, but it is suggested that it is set to '
+                      'True because %s service is available ' % (
+                          cfgname, catalog_type))
     return avail_services
 
 
@@ -489,5 +498,6 @@
             traceback.print_exc()
             raise
 
+
 if __name__ == "__main__":
     main()
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index 8166b4f..d0c4b28 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -26,28 +26,28 @@
 
 register
 --------
-Registers a new tempest workspace via a given --name and --path
+Registers a new tempest workspace via a given ``--name`` and ``--path``
 
 rename
 ------
-Renames a tempest workspace from --old-name to --new-name
+Renames a tempest workspace from ``--old-name`` to ``--new-name``
 
 move
 ----
-Changes the path of a given tempest workspace --name to --path
+Changes the path of a given tempest workspace ``--name`` to ``--path``
 
 remove
 ------
-Deletes the entry for a given tempest workspace --name
+Deletes the entry for a given tempest workspace ``--name``
 
---rmdir Deletes the given tempest workspace directory
+``--rmdir`` Deletes the given tempest workspace directory
 
 General Options
 ===============
 
- **--workspace_path**: Allows the user to specify a different location for the
-                       workspace.yaml file containing the workspace definitions
-                       instead of ~/.tempest/workspace.yaml
+* ``--workspace_path``: Allows the user to specify a different location for the
+  workspace.yaml file containing the workspace definitions instead of
+  ``~/.tempest/workspace.yaml``
 """
 
 import os
@@ -86,6 +86,7 @@
     def rename_workspace(self, old_name, new_name):
         self._populate()
         self._name_exists(old_name)
+        self._invalid_name_check(new_name)
         self._workspace_name_exists(new_name)
         self.workspaces[new_name] = self.workspaces.pop(old_name)
         self._write_file()
@@ -93,7 +94,7 @@
     @lockutils.synchronized('workspaces', external=True)
     def move_workspace(self, name, path):
         self._populate()
-        path = os.path.abspath(os.path.expanduser(path))
+        path = os.path.abspath(os.path.expanduser(path)) if path else path
         self._name_exists(name)
         self._validate_path(path)
         self.workspaces[name] = path
@@ -114,6 +115,7 @@
 
     @lockutils.synchronized('workspaces', external=True)
     def remove_workspace_directory(self, workspace_path):
+        self._validate_path(workspace_path)
         shutil.rmtree(workspace_path)
 
     @lockutils.synchronized('workspaces', external=True)
@@ -128,7 +130,17 @@
                 name))
             sys.exit(1)
 
+    def _invalid_name_check(self, name):
+        if not name:
+            print("None or empty name is specified."
+                  " Please specify correct name for workspace.")
+            sys.exit(1)
+
     def _validate_path(self, path):
+        if not path:
+            print("None or empty path is specified for workspace."
+                  " Please specify correct workspace path.")
+            sys.exit(1)
         if not os.path.exists(path):
             print("Path does not exist.")
             sys.exit(1)
@@ -137,10 +149,11 @@
     def register_new_workspace(self, name, path, init=False):
         """Adds the new workspace and writes out the new workspace config"""
         self._populate()
-        path = os.path.abspath(os.path.expanduser(path))
+        path = os.path.abspath(os.path.expanduser(path)) if path else path
         # This only happens when register is called from outside of init
         if not init:
             self._validate_path(path)
+        self._invalid_name_check(name)
         self._workspace_name_exists(name)
         self.workspaces[name] = path
         self._write_file()
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 86fe3f5..1489e60 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -44,15 +44,14 @@
 def is_scheduler_filter_enabled(filter_name):
     """Check the list of enabled compute scheduler filters from config.
 
-    This function checks whether the given compute scheduler filter is
-    available and configured in the config file. If the
-    scheduler_available_filters option is set to 'all' (Default value. which
-    means default filters are configured in nova) in tempest.conf then, this
-    function returns True with assumption that requested filter 'filter_name'
-    is one of available filter in nova ("nova.scheduler.filters.all_filters").
+    This function checks whether the given compute scheduler filter is enabled
+    in the nova config file. If the scheduler_enabled_filters option is set to
+    'all' in tempest.conf then, this function returns True with assumption that
+    requested filter 'filter_name' is one of the enabled filters in nova
+    ("nova.scheduler.filters.all_filters").
     """
 
-    filters = CONF.compute_feature_enabled.scheduler_available_filters
+    filters = CONF.compute_feature_enabled.scheduler_enabled_filters
     if not filters:
         return False
     if 'all' in filters:
@@ -79,23 +78,22 @@
     :param wait_until: Server status to wait for the server to reach after
         its creation.
     :param volume_backed: Whether the server is volume backed or not.
-                          If this is true, a volume will be created and
-                          create server will be requested with
-                          'block_device_mapping_v2' populated with below
-                          values:
-                          --------------------------------------------
-                          bd_map_v2 = [{
-                              'uuid': volume['volume']['id'],
-                              'source_type': 'volume',
-                              'destination_type': 'volume',
-                              'boot_index': 0,
-                              'delete_on_termination': True}]
-                          kwargs['block_device_mapping_v2'] = bd_map_v2
-                          ---------------------------------------------
-                          If server needs to be booted from volume with other
-                          combination of bdm inputs than mentioned above, then
-                          pass the bdm inputs explicitly as kwargs and image_id
-                          as empty string ('').
+        If this is true, a volume will be created and create server will be
+        requested with 'block_device_mapping_v2' populated with below values:
+
+        .. code-block:: python
+
+            bd_map_v2 = [{
+                'uuid': volume['volume']['id'],
+                'source_type': 'volume',
+                'destination_type': 'volume',
+                'boot_index': 0,
+                'delete_on_termination': True}]
+            kwargs['block_device_mapping_v2'] = bd_map_v2
+
+        If server needs to be booted from volume with other combination of bdm
+        inputs than mentioned above, then pass the bdm inputs explicitly as
+        kwargs and image_id as empty string ('').
     :param name: Name of the server to be provisioned. If not defined a random
         string ending with '-instance' will be generated.
     :param flavor: Flavor of the server to be provisioned. If not defined,
@@ -165,15 +163,24 @@
 
     if volume_backed:
         volume_name = data_utils.rand_name(__name__ + '-volume')
-        volumes_client = clients.volumes_v2_client
+        volumes_client = clients.volumes_client_latest
         params = {'name': volume_name,
                   'imageRef': image_id,
                   'size': CONF.volume.volume_size}
         volume = volumes_client.create_volume(**params)
-        waiters.wait_for_volume_resource_status(volumes_client,
-                                                volume['volume']['id'],
-                                                'available')
-
+        try:
+            waiters.wait_for_volume_resource_status(volumes_client,
+                                                    volume['volume']['id'],
+                                                    'available')
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                try:
+                    volumes_client.delete_volume(volume['volume']['id'])
+                    volumes_client.wait_for_resource_deletion(
+                        volume['volume']['id'])
+                except Exception as exc:
+                    LOG.exception("Deleting volume %s failed, exception %s",
+                                  volume['volume']['id'], exc)
         bd_map_v2 = [{
             'uuid': volume['volume']['id'],
             'source_type': 'volume',
@@ -229,7 +236,7 @@
                     clients.servers_client, server['id'], wait_until)
 
                 # Multiple validatable servers are not supported for now. Their
-                # creation will fail with the condition above (l.58).
+                # creation will fail with the condition above.
                 if CONF.validation.run_validation and validatable:
                     if CONF.validation.connect_method == 'floating':
                         _setup_validation_fip()
@@ -289,13 +296,26 @@
 
 def create_websocket(url):
     url = urlparse.urlparse(url)
-    if url.scheme == 'https':
-        client_socket = ssl.wrap_socket(socket.socket(socket.AF_INET,
-                                                      socket.SOCK_STREAM))
+
+    # NOTE(mnaser): It is possible that there is no port specified, so fall
+    #               back to the default port based on the scheme.
+    port = url.port or (443 if url.scheme == 'https' else 80)
+
+    for res in socket.getaddrinfo(url.hostname, port,
+                                  socket.AF_UNSPEC, socket.SOCK_STREAM):
+        af, socktype, proto, _, sa = res
+        client_socket = socket.socket(af, socktype, proto)
+        if url.scheme == 'https':
+            client_socket = ssl.wrap_socket(client_socket)
+        client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        try:
+            client_socket.connect(sa)
+        except socket.error:
+            client_socket.close()
+            continue
+        break
     else:
-        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-    client_socket.connect((url.hostname, url.port))
+        raise socket.error('WebSocket creation failed')
     # Turn the Socket into a WebSocket to do the communication
     return _WebSocket(client_socket, url)
 
@@ -374,7 +394,12 @@
         """Upgrade the HTTP connection to a WebSocket and verify."""
         # The real request goes to the /websockify URI always
         reqdata = 'GET /websockify HTTP/1.1\r\n'
-        reqdata += 'Host: %s:%s\r\n' % (url.hostname, url.port)
+        reqdata += 'Host: %s' % url.hostname
+        # Add port only if we have one specified
+        if url.port:
+            reqdata += ':%s' % url.port
+        # Line-ending for Host header
+        reqdata += '\r\n'
         # Tell the HTTP Server to Upgrade the connection to a WebSocket
         reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
         # The token=xxx is sent as a Cookie not in the URI
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index da34975..c6e5dcb 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -86,7 +86,7 @@
         ('public_network_id', CONF.network.public_network_id),
         ('create_networks', (CONF.auth.create_isolated_networks and not
                              CONF.network.shared_physical_network)),
-        ('resource_prefix', CONF.resources_prefix),
+        ('resource_prefix', 'tempest'),
         ('identity_admin_endpoint_type', endpoint_type)
     ]))
 
@@ -210,6 +210,7 @@
     except exceptions.InvalidConfiguration:
         return False
 
+
 # === Credentials
 
 # Type of credentials available from configuration
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index ed11b21..c702d88 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -225,9 +225,9 @@
             elif key in ('content-type', 'date', 'last-modified',
                          'x-copied-from-last-modified') and not value:
                 return InvalidFormat(key, value)
-            elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value):
+            elif key == 'x-timestamp' and not re.match(r"^\d+\.?\d*\Z", value):
                 return InvalidFormat(key, value)
-            elif key == 'x-copied-from' and not re.match("\S+/\S+", value):
+            elif key == 'x-copied-from' and not re.match(r"\S+/\S+", value):
                 return InvalidFormat(key, value)
             elif key == 'x-trans-id' and \
                 not re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", value):
diff --git a/tempest/common/identity.py b/tempest/common/identity.py
index eaf651b..cd6d058 100644
--- a/tempest/common/identity.py
+++ b/tempest/common/identity.py
@@ -26,7 +26,7 @@
         if project['name'] == project_name:
             return project
     raise lib_exc.NotFound('No such project(%s) in %s' % (project_name,
-                           projects))
+                                                          projects))
 
 
 def get_tenant_by_name(client, tenant_name):
@@ -64,7 +64,8 @@
     should not be used for testing identity features.
 
     :param clients: a client manager.
-    :return
+    :return: v2 or v3 of CredsClient
+    :rtype: V2CredsClient or V3CredsClient
     """
     if CONF.identity.auth_version == 'v2':
         client = clients.identity_client
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 5a86caa..167bf5b 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -31,16 +31,16 @@
 
         if attr == 'rand_name':
             # NOTE(flwang): This is a proxy to generate a random name that
-            # includes a random number and a prefix if one is configured in
-            # CONF.resources_prefix
+            # includes a random number and a prefix 'tempest'
             attr_obj = partial(lib_data_utils.rand_name,
-                               prefix=CONF.resources_prefix)
+                               prefix='tempest')
         else:
             attr_obj = getattr(lib_data_utils, attr)
 
         self.__dict__[attr] = attr_obj
         return attr_obj
 
+
 data_utils = DataUtils()
 
 
@@ -78,7 +78,7 @@
         decorators.attr(type=list(args))(f)
 
         @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
+        def wrapper(*func_args, **func_kwargs):
             service_list = get_service_list()
 
             for service in args:
@@ -86,7 +86,7 @@
                     msg = 'Skipped because the %s service is not available' % (
                         service)
                     raise testtools.TestCase.skipException(msg)
-            return f(self, *func_args, **func_kwargs)
+            return f(*func_args, **func_kwargs)
         return wrapper
     return decorator
 
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index e9fcd4b..d76a323 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -98,6 +98,7 @@
     def get_nic_name_by_ip(self, address):
         cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
         nic = self.exec_command(cmd)
+        LOG.debug('(get_nic_name_by_ip) Command result: %s', nic)
         return nic.strip().strip(":").split('@')[0].lower()
 
     def get_dns_servers(self):
diff --git a/tempest/common/utils/net_utils.py b/tempest/common/utils/net_utils.py
index 867b3dd..b697ef1 100644
--- a/tempest/common/utils/net_utils.py
+++ b/tempest/common/utils/net_utils.py
@@ -19,7 +19,6 @@
 
 def get_unused_ip_addresses(ports_client, subnets_client,
                             network_id, subnet_id, count):
-
     """Return a list with the specified number of unused IP addresses
 
     This method uses the given ports_client to find the specified number of
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 10afee0..77ec0f8 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -104,8 +104,8 @@
         body = client.show_server(server_id)['server']
     except lib_exc.NotFound:
         return
-    old_status = server_status = body['status']
-    old_task_state = task_state = _get_task_state(body)
+    old_status = body['status']
+    old_task_state = _get_task_state(body)
     start_time = int(time.time())
     while True:
         time.sleep(client.build_interval)
@@ -179,15 +179,13 @@
     raise lib_exc.TimeoutException(message)
 
 
-def wait_for_volume_resource_status(client, resource_id, statuses):
-    """Waits for a volume resource to reach any of the specified statuses.
+def wait_for_volume_resource_status(client, resource_id, status):
+    """Waits for a volume resource to reach a given status.
 
     This function is a common function for volume, snapshot and backup
     resources. The function extracts the name of the desired resource from
     the client class name of the resource.
     """
-    if not isinstance(statuses, list):
-        statuses = [statuses]
     resource_name = re.findall(
         r'(volume|group-snapshot|snapshot|backup|group)',
         client.resource_type)[-1].replace('-', '_')
@@ -195,11 +193,11 @@
     resource_status = show_resource(resource_id)[resource_name]['status']
     start = int(time.time())
 
-    while resource_status not in statuses:
+    while resource_status != status:
         time.sleep(client.build_interval)
         resource_status = show_resource(resource_id)[
             '{}'.format(resource_name)]['status']
-        if resource_status == 'error' and resource_status not in statuses:
+        if resource_status == 'error' and resource_status != status:
             raise exceptions.VolumeResourceBuildErrorException(
                 resource_name=resource_name, resource_id=resource_id)
         if resource_name == 'volume' and resource_status == 'error_restoring':
@@ -208,11 +206,36 @@
         if int(time.time()) - start >= client.build_timeout:
             message = ('%s %s failed to reach %s status (current %s) '
                        'within the required time (%s s).' %
-                       (resource_name, resource_id, statuses, resource_status,
+                       (resource_name, resource_id, status, resource_status,
                         client.build_timeout))
             raise lib_exc.TimeoutException(message)
     LOG.info('%s %s reached %s after waiting for %f seconds',
-             resource_name, resource_id, statuses, time.time() - start)
+             resource_name, resource_id, status, time.time() - start)
+
+
+def wait_for_volume_migration(client, volume_id, new_host):
+    """Waits for a Volume to move to a new host."""
+    body = client.show_volume(volume_id)['volume']
+    host = body['os-vol-host-attr:host']
+    migration_status = body['migration_status']
+    start = int(time.time())
+
+    # new_host is hostname@backend while current_host is hostname@backend#type
+    while migration_status != 'success' or new_host not in host:
+        time.sleep(client.build_interval)
+        body = client.show_volume(volume_id)['volume']
+        host = body['os-vol-host-attr:host']
+        migration_status = body['migration_status']
+
+        if migration_status == 'error':
+            message = ('volume %s failed to migrate.' % (volume_id))
+            raise lib_exc.TempestException(message)
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('Volume %s failed to migrate to %s (current %s) '
+                       'within the required time (%s s).' %
+                       (volume_id, new_host, host, client.build_timeout))
+            raise lib_exc.TimeoutException(message)
 
 
 def wait_for_volume_retype(client, volume_id, new_volume_type):
@@ -289,3 +312,24 @@
             raise lib_exc.TimeoutException(message)
 
     return body
+
+
+def wait_for_interface_detach(client, server_id, port_id):
+    """Waits for an interface to be detached from a server."""
+    body = client.list_interfaces(server_id)['interfaceAttachments']
+    ports = [iface['port_id'] for iface in body]
+    start = int(time.time())
+
+    while port_id in ports:
+        time.sleep(client.build_interval)
+        body = client.list_interfaces(server_id)['interfaceAttachments']
+        ports = [iface['port_id'] for iface in body]
+        if port_id not in ports:
+            return body
+
+        timed_out = int(time.time()) - start >= client.build_timeout
+        if timed_out:
+            message = ('Interface %s failed to detach from server %s within '
+                       'the required time (%s s)' % (port_id, server_id,
+                                                     client.build_timeout))
+            raise lib_exc.TimeoutException(message)
diff --git a/tempest/config.py b/tempest/config.py
index c017762..e3ac47c 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -20,6 +20,7 @@
 
 from oslo_concurrency import lockutils
 from oslo_config import cfg
+from oslo_config import types
 from oslo_log import log as logging
 
 from tempest.lib import exceptions
@@ -61,13 +62,7 @@
                      "users. This option requires that OpenStack Identity "
                      "API admin credentials are known. If false, isolated "
                      "test cases and parallel execution, can still be "
-                     "achieved configuring a list of test accounts",
-                deprecated_opts=[cfg.DeprecatedOpt('allow_tenant_isolation',
-                                                   group='auth'),
-                                 cfg.DeprecatedOpt('allow_tenant_isolation',
-                                                   group='compute'),
-                                 cfg.DeprecatedOpt('allow_tenant_isolation',
-                                                   group='orchestration')]),
+                     "achieved configuring a list of test accounts"),
     cfg.ListOpt('tempest_roles',
                 help="Roles to assign to all users created by tempest",
                 default=[]),
@@ -106,7 +101,8 @@
                secret=True,
                deprecated_group='identity'),
     cfg.StrOpt('admin_domain_name',
-               help="Admin domain name for authentication (Keystone V3)."
+               default='Default',
+               help="Admin domain name for authentication (Keystone V3). "
                     "The same domain applies to user and project",
                deprecated_group='identity'),
 ]
@@ -175,15 +171,35 @@
     cfg.IntOpt('user_lockout_failure_attempts',
                default=2,
                help="The number of unsuccessful login attempts the user is "
-                    "allowed before having the account locked."),
+                    "allowed before having the account locked. This only "
+                    "takes effect when identity-feature-enabled."
+                    "security_compliance is set to 'True'. For more details, "
+                    "refer to keystone config options keystone.conf:"
+                    "security_compliance.lockout_failure_attempts. "
+                    "This feature is disabled by default in keystone."),
     cfg.IntOpt('user_lockout_duration',
                default=5,
                help="The number of seconds a user account will remain "
-                    "locked."),
+                    "locked. This only takes "
+                    "effect when identity-feature-enabled.security_compliance "
+                    "is set to 'True'. For more details, refer to "
+                    "keystone config options "
+                    "keystone.conf:security_compliance.lockout_duration. "
+                    "Setting this option will have no effect unless you also "
+                    "set identity.user_lockout_failure_attempts."),
     cfg.IntOpt('user_unique_last_password_count',
                default=2,
                help="The number of passwords for a user that must be unique "
-                    "before an old password can be reused."),
+                    "before an old password can be reused. This only takes "
+                    "effect when identity-feature-enabled.security_compliance "
+                    "is set to 'True'. "
+                    "This config option corresponds to keystone.conf: "
+                    "security_compliance.unique_last_password_count, whose "
+                    "default value is 0 meaning disabling this feature. "
+                    "NOTE: This config option value must be same as "
+                    "keystone.conf: security_compliance.unique_last_password_"
+                    "count otherwise test might fail"
+               ),
 ]
 
 service_clients_group = cfg.OptGroup(name='service-clients',
@@ -227,18 +243,8 @@
                 help="A list of enabled identity extensions with a special "
                      "entry all which indicates every extension is enabled. "
                      "Empty list indicates all extensions are disabled. "
-                     "To get the list of extensions run: 'keystone discover'"),
-    # TODO(rodrigods): This is a feature flag for bug 1590578 which is fixed
-    # in Newton and Ocata. This option can be removed after Mitaka is end of
-    # life.
-    cfg.BoolOpt('forbid_global_implied_dsr',
-                default=False,
-                help='Does the environment forbid global roles implying '
-                     'domain specific ones?',
-                deprecated_for_removal=True,
-                deprecated_reason="This feature flag was introduced to "
-                                  "support testing of old OpenStack versions, "
-                                  "which are not supported anymore"),
+                     "To get the list of extensions run: "
+                     "'openstack extension list --identity'"),
     cfg.BoolOpt('domain_specific_drivers',
                 default=False,
                 help='Are domain specific drivers enabled? '
@@ -248,7 +254,22 @@
     cfg.BoolOpt('security_compliance',
                 default=False,
                 help='Does the environment have the security compliance '
-                     'settings enabled?')
+                     'settings enabled?'),
+    cfg.BoolOpt('project_tags',
+                default=False,
+                help='Is the project tags identity v3 API available?'),
+    # Application credentials is a default feature in Queens. This config
+    # option can removed once Pike is EOL.
+    cfg.BoolOpt('application_credentials',
+                default=False,
+                help='Does the environment have application credentials '
+                     'enabled?'),
+    cfg.BoolOpt('immutable_user_source',
+                default=False,
+                help='Set to True if the environment has a read-only '
+                     'user source. This will skip all tests that attempt to '
+                     'create, delete, or modify users. This should not be set '
+                     'to True if using dynamic credentials')
 ]
 
 compute_group = cfg.OptGroup(name='compute',
@@ -311,9 +332,9 @@
                default=0,
                help='Time in seconds before a shelved instance is eligible '
                     'for removing from a host.  -1 never offload, 0 offload '
-                    'when shelved. This time should be the same as the time '
-                    'of nova.conf, and some tests will run for as long as the '
-                    'time.'),
+                    'when shelved. This configuration value should be same as '
+                    'nova.conf: DEFAULT.shelved_offload_time, and '
+                    'some tests will run for as long as the time.'),
     cfg.IntOpt('min_compute_nodes',
                default=1,
                help=('The minimum number of compute nodes expected. This will '
@@ -345,6 +366,38 @@
                     "with format 'X.Y' or string 'latest'"),
 ]
 
+placement_group = cfg.OptGroup(name='placement',
+                               title='Placement Service Options')
+
+PlacementGroup = [
+    cfg.StrOpt('endpoint_type',
+               default='public',
+               choices=['public', 'admin', 'internal'],
+               help="The endpoint type to use for the placement service."),
+    cfg.StrOpt('catalog_type',
+               default='placement',
+               help="Catalog type of the Placement service."),
+    cfg.StrOpt('region',
+               default='RegionOne',
+               help="The placement region name to use. If empty, the value "
+                    "of [identity]/region is used instead. If no such region "
+                    "is found in the service catalog, the first region found "
+                    "is used."),
+    cfg.StrOpt('min_microversion',
+               default=None,
+               help="Lower version of the test target microversion range. "
+                    "The format is 'X.Y', where 'X' and 'Y' are int values. "
+                    "Valid values are string with format 'X.Y' or string "
+                    "'latest'"),
+    cfg.StrOpt('max_microversion',
+               default=None,
+               help="Upper version of the test target microversion range. "
+                    "The format is 'X.Y', where 'X' and 'Y' are int values. "
+                    "Valid values are string with format 'X.Y' or string "
+                    "'latest'"),
+]
+
+
 compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
                                       title="Enabled Compute Service Features")
 
@@ -414,20 +467,24 @@
     cfg.BoolOpt('vnc_console',
                 default=False,
                 help='Enable VNC console. This configuration value should '
-                     'be same as [nova.vnc]->vnc_enabled in nova.conf'),
+                     'be same as nova.conf: vnc.enabled'),
+    cfg.StrOpt('vnc_server_header',
+               default='WebSockify',
+               help='Expected VNC server name (WebSockify, nginx, etc) '
+                    'in response header.'),
     cfg.BoolOpt('spice_console',
                 default=False,
                 help='Enable Spice console. This configuration value should '
-                     'be same as [nova.spice]->enabled in nova.conf'),
+                     'be same as nova.conf: spice.enabled'),
     cfg.BoolOpt('rdp_console',
                 default=False,
                 help='Enable RDP console. This configuration value should '
-                     'be same as [nova.rdp]->enabled in nova.conf'),
+                     'be same as nova.conf: rdp.enabled'),
     cfg.BoolOpt('serial_console',
                 default=False,
                 help='Enable serial console. This configuration value '
-                     'should be the same as [nova.serial_console]->enabled '
-                     'in nova.conf'),
+                     'should be the same as '
+                     'nova.conf: serial_console.enabled'),
     cfg.BoolOpt('rescue',
                 default=True,
                 help='Does the test environment support instance rescue '
@@ -465,24 +522,43 @@
     cfg.BoolOpt('config_drive',
                 default=True,
                 help='Enable special configuration drive with metadata.'),
-    cfg.ListOpt('scheduler_available_filters',
-                default=['all'],
-                help="A list of enabled filters that nova will accept as hints"
-                     " to the scheduler when creating a server. A special "
-                     "entry 'all' indicates all filters that are included "
-                     "with nova are enabled. Empty list indicates all filters "
-                     "are disabled. The full list of available filters is in "
-                     "nova.conf: DEFAULT.scheduler_available_filters. If the "
+    cfg.ListOpt('scheduler_enabled_filters',
+                default=["RetryFilter", "AvailabilityZoneFilter",
+                         "ComputeFilter", "ComputeCapabilitiesFilter",
+                         "ImagePropertiesFilter",
+                         "ServerGroupAntiAffinityFilter",
+                         "ServerGroupAffinityFilter"],
+                help="A list of enabled filters that Nova will accept as "
+                     "hints to the scheduler when creating a server. If the "
                      "default value is overridden in nova.conf by the test "
                      "environment (which means that a different set of "
                      "filters is enabled than what is included in Nova by "
-                     "default) then, this option must be configured to "
+                     "default), then this option must be configured to "
                      "contain the same filters that Nova uses in the test "
-                     "environment."),
+                     "environment. A special entry 'all' indicates all "
+                     "filters that are included with Nova are enabled. If "
+                     "using 'all', be sure to enable all filters in "
+                     "nova.conf, as tests can fail in unpredictable ways if "
+                     "Nova's and Tempest's enabled filters don't match. "
+                     "Empty list indicates all filters are disabled. The "
+                     "full list of enabled filters is in nova.conf: "
+                     "filter_scheduler.enabled_filters.",
+                deprecated_opts=[cfg.DeprecatedOpt(
+                    'scheduler_available_filters',
+                    group='compute-feature-enabled')]),
     cfg.BoolOpt('swap_volume',
                 default=False,
                 help='Does the test environment support in-place swapping of '
                      'volumes attached to a server instance?'),
+    cfg.BoolOpt('volume_backed_live_migration',
+                default=False,
+                help='Does the test environment support volume-backed live '
+                     'migration?'),
+    cfg.BoolOpt('volume_multiattach',
+                default=False,
+                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.'),
 ]
 
 
@@ -540,25 +616,19 @@
                                   'test v2 APIs only so this config option '
                                   'will be removed.'),
     cfg.BoolOpt('api_v1',
-                default=True,
+                default=False,
                 help="Is the v1 image API enabled",
                 deprecated_for_removal=True,
                 deprecated_reason='Glance v1 APIs are deprecated and v2 APIs '
                                   'are current one. In future, Tempest will '
                                   'test v2 APIs only so this config option '
                                   'will be removed.'),
-    cfg.BoolOpt('deactivate_image',
-                default=False,
-                help="Is the deactivate-image feature enabled."
-                     " The feature has been integrated since Kilo.",
-                deprecated_for_removal=True,
-                deprecated_reason="All supported versions of OpenStack now "
-                                  "support the 'deactivate_image' feature"),
 ]
 
 network_group = cfg.OptGroup(name='network',
                              title='Network Service Options')
 
+ProfileType = types.Dict(types.List(types.String(), bounds=True))
 NetworkGroup = [
     cfg.StrOpt('catalog_type',
                default='network',
@@ -615,13 +685,21 @@
     cfg.ListOpt('dns_servers',
                 default=["8.8.8.8", "8.8.4.4"],
                 help="List of dns servers which should be used"
-                     " for subnet creation"),
+                     " for subnet creation",
+                deprecated_for_removal=True,
+                deprecated_reason="This config option is no longer "
+                                  "used anywhere, so it can be removed."),
     cfg.StrOpt('port_vnic_type',
                choices=[None, 'normal', 'direct', 'macvtap'],
-               help="vnic_type to use when Launching instances"
+               help="vnic_type to use when launching instances"
                     " with pre-configured ports."
                     " Supported ports are:"
                     " ['normal','direct','macvtap']"),
+    cfg.Opt('port_profile',
+            type=ProfileType,
+            default={},
+            help="port profile to use when launching instances"
+                 " with pre-configured ports."),
     cfg.ListOpt('default_network',
                 default=["1.0.0.0/16", "2.0.0.0/16"],
                 help="List of ip pools"
@@ -668,9 +746,11 @@
 
 ValidationGroup = [
     cfg.BoolOpt('run_validation',
-                default=False,
+                default=True,
                 help='Enable ssh on created servers and creation of additional'
-                     ' validation resources to enable remote access'),
+                     ' validation resources to enable remote access.'
+                     ' In case the guest does not support ssh set it'
+                     ' to false'),
     cfg.BoolOpt('security_group',
                 default=True,
                 help='Enable/disable security groups.'),
@@ -744,7 +824,7 @@
                help='Timeout in seconds to wait for a volume to become '
                     'available.'),
     cfg.StrOpt('catalog_type',
-               default='volume',
+               default='volumev3',
                help="Catalog type of the Volume Service"),
     cfg.StrOpt('region',
                default='',
@@ -832,19 +912,31 @@
                 help='A list of enabled volume extensions with a special '
                      'entry all which indicates every extension is enabled. '
                      'Empty list indicates all extensions are disabled'),
-    cfg.BoolOpt('api_v1',
-                default=False,
-                help="Is the v1 volume API enabled",
-                deprecated_for_removal=True,
-                deprecated_reason="The v1 volume API has been deprecated "
-                                  "since Juno release, and the API will be "
-                                  "removed."),
     cfg.BoolOpt('api_v2',
                 default=True,
-                help="Is the v2 volume API enabled"),
+                help="Is the v2 volume API enabled",
+                deprecated_for_removal=True,
+                deprecated_reason="The v2 volume API has been deprecated "
+                                  "since Pike release. Now Tempest run all "
+                                  "the volume tests against v2 or v3 API "
+                                  "based on CONF.volume.catalog_type which "
+                                  "makes this config option unusable. If "
+                                  "catalog_type is volumev2, then all the "
+                                  "volume tests will run against v2 API. "
+                                  "Use ``CONF.volume.catalog_type`` to run "
+                                  "the Tempest against volume v2 or v3 API"),
     cfg.BoolOpt('api_v3',
                 default=True,
-                help="Is the v3 volume API enabled"),
+                help="Is the v3 volume API enabled",
+                deprecated_for_removal=True,
+                deprecated_reason="Tempest run all the volume tests against "
+                                  "v2 or v3 API based on "
+                                  "CONF.volume.catalog_type which makes this "
+                                  "config option unusable. If catalog_type is "
+                                  "volumev3 which is default, then all the "
+                                  "volume tests will run against v3 API. "
+                                  "Use ``CONF.volume.catalog_type`` to run "
+                                  "the Tempest against volume v2 or v3 API"),
     cfg.BoolOpt('extend_attached_volume',
                 default=False,
                 help='Does the cloud support extending the size of a volume '
@@ -922,66 +1014,6 @@
                 help="Execute discoverability tests"),
 ]
 
-orchestration_group = cfg.OptGroup(name='orchestration',
-                                   title='Orchestration Service Options')
-
-OrchestrationGroup = [
-    cfg.StrOpt('catalog_type',
-               default='orchestration',
-               help="Catalog type of the Orchestration service.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('region',
-               default='',
-               help="The orchestration region name to use. If empty, the "
-                    "value of identity.region is used instead. If no such "
-                    "region is found in the service catalog, the first found "
-                    "one is used.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('endpoint_type',
-               default='publicURL',
-               choices=['public', 'admin', 'internal',
-                        'publicURL', 'adminURL', 'internalURL'],
-               help="The endpoint type to use for the orchestration service.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('stack_owner_role', default='heat_stack_owner',
-               help='Role required for users to be able to manage stacks',
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.IntOpt('build_interval',
-               default=1,
-               help="Time in seconds between build status checks.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.IntOpt('build_timeout',
-               default=1200,
-               help="Timeout in seconds to wait for a stack to build.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('instance_type',
-               default='m1.micro',
-               help="Instance type for tests. Needs to be big enough for a "
-                    "full OS plus the test workload",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('keypair_name',
-               help="Name of existing keypair to launch servers with.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.IntOpt('max_template_size',
-               default=524288,
-               help="Value must match heat configuration of the same name.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.IntOpt('max_resources_per_stack',
-               default=1000,
-               help="Value must match heat configuration of the same name.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-]
-
 
 scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options')
 
@@ -1048,11 +1080,6 @@
     cfg.BoolOpt('nova',
                 default=True,
                 help="Whether or not nova is expected to be available"),
-    cfg.BoolOpt('heat',
-                default=False,
-                help="Whether or not Heat is expected to be available",
-                deprecated_for_removal=True,
-                deprecated_reason='Heat support will be removed from Tempest'),
 ]
 
 debug_group = cfg.OptGroup(name="debug",
@@ -1081,18 +1108,19 @@
 """)
 ]
 
+
+profiler_group = cfg.OptGroup(name="profiler",
+                              title="OpenStack Profiler")
+
+ProfilerGroup = [
+    cfg.StrOpt('key',
+               help="The secret key to enable OpenStack Profiler. The value "
+                    "should match the one configured in OpenStack services "
+                    "under `[profiler]/hmac_keys` property. The default empty "
+                    "value keeps profiling disabled"),
+]
+
 DefaultGroup = [
-    cfg.StrOpt('resources_prefix',
-               default='tempest',
-               help="Prefix to be added when generating the name for "
-                    "test resources. It can be used to discover all "
-                    "resources associated with a specific test run when "
-                    "running tempest on a real-life cloud",
-               deprecated_for_removal=True,
-               deprecated_reason="It is enough to add 'tempest' as this "
-                                 "prefix to ideintify resources which are "
-                                 "created by Tempest and no projects set "
-                                 "this option on OpenStack dev community."),
     cfg.BoolOpt('pause_teardown',
                 default=False,
                 help="""Whether to pause a test in global teardown.
@@ -1120,10 +1148,11 @@
     (volume_feature_group, VolumeFeaturesGroup),
     (object_storage_group, ObjectStoreGroup),
     (object_storage_feature_group, ObjectStoreFeaturesGroup),
-    (orchestration_group, OrchestrationGroup),
     (scenario_group, ScenarioGroup),
     (service_available_group, ServiceAvailableGroup),
     (debug_group, DebugGroup),
+    (placement_group, PlacementGroup),
+    (profiler_group, ProfilerGroup),
     (None, DefaultGroup)
 ]
 
@@ -1152,7 +1181,7 @@
     return opt_list
 
 
-# this should never be called outside of this class
+# This should never be called outside of this module
 class TempestConfigPrivate(object):
     """Provides OpenStack configuration information."""
 
@@ -1187,7 +1216,6 @@
         self.object_storage = _CONF['object-storage']
         self.object_storage_feature_enabled = _CONF[
             'object-storage-feature-enabled']
-        self.orchestration = _CONF.orchestration
         self.scenario = _CONF.scenario
         self.service_available = _CONF.service_available
         self.debug = _CONF.debug
@@ -1242,7 +1270,7 @@
 
         logging_cfg_path = "%s/logging.conf" % os.path.dirname(path)
         if ((not hasattr(_CONF, 'log_config_append') or
-            _CONF.log_config_append is None) and
+             _CONF.log_config_append is None) and
             os.path.isfile(logging_cfg_path)):
             # if logging conf is in place we need to set log_config_append
             _CONF.log_config_append = logging_cfg_path
@@ -1296,6 +1324,11 @@
 
     def set_config_path(self, path):
         self._path = path
+        # FIXME(masayukig): bug#1783751 To pass the config file path to child
+        # processes, we need to set the environment variables here as a
+        # workaround.
+        os.environ['TEMPEST_CONFIG_DIR'] = os.path.dirname(path)
+        os.environ['TEMPEST_CONFIG'] = os.path.basename(path)
 
 
 CONF = TempestConfigProxy()
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index aae685c..2c40cb1 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -15,7 +15,7 @@
 import os
 import re
 
-import pep8
+import pycodestyle
 
 
 PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
@@ -34,6 +34,9 @@
 METHOD_DELETE_RESOURCE = re.compile(r"^\s*def delete_.+")
 CLASS = re.compile(r"^class .+")
 EX_ATTRIBUTE = re.compile(r'(\s+|\()(e|ex|exc|exception).message(\s+|\))')
+NEGATIVE_TEST_DECORATOR = re.compile(
+    r'\s*@decorators\.attr\(type=.*negative.*\)')
+_HAVE_NEGATIVE_DECORATOR = False
 
 
 def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
@@ -66,7 +69,7 @@
 
 def no_setup_teardown_class_for_tests(physical_line, filename):
 
-    if pep8.noqa(physical_line):
+    if pycodestyle.noqa(physical_line):
         return
 
     if 'tempest/test.py' in filename or 'tempest/lib/' in filename:
@@ -161,7 +164,7 @@
     if not METHOD.match(physical_line):
         return False
 
-    if pep8.noqa(physical_line):
+    if pycodestyle.noqa(physical_line):
         return False
 
     return True
@@ -228,12 +231,12 @@
     if 'tempest/lib/' not in filename:
         return
 
-    if not ('from tempest' in logical_line
-            or 'import tempest' in logical_line):
+    if not ('from tempest' in logical_line or
+            'import tempest' in logical_line):
         return
 
-    if ('from tempest.lib' in logical_line
-        or 'import tempest.lib' in logical_line):
+    if ('from tempest.lib' in logical_line or
+            'import tempest.lib' in logical_line):
         return
 
     msg = ("T112: tempest.lib should not import local tempest code to avoid "
@@ -266,9 +269,9 @@
     if 'tempest/lib/' not in filename:
         return
 
-    if ('tempest.config' in logical_line
-        or 'from tempest import config' in logical_line
-        or 'oslo_config' in logical_line):
+    if ('tempest.config' in logical_line or
+            'from tempest import config' in logical_line or
+            'oslo_config' in logical_line):
         msg = ('T114: tempest.lib can not have any dependency on tempest '
                'config.')
         yield(0, msg)
@@ -284,13 +287,13 @@
     if 'tempest/api/' not in filename:
         return
 
-    if pep8.noqa(physical_line):
+    if pycodestyle.noqa(physical_line):
         return
 
-    if not re.match('class .*Test.*\(.*Admin.*\):', logical_line):
+    if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line):
         return
 
-    if not re.match('.\/tempest\/api\/.*\/admin\/.*', filename):
+    if not re.match(r'.\/tempest\/api\/.*\/admin\/.*', filename):
         msg = 'T115: All admin tests should exist under admin path.'
         yield(0, msg)
 
@@ -306,6 +309,29 @@
         yield(0, msg)
 
 
+def negative_test_attribute_always_applied_to_negative_tests(physical_line,
+                                                             filename):
+    """Check ``@decorators.attr(type=['negative'])`` applied to negative tests.
+
+    T117
+    """
+    global _HAVE_NEGATIVE_DECORATOR
+
+    if re.match(r'.\/tempest\/api\/.*_negative.*', filename):
+
+        if NEGATIVE_TEST_DECORATOR.match(physical_line):
+            _HAVE_NEGATIVE_DECORATOR = True
+            return
+
+        if TEST_DEFINITION.match(physical_line):
+            if not _HAVE_NEGATIVE_DECORATOR:
+                return (
+                    0, "T117: Must apply `@decorators.attr(type=['negative'])`"
+                       " to all negative API tests"
+                )
+            _HAVE_NEGATIVE_DECORATOR = False
+
+
 def factory(register):
     register(import_no_clients_in_api_and_scenario_tests)
     register(scenario_tests_need_service_tags)
@@ -322,3 +348,4 @@
     register(use_rand_uuid_instead_of_uuid4)
     register(dont_put_admin_tests_on_nonadmin_path)
     register(unsupported_exception_attribute_PY3)
+    register(negative_test_attribute_always_applied_to_negative_tests)
diff --git a/tempest/hacking/ignored_list_T110.txt b/tempest/hacking/ignored_list_T110.txt
deleted file mode 100644
index 0e7e894..0000000
--- a/tempest/hacking/ignored_list_T110.txt
+++ /dev/null
@@ -1 +0,0 @@
-./tempest/services/object_storage/object_client.py
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors.py b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
index 547d94d..bd5e3d6 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
@@ -60,7 +60,7 @@
     },
     'additionalProperties': False,
     # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
-    # 'OS-FLV-EXT-DATA' are API extensions. So they are not 'required'.
+    # 'OS-FLV-EXT-DATA' are API extensions, so they are not 'required'.
     'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id']
 }
 
@@ -74,7 +74,7 @@
                 'items': common_flavor_info
             },
             # NOTE(gmann): flavors_links attribute is not necessary
-            # to be present always So it is not 'required'.
+            # to be present always so it is not 'required'.
             'flavors_links': parameter_types.links
         },
         'additionalProperties': False,
@@ -82,11 +82,7 @@
     }
 }
 
-unset_flavor_extra_specs = {
-    'status_code': [200]
-}
-
-create_get_flavor_details = {
+create_update_get_flavor_details = {
     'status_code': [200],
     'response_body': {
         'type': 'object',
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
index a438d48..3aa1eda 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
@@ -20,7 +20,7 @@
             'extra_specs': {
                 'type': 'object',
                 'patternProperties': {
-                    '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+                    r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
                 }
             }
         },
@@ -29,12 +29,16 @@
     }
 }
 
+unset_flavor_extra_specs = {
+    'status_code': [200]
+}
+
 set_get_flavor_extra_specs_key = {
     'status_code': [200],
     'response_body': {
         'type': 'object',
         'patternProperties': {
-            '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+            r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
         }
     }
 }
diff --git a/tempest/lib/api_schema/response/compute/v2_1/server_groups.py b/tempest/lib/api_schema/response/compute/v2_1/server_groups.py
new file mode 100644
index 0000000..01db20b
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/server_groups.py
@@ -0,0 +1,65 @@
+# Copyright 2017 NTT 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.
+
+common_server_group = {
+    'type': 'object',
+    'properties': {
+        'id': {'type': 'string'},
+        'name': {'type': 'string'},
+        'policies': {
+            'type': 'array',
+            'items': {'type': 'string'}
+        },
+        # 'members' attribute contains the array of instance's UUID of
+        # instances present in server group
+        'members': {
+            'type': 'array',
+            'items': {'type': 'string'}
+        },
+        'metadata': {'type': 'object'}
+    },
+    'additionalProperties': False,
+    'required': ['id', 'name', 'policies', 'members', 'metadata']
+}
+
+create_show_server_group = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'server_group': common_server_group
+        },
+        'additionalProperties': False,
+        'required': ['server_group']
+    }
+}
+
+delete_server_group = {
+    'status_code': [204]
+}
+
+list_server_groups = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'server_groups': {
+                'type': 'array',
+                'items': common_server_group
+            }
+        },
+        'additionalProperties': False,
+        'required': ['server_groups']
+    }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 2954de0..3300298 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -345,58 +345,6 @@
     }
 }
 
-common_server_group = {
-    'type': 'object',
-    'properties': {
-        'id': {'type': 'string'},
-        'name': {'type': 'string'},
-        'policies': {
-            'type': 'array',
-            'items': {'type': 'string'}
-        },
-        # 'members' attribute contains the array of instance's UUID of
-        # instances present in server group
-        'members': {
-            'type': 'array',
-            'items': {'type': 'string'}
-        },
-        'metadata': {'type': 'object'}
-    },
-    'additionalProperties': False,
-    'required': ['id', 'name', 'policies', 'members', 'metadata']
-}
-
-create_show_server_group = {
-    'status_code': [200],
-    'response_body': {
-        'type': 'object',
-        'properties': {
-            'server_group': common_server_group
-        },
-        'additionalProperties': False,
-        'required': ['server_group']
-    }
-}
-
-delete_server_group = {
-    'status_code': [204]
-}
-
-list_server_groups = {
-    'status_code': [200],
-    'response_body': {
-        'type': 'object',
-        'properties': {
-            'server_groups': {
-                'type': 'array',
-                'items': common_server_group
-            }
-        },
-        'additionalProperties': False,
-        'required': ['server_groups']
-    }
-}
-
 instance_actions = {
     'type': 'object',
     'properties': {
@@ -430,8 +378,9 @@
             'traceback': {'type': ['string', 'null']}
         },
         'additionalProperties': False,
-        'required': ['event', 'start_time', 'finish_time', 'result',
-                     'traceback']
+        # NOTE(zhufl): events.traceback can only be seen by admin users
+        # with default policy.json, so it shouldn't be a required field.
+        'required': ['event', 'start_time', 'finish_time', 'result']
     }
 }
 
diff --git a/tempest/lib/api_schema/response/compute/v2_1/volumes.py b/tempest/lib/api_schema/response/compute/v2_1/volumes.py
index c35dae9..d367f2a 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/volumes.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/volumes.py
@@ -50,7 +50,8 @@
                             # If it would come as empty array "[]" then,
                             # those elements can be defined as 'required'.
                         }
-                    }
+                    },
+                    'os-vol-host-attr:host': {'type': 'string'},
                 },
                 'additionalProperties': False,
                 'required': ['id', 'status', 'displayName', 'availabilityZone',
diff --git a/tempest/lib/api_schema/response/compute/v2_11/services.py b/tempest/lib/api_schema/response/compute/v2_11/services.py
index 18b833b..9ece1f9 100644
--- a/tempest/lib/api_schema/response/compute/v2_11/services.py
+++ b/tempest/lib/api_schema/response/compute/v2_11/services.py
@@ -44,3 +44,10 @@
         'required': ['service']
     }
 }
+
+# **** Schemas unchanged in microversion 2.11 since microversion 2.1 ****
+# Note(felipemonteiro): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+enable_disable_service = copy.deepcopy(services.enable_disable_service)
+disable_log_reason = copy.deepcopy(services.disable_log_reason)
diff --git a/tempest/lib/api_schema/response/compute/v2_13/servers.py b/tempest/lib/api_schema/response/compute/v2_13/server_groups.py
similarity index 69%
rename from tempest/lib/api_schema/response/compute/v2_13/servers.py
rename to tempest/lib/api_schema/response/compute/v2_13/server_groups.py
index a90f3e4..5cb4241 100644
--- a/tempest/lib/api_schema/response/compute/v2_13/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_13/server_groups.py
@@ -14,21 +14,24 @@
 
 import copy
 
-from tempest.lib.api_schema.response.compute.v2_1 import servers
+from tempest.lib.api_schema.response.compute.v2_1 import server_groups
 
-
-common_server_group = copy.deepcopy(servers.common_server_group)
+# Compute microversion 2.13:
+# 1. New attributes in 'server_group' dict.
+#      'project_id', 'user_id'
+common_server_group = copy.deepcopy(server_groups.common_server_group)
 common_server_group['properties']['project_id'] = {'type': 'string'}
 common_server_group['properties']['user_id'] = {'type': 'string'}
 common_server_group['required'].append('project_id')
 common_server_group['required'].append('user_id')
 
-create_show_server_group = copy.deepcopy(servers.create_show_server_group)
+create_show_server_group = copy.deepcopy(
+    server_groups.create_show_server_group)
 create_show_server_group['response_body']['properties'][
     'server_group'] = common_server_group
 
-delete_server_group = copy.deepcopy(servers.delete_server_group)
+delete_server_group = copy.deepcopy(server_groups.delete_server_group)
 
-list_server_groups = copy.deepcopy(servers.list_server_groups)
+list_server_groups = copy.deepcopy(server_groups.list_server_groups)
 list_server_groups['response_body']['properties']['server_groups'][
     'items'] = common_server_group
diff --git a/tempest/lib/api_schema/response/compute/v2_16/servers.py b/tempest/lib/api_schema/response/compute/v2_16/servers.py
index 3eb658f..fc81ff7 100644
--- a/tempest/lib/api_schema/response/compute/v2_16/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_16/servers.py
@@ -122,7 +122,7 @@
     '^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
         'OS-EXT-IPS:type': {'type': 'string'},
         'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
-# NOTE(gmann)dd: Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# NOTE(gmann): Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
 # attributes in server address. Those are API extension,
 # and some environments return a response without
 # these attributes. So they are not 'required'.
@@ -157,4 +157,17 @@
     }
 }
 
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.9 ******
 list_servers = copy.deepcopy(servers.list_servers)
+update_server = copy.deepcopy(servers.update_server)
+rebuild_server = copy.deepcopy(servers.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers.rebuild_server_with_admin_pass)
+show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers.get_remote_consoles)
+attach_volume = copy.deepcopy(servers.attach_volume)
+show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
index 05cc32c..b6c3c14 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -14,12 +14,12 @@
 
 import copy
 
-from tempest.lib.api_schema.response.compute.v2_1 import servers as serversv21
 from tempest.lib.api_schema.response.compute.v2_16 import servers \
     as serversv216
 
-list_servers = copy.deepcopy(serversv216.list_servers)
-
+# Compute microversion 2.19:
+# 1. New attributes in 'server' dict.
+#      'description'
 get_server = copy.deepcopy(serversv216.get_server)
 get_server['response_body']['properties']['server'][
     'properties'].update({'description': {'type': ['string', 'null']}})
@@ -32,21 +32,32 @@
 list_servers_detail['response_body']['properties']['servers']['items'][
     'required'].append('description')
 
-update_server = copy.deepcopy(serversv21.update_server)
+update_server = copy.deepcopy(serversv216.update_server)
 update_server['response_body']['properties']['server'][
     'properties'].update({'description': {'type': ['string', 'null']}})
 update_server['response_body']['properties']['server'][
     'required'].append('description')
 
-rebuild_server = copy.deepcopy(serversv21.rebuild_server)
+rebuild_server = copy.deepcopy(serversv216.rebuild_server)
 rebuild_server['response_body']['properties']['server'][
     'properties'].update({'description': {'type': ['string', 'null']}})
 rebuild_server['response_body']['properties']['server'][
     'required'].append('description')
 
 rebuild_server_with_admin_pass = copy.deepcopy(
-    serversv21.rebuild_server_with_admin_pass)
+    serversv216.rebuild_server_with_admin_pass)
 rebuild_server_with_admin_pass['response_body']['properties']['server'][
     'properties'].update({'description': {'type': ['string', 'null']}})
 rebuild_server_with_admin_pass['response_body']['properties']['server'][
     'required'].append('description')
+
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.16 ******
+list_servers = copy.deepcopy(serversv216.list_servers)
+show_server_diagnostics = copy.deepcopy(serversv216.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(serversv216.get_remote_consoles)
+attach_volume = copy.deepcopy(serversv216.attach_volume)
+show_volume_attachment = copy.deepcopy(serversv216.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(serversv216.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
index b03bdf6..5a0f987 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -15,7 +15,6 @@
 
 import copy
 
-from tempest.lib.api_schema.response.compute.v2_1 import servers as servers21
 from tempest.lib.api_schema.response.compute.v2_19 import servers as servers219
 
 # The 2.26 microversion changes the server GET and (detailed) LIST responses to
@@ -43,9 +42,24 @@
 list_servers_detail['response_body']['properties']['servers']['items'][
     'required'].append('tags')
 
-# list response schema wasn't changed for v2.26 so use v2.1
+update_server = copy.deepcopy(servers219.update_server)
+update_server['response_body']['properties']['server'][
+    'properties'].update({'tags': tag_items})
+update_server['response_body']['properties']['server'][
+    'required'].append('tags')
 
-list_servers = copy.deepcopy(servers21.list_servers)
+rebuild_server = copy.deepcopy(servers219.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+    'properties'].update({'tags': tag_items})
+rebuild_server['response_body']['properties']['server'][
+    'required'].append('tags')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers219.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'properties'].update({'tags': tag_items})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'required'].append('tags')
 
 list_tags = {
     'status_code': [200],
@@ -79,3 +93,14 @@
 }
 
 delete_tag = {'status_code': [204]}
+
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.19 ******
+list_servers = copy.deepcopy(servers219.list_servers)
+show_server_diagnostics = copy.deepcopy(servers219.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers219.get_remote_consoles)
+attach_volume = copy.deepcopy(servers219.attach_volume)
+show_volume_attachment = copy.deepcopy(servers219.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers219.list_volume_attachments)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_28/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_28/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_28/hypervisors.py b/tempest/lib/api_schema/response/compute/v2_28/hypervisors.py
new file mode 100644
index 0000000..8ea9ff8
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_28/hypervisors.py
@@ -0,0 +1,40 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 \
+    import hypervisors as hypervisorsv21
+
+# hypervisor.cpu_info change from string to JSON object.
+hypervisor_detail = copy.deepcopy(hypervisorsv21.hypervisor_detail)
+hypervisor_detail['properties'].update({'cpu_info': {'type': 'object'}})
+
+list_hypervisors_detail = copy.deepcopy(hypervisorsv21.list_hypervisors_detail)
+list_hypervisors_detail['response_body']['properties']['hypervisors'].update(
+    {'items': hypervisor_detail})
+
+get_hypervisor = copy.deepcopy(hypervisorsv21.get_hypervisor)
+get_hypervisor['response_body']['properties'].update(
+    {'hypervisor': hypervisor_detail})
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.1 ***
+get_hypervisor_statistics = \
+    copy.deepcopy(hypervisorsv21.get_hypervisor_statistics)
+list_search_hypervisors = copy.deepcopy(hypervisorsv21.list_search_hypervisors)
+get_hypervisor_uptime = copy.deepcopy(hypervisorsv21.get_hypervisor_uptime)
+get_hypervisors_servers = copy.deepcopy(hypervisorsv21.get_hypervisors_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_3/servers.py b/tempest/lib/api_schema/response/compute/v2_3/servers.py
index f24103e..1674c1b 100644
--- a/tempest/lib/api_schema/response/compute/v2_3/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_3/servers.py
@@ -128,7 +128,7 @@
     '^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
         'OS-EXT-IPS:type': {'type': 'string'},
         'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
-# NOTE(gmann)dd: Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# NOTE(gmann): Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
 # attributes in server address. Those are API extension,
 # and some environments return a response without
 # these attributes. So they are not 'required'.
@@ -163,4 +163,16 @@
     }
 }
 
+# NOTE: Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.1 ***
 list_servers = copy.deepcopy(servers.list_servers)
+update_server = copy.deepcopy(servers.update_server)
+rebuild_server = copy.deepcopy(servers.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers.rebuild_server_with_admin_pass)
+show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics)
+attach_volume = copy.deepcopy(servers.attach_volume)
+show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_36/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_36/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_36/limits.py b/tempest/lib/api_schema/response/compute/v2_36/limits.py
new file mode 100644
index 0000000..8e94690
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_36/limits.py
@@ -0,0 +1,35 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import limits as limitv21
+
+# Compute microversion 2.36:
+# remove attributes in get_limit:
+#    'maxSecurityGroupRules',
+#    'maxSecurityGroups',
+#    'maxTotalFloatingIps',
+#    'totalFloatingIpsUsed',
+#    'totalSecurityGroupsUsed'
+
+get_limit = copy.deepcopy(limitv21.get_limit)
+
+for item in ['maxSecurityGroupRules', 'maxSecurityGroups',
+             'maxTotalFloatingIps', 'totalFloatingIpsUsed',
+             'totalSecurityGroupsUsed']:
+    get_limit['response_body']['properties']['limits']['properties'][
+        'absolute']['properties'].pop(item)
+    get_limit['response_body']['properties']['limits']['properties'][
+        'absolute']['required'].remove(item)
diff --git a/tempest/lib/api_schema/response/compute/v2_36/quotas.py b/tempest/lib/api_schema/response/compute/v2_36/quotas.py
new file mode 100644
index 0000000..f191ed1
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_36/quotas.py
@@ -0,0 +1,54 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import quotas as quotasv21
+
+# Compute microversion 2.36:
+# remove attributes in quota_set:
+#    'fixed_ips',
+#    'floating_ips',
+#    'security_group_rules',
+#    'security_groups'
+
+remove_item_list = ['fixed_ips', 'floating_ips',
+                    'security_group_rules', 'security_groups']
+
+update_quota_set = copy.deepcopy(quotasv21.update_quota_set)
+for item in remove_item_list:
+    update_quota_set['response_body']['properties']['quota_set'][
+        'properties'].pop(item)
+    update_quota_set['response_body']['properties']['quota_set'][
+        'required'].remove(item)
+
+get_quota_set = copy.deepcopy(quotasv21.get_quota_set)
+for item in remove_item_list:
+    get_quota_set['response_body']['properties']['quota_set'][
+        'properties'].pop(item)
+    get_quota_set['response_body']['properties']['quota_set'][
+        'required'].remove(item)
+
+get_quota_set_details = copy.deepcopy(quotasv21.get_quota_set_details)
+for item in remove_item_list:
+    get_quota_set_details['response_body']['properties']['quota_set'][
+        'properties'].pop(item)
+    get_quota_set_details['response_body']['properties']['quota_set'][
+        'required'].remove(item)
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.1 ***
+delete_quota = copy.deepcopy(quotasv21.delete_quota)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_39/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_39/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_39/limits.py b/tempest/lib/api_schema/response/compute/v2_39/limits.py
new file mode 100644
index 0000000..3df6616
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_39/limits.py
@@ -0,0 +1,29 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_36 import limits as limitv236
+
+# Compute microversion 2.39:
+# remove attributes in get_limit:
+#    'maxImageMeta'
+
+get_limit = copy.deepcopy(limitv236.get_limit)
+
+get_limit['response_body']['properties']['limits']['properties']['absolute'][
+    'properties'].pop('maxImageMeta')
+
+get_limit['response_body']['properties']['limits']['properties']['absolute'][
+    'required'].remove('maxImageMeta')
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_41/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_41/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_41/aggregates.py b/tempest/lib/api_schema/response/compute/v2_41/aggregates.py
new file mode 100644
index 0000000..036bd83
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_41/aggregates.py
@@ -0,0 +1,54 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import aggregates
+
+# 'uuid' of an aggregate is returned in microversion 2.41
+aggregate_for_create = copy.deepcopy(aggregates.aggregate_for_create)
+aggregate_for_create['properties'].update({'uuid': {'type': 'string',
+                                                    'format': 'uuid'}})
+aggregate_for_create['required'].append('uuid')
+
+common_aggregate_info = copy.deepcopy(aggregates.common_aggregate_info)
+common_aggregate_info['properties'].update({'uuid': {'type': 'string',
+                                                     'format': 'uuid'}})
+common_aggregate_info['required'].append('uuid')
+
+list_aggregates = copy.deepcopy(aggregates.list_aggregates)
+list_aggregates['response_body']['properties']['aggregates'].update(
+    {'items': common_aggregate_info})
+
+get_aggregate = copy.deepcopy(aggregates.get_aggregate)
+get_aggregate['response_body']['properties'].update(
+    {'aggregate': common_aggregate_info})
+
+aggregate_set_metadata = get_aggregate
+
+update_aggregate = copy.deepcopy(aggregates.update_aggregate)
+update_aggregate['response_body']['properties'].update(
+    {'aggregate': common_aggregate_info})
+
+create_aggregate = copy.deepcopy(aggregates.create_aggregate)
+create_aggregate['response_body']['properties'].update(
+    {'aggregate': aggregate_for_create})
+
+aggregate_add_remove_host = get_aggregate
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.1 ***
+delete_aggregate = copy.deepcopy(aggregates.delete_aggregate)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_45/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_45/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_45/images.py b/tempest/lib/api_schema/response/compute/v2_45/images.py
new file mode 100644
index 0000000..8a48f36
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_45/images.py
@@ -0,0 +1,32 @@
+#    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.
+
+# The 2.45 microversion removes the "location" header and adds "image_id"
+# to the response body.
+create_image = {
+    'status_code': [202],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'image_id': {'type': 'string'}
+        },
+        'additionalProperties': False,
+        'required': ['image_id']
+    }
+}
+
+# NOTE(mriedem): The compute proxy APIs for showing/listing and deleting
+# images were deprecated in microversion 2.35, and the compute proxy APIs for
+# working with image metadata were deprecated in microversion 2.39. Therefore,
+# client-side code shouldn't rely on those APIs in the compute images client
+# past those microversions and should instead use the Glance images client
+# directly.
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
index 37a084f..d580f2c 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -26,7 +26,7 @@
         'extra_specs': {
             'type': 'object',
             'patternProperties': {
-                '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+                r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
             }
         }
     },
@@ -37,3 +37,35 @@
 get_server = copy.deepcopy(servers226.get_server)
 get_server['response_body']['properties']['server'][
     'properties'].update({'flavor': flavor})
+list_servers_detail = copy.deepcopy(servers226.list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+    'properties'].update({'flavor': flavor})
+
+update_server = copy.deepcopy(servers226.update_server)
+update_server['response_body']['properties']['server'][
+    'properties'].update({'flavor': flavor})
+
+rebuild_server = copy.deepcopy(servers226.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+    'properties'].update({'flavor': flavor})
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers226.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'properties'].update({'flavor': flavor})
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+show_server_diagnostics = copy.deepcopy(servers226.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers226.get_remote_consoles)
+list_tags = copy.deepcopy(servers226.list_tags)
+update_all_tags = copy.deepcopy(servers226.update_all_tags)
+delete_all_tags = copy.deepcopy(servers226.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers226.check_tag_existence)
+update_tag = copy.deepcopy(servers226.update_tag)
+delete_tag = copy.deepcopy(servers226.delete_tag)
+list_servers = copy.deepcopy(servers226.list_servers)
+attach_volume = copy.deepcopy(servers226.attach_volume)
+show_volume_attachment = copy.deepcopy(servers226.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers226.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_48/servers.py b/tempest/lib/api_schema/response/compute/v2_48/servers.py
index 5904758..e2e45bc 100644
--- a/tempest/lib/api_schema/response/compute/v2_48/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_48/servers.py
@@ -112,4 +112,23 @@
     }
 }
 
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+list_servers = copy.deepcopy(servers247.list_servers)
+get_remote_consoles = copy.deepcopy(servers247.get_remote_consoles)
+list_tags = copy.deepcopy(servers247.list_tags)
+update_all_tags = copy.deepcopy(servers247.update_all_tags)
+delete_all_tags = copy.deepcopy(servers247.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers247.check_tag_existence)
+update_tag = copy.deepcopy(servers247.update_tag)
+delete_tag = copy.deepcopy(servers247.delete_tag)
 get_server = copy.deepcopy(servers247.get_server)
+list_servers_detail = copy.deepcopy(servers247.list_servers_detail)
+update_server = copy.deepcopy(servers247.update_server)
+rebuild_server = copy.deepcopy(servers247.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers247.rebuild_server_with_admin_pass)
+attach_volume = copy.deepcopy(servers247.attach_volume)
+show_volume_attachment = copy.deepcopy(servers247.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers247.list_volume_attachments)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_53/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_53/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_53/services.py b/tempest/lib/api_schema/response/compute/v2_53/services.py
new file mode 100644
index 0000000..97b0c72
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_53/services.py
@@ -0,0 +1,71 @@
+# Copyright 2018 AT&T Corporation.
+# All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_11 import services \
+    as servicesv211
+
+# ***************** Schemas changed in microversion 2.53 *****************
+
+# NOTE(felipemonteiro): This is schema for microversion 2.53 which includes:
+#
+# * changing the service 'id' to 'string' type only
+# * adding update_service which supersedes enable_service, disable_service,
+#   disable_log_reason, update_forced_down.
+
+list_services = copy.deepcopy(servicesv211.list_services)
+# The ID of the service is a uuid, so v2.1 pattern does not apply.
+list_services['response_body']['properties']['services']['items'][
+    'properties']['id'] = {'type': 'string', 'format': 'uuid'}
+
+update_service = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'service': {
+                'type': 'object',
+                'properties': {
+                    'id': {'type': 'string', 'format': 'uuid'},
+                    'binary': {'type': 'string'},
+                    # disabled_reason can be null when status is enabled.
+                    'disabled_reason': {'type': ['string', 'null']},
+                    'host': {'type': 'string'},
+                    'state': {'type': 'string'},
+                    'status': {'type': 'string'},
+                    'updated_at': parameter_types.date_time,
+                    'zone': {'type': 'string'},
+                    'forced_down': {'type': 'boolean'}
+                },
+                'additionalProperties': False,
+                'required': ['id', 'binary', 'disabled_reason', 'host',
+                             'state', 'status', 'updated_at', 'zone',
+                             'forced_down']
+            }
+        },
+        'additionalProperties': False,
+        'required': ['service']
+    }
+}
+
+# **** Schemas unchanged in microversion 2.53 since microversion 2.11 ****
+# Note(felipemonteiro): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+enable_disable_service = copy.deepcopy(servicesv211.enable_disable_service)
+update_forced_down = copy.deepcopy(servicesv211.update_forced_down)
+disable_log_reason = copy.deepcopy(servicesv211.disable_log_reason)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_54/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_54/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_54/servers.py b/tempest/lib/api_schema/response/compute/v2_54/servers.py
new file mode 100644
index 0000000..2c2bff0
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_54/servers.py
@@ -0,0 +1,60 @@
+#    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 copy
+
+from tempest.lib.api_schema.response.compute.v2_48 import servers as servers248
+# ****** Schemas changed in microversion 2.54 *****************
+
+# Note(gmann): This is schema for microversion 2.54 which includes the
+# 'key_name' in the Response body of the following APIs:
+#    - ``POST '/servers/{server_id}/action (rebuild)``
+
+key_name = {
+    'oneOf': [
+        {'type': 'string', 'minLength': 1, 'maxLength': 255},
+        {'type': 'null'},
+    ]
+}
+
+rebuild_server = copy.deepcopy(servers248.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+    'properties'].update({'key_name': key_name})
+rebuild_server['response_body']['properties']['server'][
+    'required'].append('key_name')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers248.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'properties'].update({'key_name': key_name})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'required'].append('key_name')
+
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged in microversion 2.54 since microversion 2.48 ***
+get_server = copy.deepcopy(servers248.get_server)
+list_servers_detail = copy.deepcopy(servers248.list_servers_detail)
+update_server = copy.deepcopy(servers248.update_server)
+list_servers = copy.deepcopy(servers248.list_servers)
+show_server_diagnostics = copy.deepcopy(servers248.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers248.get_remote_consoles)
+list_tags = copy.deepcopy(servers248.list_tags)
+update_all_tags = copy.deepcopy(servers248.update_all_tags)
+delete_all_tags = copy.deepcopy(servers248.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers248.check_tag_existence)
+update_tag = copy.deepcopy(servers248.update_tag)
+delete_tag = copy.deepcopy(servers248.delete_tag)
+attach_volume = copy.deepcopy(servers248.attach_volume)
+show_volume_attachment = copy.deepcopy(servers248.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers248.list_volume_attachments)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_55/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_55/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_55/flavors.py b/tempest/lib/api_schema/response/compute/v2_55/flavors.py
new file mode 100644
index 0000000..554f43b
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_55/flavors.py
@@ -0,0 +1,120 @@
+# Copyright 2018 NEC 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.
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import flavors as flavorsv21
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+# Note(gmann): This is schema for microversion 2.55 which includes the
+# following changes:
+# Add new PUT API
+# Adds a ``description`` field to the following APIs response:
+#    - ``GET /flavors``
+#    - ``GET /flavors/detail``
+#    - ``GET /flavors/{flavor_id}``
+#    - ``POST /flavors``
+
+flavor_description = {
+    'type': ['string', 'null'],
+    'minLength': 0, 'maxLength': 65535
+}
+
+list_flavors = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavors': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'name': {'type': 'string'},
+                        'links': parameter_types.links,
+                        'id': {'type': 'string'},
+                        'description': flavor_description
+                    },
+                    'additionalProperties': False,
+                    'required': ['name', 'links', 'id', 'description']
+                }
+            },
+            'flavors_links': parameter_types.links
+        },
+        'additionalProperties': False,
+        # NOTE(gmann): flavors_links attribute is not necessary
+        # to be present always So it is not 'required'.
+        'required': ['flavors']
+    }
+}
+
+common_flavor_info = {
+    'type': 'object',
+    'properties': {
+        'name': {'type': 'string'},
+        'links': parameter_types.links,
+        'ram': {'type': 'integer'},
+        'vcpus': {'type': 'integer'},
+        # 'swap' attributes comes as integer value but if it is empty
+        # it comes as "". So defining type of as string and integer.
+        'swap': {'type': ['integer', 'string']},
+        'disk': {'type': 'integer'},
+        'id': {'type': 'string'},
+        'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
+        'os-flavor-access:is_public': {'type': 'boolean'},
+        'rxtx_factor': {'type': 'number'},
+        'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'},
+        'description': flavor_description
+    },
+    'additionalProperties': False,
+    # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
+    # 'OS-FLV-EXT-DATA' are API extensions. So they are not 'required'.
+    'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id',
+                 'description']
+}
+
+list_flavors_details = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavors': {
+                'type': 'array',
+                'items': common_flavor_info
+            },
+            # NOTE(gmann): flavors_links attribute is not necessary
+            # to be present always So it is not 'required'.
+            'flavors_links': parameter_types.links
+        },
+        'additionalProperties': False,
+        'required': ['flavors']
+    }
+}
+
+create_update_get_flavor_details = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavor': common_flavor_info
+        },
+        'additionalProperties': False,
+        'required': ['flavor']
+    }
+}
+
+# Note(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.1 ***
+delete_flavor = copy.deepcopy(flavorsv21.delete_flavor)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_57/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_57/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_57/limits.py b/tempest/lib/api_schema/response/compute/v2_57/limits.py
new file mode 100644
index 0000000..dcb8b3d
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_57/limits.py
@@ -0,0 +1,30 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_39 import limits as limitv239
+
+# Compute microversion 2.57:
+# remove attributes in get_limit:
+#    'maxPersonality',
+#    'maxPersonalitySize'
+
+get_limit = copy.deepcopy(limitv239.get_limit)
+
+for item in ['maxPersonality', 'maxPersonalitySize']:
+    get_limit['response_body']['properties']['limits']['properties'][
+        'absolute']['properties'].pop(item)
+    get_limit['response_body']['properties']['limits']['properties'][
+        'absolute']['required'].remove(item)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/quotas.py b/tempest/lib/api_schema/response/compute/v2_57/quotas.py
new file mode 100644
index 0000000..4664a1a
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_57/quotas.py
@@ -0,0 +1,53 @@
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_36 import quotas as quotasv236
+
+# Compute microversion 2.57:
+# remove attributes in quota_set:
+#    'injected_file_content_bytes',
+#    'injected_file_path_bytes',
+#    'injected_files'
+
+remove_item_list = ['injected_file_content_bytes', 'injected_file_path_bytes',
+                    'injected_files']
+
+update_quota_set = copy.deepcopy(quotasv236.update_quota_set)
+for item in remove_item_list:
+    update_quota_set['response_body']['properties']['quota_set'][
+        'properties'].pop(item)
+    update_quota_set['response_body']['properties']['quota_set'][
+        'required'].remove(item)
+
+get_quota_set = copy.deepcopy(quotasv236.get_quota_set)
+for item in remove_item_list:
+    get_quota_set['response_body']['properties']['quota_set'][
+        'properties'].pop(item)
+    get_quota_set['response_body']['properties']['quota_set'][
+        'required'].remove(item)
+
+get_quota_set_details = copy.deepcopy(quotasv236.get_quota_set_details)
+for item in remove_item_list:
+    get_quota_set_details['response_body']['properties']['quota_set'][
+        'properties'].pop(item)
+    get_quota_set_details['response_body']['properties']['quota_set'][
+        'required'].remove(item)
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.1 ***
+delete_quota = copy.deepcopy(quotasv236.delete_quota)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/servers.py b/tempest/lib/api_schema/response/compute/v2_57/servers.py
new file mode 100644
index 0000000..aa57d25
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_57/servers.py
@@ -0,0 +1,64 @@
+#    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 copy
+
+from tempest.lib.api_schema.response.compute.v2_54 import servers as servers254
+# ****** Schemas changed in microversion 2.57 *****************
+
+# Note(gmann): This is schema for microversion 2.57 which includes the
+# 'user_data' in the Response body of the following APIs:
+#    - ``POST '/servers/{server_id}/action (rebuild)``
+
+user_data = {
+    'oneOf': [
+        {
+            'type': 'string',
+            'format': 'base64',
+            'maxLength': 65535
+        },
+        {'type': 'null'}
+    ]
+}
+
+rebuild_server = copy.deepcopy(servers254.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+    'properties'].update({'user_data': user_data})
+rebuild_server['response_body']['properties']['server'][
+    'required'].append('user_data')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers254.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'properties'].update({'user_data': user_data})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'required'].append('user_data')
+
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged in microversion 2.57 since microversion 2.54 ***
+get_server = copy.deepcopy(servers254.get_server)
+list_servers_detail = copy.deepcopy(servers254.list_servers_detail)
+update_server = copy.deepcopy(servers254.update_server)
+list_servers = copy.deepcopy(servers254.list_servers)
+show_server_diagnostics = copy.deepcopy(servers254.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers254.get_remote_consoles)
+list_tags = copy.deepcopy(servers254.list_tags)
+update_all_tags = copy.deepcopy(servers254.update_all_tags)
+delete_all_tags = copy.deepcopy(servers254.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers254.check_tag_existence)
+update_tag = copy.deepcopy(servers254.update_tag)
+delete_tag = copy.deepcopy(servers254.delete_tag)
+attach_volume = copy.deepcopy(servers254.attach_volume)
+show_volume_attachment = copy.deepcopy(servers254.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers254.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_6/servers.py b/tempest/lib/api_schema/response/compute/v2_6/servers.py
index 29b3e86..922bf79 100644
--- a/tempest/lib/api_schema/response/compute/v2_6/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_6/servers.py
@@ -16,9 +16,21 @@
 
 from tempest.lib.api_schema.response.compute.v2_3 import servers
 
+# NOTE: Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.3 ******
 list_servers = copy.deepcopy(servers.list_servers)
 get_server = copy.deepcopy(servers.get_server)
 list_servers_detail = copy.deepcopy(servers.list_servers_detail)
+update_server = copy.deepcopy(servers.update_server)
+rebuild_server = copy.deepcopy(servers.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers.rebuild_server_with_admin_pass)
+show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics)
+attach_volume = copy.deepcopy(servers.attach_volume)
+show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
 
 # NOTE: The consolidated remote console API got introduced with v2.6
 # with bp/consolidate-console-api. See Nova commit 578bafeda
@@ -31,7 +43,7 @@
                 'type': 'object',
                 'properties': {
                     'protocol': {'enum': ['vnc', 'rdp', 'serial', 'spice']},
-                    'type': {'enum': ['novnc', 'xpvnc', 'rdp-html5',
+                    'type': {'enum': ['novnc', 'xvpvnc', 'rdp-html5',
                                       'spice-html5', 'serial']},
                     'url': {
                         'type': 'string',
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_61/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_61/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_61/flavors.py b/tempest/lib/api_schema/response/compute/v2_61/flavors.py
new file mode 100644
index 0000000..5119466
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_61/flavors.py
@@ -0,0 +1,106 @@
+# Copyright 2018 NEC 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_55 import flavors \
+    as flavorsv255
+
+# ****** Schemas changed in microversion 2.61 *****************
+
+# Note(gmann): This is schema for microversion 2.61 which includes the
+# Flavor extra_specs in the Response body of the following APIs:
+#    - ``PUT /flavors/{flavor_id}``
+#    - ``GET /flavors/detail``
+#    - ``GET /flavors/{flavor_id}``
+#    - ``POST /flavors``
+
+flavor_description = {
+    'type': ['string', 'null'],
+    'minLength': 0, 'maxLength': 65535
+}
+
+flavor_extra_specs = {
+    'type': 'object',
+    'patternProperties': {
+        '^[a-zA-Z0-9-_:. ]{1,255}$': {'type': 'string'}
+    }
+}
+
+common_flavor_info = {
+    'type': 'object',
+    'properties': {
+        'name': {'type': 'string'},
+        'links': parameter_types.links,
+        'ram': {'type': 'integer'},
+        'vcpus': {'type': 'integer'},
+        # 'swap' attributes comes as integer value but if it is empty
+        # it comes as "". So defining type of as string and integer.
+        'swap': {'type': ['integer', 'string']},
+        'disk': {'type': 'integer'},
+        'id': {'type': 'string'},
+        'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
+        'os-flavor-access:is_public': {'type': 'boolean'},
+        'rxtx_factor': {'type': 'number'},
+        'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'},
+        'description': flavor_description,
+        'extra_specs': flavor_extra_specs
+    },
+    'additionalProperties': False,
+    # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
+    # 'OS-FLV-EXT-DATA' are API extensions. so they are not 'required'.
+    'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id',
+                 'description']
+}
+
+list_flavors_details = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavors': {
+                'type': 'array',
+                'items': common_flavor_info
+            },
+            # NOTE(gmann): flavors_links attribute is not necessary
+            # to be present always so it is not 'required'.
+            'flavors_links': parameter_types.links
+        },
+        'additionalProperties': False,
+        'required': ['flavors']
+    }
+}
+
+create_update_get_flavor_details = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavor': common_flavor_info
+        },
+        'additionalProperties': False,
+        'required': ['flavor']
+    }
+}
+
+# ****** Schemas unchanged in microversion 2.61 since microversion 2.55 ***
+# Note(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.55 ***
+list_flavors = copy.deepcopy(flavorsv255.list_flavors)
+
+# ****** Schemas unchanged since microversion 2.1 ***
+delete_flavor = copy.deepcopy(flavorsv255.delete_flavor)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_63/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_63/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_63/servers.py b/tempest/lib/api_schema/response/compute/v2_63/servers.py
new file mode 100644
index 0000000..01910aa
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_63/servers.py
@@ -0,0 +1,78 @@
+#    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 copy
+
+from tempest.lib.api_schema.response.compute.v2_57 import servers as servers257
+
+# Nova microversion 2.63 adds 'trusted_image_certificates' (a list of
+# certificate IDs) to the server rebuild and servers details responses.
+
+
+trusted_certs = {
+    'type': ['array', 'null'],
+    'minItems': 1,
+    'maxItems': 50,
+    'uniqueItems': True,
+    'items': {
+        'type': 'string',
+        'minLength': 1
+    }
+}
+
+list_servers_detail = copy.deepcopy(servers257.list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+list_servers_detail['response_body']['properties']['servers']['items'][
+    'required'].append('trusted_image_certificates')
+
+rebuild_server = copy.deepcopy(servers257.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+rebuild_server['response_body']['properties']['server'][
+    'required'].append('trusted_image_certificates')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers257.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'required'].append('trusted_image_certificates')
+
+update_server = copy.deepcopy(servers257.update_server)
+update_server['response_body']['properties']['server'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+update_server['response_body']['properties']['server'][
+    'required'].append('trusted_image_certificates')
+
+get_server = copy.deepcopy(servers257.get_server)
+get_server['response_body']['properties']['server'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+get_server['response_body']['properties']['server'][
+    'required'].append('trusted_image_certificates')
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.57 ***
+list_servers = copy.deepcopy(servers257.list_servers)
+show_server_diagnostics = copy.deepcopy(servers257.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers257.get_remote_consoles)
+list_tags = copy.deepcopy(servers257.list_tags)
+update_all_tags = copy.deepcopy(servers257.update_all_tags)
+delete_all_tags = copy.deepcopy(servers257.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers257.check_tag_existence)
+update_tag = copy.deepcopy(servers257.update_tag)
+delete_tag = copy.deepcopy(servers257.delete_tag)
+attach_volume = copy.deepcopy(servers257.attach_volume)
+show_volume_attachment = copy.deepcopy(servers257.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers257.list_volume_attachments)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_70/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_70/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_70/servers.py b/tempest/lib/api_schema/response/compute/v2_70/servers.py
new file mode 100644
index 0000000..5ca4cc8
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_70/servers.py
@@ -0,0 +1,80 @@
+#    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 copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import servers as servers2_1
+from tempest.lib.api_schema.response.compute.v2_63 import servers as servers263
+
+
+###########################################################################
+#
+# 2.70:
+#
+# Exposes virtual device tags for volume attachments and virtual interfaces
+# (ports). A tag parameter is added to the response body for the following
+# APIs:
+#
+# Volumes
+#
+# - GET /servers/{server_id}/os-volume_attachments (list)
+# - GET /servers/{server_id}/os-volume_attachments/{volume_id} (show)
+# - POST /servers/{server_id}/os-volume_attachments (attach)
+#
+# Ports
+#
+# - GET /servers/{server_id}/os-interface (list)
+# - GET /servers/{server_id}/os-interface/{port_id} (show)
+# - POST /servers/{server_id}/os-interface (attach)
+#
+###########################################################################
+
+attach_volume = copy.deepcopy(servers2_1.attach_volume)
+attach_volume['response_body']['properties']['volumeAttachment'][
+    'properties'].update({'tag': {'type': ['string', 'null']}})
+attach_volume['response_body']['properties']['volumeAttachment'][
+    'required'].append('tag')
+
+show_volume_attachment = copy.deepcopy(servers2_1.show_volume_attachment)
+show_volume_attachment['response_body']['properties']['volumeAttachment'][
+    'properties'].update({'tag': {'type': ['string', 'null']}})
+show_volume_attachment['response_body']['properties'][
+    'volumeAttachment']['required'].append('tag')
+
+list_volume_attachments = copy.deepcopy(servers2_1.list_volume_attachments)
+list_volume_attachments['response_body']['properties']['volumeAttachments'][
+    'items']['properties'].update({'tag': {'type': ['string', 'null']}})
+list_volume_attachments['response_body']['properties'][
+    'volumeAttachments']['items']['required'].append('tag')
+
+# TODO(mriedem): Handle the os-interface changes when there is a test that
+# needs them from this microversion onward.
+
+# NOTE(lajoskatona): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.63 ***
+list_servers_detail = copy.deepcopy(servers263.list_servers_detail)
+rebuild_server = copy.deepcopy(servers263.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers263.rebuild_server_with_admin_pass)
+update_server = copy.deepcopy(servers263.update_server)
+get_server = copy.deepcopy(servers263.get_server)
+list_servers = copy.deepcopy(servers263.list_servers)
+show_server_diagnostics = copy.deepcopy(servers263.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers263.get_remote_consoles)
+list_tags = copy.deepcopy(servers263.list_tags)
+update_all_tags = copy.deepcopy(servers263.update_all_tags)
+delete_all_tags = copy.deepcopy(servers263.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers263.check_tag_existence)
+update_tag = copy.deepcopy(servers263.update_tag)
+delete_tag = copy.deepcopy(servers263.delete_tag)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_71/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_71/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_71/servers.py b/tempest/lib/api_schema/response/compute/v2_71/servers.py
new file mode 100644
index 0000000..0c526fb
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_71/servers.py
@@ -0,0 +1,81 @@
+#    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 copy
+
+from tempest.lib.api_schema.response.compute.v2_70 import servers as servers270
+
+
+###########################################################################
+#
+# 2.71:
+#
+# The server_groups parameter will be in the response body of the following
+# APIs to list the server groups to which the server belongs:
+#
+# - GET /servers/{server_id} (show)
+# - PUT /servers/{server_id} (update)
+# - POST /servers/{server_id}/action (rebuild)
+#
+###########################################################################
+
+# The "server_groups" parameter will always be present and contain at most one
+# UUID entry.
+server_groups = {
+    'type': 'array',
+    'minItems': 0,
+    'maxItems': 1,
+    'items': {
+        'type': 'string',
+        'format': 'uuid'
+    }
+}
+
+rebuild_server = copy.deepcopy(servers270.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+    'properties'].update({'server_groups': server_groups})
+rebuild_server['response_body']['properties']['server'][
+    'required'].append('server_groups')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers270.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'properties'].update({'server_groups': server_groups})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'required'].append('server_groups')
+
+update_server = copy.deepcopy(servers270.update_server)
+update_server['response_body']['properties']['server'][
+    'properties'].update({'server_groups': server_groups})
+update_server['response_body']['properties']['server'][
+    'required'].append('server_groups')
+
+get_server = copy.deepcopy(servers270.get_server)
+get_server['response_body']['properties']['server'][
+    'properties'].update({'server_groups': server_groups})
+get_server['response_body']['properties']['server'][
+    'required'].append('server_groups')
+
+# NOTE(lajoskatona): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.70 ***
+list_servers_details = copy.deepcopy(servers270.list_servers_detail)
+list_servers = copy.deepcopy(servers270.list_servers)
+show_server_diagnostics = copy.deepcopy(servers270.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers270.get_remote_consoles)
+list_tags = copy.deepcopy(servers270.list_tags)
+update_all_tags = copy.deepcopy(servers270.update_all_tags)
+delete_all_tags = copy.deepcopy(servers270.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers270.check_tag_existence)
+update_tag = copy.deepcopy(servers270.update_tag)
+delete_tag = copy.deepcopy(servers270.delete_tag)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_8/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_8/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_8/servers.py b/tempest/lib/api_schema/response/compute/v2_8/servers.py
new file mode 100644
index 0000000..3dbab3f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_8/servers.py
@@ -0,0 +1,40 @@
+# Copyright 2018 AT&T Corporation.  All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_6 import servers
+
+# 2.8: Add 'mks' protocol and 'webmks' type for remote consoles.
+get_remote_consoles = copy.deepcopy(servers.get_remote_consoles)
+get_remote_consoles['response_body']['properties']['remote_console'][
+    'properties']['protocol']['enum'].append('mks')
+get_remote_consoles['response_body']['properties']['remote_console'][
+    'properties']['type']['enum'].append('webmks')
+
+# NOTE: Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.6 ******
+list_servers = copy.deepcopy(servers.list_servers)
+get_server = copy.deepcopy(servers.get_server)
+list_servers_detail = copy.deepcopy(servers.list_servers_detail)
+update_server = copy.deepcopy(servers.update_server)
+rebuild_server = copy.deepcopy(servers.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers.rebuild_server_with_admin_pass)
+show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics)
+attach_volume = copy.deepcopy(servers.attach_volume)
+show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index e260e48..ee0313d 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -14,9 +14,7 @@
 
 import copy
 
-from tempest.lib.api_schema.response.compute.v2_6 import servers
-
-list_servers = copy.deepcopy(servers.list_servers)
+from tempest.lib.api_schema.response.compute.v2_8 import servers
 
 get_server = copy.deepcopy(servers.get_server)
 get_server['response_body']['properties']['server'][
@@ -29,3 +27,33 @@
     'properties'].update({'locked': {'type': 'boolean'}})
 list_servers_detail['response_body']['properties']['servers']['items'][
     'required'].append('locked')
+
+update_server = copy.deepcopy(servers.update_server)
+update_server['response_body']['properties']['server'][
+    'properties'].update({'locked': {'type': 'boolean'}})
+update_server['response_body']['properties']['server'][
+    'required'].append('locked')
+
+rebuild_server = copy.deepcopy(servers.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+    'properties'].update({'locked': {'type': 'boolean'}})
+rebuild_server['response_body']['properties']['server'][
+    'required'].append('locked')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'properties'].update({'locked': {'type': 'boolean'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'required'].append('locked')
+
+# NOTE: Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.8 ******
+list_servers = copy.deepcopy(servers.list_servers)
+show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers.get_remote_consoles)
+attach_volume = copy.deepcopy(servers.attach_volume)
+show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/volume/qos.py b/tempest/lib/api_schema/response/volume/qos.py
new file mode 100644
index 0000000..d1b3910
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/qos.py
@@ -0,0 +1,123 @@
+# 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.
+
+show_qos = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'qos_specs': {
+                'type': 'object',
+                'properties': {
+                    'name': {'type': 'string'},
+                    'id': {'type': 'string', 'format': 'uuid'},
+                    'consumer': {'type': 'string'},
+                    'specs': {'type': ['object', 'null']},
+                },
+                'additionalProperties': False,
+                'required': ['name', 'id', 'specs']
+            },
+            'links': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'href': {'type': 'string',
+                                 'format': 'uri'},
+                        'rel': {'type': 'string'},
+                    },
+                    'additionalProperties': False,
+                    'required': ['href', 'rel']
+                }
+            }
+        },
+        'additionalProperties': False,
+        'required': ['qos_specs', 'links']
+    }
+}
+
+delete_qos = {'status_code': [202]}
+
+list_qos = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'qos_specs': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'specs': {
+                            'type': 'object',
+                            'patternProperties': {'^.+$': {'type': 'string'}}
+                        },
+                        'consumer': {'type': 'string'},
+                        'id': {'type': 'string', 'format': 'uuid'},
+                        'name': {'type': 'string'}
+                    },
+                    'additionalProperties': False,
+                    'required': ['specs', 'id', 'name']
+                }
+            }
+        },
+        'additionalProperties': False,
+        'required': ['qos_specs']
+    }
+}
+
+set_qos_key = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'qos_specs': {
+                'type': 'object',
+                'patternProperties': {'^.+$': {'type': 'string'}}
+            },
+        },
+        'additionalProperties': False,
+        'required': ['qos_specs']
+    }
+}
+
+unset_qos_key = {'status_code': [202]}
+associate_qos = {'status_code': [202]}
+
+show_association_qos = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'qos_associations': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'association_type': {'type': 'string'},
+                        'id': {'type': 'string', 'format': 'uuid'},
+                        'name': {'type': 'string'}
+                    },
+                    'additionalProperties': False,
+                    'required': ['association_type', 'id', 'name']
+                }
+            },
+        },
+        'additionalProperties': False,
+        'required': ['qos_associations']
+    }
+}
+
+disassociate_qos = {'status_code': [202]}
+disassociate_all_qos = {'status_code': [202]}
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 2dd9d00..8e6d3d5 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -324,7 +324,7 @@
                 pass
         if expiry is None:
             raise ValueError(
-                "time data '{data}' does not match any of the"
+                "time data '{data}' does not match any of the "
                 "expected formats: {formats}".format(
                     data=expiry_string, formats=self.EXPIRY_DATE_FORMATS))
         return expiry
diff --git a/tempest/lib/base.py b/tempest/lib/base.py
index 33a32ee..3be55c0 100644
--- a/tempest/lib/base.py
+++ b/tempest/lib/base.py
@@ -43,8 +43,7 @@
         super(BaseTestCase, self).setUp()
         if not self.setUpClassCalled:
             raise RuntimeError("setUpClass does not calls the super's "
-                               "setUpClass in the "
-                               + self.__class__.__name__)
+                               "setUpClass in {!r}".format(type(self)))
         test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
         try:
             test_timeout = int(test_timeout)
@@ -62,7 +61,7 @@
             stderr = self.useFixture(fixtures.StringStream('stderr')).stream
             self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
         if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
-            os.environ.get('OS_LOG_CAPTURE') != '0'):
+                os.environ.get('OS_LOG_CAPTURE') != '0'):
             self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
                                                    format=self.log_format,
                                                    level=None))
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
index f39ecbc..d8c776b 100644
--- a/tempest/lib/cli/base.py
+++ b/tempest/lib/cli/base.py
@@ -58,8 +58,6 @@
     if six.PY2:
         cmd = cmd.encode('utf-8')
     cmd = shlex.split(cmd)
-    result = ''
-    result_err = ''
     stdout = subprocess.PIPE
     stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
     proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
@@ -101,12 +99,15 @@
     :type project_domain_name: string
     :param project_domain_id: Project's domain ID
     :type project_domain_id: string
+    :param identity_api_version: Version of the Identity API
+    :type identity_api_version: string
     """
 
     def __init__(self, username='', password='', tenant_name='', uri='',
                  cli_dir='', insecure=False, prefix='', user_domain_name=None,
                  user_domain_id=None, project_domain_name=None,
-                 project_domain_id=None, *args, **kwargs):
+                 project_domain_id=None, identity_api_version=None, *args,
+                 **kwargs):
         """Initialize a new CLIClient object."""
         super(CLIClient, self).__init__()
         self.cli_dir = cli_dir if cli_dir else '/usr/bin'
@@ -120,6 +121,7 @@
         self.user_domain_id = user_domain_id
         self.project_domain_name = project_domain_name
         self.project_domain_id = project_domain_id
+        self.identity_api_version = identity_api_version
 
     def nova(self, action, flags='', params='', fail_ok=False,
              endpoint_type='publicURL', merge_stderr=False):
@@ -267,7 +269,7 @@
         :param merge_stderr: if True the stderr buffer is merged into stdout
         :type merge_stderr: boolean
         """
-        flags += ' --endpoint-type %s' % endpoint_type
+        flags += ' --os-endpoint-type %s' % endpoint_type
         return self.cmd_with_auth(
             'cinder', action, flags, params, fail_ok, merge_stderr)
 
@@ -374,12 +376,15 @@
         :param merge_stderr:  if True the stderr buffer is merged into stdout
         :type merge_stderr: boolean
         """
-        creds = ('--os-username %s --os-tenant-name %s --os-password %s '
+        creds = ('--os-username %s --os-project-name %s --os-password %s '
                  '--os-auth-url %s' %
                  (self.username,
                   self.tenant_name,
                   self.password,
                   self.uri))
+        if self.identity_api_version:
+            creds += ' --os-identity-api-version %s' % (
+                self.identity_api_version)
         if self.user_domain_name is not None:
             creds += ' --os-user-domain-name %s' % self.user_domain_name
         if self.user_domain_id is not None:
diff --git a/tempest/lib/cli/output_parser.py b/tempest/lib/cli/output_parser.py
index 2edd5c1..45d41c7 100644
--- a/tempest/lib/cli/output_parser.py
+++ b/tempest/lib/cli/output_parser.py
@@ -25,7 +25,7 @@
 LOG = logging.getLogger(__name__)
 
 
-delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
+delimiter_line = re.compile(r'^\+\-[\+\-]+\-\+$')
 
 
 def details_multiple(output_lines, with_label=False):
@@ -37,8 +37,8 @@
     items = []
     tables_ = tables(output_lines)
     for table_ in tables_:
-        if ('Property' not in table_['headers']
-            or 'Value' not in table_['headers']):
+        if ('Property' not in table_['headers'] or
+                'Value' not in table_['headers']):
             raise exceptions.InvalidStructure()
         item = {}
         for value in table_['values']:
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index 101d692..71ecb32 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -103,14 +103,14 @@
     def _modules_search(self):
         """Recursive search for python modules in base package"""
         modules = []
-        for root, dirs, files in os.walk(self.base_path):
+        for root, _, files in os.walk(self.base_path):
             if not os.path.exists(os.path.join(root, '__init__.py')):
                 continue
             root_package = self._path_to_package(root)
             for item in files:
                 if item.endswith('.py'):
                     module_name = '.'.join((root_package,
-                                           os.path.splitext(item)[0]))
+                                            os.path.splitext(item)[0]))
                     if not module_name.startswith(UNIT_TESTS_EXCLUDE):
                         modules.append(module_name)
         return modules
@@ -121,10 +121,10 @@
         idempotent_id = None
         for decorator in test_node.decorator_list:
             if (hasattr(decorator, 'func') and
-                hasattr(decorator.func, 'attr') and
-                decorator.func.attr == DECORATOR_NAME and
-                hasattr(decorator.func, 'value') and
-                decorator.func.value.id == DECORATOR_MODULE):
+                    hasattr(decorator.func, 'attr') and
+                    decorator.func.attr == DECORATOR_NAME and
+                    hasattr(decorator.func, 'value') and
+                    decorator.func.value.id == DECORATOR_MODULE):
                 for arg in decorator.args:
                     idempotent_id = ast.literal_eval(arg)
         return idempotent_id
@@ -165,8 +165,8 @@
 
     @staticmethod
     def _is_test_method(node):
-        return (node.__class__ is ast.FunctionDef
-                and node.name.startswith('test_'))
+        return (node.__class__ is ast.FunctionDef and
+                node.name.startswith('test_'))
 
     @staticmethod
     def _next_node(body, node):
@@ -233,8 +233,8 @@
                           if self._is_test_case(module, node))
             for node in test_cases:
                 for subnode in filter(self._is_test_method, node.body):
-                        test_name = '%s.%s' % (node.name, subnode.name)
-                        tests[module_name]['tests'][test_name] = subnode
+                    test_name = '%s.%s' % (node.name, subnode.name)
+                    tests[module_name]['tests'][test_name] = subnode
         return tests
 
     @staticmethod
@@ -358,5 +358,6 @@
                  "Run 'tox -v -e uuidgen' to automatically fix tests with\n"
                  "missing @decorators.idempotent_id decorators.")
 
+
 if __name__ == '__main__':
     run()
diff --git a/tempest/lib/common/api_microversion_fixture.py b/tempest/lib/common/api_microversion_fixture.py
new file mode 100644
index 0000000..3837138
--- /dev/null
+++ b/tempest/lib/common/api_microversion_fixture.py
@@ -0,0 +1,82 @@
+# Copyright 2019 NEC 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.
+
+import fixtures
+
+from tempest.lib.services.compute import base_compute_client
+from tempest.lib.services.placement import base_placement_client
+from tempest.lib.services.volume import base_client as base_volume_client
+
+
+class APIMicroversionFixture(fixtures.Fixture):
+    """API Microversion Fixture to set service microversion.
+
+    This class provides the fixture to set and reset the microversion
+    on service client. Service client has global variable to set the
+    microversion for that service API request.
+    For example: base_compute_client.COMPUTE_MICROVERSION
+    Global variable is always risky to set directly which can affect the
+    other test's API request also. This class provides a way to reset the
+    service microversion once test finish the API request.
+    This class can be used with useFixture: Example::
+
+        def setUp(self):
+            super(BaseV2ComputeTest, self).setUp()
+            self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+                compute_microversion=self.compute_request_microversion))
+
+    Or you can set microversion on multiple services together::
+
+        def setUp(self):
+            super(ScenarioTest, self).setUp()
+            self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+                compute_microversion=self.compute_request_microversion,
+                volume_microversion=self.volume_request_microversion))
+
+    Current supported services:
+    - Compute
+    - Volume
+    - Placement
+
+    :param str compute_microversion: microvesion to be set on compute
+                                     service clients
+    :param str volume_microversion: microvesion to be set on volume
+                                    service clients
+    :param str placement_microversion: microvesion to be set on placement
+                                       service clients
+    """
+
+    def __init__(self, compute_microversion=None, volume_microversion=None,
+                 placement_microversion=None):
+        self.compute_microversion = compute_microversion
+        self.volume_microversion = volume_microversion
+        self.placement_microversion = placement_microversion
+
+    def _setUp(self):
+        super(APIMicroversionFixture, self)._setUp()
+        if self.compute_microversion:
+            base_compute_client.COMPUTE_MICROVERSION = (
+                self.compute_microversion)
+        if self.volume_microversion:
+            base_volume_client.VOLUME_MICROVERSION = self.volume_microversion
+        if self.placement_microversion:
+            base_placement_client.PLACEMENT_MICROVERSION = (
+                self.placement_microversion)
+
+        self.addCleanup(self._reset_microversion)
+
+    def _reset_microversion(self):
+        base_compute_client.COMPUTE_MICROVERSION = None
+        base_volume_client.VOLUME_MICROVERSION = None
+        base_placement_client.PLACEMENT_MICROVERSION = None
diff --git a/tempest/lib/common/api_version_utils.py b/tempest/lib/common/api_version_utils.py
index bcb076b..d29362d 100644
--- a/tempest/lib/common/api_version_utils.py
+++ b/tempest/lib/common/api_version_utils.py
@@ -12,6 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import six
 import testtools
 
 from tempest.lib.common import api_version_request
@@ -54,7 +55,7 @@
     config_min_version = api_version_request.APIVersionRequest(cfg_min_version)
     config_max_version = api_version_request.APIVersionRequest(cfg_max_version)
     if ((min_version > max_version) or
-       (config_min_version > config_max_version)):
+        (config_min_version > config_max_version)):
         msg = ("Test Class versions [%s - %s]. "
                "Configuration versions [%s - %s]."
                % (min_version.get_string(),
@@ -108,10 +109,12 @@
 
     :param api_microversion_header_name: Microversion header name
             Example- "X-OpenStack-Nova-API-Version"
-    :param api_microversion: Microversion number like "2.10"
+    :param api_microversion: Microversion number like "2.10", type str.
     :param response_header: Response header where microversion is
             expected to be present.
     """
+    if not isinstance(api_microversion, six.string_types):
+        raise TypeError('api_microversion must be a string')
     api_microversion_header_name = api_microversion_header_name.lower()
     if (api_microversion_header_name not in response_header or
         api_microversion != response_header[api_microversion_header_name]):
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index 4f1a883..f27e926 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -338,15 +338,15 @@
                 credentials = self._create_creds(roles=credential_type)
             self._creds[str(credential_type)] = credentials
             # Maintained until tests are ported
-            LOG.info("Acquired dynamic creds:\n credentials: %s", credentials)
-            if (self.neutron_available and
-                self.create_networks):
+            LOG.info("Acquired dynamic creds:\n"
+                     " credentials: %s", credentials)
+            if (self.neutron_available and self.create_networks):
                 network, subnet, router = self._create_network_resources(
                     credentials.tenant_id)
                 credentials.set_resources(network=network, subnet=subnet,
                                           router=router)
-                LOG.info("Created isolated network resources for : \n"
-                         + " credentials: %s", credentials)
+                LOG.info("Created isolated network resources for:\n"
+                         " credentials: %s", credentials)
         return credentials
 
     def get_primary_creds(self):
diff --git a/tempest/lib/common/fixed_network.py b/tempest/lib/common/fixed_network.py
index e2054a4..926c3a4 100644
--- a/tempest/lib/common/fixed_network.py
+++ b/tempest/lib/common/fixed_network.py
@@ -24,7 +24,7 @@
     """Get a full network dict from just a network name
 
     :param str name: the name of the network to use
-    :param NetworksClient compute_networks_client: The network client
+    :param network.NetworksClient compute_networks_client: The network client
         object to use for making the network lists api request
     :return: The full dictionary for the network in question
     :rtype: dict
@@ -38,7 +38,12 @@
         raise exceptions.InvalidTestResource(type='network', name=name)
 
     networks = compute_networks_client.list_networks()['networks']
-    networks = [n for n in networks if n['label'] == name]
+    # NOTE(zhufl) compute networks_client uses 'label' as network name field,
+    # while neutron networks_client uses 'name' as network name field.
+    try:
+        networks = [n for n in networks if n['label'] == name]
+    except KeyError:
+        networks = [n for n in networks if n['name'] == name]
 
     # Check that a network exists, else raise an InvalidConfigurationException
     if len(networks) == 1:
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
index 738c37f..8c1a802 100644
--- a/tempest/lib/common/http.py
+++ b/tempest/lib/common/http.py
@@ -19,7 +19,8 @@
 
 class ClosingProxyHttp(urllib3.ProxyManager):
     def __init__(self, proxy_url, disable_ssl_certificate_validation=False,
-                 ca_certs=None, timeout=None):
+                 ca_certs=None, timeout=None, follow_redirects=True):
+        self.follow_redirects = follow_redirects
         kwargs = {}
 
         if disable_ssl_certificate_validation:
@@ -50,9 +51,14 @@
         new_headers = dict(original_headers, connection='close')
         new_kwargs = dict(kwargs, headers=new_headers)
 
-        # Follow up to 5 redirections. Don't raise an exception if
-        # it's exceeded but return the HTTP 3XX response instead.
-        retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+        if self.follow_redirects:
+            # Follow up to 5 redirections. Don't raise an exception if
+            # it's exceeded but return the HTTP 3XX response instead.
+            retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+        else:
+            # Do not follow redirections. Don't raise an exception if
+            # a redirect is found, but return the HTTP 3XX response instead.
+            retry = urllib3.util.Retry(redirect=False)
         r = super(ClosingProxyHttp, self).request(method, url, retries=retry,
                                                   *args, **new_kwargs)
         return Response(r), r.data
@@ -60,7 +66,8 @@
 
 class ClosingHttp(urllib3.poolmanager.PoolManager):
     def __init__(self, disable_ssl_certificate_validation=False,
-                 ca_certs=None, timeout=None):
+                 ca_certs=None, timeout=None, follow_redirects=True):
+        self.follow_redirects = follow_redirects
         kwargs = {}
 
         if disable_ssl_certificate_validation:
@@ -93,9 +100,14 @@
         new_headers = dict(original_headers, connection='close')
         new_kwargs = dict(kwargs, headers=new_headers)
 
-        # Follow up to 5 redirections. Don't raise an exception if
-        # it's exceeded but return the HTTP 3XX response instead.
-        retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+        if self.follow_redirects:
+            # Follow up to 5 redirections. Don't raise an exception if
+            # it's exceeded but return the HTTP 3XX response instead.
+            retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+        else:
+            # Do not follow redirections. Don't raise an exception if
+            # a redirect is found, but return the HTTP 3XX response instead.
+            retry = urllib3.util.Retry(redirect=False)
         r = super(ClosingHttp, self).request(method, url, retries=retry,
                                              *args, **new_kwargs)
         return Response(r), r.data
diff --git a/tempest/lib/common/jsonschema_validator.py b/tempest/lib/common/jsonschema_validator.py
index bbdf382..bbf5e89 100644
--- a/tempest/lib/common/jsonschema_validator.py
+++ b/tempest/lib/common/jsonschema_validator.py
@@ -12,9 +12,10 @@
 #    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 jsonschema
+from oslo_serialization import base64
 from oslo_utils import timeutils
+import six
 
 # JSON Schema validator and format checker used for JSON Schema validation
 JSONSCHEMA_VALIDATOR = jsonschema.Draft4Validator
@@ -37,3 +38,17 @@
         return False
     else:
         return True
+
+
+@jsonschema.FormatChecker.cls_checks('base64')
+def _validate_base64_format(instance):
+    try:
+        if isinstance(instance, six.text_type):
+            instance = instance.encode('utf-8')
+        base64.decode_as_bytes(instance)
+    except TypeError:
+        # The name must be string type. If instance isn't string type, the
+        # TypeError will be raised at here.
+        return False
+
+    return True
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index 83db513..1011504 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -273,7 +273,7 @@
             # NOTE(andreaf) Not all fields may be available on all credentials
             # so defaulting to None for that case.
             if all([getattr(creds, k, None) == hash_attributes.get(k, None) for
-                   k in init_attributes]):
+                    k in init_attributes]):
                 return _hash
         raise AttributeError('Invalid credentials %s' % creds)
 
@@ -344,11 +344,11 @@
         net_creds = cred_provider.TestResources(credential)
         net_clients = clients.ServiceClients(credentials=credential,
                                              identity_uri=self.identity_uri)
-        compute_network_client = net_clients.compute.NetworksClient()
+        networks_client = net_clients.network.NetworksClient()
         net_name = self.hash_dict['networks'].get(hash, None)
         try:
             network = fixed_network.get_network_from_name(
-                net_name, compute_network_client)
+                net_name, networks_client)
         except lib_exc.InvalidTestResource:
             network = {}
         net_creds.set_resources(network=network)
diff --git a/tempest/lib/common/profiler.py b/tempest/lib/common/profiler.py
new file mode 100644
index 0000000..1544337
--- /dev/null
+++ b/tempest/lib/common/profiler.py
@@ -0,0 +1,64 @@
+#    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 base64
+import hashlib
+import hmac
+import json
+
+from oslo_utils import encodeutils
+from oslo_utils import uuidutils
+
+_profiler = {}
+
+
+def enable(profiler_key, trace_id=None):
+    """Enable global profiler instance
+
+    :param profiler_key: the secret key used to enable profiling in services
+    :param trace_id: unique id of the trace, if empty the id is generated
+        automatically
+    """
+    _profiler['key'] = profiler_key
+    _profiler['uuid'] = trace_id or uuidutils.generate_uuid()
+
+
+def disable():
+    """Disable global profiler instance"""
+    _profiler.clear()
+
+
+def serialize_as_http_headers():
+    """Serialize profiler state as HTTP headers
+
+    This function corresponds to the one from osprofiler library.
+    :return: dictionary with 2 keys `X-Trace-Info` and `X-Trace-HMAC`.
+    """
+    p = _profiler
+    if not p:  # profiler is not enabled
+        return {}
+
+    info = {'base_id': p['uuid'], 'parent_id': p['uuid']}
+    trace_info = base64.urlsafe_b64encode(
+        encodeutils.to_utf8(json.dumps(info)))
+    trace_hmac = _sign(trace_info, p['key'])
+
+    return {
+        'X-Trace-Info': trace_info,
+        'X-Trace-HMAC': trace_hmac,
+    }
+
+
+def _sign(trace_info, key):
+    h = hmac.new(encodeutils.to_utf8(key), digestmod=hashlib.sha1)
+    h.update(trace_info)
+    return h.hexdigest()
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 22276d4..f076727 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -27,6 +27,7 @@
 
 from tempest.lib.common import http
 from tempest.lib.common import jsonschema_validator
+from tempest.lib.common import profiler
 from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions
 
@@ -70,6 +71,7 @@
     :param str http_timeout: Timeout in seconds to wait for the http request to
                              return
     :param str proxy_url: http proxy url to use.
+    :param bool follow_redirects: Set to false to stop following redirects.
     """
 
     # The version of the API this client implements
@@ -82,7 +84,7 @@
                  build_interval=1, build_timeout=60,
                  disable_ssl_certificate_validation=False, ca_certs=None,
                  trace_requests='', name=None, http_timeout=None,
-                 proxy_url=None):
+                 proxy_url=None, follow_redirects=True):
         self.auth_provider = auth_provider
         self.service = service
         self.region = region
@@ -107,11 +109,11 @@
             self.http_obj = http.ClosingProxyHttp(
                 proxy_url,
                 disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
-                timeout=http_timeout)
+                timeout=http_timeout, follow_redirects=follow_redirects)
         else:
             self.http_obj = http.ClosingHttp(
                 disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
-                timeout=http_timeout)
+                timeout=http_timeout, follow_redirects=follow_redirects)
 
     def get_headers(self, accept_type=None, send_type=None):
         """Return the default headers which will be used with outgoing requests
@@ -130,8 +132,10 @@
             accept_type = 'json'
         if send_type is None:
             send_type = 'json'
-        return {'Content-Type': 'application/%s' % send_type,
-                'Accept': 'application/%s' % accept_type}
+        headers = {'Content-Type': 'application/%s' % send_type,
+                   'Accept': 'application/%s' % accept_type}
+        headers.update(profiler.serialize_as_http_headers())
+        return headers
 
     def __str__(self):
         STRING_LIMIT = 80
@@ -281,7 +285,7 @@
     def get(self, url, headers=None, extra_headers=False):
         """Send a HTTP GET request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the get request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
@@ -296,7 +300,7 @@
     def delete(self, url, headers=None, body=None, extra_headers=False):
         """Send a HTTP DELETE request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the delete request to
         :param dict headers: The headers to use for the request
         :param dict body: the request body
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -312,7 +316,7 @@
     def patch(self, url, body, headers=None, extra_headers=False):
         """Send a HTTP PATCH request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the patch request to
         :param dict body: the request body
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -328,7 +332,7 @@
     def put(self, url, body, headers=None, extra_headers=False, chunked=False):
         """Send a HTTP PUT request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the put request to
         :param dict body: the request body
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -345,7 +349,7 @@
     def head(self, url, headers=None, extra_headers=False):
         """Send a HTTP HEAD request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the head request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
@@ -360,7 +364,7 @@
     def copy(self, url, headers=None, extra_headers=False):
         """Send a HTTP COPY request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the copy request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
@@ -373,7 +377,7 @@
         return self.request('COPY', url, extra_headers, headers)
 
     def get_versions(self):
-        """Get the versions on a endpoint from the keystone catalog
+        """Get the versions on an endpoint from the keystone catalog
 
         This method will make a GET request on the baseurl from the keystone
         catalog to return a list of API versions. It is expected that a GET
@@ -416,6 +420,8 @@
                           resp_body=None, extra=None):
         if 'X-Auth-Token' in req_headers:
             req_headers['X-Auth-Token'] = '<omitted>'
+        if 'X-Subject-Token' in req_headers:
+            req_headers['X-Subject-Token'] = '<omitted>'
         # A shallow copy is sufficient
         resp_log = resp.copy()
         if 'x-subject-token' in resp_log:
@@ -523,7 +529,7 @@
         if (resp.status == 205 and
             0 != len(set(resp.keys()) - set(('status',)) -
                      self.response_header_lc - self.general_header_lc)):
-                        raise exceptions.ResponseWithEntity()
+            raise exceptions.ResponseWithEntity()
         # NOTE(afazekas)
         # Now the swift sometimes (delete not empty container)
         # returns with non json error response, we can create new rest class
@@ -568,8 +574,10 @@
 
         :param str url: Full url to send the request
         :param str method: The HTTP verb to use for the request
-        :param str headers: Headers to use for the request if none are specifed
-                            the headers
+        :param dict headers: Headers to use for the request. If none are
+                             specified, then the headers returned from the
+                             get_headers() method are used. If the request
+                             explicitly requires no headers use an empty dict.
         :param str body: Body to send with the request
         :param bool chunked: sends the body with chunked encoding
         :rtype: tuple
@@ -603,8 +611,8 @@
                                    returned by the get_headers() method are to
                                    be used but additional headers are needed in
                                    the request pass them in as a dict.
-        :param dict headers: Headers to use for the request if none are
-                             specifed the headers returned from the
+        :param dict headers: Headers to use for the request. If none are
+                             specified, then the headers returned from the
                              get_headers() method are used. If the request
                              explicitly requires no headers use an empty dict.
         :param str body: Body to send with the request
@@ -621,10 +629,13 @@
         :raises Gone: If a 410 response code is received
         :raises Conflict: If a 409 response code is received
         :raises PreconditionFailed: If a 412 response code is received
-        :raises OverLimit: If a 413 response code is received and over_limit is
-                          not in the response body
+        :raises OverLimit: If a 413 response code is received and retry-after
+                           is not in the response body or its retry operation
+                           exceeds the limits defined by the server
         :raises RateLimitExceeded: If a 413 response code is received and
-                                   over_limit is in the response body
+                                   retry-after is in the response body and
+                                   its retry operation does not exceeds the
+                                   limits defined by the server
         :raises InvalidContentType: If a 415 response code is received
         :raises UnprocessableEntity: If a 422 response code is received
         :raises InvalidHTTPResponseBody: The response body wasn't valid JSON
diff --git a/tempest/lib/common/thread.py b/tempest/lib/common/thread.py
new file mode 100644
index 0000000..510fc36
--- /dev/null
+++ b/tempest/lib/common/thread.py
@@ -0,0 +1,29 @@
+# Copyright 2018 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# This make disable relative module import
+from __future__ import absolute_import
+
+
+import six
+
+if six.PY2:
+    # module thread is removed in Python 3
+    from thread import get_ident  # noqa: H237,F401
+
+else:
+    # On Python3 thread module has been deprecated and get_ident has been moved
+    # to threading module
+    from threading import get_ident  # noqa: F401
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index c5df590..7f94612 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -50,8 +50,7 @@
              (e.g. 'prefixfoo-namebar-154876201')
     :rtype: string
     """
-    randbits = str(random.randint(1, 0x7fffffff))
-    rand_name = randbits
+    rand_name = str(random.randint(1, 0x7fffffff))
     if name:
         rand_name = name + '-' + rand_name
     if prefix:
@@ -65,9 +64,9 @@
     :param int length: The length of password that you expect to set
                        (If it's smaller than 3, it's same as 3.)
     :return: a random password. The format is
-             '<random upper letter>-<random number>-<random special character>
-              -<random ascii letters or digit characters or special symbols>'
-             (e.g. 'G2*ac8&lKFFgh%2')
+        ``'<random upper letter>-<random number>-<random special character>
+        -<random ascii letters or digit characters or special symbols>'``
+        (e.g. ``G2*ac8&lKFFgh%2``)
     :rtype: string
     """
     upper = random.choice(string.ascii_uppercase)
@@ -171,7 +170,7 @@
     :rtype: string
     """
     return b''.join([six.int2byte(random.randint(0, 255))
-                    for i in range(size)])
+                     for i in range(size)])
 
 
 # Courtesy of http://stackoverflow.com/a/312464
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index cd4092b..8ac1d38 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -10,6 +10,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import functools
+import re
 import sys
 
 import netaddr
@@ -25,13 +27,14 @@
 
 def debug_ssh(function):
     """Decorator to generate extra debug info in case off SSH failure"""
+    @functools.wraps(function)
     def wrapper(self, *args, **kwargs):
         try:
             return function(self, *args, **kwargs)
         except Exception as e:
             caller = test_utils.find_test_caller() or "not found"
             if not isinstance(e, tempest.lib.exceptions.SSHTimeout):
-                message = ('Initializing SSH connection to %(ip)s failed. '
+                message = ('Executing command on %(ip)s failed. '
                            'Error: %(error)s' % {'ip': self.ip_address,
                                                  'error': e})
                 message = '(%s) %s' % (caller, message)
@@ -124,3 +127,27 @@
             cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
         cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
         return self.exec_command(cmd)
+
+    def mount_config_drive(self):
+        """Mount the config drive inside a virtual machine
+
+        This method will not unmount the config drive, so unmount_config_drive
+        must be used for cleanup.
+        """
+        cmd_blkid = 'blkid | grep -i config-2'
+        result = self.exec_command(cmd_blkid)
+        dev_name = re.match('([^:]+)', result).group()
+
+        try:
+            self.exec_command('sudo mount %s /mnt' % dev_name)
+        except tempest.lib.exceptions.SSHExecCommandFailed:
+            # So the command failed, let's try to know why and print some
+            # useful information.
+            lsblk = self.exec_command('sudo lsblk --fs --ascii')
+            LOG.error("Mounting %s on /mnt failed. Right after the "
+                      "failure 'lsblk' in the guest reported:\n%s",
+                      dev_name, lsblk)
+            raise
+
+    def unmount_config_drive(self):
+        self.exec_command('sudo umount /mnt')
diff --git a/tempest/lib/common/utils/misc.py b/tempest/lib/common/utils/misc.py
index f13b4c8..a0b0c0a 100644
--- a/tempest/lib/common/utils/misc.py
+++ b/tempest/lib/common/utils/misc.py
@@ -12,11 +12,6 @@
 #    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_log import log as logging
-
-from tempest.lib.common.utils import test_utils
-
-LOG = logging.getLogger(__name__)
 
 
 def singleton(cls):
@@ -28,10 +23,3 @@
             instances[cls] = cls()
         return instances[cls]
     return getinstance
-
-
-def find_test_caller(*args, **kwargs):
-    LOG.warning("tempest.lib.common.utils.misc.find_test_caller is deprecated "
-                "in favor of tempest.lib.common.utils.test_utils."
-                "find_test_caller")
-    test_utils.find_test_caller(*args, **kwargs)
diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py
index c2e93ee..2a9f3a9 100644
--- a/tempest/lib/common/utils/test_utils.py
+++ b/tempest/lib/common/utils/test_utils.py
@@ -102,13 +102,13 @@
     now = time.time()
     begin_time = now
     timeout = now + duration
+    func_name = getattr(func, '__name__', getattr(func.__class__, '__name__'))
     while now < timeout:
         if func(*args, **kwargs):
             LOG.debug("Call %s returns true in %f seconds",
-                      getattr(func, '__name__'), time.time() - begin_time)
+                      func_name, time.time() - begin_time)
             return True
         time.sleep(sleep_for)
         now = time.time()
-    LOG.debug("Call %s returns false in %f seconds",
-              getattr(func, '__name__'), duration)
+    LOG.debug("Call %s returns false in %f seconds", func_name, duration)
     return False
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index f82f707..808e0fb 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -15,56 +15,100 @@
 import functools
 import uuid
 
-import debtcollector.removals
 from oslo_log import log as logging
 import six
 import testtools
 
+from tempest.lib import exceptions as lib_exc
+
 LOG = logging.getLogger(__name__)
 
+_SUPPORTED_BUG_TYPES = {
+    'launchpad': 'https://launchpad.net/bugs/%s',
+    'storyboard': 'https://storyboard.openstack.org/#!/story/%s',
+}
+
+
+def _validate_bug_and_bug_type(bug, bug_type):
+    """Validates ``bug`` and ``bug_type`` values.
+
+    :param bug: bug number causing the test to skip (launchpad or storyboard)
+    :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+    :raises: InvalidParam if ``bug`` is not a digit or ``bug_type`` is not
+        a valid value
+    """
+    if not bug.isdigit():
+        invalid_param = '%s must be a valid %s number' % (bug, bug_type)
+        raise lib_exc.InvalidParam(invalid_param=invalid_param)
+    if bug_type not in _SUPPORTED_BUG_TYPES:
+        invalid_param = 'bug_type "%s" must be one of: %s' % (
+            bug_type, ', '.join(_SUPPORTED_BUG_TYPES.keys()))
+        raise lib_exc.InvalidParam(invalid_param=invalid_param)
+
+
+def _get_bug_url(bug, bug_type='launchpad'):
+    """Get the bug URL based on the ``bug_type`` and ``bug``
+
+    :param bug: The launchpad/storyboard bug number causing the test
+    :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+    :returns: Bug URL corresponding to ``bug_type`` value
+    """
+    _validate_bug_and_bug_type(bug, bug_type)
+    return _SUPPORTED_BUG_TYPES[bug_type] % bug
+
 
 def skip_because(*args, **kwargs):
     """A decorator useful to skip tests hitting known bugs
 
-    @param bug: bug number causing the test to skip
-    @param condition: optional condition to be True for the skip to have place
+    ``bug`` must be a number and ``condition`` must be true for the test to
+    skip.
+
+    :param bug: bug number causing the test to skip (launchpad or storyboard)
+    :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+    :param condition: optional condition to be True for the skip to have place
+    :raises: testtools.TestCase.skipException if ``condition`` is True and
+        ``bug`` is included
     """
     def decorator(f):
         @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
+        def wrapper(*func_args, **func_kwargs):
             skip = False
+            msg = ''
             if "condition" in kwargs:
                 if kwargs["condition"] is True:
                     skip = True
             else:
                 skip = True
             if "bug" in kwargs and skip is True:
-                if not kwargs['bug'].isdigit():
-                    raise ValueError('bug must be a valid bug number')
-                msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
+                bug = kwargs['bug']
+                bug_type = kwargs.get('bug_type', 'launchpad')
+                bug_url = _get_bug_url(bug, bug_type)
+                msg = "Skipped until bug: %s is resolved." % bug_url
                 raise testtools.TestCase.skipException(msg)
-            return f(self, *func_args, **func_kwargs)
+            return f(*func_args, **func_kwargs)
         return wrapper
     return decorator
 
 
-def related_bug(bug, status_code=None):
-    """A decorator useful to know solutions from launchpad bug reports
+def related_bug(bug, status_code=None, bug_type='launchpad'):
+    """A decorator useful to know solutions from launchpad/storyboard reports
 
-    @param bug: The launchpad bug number causing the test
-    @param status_code: The status code related to the bug report
+    :param bug: The launchpad/storyboard bug number causing the test bug
+    :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+    :param status_code: The status code related to the bug report
     """
     def decorator(f):
         @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
+        def wrapper(*func_args, **func_kwargs):
             try:
-                return f(self, *func_args, **func_kwargs)
+                return f(*func_args, **func_kwargs)
             except Exception as exc:
                 exc_status_code = getattr(exc, 'status_code', None)
                 if status_code is None or status_code == exc_status_code:
-                    LOG.error('Hints: This test was made for the bug %s. '
-                              'The failure could be related to '
-                              'https://launchpad.net/bugs/%s', bug, bug)
+                    if bug:
+                        LOG.error('Hints: This test was made for the bug_type '
+                                  '%s. The failure could be related to '
+                                  '%s', bug, _get_bug_url(bug, bug_type))
                 raise exc
         return wrapper
     return decorator
@@ -87,34 +131,22 @@
     return decorator
 
 
-@debtcollector.removals.remove(removal_version='Queen')
-class skip_unless_attr(object):
-    """Decorator to skip tests if a specified attr does not exists or False"""
-    def __init__(self, attr, msg=None):
-        self.attr = attr
-        self.message = msg or ("Test case attribute %s not found "
-                               "or False") % attr
-
-    def __call__(self, func):
-        @functools.wraps(func)
-        def _skipper(*args, **kw):
-            """Wrapped skipper function."""
-            testobj = args[0]
-            if not getattr(testobj, self.attr, False):
-                raise testtools.TestCase.skipException(self.message)
-            func(*args, **kw)
-        return _skipper
-
-
 def attr(**kwargs):
     """A decorator which applies the testtools attr decorator
 
     This decorator applies the testtools.testcase.attr if it is in the list of
     attributes to testtools we want to apply.
+
+    :param condition: Optional condition which if true will apply the attr. If
+        a condition is specified which is false the attr will not be applied to
+        the test function. If not specified, the attr is always applied.
     """
 
     def decorator(f):
-        if 'type' in kwargs and isinstance(kwargs['type'], str):
+        # Check to see if the attr should be conditional applied.
+        if 'condition' in kwargs and not kwargs.get('condition'):
+            return f
+        if 'type' in kwargs and isinstance(kwargs['type'], six.string_types):
             f = testtools.testcase.attr(kwargs['type'])(f)
         elif 'type' in kwargs and isinstance(kwargs['type'], list):
             for attr in kwargs['type']:
@@ -122,3 +154,45 @@
         return f
 
     return decorator
+
+
+def unstable_test(*args, **kwargs):
+    """A decorator useful to run tests hitting known bugs and skip it if fails
+
+    This decorator can be used in cases like:
+
+    * We have skipped tests with some bug and now bug is claimed to be fixed.
+      Now we want to check the test stability so we use this decorator.
+      The number of skipped cases with that bug can be counted to mark test
+      stable again.
+    * There is test which is failing often, but not always. If there is known
+      bug related to it, and someone is working on fix, this decorator can be
+      used instead of "skip_because". That will ensure that test is still run
+      so new debug data can be collected from jobs' logs but it will not make
+      life of other developers harder by forcing them to recheck jobs more
+      often.
+
+    ``bug`` must be a number for the test to skip.
+
+    :param bug: bug number causing the test to skip (launchpad or storyboard)
+    :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+    :raises: testtools.TestCase.skipException if test actually fails,
+        and ``bug`` is included
+    """
+    def decor(f):
+        @functools.wraps(f)
+        def inner(self, *func_args, **func_kwargs):
+            try:
+                return f(self, *func_args, **func_kwargs)
+            except Exception as e:
+                if "bug" in kwargs:
+                    bug = kwargs['bug']
+                    bug_type = kwargs.get('bug_type', 'launchpad')
+                    bug_url = _get_bug_url(bug, bug_type)
+                    msg = ("Marked as unstable and skipped because of bug: "
+                           "%s, failure was: %s") % (bug_url, e)
+                    raise testtools.TestCase.skipException(msg)
+                else:
+                    raise e
+        return inner
+    return decor
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 9b2e87e..13af890 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -96,7 +96,7 @@
 
 class Conflict(ClientRestClientException):
     status_code = 409
-    message = "An object with that identifier already exists"
+    message = "Conflict with state of target resource"
 
 
 class Gone(ClientRestClientException):
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 8918a8c..90debd9 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -18,7 +18,6 @@
 import importlib
 import inspect
 import sys
-import warnings
 
 from debtcollector import removals
 from oslo_log import log as logging
@@ -32,9 +31,9 @@
 from tempest.lib.services import image
 from tempest.lib.services import network
 from tempest.lib.services import object_storage
+from tempest.lib.services import placement
 from tempest.lib.services import volume
 
-warnings.simplefilter("once")
 LOG = logging.getLogger(__name__)
 
 
@@ -46,6 +45,7 @@
     """
     return {
         'compute': compute,
+        'placement': placement,
         'identity.v2': identity.v2,
         'identity.v3': identity.v3,
         'image.v1': image.v1,
@@ -331,7 +331,7 @@
         self.region = region
         # Check if passed or default credentials are valid
         if not self.credentials.is_valid():
-            raise exceptions.InvalidCredentials()
+            raise exceptions.InvalidCredentials(credentials)
         # Get the identity classes matching the provided credentials
         # TODO(andreaf) Define a new interface in Credentials to get
         # the API version from an instance
@@ -340,7 +340,9 @@
                     isinstance(self.credentials, auth.IDENTITY_VERSION[k][0])]
         # Zero matches or more than one are both not valid.
         if len(identity) != 1:
-            raise exceptions.InvalidCredentials()
+            msg = "Zero or %d ambiguous auth provider found. identity: %s, " \
+                "credentials: %s" % (len(identity), identity, credentials)
+            raise exceptions.InvalidCredentials(msg)
         self.auth_version, auth_provider_class = identity[0]
         self.dscv = disable_ssl_certificate_validation
         self.ca_certs = ca_certs
diff --git a/tempest/lib/services/compute/aggregates_client.py b/tempest/lib/services/compute/aggregates_client.py
index 713d7a3..57f5e4e 100644
--- a/tempest/lib/services/compute/aggregates_client.py
+++ b/tempest/lib/services/compute/aggregates_client.py
@@ -15,7 +15,10 @@
 
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.api_schema.response.compute.v2_1 import aggregates as schema
+from tempest.lib.api_schema.response.compute.v2_1 \
+    import aggregates as schema
+from tempest.lib.api_schema.response.compute.v2_41 \
+    import aggregates as schemav241
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services.compute import base_compute_client
@@ -23,10 +26,15 @@
 
 class AggregatesClient(base_compute_client.BaseComputeClient):
 
+    schema_versions_info = [
+        {'min': None, 'max': '2.40', 'schema': schema},
+        {'min': '2.41', 'max': None, 'schema': schemav241}]
+
     def list_aggregates(self):
         """Get aggregate list."""
         resp, body = self.get("os-aggregates")
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.list_aggregates, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -34,6 +42,7 @@
         """Get details of the given aggregate."""
         resp, body = self.get("os-aggregates/%s" % aggregate_id)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_aggregate, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -48,6 +57,7 @@
         resp, body = self.post('os-aggregates', post_body)
 
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.create_aggregate, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -62,12 +72,14 @@
         resp, body = self.put('os-aggregates/%s' % aggregate_id, put_body)
 
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.update_aggregate, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_aggregate(self, aggregate_id):
         """Delete the given aggregate."""
         resp, body = self.delete("os-aggregates/%s" % aggregate_id)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.delete_aggregate, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -94,6 +106,7 @@
         resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
                                post_body)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.aggregate_add_remove_host, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -108,6 +121,7 @@
         resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
                                post_body)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.aggregate_add_remove_host, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -122,5 +136,6 @@
         resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
                                post_body)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.aggregate_set_metadata, resp, body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index 0fb1991..5d2dd46 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -21,12 +21,21 @@
     as schema_access
 from tempest.lib.api_schema.response.compute.v2_1 import flavors_extra_specs \
     as schema_extra_specs
+from tempest.lib.api_schema.response.compute.v2_55 import flavors \
+    as schemav255
+from tempest.lib.api_schema.response.compute.v2_61 import flavors \
+    as schemav261
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
 
 class FlavorsClient(base_compute_client.BaseComputeClient):
 
+    schema_versions_info = [
+        {'min': None, 'max': '2.54', 'schema': schema},
+        {'min': '2.55', 'max': '2.60', 'schema': schemav255},
+        {'min': '2.61', 'max': None, 'schema': schemav261}]
+
     def list_flavors(self, detail=False, **params):
         """Lists flavors.
 
@@ -36,11 +45,12 @@
         https://developer.openstack.org/api-ref/compute/#list-flavors-with-details
         """
         url = 'flavors'
-        _schema = schema.list_flavors
-
+        schema = self.get_schema(self.schema_versions_info)
         if detail:
             url += '/detail'
             _schema = schema.list_flavors_details
+        else:
+            _schema = schema.list_flavors
         if params:
             url += '?%s' % urllib.urlencode(params)
 
@@ -58,7 +68,9 @@
         """
         resp, body = self.get("flavors/%s" % flavor_id)
         body = json.loads(body)
-        self.validate_response(schema.create_get_flavor_details, resp, body)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.create_update_get_flavor_details,
+                               resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def create_flavor(self, **kwargs):
@@ -77,7 +89,25 @@
         resp, body = self.post('flavors', post_body)
 
         body = json.loads(body)
-        self.validate_response(schema.create_get_flavor_details, resp, body)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.create_update_get_flavor_details,
+                               resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_flavor(self, flavor_id, **kwargs):
+        """Uodate the flavor or instance type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#update-flavor-description
+        """
+        put_body = json.dumps({'flavor': kwargs})
+        resp, body = self.put("flavors/%s" % flavor_id, put_body)
+
+        body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.create_update_get_flavor_details,
+                               resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_flavor(self, flavor_id):
@@ -142,7 +172,7 @@
         https://developer.openstack.org/api-ref/compute/#show-an-extra-spec-for-a-flavor
         """
         resp, body = self.get('flavors/%s/os-extra_specs/%s' % (flavor_id,
-                              key))
+                                                                key))
         body = json.loads(body)
         self.validate_response(
             schema_extra_specs.set_get_flavor_extra_specs_key,
@@ -175,7 +205,8 @@
         """
         resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
                                  (flavor_id, key))
-        self.validate_response(schema.unset_flavor_extra_specs, resp, body)
+        self.validate_response(schema_extra_specs.unset_flavor_extra_specs,
+                               resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def list_flavor_access(self, flavor_id):
diff --git a/tempest/lib/services/compute/hypervisor_client.py b/tempest/lib/services/compute/hypervisor_client.py
index 23c304e..1cbfcc3 100644
--- a/tempest/lib/services/compute/hypervisor_client.py
+++ b/tempest/lib/services/compute/hypervisor_client.py
@@ -15,16 +15,24 @@
 
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.api_schema.response.compute.v2_1 import hypervisors as schema
+from tempest.lib.api_schema.response.compute.v2_1 \
+    import hypervisors as schemav21
+from tempest.lib.api_schema.response.compute.v2_28 \
+    import hypervisors as schemav228
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
 
 class HypervisorClient(base_compute_client.BaseComputeClient):
 
+    schema_versions_info = [
+        {'min': None, 'max': '2.27', 'schema': schemav21},
+        {'min': '2.28', 'max': None, 'schema': schemav228}]
+
     def list_hypervisors(self, detail=False):
         """List hypervisors information."""
         url = 'os-hypervisors'
+        schema = self.get_schema(self.schema_versions_info)
         _schema = schema.list_search_hypervisors
         if detail:
             url += '/detail'
@@ -39,6 +47,7 @@
         """Display the details of the specified hypervisor."""
         resp, body = self.get('os-hypervisors/%s' % hypervisor_id)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_hypervisor, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -46,6 +55,7 @@
         """List instances belonging to the specified hypervisor."""
         resp, body = self.get('os-hypervisors/%s/servers' % hypervisor_name)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_hypervisors_servers, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -53,6 +63,7 @@
         """Get hypervisor statistics over all compute nodes."""
         resp, body = self.get('os-hypervisors/statistics')
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_hypervisor_statistics, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -60,6 +71,7 @@
         """Display the uptime of the specified hypervisor."""
         resp, body = self.get('os-hypervisors/%s/uptime' % hypervisor_id)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_hypervisor_uptime, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -69,5 +81,6 @@
         """Search specified hypervisor."""
         resp, body = self.get('os-hypervisors/%s/search' % hypervisor_name)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.list_search_hypervisors, resp, body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/images_client.py b/tempest/lib/services/compute/images_client.py
index 86bea9e..0f4eb42 100644
--- a/tempest/lib/services/compute/images_client.py
+++ b/tempest/lib/services/compute/images_client.py
@@ -17,6 +17,7 @@
 from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import images as schema
+from tempest.lib.api_schema.response.compute.v2_45 import images as schemav245
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services.compute import base_compute_client
@@ -24,6 +25,10 @@
 
 class ImagesClient(base_compute_client.BaseComputeClient):
 
+    schema_versions_info = [
+        {'min': None, 'max': '2.44', 'schema': schema},
+        {'min': '2.45', 'max': None, 'schema': schemav245}]
+
     def create_image(self, server_id, **kwargs):
         """Create an image of the original server.
 
@@ -36,7 +41,10 @@
         post_body = json.dumps(post_body)
         resp, body = self.post('servers/%s/action' % server_id,
                                post_body)
-        self.validate_response(schema.create_image, resp, body)
+        _schema = self.get_schema(self.schema_versions_info)
+        if body:
+            body = json.loads(body)
+        self.validate_response(_schema.create_image, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def list_images(self, detail=False, **params):
diff --git a/tempest/lib/services/compute/limits_client.py b/tempest/lib/services/compute/limits_client.py
index efe9889..9af80c4 100644
--- a/tempest/lib/services/compute/limits_client.py
+++ b/tempest/lib/services/compute/limits_client.py
@@ -15,15 +15,25 @@
 
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.api_schema.response.compute.v2_1 import limits as schema
+from tempest.lib.api_schema.response.compute.v2_1 import limits as schemav21
+from tempest.lib.api_schema.response.compute.v2_36 import limits as schemav236
+from tempest.lib.api_schema.response.compute.v2_39 import limits as schemav239
+from tempest.lib.api_schema.response.compute.v2_57 import limits as schemav257
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
 
 class LimitsClient(base_compute_client.BaseComputeClient):
 
+    schema_versions_info = [
+        {'min': None, 'max': '2.35', 'schema': schemav21},
+        {'min': '2.36', 'max': '2.38', 'schema': schemav236},
+        {'min': '2.39', 'max': '2.56', 'schema': schemav239},
+        {'min': '2.57', 'max': None, 'schema': schemav257}]
+
     def show_limits(self):
         resp, body = self.get("limits")
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_limit, resp, body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/quota_classes_client.py b/tempest/lib/services/compute/quota_classes_client.py
index 0fe9868..64e06f4 100644
--- a/tempest/lib/services/compute/quota_classes_client.py
+++ b/tempest/lib/services/compute/quota_classes_client.py
@@ -35,8 +35,9 @@
     def update_quota_class_set(self, quota_class_id, **kwargs):
         """Update the quota class's limits for one or more resources.
 
-        # NOTE: Current api-site doesn't contain this API description.
-        # LP: https://bugs.launchpad.net/nova/+bug/1602400
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#create-or-update-quotas-for-quota-class
         """
         post_body = json.dumps({'quota_class_set': kwargs})
 
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
index daf4bc0..99c8d0f 100644
--- a/tempest/lib/services/compute/quotas_client.py
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -17,19 +17,26 @@
 from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import quotas as schema
+from tempest.lib.api_schema.response.compute.v2_36 import quotas as schemav236
+from tempest.lib.api_schema.response.compute.v2_57 import quotas as schemav257
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
 
 class QuotasClient(base_compute_client.BaseComputeClient):
 
+    schema_versions_info = [
+        {'min': None, 'max': '2.35', 'schema': schema},
+        {'min': '2.36', 'max': '2.56', 'schema': schemav236},
+        {'min': '2.57', 'max': None, 'schema': schemav257}]
+
     def show_quota_set(self, tenant_id, user_id=None, detail=False):
         """List the quota set for a tenant.
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref-compute-v2.1.html/#show-a-quota
-        http://developer.openstack.org/api-ref-compute-v2.1.html/#show-the-detail-of-quota
+        https://developer.openstack.org/api-ref/compute/#show-a-quota
+        https://developer.openstack.org/api-ref/compute/#show-the-detail-of-quota
         """
 
         params = {}
@@ -42,6 +49,7 @@
             url += '?%s' % urllib.urlencode(params)
         resp, body = self.get(url)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         if detail:
             self.validate_response(schema.get_quota_set_details, resp, body)
         else:
@@ -49,11 +57,15 @@
         return rest_client.ResponseBody(resp, body)
 
     def show_default_quota_set(self, tenant_id):
-        """List the default quota set for a tenant."""
+        """List the default quota set for a tenant.
+
+        https://developer.openstack.org/api-ref/compute/#list-default-quotas-for-tenant
+        """
 
         url = 'os-quota-sets/%s/defaults' % tenant_id
         resp, body = self.get(url)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_quota_set, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -75,11 +87,16 @@
                                   post_body)
 
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.update_quota_set, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_quota_set(self, tenant_id):
-        """Delete the tenant's quota set."""
+        """Delete the tenant's quota set.
+
+        https://developer.openstack.org/api-ref/compute/#revert-quotas-to-defaults
+        """
         resp, body = self.delete('os-quota-sets/%s' % tenant_id)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.delete_quota, resp, body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/server_groups_client.py b/tempest/lib/services/compute/server_groups_client.py
index 03cd645..0d440d5 100644
--- a/tempest/lib/services/compute/server_groups_client.py
+++ b/tempest/lib/services/compute/server_groups_client.py
@@ -16,8 +16,10 @@
 
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
-from tempest.lib.api_schema.response.compute.v2_13 import servers as schemav213
+from tempest.lib.api_schema.response.compute.v2_1 import server_groups \
+    as schema
+from tempest.lib.api_schema.response.compute.v2_13 import server_groups \
+    as schemav213
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 598d5a6..f2270f8 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -29,7 +29,13 @@
 from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
 from tempest.lib.api_schema.response.compute.v2_47 import servers as schemav247
 from tempest.lib.api_schema.response.compute.v2_48 import servers as schemav248
+from tempest.lib.api_schema.response.compute.v2_54 import servers as schemav254
+from tempest.lib.api_schema.response.compute.v2_57 import servers as schemav257
 from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
+from tempest.lib.api_schema.response.compute.v2_63 import servers as schemav263
+from tempest.lib.api_schema.response.compute.v2_70 import servers as schemav270
+from tempest.lib.api_schema.response.compute.v2_71 import servers as schemav271
+from tempest.lib.api_schema.response.compute.v2_8 import servers as schemav28
 from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
@@ -41,13 +47,19 @@
     schema_versions_info = [
         {'min': None, 'max': '2.2', 'schema': schema},
         {'min': '2.3', 'max': '2.5', 'schema': schemav23},
-        {'min': '2.6', 'max': '2.8', 'schema': schemav26},
+        {'min': '2.6', 'max': '2.7', 'schema': schemav26},
+        {'min': '2.8', 'max': '2.8', 'schema': schemav28},
         {'min': '2.9', 'max': '2.15', 'schema': schemav29},
         {'min': '2.16', 'max': '2.18', 'schema': schemav216},
         {'min': '2.19', 'max': '2.25', 'schema': schemav219},
         {'min': '2.26', 'max': '2.46', 'schema': schemav226},
         {'min': '2.47', 'max': '2.47', 'schema': schemav247},
-        {'min': '2.48', 'max': None, 'schema': schemav248}]
+        {'min': '2.48', 'max': '2.53', 'schema': schemav248},
+        {'min': '2.54', 'max': '2.56', 'schema': schemav254},
+        {'min': '2.57', 'max': '2.62', 'schema': schemav257},
+        {'min': '2.63', 'max': '2.69', 'schema': schemav263},
+        {'min': '2.70', 'max': '2.70', 'schema': schemav270},
+        {'min': '2.71', 'max': None, 'schema': schemav271}]
 
     def __init__(self, auth_provider, service, region,
                  enable_instance_password=True, **kwargs):
@@ -126,7 +138,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref-compute-v2.1.html#showServer
+        https://developer.openstack.org/api-ref/compute/#show-server-details
         """
         resp, body = self.get("servers/%s" % server_id)
         body = json.loads(body)
@@ -156,11 +168,11 @@
 
         url = 'servers'
         schema = self.get_schema(self.schema_versions_info)
-        _schema = schema.list_servers
-
         if detail:
             url += '/detail'
             _schema = schema.list_servers_detail
+        else:
+            _schema = schema.list_servers
         if params:
             url += '?%s' % urllib.urlencode(params)
 
@@ -321,7 +333,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#create-or-replace-metadata-items
+        https://developer.openstack.org/api-ref/compute/#replace-metadata-items
         """
         if no_metadata_field:
             post_body = ""
@@ -338,7 +350,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#update-metadata-items
+        https://developer.openstack.org/api-ref/compute/#create-or-update-metadata-items
         """
         post_body = json.dumps({'metadata': meta})
         resp, body = self.post('servers/%s/metadata' % server_id,
@@ -418,6 +430,7 @@
         resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
                                post_body)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.attach_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -452,6 +465,7 @@
         resp, body = self.get('servers/%s/os-volume_attachments/%s' % (
             server_id, volume_id))
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.show_volume_attachment, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -465,6 +479,7 @@
         resp, body = self.get('servers/%s/os-volume_attachments' % (
             server_id))
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.list_volume_attachments, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -609,9 +624,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        TODO (markus_z) The api-ref for that isn't yet available, update this
-        here when the docs in Nova are updated. The old API is at
-        http://developer.openstack.org/api-ref/compute/#get-serial-console-os-getserialconsole-action
+        https://developer.openstack.org/api-ref/compute/#create-remote-console
         """
         param = {
             'remote_console': {
@@ -630,7 +643,7 @@
     def list_virtual_interfaces(self, server_id):
         """List the virtual interfaces used in an instance."""
         resp, body = self.get('/'.join(['servers', server_id,
-                              'os-virtual-interfaces']))
+                                        'os-virtual-interfaces']))
         body = json.loads(body)
         self.validate_response(schema.list_virtual_interfaces, resp, body)
         return rest_client.ResponseBody(resp, body)
@@ -722,7 +735,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#get-vnc-console-os-getvncconsole-action
+        https://developer.openstack.org/api-ref/compute/#get-vnc-console-os-getvncconsole-action-deprecated
         """
         return self.action(server_id, "os-getVNCConsole",
                            schema.get_vnc_console, **kwargs)
@@ -732,7 +745,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#add-associate-fixed-ip-addfixedip-action
+        https://developer.openstack.org/api-ref/compute/#add-associate-fixed-ip-addfixedip-action-deprecated
         """
         return self.action(server_id, 'addFixedIp', **kwargs)
 
@@ -741,7 +754,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#remove-disassociate-fixed-ip-removefixedip-action
+        https://developer.openstack.org/api-ref/compute/#remove-disassociate-fixed-ip-removefixedip-action-deprecated
         """
         return self.action(server_id, 'removeFixedIp', **kwargs)
 
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
index b046c35..d52de3a 100644
--- a/tempest/lib/services/compute/services_client.py
+++ b/tempest/lib/services/compute/services_client.py
@@ -20,6 +20,8 @@
 from tempest.lib.api_schema.response.compute.v2_1 import services as schema
 from tempest.lib.api_schema.response.compute.v2_11 import services \
     as schemav211
+from tempest.lib.api_schema.response.compute.v2_53 import services \
+    as schemav253
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
@@ -28,7 +30,8 @@
 
     schema_versions_info = [
         {'min': None, 'max': '2.10', 'schema': schema},
-        {'min': '2.11', 'max': None, 'schema': schemav211}]
+        {'min': '2.11', 'max': '2.52', 'schema': schemav211},
+        {'min': '2.53', 'max': None, 'schema': schemav253}]
 
     def list_services(self, **params):
         """Lists all running Compute services for a tenant.
@@ -47,9 +50,30 @@
         self.validate_response(_schema.list_services, resp, body)
         return rest_client.ResponseBody(resp, body)
 
+    def update_service(self, service_id, **kwargs):
+        """Update a compute service.
+
+        Update a compute service to enable or disable scheduling, including
+        recording a reason why a compute service was disabled from scheduling.
+
+        This API is available starting with microversion 2.53.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#update-compute-service
+        """
+        put_body = json.dumps(kwargs)
+        resp, body = self.put('os-services/%s' % service_id, put_body)
+        body = json.loads(body)
+        _schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(_schema.update_service, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
     def enable_service(self, **kwargs):
         """Enable service on a host.
 
+        ``update_service`` supersedes this API starting with microversion 2.53.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/compute/#enable-scheduling-for-a-compute-service
@@ -63,6 +87,8 @@
     def disable_service(self, **kwargs):
         """Disable service on a host.
 
+        ``update_service`` supersedes this API starting with microversion 2.53.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service
@@ -76,6 +102,8 @@
     def disable_log_reason(self, **kwargs):
         """Disables scheduling for a Compute service and logs reason.
 
+        ``update_service`` supersedes this API starting with microversion 2.53.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service-and-log-disabled-reason
@@ -89,6 +117,8 @@
     def update_forced_down(self, **kwargs):
         """Set or unset ``forced_down`` flag for the service.
 
+        ``update_service`` supersedes this API starting with microversion 2.53.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/compute/#update-forced-down
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index a539d08..da1c51c 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.application_credentials_client import \
+    ApplicationCredentialsClient
 from tempest.lib.services.identity.v3.catalog_client import \
     CatalogClient
 from tempest.lib.services.identity.v3.credentials_client import \
@@ -33,6 +35,8 @@
 from tempest.lib.services.identity.v3.oauth_token_client import \
     OAUTHTokenClient
 from tempest.lib.services.identity.v3.policies_client import PoliciesClient
+from tempest.lib.services.identity.v3.project_tags_client import \
+    ProjectTagsClient
 from tempest.lib.services.identity.v3.projects_client import ProjectsClient
 from tempest.lib.services.identity.v3.regions_client import RegionsClient
 from tempest.lib.services.identity.v3.role_assignments_client import \
@@ -44,11 +48,11 @@
 from tempest.lib.services.identity.v3.users_client import UsersClient
 from tempest.lib.services.identity.v3.versions_client import VersionsClient
 
-__all__ = ['CatalogClient', 'CredentialsClient', 'DomainsClient',
-           'DomainConfigurationClient', 'EndPointGroupsClient',
-           'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient',
-           'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient',
-           'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient',
-           'RegionsClient', 'RoleAssignmentsClient', 'RolesClient',
-           'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient',
-           'VersionsClient']
+__all__ = ['ApplicationCredentialsClient', 'CatalogClient',
+           'CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
+           'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient',
+           'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
+           'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient',
+           'ProjectsClient', 'ProjectTagsClient', 'RegionsClient',
+           'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
+           'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient']
diff --git a/tempest/lib/services/identity/v3/application_credentials_client.py b/tempest/lib/services/identity/v3/application_credentials_client.py
new file mode 100644
index 0000000..557aa9e
--- /dev/null
+++ b/tempest/lib/services/identity/v3/application_credentials_client.py
@@ -0,0 +1,83 @@
+# Copyright 2018 SUSE Linux GmbH
+#
+# 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://developer.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 ApplicationCredentialsClient(rest_client.RestClient):
+    api_version = "v3"
+
+    def create_application_credential(self, user_id, **kwargs):
+        """Creates an application credential.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3/index.html#create-application-credential
+        """
+        post_body = json.dumps({'application_credential': kwargs})
+        resp, body = self.post('users/%s/application_credentials' % user_id,
+                               post_body)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_application_credential(self, user_id, application_credential_id):
+        """Gets details of an application credential.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3/index.html#show-application-credential-details
+        """
+        resp, body = self.get('users/%s/application_credentials/%s' %
+                              (user_id, application_credential_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_application_credentials(self, user_id, **params):
+        """Lists out all of a user's application credentials.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3/index.html#list-application-credentials
+        """
+        url = 'users/%s/application_credentials' % 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_application_credential(self, user_id,
+                                      application_credential_id):
+        """Deletes an application credential.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3/index.html#delete-application-credential
+        """
+        resp, body = self.delete('users/%s/application_credentials/%s' %
+                                 (user_id, application_credential_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/inherited_roles_client.py b/tempest/lib/services/identity/v3/inherited_roles_client.py
index 691c7fd..3949437 100644
--- a/tempest/lib/services/identity/v3/inherited_roles_client.py
+++ b/tempest/lib/services/identity/v3/inherited_roles_client.py
@@ -114,8 +114,7 @@
 
     def check_user_has_flag_on_inherited_to_project(
             self, project_id, user_id, role_id):
-        """Checks whether a user has a role assignment"""
-        """with the inherited_to_projects flag on a project."""
+        """Check if user has an inherited project role on project"""
         resp, body = self.head(
             "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
             % (project_id, user_id, role_id))
@@ -142,8 +141,7 @@
 
     def check_group_has_flag_on_inherited_to_project(
             self, project_id, group_id, role_id):
-        """Checks whether a group has a role assignment"""
-        """with the inherited_to_projects flag on a project."""
+        """Check if group has an inherited project role on project"""
         resp, body = self.head(
             "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
             % (project_id, group_id, role_id))
diff --git a/tempest/lib/services/identity/v3/oauth_token_client.py b/tempest/lib/services/identity/v3/oauth_token_client.py
index b1d298b..94da043 100644
--- a/tempest/lib/services/identity/v3/oauth_token_client.py
+++ b/tempest/lib/services/identity/v3/oauth_token_client.py
@@ -74,6 +74,7 @@
         scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
         scheme = scheme.lower()
         netloc = netloc.lower()
+        path = path.replace('//', '/')
         normalized_uri = urlparse.urlunparse((scheme, netloc, path,
                                               params, '', ''))
 
diff --git a/tempest/lib/services/identity/v3/policies_client.py b/tempest/lib/services/identity/v3/policies_client.py
index d4560e2..ca8dbbd 100644
--- a/tempest/lib/services/identity/v3/policies_client.py
+++ b/tempest/lib/services/identity/v3/policies_client.py
@@ -73,3 +73,115 @@
         resp, body = self.delete(url)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def update_policy_association_for_endpoint(self, policy_id, endpoint_id):
+        """Create policy association with endpoint.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-endpoint
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\
+              .format(policy_id, endpoint_id)
+        resp, body = self.put(url, '{}')
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_policy_association_for_endpoint(self, policy_id, endpoint_id):
+        """Get policy association of endpoint.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-endpoint-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\
+              .format(policy_id, endpoint_id)
+        resp, body = self.get(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_policy_association_for_endpoint(self, policy_id, endpoint_id):
+        """Delete policy association with endpoint.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-endpoint-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\
+              .format(policy_id, endpoint_id)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_policy_association_for_service(self, policy_id, service_id):
+        """Create policy association with service.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-service-type-endpoint
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\
+              .format(policy_id, service_id)
+        resp, body = self.put(url, '{}')
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_policy_association_for_service(self, policy_id, service_id):
+        """Get policy association of service.
+
+        API Reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-service-type-endpoint-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\
+              .format(policy_id, service_id)
+        resp, body = self.get(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_policy_association_for_service(self, policy_id, service_id):
+        """Delete policy association with service.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-service-type-endpoint-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\
+              .format(policy_id, service_id)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_policy_association_for_region_and_service(
+            self, policy_id, service_id, region_id):
+        """Create policy association with service and region.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-service-type-endpoint-in-a-region
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\
+              .format(policy_id, service_id, region_id)
+        resp, body = self.put(url, '{}')
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_policy_association_for_region_and_service(
+        self, policy_id, service_id, region_id):
+        """Get policy association of service and region.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-service-type-endpoint-in-a-region-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\
+              .format(policy_id, service_id, region_id)
+        resp, body = self.get(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_policy_association_for_region_and_service(
+        self, policy_id, service_id, region_id):
+        """Delete policy association with service and region.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-service-type-endpoint-in-a-region-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\
+              .format(policy_id, service_id, region_id)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/project_tags_client.py b/tempest/lib/services/identity/v3/project_tags_client.py
new file mode 100644
index 0000000..dd1a2a5
--- /dev/null
+++ b/tempest/lib/services/identity/v3/project_tags_client.py
@@ -0,0 +1,80 @@
+# Copyright 2018 AT&T Corporation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ProjectTagsClient(rest_client.RestClient):
+    api_version = "v3"
+
+    def update_project_tag(self, project_id, tag):
+        """Updates the specified tag and adds it to the project's list of tags.
+
+        """
+        url = 'projects/%s/tags/%s' % (project_id, tag)
+        resp, body = self.put(url, '{}')
+        # NOTE(felipemonteiro): This API endpoint returns 201 AND an empty
+        # response body, which is consistent with the spec:
+        # https://specs.openstack.org/openstack/api-wg/guidelines/tags.html#addressing-individual-tags
+        self.expected_success(201, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_project_tags(self, project_id):
+        """List tags for a project."""
+        url = "projects/%s/tags" % project_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_all_project_tags(self, project_id, tags, **kwargs):
+        """Updates all the tags for a project.
+
+        Any existing tags not specified will be deleted.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3/#modify-tag-list-for-a-project
+        """
+        body = {'tags': tags}
+        if kwargs:
+            body.update(kwargs)
+        put_body = json.dumps(body)
+        resp, body = self.put('projects/%s/tags' % project_id, put_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def check_project_tag_existence(self, project_id, tag):
+        """Check if a project contains a tag."""
+        url = 'projects/%s/tags/%s' % (project_id, tag)
+        resp, body = self.get(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_project_tag(self, project_id, tag):
+        """Delete a project tag."""
+        url = 'projects/%s/tags/%s' % (project_id, tag)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_all_project_tags(self, project_id):
+        """Delete all tags from a project."""
+        resp, body = self.delete('projects/%s/tags' % project_id)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
index 33f6f16..d591f03 100644
--- a/tempest/lib/services/identity/v3/token_client.py
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -51,7 +51,8 @@
     def auth(self, user_id=None, username=None, password=None, project_id=None,
              project_name=None, user_domain_id=None, user_domain_name=None,
              project_domain_id=None, project_domain_name=None, domain_id=None,
-             domain_name=None, token=None):
+             domain_name=None, token=None, app_cred_id=None,
+             app_cred_secret=None):
         """Obtains a token from the authentication service
 
         :param user_id: user id
@@ -109,6 +110,13 @@
             if _domain:
                 id_obj['password']['user']['domain'] = _domain
 
+        if app_cred_id and app_cred_secret:
+            id_obj['methods'].append('application_credential')
+            id_obj['application_credential'] = {
+                'id': app_cred_id,
+                'secret': app_cred_secret,
+            }
+
         if (project_id or project_name):
             _project = dict()
 
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index bcdae44..3c38dba 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -32,7 +32,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/image/v2/index.html#update-an-image
+        https://developer.openstack.org/api-ref/image/v2/#update-image
         """
         data = json.dumps(patch)
         headers = {"Content-Type": "application/openstack-images-v2.0"
@@ -47,7 +47,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/image/v2/index.html#create-an-image
+        https://developer.openstack.org/api-ref/image/v2/#create-image
         """
         data = json.dumps(kwargs)
         resp, body = self.post('images', data)
@@ -84,7 +84,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/image/v2/#delete-an-image
+        https://developer.openstack.org/api-ref/image/v2/#delete-image
          """
         url = 'images/%s' % image_id
         resp, _ = self.delete(url)
@@ -96,7 +96,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/image/v2/#show-images
+        https://developer.openstack.org/api-ref/image/v2/#list-images
         """
         url = 'images'
 
@@ -113,7 +113,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/image/v2/#show-image-details
+        https://developer.openstack.org/api-ref/image/v2/#show-image
         """
         url = 'images/%s' % image_id
         resp, body = self.get(url)
@@ -161,7 +161,7 @@
         """
         url = 'images/%s/file' % image_id
         resp, body = self.get(url)
-        self.expected_success(200, resp.status)
+        self.expected_success([200, 204, 206], resp.status)
         return rest_client.ResponseBodyData(resp, body)
 
     def add_image_tag(self, image_id, tag):
diff --git a/tempest/lib/services/network/agents_client.py b/tempest/lib/services/network/agents_client.py
index 9bdf090..9fa4672 100644
--- a/tempest/lib/services/network/agents_client.py
+++ b/tempest/lib/services/network/agents_client.py
@@ -18,35 +18,72 @@
 class AgentsClient(base.BaseNetworkClient):
 
     def update_agent(self, agent_id, **kwargs):
-        """Update agent."""
-        # TODO(piyush): Current api-site doesn't contain this API description.
-        # After fixing the api-site, we need to fix here also for putting the
-        # link to api-site.
-        # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526673
+        """Update an agent.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/#update-agent
+        """
         uri = '/agents/%s' % agent_id
         return self.update_resource(uri, kwargs)
 
     def show_agent(self, agent_id, **fields):
+        """Show details for an agent.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/#show-agent-details
+        """
         uri = '/agents/%s' % agent_id
         return self.show_resource(uri, **fields)
 
+    def delete_agent(self, agent_id):
+        """Delete agent.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#delete-agent
+        """
+        uri = '/agents/%s' % agent_id
+        return self.delete_resource(uri)
+
     def list_agents(self, **filters):
+        """List all agents.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/#list-all-agents
+        """
         uri = '/agents'
         return self.list_resources(uri, **filters)
 
     def list_routers_on_l3_agent(self, agent_id):
+        """List routers that an l3 agent hosts.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/#list-routers-hosted-by-an-l3-agent
+        """
         uri = '/agents/%s/l3-routers' % agent_id
         return self.list_resources(uri)
 
     def create_router_on_l3_agent(self, agent_id, **kwargs):
-        # TODO(piyush): Current api-site doesn't contain this API description.
-        # After fixing the api-site, we need to fix here also for putting the
-        # link to api-site.
-        # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526670
+        """Add a router to an l3 agent.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/#schedule-router-to-an-l3-agent
+        """
         uri = '/agents/%s/l3-routers' % agent_id
         return self.create_resource(uri, kwargs, expect_empty_body=True)
 
     def delete_router_from_l3_agent(self, agent_id, router_id):
+        """Remove a router to an l3 agent.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/#remove-l3-router-from-an-l3-agent
+        """
         uri = '/agents/%s/l3-routers/%s' % (agent_id, router_id)
         return self.delete_resource(uri)
 
@@ -60,9 +97,11 @@
         return self.delete_resource(uri)
 
     def add_dhcp_agent_to_network(self, agent_id, **kwargs):
-        # TODO(piyush): Current api-site doesn't contain this API description.
-        # After fixing the api-site, we need to fix here also for putting the
-        # link to api-site.
-        # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526212
+        """Schedule a network to a DHCP agent.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/#schedule-a-network-to-a-dhcp-agent
+        """
         uri = '/agents/%s/dhcp-networks' % agent_id
         return self.create_resource(uri, kwargs, expect_empty_body=True)
diff --git a/tempest/lib/services/network/metering_label_rules_client.py b/tempest/lib/services/network/metering_label_rules_client.py
index 36cf8e3..9542e8f 100644
--- a/tempest/lib/services/network/metering_label_rules_client.py
+++ b/tempest/lib/services/network/metering_label_rules_client.py
@@ -16,6 +16,12 @@
 class MeteringLabelRulesClient(base.BaseNetworkClient):
 
     def create_metering_label_rule(self, **kwargs):
+        """Create metering label rule.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#create-metering-label-rule
+        """
         uri = '/metering/metering-label-rules'
         post_data = {'metering_label_rule': kwargs}
         return self.create_resource(uri, post_data)
@@ -29,5 +35,11 @@
         return self.delete_resource(uri)
 
     def list_metering_label_rules(self, **filters):
+        """List metering label rules.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#list-metering-label-rules
+        """
         uri = '/metering/metering-label-rules'
         return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
index 752b253..e9666de 100644
--- a/tempest/lib/services/network/quotas_client.py
+++ b/tempest/lib/services/network/quotas_client.py
@@ -18,6 +18,12 @@
 class QuotasClient(base.BaseNetworkClient):
 
     def update_quotas(self, tenant_id, **kwargs):
+        """Update quota for a project.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#update-quota-for-a-project
+        """
         put_body = {'quota': kwargs}
         uri = '/quotas/%s' % tenant_id
         return self.update_resource(uri, put_body)
@@ -35,3 +41,13 @@
     def list_quotas(self, **filters):
         uri = '/quotas'
         return self.list_resources(uri, **filters)
+
+    def show_default_quotas(self, tenant_id):
+        """List default quotas for a project."""
+        uri = '/quotas/%s/default' % tenant_id
+        return self.show_resource(uri)
+
+    def show_quota_details(self, tenant_id):
+        """Show quota details for a project."""
+        uri = '/quotas/%s/details.json' % tenant_id
+        return self.show_resource(uri)
diff --git a/tempest/lib/services/network/service_providers_client.py b/tempest/lib/services/network/service_providers_client.py
index 0ee9bc3..01313a0 100644
--- a/tempest/lib/services/network/service_providers_client.py
+++ b/tempest/lib/services/network/service_providers_client.py
@@ -16,6 +16,11 @@
 class ServiceProvidersClient(base.BaseNetworkClient):
 
     def list_service_providers(self, **filters):
-        """Lists service providers."""
+        """Lists service providers.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#list-service-providers
+        """
         uri = '/service-providers'
         return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/versions_client.py b/tempest/lib/services/network/versions_client.py
index f87fe87..807f416 100644
--- a/tempest/lib/services/network/versions_client.py
+++ b/tempest/lib/services/network/versions_client.py
@@ -12,32 +12,36 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import time
-
 from oslo_serialization import jsonutils as json
 
+from tempest.lib.common import rest_client
 from tempest.lib.services.network import base
 
 
 class NetworkVersionsClient(base.BaseNetworkClient):
 
     def list_versions(self):
-        """Do a GET / to fetch available API version information."""
+        """Do a GET / to fetch available API version information.
 
-        version_url = self._get_base_version_url()
+        For more information, please refer to the official API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#list-api-versions
+        """
 
-        # Note: we do a raw_request here because we want to use
+        # Note: we do a self.get('/') here because we want to use
         # an unversioned URL, not "v2/$project_id/".
-        # Since raw_request doesn't log anything, we do that too.
-        start = time.time()
-        self._log_request_start('GET', version_url)
-        response, body = self.raw_request(version_url, 'GET')
-        self._error_checker(response, body)
-        end = time.time()
-        self._log_request('GET', version_url, response,
-                          secs=(end - start), resp_body=body)
-
-        self.response_checker('GET', response, body)
-        self.expected_success(200, response.status)
+        resp, body = self.get('/')
         body = json.loads(body)
-        return body
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_version(self, version):
+        """Do a GET /<version> to fetch available resources.
+
+        For more information, please refer to the official API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#show-api-v2-details
+        """
+
+        resp, body = self.get(version + '/')
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/object_storage/account_client.py b/tempest/lib/services/object_storage/account_client.py
index 67f01a6..6b097c1 100644
--- a/tempest/lib/services/object_storage/account_client.py
+++ b/tempest/lib/services/object_storage/account_client.py
@@ -34,7 +34,7 @@
         Account Metadata can be created, updated or deleted based on
         metadata header or value. For detailed info, please refer to the
         official API reference:
-        http://developer.openstack.org/api-ref/object-storage/?expanded=create-update-or-delete-account-metadata-detail
+        https://developer.openstack.org/api-ref/object-store/#create-update-or-delete-account-metadata
         """
         headers = {}
         if create_update_metadata:
diff --git a/tempest/lib/services/object_storage/container_client.py b/tempest/lib/services/object_storage/container_client.py
index 2da8e24..430e0d4 100644
--- a/tempest/lib/services/object_storage/container_client.py
+++ b/tempest/lib/services/object_storage/container_client.py
@@ -97,7 +97,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/object-storage/?expanded=show-container-details-and-list-objects-detail
+        https://developer.openstack.org/api-ref/object-store/#show-container-details-and-list-objects
         """
 
         url = str(container_name)
diff --git a/tempest/lib/services/placement/__init__.py b/tempest/lib/services/placement/__init__.py
new file mode 100644
index 0000000..5c20c57
--- /dev/null
+++ b/tempest/lib/services/placement/__init__.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2019 Ericsson
+#
+#    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.placement_client import \
+    PlacementClient
+
+__all__ = ['PlacementClient']
diff --git a/tempest/lib/services/placement/base_placement_client.py b/tempest/lib/services/placement/base_placement_client.py
new file mode 100644
index 0000000..505a515
--- /dev/null
+++ b/tempest/lib/services/placement/base_placement_client.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2019 Ericsson
+#
+#    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.common import api_version_utils
+from tempest.lib.common import rest_client
+
+PLACEMENT_MICROVERSION = None
+
+
+class BasePlacementClient(rest_client.RestClient):
+
+    api_microversion_header_name = 'OpenStack-API-Version'
+    version_header_value = 'placement %s'
+
+    def get_headers(self):
+        headers = super(BasePlacementClient, self).get_headers()
+        if PLACEMENT_MICROVERSION:
+            headers[self.api_microversion_header_name] = \
+                self.version_header_value % PLACEMENT_MICROVERSION
+        return headers
+
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None, chunked=False):
+        resp, resp_body = super(BasePlacementClient, self).request(
+            method, url, extra_headers, headers, body, chunked)
+        if (PLACEMENT_MICROVERSION and
+            PLACEMENT_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
+            api_version_utils.assert_version_header_matches_request(
+                self.api_microversion_header_name,
+                self.version_header_value % PLACEMENT_MICROVERSION,
+                resp)
+        return resp, resp_body
diff --git a/tempest/lib/services/placement/placement_client.py b/tempest/lib/services/placement/placement_client.py
new file mode 100644
index 0000000..2c6d919
--- /dev/null
+++ b/tempest/lib/services/placement/placement_client.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2019 Ericsson
+#
+#    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 PlacementClient(base_placement_client.BasePlacementClient):
+
+    def list_allocation_candidates(self, **params):
+        """List allocation candidates.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/placement/#list-allocation-candidates
+        """
+        url = '/allocation_candidates'
+        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 list_allocations(self, consumer_uuid):
+        """List all allocation records for the consumer.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/placement/#list-allocations
+        """
+        url = '/allocations/%s' % consumer_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/v1/encryption_types_client.py b/tempest/lib/services/volume/v1/encryption_types_client.py
index 067b4e8..1fde79f 100644
--- a/tempest/lib/services/volume/v1/encryption_types_client.py
+++ b/tempest/lib/services/volume/v1/encryption_types_client.py
@@ -38,7 +38,7 @@
     def show_encryption_type(self, volume_type_id):
         """Get the volume encryption type for the specified volume type.
 
-        volume_type_id: Id of volume_type.
+        :param volume_type_id: Id of volume type.
         """
         url = "/types/%s/encryption" % volume_type_id
         resp, body = self.get(url)
@@ -49,9 +49,9 @@
     def create_encryption_type(self, volume_type_id, **kwargs):
         """Create encryption type.
 
-        TODO: Current api-site doesn't contain this API description.
-        After fixing the api-site, we need to fix here also for putting
-        the link to api-site.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2
         """
         url = "/types/%s/encryption" % volume_type_id
         post_body = json.dumps({'encryption': kwargs})
@@ -61,7 +61,7 @@
         return rest_client.ResponseBody(resp, body)
 
     def delete_encryption_type(self, volume_type_id):
-        """Delete the encryption type for the specified volume-type."""
+        """Delete the encryption type for the specified volume type."""
         resp, body = self.delete(
             "/types/%s/encryption/provider" % volume_type_id)
         self.expected_success(202, resp.status)
diff --git a/tempest/lib/services/volume/v1/hosts_client.py b/tempest/lib/services/volume/v1/hosts_client.py
index 56ba12c..9b19b84 100644
--- a/tempest/lib/services/volume/v1/hosts_client.py
+++ b/tempest/lib/services/volume/v1/hosts_client.py
@@ -23,8 +23,12 @@
     """Client class to send CRUD Volume Host API V1 requests"""
 
     def list_hosts(self, **params):
-        """Lists all hosts."""
+        """Lists all hosts.
 
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts
+        """
         url = 'os-hosts'
         if params:
             url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/volume/v1/qos_client.py b/tempest/lib/services/volume/v1/qos_client.py
index e247b7b..593bddd 100644
--- a/tempest/lib/services/volume/v1/qos_client.py
+++ b/tempest/lib/services/volume/v1/qos_client.py
@@ -92,7 +92,9 @@
 
         :param keys: keys to delete from the QoS specification.
 
-        TODO(jordanP): Add a link once LP #1524877 is fixed.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#unset-keys-in-qos-specification
         """
         put_body = json.dumps({'keys': keys})
         resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
diff --git a/tempest/lib/services/volume/v1/quotas_client.py b/tempest/lib/services/volume/v1/quotas_client.py
index 678fd82..84f34f2 100644
--- a/tempest/lib/services/volume/v1/quotas_client.py
+++ b/tempest/lib/services/volume/v1/quotas_client.py
@@ -47,7 +47,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref-blockstorage-v1.html#updateQuota
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-quotas
         """
         put_body = jsonutils.dumps({'quota_set': kwargs})
         resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
diff --git a/tempest/lib/services/volume/v1/snapshots_client.py b/tempest/lib/services/volume/v1/snapshots_client.py
index 3433e68..51f7b9b 100644
--- a/tempest/lib/services/volume/v1/snapshots_client.py
+++ b/tempest/lib/services/volume/v1/snapshots_client.py
@@ -27,7 +27,8 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#list-snapshots-with-details-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-with-details
         """
         url = 'snapshots'
         if detail:
@@ -45,7 +46,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#show-snapshot-details-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-details
         """
         url = "snapshots/%s" % snapshot_id
         resp, body = self.get(url)
@@ -58,7 +59,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#create-snapshot-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot
         """
         post_body = json.dumps({'snapshot': kwargs})
         resp, body = self.post('snapshots', post_body)
@@ -71,7 +72,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#delete-snapshot-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot
         """
         resp, body = self.delete("snapshots/%s" % snapshot_id)
         self.expected_success(202, resp.status)
@@ -123,7 +124,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-snapshot-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot
         """
         put_body = json.dumps({'snapshot': kwargs})
         resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
@@ -136,7 +137,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#show-snapshot-metadata-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata
         """
         url = "snapshots/%s/metadata" % snapshot_id
         resp, body = self.get(url)
@@ -149,7 +150,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-snapshot-metadata-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata
         """
         put_body = json.dumps(kwargs)
         url = "snapshots/%s/metadata" % snapshot_id
diff --git a/tempest/lib/services/volume/v1/types_client.py b/tempest/lib/services/volume/v1/types_client.py
index 4ae9935..da9eb8b 100644
--- a/tempest/lib/services/volume/v1/types_client.py
+++ b/tempest/lib/services/volume/v1/types_client.py
@@ -40,7 +40,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#list-volume-types-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-all-volume-types-for-v2
         """
         url = 'types'
         if params:
@@ -56,7 +56,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#show-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details-for-v2
         """
         url = "types/%s" % volume_type_id
         resp, body = self.get(url)
@@ -69,7 +69,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#create-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-for-v2
         """
         post_body = json.dumps({'volume_type': kwargs})
         resp, body = self.post('types', post_body)
@@ -82,7 +82,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#delete-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type
         """
         resp, body = self.delete("types/%s" % volume_type_id)
         self.expected_success(202, resp.status)
@@ -137,7 +137,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type
         """
         put_body = json.dumps({'volume_type': kwargs})
         resp, body = self.put('types/%s' % volume_type_id, put_body)
@@ -149,13 +149,14 @@
                                        extra_specs):
         """Update a volume_type extra spec.
 
-        volume_type_id: Id of volume_type.
-        extra_spec_name: Name of the extra spec to be updated.
-        extra_spec: A dictionary of with key as extra_spec_name and the
-                     updated value.
+        :param volume_type_id: Id of volume_type.
+        :param extra_spec_name: Name of the extra spec to be updated.
+        :param extra_specs: A dictionary of with key as extra_spec_name and the
+                            updated value.
+
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-extra-specs-for-a-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-extra-specs-for-a-volume-type
         """
         url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
         put_body = json.dumps(extra_specs)
diff --git a/tempest/lib/services/volume/v1/volumes_client.py b/tempest/lib/services/volume/v1/volumes_client.py
index 7a25697..0e6ea9f 100644
--- a/tempest/lib/services/volume/v1/volumes_client.py
+++ b/tempest/lib/services/volume/v1/volumes_client.py
@@ -38,6 +38,11 @@
         """List all the volumes created.
 
         Params can be a string (must be urlencoded) or a dictionary.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-volumes
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details
         """
         url = 'volumes'
         if detail:
@@ -63,7 +68,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#create-volume
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume
         """
         post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('volumes', post_body)
@@ -76,7 +81,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-volume
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-volume
         """
         put_body = json.dumps({'volume': kwargs})
         resp, body = self.put('volumes/%s' % volume_id, put_body)
@@ -104,7 +109,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#attach-volume
+        https://developer.openstack.org/api-ref/block-storage/v2/#attach-volume-to-server
         """
         post_body = json.dumps({'os-attach': kwargs})
         url = 'volumes/%s/action' % (volume_id)
@@ -161,7 +166,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#extend-volume
+        https://developer.openstack.org/api-ref/block-storage/v2/#extend-volume-size
         """
         post_body = json.dumps({'os-extend': kwargs})
         url = 'volumes/%s/action' % (volume_id)
@@ -174,7 +179,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#reset-volume-status
+        https://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-statuses
         """
         post_body = json.dumps({'os-reset_status': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
@@ -186,7 +191,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#create-volume-transfer
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-transfer
         """
         post_body = json.dumps({'transfer': kwargs})
         resp, body = self.post('os-volume-transfer', post_body)
@@ -207,7 +212,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#list-volume-transfers
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers
         """
         url = 'os-volume-transfer'
         if params:
@@ -228,7 +233,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#accept-volume-transfer
+        https://developer.openstack.org/api-ref/block-storage/v2/#accept-volume-transfer
         """
         url = 'os-volume-transfer/%s/accept' % transfer_id
         post_body = json.dumps({'accept': kwargs})
diff --git a/tempest/lib/services/volume/v2/availability_zone_client.py b/tempest/lib/services/volume/v2/availability_zone_client.py
index bb4a357..bdb2304 100644
--- a/tempest/lib/services/volume/v2/availability_zone_client.py
+++ b/tempest/lib/services/volume/v2/availability_zone_client.py
@@ -13,16 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from oslo_serialization import jsonutils as json
+from debtcollector import moves
 
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import availability_zone_client
 
 
-class AvailabilityZoneClient(rest_client.RestClient):
-    api_version = "v2"
-
-    def list_availability_zones(self):
-        resp, body = self.get('os-availability-zone')
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+AvailabilityZoneClient = moves.moved_class(
+    availability_zone_client.AvailabilityZoneClient, 'AvailabilityZoneClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
index adfa6a6..80b3631 100644
--- a/tempest/lib/services/volume/v2/backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -12,108 +12,11 @@
 #    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 debtcollector import moves
 
-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 import exceptions as lib_exc
-from tempest.lib.services.volume import base_client
+from tempest.lib.services.volume.v3 import backups_client
 
 
-class BackupsClient(base_client.BaseClient):
-    """Volume V2 Backups client"""
-    api_version = "v2"
-
-    def create_backup(self, **kwargs):
-        """Creates a backup of volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/index.html#create-backup
-        """
-        post_body = json.dumps({'backup': kwargs})
-        resp, body = self.post('backups', post_body)
-        body = json.loads(body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def restore_backup(self, backup_id, **kwargs):
-        """Restore volume from backup.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/index.html#restore-backup
-        """
-        post_body = json.dumps({'restore': kwargs})
-        resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
-        body = json.loads(body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_backup(self, backup_id):
-        """Delete a backup of volume."""
-        resp, body = self.delete('backups/%s' % backup_id)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_backup(self, backup_id):
-        """Returns the details of a single backup."""
-        url = "backups/%s" % backup_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_backups(self, detail=False, **params):
-        """List all the tenant's backups.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-backups
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-backups-with-details
-        """
-        url = "backups"
-        if detail:
-            url += "/detail"
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def export_backup(self, backup_id):
-        """Export backup metadata record."""
-        url = "backups/%s/export_record" % backup_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def import_backup(self, **kwargs):
-        """Import backup metadata record."""
-        post_body = json.dumps({'backup-record': kwargs})
-        resp, body = self.post("backups/import_record", post_body)
-        body = json.loads(body)
-        self.expected_success(201, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def reset_backup_status(self, backup_id, status):
-        """Reset the specified backup's status."""
-        post_body = json.dumps({'os-reset_status': {"status": status}})
-        resp, body = self.post('backups/%s/action' % backup_id, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def is_resource_deleted(self, id):
-        try:
-            self.show_backup(id)
-        except lib_exc.NotFound:
-            return True
-        return False
-
-    @property
-    def resource_type(self):
-        """Returns the primary type of resource this client works with."""
-        return 'backup'
+BackupsClient = moves.moved_class(
+    backups_client.BackupsClient, 'BackupsClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/capabilities_client.py b/tempest/lib/services/volume/v2/capabilities_client.py
index 240be13..d8cf806 100644
--- a/tempest/lib/services/volume/v2/capabilities_client.py
+++ b/tempest/lib/services/volume/v2/capabilities_client.py
@@ -13,23 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from oslo_serialization import jsonutils as json
+from debtcollector import moves
 
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import capabilities_client
 
 
-class CapabilitiesClient(rest_client.RestClient):
-    api_version = "v2"
-
-    def show_backend_capabilities(self, host):
-        """Shows capabilities for a storage back end.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/index.html#show-back-end-capabilities
-        """
-        url = 'capabilities/%s' % host
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+CapabilitiesClient = moves.moved_class(
+    capabilities_client.CapabilitiesClient, 'CapabilitiesClient',
+    __name__, version="Queens", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index 20f3356..875e59e 100644
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -13,71 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from oslo_serialization import jsonutils as json
+from debtcollector import moves
 
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume.v3 import encryption_types_client
 
 
-class EncryptionTypesClient(rest_client.RestClient):
-    api_version = "v2"
-
-    def is_resource_deleted(self, id):
-        try:
-            body = self.show_encryption_type(id)
-            if not body:
-                return True
-        except lib_exc.NotFound:
-            return True
-        return False
-
-    @property
-    def resource_type(self):
-        """Returns the primary type of resource this client works with."""
-        return 'encryption-type'
-
-    def show_encryption_type(self, volume_type_id):
-        """Get the volume encryption type for the specified volume type.
-
-        volume_type_id: Id of volume_type.
-        """
-        url = "/types/%s/encryption" % volume_type_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_encryption_type(self, volume_type_id, **kwargs):
-        """Create encryption type.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2
-        """
-        url = "/types/%s/encryption" % volume_type_id
-        post_body = json.dumps({'encryption': kwargs})
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_encryption_type(self, volume_type_id):
-        """Delete the encryption type for the specified volume-type."""
-        resp, body = self.delete(
-            "/types/%s/encryption/provider" % volume_type_id)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_encryption_type(self, volume_type_id, **kwargs):
-        """Update an encryption type for an existing volume type.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#update-an-encryption-type-for-v2
-        """
-        url = "/types/%s/encryption/provider" % volume_type_id
-        put_body = json.dumps({'encryption': kwargs})
-        resp, body = self.put(url, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+EncryptionTypesClient = moves.moved_class(
+    encryption_types_client.EncryptionTypesClient, 'EncryptionTypesClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/extensions_client.py b/tempest/lib/services/volume/v2/extensions_client.py
index 09279d5..6316ef5 100644
--- a/tempest/lib/services/volume/v2/extensions_client.py
+++ b/tempest/lib/services/volume/v2/extensions_client.py
@@ -12,19 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import extensions_client
 
 
-class ExtensionsClient(rest_client.RestClient):
-    """Volume V2 extensions client."""
-    api_version = "v2"
-
-    def list_extensions(self):
-        url = 'extensions'
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+ExtensionsClient = moves.moved_class(
+    extensions_client.ExtensionsClient, 'ExtensionsClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/hosts_client.py b/tempest/lib/services/volume/v2/hosts_client.py
index f44bda3..38f1b38 100644
--- a/tempest/lib/services/volume/v2/hosts_client.py
+++ b/tempest/lib/services/volume/v2/hosts_client.py
@@ -12,37 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import hosts_client
 
 
-class HostsClient(rest_client.RestClient):
-    """Client class to send CRUD Volume V2 API requests"""
-    api_version = "v2"
-
-    def list_hosts(self, **params):
-        """Lists all hosts.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts
-        """
-        url = 'os-hosts'
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_host(self, host_name):
-        """Show host details."""
-        url = 'os-hosts/%s' % host_name
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
+HostsClient = moves.moved_class(
+    hosts_client.HostsClient, 'HostsClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/limits_client.py b/tempest/lib/services/volume/v2/limits_client.py
index ce9fba9..a6b8c5a 100644
--- a/tempest/lib/services/volume/v2/limits_client.py
+++ b/tempest/lib/services/volume/v2/limits_client.py
@@ -12,21 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import limits_client
 
 
-class LimitsClient(rest_client.RestClient):
-    """Volume V2 limits client."""
-
-    api_version = "v2"
-
-    def show_limits(self):
-        """Returns the details of a volume absolute limits."""
-        url = "limits"
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+LimitsClient = moves.moved_class(
+    limits_client.LimitsClient, 'LimitsClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/qos_client.py b/tempest/lib/services/volume/v2/qos_client.py
index 47d3914..b81384e 100644
--- a/tempest/lib/services/volume/v2/qos_client.py
+++ b/tempest/lib/services/volume/v2/qos_client.py
@@ -11,123 +11,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume.v3 import qos_client
 
 
-class QosSpecsClient(rest_client.RestClient):
-    """Volume V2 QoS client.
-
-       Client class to send CRUD QoS API requests
-    """
-
-    api_version = "v2"
-
-    def is_resource_deleted(self, qos_id):
-        try:
-            self.show_qos(qos_id)
-        except lib_exc.NotFound:
-            return True
-        return False
-
-    @property
-    def resource_type(self):
-        """Returns the primary type of resource this client works with."""
-        return 'qos'
-
-    def create_qos(self, **kwargs):
-        """Create a QoS Specification.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#create-qos-specification
-        """
-        post_body = json.dumps({'qos_specs': kwargs})
-        resp, body = self.post('qos-specs', post_body)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_qos(self, qos_id, force=False):
-        """Delete the specified QoS specification."""
-        resp, body = self.delete(
-            "qos-specs/%s?force=%s" % (qos_id, force))
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_qos(self):
-        """List all the QoS specifications created."""
-        url = 'qos-specs'
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_qos(self, qos_id):
-        """Get the specified QoS specification."""
-        url = "qos-specs/%s" % qos_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def set_qos_key(self, qos_id, **kwargs):
-        """Set the specified keys/values of QoS specification.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#set-keys-in-qos-specification
-        """
-        put_body = json.dumps({"qos_specs": kwargs})
-        resp, body = self.put('qos-specs/%s' % qos_id, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def unset_qos_key(self, qos_id, keys):
-        """Unset the specified keys of QoS specification.
-
-        :param keys: keys to delete from the QoS specification.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#unset-keys-in-qos-specification
-        """
-        put_body = json.dumps({'keys': keys})
-        resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def associate_qos(self, qos_id, vol_type_id):
-        """Associate the specified QoS with specified volume-type."""
-        url = "qos-specs/%s/associate" % qos_id
-        url += "?vol_type_id=%s" % vol_type_id
-        resp, body = self.get(url)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_association_qos(self, qos_id):
-        """Get the association of the specified QoS specification."""
-        url = "qos-specs/%s/associations" % qos_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def disassociate_qos(self, qos_id, vol_type_id):
-        """Disassociate the specified QoS with specified volume-type."""
-        url = "qos-specs/%s/disassociate" % qos_id
-        url += "?vol_type_id=%s" % vol_type_id
-        resp, body = self.get(url)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def disassociate_all_qos(self, qos_id):
-        """Disassociate the specified QoS with all associations."""
-        url = "qos-specs/%s/disassociate_all" % qos_id
-        resp, body = self.get(url)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
+QosSpecsClient = moves.moved_class(
+    qos_client.QosSpecsClient, 'QosSpecsClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/quota_classes_client.py b/tempest/lib/services/volume/v2/quota_classes_client.py
index d40d2d9..24aab89 100644
--- a/tempest/lib/services/volume/v2/quota_classes_client.py
+++ b/tempest/lib/services/volume/v2/quota_classes_client.py
@@ -12,38 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import quota_classes_client
 
 
-class QuotaClassesClient(rest_client.RestClient):
-    """Volume quota class V2 client."""
-
-    api_version = "v2"
-
-    def show_quota_class_set(self, quota_class_id):
-        """List quotas for a quota class.
-
-        TODO: Current api-site doesn't contain this API description.
-        LP: https://bugs.launchpad.net/nova/+bug/1602400
-        """
-        url = 'os-quota-class-sets/%s' % quota_class_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_quota_class_set(self, quota_class_id, **kwargs):
-        """Update quotas for a quota class.
-
-        TODO: Current api-site doesn't contain this API description.
-        LP: https://bugs.launchpad.net/nova/+bug/1602400
-        """
-        url = 'os-quota-class-sets/%s' % quota_class_id
-        put_body = json.dumps({'quota_class_set': kwargs})
-        resp, body = self.put(url, put_body)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
+QuotaClassesClient = moves.moved_class(
+    quota_classes_client.QuotaClassesClient, 'QuotaClassesClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/quotas_client.py b/tempest/lib/services/volume/v2/quotas_client.py
index e4b2895..6f9f61c 100644
--- a/tempest/lib/services/volume/v2/quotas_client.py
+++ b/tempest/lib/services/volume/v2/quotas_client.py
@@ -12,53 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import quotas_client
 
 
-class QuotasClient(rest_client.RestClient):
-    """Client class to send CRUD Volume Quotas API V2 requests"""
-    api_version = "v2"
-
-    def show_default_quota_set(self, tenant_id):
-        """List the default volume quota set for a tenant."""
-
-        url = 'os-quota-sets/%s/defaults' % tenant_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = jsonutils.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_quota_set(self, tenant_id, params=None):
-        """List the quota set for a tenant."""
-
-        url = 'os-quota-sets/%s' % tenant_id
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = jsonutils.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_quota_set(self, tenant_id, **kwargs):
-        """Updates quota set
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/index.html#update-quotas
-        """
-        put_body = jsonutils.dumps({'quota_set': kwargs})
-        resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
-        self.expected_success(200, resp.status)
-        body = jsonutils.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_quota_set(self, tenant_id):
-        """Delete the tenant's quota set."""
-        resp, body = self.delete('os-quota-sets/%s' % tenant_id)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+QuotasClient = moves.moved_class(
+    quotas_client.QuotasClient, 'QuotasClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/scheduler_stats_client.py b/tempest/lib/services/volume/v2/scheduler_stats_client.py
index 0d04f85..a5adb34 100644
--- a/tempest/lib/services/volume/v2/scheduler_stats_client.py
+++ b/tempest/lib/services/volume/v2/scheduler_stats_client.py
@@ -12,26 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import scheduler_stats_client
 
 
-class SchedulerStatsClient(rest_client.RestClient):
-    api_version = "v2"
-
-    def list_pools(self, detail=False):
-        """List all the volumes pools (hosts).
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/index.html#list-back-end-storage-pools
-        """
-        url = 'scheduler-stats/get_pools'
-        if detail:
-            url += '?detail=True'
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+SchedulerStatsClient = moves.moved_class(
+    scheduler_stats_client.SchedulerStatsClient, 'SchedulerStatsClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/services_client.py b/tempest/lib/services/volume/v2/services_client.py
index bc55469..a4491d3 100644
--- a/tempest/lib/services/volume/v2/services_client.py
+++ b/tempest/lib/services/volume/v2/services_client.py
@@ -12,23 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import services_client
 
 
-class ServicesClient(rest_client.RestClient):
-    """Client class to send CRUD Volume V2 API requests"""
-    api_version = "v2"
-
-    def list_services(self, **params):
-        url = 'os-services'
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+ServicesClient = moves.moved_class(
+    services_client.ServicesClient, 'ServicesClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/snapshot_manage_client.py b/tempest/lib/services/volume/v2/snapshot_manage_client.py
index aecd30b..132209f 100644
--- a/tempest/lib/services/volume/v2/snapshot_manage_client.py
+++ b/tempest/lib/services/volume/v2/snapshot_manage_client.py
@@ -12,22 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import snapshot_manage_client
 
 
-class SnapshotManageClient(rest_client.RestClient):
-    """Snapshot manage V2 client."""
-
-    api_version = "v2"
-
-    def manage_snapshot(self, **kwargs):
-        """Manage a snapshot."""
-        post_body = json.dumps({'snapshot': kwargs})
-        url = 'os-snapshot-manage'
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
+SnapshotManageClient = moves.moved_class(
+    snapshot_manage_client.SnapshotManageClient, 'SnapshotManageClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index 4bc2842..3a72cc1 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -9,200 +9,11 @@
 #    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 debtcollector import moves
 
-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 import exceptions as lib_exc
+from tempest.lib.services.volume.v3 import snapshots_client
 
 
-class SnapshotsClient(rest_client.RestClient):
-    """Client class to send CRUD Volume V2 API requests."""
-    api_version = "v2"
-    create_resp = 202
-
-    def list_snapshots(self, detail=False, **params):
-        """List all the snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-with-details
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots
-        """
-        url = 'snapshots'
-        if detail:
-            url += '/detail'
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_snapshot(self, snapshot_id):
-        """Returns the details of a single snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-details
-        """
-        url = "snapshots/%s" % snapshot_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_snapshot(self, **kwargs):
-        """Creates a new snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot
-        """
-        post_body = json.dumps({'snapshot': kwargs})
-        resp, body = self.post('snapshots', post_body)
-        body = json.loads(body)
-        self.expected_success(self.create_resp, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_snapshot(self, snapshot_id, **kwargs):
-        """Updates a snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot
-        """
-        put_body = json.dumps({'snapshot': kwargs})
-        resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_snapshot(self, snapshot_id):
-        """Delete Snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot
-        """
-        resp, body = self.delete("snapshots/%s" % snapshot_id)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def is_resource_deleted(self, id):
-        try:
-            self.show_snapshot(id)
-        except lib_exc.NotFound:
-            return True
-        return False
-
-    @property
-    def resource_type(self):
-        """Returns the primary type of resource this client works with."""
-        return 'volume-snapshot'
-
-    def reset_snapshot_status(self, snapshot_id, status):
-        """Reset the specified snapshot's status."""
-        post_body = json.dumps({'os-reset_status': {"status": status}})
-        resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_snapshot_status(self, snapshot_id, **kwargs):
-        """Update the specified snapshot's status."""
-        # TODO(gmann): api-site doesn't contain doc ref
-        # for this API. After fixing the api-site, we need to
-        # add the link here.
-        # Bug https://bugs.launchpad.net/openstack-api-site/+bug/1532645
-
-        post_body = json.dumps({'os-update_snapshot_status': kwargs})
-        url = 'snapshots/%s/action' % snapshot_id
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_snapshot_metadata(self, snapshot_id, metadata):
-        """Create metadata for the snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot-metadata
-        """
-        put_body = json.dumps({'metadata': metadata})
-        url = "snapshots/%s/metadata" % snapshot_id
-        resp, body = self.post(url, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_snapshot_metadata(self, snapshot_id):
-        """Get metadata of the snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata
-        """
-        url = "snapshots/%s/metadata" % snapshot_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_snapshot_metadata(self, snapshot_id, **kwargs):
-        """Update metadata for the snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata
-        """
-        put_body = json.dumps(kwargs)
-        url = "snapshots/%s/metadata" % snapshot_id
-        resp, body = self.put(url, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_snapshot_metadata_item(self, snapshot_id, id):
-        """Show metadata item for the snapshot."""
-        url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
-        """Update metadata item for the snapshot."""
-        # TODO(piyush): Current api-site doesn't contain this API description.
-        # After fixing the api-site, we need to fix here also for putting the
-        # link to api-site.
-        # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1529064
-        put_body = json.dumps(kwargs)
-        url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
-        resp, body = self.put(url, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_snapshot_metadata_item(self, snapshot_id, id):
-        """Delete metadata item for the snapshot."""
-        url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
-        resp, body = self.delete(url)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def force_delete_snapshot(self, snapshot_id):
-        """Force Delete Snapshot."""
-        post_body = json.dumps({'os-force_delete': {}})
-        resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def unmanage_snapshot(self, snapshot_id):
-        """Unmanage a snapshot."""
-        post_body = json.dumps({'os-unmanage': {}})
-        url = 'snapshots/%s/action' % (snapshot_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
+SnapshotsClient = moves.moved_class(
+    snapshots_client.SnapshotsClient, 'SnapshotsClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/transfers_client.py b/tempest/lib/services/volume/v2/transfers_client.py
index 2dfbe7b..701d0ae 100644
--- a/tempest/lib/services/volume/v2/transfers_client.py
+++ b/tempest/lib/services/volume/v2/transfers_client.py
@@ -12,72 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import transfers_client
 
 
-class TransfersClient(rest_client.RestClient):
-    """Client class to send CRUD Volume Transfer V2 API requests"""
-    api_version = "v2"
-
-    def create_volume_transfer(self, **kwargs):
-        """Create a volume transfer.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-transfer
-        """
-        post_body = json.dumps({'transfer': kwargs})
-        resp, body = self.post('os-volume-transfer', post_body)
-        body = json.loads(body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_volume_transfer(self, transfer_id):
-        """Returns the details of a volume transfer."""
-        url = "os-volume-transfer/%s" % transfer_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_volume_transfers(self, detail=False, **params):
-        """List all the volume transfers created.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers
-        https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers-with-details
-        """
-        url = 'os-volume-transfer'
-        if detail:
-            url += '/detail'
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_volume_transfer(self, transfer_id):
-        """Delete a volume transfer."""
-        resp, body = self.delete("os-volume-transfer/%s" % transfer_id)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def accept_volume_transfer(self, transfer_id, **kwargs):
-        """Accept a volume transfer.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#accept-volume-transfer
-        """
-        url = 'os-volume-transfer/%s/accept' % transfer_id
-        post_body = json.dumps({'accept': kwargs})
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
+TransfersClient = moves.moved_class(
+    transfers_client.TransfersClient, 'TransfersClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/types_client.py b/tempest/lib/services/volume/v2/types_client.py
index af4fd8c..8457f91 100644
--- a/tempest/lib/services/volume/v2/types_client.py
+++ b/tempest/lib/services/volume/v2/types_client.py
@@ -12,194 +12,11 @@
 #    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 debtcollector import moves
 
-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 import exceptions as lib_exc
+from tempest.lib.services.volume.v3 import types_client
 
 
-class TypesClient(rest_client.RestClient):
-    """Client class to send CRUD Volume V2 API requests"""
-    api_version = "v2"
-
-    def is_resource_deleted(self, id):
-        try:
-            self.show_volume_type(id)
-        except lib_exc.NotFound:
-            return True
-        return False
-
-    @property
-    def resource_type(self):
-        """Returns the primary type of resource this client works with."""
-        return 'volume-type'
-
-    def list_volume_types(self, **params):
-        """List all the volume_types created.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#list-all-volume-types-for-v2
-        """
-        url = 'types'
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_volume_type(self, volume_type_id):
-        """Returns the details of a single volume_type.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details-for-v2
-        """
-        url = "types/%s" % volume_type_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_volume_type(self, **kwargs):
-        """Create volume type.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-for-v2
-        """
-        post_body = json.dumps({'volume_type': kwargs})
-        resp, body = self.post('types', post_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_volume_type(self, volume_type_id):
-        """Deletes the Specified Volume_type.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type
-        """
-        resp, body = self.delete("types/%s" % volume_type_id)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_volume_types_extra_specs(self, volume_type_id, **params):
-        """List all the volume_types extra specs created.
-
-        TODO: Current api-site doesn't contain this API description.
-        After fixing the api-site, we need to fix here also for putting
-        the link to api-site.
-        """
-        url = 'types/%s/extra_specs' % volume_type_id
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name):
-        """Returns the details of a single volume_type extra spec."""
-        url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name)
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
-        """Creates a new Volume_type extra spec.
-
-        volume_type_id: Id of volume_type.
-        extra_specs: A dictionary of values to be used as extra_specs.
-        """
-        url = "types/%s/extra_specs" % volume_type_id
-        post_body = json.dumps({'extra_specs': extra_specs})
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name):
-        """Deletes the Specified Volume_type extra spec."""
-        resp, body = self.delete("types/%s/extra_specs/%s" % (
-            volume_type_id, extra_spec_name))
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_volume_type(self, volume_type_id, **kwargs):
-        """Updates volume type name, description, and/or is_public.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type
-        """
-        put_body = json.dumps({'volume_type': kwargs})
-        resp, body = self.put('types/%s' % volume_type_id, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name,
-                                       extra_specs):
-        """Update a volume_type extra spec.
-
-        volume_type_id: Id of volume_type.
-        extra_spec_name: Name of the extra spec to be updated.
-        extra_spec: A dictionary of with key as extra_spec_name and the
-                     updated value.
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-extra-specs-for-a-volume-type
-        """
-        url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
-        put_body = json.dumps(extra_specs)
-        resp, body = self.put(url, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def add_type_access(self, volume_type_id, **kwargs):
-        """Adds volume type access for the given project.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#add-private-volume-type-access
-        """
-        post_body = json.dumps({'addProjectAccess': kwargs})
-        url = 'types/%s/action' % volume_type_id
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def remove_type_access(self, volume_type_id, **kwargs):
-        """Removes volume type access for the given project.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#remove-private-volume-type-access
-        """
-        post_body = json.dumps({'removeProjectAccess': kwargs})
-        url = 'types/%s/action' % volume_type_id
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_type_access(self, volume_type_id):
-        """Print access information about the given volume type.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-private-volume-type-access-details
-        """
-        url = 'types/%s/os-volume-type-access' % volume_type_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
+TypesClient = moves.moved_class(
+    types_client.TypesClient, 'TypesClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/volume_manage_client.py b/tempest/lib/services/volume/v2/volume_manage_client.py
index 12f4240..0669326 100644
--- a/tempest/lib/services/volume/v2/volume_manage_client.py
+++ b/tempest/lib/services/volume/v2/volume_manage_client.py
@@ -12,26 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import volume_manage_client
 
 
-class VolumeManageClient(rest_client.RestClient):
-    """Volume manage V2 client."""
-
-    api_version = "v2"
-
-    def manage_volume(self, **kwargs):
-        """Manage existing volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#manage-existing-volume
-        """
-        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)
-        return rest_client.ResponseBody(resp, body)
+VolumeManageClient = moves.moved_class(
+    volume_manage_client.VolumeManageClient, 'VolumeManageClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index da3f2b5..f5f9e6e 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -12,341 +12,11 @@
 #    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 debtcollector import moves
 
-from oslo_serialization import jsonutils as json
-import six
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.volume import base_client
+from tempest.lib.services.volume.v3 import volumes_client
 
 
-class VolumesClient(base_client.BaseClient):
-    """Client class to send CRUD Volume V2 API requests"""
-    api_version = "v2"
-
-    def _prepare_params(self, params):
-        """Prepares params for use in get or _ext_get methods.
-
-        If params is a string it will be left as it is, but if it's not it will
-        be urlencoded.
-        """
-        if isinstance(params, six.string_types):
-            return params
-        return urllib.urlencode(params)
-
-    def list_volumes(self, detail=False, params=None):
-        """List all the volumes created.
-
-        Params can be a string (must be urlencoded) or a dictionary.
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes
-        """
-        url = 'volumes'
-        if detail:
-            url += '/detail'
-        if params:
-            url += '?%s' % self._prepare_params(params)
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_volume(self, volume_id):
-        """Returns the details of a single volume."""
-        url = "volumes/%s" % volume_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_volume(self, **kwargs):
-        """Creates a new Volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#create-volume
-        """
-        post_body = json.dumps({'volume': kwargs})
-        resp, body = self.post('volumes', post_body)
-        body = json.loads(body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_volume(self, volume_id, **kwargs):
-        """Updates the Specified Volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume
-        """
-        put_body = json.dumps({'volume': kwargs})
-        resp, body = self.put('volumes/%s' % volume_id, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_volume(self, volume_id, **params):
-        """Deletes the Specified Volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#delete-volume
-        """
-        url = 'volumes/%s' % volume_id
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-        resp, body = self.delete(url)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def upload_volume(self, volume_id, **kwargs):
-        """Uploads a volume in Glance."""
-        post_body = json.dumps({'os-volume_upload_image': kwargs})
-        url = 'volumes/%s/action' % (volume_id)
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def attach_volume(self, volume_id, **kwargs):
-        """Attaches a volume to a given instance on a given mountpoint.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#attach-volume-to-server
-        """
-        post_body = json.dumps({'os-attach': kwargs})
-        url = 'volumes/%s/action' % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def set_bootable_volume(self, volume_id, **kwargs):
-        """Set a bootable flag for a volume - true or false.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-bootable-status
-        """
-        post_body = json.dumps({'os-set_bootable': kwargs})
-        url = 'volumes/%s/action' % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def detach_volume(self, volume_id):
-        """Detaches a volume from an instance."""
-        post_body = json.dumps({'os-detach': {}})
-        url = 'volumes/%s/action' % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def reserve_volume(self, volume_id):
-        """Reserves a volume."""
-        post_body = json.dumps({'os-reserve': {}})
-        url = 'volumes/%s/action' % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def unreserve_volume(self, volume_id):
-        """Restore a reserved volume ."""
-        post_body = json.dumps({'os-unreserve': {}})
-        url = 'volumes/%s/action' % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def is_resource_deleted(self, id):
-        """Check the specified resource is deleted or not.
-
-        :param id: A checked resource id
-        :raises lib_exc.DeleteErrorException: If the specified resource is on
-        the status the delete was failed.
-        """
-        try:
-            volume = self.show_volume(id)
-        except lib_exc.NotFound:
-            return True
-        if volume["volume"]["status"] == "error_deleting":
-            raise lib_exc.DeleteErrorException(resource_id=id)
-        return False
-
-    @property
-    def resource_type(self):
-        """Returns the primary type of resource this client works with."""
-        return 'volume'
-
-    def extend_volume(self, volume_id, **kwargs):
-        """Extend a volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#extend-volume-size
-        """
-        post_body = json.dumps({'os-extend': kwargs})
-        url = 'volumes/%s/action' % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def reset_volume_status(self, volume_id, **kwargs):
-        """Reset the Specified Volume's Status.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-statuses
-        """
-        post_body = json.dumps({'os-reset_status': kwargs})
-        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_volume_readonly(self, volume_id, **kwargs):
-        """Update the Specified Volume readonly."""
-        post_body = json.dumps({'os-update_readonly_flag': kwargs})
-        url = 'volumes/%s/action' % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def force_delete_volume(self, volume_id):
-        """Force Delete Volume."""
-        post_body = json.dumps({'os-force_delete': {}})
-        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_volume_metadata(self, volume_id, metadata):
-        """Create metadata for the volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-metadata
-        """
-        put_body = json.dumps({'metadata': metadata})
-        url = "volumes/%s/metadata" % volume_id
-        resp, body = self.post(url, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_volume_metadata(self, volume_id):
-        """Get metadata of the volume."""
-        url = "volumes/%s/metadata" % volume_id
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_volume_metadata(self, volume_id, metadata):
-        """Update metadata for the volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-metadata
-        """
-        put_body = json.dumps({'metadata': metadata})
-        url = "volumes/%s/metadata" % volume_id
-        resp, body = self.put(url, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_volume_metadata_item(self, volume_id, id):
-        """Show metadata item for the volume."""
-        url = "volumes/%s/metadata/%s" % (volume_id, id)
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_volume_metadata_item(self, volume_id, id, meta_item):
-        """Update metadata item for the volume."""
-        put_body = json.dumps({'meta': meta_item})
-        url = "volumes/%s/metadata/%s" % (volume_id, id)
-        resp, body = self.put(url, put_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_volume_metadata_item(self, volume_id, id):
-        """Delete metadata item for the volume."""
-        url = "volumes/%s/metadata/%s" % (volume_id, id)
-        resp, body = self.delete(url)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def retype_volume(self, volume_id, **kwargs):
-        """Updates volume with new volume type.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#retype-volume
-        """
-        post_body = json.dumps({'os-retype': kwargs})
-        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def force_detach_volume(self, volume_id, **kwargs):
-        """Force detach a volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#force-detach-volume
-        """
-        post_body = json.dumps({'os-force_detach': kwargs})
-        url = 'volumes/%s/action' % volume_id
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_volume_image_metadata(self, volume_id, **kwargs):
-        """Update image metadata for the volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#set-image-metadata-for-volume
-        """
-        post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
-        url = "volumes/%s/action" % (volume_id)
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_volume_image_metadata(self, volume_id, key_name):
-        """Delete image metadata item for the volume."""
-        post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
-        url = "volumes/%s/action" % (volume_id)
-        resp, body = self.post(url, post_body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_volume_image_metadata(self, volume_id):
-        """Show image metadata for the volume."""
-        post_body = json.dumps({'os-show_image_metadata': {}})
-        url = "volumes/%s/action" % volume_id
-        resp, body = self.post(url, post_body)
-        body = json.loads(body)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def unmanage_volume(self, volume_id):
-        """Unmanage volume.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://developer.openstack.org/api-ref/block-storage/v2/#unmanage-volume
-        """
-        post_body = json.dumps({'os-unmanage': {}})
-        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
+VolumesClient = moves.moved_class(
+    volumes_client.VolumesClient, 'VolumesClient',
+    __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
index 2d85553..a1b7de3 100644
--- a/tempest/lib/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -11,19 +11,44 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-
+from tempest.lib.services.volume.v3.availability_zone_client \
+    import AvailabilityZoneClient
 from tempest.lib.services.volume.v3.backups_client import BackupsClient
 from tempest.lib.services.volume.v3.base_client import BaseClient
+from tempest.lib.services.volume.v3.capabilities_client import \
+    CapabilitiesClient
+from tempest.lib.services.volume.v3.encryption_types_client import \
+    EncryptionTypesClient
+from tempest.lib.services.volume.v3.extensions_client import ExtensionsClient
 from tempest.lib.services.volume.v3.group_snapshots_client import \
     GroupSnapshotsClient
 from tempest.lib.services.volume.v3.group_types_client import GroupTypesClient
 from tempest.lib.services.volume.v3.groups_client import GroupsClient
+from tempest.lib.services.volume.v3.hosts_client import HostsClient
+from tempest.lib.services.volume.v3.limits_client import LimitsClient
 from tempest.lib.services.volume.v3.messages_client import MessagesClient
+from tempest.lib.services.volume.v3.qos_client import QosSpecsClient
+from tempest.lib.services.volume.v3.quota_classes_client import \
+    QuotaClassesClient
+from tempest.lib.services.volume.v3.quotas_client import QuotasClient
+from tempest.lib.services.volume.v3.scheduler_stats_client import \
+    SchedulerStatsClient
+from tempest.lib.services.volume.v3.services_client import ServicesClient
+from tempest.lib.services.volume.v3.snapshot_manage_client import \
+    SnapshotManageClient
 from tempest.lib.services.volume.v3.snapshots_client import SnapshotsClient
+from tempest.lib.services.volume.v3.transfers_client import TransfersClient
+from tempest.lib.services.volume.v3.types_client import TypesClient
 from tempest.lib.services.volume.v3.versions_client import VersionsClient
+from tempest.lib.services.volume.v3.volume_manage_client import \
+    VolumeManageClient
 from tempest.lib.services.volume.v3.volumes_client import VolumesClient
 
-__all__ = ['BackupsClient', 'BaseClient', 'GroupsClient',
-           'GroupSnapshotsClient', 'GroupTypesClient',
-           'MessagesClient', 'SnapshotsClient', 'VersionsClient',
-           'VolumesClient']
+__all__ = ['AvailabilityZoneClient', 'BackupsClient', 'BaseClient',
+           'CapabilitiesClient', 'EncryptionTypesClient', 'ExtensionsClient',
+           'GroupSnapshotsClient', 'GroupTypesClient', 'GroupsClient',
+           'HostsClient', 'LimitsClient', 'MessagesClient', 'QosSpecsClient',
+           'QuotaClassesClient', 'QuotasClient', 'SchedulerStatsClient',
+           'ServicesClient', 'SnapshotManageClient', 'SnapshotsClient',
+           'TransfersClient', 'TypesClient', 'VersionsClient',
+           'VolumeManageClient', 'VolumesClient']
diff --git a/tempest/lib/services/volume/v3/availability_zone_client.py b/tempest/lib/services/volume/v3/availability_zone_client.py
new file mode 100644
index 0000000..147e4c6
--- /dev/null
+++ b/tempest/lib/services/volume/v3/availability_zone_client.py
@@ -0,0 +1,27 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class AvailabilityZoneClient(rest_client.RestClient):
+
+    def list_availability_zones(self):
+        resp, body = self.get('os-availability-zone')
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index e742e39..fb64333 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -14,14 +14,28 @@
 #    under the License.
 
 from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
-from tempest.lib.services.volume.v2 import backups_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
 
 
-class BackupsClient(backups_client.BackupsClient):
+class BackupsClient(base_client.BaseClient):
     """Volume V3 Backups client"""
-    api_version = "v3"
+
+    def create_backup(self, **kwargs):
+        """Creates a backup of volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-backup
+        """
+        post_body = json.dumps({'backup': kwargs})
+        resp, body = self.post('backups', post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
 
     def update_backup(self, backup_id, **kwargs):
         """Updates the specified volume backup.
@@ -35,3 +49,88 @@
         body = json.loads(body)
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def restore_backup(self, backup_id, **kwargs):
+        """Restore volume from backup.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#restore-a-backup
+        """
+        post_body = json.dumps({'restore': kwargs})
+        resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_backup(self, backup_id):
+        """Delete a backup of volume."""
+        resp, body = self.delete('backups/%s' % backup_id)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_backup(self, backup_id):
+        """Returns the details of a single backup."""
+        url = "backups/%s" % backup_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_backups(self, detail=False, **params):
+        """List all the tenant's backups.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-backups-for-project
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-backups-with-detail
+        """
+        url = "backups"
+        if detail:
+            url += "/detail"
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def export_backup(self, backup_id):
+        """Export backup metadata record."""
+        url = "backups/%s/export_record" % backup_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def import_backup(self, **kwargs):
+        """Import backup metadata record.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#import-a-backup
+        """
+        post_body = json.dumps({'backup-record': kwargs})
+        resp, body = self.post("backups/import_record", post_body)
+        body = json.loads(body)
+        self.expected_success(201, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def reset_backup_status(self, backup_id, status):
+        """Reset the specified backup's status."""
+        post_body = json.dumps({'os-reset_status': {"status": status}})
+        resp, body = self.post('backups/%s/action' % backup_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.show_backup(id)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'backup'
diff --git a/tempest/lib/services/volume/v3/base_client.py b/tempest/lib/services/volume/v3/base_client.py
index e78380b..617da2e 100644
--- a/tempest/lib/services/volume/v3/base_client.py
+++ b/tempest/lib/services/volume/v3/base_client.py
@@ -20,4 +20,3 @@
 
 BaseClient = moves.moved_class(base_client.BaseClient, 'BaseClient', __name__,
                                version="Pike", removal_version='?')
-BaseClient.api_version = 'v3'
diff --git a/tempest/lib/services/volume/v3/capabilities_client.py b/tempest/lib/services/volume/v3/capabilities_client.py
new file mode 100644
index 0000000..7ebcd69
--- /dev/null
+++ b/tempest/lib/services/volume/v3/capabilities_client.py
@@ -0,0 +1,34 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class CapabilitiesClient(rest_client.RestClient):
+
+    def show_backend_capabilities(self, host):
+        """Shows capabilities for a storage back end.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/index.html#show-back-end-capabilities
+        """
+        url = 'capabilities/%s' % host
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/encryption_types_client.py b/tempest/lib/services/volume/v3/encryption_types_client.py
new file mode 100644
index 0000000..03de187
--- /dev/null
+++ b/tempest/lib/services/volume/v3/encryption_types_client.py
@@ -0,0 +1,90 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class EncryptionTypesClient(rest_client.RestClient):
+
+    def is_resource_deleted(self, id):
+        try:
+            body = self.show_encryption_type(id)
+            if not body:
+                return True
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'encryption-type'
+
+    def show_encryption_type(self, volume_type_id):
+        """Get the volume encryption type for the specified volume type.
+
+        :param volume_type_id: Id of volume type.
+        """
+        url = "/types/%s/encryption" % volume_type_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_encryption_specs_item(self, volume_type_id, key):
+        """Get the encryption specs item for the specified volume type."""
+        url = "/types/%s/encryption/%s" % (volume_type_id, key)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_encryption_type(self, volume_type_id, **kwargs):
+        """Create encryption type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-an-encryption-type
+        """
+        url = "/types/%s/encryption" % volume_type_id
+        post_body = json.dumps({'encryption': kwargs})
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_encryption_type(self, volume_type_id):
+        """Delete the encryption type for the specified volume-type."""
+        resp, body = self.delete(
+            "/types/%s/encryption/provider" % volume_type_id)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_encryption_type(self, volume_type_id, **kwargs):
+        """Update an encryption type for an existing volume type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-an-encryption-type
+        """
+        url = "/types/%s/encryption/provider" % volume_type_id
+        put_body = json.dumps({'encryption': kwargs})
+        resp, body = self.put(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/extensions_client.py b/tempest/lib/services/volume/v3/extensions_client.py
new file mode 100644
index 0000000..45b7a56
--- /dev/null
+++ b/tempest/lib/services/volume/v3/extensions_client.py
@@ -0,0 +1,29 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ExtensionsClient(rest_client.RestClient):
+    """Volume extensions client."""
+
+    def list_extensions(self):
+        url = 'extensions'
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/group_snapshots_client.py b/tempest/lib/services/volume/v3/group_snapshots_client.py
index 6e53e3e..16412d3 100644
--- a/tempest/lib/services/volume/v3/group_snapshots_client.py
+++ b/tempest/lib/services/volume/v3/group_snapshots_client.py
@@ -23,7 +23,6 @@
 
 class GroupSnapshotsClient(base_client.BaseClient):
     """Client class to send CRUD Volume Group Snapshot API requests"""
-    api_version = 'v3'
 
     def create_group_snapshot(self, **kwargs):
         """Creates a group snapshot.
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index 97bac48..1ccb9f8 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -22,7 +22,6 @@
 
 class GroupTypesClient(base_client.BaseClient):
     """Client class to send CRUD Volume V3 Group Types API requests"""
-    api_version = 'v3'
 
     @property
     def resource_type(self):
@@ -75,3 +74,67 @@
         body = json.loads(body)
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def update_group_type(self, group_type_id, **kwargs):
+        """Updates a group type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#update-group-type
+        """
+        post_body = json.dumps({'group_type': kwargs})
+        resp, body = self.put('group_types/%s' % group_type_id, post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_or_update_group_type_specs(self, group_type_id, group_specs):
+        """Creates new group specs or updates existing group specs.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#create-or-update-group-specs-for-a-group-type
+        """
+        url = "group_types/%s/group_specs" % group_type_id
+        post_body = json.dumps({'group_specs': group_specs})
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_group_type_specs(self, group_type_id):
+        """Lists all group specs for a given group type."""
+        url = 'group_types/%s/group_specs' % group_type_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_group_type_specs_item(self, group_type_id, spec_id):
+        """Shows specified item of group specs for a given group type."""
+        url = "group_types/%s/group_specs/%s" % (group_type_id, spec_id)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_group_type_specs_item(self, group_type_id, spec_id, spec):
+        """Updates specified item of group specs for a given group type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#update-one-specific-group-spec-for-a-group-type
+        """
+        url = "group_types/%s/group_specs/%s" % (group_type_id, spec_id)
+        put_body = json.dumps(spec)
+        resp, body = self.put(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_group_type_specs_item(self, group_type_id, spec_id):
+        """Deletes specified item of group specs for a given group type."""
+        resp, body = self.delete("group_types/%s/group_specs/%s" % (
+            group_type_id, spec_id))
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index e2e477d..3cf1e6a 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -23,7 +23,6 @@
 
 class GroupsClient(base_client.BaseClient):
     """Client class to send CRUD Volume Group API requests"""
-    api_version = 'v3'
 
     def create_group(self, **kwargs):
         """Creates a group.
diff --git a/tempest/lib/services/volume/v3/hosts_client.py b/tempest/lib/services/volume/v3/hosts_client.py
new file mode 100644
index 0000000..8b65805
--- /dev/null
+++ b/tempest/lib/services/volume/v3/hosts_client.py
@@ -0,0 +1,47 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class HostsClient(rest_client.RestClient):
+    """Client class to send CRUD Volume API requests"""
+
+    def list_hosts(self, **params):
+        """Lists all hosts.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-all-hosts-for-a-project
+        """
+        url = 'os-hosts'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_host(self, host_name):
+        """Show host details."""
+        url = 'os-hosts/%s' % host_name
+        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/limits_client.py b/tempest/lib/services/volume/v3/limits_client.py
new file mode 100644
index 0000000..9500254
--- /dev/null
+++ b/tempest/lib/services/volume/v3/limits_client.py
@@ -0,0 +1,30 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class LimitsClient(rest_client.RestClient):
+    """Volume limits client."""
+
+    def show_limits(self):
+        """Returns the details of a volume absolute limits."""
+        url = "limits"
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/messages_client.py b/tempest/lib/services/volume/v3/messages_client.py
index 0127271..47538cd 100644
--- a/tempest/lib/services/volume/v3/messages_client.py
+++ b/tempest/lib/services/volume/v3/messages_client.py
@@ -22,7 +22,6 @@
 
 class MessagesClient(base_client.BaseClient):
     """Client class to send user messages API requests."""
-    api_version = 'v3'
 
     def show_message(self, message_id):
         """Show details for a single message."""
diff --git a/tempest/lib/services/volume/v3/qos_client.py b/tempest/lib/services/volume/v3/qos_client.py
new file mode 100644
index 0000000..5205590
--- /dev/null
+++ b/tempest/lib/services/volume/v3/qos_client.py
@@ -0,0 +1,132 @@
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.volume import qos as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class QosSpecsClient(rest_client.RestClient):
+    """Volume QoS client.
+
+       Client class to send CRUD QoS API requests
+    """
+
+    def is_resource_deleted(self, qos_id):
+        try:
+            self.show_qos(qos_id)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'qos'
+
+    def create_qos(self, **kwargs):
+        """Create a QoS Specification.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-qos-specification
+        """
+        post_body = json.dumps({'qos_specs': kwargs})
+        resp, body = self.post('qos-specs', post_body)
+        body = json.loads(body)
+        self.validate_response(schema.show_qos, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_qos(self, qos_id, force=False):
+        """Delete the specified QoS specification."""
+        resp, body = self.delete(
+            "qos-specs/%s?force=%s" % (qos_id, force))
+        self.validate_response(schema.delete_qos, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_qos(self):
+        """List all the QoS specifications created."""
+        url = 'qos-specs'
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.validate_response(schema.list_qos, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_qos(self, qos_id):
+        """Get the specified QoS specification."""
+        url = "qos-specs/%s" % qos_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.validate_response(schema.show_qos, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def set_qos_key(self, qos_id, **kwargs):
+        """Set the specified keys/values of QoS specification.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#set-keys-in-a-qos-specification
+        """
+        put_body = json.dumps({"qos_specs": kwargs})
+        resp, body = self.put('qos-specs/%s' % qos_id, put_body)
+        body = json.loads(body)
+        self.validate_response(schema.set_qos_key, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def unset_qos_key(self, qos_id, keys):
+        """Unset the specified keys of QoS specification.
+
+        :param keys: keys to delete from the QoS specification.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#unset-keys-in-a-qos-specification
+        """
+        put_body = json.dumps({'keys': keys})
+        resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
+        self.validate_response(schema.unset_qos_key, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def associate_qos(self, qos_id, vol_type_id):
+        """Associate the specified QoS with specified volume-type."""
+        url = "qos-specs/%s/associate" % qos_id
+        url += "?vol_type_id=%s" % vol_type_id
+        resp, body = self.get(url)
+        self.validate_response(schema.associate_qos, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_association_qos(self, qos_id):
+        """Get the association of the specified QoS specification."""
+        url = "qos-specs/%s/associations" % qos_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.validate_response(schema.show_association_qos, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def disassociate_qos(self, qos_id, vol_type_id):
+        """Disassociate the specified QoS with specified volume-type."""
+        url = "qos-specs/%s/disassociate" % qos_id
+        url += "?vol_type_id=%s" % vol_type_id
+        resp, body = self.get(url)
+        self.validate_response(schema.disassociate_qos, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def disassociate_all_qos(self, qos_id):
+        """Disassociate the specified QoS with all associations."""
+        url = "qos-specs/%s/disassociate_all" % qos_id
+        resp, body = self.get(url)
+        self.validate_response(schema.disassociate_all_qos, resp, body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/quota_classes_client.py b/tempest/lib/services/volume/v3/quota_classes_client.py
new file mode 100644
index 0000000..a8eb536
--- /dev/null
+++ b/tempest/lib/services/volume/v3/quota_classes_client.py
@@ -0,0 +1,49 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class QuotaClassesClient(rest_client.RestClient):
+    """Volume quota class client."""
+
+    def show_quota_class_set(self, quota_class_id):
+        """List quotas for a quota class.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#show-quota-classes-for-a-project
+        """
+        url = 'os-quota-class-sets/%s' % quota_class_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_quota_class_set(self, quota_class_id, **kwargs):
+        """Update quotas for a quota class.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-quota-classes-for-a-project
+        """
+        url = 'os-quota-class-sets/%s' % quota_class_id
+        put_body = json.dumps({'quota_class_set': kwargs})
+        resp, body = self.put(url, put_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/quotas_client.py b/tempest/lib/services/volume/v3/quotas_client.py
new file mode 100644
index 0000000..538a915
--- /dev/null
+++ b/tempest/lib/services/volume/v3/quotas_client.py
@@ -0,0 +1,63 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class QuotasClient(rest_client.RestClient):
+    """Client class to send CRUD Volume Quotas API requests"""
+
+    def show_default_quota_set(self, tenant_id):
+        """List the default volume quota set for a tenant."""
+
+        url = 'os-quota-sets/%s/defaults' % tenant_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_quota_set(self, tenant_id, params=None):
+        """List the quota set for a tenant."""
+
+        url = 'os-quota-sets/%s' % tenant_id
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_quota_set(self, tenant_id, **kwargs):
+        """Updates quota set
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-quotas-for-a-project
+        """
+        put_body = jsonutils.dumps({'quota_set': kwargs})
+        resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_quota_set(self, tenant_id):
+        """Delete the tenant's quota set."""
+        resp, body = self.delete('os-quota-sets/%s' % tenant_id)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/scheduler_stats_client.py b/tempest/lib/services/volume/v3/scheduler_stats_client.py
new file mode 100644
index 0000000..9b80851
--- /dev/null
+++ b/tempest/lib/services/volume/v3/scheduler_stats_client.py
@@ -0,0 +1,36 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class SchedulerStatsClient(rest_client.RestClient):
+
+    def list_pools(self, detail=False):
+        """List all the volumes pools (hosts).
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-all-back-end-storage-pools
+        """
+        url = 'scheduler-stats/get_pools'
+        if detail:
+            url += '?detail=True'
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/services_client.py b/tempest/lib/services/volume/v3/services_client.py
new file mode 100644
index 0000000..22155a9
--- /dev/null
+++ b/tempest/lib/services/volume/v3/services_client.py
@@ -0,0 +1,102 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class ServicesClient(rest_client.RestClient):
+    """Client class to send CRUD Volume Services API requests"""
+
+    def list_services(self, **params):
+        """List all Cinder services.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#list-all-cinder-services
+        """
+        url = 'os-services'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def enable_service(self, **kwargs):
+        """Enable service on a host.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#enable-a-cinder-service
+        """
+        put_body = json.dumps(kwargs)
+        resp, body = self.put('os-services/enable', put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def disable_service(self, **kwargs):
+        """Disable service on a host.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#disable-a-cinder-service
+        """
+        put_body = json.dumps(kwargs)
+        resp, body = self.put('os-services/disable', put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def disable_log_reason(self, **kwargs):
+        """Disable scheduling for a volume service and log disabled reason.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#log-disabled-cinder-service-information
+        """
+        put_body = json.dumps(kwargs)
+        resp, body = self.put('os-services/disable-log-reason', put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def freeze_host(self, **kwargs):
+        """Freeze a Cinder backend host.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#freeze-a-cinder-backend-host
+        """
+        put_body = json.dumps(kwargs)
+        resp, _ = self.put('os-services/freeze', put_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp)
+
+    def thaw_host(self, **kwargs):
+        """Thaw a Cinder backend host.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#thaw-a-cinder-backend-host
+        """
+        put_body = json.dumps(kwargs)
+        resp, _ = self.put('os-services/thaw', put_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/volume/v3/snapshot_manage_client.py b/tempest/lib/services/volume/v3/snapshot_manage_client.py
new file mode 100644
index 0000000..43fd328
--- /dev/null
+++ b/tempest/lib/services/volume/v3/snapshot_manage_client.py
@@ -0,0 +1,31 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class SnapshotManageClient(rest_client.RestClient):
+    """Snapshot manage client."""
+
+    def manage_snapshot(self, **kwargs):
+        """Manage a snapshot."""
+        post_body = json.dumps({'snapshot': kwargs})
+        url = 'os-snapshot-manage'
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
index 88c094f..cae65b2 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -13,9 +13,198 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import snapshots_client
+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 import exceptions as lib_exc
 
 
-class SnapshotsClient(snapshots_client.SnapshotsClient):
+class SnapshotsClient(rest_client.RestClient):
     """Client class to send CRUD Volume Snapshot V3 API requests."""
-    api_version = "v3"
+
+    def list_snapshots(self, detail=False, **params):
+        """List all the snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-snapshots
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-snapshots-and-details
+        """
+        url = 'snapshots'
+        if detail:
+            url += '/detail'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_snapshot(self, snapshot_id):
+        """Returns the details of a single snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#show-a-snapshot-s-details
+        """
+        url = "snapshots/%s" % snapshot_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_snapshot(self, **kwargs):
+        """Creates a new snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-snapshot
+        """
+        post_body = json.dumps({'snapshot': kwargs})
+        resp, body = self.post('snapshots', post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_snapshot(self, snapshot_id, **kwargs):
+        """Updates a snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-snapshot
+        """
+        put_body = json.dumps({'snapshot': kwargs})
+        resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_snapshot(self, snapshot_id):
+        """Delete Snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#delete-a-snapshot
+        """
+        resp, body = self.delete("snapshots/%s" % snapshot_id)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.show_snapshot(id)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'volume-snapshot'
+
+    def reset_snapshot_status(self, snapshot_id, status):
+        """Reset the specified snapshot's status."""
+        post_body = json.dumps({'os-reset_status': {"status": status}})
+        resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_snapshot_status(self, snapshot_id, **kwargs):
+        """Update status of a snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#update-status-of-a-snapshot
+        """
+        post_body = json.dumps({'os-update_snapshot_status': kwargs})
+        url = 'snapshots/%s/action' % snapshot_id
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_snapshot_metadata(self, snapshot_id, metadata):
+        """Create metadata for the snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-snapshot-s-metadata
+        """
+        put_body = json.dumps({'metadata': metadata})
+        url = "snapshots/%s/metadata" % snapshot_id
+        resp, body = self.post(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_snapshot_metadata(self, snapshot_id):
+        """Get metadata of the snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#show-a-snapshot-s-metadata
+        """
+        url = "snapshots/%s/metadata" % snapshot_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_snapshot_metadata(self, snapshot_id, **kwargs):
+        """Update metadata for the snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-snapshot-s-metadata
+        """
+        put_body = json.dumps(kwargs)
+        url = "snapshots/%s/metadata" % snapshot_id
+        resp, body = self.put(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_snapshot_metadata_item(self, snapshot_id, id):
+        """Show metadata item for the snapshot."""
+        url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
+        """Update metadata for the snapshot for a specific key.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#update-a-snapshot-s-metadata-for-a-specific-key
+        """
+        put_body = json.dumps(kwargs)
+        url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
+        resp, body = self.put(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_snapshot_metadata_item(self, snapshot_id, id):
+        """Delete metadata item for the snapshot."""
+        url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
+        resp, body = self.delete(url)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def force_delete_snapshot(self, snapshot_id):
+        """Force Delete Snapshot."""
+        post_body = json.dumps({'os-force_delete': {}})
+        resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def unmanage_snapshot(self, snapshot_id):
+        """Unmanage a snapshot."""
+        post_body = json.dumps({'os-unmanage': {}})
+        url = 'snapshots/%s/action' % (snapshot_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/transfers_client.py b/tempest/lib/services/volume/v3/transfers_client.py
new file mode 100644
index 0000000..97c5597
--- /dev/null
+++ b/tempest/lib/services/volume/v3/transfers_client.py
@@ -0,0 +1,82 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class TransfersClient(rest_client.RestClient):
+    """Client class to send CRUD Volume Transfer API requests"""
+
+    def create_volume_transfer(self, **kwargs):
+        """Create a volume transfer.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume-transfer
+        """
+        post_body = json.dumps({'transfer': kwargs})
+        resp, body = self.post('os-volume-transfer', post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_volume_transfer(self, transfer_id):
+        """Returns the details of a volume transfer."""
+        url = "os-volume-transfer/%s" % transfer_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_volume_transfers(self, detail=False, **params):
+        """List all the volume transfers created.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-for-a-project
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-and-details
+        """
+        url = 'os-volume-transfer'
+        if detail:
+            url += '/detail'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_volume_transfer(self, transfer_id):
+        """Delete a volume transfer."""
+        resp, body = self.delete("os-volume-transfer/%s" % transfer_id)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def accept_volume_transfer(self, transfer_id, **kwargs):
+        """Accept a volume transfer.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#accept-a-volume-transfer
+        """
+        url = 'os-volume-transfer/%s/accept' % transfer_id
+        post_body = json.dumps({'accept': kwargs})
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/types_client.py b/tempest/lib/services/volume/v3/types_client.py
new file mode 100644
index 0000000..1405785
--- /dev/null
+++ b/tempest/lib/services/volume/v3/types_client.py
@@ -0,0 +1,205 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class TypesClient(rest_client.RestClient):
+    """Client class to send CRUD Volume Types API requests"""
+
+    def is_resource_deleted(self, id):
+        try:
+            self.show_volume_type(id)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'volume-type'
+
+    def list_volume_types(self, **params):
+        """List all the volume types created.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-all-volume-types
+        """
+        url = 'types'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_volume_type(self, volume_type_id):
+        """Returns the details of a single volume type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#show-volume-type-detail
+        """
+        url = "types/%s" % volume_type_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_volume_type(self, **kwargs):
+        """Create volume type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume-type
+        """
+        post_body = json.dumps({'volume_type': kwargs})
+        resp, body = self.post('types', post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_volume_type(self, volume_type_id):
+        """Deletes the specified volume type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#delete-a-volume-type
+        """
+        resp, body = self.delete("types/%s" % volume_type_id)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_volume_types_extra_specs(self, volume_type_id, **params):
+        """List all the volume type extra specs created.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#show-all-extra-specifications-for-volume-type
+        """
+        url = 'types/%s/extra_specs' % volume_type_id
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name):
+        """Returns the details of a single volume type extra spec."""
+        url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
+        """Creates new volume type extra specs.
+
+        :param volume_type_id: Id of volume type.
+        :param extra_specs: A dictionary of values to be used as extra_specs.
+        """
+        url = "types/%s/extra_specs" % volume_type_id
+        post_body = json.dumps({'extra_specs': extra_specs})
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name):
+        """Deletes the specified volume type extra spec."""
+        resp, body = self.delete("types/%s/extra_specs/%s" % (
+            volume_type_id, extra_spec_name))
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_volume_type(self, volume_type_id, **kwargs):
+        """Updates volume type name, description, and/or is_public.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume-type
+        """
+        put_body = json.dumps({'volume_type': kwargs})
+        resp, body = self.put('types/%s' % volume_type_id, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name,
+                                       extra_specs):
+        """Update a volume_type extra spec.
+
+        :param volume_type_id: Id of volume type.
+        :param extra_spec_name: Name of the extra spec to be updated.
+        :param extra_specs: A dictionary of with key as extra_spec_name and the
+                            updated value.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-extra-specification-for-volume-type
+        """
+        url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
+        put_body = json.dumps(extra_specs)
+        resp, body = self.put(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def add_type_access(self, volume_type_id, **kwargs):
+        """Adds volume type access for the given project.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#add-private-volume-type-access-to-project
+        """
+        post_body = json.dumps({'addProjectAccess': kwargs})
+        url = 'types/%s/action' % volume_type_id
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def remove_type_access(self, volume_type_id, **kwargs):
+        """Removes volume type access for the given project.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#remove-private-volume-type-access-from-project
+        """
+        post_body = json.dumps({'removeProjectAccess': kwargs})
+        url = 'types/%s/action' % volume_type_id
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_type_access(self, volume_type_id):
+        """Print access information about the given volume type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-private-volume-type-access-detail
+        """
+        url = 'types/%s/os-volume-type-access' % volume_type_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index 5702f95..57629bd 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -22,7 +22,6 @@
 
 
 class VersionsClient(base_client.BaseClient):
-    api_version = 'v3'
 
     def list_versions(self):
         """List API versions
diff --git a/tempest/lib/services/volume/v3/volume_manage_client.py b/tempest/lib/services/volume/v3/volume_manage_client.py
new file mode 100644
index 0000000..349e11d
--- /dev/null
+++ b/tempest/lib/services/volume/v3/volume_manage_client.py
@@ -0,0 +1,35 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class VolumeManageClient(rest_client.RestClient):
+    """Volume manage client."""
+
+    def manage_volume(self, **kwargs):
+        """Manage existing volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#manage-an-existing-volume
+        """
+        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)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 5f4b278..2dbdd11 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -14,15 +14,117 @@
 #    under the License.
 
 from oslo_serialization import jsonutils as json
+import six
 from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
-from tempest.lib.services.volume.v2 import volumes_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
 
 
-class VolumesClient(volumes_client.VolumesClient):
+class VolumesClient(base_client.BaseClient):
     """Client class to send CRUD Volume V3 API requests"""
-    api_version = "v3"
+
+    def _prepare_params(self, params):
+        """Prepares params for use in get or _ext_get methods.
+
+        If params is a string it will be left as it is, but if it's not it will
+        be urlencoded.
+        """
+        if isinstance(params, six.string_types):
+            return params
+        return urllib.urlencode(params)
+
+    def list_hosts(self):
+        """Lists all hosts summary info that is not disabled.
+
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-all-hosts-for-a-project
+        """
+        resp, body = self.get('os-hosts')
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_volumes(self, detail=False, params=None):
+        """List all the volumes created.
+
+        Params can be a string (must be urlencoded) or a dictionary.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-volumes-with-details
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-volumes
+        """
+        url = 'volumes'
+        if detail:
+            url += '/detail'
+        if params:
+            url += '?%s' % self._prepare_params(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def migrate_volume(self, volume_id, **kwargs):
+        """Migrate a volume to a new backend
+
+        For a full list of available parameters please refer to the offical
+        API reference:
+
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#migrate-a-volume
+        """
+        post_body = json.dumps({'os-migrate_volume': kwargs})
+        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_volume(self, volume_id):
+        """Returns the details of a single volume."""
+        url = "volumes/%s" % volume_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_volume(self, **kwargs):
+        """Creates a new Volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume
+        """
+        post_body = json.dumps({'volume': kwargs})
+        resp, body = self.post('volumes', post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_volume(self, volume_id, **kwargs):
+        """Updates the Specified Volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume
+        """
+        put_body = json.dumps({'volume': kwargs})
+        resp, body = self.put('volumes/%s' % volume_id, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_volume(self, volume_id, **params):
+        """Deletes the Specified Volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#delete-a-volume
+        """
+        url = 'volumes/%s' % volume_id
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.delete(url)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
 
     def show_volume_summary(self, **params):
         """Get volumes summary.
@@ -38,3 +140,250 @@
         body = json.loads(body)
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def upload_volume(self, volume_id, **kwargs):
+        """Uploads a volume in Glance."""
+        post_body = json.dumps({'os-volume_upload_image': kwargs})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def attach_volume(self, volume_id, **kwargs):
+        """Attaches a volume to a given instance on a given mountpoint.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#attach-volume-to-a-server
+        """
+        post_body = json.dumps({'os-attach': kwargs})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def set_bootable_volume(self, volume_id, **kwargs):
+        """Set a bootable flag for a volume - true or false.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume-s-bootable-status
+        """
+        post_body = json.dumps({'os-set_bootable': kwargs})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def detach_volume(self, volume_id):
+        """Detaches a volume from an instance."""
+        post_body = json.dumps({'os-detach': {}})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def reserve_volume(self, volume_id):
+        """Reserves a volume."""
+        post_body = json.dumps({'os-reserve': {}})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def unreserve_volume(self, volume_id):
+        """Restore a reserved volume ."""
+        post_body = json.dumps({'os-unreserve': {}})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def is_resource_deleted(self, id):
+        """Check the specified resource is deleted or not.
+
+        :param id: A checked resource id
+        :raises lib_exc.DeleteErrorException: If the specified resource is on
+            the status the delete was failed.
+        """
+        try:
+            volume = self.show_volume(id)
+        except lib_exc.NotFound:
+            return True
+        if volume["volume"]["status"] == "error_deleting":
+            raise lib_exc.DeleteErrorException(resource_id=id)
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'volume'
+
+    def extend_volume(self, volume_id, **kwargs):
+        """Extend a volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#extend-a-volume-size
+        """
+        post_body = json.dumps({'os-extend': kwargs})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def reset_volume_status(self, volume_id, **kwargs):
+        """Reset the Specified Volume's Status.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#reset-a-volume-s-statuses
+        """
+        post_body = json.dumps({'os-reset_status': kwargs})
+        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_volume_readonly(self, volume_id, **kwargs):
+        """Update the Specified Volume readonly."""
+        post_body = json.dumps({'os-update_readonly_flag': kwargs})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def force_delete_volume(self, volume_id):
+        """Force Delete Volume."""
+        post_body = json.dumps({'os-force_delete': {}})
+        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_volume_metadata(self, volume_id, metadata):
+        """Create metadata for the volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-metadata-for-volume
+        """
+        put_body = json.dumps({'metadata': metadata})
+        url = "volumes/%s/metadata" % volume_id
+        resp, body = self.post(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_volume_metadata(self, volume_id):
+        """Get metadata of the volume."""
+        url = "volumes/%s/metadata" % volume_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_volume_metadata(self, volume_id, metadata):
+        """Update metadata for the volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume-s-metadata
+        """
+        put_body = json.dumps({'metadata': metadata})
+        url = "volumes/%s/metadata" % volume_id
+        resp, body = self.put(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_volume_metadata_item(self, volume_id, id):
+        """Show metadata item for the volume."""
+        url = "volumes/%s/metadata/%s" % (volume_id, id)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_volume_metadata_item(self, volume_id, id, meta_item):
+        """Update metadata item for the volume."""
+        put_body = json.dumps({'meta': meta_item})
+        url = "volumes/%s/metadata/%s" % (volume_id, id)
+        resp, body = self.put(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_volume_metadata_item(self, volume_id, id):
+        """Delete metadata item for the volume."""
+        url = "volumes/%s/metadata/%s" % (volume_id, id)
+        resp, body = self.delete(url)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def retype_volume(self, volume_id, **kwargs):
+        """Updates volume with new volume type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#retype-a-volume
+        """
+        post_body = json.dumps({'os-retype': kwargs})
+        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def force_detach_volume(self, volume_id, **kwargs):
+        """Force detach a volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#force-delete-a-volume
+        """
+        post_body = json.dumps({'os-force_detach': kwargs})
+        url = 'volumes/%s/action' % volume_id
+        resp, body = self.post(url, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_volume_image_metadata(self, volume_id, **kwargs):
+        """Update image metadata for the volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#set-image-metadata-for-a-volume
+        """
+        post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
+        url = "volumes/%s/action" % (volume_id)
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_volume_image_metadata(self, volume_id, key_name):
+        """Delete image metadata item for the volume."""
+        post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
+        url = "volumes/%s/action" % (volume_id)
+        resp, body = self.post(url, post_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_volume_image_metadata(self, volume_id):
+        """Show image metadata for the volume."""
+        post_body = json.dumps({'os-show_image_metadata': {}})
+        url = "volumes/%s/action" % volume_id
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def unmanage_volume(self, volume_id):
+        """Unmanage volume.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#unmanage-a-volume
+        """
+        post_body = json.dumps({'os-unmanage': {}})
+        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index c1dcccc..efcd139 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -14,11 +14,12 @@
 
 Any scenario test should have a real-life use case. An example would be:
 
- - "As operator I want to start with a blank environment":
-    1. upload a glance image
-    2. deploy a vm from it
-    3. ssh to the guest
-    4. create a snapshot of the vm
+- "As operator I want to start with a blank environment":
+
+  1. upload a glance image
+  2. deploy a vm from it
+  3. ssh to the guest
+  4. create a snapshot of the vm
 
 
 Why are these tests in Tempest?
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a47b6f2..aa06fd0 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -83,6 +83,7 @@
         if CONF.service_available.cinder:
             cls.volumes_client = cls.os_primary.volumes_client_latest
             cls.snapshots_client = cls.os_primary.snapshots_client_latest
+            cls.backups_client = cls.os_primary.backups_client_latest
 
     # ## Test functions library
     #
@@ -93,6 +94,10 @@
         if not client:
             client = self.ports_client
         name = data_utils.rand_name(self.__class__.__name__)
+        if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
+            kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
+        if CONF.network.port_profile and 'binding:profile' not in kwargs:
+            kwargs['binding:profile'] = CONF.network.port_profile
         result = client.create_port(
             name=name,
             network_id=network_id,
@@ -120,6 +125,27 @@
         returns a test server. The purpose of this wrapper is to minimize
         the impact on the code of the tests already using this
         function.
+
+        :param **kwargs:
+            See extra parameters below
+
+        :Keyword Arguments:
+            * *vnic_type* (``string``) --
+              used when launching instances with pre-configured ports.
+              Examples:
+                normal: a traditional virtual port that is either attached
+                        to a linux bridge or an openvswitch bridge on a
+                        compute node.
+                direct: an SR-IOV port that is directly attached to a VM
+                macvtap: an SR-IOV port that is attached to a VM via a macvtap
+                         device.
+              Defaults to ``CONF.network.port_vnic_type``.
+            * *port_profile* (``dict``) --
+              This attribute is a dictionary that can be used (with admin
+              credentials) to supply information influencing the binding of
+              the port.
+              example: port_profile = "capabilities:[switchdev]"
+              Defaults to ``CONF.network.port_profile``.
         """
 
         # NOTE(jlanoux): As a first step, ssh checks in the scenario
@@ -138,14 +164,21 @@
         if name is None:
             name = data_utils.rand_name(self.__class__.__name__ + "-server")
 
-        vnic_type = CONF.network.port_vnic_type
+        vnic_type = kwargs.pop('vnic_type', CONF.network.port_vnic_type)
+        profile = kwargs.pop('port_profile', CONF.network.port_profile)
 
-        # If vnic_type is configured create port for
+        # If vnic_type or profile are configured create port for
         # every network
-        if vnic_type:
+        if vnic_type or profile:
             ports = []
+            create_port_body = {}
 
-            create_port_body = {'binding:vnic_type': vnic_type}
+            if vnic_type:
+                create_port_body['binding:vnic_type'] = vnic_type
+
+            if profile:
+                create_port_body['binding:profile'] = profile
+
             if kwargs:
                 # Convert security group names to security group ids
                 # to pass to create_port
@@ -154,7 +187,7 @@
                         clients.security_groups_client.list_security_groups(
                         ).get('security_groups')
                     sec_dict = dict([(s['name'], s['id'])
-                                    for s in security_groups])
+                                     for s in security_groups])
 
                     sec_groups_names = [s['name'] for s in kwargs.pop(
                         'security_groups')]
@@ -213,8 +246,12 @@
         if size is None:
             size = CONF.volume.volume_size
         if imageRef:
-            image = self.compute_images_client.show_image(imageRef)['image']
-            min_disk = image.get('minDisk')
+            if CONF.image_feature_enabled.api_v1:
+                resp = self.image_client.check_image(imageRef)
+                image = common_image.get_image_meta_from_headers(resp)
+            else:
+                image = self.image_client.show_image(imageRef)
+            min_disk = image.get('min_disk')
             size = max(size, min_disk)
         if name is None:
             name = data_utils.rand_name(self.__class__.__name__ + "-volume")
@@ -237,6 +274,37 @@
         volume = self.volumes_client.show_volume(volume['id'])['volume']
         return volume
 
+    def create_backup(self, volume_id, name=None, description=None,
+                      force=False, snapshot_id=None, incremental=False,
+                      container=None):
+
+        name = name or data_utils.rand_name(
+            self.__class__.__name__ + "-backup")
+        kwargs = {'name': name,
+                  'description': description,
+                  'force': force,
+                  'snapshot_id': snapshot_id,
+                  'incremental': incremental,
+                  'container': container}
+        backup = self.backups_client.create_backup(volume_id=volume_id,
+                                                   **kwargs)['backup']
+        self.addCleanup(self.backups_client.delete_backup, backup['id'])
+        waiters.wait_for_volume_resource_status(self.backups_client,
+                                                backup['id'], 'available')
+        return backup
+
+    def restore_backup(self, backup_id):
+        restore = self.backups_client.restore_backup(backup_id)['restore']
+        self.addCleanup(self.volumes_client.delete_volume,
+                        restore['volume_id'])
+        waiters.wait_for_volume_resource_status(self.backups_client,
+                                                backup_id, 'available')
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                restore['volume_id'],
+                                                'available')
+        self.assertEqual(backup_id, restore['backup_id'])
+        return restore
+
     def create_volume_snapshot(self, volume_id, name=None, description=None,
                                metadata=None, force=False):
         name = name or data_utils.rand_name(
@@ -252,11 +320,32 @@
         self.addCleanup(self.snapshots_client.delete_snapshot, snapshot['id'])
         waiters.wait_for_volume_resource_status(self.snapshots_client,
                                                 snapshot['id'], 'available')
+        snapshot = self.snapshots_client.show_snapshot(
+            snapshot['id'])['snapshot']
         return snapshot
 
+    def _cleanup_volume_type(self, volume_type):
+        """Clean up a given volume type.
+
+        Ensuring all volumes associated to a type are first removed before
+        attempting to remove the type itself. This includes any image volume
+        cache volumes stored in a separate tenant to the original volumes
+        created from the type.
+        """
+        admin_volume_type_client = self.os_admin.volume_types_client_latest
+        admin_volumes_client = self.os_admin.volumes_client_latest
+        volumes = admin_volumes_client.list_volumes(
+            detail=True, params={'all_tenants': 1})['volumes']
+        type_name = volume_type['name']
+        for volume in [v for v in volumes if v['volume_type'] == type_name]:
+            test_utils.call_and_ignore_notfound_exc(
+                admin_volumes_client.delete_volume, volume['id'])
+            admin_volumes_client.wait_for_resource_deletion(volume['id'])
+        admin_volume_type_client.delete_volume_type(volume_type['id'])
+
     def create_volume_type(self, client=None, name=None, backend_name=None):
         if not client:
-            client = self.os_admin.volume_types_v2_client
+            client = self.os_admin.volume_types_client_latest
         if not name:
             class_name = self.__class__.__name__
             name = data_utils.rand_name(class_name + '-volume-type')
@@ -270,7 +359,7 @@
 
         volume_type = client.create_volume_type(
             name=randomized_name, extra_specs=extra_specs)['volume_type']
-        self.addCleanup(client.delete_volume_type, volume_type['id'])
+        self.addCleanup(self._cleanup_volume_type, volume_type)
         return volume_type
 
     def _create_loginable_secgroup_rule(self, secgroup_id=None):
@@ -331,12 +420,12 @@
                           server=None):
         """Get a SSH client to a remote server
 
-        @param ip_address the server floating or fixed IP address to use
-                          for ssh validation
-        @param username name of the Linux account on the remote server
-        @param private_key the SSH private key to use
-        @param server: server dict, used for debugging purposes
-        @return a RemoteClient object
+        :param ip_address: the server floating or fixed IP address to use
+                           for ssh validation
+        :param username: name of the Linux account on the remote server
+        :param private_key: the SSH private key to use
+        :param server: server dict, used for debugging purposes
+        :return: a RemoteClient object
         """
 
         if username is None:
@@ -404,7 +493,9 @@
                                        disk_format=img_disk_format,
                                        properties=img_properties)
         except IOError:
-            LOG.debug("A qcow2 image was not found. Try to get a uec image.")
+            LOG.warning(
+                "A(n) %s image was not found. Retrying with uec image.",
+                img_disk_format)
             kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
             ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
             properties = {'kernel_id': kernel, 'ramdisk_id': ramdisk}
@@ -502,7 +593,7 @@
                                                 volume['id'], 'available')
 
     def ping_ip_address(self, ip_address, should_succeed=True,
-                        ping_timeout=None, mtu=None):
+                        ping_timeout=None, mtu=None, server=None):
         timeout = ping_timeout or CONF.validation.ping_timeout
         cmd = ['ping', '-c1', '-w1']
 
@@ -536,12 +627,16 @@
                       'caller': caller, 'ip': ip_address, 'timeout': timeout,
                       'result': 'expected' if result else 'unexpected'
                   })
+        if server:
+            self._log_console_output([server])
         return result
 
     def check_vm_connectivity(self, ip_address,
                               username=None,
                               private_key=None,
                               should_connect=True,
+                              extra_msg="",
+                              server=None,
                               mtu=None):
         """Check server connectivity
 
@@ -551,43 +646,36 @@
         :param should_connect: True/False indicates positive/negative test
             positive - attempt ping and ssh
             negative - attempt ping and fail if succeed
+        :param extra_msg: Message to help with debugging if ``ping_ip_address``
+            fails
+        :param server: The server whose console to log for debugging
         :param mtu: network MTU to use for connectivity validation
 
         :raises: AssertError if the result of the connectivity check does
             not match the value of the should_connect param
         """
+        LOG.debug('checking network connections to IP %s with user: %s',
+                  ip_address, username)
         if should_connect:
             msg = "Timed out waiting for %s to become reachable" % ip_address
         else:
             msg = "ip address %s is reachable" % ip_address
+        if extra_msg:
+            msg = "%s\n%s" % (extra_msg, msg)
         self.assertTrue(self.ping_ip_address(ip_address,
                                              should_succeed=should_connect,
-                                             mtu=mtu),
+                                             mtu=mtu, server=server),
                         msg=msg)
         if should_connect:
             # no need to check ssh for negative connectivity
-            self.get_remote_client(ip_address, username, private_key)
-
-    def check_public_network_connectivity(self, ip_address, username,
-                                          private_key, should_connect=True,
-                                          msg=None, servers=None, mtu=None):
-        # The target login is assumed to have been configured for
-        # key-based authentication by cloud-init.
-        LOG.debug('checking network connections to IP %s with user: %s',
-                  ip_address, username)
-        try:
-            self.check_vm_connectivity(ip_address,
-                                       username,
-                                       private_key,
-                                       should_connect=should_connect,
-                                       mtu=mtu)
-        except Exception:
-            ex_msg = 'Public network connectivity check failed'
-            if msg:
-                ex_msg += ": " + msg
-            LOG.exception(ex_msg)
-            self._log_console_output(servers)
-            raise
+            try:
+                self.get_remote_client(ip_address, username, private_key,
+                                       server=server)
+            except Exception:
+                if not extra_msg:
+                    extra_msg = 'Failed to ssh to %s' % ip_address
+                LOG.exception(extra_msg)
+                raise
 
     def create_floating_ip(self, thing, pool_name=None):
         """Create a floating IP and associates to a server on Nova"""
@@ -604,9 +692,10 @@
         return floating_ip
 
     def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
-                         private_key=None):
+                         private_key=None, server=None):
         ssh_client = self.get_remote_client(ip_address,
-                                            private_key=private_key)
+                                            private_key=private_key,
+                                            server=server)
         if dev_name is not None:
             ssh_client.make_fs(dev_name)
             ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
@@ -620,9 +709,10 @@
         return timestamp
 
     def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
-                      private_key=None):
+                      private_key=None, server=None):
         ssh_client = self.get_remote_client(ip_address,
-                                            private_key=private_key)
+                                            private_key=private_key,
+                                            server=server)
         if dev_name is not None:
             ssh_client.mount(dev_name, mount_path)
         timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
@@ -649,19 +739,22 @@
                 addresses = server['addresses'][
                     CONF.validation.network_for_ssh]
             else:
-                creds_provider = self._get_credentials_provider()
-                net_creds = creds_provider.get_primary_creds()
-                network = getattr(net_creds, 'network', None)
+                network = self.get_tenant_network()
                 addresses = (server['addresses'][network['name']]
                              if network else [])
             for address in addresses:
-                if (address['version'] == CONF.validation.ip_version_for_ssh
-                        and address['OS-EXT-IPS:type'] == 'fixed'):
+                if (address['version'] == CONF.validation.ip_version_for_ssh and  # noqa
+                        address['OS-EXT-IPS:type'] == 'fixed'):
                     return address['addr']
             raise exceptions.ServerUnreachable(server_id=server['id'])
         else:
             raise lib_exc.InvalidConfiguration()
 
+    @classmethod
+    def get_host_for_server(cls, server_id):
+        server_details = cls.os_admin.servers_client.show_server(server_id)
+        return server_details['server']['OS-EXT-SRV-ATTR:host']
+
 
 class NetworkScenarioTest(ScenarioTest):
     """Base class for network scenario tests.
@@ -770,8 +863,13 @@
         return subnet
 
     def _get_server_port_id_and_ip4(self, server, ip_addr=None):
-        ports = self.os_admin.ports_client.list_ports(
-            device_id=server['id'], fixed_ip=ip_addr)['ports']
+        if ip_addr:
+            ports = self.os_admin.ports_client.list_ports(
+                device_id=server['id'],
+                fixed_ips='ip_address=%s' % ip_addr)['ports']
+        else:
+            ports = self.os_admin.ports_client.list_ports(
+                device_id=server['id'])['ports']
         # A port can have more than one IP address in some cases.
         # If the network is dual-stack (IPv4 + IPv6), this port is associated
         # with 2 subnets
@@ -785,8 +883,8 @@
         port_map = [(p["id"], fxip["ip_address"])
                     for p in ports
                     for fxip in p["fixed_ips"]
-                    if netutils.is_valid_ipv4(fxip["ip_address"])
-                    and p['status'] in p_status]
+                    if (netutils.is_valid_ipv4(fxip["ip_address"]) and
+                        p['status'] in p_status)]
         inactive = [p for p in ports if p['status'] != 'ACTIVE']
         if inactive:
             LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
@@ -909,13 +1007,17 @@
 
         result = test_utils.call_until_true(connect_remote,
                                             CONF.validation.ping_timeout, 1)
+        if result:
+            return
+
         source_host = source.ssh_client.host
         if should_succeed:
             msg = "Timed out waiting for %s to become reachable from %s" \
                 % (dest, source_host)
         else:
             msg = "%s is reachable from %s" % (dest, source_host)
-        self.assertTrue(result, msg)
+        self._log_console_output()
+        self.fail(msg)
 
     def _create_security_group(self, security_group_rules_client=None,
                                tenant_id=None,
@@ -1166,9 +1268,9 @@
     @classmethod
     def setup_clients(cls):
         super(EncryptionScenarioTest, cls).setup_clients()
-        cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client
+        cls.admin_volume_types_client = cls.os_admin.volume_types_client_latest
         cls.admin_encryption_types_client =\
-            cls.os_admin.encryption_types_v2_client
+            cls.os_admin.encryption_types_client_latest
 
     def create_encryption_type(self, client=None, type_id=None, provider=None,
                                key_size=None, cipher=None,
@@ -1181,7 +1283,7 @@
         LOG.debug("Creating an encryption type for volume type: %s", type_id)
         client.create_encryption_type(
             type_id, provider=provider, key_size=key_size, cipher=cipher,
-            control_location=control_location)['encryption']
+            control_location=control_location)
 
     def create_encrypted_volume(self, encryption_provider, volume_type,
                                 key_size=256, cipher='aes-xts-plain64',
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 9ff6227..b515639 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -37,7 +37,7 @@
         super(TestAggregatesBasicOps, cls).setup_clients()
         # Use admin client by default
         cls.aggregates_client = cls.os_admin.aggregates_client
-        cls.hosts_client = cls.os_admin.hosts_client
+        cls.services_client = cls.os_admin.services_client
 
     def _create_aggregate(self, **kwargs):
         aggregate = (self.aggregates_client.create_aggregate(**kwargs)
@@ -51,10 +51,10 @@
         return aggregate
 
     def _get_host_name(self):
-        hosts = self.hosts_client.list_hosts()['hosts']
-        self.assertNotEmpty(hosts)
-        computes = [x for x in hosts if x['service'] == 'compute']
-        return computes[0]['host_name']
+        svc_list = self.services_client.list_services(
+            binary='nova-compute')['services']
+        self.assertNotEmpty(svc_list)
+        return svc_list[0]['host']
 
     def _add_host(self, aggregate_id, host):
         aggregate = (self.aggregates_client.add_host(aggregate_id, host=host)
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index b5220e9..008d1ae 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -29,11 +29,12 @@
 
     For both LUKS and cryptsetup encryption types, this test performs
     the following:
-        * Creates an image in Glance
-        * Boots an instance from the image
-        * Creates an encryption type (as admin)
-        * Creates a volume of that encryption type (as a regular user)
-        * Attaches and detaches the encrypted volume to the instance
+
+    * Creates an image in Glance
+    * Boots an instance from the image
+    * Creates an encryption type (as admin)
+    * Creates a volume of that encryption type (as a regular user)
+    * Attaches and detaches the encrypted volume to the instance
     """
 
     @classmethod
@@ -57,8 +58,7 @@
     @utils.services('compute', 'volume', 'image')
     def test_encrypted_cinder_volumes_luks(self):
         server = self.launch_instance()
-        volume = self.create_encrypted_volume('nova.volume.encryptors.'
-                                              'luks.LuksEncryptor',
+        volume = self.create_encrypted_volume('luks',
                                               volume_type='luks')
         self.attach_detach_volume(server, volume)
 
@@ -67,7 +67,6 @@
     @utils.services('compute', 'volume', 'image')
     def test_encrypted_cinder_volumes_cryptsetup(self):
         server = self.launch_instance()
-        volume = self.create_encrypted_volume('nova.volume.encryptors.'
-                                              'cryptsetup.CryptsetupEncryptor',
+        volume = self.create_encrypted_volume('plain',
                                               volume_type='cryptsetup')
         self.attach_detach_volume(server, volume)
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 29f1743..cee543b 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -13,8 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
 from tempest.common import custom_matchers
 from tempest.common import utils
 from tempest.common import waiters
@@ -50,6 +48,7 @@
     10. Check SSH connection to instance after reboot
 
     """
+
     def nova_show(self, server):
         got_server = (self.servers_client.show_server(server['id'])
                       ['server'])
@@ -101,10 +100,6 @@
                     return address
 
     @decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8')
-    @testtools.skipUnless(CONF.network.public_network_id,
-                          'The public_network_id option must be specified.')
-    @testtools.skipUnless(CONF.network_feature_enabled.floating_ips,
-                          'Floating ips are not available')
     @utils.services('compute', 'volume', 'image', 'network')
     def test_minimum_basic_scenario(self):
         image = self.glance_image_create()
@@ -126,22 +121,29 @@
         self.addCleanup(self.nova_volume_detach, server, volume)
         self.cinder_show(volume)
 
-        floating_ip = self.create_floating_ip(server)
-        # fetch the server again to make sure the addresses were refreshed
-        # after associating the floating IP
+        floating_ip = None
         server = self.servers_client.show_server(server['id'])['server']
-        address = self._get_floating_ip_in_server_addresses(
-            floating_ip, server)
-        self.assertIsNotNone(
-            address,
-            "Failed to find floating IP '%s' in server addresses: %s" %
-            (floating_ip['ip'], server['addresses']))
+        if (CONF.network_feature_enabled.floating_ips and
+            CONF.network.floating_network_name):
+            floating_ip = self.create_floating_ip(server)
+            # fetch the server again to make sure the addresses were refreshed
+            # after associating the floating IP
+            server = self.servers_client.show_server(server['id'])['server']
+            address = self._get_floating_ip_in_server_addresses(
+                floating_ip, server)
+            self.assertIsNotNone(
+                address,
+                "Failed to find floating IP '%s' in server addresses: %s" %
+                (floating_ip['ip'], server['addresses']))
+            ssh_ip = floating_ip['ip']
+        else:
+            ssh_ip = self.get_server_ip(server)
 
         self.create_and_add_security_group_to_server(server)
 
         # check that we can SSH to the server before reboot
         self.linux_client = self.get_remote_client(
-            floating_ip['ip'], private_key=keypair['private_key'],
+            ssh_ip, private_key=keypair['private_key'],
             server=server)
 
         self.nova_reboot(server)
@@ -149,25 +151,27 @@
         # check that we can SSH to the server after reboot
         # (both connections are part of the scenario)
         self.linux_client = self.get_remote_client(
-            floating_ip['ip'], private_key=keypair['private_key'],
+            ssh_ip, private_key=keypair['private_key'],
             server=server)
 
         self.check_disks()
 
-        # delete the floating IP, this should refresh the server addresses
-        self.compute_floating_ips_client.delete_floating_ip(floating_ip['id'])
+        if floating_ip:
+            # delete the floating IP, this should refresh the server addresses
+            self.compute_floating_ips_client.delete_floating_ip(
+                floating_ip['id'])
 
-        def is_floating_ip_detached_from_server():
-            server_info = self.servers_client.show_server(
-                server['id'])['server']
-            address = self._get_floating_ip_in_server_addresses(
-                floating_ip, server_info)
-            return (not address)
+            def is_floating_ip_detached_from_server():
+                server_info = self.servers_client.show_server(
+                    server['id'])['server']
+                address = self._get_floating_ip_in_server_addresses(
+                    floating_ip, server_info)
+                return (not address)
 
-        if not test_utils.call_until_true(
-            is_floating_ip_detached_from_server,
-            CONF.compute.build_timeout,
-            CONF.compute.build_interval):
-            msg = ("Floating IP '%s' should not be in server addresses: %s" %
-                   (floating_ip['ip'], server['addresses']))
-            raise exceptions.TimeoutException(msg)
+            if not test_utils.call_until_true(
+                is_floating_ip_detached_from_server,
+                CONF.compute.build_timeout,
+                CONF.compute.build_interval):
+                msg = ("Floating IP '%s' should not be in server addresses: %s"
+                       % (floating_ip['ip'], server['addresses']))
+                raise exceptions.TimeoutException(msg)
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 7c404ad..37bcd04 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -43,8 +43,8 @@
     @classmethod
     def skip_checks(cls):
         super(TestNetworkAdvancedServerOps, cls).skip_checks()
-        if not (CONF.network.project_networks_reachable
-                or CONF.network.public_network_id):
+        if not (CONF.network.project_networks_reachable or
+                CONF.network.public_network_id):
             msg = ('Either project_networks_reachable must be "true", or '
                    'public_network_id must be defined.')
             raise cls.skipException(msg)
@@ -90,9 +90,10 @@
         floating_ip_addr = floating_ip['floating_ip_address']
         # Check FloatingIP status before checking the connectivity
         self.check_floating_ip_status(floating_ip, 'ACTIVE')
-        self.check_public_network_connectivity(floating_ip_addr, username,
-                                               private_key, should_connect,
-                                               servers=[server])
+        self.check_vm_connectivity(floating_ip_addr, username,
+                                   private_key, should_connect,
+                                   'Public network connectivity check failed',
+                                   server)
 
     def _wait_server_status_and_check_network_connectivity(self, server,
                                                            keypair,
@@ -101,10 +102,6 @@
                                        'ACTIVE')
         self._check_network_connectivity(server, keypair, floating_ip)
 
-    def _get_host_for_server(self, server_id):
-        body = self.admin_servers_client.show_server(server_id)['server']
-        return body['OS-EXT-SRV-ATTR:host']
-
     @decorators.idempotent_id('61f1aa9a-1573-410e-9054-afa557cab021')
     @decorators.attr(type='slow')
     @utils.services('compute', 'network')
@@ -122,6 +119,7 @@
             server, keypair, floating_ip)
 
     @decorators.idempotent_id('7b6860c2-afa3-4846-9522-adeb38dfbe08')
+    @decorators.attr(type='slow')
     @utils.services('compute', 'network')
     def test_server_connectivity_reboot(self):
         keypair = self.create_keypair()
@@ -195,6 +193,15 @@
         waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'VERIFY_RESIZE')
         self.servers_client.confirm_resize_server(server['id'])
+        server = self.servers_client.show_server(server['id'])['server']
+        # Nova API > 2.46 no longer includes flavor.id, and schema check
+        # will cover whether 'id' should be in flavor
+        if server['flavor'].get('id'):
+            self.assertEqual(resize_flavor, server['flavor']['id'])
+        else:
+            flavor = self.flavors_client.show_flavor(resize_flavor)['flavor']
+            for key in ['original_name', 'ram', 'vcpus', 'disk']:
+                self.assertEqual(flavor[key], server['flavor'][key])
         self._wait_server_status_and_check_network_connectivity(
             server, keypair, floating_ip)
 
@@ -210,7 +217,7 @@
         keypair = self.create_keypair()
         server = self._setup_server(keypair)
         floating_ip = self._setup_network(server, keypair)
-        src_host = self._get_host_for_server(server['id'])
+        src_host = self.get_host_for_server(server['id'])
         self._wait_server_status_and_check_network_connectivity(
             server, keypair, floating_ip)
 
@@ -220,10 +227,37 @@
         self.servers_client.confirm_resize_server(server['id'])
         self._wait_server_status_and_check_network_connectivity(
             server, keypair, floating_ip)
-        dst_host = self._get_host_for_server(server['id'])
+        dst_host = self.get_host_for_server(server['id'])
 
         self.assertNotEqual(src_host, dst_host)
 
+    @decorators.idempotent_id('03fd1562-faad-11e7-9ea0-fa163e65f5ce')
+    @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
+                          'Live migration is not available.')
+    @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
+                          'Less than 2 compute nodes, skipping multinode '
+                          'tests.')
+    @decorators.attr(type='slow')
+    @utils.services('compute', 'network')
+    def test_server_connectivity_live_migration(self):
+        keypair = self.create_keypair()
+        server = self._setup_server(keypair)
+        floating_ip = self._setup_network(server, keypair)
+        self._wait_server_status_and_check_network_connectivity(
+            server, keypair, floating_ip)
+
+        block_migration = (CONF.compute_feature_enabled.
+                           block_migration_for_live_migration)
+        self.admin_servers_client.live_migrate_server(
+            server['id'], host=None, block_migration=block_migration,
+            disk_over_commit=False)
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+
+        self._wait_server_status_and_check_network_connectivity(
+            server, keypair, floating_ip)
+
+    @decorators.skip_because(bug='1788403')
     @decorators.idempotent_id('25b188d7-0183-4b1e-a11d-15840c8e2fd6')
     @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
                           'Cold migration is not available.')
@@ -236,7 +270,7 @@
         keypair = self.create_keypair()
         server = self._setup_server(keypair)
         floating_ip = self._setup_network(server, keypair)
-        src_host = self._get_host_for_server(server['id'])
+        src_host = self.get_host_for_server(server['id'])
         self._wait_server_status_and_check_network_connectivity(
             server, keypair, floating_ip)
 
@@ -246,6 +280,6 @@
         self.servers_client.revert_resize_server(server['id'])
         self._wait_server_status_and_check_network_connectivity(
             server, keypair, floating_ip)
-        dst_host = self._get_host_for_server(server['id'])
+        dst_host = self.get_host_for_server(server['id'])
 
         self.assertEqual(src_host, dst_host)
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 6332c6d..f46c7e8 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -81,8 +81,8 @@
     @classmethod
     def skip_checks(cls):
         super(TestNetworkBasicOps, cls).skip_checks()
-        if not (CONF.network.project_networks_reachable
-                or CONF.network.public_network_id):
+        if not (CONF.network.project_networks_reachable or
+                CONF.network.public_network_id):
             msg = ('Either project_networks_reachable must be "true", or '
                    'public_network_id must be defined.')
             raise cls.skipException(msg)
@@ -175,7 +175,7 @@
     def _get_server_key(self, server):
         return self.keypairs[server['key_name']]['private_key']
 
-    def check_public_network_connectivity(
+    def _check_public_network_connectivity(
             self, should_connect=True, msg=None,
             should_check_floating_ip_status=True, mtu=None):
         """Verifies connectivty to a VM via public network and floating IP
@@ -199,13 +199,18 @@
         if should_connect:
             private_key = self._get_server_key(server)
             floatingip_status = 'ACTIVE'
+
         # Check FloatingIP Status before initiating a connection
         if should_check_floating_ip_status:
             self.check_floating_ip_status(floating_ip, floatingip_status)
-        # call the common method in the parent class
-        super(TestNetworkBasicOps, self).check_public_network_connectivity(
-            ip_address, ssh_login, private_key, should_connect, msg,
-            self.servers, mtu=mtu)
+
+        message = 'Public network connectivity check failed'
+        if msg:
+            message += '. Reason: %s' % msg
+
+        self.check_vm_connectivity(
+            ip_address, ssh_login, private_key, should_connect,
+            message, server, mtu=mtu)
 
     def _disassociate_floating_ips(self):
         floating_ip, _ = self.floating_ip_tuple
@@ -287,11 +292,14 @@
                                               % CONF.network.build_timeout)
 
         _, new_nic = self.diff_list[0]
-        ssh_client.exec_command("sudo ip addr add %s/%s dev %s" % (
-                                new_port['fixed_ips'][0]['ip_address'],
-                                CONF.network.project_network_mask_bits,
-                                new_nic))
-        ssh_client.exec_command("sudo ip link set %s up" % new_nic)
+        ip_output = ssh_client.exec_command('ip a')
+        ip_address = new_port['fixed_ips'][0]['ip_address']
+        ip_mask = CONF.network.project_network_mask_bits
+        # check if the address is not already in use, if not, set it
+        if ' ' + ip_address + '/' + str(ip_mask) not in ip_output:
+            ssh_client.exec_command("sudo ip addr add %s/%s dev %s" % (
+                                    ip_address, ip_mask, new_nic))
+            ssh_client.exec_command("sudo ip link set %s up" % new_nic)
 
     def _get_server_nics(self, ssh_client):
         reg = re.compile(r'(?P<num>\d+): (?P<nic_name>\w+)[@]?.*:')
@@ -304,16 +312,19 @@
 
         - ping internal gateway and DHCP port, implying in-tenant connectivity
         pinging both, because L3 and DHCP agents might be on different nodes
+        - ping internal compute port, implying connectivity to other VMs on
+        this network
         """
         floating_ip, server = self.floating_ip_tuple
         # get internal ports' ips:
-        # get all network ports in the new network
+        # get all network and compute ports in the new network
         internal_ips = (
             p['fixed_ips'][0]['ip_address'] for p in
             self.os_admin.ports_client.list_ports(
                 tenant_id=server['tenant_id'],
                 network_id=network['id'])['ports']
-            if p['device_owner'].startswith('network')
+            if p['device_owner'].startswith('network') or
+            p['device_owner'].startswith('compute')
         )
 
         self._check_server_connectivity(floating_ip,
@@ -366,52 +377,50 @@
     def test_network_basic_ops(self):
         """Basic network operation test
 
-        For a freshly-booted VM with an IP address ("port") on a given
-            network:
+        For a freshly-booted VM with an IP address ("port") on a given network:
 
         - the Tempest host can ping the IP address.  This implies, but
-         does not guarantee (see the ssh check that follows), that the
-         VM has been assigned the correct IP address and has
-         connectivity to the Tempest host.
+            does not guarantee (see the ssh check that follows), that the
+            VM has been assigned the correct IP address and has
+            connectivity to the Tempest host.
 
         - the Tempest host can perform key-based authentication to an
-         ssh server hosted at the IP address.  This check guarantees
-         that the IP address is associated with the target VM.
+            ssh server hosted at the IP address.  This check guarantees
+            that the IP address is associated with the target VM.
 
         - the Tempest host can ssh into the VM via the IP address and
-         successfully execute the following:
+            successfully execute the following:
 
-         - ping an external IP address, implying external connectivity.
+            - ping an external IP address, implying external connectivity.
 
-         - ping an external hostname, implying that dns is correctly
-           configured.
+            - ping an external hostname, implying that dns is correctly
+                configured.
 
-         - ping an internal IP address, implying connectivity to another
-           VM on the same network.
+            - ping an internal IP address, implying connectivity to another
+               VM on the same network.
 
         - detach the floating-ip from the VM and verify that it becomes
-        unreachable
+            unreachable
 
         - associate detached floating ip to a new VM and verify connectivity.
-        VMs are created with unique keypair so connectivity also asserts that
-        floating IP is associated with the new VM instead of the old one
+            VMs are created with unique keypair so connectivity also asserts
+            that floating IP is associated with the new VM instead of the old
+            one
 
         Verifies that floating IP status is updated correctly after each change
-
-
         """
         self._setup_network_and_servers()
-        self.check_public_network_connectivity(should_connect=True)
+        self._check_public_network_connectivity(should_connect=True)
         self._check_network_internal_connectivity(network=self.network)
         self._check_network_external_connectivity()
         self._disassociate_floating_ips()
-        self.check_public_network_connectivity(should_connect=False,
-                                               msg="after disassociate "
-                                                   "floating ip")
+        self._check_public_network_connectivity(should_connect=False,
+                                                msg="after disassociate "
+                                                    "floating ip")
         self._reassociate_floating_ips()
-        self.check_public_network_connectivity(should_connect=True,
-                                               msg="after re-associate "
-                                                   "floating ip")
+        self._check_public_network_connectivity(should_connect=True,
+                                                msg="after re-associate "
+                                                    "floating ip")
 
     @decorators.idempotent_id('b158ea55-472e-4086-8fa9-c64ac0c6c1d0')
     @testtools.skipUnless(utils.is_extension_enabled('net-mtu', 'network'),
@@ -421,53 +430,56 @@
     def test_mtu_sized_frames(self):
         """Validate that network MTU sized frames fit through."""
         self._setup_network_and_servers()
-        self.check_public_network_connectivity(
+        # first check that connectivity works in general for the instance
+        self._check_public_network_connectivity(should_connect=True)
+        # now that we checked general connectivity, test that full size frames
+        # can also pass between nodes
+        self._check_public_network_connectivity(
             should_connect=True, mtu=self.network['mtu'])
 
     @decorators.idempotent_id('1546850e-fbaa-42f5-8b5f-03d8a6a95f15')
     @testtools.skipIf(CONF.network.shared_physical_network,
                       'Connectivity can only be tested when in a '
                       'multitenant network environment')
-    @decorators.skip_because(bug="1610994")
     @decorators.attr(type='slow')
     @utils.services('compute', 'network')
     def test_connectivity_between_vms_on_different_networks(self):
         """Test connectivity between VMs on different networks
 
-        For a freshly-booted VM with an IP address ("port") on a given
-            network:
+        For a freshly-booted VM with an IP address ("port") on a given network:
 
         - the Tempest host can ping the IP address.
-
         - the Tempest host can ssh into the VM via the IP address and
-         successfully execute the following:
+            successfully execute the following:
 
-         - ping an external IP address, implying external connectivity.
-
-         - ping an external hostname, implying that dns is correctly
-           configured.
-
-         - ping an internal IP address, implying connectivity to another
-           VM on the same network.
+            - ping an external IP address, implying external connectivity.
+            - ping an external hostname, implying that dns is correctly
+               configured.
+            - ping an internal IP address, implying connectivity to another
+               VM on the same network.
 
         - Create another network on the same tenant with subnet, create
-        an VM on the new network.
+            an VM on the new network.
 
-         - Ping the new VM from previous VM failed since the new network
-         was not attached to router yet.
-
-         - Attach the new network to the router, Ping the new VM from
-         previous VM succeed.
+            - Ping the new VM from previous VM failed since the new network
+                was not attached to router yet.
+            - Attach the new network to the router, Ping the new VM from
+                previous VM succeed.
 
         """
         self._setup_network_and_servers()
-        self.check_public_network_connectivity(should_connect=True)
+        self._check_public_network_connectivity(should_connect=True)
         self._check_network_internal_connectivity(network=self.network)
         self._check_network_external_connectivity()
         self._create_new_network(create_gateway=True)
-        self._create_server(self.new_net)
-        self._check_network_internal_connectivity(network=self.new_net,
-                                                  should_connect=False)
+        new_server = self._create_server(self.new_net)
+        new_server_ips = [addr['addr'] for addr in
+                          new_server['addresses'][self.new_net['name']]]
+
+        # Assert that pinging the new VM fails since the new network is not
+        # connected to a router
+        self._check_server_connectivity(self.floating_ip_tuple.floating_ip,
+                                        new_server_ips, should_connect=False)
         router_id = self.router['id']
         self.routers_client.add_router_interface(
             router_id, subnet_id=self.new_subnet['id'])
@@ -475,8 +487,9 @@
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.routers_client.remove_router_interface,
                         router_id, subnet_id=self.new_subnet['id'])
-        self._check_network_internal_connectivity(network=self.new_net,
-                                                  should_connect=True)
+
+        self._check_server_connectivity(self.floating_ip_tuple.floating_ip,
+                                        new_server_ips, should_connect=True)
 
     @decorators.idempotent_id('c5adff73-e961-41f1-b4a9-343614f18cfa')
     @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
@@ -496,7 +509,7 @@
 
         """
         self._setup_network_and_servers()
-        self.check_public_network_connectivity(should_connect=True)
+        self._check_public_network_connectivity(should_connect=True)
         self._create_new_network()
         self._hotplug_server()
         self._check_network_internal_connectivity(network=self.new_net)
@@ -518,19 +531,19 @@
                 admin_state_up attribute of router to True
         """
         self._setup_network_and_servers()
-        self.check_public_network_connectivity(
+        self._check_public_network_connectivity(
             should_connect=True, msg="before updating "
             "admin_state_up of router to False")
         self._update_router_admin_state(self.router, False)
         # TODO(alokmaurya): Remove should_check_floating_ip_status=False check
         # once bug 1396310 is fixed
 
-        self.check_public_network_connectivity(
+        self._check_public_network_connectivity(
             should_connect=False, msg="after updating "
             "admin_state_up of router to False",
             should_check_floating_ip_status=False)
         self._update_router_admin_state(self.router, True)
-        self.check_public_network_connectivity(
+        self._check_public_network_connectivity(
             should_connect=True, msg="after updating "
             "admin_state_up of router to True")
 
@@ -544,38 +557,46 @@
     def test_subnet_details(self):
         """Tests that subnet's extra configuration details are affecting VMs.
 
-         This test relies on non-shared, isolated tenant networks.
+        This test relies on non-shared, isolated tenant networks.
 
-         NOTE: Neutron subnets push data to servers via dhcp-agent, so any
-         update in subnet requires server to actively renew its DHCP lease.
+        NOTE: Neutron subnets push data to servers via dhcp-agent, so any
+        update in subnet requires server to actively renew its DHCP lease.
 
-         1. Configure subnet with dns nameserver
-         2. retrieve the VM's configured dns and verify it matches the one
-         configured for the subnet.
-         3. update subnet's dns
-         4. retrieve the VM's configured dns and verify it matches the new one
-         configured for the subnet.
+        1. Configure subnet with dns nameserver
+        2. retrieve the VM's configured dns and verify it matches the one
+           configured for the subnet.
+        3. update subnet's dns
+        4. retrieve the VM's configured dns and verify it matches the new one
+           configured for the subnet.
 
-         TODO(yfried): add host_routes
+        TODO(yfried): add host_routes
 
-         any resolution check would be testing either:
-            * l3 forwarding (tested in test_network_basic_ops)
-            * Name resolution of an external DNS nameserver - out of scope for
-            Tempest
+        any resolution check would be testing either:
+
+        * l3 forwarding (tested in test_network_basic_ops)
+        * Name resolution of an external DNS nameserver - out of scope for
+          Tempest
         """
         # this test check only updates (no actual resolution) so using
         # arbitrary ip addresses as nameservers, instead of parsing CONF
         initial_dns_server = '1.2.3.4'
         alt_dns_server = '9.8.7.6'
 
-        # renewal should be immediate.
-        # Timeouts are suggested by salvatore-orlando in
+        # Original timeouts are suggested by salvatore-orlando in
         # https://bugs.launchpad.net/neutron/+bug/1412325/comments/3
-        renew_delay = CONF.network.build_interval
+        #
+        # Compared to that renew_delay was increased, because
+        # busybox's udhcpc accepts SIGUSR1 as a renew request. Internally
+        # it goes into RENEW_REQUESTED state. If it receives a 2nd SIGUSR1
+        # signal while in that state then it calls the deconfig script
+        # ("/sbin/cirros-dhcpc deconfig" in sufficiently new cirros versions)
+        # which leads to the address being transiently deconfigured which
+        # for our case is unwanted.
+        renew_delay = 3 * CONF.network.build_interval
         renew_timeout = CONF.network.build_timeout
 
         self._setup_network_and_servers(dns_nameservers=[initial_dns_server])
-        self.check_public_network_connectivity(should_connect=True)
+        self._check_public_network_connectivity(should_connect=True)
 
         floating_ip, server = self.floating_ip_tuple
         ip_address = floating_ip['floating_ip_address']
@@ -650,20 +671,20 @@
                                             private_key=private_key,
                                             server=server2)
 
-        self.check_public_network_connectivity(
+        self._check_public_network_connectivity(
             should_connect=True, msg="before updating "
             "admin_state_up of instance port to False")
         self.check_remote_connectivity(ssh_client, dest=server_pip,
                                        should_succeed=True)
         self.ports_client.update_port(port_id, admin_state_up=False)
-        self.check_public_network_connectivity(
+        self._check_public_network_connectivity(
             should_connect=False, msg="after updating "
             "admin_state_up of instance port to False",
             should_check_floating_ip_status=False)
         self.check_remote_connectivity(ssh_client, dest=server_pip,
                                        should_succeed=False)
         self.ports_client.update_port(port_id, admin_state_up=True)
-        self.check_public_network_connectivity(
+        self._check_public_network_connectivity(
             should_connect=True, msg="after updating "
             "admin_state_up of instance port to True")
         self.check_remote_connectivity(ssh_client, dest=server_pip,
@@ -731,7 +752,7 @@
         2. Remove router from all l3-agents
         3. Verify connectivity is down
         4. Assign router to new l3-agent (or old one if no new agent is
-         available)
+           available)
         5. Verify connectivity
         """
 
@@ -760,7 +781,7 @@
             msg = "Rescheduling test does not apply to distributed routers."
             raise self.skipException(msg)
 
-        self.check_public_network_connectivity(should_connect=True)
+        self._check_public_network_connectivity(should_connect=True)
 
         # remove resource from agents
         hosting_agents = set(a["id"] for a in
@@ -777,7 +798,7 @@
                              'unscheduling router failed')
 
         # verify resource is un-functional
-        self.check_public_network_connectivity(
+        self._check_public_network_connectivity(
             should_connect=False,
             msg='after router unscheduling',
         )
@@ -794,7 +815,7 @@
             "target agent")
 
         # verify resource is functional
-        self.check_public_network_connectivity(
+        self._check_public_network_connectivity(
             should_connect=True,
             msg='After router rescheduling')
 
@@ -812,7 +833,8 @@
         prevents traffic to pass through the VM. Anti-spoof rules are not
         required in cases where the VM routes traffic through it.
 
-        The test steps are :
+        The test steps are:
+
         1. Create a new network.
         2. Connect (hotplug) the VM to a new network.
         3. Check the VM can ping a server on the new network ("peer")
@@ -821,14 +843,14 @@
            spoofed interface (VM cannot ping the peer).
         6. Disable port-security of the spoofed port- set the flag to false.
         7. Retest 3rd step and check that the Security Group allows pings via
-        the spoofed interface.
+           the spoofed interface.
         """
 
         spoof_mac = "00:00:00:00:00:01"
 
         # Create server
         self._setup_network_and_servers()
-        self.check_public_network_connectivity(should_connect=True)
+        self._check_public_network_connectivity(should_connect=True)
         self._create_new_network()
         self._hotplug_server()
         fip, server = self.floating_ip_tuple
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 934e1dd..438ee01 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -12,13 +12,18 @@
 #    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_log import log as logging
+
 from tempest.common import utils
 from tempest import config
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions
 from tempest.scenario import manager
 
 CONF = config.CONF
+LOG = logging.getLogger(__name__)
 
 
 class TestGettingAddress(manager.NetworkScenarioTest):
@@ -38,11 +43,11 @@
     @classmethod
     def skip_checks(cls):
         super(TestGettingAddress, cls).skip_checks()
-        if not (CONF.network_feature_enabled.ipv6
-                and CONF.network_feature_enabled.ipv6_subnet_attributes):
+        if not (CONF.network_feature_enabled.ipv6 and
+                CONF.network_feature_enabled.ipv6_subnet_attributes):
             raise cls.skipException('IPv6 or its attributes not supported')
-        if not (CONF.network.project_networks_reachable
-                or CONF.network.public_network_id):
+        if not (CONF.network.project_networks_reachable or
+                CONF.network.public_network_id):
             msg = ('Either project_networks_reachable must be "true", or '
                    'public_network_id must be defined.')
             raise cls.skipException(msg)
@@ -130,7 +135,7 @@
         ssh = self.get_remote_client(
             ip_address=fip['floating_ip_address'],
             username=username, server=srv)
-        return ssh, ips, srv["id"]
+        return ssh, ips, srv
 
     def turn_nic6_on(self, ssh, sid, network_id):
         """Turns the IPv6 vNIC on
@@ -154,15 +159,38 @@
                          % (network_id, ports))
         mac6 = ports[0]
         nic = ssh.get_nic_name_by_mac(mac6)
+        # NOTE(slaweq): on RHEL based OS ifcfg file for new interface is
+        # needed to make IPv6 working on it, so if
+        # /etc/sysconfig/network-scripts directory exists ifcfg-%(nic)s file
+        # should be added in it
+        if self._sysconfig_network_scripts_dir_exists(ssh):
+            try:
+                ssh.exec_command(
+                    'echo -e "DEVICE=%(nic)s\\nIPV6INIT=yes" | '
+                    'sudo tee /etc/sysconfig/network-scripts/ifcfg-%(nic)s; '
+                    'sudo /sbin/service network restart' % {'nic': nic})
+            except exceptions.SSHExecCommandFailed as e:
+                # NOTE(slaweq): Sometimes it can happen that this SSH command
+                # will fail because of some error from network manager in
+                # guest os.
+                # But even then doing ip link set up below is fine and
+                # IP address should be configured properly.
+                LOG.debug("Error during restarting %(nic)s interface on "
+                          "instance. Error message: %(error)s",
+                          {'nic': nic, 'error': e})
         ssh.exec_command("sudo ip link set %s up" % nic)
 
+    def _sysconfig_network_scripts_dir_exists(self, ssh):
+        return "False" not in ssh.exec_command(
+            'test -d /etc/sysconfig/network-scripts/ || echo "False"')
+
     def _prepare_and_test(self, address6_mode, n_subnets6=1, dualnet=False):
         net_list = self.prepare_network(address6_mode=address6_mode,
                                         n_subnets6=n_subnets6,
                                         dualnet=dualnet)
 
-        sshv4_1, ips_from_api_1, sid1 = self.prepare_server(networks=net_list)
-        sshv4_2, ips_from_api_2, sid2 = self.prepare_server(networks=net_list)
+        sshv4_1, ips_from_api_1, srv1 = self.prepare_server(networks=net_list)
+        sshv4_2, ips_from_api_2, srv2 = self.prepare_server(networks=net_list)
 
         def guest_has_address(ssh, addr):
             return addr in ssh.exec_command("ip address")
@@ -170,8 +198,8 @@
         # Turn on 2nd NIC for Cirros when dualnet
         if dualnet:
             _, network_v6 = net_list
-            self.turn_nic6_on(sshv4_1, sid1, network_v6['id'])
-            self.turn_nic6_on(sshv4_2, sid2, network_v6['id'])
+            self.turn_nic6_on(sshv4_1, srv1['id'], network_v6['id'])
+            self.turn_nic6_on(sshv4_2, srv2['id'], network_v6['id'])
 
         # get addresses assigned to vNIC as reported by 'ip address' utility
         ips_from_ip_1 = sshv4_1.exec_command("ip address")
@@ -181,13 +209,19 @@
         for i in range(n_subnets6):
             # v6 should be configured since the image supports it
             # It can take time for ipv6 automatic address to get assigned
-            self.assertTrue(test_utils.call_until_true(guest_has_address,
-                            CONF.validation.ping_timeout, 1,
-                            sshv4_1, ips_from_api_1['6'][i]))
-
-            self.assertTrue(test_utils.call_until_true(guest_has_address,
-                            CONF.validation.ping_timeout, 1,
-                            sshv4_2, ips_from_api_2['6'][i]))
+            for srv, ssh, ips in (
+                    (srv1, sshv4_1, ips_from_api_1),
+                    (srv2, sshv4_2, ips_from_api_2)):
+                ip = ips['6'][i]
+                result = test_utils.call_until_true(
+                    guest_has_address,
+                    CONF.validation.ping_timeout, 1, ssh, ip)
+                if not result:
+                    self._log_console_output(servers=[srv])
+                    self.fail(
+                        'Address %s not configured for instance %s, '
+                        'ip address output is\n%s' %
+                        (ip, srv['id'], ssh.exec_command("ip address")))
 
         self.check_remote_connectivity(sshv4_1, ips_from_api_2['4'])
         self.check_remote_connectivity(sshv4_2, ips_from_api_1['4'])
@@ -246,6 +280,7 @@
                                dualnet=True)
 
     @decorators.idempotent_id('9178ad42-10e4-47e9-8987-e02b170cc5cd')
+    @decorators.attr(type='slow')
     @utils.services('compute', 'network')
     def test_dualnet_multi_prefix_slaac(self):
         self._prepare_and_test(address6_mode='slaac', n_subnets6=2,
diff --git a/tempest/scenario/test_object_storage_basic_ops.py b/tempest/scenario/test_object_storage_basic_ops.py
index cbe321e..b635ca0 100644
--- a/tempest/scenario/test_object_storage_basic_ops.py
+++ b/tempest/scenario/test_object_storage_basic_ops.py
@@ -24,15 +24,15 @@
     def test_swift_basic_ops(self):
         """Test swift basic ops.
 
-         * get swift stat.
-         * create container.
-         * upload a file to the created container.
-         * list container's objects and assure that the uploaded file is
-         present.
-         * download the object and check the content
-         * delete object from container.
-         * list container's objects and assure that the deleted file is gone.
-         * delete a container.
+        * get swift stat.
+        * create container.
+        * upload a file to the created container.
+        * list container's objects and assure that the uploaded file is
+          present.
+        * download the object and check the content
+        * delete object from container.
+        * list container's objects and assure that the deleted file is gone.
+        * delete a container.
         """
         self.get_swift_stat()
         container_name = self.create_container()
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index bfe3604..9cbd831 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -63,28 +63,28 @@
                 a. a security group open to incoming ssh connection
                 b. a VM with a floating ip
             5. create a general empty security group (same as "default", but
-            without rules allowing in-tenant traffic)
+               without rules allowing in-tenant traffic)
 
     tests:
         1. _verify_network_details
         2. _verify_mac_addr: for each access point verify that
-        (subnet, fix_ip, mac address) are as defined in the port list
+           (subnet, fix_ip, mac address) are as defined in the port list
         3. _test_in_tenant_block: test that in-tenant traffic is disabled
-        without rules allowing it
+           without rules allowing it
         4. _test_in_tenant_allow: test that in-tenant traffic is enabled
-        once an appropriate rule has been created
+           once an appropriate rule has been created
         5. _test_cross_tenant_block: test that cross-tenant traffic is disabled
-        without a rule allowing it on destination tenant
+           without a rule allowing it on destination tenant
         6. _test_cross_tenant_allow:
             * test that cross-tenant traffic is enabled once an appropriate
-            rule has been created on destination tenant.
+              rule has been created on destination tenant.
             * test that reverse traffic is still blocked
             * test than reverse traffic is enabled once an appropriate rule has
-            been created on source tenant
-        7._test_port_update_new_security_group:
-           * test that traffic is blocked with default security group
-           * test that traffic is enabled after updating port with new security
-           group having appropriate rule
+              been created on source tenant
+        7. _test_port_update_new_security_group:
+            * test that traffic is blocked with default security group
+            * test that traffic is enabled after updating port with new
+              security group having appropriate rule
         8. _test_multiple_security_groups: test multiple security groups can be
            associated with the vm
 
@@ -93,11 +93,13 @@
         2. Public network is defined and reachable from the Tempest host
         3. Public router can either be:
             * defined, in which case all tenants networks can connect directly
-            to it, and cross tenant check will be done on the private IP of the
-            destination tenant
+              to it, and cross tenant check will be done on the private IP of
+              the destination tenant
+
             or
+
             * not defined (empty string), in which case each tenant will have
-            its own router connected to the public network
+              its own router connected to the public network
     """
 
     credentials = ['primary', 'alt', 'admin']
@@ -282,11 +284,8 @@
 
         # Verify servers are on different compute nodes
         if self.multi_node:
-            adm_get_server = self.os_admin.servers_client.show_server
-            new_host = adm_get_server(server["id"])["server"][
-                "OS-EXT-SRV-ATTR:host"]
-            host_list = [adm_get_server(s)["server"]["OS-EXT-SRV-ATTR:host"]
-                         for s in self.servers]
+            new_host = self.get_host_for_server(server["id"])
+            host_list = [self.get_host_for_server(s) for s in self.servers]
             self.assertNotIn(new_host, host_list,
                              message="Failed to boot servers on different "
                                      "Compute nodes.")
@@ -369,7 +368,8 @@
             self.floating_ips[tenant.access_point['id']]['floating_ip_address']
         private_key = tenant.keypair['private_key']
         access_point_ssh = self.get_remote_client(
-            access_point_ssh, private_key=private_key)
+            access_point_ssh, private_key=private_key,
+            server=tenant.access_point)
         return access_point_ssh
 
     def _test_in_tenant_block(self, tenant):
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 89b9fdd..8aa729b 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -32,7 +32,6 @@
     """The test suite for server advanced operations
 
     This test case stresses some advanced server instance operations:
-     * Resizing a volume-backed instance
      * Sequence suspend resume
     """
 
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index d5c378e..02bc692 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -13,8 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import json
-import re
+from oslo_serialization import jsonutils as json
 
 from tempest.common import utils
 from tempest.common import waiters
@@ -43,12 +42,6 @@
      * Terminate the instance
     """
 
-    @classmethod
-    def skip_checks(cls):
-        super(TestServerBasicOps, cls).skip_checks()
-        if not CONF.network_feature_enabled.floating_ips:
-            raise cls.skipException("Floating ips are not available")
-
     def setUp(self):
         super(TestServerBasicOps, self).setUp()
         self.run_ssh = CONF.validation.run_validation
@@ -56,11 +49,17 @@
 
     def verify_ssh(self, keypair):
         if self.run_ssh:
-            # Obtain a floating IP
-            self.fip = self.create_floating_ip(self.instance)['ip']
+            # Obtain a floating IP if floating_ips is enabled
+            if (CONF.network_feature_enabled.floating_ips and
+                CONF.network.floating_network_name):
+                self.ip = self.create_floating_ip(self.instance)['ip']
+            else:
+                server = self.servers_client.show_server(
+                    self.instance['id'])['server']
+                self.ip = self.get_server_ip(server)
             # Check ssh
             self.ssh_client = self.get_remote_client(
-                ip_address=self.fip,
+                ip_address=self.ip,
                 username=self.ssh_user,
                 private_key=keypair['private_key'],
                 server=self.instance)
@@ -75,8 +74,8 @@
                 result = self.ssh_client.exec_command(cmd)
                 if result:
                     msg = ('Failed while verifying metadata on server. Result '
-                           'of command "%s" is NOT "%s".' % (cmd, self.fip))
-                    self.assertEqual(self.fip, result, msg)
+                           'of command "%s" is NOT "%s".' % (cmd, self.ip))
+                    self.assertEqual(self.ip, result, msg)
                     return 'Verification is successful!'
 
             if not test_utils.call_until_true(exec_cmd_and_verify_output,
@@ -94,22 +93,13 @@
             result = self.servers_client.show_password(self.instance['id'])
             self.assertEqual(data, result['password'])
 
-    def _mount_config_drive(self):
-        cmd_blkid = 'blkid | grep -i config-2'
-        result = self.ssh_client.exec_command(cmd_blkid)
-        dev_name = re.match('([^:]+)', result).group()
-        self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
-
-    def _unmount_config_drive(self):
-        self.ssh_client.exec_command('sudo umount /mnt')
-
     def verify_metadata_on_config_drive(self):
         if self.run_ssh and CONF.compute_feature_enabled.config_drive:
             # Verify metadata on config_drive
-            self._mount_config_drive()
+            self.ssh_client.mount_config_drive()
             cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
             result = self.ssh_client.exec_command(cmd_md)
-            self._unmount_config_drive()
+            self.ssh_client.unmount_config_drive()
             result = json.loads(result)
             self.assertIn('meta', result)
             msg = ('Failed while verifying metadata on config_drive on server.'
@@ -119,10 +109,10 @@
     def verify_networkdata_on_config_drive(self):
         if self.run_ssh and CONF.compute_feature_enabled.config_drive:
             # Verify network data on config_drive
-            self._mount_config_drive()
+            self.ssh_client.mount_config_drive()
             cmd_md = 'sudo cat /mnt/openstack/latest/network_data.json'
             result = self.ssh_client.exec_command(cmd_md)
-            self._unmount_config_drive()
+            self.ssh_client.unmount_config_drive()
             result = json.loads(result)
             self.assertIn('services', result)
             self.assertIn('links', result)
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 68f18d1..d6b6d14 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -63,7 +63,8 @@
 
         instance_ip = self.get_server_ip(server)
         timestamp = self.create_timestamp(instance_ip,
-                                          private_key=keypair['private_key'])
+                                          private_key=keypair['private_key'],
+                                          server=server)
 
         # Prevent bug #1257594 from coming back
         # Unshelve used to boot the instance with the original image, not
@@ -71,7 +72,8 @@
         self._shelve_then_unshelve_server(server)
 
         timestamp2 = self.get_timestamp(instance_ip,
-                                        private_key=keypair['private_key'])
+                                        private_key=keypair['private_key'],
+                                        server=server)
         self.assertEqual(timestamp, timestamp2)
 
     @decorators.attr(type='slow')
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index b51a781..a33d4d4 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -57,7 +57,8 @@
 
         instance_ip = self.get_server_ip(server)
         timestamp = self.create_timestamp(instance_ip,
-                                          private_key=keypair['private_key'])
+                                          private_key=keypair['private_key'],
+                                          server=server)
 
         # snapshot the instance
         snapshot_image = self.create_server_snapshot(server=server)
@@ -71,5 +72,6 @@
         # check the existence of the timestamp file in the second instance
         server_from_snapshot_ip = self.get_server_ip(server_from_snapshot)
         timestamp2 = self.get_timestamp(server_from_snapshot_ip,
-                                        private_key=keypair['private_key'])
+                                        private_key=keypair['private_key'],
+                                        server=server_from_snapshot)
         self.assertEqual(timestamp, timestamp2)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index ef369d6..2782119 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -96,7 +96,8 @@
                                                       keypair['private_key'])
         timestamp = self.create_timestamp(ip_for_server,
                                           CONF.compute.volume_device_name,
-                                          private_key=keypair['private_key'])
+                                          private_key=keypair['private_key'],
+                                          server=server)
         self.nova_volume_detach(server, volume)
 
         # snapshot the volume
@@ -126,5 +127,6 @@
         # check the existence of the timestamp file in the volume2
         timestamp2 = self.get_timestamp(ip_for_snapshot,
                                         CONF.compute.volume_device_name,
-                                        private_key=keypair['private_key'])
+                                        private_key=keypair['private_key'],
+                                        server=server_from_snapshot)
         self.assertEqual(timestamp, timestamp2)
diff --git a/tempest/scenario/test_volume_backup_restore.py b/tempest/scenario/test_volume_backup_restore.py
new file mode 100644
index 0000000..8a8c54e
--- /dev/null
+++ b/tempest/scenario/test_volume_backup_restore.py
@@ -0,0 +1,94 @@
+# Copyright 2018 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.common import utils
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+from tempest.scenario import manager
+
+CONF = config.CONF
+
+
+class TestVolumeBackupRestore(manager.ScenarioTest):
+    """Test cinder backup and restore
+
+    This testcase verifies content preservation after backup and restore
+    operations by booting a server from a restored backup and check the
+    connectivity to it.
+
+    The following is the scenario outline:
+    1. Create volume from image.
+    2. Create a backup for the volume.
+    3. Restore the backup.
+    4. Boot a server from the restored backup.
+    5. Create a floating ip.
+    6. Check server connectivity.
+    """
+
+    @classmethod
+    def skip_checks(cls):
+        super(TestVolumeBackupRestore, cls).skip_checks()
+        if not CONF.volume_feature_enabled.backup:
+            raise cls.skipException('Backup is not enable.')
+
+    @decorators.idempotent_id('2ce5e55c-4085-43c1-98c6-582525334ad7')
+    @decorators.attr(type='slow')
+    @utils.services('compute', 'volume', 'image')
+    def test_volume_backup_restore(self):
+        # Create volume from image
+        img_uuid = CONF.compute.image_ref
+        volume = self.create_volume(imageRef=img_uuid)
+        volume_details = self.volumes_client.show_volume(
+            volume['id'])['volume']
+        self.assertEqual('true', volume_details['bootable'])
+
+        # Create a backup
+        backup = self.create_backup(volume_id=volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
+
+        # Restore the backup
+        restored_volume_id = self.restore_backup(backup['id'])['volume_id']
+
+        # Verify the restored backup volume is bootable
+        restored_volume_info = self.volumes_client.show_volume(
+            restored_volume_id)['volume']
+        self.assertEqual('true', restored_volume_info['bootable'])
+
+        # Create keypair and security group
+        keypair = self.create_keypair()
+        security_group = self._create_security_group()
+
+        # Boot a server from the restored backup
+        bd_map_v2 = [{
+            'uuid': restored_volume_id,
+            'source_type': 'volume',
+            'destination_type': 'volume',
+            'boot_index': 0}]
+        server = self.create_server(image_id='',
+                                    block_device_mapping_v2=bd_map_v2,
+                                    key_name=keypair['name'],
+                                    security_groups=[
+                                        {'name': security_group['name']}])
+
+        # Create a floating ip
+        floating_ip = self.create_floating_ip(server)
+
+        # Check server connectivity
+        self.check_vm_connectivity(floating_ip['ip'],
+                                   username=CONF.validation.image_ssh_user,
+                                   private_key=keypair['private_key'],
+                                   should_connect=True)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 64ea8f6..6ed7e30 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -11,6 +11,7 @@
 #    under the License.
 
 from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
 import testtools
 
 from tempest.common import utils
@@ -30,12 +31,6 @@
     # breathing room to get through deletes in the time allotted.
     TIMEOUT_SCALING_FACTOR = 2
 
-    @classmethod
-    def skip_checks(cls):
-        super(TestVolumeBootPattern, cls).skip_checks()
-        if not CONF.volume_feature_enabled.snapshot:
-            raise cls.skipException("Cinder volume snapshots are disabled")
-
     def _create_volume_from_image(self):
         img_uuid = CONF.compute.image_ref
         vol_name = data_utils.rand_name(
@@ -55,7 +50,8 @@
                                      source_type,
                                      keypair=None,
                                      security_group=None,
-                                     delete_on_termination=False):
+                                     delete_on_termination=False,
+                                     name=None):
         create_kwargs = dict()
         if keypair:
             create_kwargs['key_name'] = keypair['name']
@@ -66,6 +62,8 @@
             source_id,
             source_type,
             delete_on_termination=delete_on_termination))
+        if name:
+            create_kwargs['name'] = name
 
         return self.create_server(image_id='', **create_kwargs)
 
@@ -73,12 +71,21 @@
         self.servers_client.delete_server(server['id'])
         waiters.wait_for_server_termination(self.servers_client, server['id'])
 
+    def _delete_snapshot(self, snapshot_id):
+        self.snapshots_client.delete_snapshot(snapshot_id)
+        self.snapshots_client.wait_for_resource_deletion(snapshot_id)
+
     @decorators.idempotent_id('557cd2c2-4eb8-4dce-98be-f86765ff311b')
+    @decorators.attr(type='slow')
+    # Note: This test is being skipped based on 'public_network_id'.
+    # It is being used in create_floating_ip() method which gets called
+    # from get_server_ip() method
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
+    @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+                          'Cinder volume snapshots are disabled')
     @utils.services('compute', 'volume', 'image')
     def test_volume_boot_pattern(self):
-
         """This test case attempts to reproduce the following steps:
 
         * Create in Cinder some bootable volume importing a Glance image
@@ -109,7 +116,8 @@
         LOG.info("Setting timestamp in instance %s", instance_1st)
         ip_instance_1st = self.get_server_ip(instance_1st)
         timestamp = self.create_timestamp(ip_instance_1st,
-                                          private_key=keypair['private_key'])
+                                          private_key=keypair['private_key'],
+                                          server=instance_1st)
 
         # delete instance
         LOG.info("Deleting first instance: %s", instance_1st)
@@ -127,7 +135,8 @@
         LOG.info("Getting timestamp in instance %s", instance_2nd)
         ip_instance_2nd = self.get_server_ip(instance_2nd)
         timestamp2 = self.get_timestamp(ip_instance_2nd,
-                                        private_key=keypair['private_key'])
+                                        private_key=keypair['private_key'],
+                                        server=instance_2nd)
         self.assertEqual(timestamp, timestamp2)
 
         # snapshot a volume
@@ -151,11 +160,14 @@
                  server_from_snapshot)
         server_from_snapshot_ip = self.get_server_ip(server_from_snapshot)
         timestamp3 = self.get_timestamp(server_from_snapshot_ip,
-                                        private_key=keypair['private_key'])
+                                        private_key=keypair['private_key'],
+                                        server=server_from_snapshot)
         self.assertEqual(timestamp, timestamp3)
 
     @decorators.idempotent_id('05795fb2-b2a7-4c9f-8fac-ff25aedb1489')
     @decorators.attr(type='slow')
+    @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+                          'Cinder volume snapshots are disabled')
     @utils.services('compute', 'image', 'volume')
     def test_create_server_from_volume_snapshot(self):
         # Create a volume from an image
@@ -192,26 +204,76 @@
                          created_volume_info['attachments'][0]['volume_id'])
 
     @decorators.idempotent_id('36c34c67-7b54-4b59-b188-02a2f458a63b')
+    @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+                          'Cinder volume snapshots are disabled')
     @utils.services('compute', 'volume', 'image')
-    def test_create_ebs_image_and_check_boot(self):
-        # create an instance from volume
+    def test_image_defined_boot_from_volume(self):
+        # create an instance from image-backed volume
         volume_origin = self._create_volume_from_image()
-        instance = self._boot_instance_from_resource(
+        name = data_utils.rand_name(self.__class__.__name__ +
+                                    '-volume-backed-server')
+        instance1 = self._boot_instance_from_resource(
             source_id=volume_origin['id'],
             source_type='volume',
-            delete_on_termination=True)
-        # create EBS image
-        image = self.create_server_snapshot(instance)
+            delete_on_termination=True,
+            name=name)
+        # Create a snapshot image from the volume-backed server.
+        # The compute service will have the block service create a snapshot of
+        # the root volume and store its metadata in the image.
+        image = self.create_server_snapshot(instance1)
 
-        # delete instance
-        self._delete_server(instance)
+        # Create a server from the image snapshot which has an
+        # "image-defined block device mapping (BDM)" in it, i.e. the metadata
+        # about the volume snapshot. The compute service will use this to
+        # create a volume from the volume snapshot and use that as the root
+        # disk for the server.
+        name = data_utils.rand_name(self.__class__.__name__ +
+                                    '-image-snapshot-server')
+        instance2 = self.create_server(image_id=image['id'], name=name)
 
-        # boot instance from EBS image
-        instance = self.create_server(image_id=image['id'])
-        # just ensure that instance booted
+        # Verify the server was created from the image-defined BDM.
+        volume_attachments = instance2['os-extended-volumes:volumes_attached']
+        self.assertEqual(1, len(volume_attachments),
+                         "No volume attachment found.")
+        created_volume = self.volumes_client.show_volume(
+            volume_attachments[0]['id'])['volume']
+        # Assert that the volume service also shows the server attachment.
+        self.assertEqual(1, len(created_volume['attachments']),
+                         "No server attachment found for volume: %s" %
+                         created_volume)
+        self.assertEqual(instance2['id'],
+                         created_volume['attachments'][0]['server_id'])
+        self.assertEqual(volume_attachments[0]['id'],
+                         created_volume['attachments'][0]['volume_id'])
+        self.assertEqual(
+            volume_origin['volume_image_metadata']['image_id'],
+            created_volume['volume_image_metadata']['image_id'])
 
-        # delete instance
-        self._delete_server(instance)
+        # Delete the second server which should also delete the second volume
+        # created from the volume snapshot.
+        self._delete_server(instance2)
+
+        # Assert that the underlying volume is gone.
+        self.volumes_client.wait_for_resource_deletion(created_volume['id'])
+
+        # Delete the volume snapshot. We must do this before deleting the first
+        # server created in this test because the snapshot depends on the first
+        # instance's underlying volume (volume_origin).
+        # In glance v2, the image properties are flattened and in glance v1,
+        # the image properties are under the 'properties' key.
+        bdms = image.get('block_device_mapping')
+        if not bdms:
+            bdms = image['properties']['block_device_mapping']
+        bdms = json.loads(bdms)
+        snapshot_id = bdms[0]['snapshot_id']
+        self._delete_snapshot(snapshot_id)
+
+        # Now, delete the first server which will also delete the first
+        # image-backed volume.
+        self._delete_server(instance1)
+
+        # Assert that the underlying volume is gone.
+        self.volumes_client.wait_for_resource_deletion(volume_origin['id'])
 
     @decorators.idempotent_id('cb78919a-e553-4bab-b73b-10cf4d2eb125')
     @testtools.skipUnless(CONF.compute_feature_enabled.attach_encrypted_volume,
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index cd10bbd..106500e 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -33,11 +33,19 @@
      * Write to the volume
      * Perform a cinder retype --on-demand of the volume to type of backend #2
      * Check written content of migrated volume
+     * Check the type of the volume has been updated.
+     * Check the volume is still in-use and the migration was successful.
+     * Check that the same volume is attached to the instance.
     """
 
     credentials = ['primary', 'admin']
 
     @classmethod
+    def setup_clients(cls):
+        super(TestVolumeMigrateRetypeAttached, cls).setup_clients()
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
+
+    @classmethod
     def skip_checks(cls):
         super(TestVolumeMigrateRetypeAttached, cls).skip_checks()
         if not CONF.volume_feature_enabled.multi_backend:
@@ -73,11 +81,14 @@
                                         'src_backend': backend_source,
                                         'dst': dest_body['name'],
                                         'dst_backend': backend_dest})
-        return source_body['name'], dest_body['name']
+        return ({'name': source_body['name'], 'host': backend_source},
+                {'name': dest_body['name'], 'host': backend_dest})
 
     def _volume_retype_with_migration(self, volume_id, new_volume_type):
+        # NOTE: The 'on-demand' migration requires admin operation, so
+        # admin_volumes_client() should be used here.
         migration_policy = 'on-demand'
-        self.volumes_client.retype_volume(
+        self.admin_volumes_client.retype_volume(
             volume_id, new_type=new_volume_type,
             migration_policy=migration_policy)
         waiters.wait_for_volume_retype(self.volumes_client,
@@ -86,7 +97,7 @@
     @decorators.attr(type='slow')
     @decorators.idempotent_id('deadd2c2-beef-4dce-98be-f86765ff311b')
     @utils.services('compute', 'volume')
-    def test_volume_migrate_attached(self):
+    def test_volume_retype_attached(self):
         LOG.info("Creating keypair and security group")
         keypair = self.create_keypair()
         security_group = self._create_security_group()
@@ -97,26 +108,109 @@
 
         # create an instance from volume
         LOG.info("Booting instance from volume")
-        volume_origin = self.create_volume(imageRef=CONF.compute.image_ref,
-                                           volume_type=source_type)
+        volume_id = self.create_volume(imageRef=CONF.compute.image_ref,
+                                       volume_type=source_type['name'])['id']
 
-        instance = self._boot_instance_from_volume(volume_origin['id'],
-                                                   keypair, security_group)
+        instance = self._boot_instance_from_volume(volume_id, keypair,
+                                                   security_group)
 
         # write content to volume on instance
         LOG.info("Setting timestamp in instance %s", instance['id'])
         ip_instance = self.get_server_ip(instance)
         timestamp = self.create_timestamp(ip_instance,
-                                          private_key=keypair['private_key'])
+                                          private_key=keypair['private_key'],
+                                          server=instance)
 
         # retype volume with migration from backend #1 to backend #2
-        LOG.info("Retyping Volume %s to new type %s", volume_origin['id'],
-                 dest_type)
-        self._volume_retype_with_migration(volume_origin['id'], dest_type)
+        LOG.info("Retyping Volume %s to new type %s", volume_id,
+                 dest_type['name'])
+        # This method calls for the retype of the volume before calling a
+        # waiter that asserts that the volume type has changed successfully.
+        self._volume_retype_with_migration(volume_id, dest_type['name'])
 
         # check the content of written file
         LOG.info("Getting timestamp in postmigrated instance %s",
                  instance['id'])
         timestamp2 = self.get_timestamp(ip_instance,
-                                        private_key=keypair['private_key'])
+                                        private_key=keypair['private_key'],
+                                        server=instance)
         self.assertEqual(timestamp, timestamp2)
+
+        # Assert that the volume is on the new host, is still in-use and has a
+        # migration_status of success
+        volume = self.admin_volumes_client.show_volume(volume_id)['volume']
+        # dest_type is host@backend, os-vol-host-attr:host is host@backend#type
+        self.assertIn(dest_type['host'], volume['os-vol-host-attr:host'])
+        self.assertEqual('in-use', volume['status'])
+        self.assertEqual('success', volume['migration_status'])
+
+        # Assert that the same volume id is attached to the instance, ensuring
+        # the os-migrate_volume_completion Cinder API has been called.
+        attached_volumes = self.servers_client.list_volume_attachments(
+            instance['id'])['volumeAttachments']
+        self.assertEqual(volume_id, attached_volumes[0]['id'])
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('fe47b1ed-640e-4e3b-a090-200e25607362')
+    @utils.services('compute', 'volume')
+    def test_volume_migrate_attached(self):
+        LOG.info("Creating keypair and security group")
+        keypair = self.create_keypair()
+        security_group = self._create_security_group()
+
+        LOG.info("Creating volume")
+        # Create a unique volume type to avoid using the backend default
+        migratable_type = self.create_volume_type()['name']
+        volume_id = self.create_volume(imageRef=CONF.compute.image_ref,
+                                       volume_type=migratable_type)['id']
+        volume = self.admin_volumes_client.show_volume(volume_id)
+
+        LOG.info("Booting instance from volume")
+        instance = self._boot_instance_from_volume(volume_id, keypair,
+                                                   security_group)
+
+        # Identify the source and destination hosts for the migration
+        src_host = volume['volume']['os-vol-host-attr:host']
+
+        # Select the first c-vol host that isn't hosting the volume as the dest
+        # host['host_name'] should take the format of host@backend.
+        # src_host should take the format of host@backend#type
+        hosts = self.admin_volumes_client.list_hosts()['hosts']
+        for host in hosts:
+            if (host['service'] == 'cinder-volume' and
+                not src_host.startswith(host['host_name'])):
+                dest_host = host['host_name']
+                break
+
+        ip_instance = self.get_server_ip(instance)
+        timestamp = self.create_timestamp(ip_instance,
+                                          private_key=keypair['private_key'],
+                                          server=instance)
+
+        LOG.info("Migrating Volume %s from host %s to host %s",
+                 volume_id, src_host, dest_host)
+        self.admin_volumes_client.migrate_volume(volume_id, host=dest_host)
+
+        # This waiter asserts that the migration_status is success and that
+        # the volume has moved to the dest_host
+        waiters.wait_for_volume_migration(self.admin_volumes_client, volume_id,
+                                          dest_host)
+
+        # check the content of written file
+        LOG.info("Getting timestamp in postmigrated instance %s",
+                 instance['id'])
+        timestamp2 = self.get_timestamp(ip_instance,
+                                        private_key=keypair['private_key'],
+                                        server=instance)
+        self.assertEqual(timestamp, timestamp2)
+
+        # Assert that the volume is in-use
+        volume = self.admin_volumes_client.show_volume(volume_id)['volume']
+        self.assertEqual('in-use', volume['status'])
+
+        # Assert that the same volume id is attached to the instance, ensuring
+        # the os-migrate_volume_completion Cinder API has been called
+        attached_volumes = self.servers_client.list_volume_attachments(
+            instance['id'])['volumeAttachments']
+        attached_volume_id = attached_volumes[0]['id']
+        self.assertEqual(volume_id, attached_volume_id)
diff --git a/tempest/test.py b/tempest/test.py
index 9da85d5..85000b6 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -28,6 +28,7 @@
 from tempest.common import utils
 from tempest import config
 from tempest.lib.common import fixed_network
+from tempest.lib.common import profiler
 from tempest.lib.common import validation_resources as vr
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
@@ -68,9 +69,9 @@
 def validate_tearDownClass():
     if at_exit_set:
         LOG.error(
-            "tearDownClass does not call the super's "
-            "tearDownClass in these classes: \n"
-            + str(at_exit_set))
+            "tearDownClass does not call the super's tearDownClass in "
+            "these classes:\n"
+            "  %s", at_exit_set)
 
 
 atexit.register(validate_tearDownClass)
@@ -231,6 +232,9 @@
         if CONF.pause_teardown:
             BaseTestCase.insert_pdb_breakpoint()
 
+        if CONF.profiler.key:
+            profiler.disable()
+
     @classmethod
     def insert_pdb_breakpoint(cls):
         """Add pdb breakpoint.
@@ -259,6 +263,7 @@
         based on the result of an API call are discouraged.
 
         The following checks are implemented in `test.py` already:
+
         - check that alt credentials are available when requested by the test
         - check that admin credentials are available when requested by the test
         - check that the identity version specified by the test is marked as
@@ -310,6 +315,7 @@
         `os_[type]`:
 
         Valid values in `credentials` are:
+
         - 'primary':
             A normal user is provisioned.
             It can be used only once. Multiple entries will be ignored.
@@ -581,9 +587,9 @@
     def setUp(self):
         super(BaseTestCase, self).setUp()
         if not self.__setupclass_called:
-            raise RuntimeError("setUpClass does not calls the super's"
-                               "setUpClass in the "
-                               + self.__class__.__name__)
+            raise RuntimeError("setUpClass does not calls the super's "
+                               "setUpClass in the " +
+                               self.__class__.__name__)
         at_exit_set.add(self.__class__)
         test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
         try:
@@ -602,10 +608,12 @@
             stderr = self.useFixture(fixtures.StringStream('stderr')).stream
             self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
         if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
-            os.environ.get('OS_LOG_CAPTURE') != '0'):
+                os.environ.get('OS_LOG_CAPTURE') != '0'):
             self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
                                                    format=self.log_format,
                                                    level=None))
+        if CONF.profiler.key:
+            profiler.enable(CONF.profiler.key)
 
     @property
     def credentials_provider(self):
@@ -836,7 +844,7 @@
             manager = cls.get_client_manager()
 
         # Make sure cred_provider exists and get a network client
-        networks_client = manager.compute_networks_client
+        networks_client = manager.networks_client
         cred_provider = cls._get_credentials_provider()
         # In case of nova network, isolated tenants are not able to list the
         # network configured in fixed_network_name, even if they can use it
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index 9c18052..7a037eb 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -179,6 +179,7 @@
     This class is used to manage the lifecycle of external tempest test
     plugins. It provides functions for getting set
     """
+
     def __init__(self):
         self.ext_plugins = stevedore.ExtensionManager(
             'tempest.test_plugins', invoke_on_load=True,
diff --git a/tempest/test_discover/test_discover.py b/tempest/test_discover/test_discover.py
index 330f370..143c6e1 100644
--- a/tempest/test_discover/test_discover.py
+++ b/tempest/test_discover/test_discover.py
@@ -37,7 +37,7 @@
                                            top_level_dir=base_path))
         else:
             suite.addTests(loader.discover(full_test_dir, pattern=pattern,
-                           top_level_dir=base_path))
+                                           top_level_dir=base_path))
 
     plugin_load_tests = ext_plugins.get_plugin_load_tests_tuple()
     if not plugin_load_tests:
diff --git a/tempest/tests/api/compute/test_base.py b/tempest/tests/api/compute/test_base.py
index 5024100..1593464 100644
--- a/tempest/tests/api/compute/test_base.py
+++ b/tempest/tests/api/compute/test_base.py
@@ -131,8 +131,13 @@
             self.assertIn(fault, six.text_type(ex))
         else:
             self.assertNotIn(fault, six.text_type(ex))
+        if compute_base.BaseV2ComputeTest.is_requested_microversion_compatible(
+            '2.35'):
+            status = 'ACTIVE'
+        else:
+            status = 'active'
         wait_for_image_status.assert_called_once_with(
-            compute_images_client, image_id, 'active')
+            compute_images_client, image_id, status)
         servers_client.show_server.assert_called_once_with(
             mock.sentinel.server_id)
 
@@ -173,3 +178,20 @@
         # make our assertions
         wait_for_image_status.assert_called_once_with(
             compute_images_client, image_id, 'SAVING')
+
+    def _test_version_compatible(self, max_version, expected=True):
+        actual = (compute_base.BaseV2ComputeTest.
+                  is_requested_microversion_compatible(max_version))
+        self.assertEqual(expected, actual)
+
+    def test_check_lower_version(self):
+        compute_base.BaseV2ComputeTest.request_microversion = '2.8'
+        self._test_version_compatible('2.40')
+
+    def test_check_euqal_version(self):
+        compute_base.BaseV2ComputeTest.request_microversion = '2.40'
+        self._test_version_compatible('2.40')
+
+    def test_check_higher_version(self):
+        compute_base.BaseV2ComputeTest.request_microversion = '2.41'
+        self._test_version_compatible('2.40', expected=False)
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
index ca81d4d..0b53b45 100644
--- a/tempest/tests/base.py
+++ b/tempest/tests/base.py
@@ -18,7 +18,7 @@
 
 class TestCase(base.BaseTestCase):
 
-    def patch(self, target, **kwargs):
+    def patch(self, target, *args, **kwargs):
         """Returns a started `mock.patch` object for the supplied target.
 
         The caller may then call the returned patcher to create a mock object.
@@ -27,23 +27,35 @@
         patcher object, as this method automatically adds a cleanup
         to the test class to stop the patcher.
 
-        :param target: String module.class or module.object expression to patch
-        :param **kwargs: Passed as-is to `mock.patch`. See mock documentation
-                         for details.
+        :param target: string module.class or module.object expression to patch
+        :param *args: passed as-is to `mock.patch`.
+        :param **kwargs: passed as-is to `mock.patch`.
+
+        See mock documentation for more details:
+        https://docs.python.org/3.5/library/unittest.mock.html#unittest.mock.patch
         """
-        p = mock.patch(target, **kwargs)
+
+        p = mock.patch(target, *args, **kwargs)
         m = p.start()
         self.addCleanup(p.stop)
         return m
 
-    def patchobject(self, target, attribute, new=mock.DEFAULT):
+    def patchobject(self, target, attribute, *args, **kwargs):
         """Convenient wrapper around `mock.patch.object`
 
         Returns a started mock that will be automatically stopped after the
         test ran.
+
+        :param target: object to have the attribute patched
+        :param attribute: name of the attribute to be patched
+        :param *args: passed as-is to `mock.patch.object`.
+        :param **kwargs: passed as-is to `mock.patch.object`.
+
+        See mock documentation for more details:
+        https://docs.python.org/3.5/library/unittest.mock.html#unittest.mock.patch.object
         """
 
-        p = mock.patch.object(target, attribute, new)
+        p = mock.patch.object(target, attribute, *args, **kwargs)
         m = p.start()
         self.addCleanup(p.stop)
         return m
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index 8bf4c5b..b349bba 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -106,6 +106,8 @@
         cp = account_generator.get_credential_provider(self.opts)
         admin_creds = cp.default_admin_creds
         self.assertEqual(self.opts.os_tenant_name, admin_creds.tenant_name)
+        self.assertEqual(self.opts.os_username, admin_creds.username)
+        self.assertEqual(self.opts.os_password, admin_creds.password)
 
 
 class TestAccountGeneratorV3(TestAccountGeneratorV2):
@@ -153,17 +155,14 @@
 
     def test_generate_resources_no_admin(self):
         cfg.CONF.set_default('swift', False, group='service_available')
-        cfg.CONF.set_default('heat', False, group='service_available')
         cfg.CONF.set_default('operator_role', 'fake_operator',
                              group='object-storage')
         cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
                              group='object-storage')
-        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
-                             group='orchestration')
         resources = account_generator.generate_resources(
             self.cred_provider, admin=False)
         resource_types = [k for k, _ in resources]
-        # No admin, no heat, no swift, expect two credentials only
+        # No admin, no swift, expect two credentials only
         self.assertEqual(2, len(resources))
         # Ensure create_user was invoked twice (two distinct users)
         self.assertEqual(2, self.user_create_fixture.mock.call_count)
@@ -180,17 +179,14 @@
 
     def test_generate_resources_admin(self):
         cfg.CONF.set_default('swift', False, group='service_available')
-        cfg.CONF.set_default('heat', False, group='service_available')
         cfg.CONF.set_default('operator_role', 'fake_operator',
                              group='object-storage')
         cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
                              group='object-storage')
-        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
-                             group='orchestration')
         resources = account_generator.generate_resources(
             self.cred_provider, admin=True)
         resource_types = [k for k, _ in resources]
-        # Admin, no heat, no swift, expect three credentials only
+        # Admin, no swift, expect three credentials only
         self.assertEqual(3, len(resources))
         # Ensure create_user was invoked 3 times (3 distinct users)
         self.assertEqual(3, self.user_create_fixture.mock.call_count)
@@ -205,28 +201,48 @@
             self.assertIsNotNone(resource[1].router)
             self.assertIsNotNone(resource[1].subnet)
 
-    def test_generate_resources_swift_heat_admin(self):
+    def test_generate_resources_swift_admin(self):
         cfg.CONF.set_default('swift', True, group='service_available')
-        cfg.CONF.set_default('heat', True, group='service_available')
         cfg.CONF.set_default('operator_role', 'fake_operator',
                              group='object-storage')
         cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
                              group='object-storage')
-        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
-                             group='orchestration')
         resources = account_generator.generate_resources(
             self.cred_provider, admin=True)
         resource_types = [k for k, _ in resources]
-        # all options on, expect six credentials
-        self.assertEqual(6, len(resources))
-        # Ensure create_user was invoked 6 times (6 distinct users)
-        self.assertEqual(6, self.user_create_fixture.mock.call_count)
+        # all options on, expect five credentials
+        self.assertEqual(5, len(resources))
+        # Ensure create_user was invoked 5 times (5 distinct users)
+        self.assertEqual(5, self.user_create_fixture.mock.call_count)
         self.assertIn('primary', resource_types)
         self.assertIn('alt', resource_types)
         self.assertIn('admin', resource_types)
         self.assertIn(['fake_operator'], resource_types)
         self.assertIn(['fake_reseller'], resource_types)
-        self.assertIn(['fake_owner', 'fake_operator'], resource_types)
+        for resource in resources:
+            self.assertIsNotNone(resource[1].network)
+            self.assertIsNotNone(resource[1].router)
+            self.assertIsNotNone(resource[1].subnet)
+
+    def test_generate_resources_swift_no_admin(self):
+        cfg.CONF.set_default('swift', True, group='service_available')
+        cfg.CONF.set_default('operator_role', 'fake_operator',
+                             group='object-storage')
+        cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
+                             group='object-storage')
+        resources = account_generator.generate_resources(
+            self.cred_provider, admin=False)
+        resource_types = [k for k, _ in resources]
+        # No Admin, swift, expect four credentials only
+        self.assertEqual(4, len(resources))
+        # Ensure create_user was invoked 4 times (4 distinct users)
+        self.assertEqual(4, self.user_create_fixture.mock.call_count)
+        self.assertIn('primary', resource_types)
+        self.assertIn('alt', resource_types)
+        self.assertNotIn('admin', resource_types)
+        self.assertIn(['fake_operator'], resource_types)
+        self.assertIn(['fake_reseller'], resource_types)
+        self.assertNotIn(['fake_owner'], resource_types)
         for resource in resources:
             self.assertIsNotNone(resource[1].network)
             self.assertIsNotNone(resource[1].router)
@@ -258,7 +274,6 @@
             self.opts)
         self.mock_resource_creation()
         cfg.CONF.set_default('swift', True, group='service_available')
-        cfg.CONF.set_default('heat', True, group='service_available')
         self.resources = account_generator.generate_resources(
             self.cred_provider, admin=True)
 
@@ -278,14 +293,14 @@
         # Ordered args in [0], keyword args in [1]
         accounts, f = yaml_dump_mock.call_args[0]
         self.assertEqual(handle, f)
-        self.assertEqual(6, len(accounts))
+        self.assertEqual(5, len(accounts))
         if self.domain_is_in:
             self.assertIn('domain_name', accounts[0].keys())
         else:
             self.assertNotIn('domain_name', accounts[0].keys())
         self.assertEqual(1, len([x for x in accounts if
                                  x.get('types') == ['admin']]))
-        self.assertEqual(3, len([x for x in accounts if 'roles' in x]))
+        self.assertEqual(2, len([x for x in accounts if 'roles' in x]))
         for account in accounts:
             self.assertIn('resources', account)
             self.assertIn('network', account.get('resources'))
@@ -309,14 +324,14 @@
         # Ordered args in [0], keyword args in [1]
         accounts, f = yaml_dump_mock.call_args[0]
         self.assertEqual(handle, f)
-        self.assertEqual(6, len(accounts))
+        self.assertEqual(5, len(accounts))
         if self.domain_is_in:
             self.assertIn('domain_name', accounts[0].keys())
         else:
             self.assertNotIn('domain_name', accounts[0].keys())
         self.assertEqual(1, len([x for x in accounts if
                                  x.get('types') == ['admin']]))
-        self.assertEqual(3, len([x for x in accounts if 'roles' in x]))
+        self.assertEqual(2, len([x for x in accounts if 'roles' in x]))
         for account in accounts:
             self.assertIn('resources', account)
             self.assertIn('network', account.get('resources'))
diff --git a/tempest/tests/cmd/test_cleanup.py b/tempest/tests/cmd/test_cleanup.py
new file mode 100644
index 0000000..b47da0b
--- /dev/null
+++ b/tempest/tests/cmd/test_cleanup.py
@@ -0,0 +1,26 @@
+# Copyright 2018 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.cmd import cleanup
+from tempest.tests import base
+
+
+class TestTempestCleanup(base.TestCase):
+
+    def test_load_json(self):
+        # instantiate "empty" TempestCleanup
+        c = cleanup.TempestCleanup(None, None, 'test')
+        test_saved_json = 'tempest/tests/cmd/test_saved_state_json.json'
+        # test if the file is loaded without any issues/exceptions
+        c._load_json(test_saved_json)
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
new file mode 100644
index 0000000..3262b1c
--- /dev/null
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -0,0 +1,1559 @@
+# Copyright 2018 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+
+from oslo_serialization import jsonutils as json
+from tempest import clients
+from tempest.cmd import cleanup_service
+from tempest import config
+from tempest.tests import base
+from tempest.tests import fake_config
+from tempest.tests.lib import fake_credentials
+from tempest.tests.lib import fake_http
+
+
+class TestBaseService(base.TestCase):
+
+    def test_base_service_init(self):
+        kwargs = {'data': {'data': 'test'},
+                  'is_dry_run': False,
+                  'saved_state_json': {'saved': 'data'},
+                  'is_preserve': False,
+                  'is_save_state': True,
+                  'tenant_id': 'project_id'}
+        base = cleanup_service.BaseService(kwargs)
+        self.assertEqual(base.data, kwargs['data'])
+        self.assertFalse(base.is_dry_run)
+        self.assertEqual(base.saved_state_json, kwargs['saved_state_json'])
+        self.assertFalse(base.is_preserve)
+        self.assertTrue(base.is_save_state)
+        self.assertEqual(base.tenant_filter['project_id'], kwargs['tenant_id'])
+
+
+class MockFunctionsBase(base.TestCase):
+
+    def _create_response(self, body, status, headers):
+        if status:
+            if body:
+                body = json.dumps(body)
+            resp = fake_http.fake_http_response(headers, status=status), body
+            return resp
+        else:
+            return body
+
+    def _create_fixtures(self, fixtures_to_make):
+        mocked_fixtures = []
+        for fixture in fixtures_to_make:
+            func, body, status = fixture
+            mocked_response = self._create_response(body, status, None)
+            if mocked_response == 'error':
+                mocked_func = self.useFixture(fixtures.MockPatch(
+                    func, side_effect=Exception("error")))
+            else:
+                mocked_func = self.useFixture(fixtures.MockPatch(
+                    func, return_value=mocked_response))
+            mocked_fixtures.append(mocked_func)
+        return mocked_fixtures
+
+    def run_function_with_mocks(self, function_to_run, functions_to_mock):
+        """Mock a service client function for testing.
+
+        :param function_to_run: The service client function to call.
+        :param functions_to_mock: a list of tuples containing the function
+               to mock, the response body, and the response status.
+               EX:
+               ('tempest.lib.common.rest_client.RestClient.get',
+                {'users': ['']},
+                200)
+        """
+        mocked_fixtures = self._create_fixtures(functions_to_mock)
+        func_return = function_to_run()
+        return func_return, mocked_fixtures
+
+
+class BaseCmdServiceTests(MockFunctionsBase):
+
+    def setUp(self):
+        super(BaseCmdServiceTests, self).setUp()
+        self.useFixture(fake_config.ConfigFixture())
+        self.patchobject(config, 'TempestConfigPrivate',
+                         fake_config.FakePrivate)
+        self.useFixture(fixtures.MockPatch(
+            'tempest.cmd.cleanup_service._get_network_id',
+            return_value=''))
+        cleanup_service.init_conf()
+        self.conf_values = {"flavors": cleanup_service.CONF_FLAVORS[0],
+                            "images": cleanup_service.CONF_IMAGES[0],
+                            "projects": cleanup_service.CONF_PROJECTS[0],
+                            "users": cleanup_service.CONF_USERS[0],
+                            "networks": cleanup_service.CONF_PUB_NETWORK,
+                            "security_groups":
+                                cleanup_service.CONF_PROJECTS[0],
+                            "ports": cleanup_service.CONF_PUB_NETWORK,
+                            "routers": cleanup_service.CONF_PUB_ROUTER,
+                            "subnetpools": cleanup_service.CONF_PROJECTS[0],
+                            }
+
+    saved_state = {
+        # Static list to ensure global service saved items are not deleted
+        "users": {u'32rwef64245tgr20121qw324bgg': u'Lightning'},
+        "flavors": {u'42': u'm1.tiny'},
+        "images": {u'34yhwr-4t3q': u'stratus-0.3.2-x86_64-disk'},
+        "roles": {u'3efrt74r45hn': u'president'},
+        "projects": {u'f38ohgp93jj032': u'manhattan'},
+        "domains": {u'default': u'Default'},
+        # Static list to ensure project service saved items are not deleted
+        "snapshots": {u'1ad4c789-7e8w-4dwg-afc5': u'saved-snapshot'},
+        "servers": {u'7a6d4v7w-36ds-4216': u'saved-server'},
+        "server_groups": {u'as6d5f7g-46ca-475e': u'saved-server-group'},
+        "keypairs": {u'saved-key-pair': {
+            u'fingerprint': u'7e:eb:ab:24',
+            u'name': u'saved-key-pair'
+        }},
+        "volumes": {u'aa77asdf-1234': u'saved-volume'},
+        "networks": {u'6722fc13-4319': {
+            u'id': u'6722fc13-4319',
+            u'name': u'saved-network'
+        }},
+        "floatingips": {u'9e82d248-408a': {
+            u'id': u'9e82d248-408a',
+            u'status': u'ACTIVE'
+        }},
+        "routers": {u'4s5w34hj-id44': u'saved-router'},
+        "metering_label_rules": {u'93a973ce-4dc5': {
+            u'direction': u'ingress',
+            u'id': u'93a973ce-4dc5'
+        }},
+        "metering_labels": {u'723b346ce866-4c7q': u'saved-label'},
+        "ports": {u'aa74aa4v-741a': u'saved-port'},
+        "security_groups": {u'7q844add-3697': u'saved-sec-group'},
+        "subnets": {u'55ttda4a-2584': u'saved-subnet'},
+        "subnetpools": {u'8acf64c1-43fc': u'saved-subnet-pool'}
+    }
+    # Mocked methods
+    get_method = 'tempest.lib.common.rest_client.RestClient.get'
+    delete_method = 'tempest.lib.common.rest_client.RestClient.delete'
+    log_method = 'tempest.cmd.cleanup_service.LOG.exception'
+    # Override parameters
+    service_class = 'BaseService'
+    response = None
+    service_name = 'default'
+
+    def _create_cmd_service(self, service_type, is_save_state=False,
+                            is_preserve=False, is_dry_run=False):
+        creds = fake_credentials.FakeKeystoneV3Credentials()
+        os = clients.Manager(creds)
+        return getattr(cleanup_service, service_type)(
+            os,
+            is_save_state=is_save_state,
+            is_preserve=is_preserve,
+            is_dry_run=is_dry_run,
+            data={},
+            saved_state_json=self.saved_state
+            )
+
+    def _test_delete(self, mocked_fixture_tuple_list, fail=False):
+        serv = self._create_cmd_service(self.service_class)
+        resp, fixtures = self.run_function_with_mocks(
+            serv.run,
+            mocked_fixture_tuple_list,
+        )
+        for fixture in fixtures:
+            if fixture.mock.return_value == 'validate':
+                fixture.mock.assert_called()
+            elif fail is False and fixture.mock.return_value == 'exception':
+                fixture.mock.assert_not_called()
+            elif self.service_name in self.saved_state.keys():
+                fixture.mock.assert_called_once()
+                for key in self.saved_state[self.service_name].keys():
+                    self.assertNotIn(key, fixture.mock.call_args[0][0])
+            else:
+                fixture.mock.assert_called_once()
+        self.assertFalse(serv.data)
+
+    def _test_dry_run_true(self, mocked_fixture_tuple_list):
+        serv = self._create_cmd_service(self.service_class, is_dry_run=True)
+        _, fixtures = self.run_function_with_mocks(
+            serv.run,
+            mocked_fixture_tuple_list
+        )
+        for fixture in fixtures:
+            if fixture.mock.return_value == 'delete':
+                fixture.mock.assert_not_called()
+            elif self.service_name in self.saved_state.keys():
+                fixture.mock.assert_called_once()
+                for key in self.saved_state[self.service_name].keys():
+                    self.assertNotIn(key, fixture.mock.call_args[0][0])
+            else:
+                fixture.mock.assert_called_once()
+
+    def _test_saved_state_true(self, mocked_fixture_tuple_list):
+        serv = self._create_cmd_service(self.service_class, is_save_state=True)
+        _, fixtures = self.run_function_with_mocks(
+            serv.run,
+            mocked_fixture_tuple_list
+        )
+        for item in self.response[self.service_name]:
+            self.assertIn(item['id'],
+                          serv.data[self.service_name])
+        for fixture in fixtures:
+            fixture.mock.assert_called_once()
+
+    def _test_is_preserve_true(self, mocked_fixture_tuple_list):
+        serv = self._create_cmd_service(self.service_class, is_preserve=True)
+        resp, fixtures = self.run_function_with_mocks(
+            serv.list,
+            mocked_fixture_tuple_list
+        )
+        for fixture in fixtures:
+            fixture.mock.assert_called_once()
+        self.assertIn(resp[0], self.response[self.service_name])
+        for rsp in resp:
+            self.assertNotIn(rsp['id'], self.conf_values.values())
+            self.assertNotIn(rsp['name'], self.conf_values.values())
+
+
+class TestSnapshotService(BaseCmdServiceTests):
+
+    service_class = 'SnapshotService'
+    service_name = 'snapshots'
+    response = {
+        "snapshots": [
+            {
+                "status": "available",
+                "metadata": {
+                    "name": "test"
+                },
+                "name": "test-volume-snapshot",
+                "user_id": "40c2102f4a554b848d96b14f3eec39ed",
+                "volume_id": "173f7b48-c4c1-4e70-9acc-086b39073506",
+                "created_at": "2015-11-29T02:25:51.000000",
+                "size": 1,
+                "updated_at": "2015-11-20T05:36:40.000000",
+                "os-extended-snapshot-attributes:progress": "100%",
+                "id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c",
+                "description": "volume snapshot"
+            },
+            {
+                "status": "available",
+                "name": "saved-snapshot",
+                "id": "1ad4c789-7e8w-4dwg-afc5",
+                "description": "snapshot in saved state"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 202),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
+class TestServerService(BaseCmdServiceTests):
+
+    service_class = 'ServerService'
+    service_name = 'servers'
+    response = {
+        "servers": [
+            {
+                "id": "22c91117-08de-4894-9aa9-6ef382400985",
+                "links": [
+                    {
+                        "href": "http://openstack.example.com/v2/6f70-6ef0985",
+                        "rel": "self"
+                    },
+                    {
+                        "href": "http://openstack.example.com/6f70656e7-6ef35",
+                        "rel": "bookmark"
+                    }
+                ],
+                "name": "new-server-test"
+            },
+            {
+                "id": "7a6d4v7w-36ds-4216",
+                "links": [
+                    {
+                        "href": "http://openstack.example.com/v2/6f70-6ef0985",
+                        "rel": "self"
+                    },
+                    {
+                        "href": "http://openstack.example.com/6f70656e7-6ef35",
+                        "rel": "bookmark"
+                    }
+                ],
+                "name": "saved-server"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
+class TestServerGroupService(BaseCmdServiceTests):
+
+    service_class = 'ServerGroupService'
+    service_name = 'server_groups'
+    validate_response = ('tempest.lib.services.compute.server_groups_client'
+                         '.ServerGroupsClient.validate_response')
+
+    response = {
+        "server_groups": [
+            {
+                "id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
+                "name": "test",
+                "policy": "anti-affinity",
+                "rules": {"max_server_per_host": 3},
+                "members": [],
+                "project_id": "6f70656e737461636b20342065766572",
+                "user_id": "fake"
+            },
+            {
+                "id": "as6d5f7g-46ca-475e",
+                "name": "saved-server-group"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.validate_response, 'validate', None),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.validate_response, 'validate', None),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.validate_response, 'validate', None),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200),
+                                     (self.validate_response, 'validate', None)
+                                     ])
+
+
+class TestKeyPairService(BaseCmdServiceTests):
+
+    service_class = 'KeyPairService'
+    service_name = 'keypairs'
+    validate_response = ('tempest.lib.services.compute.keypairs_client'
+                         '.KeyPairsClient.validate_response')
+    response = {
+        "keypairs": [
+            {
+                "keypair": {
+                    "fingerprint": "7e:eb:ab:24:ba:d1:e1:88:ae:9a:fb:66:53:bd",
+                    "name": "keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3",
+                    "type": "ssh",
+                    "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkF\n"
+                }
+            },
+            {
+                "keypair": {
+                    "fingerprint": "7e:eb:ab:24",
+                    "name": "saved-key-pair"
+                }
+            }
+        ]
+    }
+
+    def _test_saved_state_true(self, mocked_fixture_tuple_list):
+        serv = self._create_cmd_service(self.service_class, is_save_state=True)
+        _, fixtures = self.run_function_with_mocks(
+            serv.run,
+            mocked_fixture_tuple_list
+        )
+        for item in self.response[self.service_name]:
+            self.assertTrue(item['keypair']['name'],
+                            serv.data[self.service_name])
+        for fixture in fixtures:
+            fixture.mock.assert_called_once()
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.validate_response, 'validate', None),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.validate_response, 'validate', None),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.validate_response, 'validate', None),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([
+            (self.get_method, self.response, 200),
+            (self.validate_response, 'validate', None)
+        ])
+
+
+class TestVolumeService(BaseCmdServiceTests):
+
+    service_class = 'VolumeService'
+    service_name = 'volumes'
+    response = {
+        "volumes": [
+            {
+                "id": "efa54464-8fab-47cd-a05a-be3e6b396188",
+                "links": [
+                    {
+                        "href": "http://127.0.0.1:37097/v3/89af/volumes/efa54",
+                        "rel": "self"
+                    },
+                    {
+                        "href": "http://127.0.0.1:37097/89af/volumes/efa54464",
+                        "rel": "bookmark"
+                    }
+                ],
+                "name": "volume-name"
+            },
+            {
+                "id": "aa77asdf-1234",
+                "name": "saved-volume"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 202),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
+# Begin network service classes
+class TestNetworkService(BaseCmdServiceTests):
+
+    service_class = 'NetworkService'
+    service_name = 'networks'
+    response = {
+        "networks": [
+            {
+                "admin_state_up": True,
+                "availability_zone_hints": [],
+                "availability_zones": [
+                    "nova"
+                ],
+                "created_at": "2016-03-08T20:19:41",
+                "dns_domain": "my-domain.org.",
+                "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+                "l2_adjacency": False,
+                "mtu": 1500,
+                "name": "net1",
+                "port_security_enabled": True,
+                "project_id": "4fd44f30292945e481c7b8a0c8908869",
+                "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
+                "revision_number": 1,
+                "router:external": False,
+                "shared": False,
+                "status": "ACTIVE",
+                "subnets": [
+                    "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+                ],
+                "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+                "updated_at": "2016-03-08T20:19:41",
+                "vlan_transparent": True,
+                "description": "",
+                "is_default": False
+            },
+            {
+                "id": "6722fc13-4319",
+                "name": "saved-network"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['networks'].append(
+            {
+                "admin_state_up": True,
+                "availability_zone_hints": [],
+                "availability_zones": [
+                    "nova"
+                ],
+                "created_at": "2017-03-08T20:19:41",
+                "dns_domain": "my-domain.org.",
+                "id": cleanup_service.CONF_PUB_NETWORK,
+                "name": "net2",
+                "port_security_enabled": True,
+                "project_id": "4fd44f30292945e481c7b8a0c8908869",
+                "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
+                "revision_number": 1,
+                "status": "ACTIVE",
+                "subnets": [
+                    "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+                ],
+                "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+                "updated_at": "2018-03-08T20:19:41",
+                "vlan_transparent": True,
+                "is_default": False
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+class TestNetworkFloatingIpService(BaseCmdServiceTests):
+
+    service_class = 'NetworkFloatingIpService'
+    service_name = 'floatingips'
+    response = {
+        "floatingips": [
+            {
+                "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,
+                "project_id": "4969c491a3c74ee4af974e6d800c62de",
+                "tenant_id": "4969c491a3c74ee4af974e6d800c62de",
+                "floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57",
+                "fixed_ip_address": "10.0.0.3",
+                "floating_ip_address": "172.24.4.228",
+                "port_id": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
+                "id": "2f245a7b-796b-4f26-9cf9-9e82d248fda7",
+                "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": []
+            },
+            {
+                "id": "9e82d248-408a",
+                "status": "ACTIVE"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
+class TestNetworkRouterService(BaseCmdServiceTests):
+
+    service_class = 'NetworkRouterService'
+    service_name = 'routers'
+    validate_response = ('tempest.lib.services.network.routers_client'
+                         '.RoutersClient.validate_response')
+    response = {
+        "routers": [
+            {
+                "admin_state_up": True,
+                "availability_zone_hints": [],
+                "availability_zones": [
+                    "nova"
+                ],
+                "created_at": "2018-03-19T19:17:04Z",
+                "description": "",
+                "distributed": False,
+                "external_gateway_info": {
+                    "enable_snat": True,
+                    "external_fixed_ips": [
+                        {
+                            "ip_address": "172.24.4.3",
+                            "subnet_id": "b930d7f6-ceb7-40a0-8b81-a425dd994ccf"
+                        },
+                        {
+                            "ip_address": "2001:db8::c",
+                            "subnet_id": "0c56df5d-ace5-46c8-8f4c-45fa4e334d18"
+                        }
+                    ],
+                    "network_id": "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3"
+                },
+                "flavor_id": "f7b14d9a-b0dc-4fbe-bb14-a0f4970a69e0",
+                "ha": False,
+                "id": "915a14a6-867b-4af7-83d1-70efceb146f9",
+                "name": "router2",
+                "revision_number": 1,
+                "routes": [
+                    {
+                        "destination": "179.24.1.0/24",
+                        "nexthop": "172.24.3.99"
+                    }
+                ],
+                "status": "ACTIVE",
+                "updated_at": "2018-03-19T19:17:22Z",
+                "project_id": "0bd18306d801447bb457a46252d82d13",
+                "tenant_id": "0bd18306d801447bb457a46252d82d13",
+                "tags": ["tag1,tag2"]
+            },
+            {
+                "id": "4s5w34hj-id44",
+                "name": "saved-router"
+            }
+        ],
+        # "ports" key is added to the response in order to simplify unit
+        # testing - it's because NetworkRouterService's delete method lists
+        # ports before deleting any router
+        "ports": []
+    }
+
+    def _test_delete(self, mocked_fixture_tuple_list, fail=False):
+        serv = self._create_cmd_service(self.service_class)
+        resp, fixtures = self.run_function_with_mocks(
+            serv.run,
+            mocked_fixture_tuple_list,
+        )
+        for fixture in fixtures:
+            if fail is False and fixture.mock.return_value == 'exception':
+                fixture.mock.assert_not_called()
+            elif self.service_name in self.saved_state.keys():
+                fixture.mock.assert_called()
+                for key in self.saved_state[self.service_name].keys():
+                    self.assertNotIn(key, fixture.mock.call_args[0][0])
+        self.assertFalse(serv.data)
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['routers'].append(
+            {
+                "admin_state_up": True,
+                "availability_zone_hints": [],
+                "availability_zones": [
+                    "nova"
+                ],
+                "created_at": "2018-03-19T19:17:04Z",
+                "id": cleanup_service.CONF_PUB_ROUTER,
+                "name": "router-preserve",
+                "status": "ACTIVE",
+                "updated_at": "2018-03-19T19:17:22Z",
+                "project_id": "0bd18306d801447bb457a46252d82d13",
+                "tenant_id": "0bd18306d801447bb457a46252d82d13",
+                "tags": ["tag1,tag2"]
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+class TestNetworkMeteringLabelRuleService(BaseCmdServiceTests):
+
+    service_class = 'NetworkMeteringLabelRuleService'
+    service_name = 'metering_label_rules'
+    response = {
+        "metering_label_rules": [
+            {
+                "remote_ip_prefix": "20.0.0.0/24",
+                "direction": "ingress",
+                "metering_label_id": "e131d186-b02d-4c0b-83d5-0c0725c4f812",
+                "id": "9536641a-7d14-4dc5-afaf-93a973ce0eb8",
+                "excluded": False
+            },
+            {
+                "direction": "ingress",
+                "id": "93a973ce-4dc5"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
+class TestNetworkMeteringLabelService(BaseCmdServiceTests):
+
+    service_class = 'NetworkMeteringLabelService'
+    service_name = 'metering_labels'
+    response = {
+        "metering_labels": [
+            {
+                "project_id": "45345b0ee1ea477fac0f541b2cb79cd4",
+                "tenant_id": "45345b0ee1ea477fac0f541b2cb79cd4",
+                "description": "label1 description",
+                "name": "label1",
+                "id": "a6700594-5b7a-4105-8bfe-723b346ce866",
+                "shared": False
+            },
+            {
+                "name": "saved-label",
+                "id": "723b346ce866-4c7q",
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
+class TestNetworkPortService(BaseCmdServiceTests):
+
+    service_class = 'NetworkPortService'
+    service_name = 'ports'
+    response = {
+        "ports": [
+            {
+                "admin_state_up": True,
+                "allowed_address_pairs": [],
+                "created_at": "2016-03-08T20:19:41",
+                "description": "",
+                "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
+                "device_owner": "",
+                "dns_assignment": {
+                    "hostname": "myport",
+                    "ip_address": "172.24.4.2",
+                    "fqdn": "myport.my-domain.org"
+                },
+                "dns_domain": "my-domain.org.",
+                "dns_name": "myport",
+                "extra_dhcp_opts": [
+                    {
+                        "opt_value": "pxelinux.0",
+                        "ip_version": 4,
+                        "opt_name": "bootfile-name"
+                    }
+                ],
+                "fixed_ips": [
+                    {
+                        "ip_address": "172.24.4.2",
+                        "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062"
+                    }
+                ],
+                "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b",
+                "ip_allocation": "immediate",
+                "mac_address": "fa:16:3e:58:42:ed",
+                "name": "test_port",
+                "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3",
+                "project_id": "",
+                "revision_number": 1,
+                "security_groups": [],
+                "status": "ACTIVE",
+                "tags": ["tag1,tag2"],
+                "tenant_id": "",
+                "updated_at": "2016-03-08T20:19:41",
+                "qos_policy_id": "29d5e02e-d5ab-4929-bee4-4a9fc12e22ae",
+                "port_security_enabled": False
+            },
+            {
+                "id": "aa74aa4v-741a",
+                "name": "saved-port",
+                "device_owner": ""
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['ports'].append(
+            {
+                "created_at": "2018-03-08T20:19:41",
+                "description": "",
+                "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
+                "device_owner": "compute:router_gateway",
+                "id": "d80b1a3b-4fc1-49f3-952e-1fdy1ws542",
+                "ip_allocation": "immediate",
+                "mac_address": "fa:16:3e:58:42:ed",
+                "name": "preserve_port",
+                "network_id": cleanup_service.CONF_PUB_NETWORK,
+                "project_id": "",
+                "security_groups": [],
+                "status": "ACTIVE",
+                "tags": ["tag1,tag2"],
+                "tenant_id": "",
+                "updated_at": "2018-03-08T20:19:41",
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+class TestNetworkSecGroupService(BaseCmdServiceTests):
+
+    service_class = 'NetworkSecGroupService'
+    service_name = 'security_groups'
+    response = {
+        "security_groups": [
+            {
+                "description": "default",
+                "id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+                "name": "test",
+                "security_group_rules": [
+                    {
+                        "direction": "egress",
+                        "ethertype": "IPv6",
+                        "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff",
+                        "security_group_id": "85cc3048-abc3-43cc-89b3-3773414",
+                        "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+                        "revision_number": 1,
+                        "tags": ["tag1,tag2"],
+                        "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+                        "created_at": "2018-03-19T19:16:56Z",
+                        "updated_at": "2018-03-19T19:16:56Z",
+                        "description": ""
+                    }
+                ]
+            },
+            {
+                "id": "7q844add-3697",
+                "name": "saved-sec-group"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['security_groups'].append(
+            {
+                "description": "default",
+                "id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+                "name": "test",
+                "security_group_rules": [
+                    {
+                        "direction": "egress",
+                        "ethertype": "IPv6",
+                        "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff",
+                        "security_group_id": "85cc3048-abc3-43cc-89b3-3773414",
+                        "project_id": cleanup_service.CONF_PROJECTS[0],
+                        "revision_number": 1,
+                        "tags": ["tag1,tag2"],
+                        "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+                        "created_at": "2018-03-19T19:16:56Z",
+                        "updated_at": "2018-03-19T19:16:56Z",
+                        "description": ""
+                    }
+                ]
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+class TestNetworkSubnetService(BaseCmdServiceTests):
+
+    service_class = 'NetworkSubnetService'
+    service_name = 'subnets'
+    response = {
+        "subnets": [
+            {
+                "name": "private-subnet",
+                "enable_dhcp": True,
+                "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+                "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "dns_nameservers": [],
+                "allocation_pools": [
+                    {
+                        "start": "10.0.0.2",
+                        "end": "10.0.0.254"
+                    }
+                ],
+                "host_routes": [],
+                "ip_version": 4,
+                "gateway_ip": "10.0.0.1",
+                "cidr": "10.0.0.0/24",
+                "id": "08eae331-0402-425a-923c-34f7cfe39c1b",
+                "created_at": "2016-10-10T14:35:34Z",
+                "revision_number": 2,
+                "service_types": [],
+                "tags": ["tag1,tag2"],
+                "updated_at": "2016-10-10T14:35:34Z"
+            },
+            {
+                "id": "55ttda4a-2584",
+                "name": "saved-subnet"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['subnets'].append(
+            {
+                "name": "public-subnet",
+                "network_id": cleanup_service.CONF_PUB_NETWORK,
+                "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "ip_version": 4,
+                "gateway_ip": "10.0.0.1",
+                "cidr": "10.0.0.0/24",
+                "id": "08eae331-0402-425a-923c-34f7cfe39c1b",
+                "created_at": "2018-10-10T14:35:34Z",
+                "service_types": [],
+                "tags": ["tag1,tag2"],
+                "updated_at": "2018-10-10T14:35:34Z"
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+class TestNetworkSubnetPoolsService(BaseCmdServiceTests):
+
+    service_class = 'NetworkSubnetPoolsService'
+    service_name = 'subnetpools'
+    response = {
+        "subnetpools": [
+            {
+                "min_prefixlen": "64",
+                "default_prefixlen": "64",
+                "id": "03f761e6-eee0-43fc-a921-8acf64c14988",
+                "max_prefixlen": "64",
+                "name": "my-subnet-pool-ipv6",
+                "is_default": False,
+                "project_id": "9fadcee8aa7c40cdb2114fff7d569c08",
+                "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08",
+                "prefixes": [
+                    "2001:db8:0:2::/64",
+                    "2001:db8::/63"
+                ],
+                "ip_version": 6,
+                "shared": False,
+                "description": "",
+                "created_at": "2016-03-08T20:19:41",
+                "updated_at": "2016-03-08T20:19:41",
+                "revision_number": 2,
+                "tags": ["tag1,tag2"]
+            },
+            {
+                "id": "8acf64c1-43fc",
+                "name": "saved-subnet-pool"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['subnetpools'].append(
+            {
+                "min_prefixlen": "64",
+                "default_prefixlen": "64",
+                "id": "9acf64c1-43fc",
+                "name": "preserve-pool",
+                "project_id": cleanup_service.CONF_PROJECTS[0],
+                "created_at": "2016-03-08T20:19:41",
+                "updated_at": "2016-03-08T20:19:41"
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+# begin global services
+class TestDomainService(BaseCmdServiceTests):
+
+    service_class = 'DomainService'
+    service_name = 'domains'
+    response = {
+        "domains": [
+            {
+                "description": "Destroy all humans",
+                "enabled": True,
+                "id": "5a75994a3",
+                "links": {
+                    "self": "http://example.com/identity/v3/domains/5a75994a3"
+                },
+                "name": "Sky_net"
+            },
+            {
+                "description": "Owns users and tenants on Identity API",
+                "enabled": False,
+                "id": "default",
+                "links": {
+                    "self": "http://example.com/identity/v3/domains/default"
+                },
+                "name": "Default"
+            }
+        ]
+    }
+
+    mock_update = ("tempest.lib.services.identity.v3."
+                   "domains_client.DomainsClient.update_domain")
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None),
+                       (self.mock_update, 'update', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None),
+                       (self.mock_update, 'update', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
+class TestProjectsService(BaseCmdServiceTests):
+
+    service_class = 'ProjectService'
+    service_name = 'projects'
+    response = {
+        "projects": [
+            {
+                "is_domain": False,
+                "description": None,
+                "domain_id": "default",
+                "enabled": True,
+                "id": "f38ohgp93jj032",
+                "links": {
+                    "self": "http://example.com/identity/v3/projects"
+                            "/f38ohgp93jj032"
+                },
+                "name": "manhattan",
+                "parent_id": None
+            },
+            {
+                "is_domain": False,
+                "description": None,
+                "domain_id": "default",
+                "enabled": True,
+                "id": "098f89d3292ri4jf4",
+                "links": {
+                    "self": "http://example.com/identity/v3/projects"
+                            "/098f89d3292ri4jf4"
+                },
+                "name": "Apollo",
+                "parent_id": None
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['projects'].append(
+            {
+                "is_domain": False,
+                "description": None,
+                "domain_id": "default",
+                "enabled": True,
+                "id": "r343q98h09f3092",
+                "links": {
+                    "self": "http://example.com/identity/v3/projects"
+                            "/r343q98h09f3092"
+                },
+                "name": cleanup_service.CONF_PROJECTS[0],
+                "parent_id": None
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+class TestImagesService(BaseCmdServiceTests):
+
+    service_class = 'ImageService'
+    service_name = 'images'
+    response = {
+        "images": [
+            {
+                "status": "ACTIVE",
+                "name": "stratus-0.3.2-x86_64-disk",
+                "id": "34yhwr-4t3q",
+                "updated": "2014-11-03T16:40:10Z",
+                "links": [{
+                    "href": "http://openstack.ex.com/v2/openstack/images/"
+                    "34yhwr-4t3q",
+                    "rel": "self"}],
+                "created": "2014-10-30T08:23:39Z",
+                "minDisk": 0,
+                "minRam": 0,
+                "progress": 0,
+                "metadata": {},
+            },
+            {
+                "status": "ACTIVE",
+                "name": "cirros-0.3.2-x86_64-disk",
+                "id": "1bea47ed-f6a9",
+                "updated": "2014-11-03T16:40:10Z",
+                "links": [{
+                    "href": "http://openstack.ex.com/v2/openstack/images/"
+                    "1bea47ed-f6a9",
+                    "rel": "self"}],
+                "created": "2014-10-30T08:23:39Z",
+                "minDisk": 0,
+                "minRam": 0,
+                "progress": 0,
+                "metadata": {},
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['images'].append(
+            {
+                "status": "ACTIVE",
+                "name": "cirros-0.3.2-x86_64-disk",
+                "id": cleanup_service.CONF_IMAGES[0],
+                "updated": "2014-11-03T16:40:10Z",
+                "links": [{
+                    "href": "http://openstack.ex.com/v2/openstack/images/"
+                    "None",
+                    "rel": "self"}],
+                "created": "2014-10-30T08:23:39Z",
+                "minDisk": 0,
+                "minRam": 0,
+                "progress": 0,
+                "metadata": {},
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+class TestFlavorService(BaseCmdServiceTests):
+
+    service_class = 'FlavorService'
+    service_name = 'flavors'
+    response = {
+        "flavors": [
+            {
+                "disk": 1,
+                "id": "42",
+                "links": [{
+                    "href": "http://openstack.ex.com/v2/openstack/flavors/1",
+                    "rel": "self"}, {
+                    "href": "http://openstack.ex.com/openstack/flavors/1",
+                    "rel": "bookmark"}],
+                "name": "m1.tiny",
+                "ram": 512,
+                "swap": 1,
+                "vcpus": 1
+            },
+            {
+                "disk": 2,
+                "id": "13",
+                "links": [{
+                    "href": "http://openstack.ex.com/v2/openstack/flavors/2",
+                    "rel": "self"}, {
+                    "href": "http://openstack.ex.com/openstack/flavors/2",
+                    "rel": "bookmark"}],
+                "name": "m1.tiny",
+                "ram": 512,
+                "swap": 1,
+                "vcpus": 1
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 202),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['flavors'].append(
+            {
+                "disk": 3,
+                "id": cleanup_service.CONF_FLAVORS[0],
+                "links": [{
+                    "href": "http://openstack.ex.com/v2/openstack/flavors/3",
+                    "rel": "self"}, {
+                    "href": "http://openstack.ex.com/openstack/flavors/3",
+                    "rel": "bookmark"}],
+                "name": "m1.tiny",
+                "ram": 512,
+                "swap": 1,
+                "vcpus": 1
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
+class TestRoleService(BaseCmdServiceTests):
+
+    service_class = 'RoleService'
+    service_name = 'roles'
+    response = {
+        "roles": [
+            {
+                "domain_id": "FakeDomain",
+                "id": "3efrt74r45hn",
+                "name": "president",
+                "links": {
+                    "self": "http://ex.com/identity/v3/roles/3efrt74r45hn"
+                }
+            },
+            {
+                "domain_id": 'FakeDomain',
+                "id": "39ruo5sdk040",
+                "name": "vice-p",
+                "links": {
+                    "self": "http://ex.com/identity/v3/roles/39ruo5sdk040"
+                }
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
+class TestUserService(BaseCmdServiceTests):
+
+    service_class = 'UserService'
+    service_name = 'users'
+    response = {
+        "users": [
+            {
+                "domain_id": "TempestDomain",
+                "enabled": True,
+                "id": "e812fb332456423fdv1b1320121qwe2",
+                "links": {
+                    "self": "http://example.com/identity/v3/users/"
+                            "e812fb332456423fdv1b1320121qwe2",
+                },
+                "name": "Thunder",
+                "password_expires_at": "3102-11-06T15:32:17.000000",
+            },
+            {
+                "domain_id": "TempestDomain",
+                "enabled": True,
+                "id": "32rwef64245tgr20121qw324bgg",
+                "links": {
+                    "self": "http://example.com/identity/v3/users/"
+                            "32rwef64245tgr20121qw324bgg",
+                },
+                "name": "Lightning",
+                "password_expires_at": "1893-11-06T15:32:17.000000",
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['users'].append(
+            {
+                "domain_id": "TempestDomain",
+                "enabled": True,
+                "id": "23ads5tg3rtrhe30121qwhyth",
+                "links": {
+                    "self": "http://example.com/identity/v3/users/"
+                            "23ads5tg3rtrhe30121qwhyth",
+                },
+                "name": cleanup_service.CONF_USERS[0],
+                "password_expires_at": "1893-11-06T15:32:17.000000",
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 0485e14..0e00d94 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -21,13 +21,23 @@
 
 import fixtures
 import mock
+import six
 
 from tempest.cmd import run
+from tempest.cmd import workspace
+from tempest import config
+from tempest.lib.common.utils import data_utils
 from tempest.tests import base
 
+if six.PY2:
+    # Python 2 has not FileNotFoundError exception
+    FileNotFoundError = IOError
+
 DEVNULL = open(os.devnull, 'wb')
 atexit.register(DEVNULL.close)
 
+CONF = config.CONF
+
 
 class TestTempestRun(base.TestCase):
 
@@ -35,69 +45,30 @@
         super(TestTempestRun, self).setUp()
         self.run_cmd = run.TempestRun(None, None)
 
-    def test_build_options(self):
-        args = mock.Mock(spec=argparse.Namespace)
-        setattr(args, "subunit", True)
-        setattr(args, "parallel", False)
-        setattr(args, "concurrency", 10)
-        setattr(args, "load_list", '')
-        options = self.run_cmd._build_options(args)
-        self.assertEqual(['--subunit',
-                          '--concurrency=10'],
-                         options)
-
     def test__build_regex_default(self):
         args = mock.Mock(spec=argparse.Namespace)
         setattr(args, 'smoke', False)
         setattr(args, 'regex', '')
-        setattr(args, 'whitelist_file', None)
-        setattr(args, 'blacklist_file', None)
-        self.assertEqual('', self.run_cmd._build_regex(args))
+        self.assertIsNone(None, self.run_cmd._build_regex(args))
 
     def test__build_regex_smoke(self):
         args = mock.Mock(spec=argparse.Namespace)
         setattr(args, "smoke", True)
         setattr(args, 'regex', '')
-        setattr(args, 'whitelist_file', None)
-        setattr(args, 'blacklist_file', None)
-        self.assertEqual('smoke', self.run_cmd._build_regex(args))
+        self.assertEqual(['smoke'], self.run_cmd._build_regex(args))
 
     def test__build_regex_regex(self):
         args = mock.Mock(spec=argparse.Namespace)
         setattr(args, 'smoke', False)
         setattr(args, "regex", 'i_am_a_fun_little_regex')
-        setattr(args, 'whitelist_file', None)
-        setattr(args, 'blacklist_file', None)
-        self.assertEqual('i_am_a_fun_little_regex',
+        self.assertEqual(['i_am_a_fun_little_regex'],
                          self.run_cmd._build_regex(args))
 
-    def test__build_whitelist_file(self):
+    def test__build_regex_smoke_regex(self):
         args = mock.Mock(spec=argparse.Namespace)
-        setattr(args, 'smoke', False)
-        setattr(args, 'regex', None)
-        self.tests = tempfile.NamedTemporaryFile(
-            prefix='whitelist', delete=False)
-        self.tests.write(b"volume \n compute")
-        self.tests.close()
-        setattr(args, 'whitelist_file', self.tests.name)
-        setattr(args, 'blacklist_file', None)
-        self.assertEqual("volume|compute",
-                         self.run_cmd._build_regex(args))
-        os.unlink(self.tests.name)
-
-    def test__build_blacklist_file(self):
-        args = mock.Mock(spec=argparse.Namespace)
-        setattr(args, 'smoke', False)
-        setattr(args, 'regex', None)
-        self.tests = tempfile.NamedTemporaryFile(
-            prefix='blacklist', delete=False)
-        self.tests.write(b"volume \n compute")
-        self.tests.close()
-        setattr(args, 'whitelist_file', None)
-        setattr(args, 'blacklist_file', self.tests.name)
-        self.assertEqual("^((?!compute|volume).)*$",
-                         self.run_cmd._build_regex(args))
-        os.unlink(self.tests.name)
+        setattr(args, "smoke", True)
+        setattr(args, 'regex', 'i_am_a_fun_little_regex')
+        self.assertEqual(['smoke'], self.run_cmd._build_regex(args))
 
 
 class TestRunReturnCode(base.TestCase):
@@ -109,13 +80,13 @@
         self.test_dir = os.path.join(self.directory, 'tests')
         os.mkdir(self.test_dir)
         # Setup Test files
-        self.testr_conf_file = os.path.join(self.directory, '.testr.conf')
+        self.stestr_conf_file = os.path.join(self.directory, '.stestr.conf')
         self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg')
         self.passing_file = os.path.join(self.test_dir, 'test_passing.py')
         self.failing_file = os.path.join(self.test_dir, 'test_failing.py')
         self.init_file = os.path.join(self.test_dir, '__init__.py')
         self.setup_py = os.path.join(self.directory, 'setup.py')
-        shutil.copy('tempest/tests/files/testr-conf', self.testr_conf_file)
+        shutil.copy('tempest/tests/files/testr-conf', self.stestr_conf_file)
         shutil.copy('tempest/tests/files/passing-tests', self.passing_file)
         shutil.copy('tempest/tests/files/failing-tests', self.failing_file)
         shutil.copy('setup.py', self.setup_py)
@@ -132,31 +103,199 @@
         msg = ("Running %s got an unexpected returncode\n"
                "Stdout: %s\nStderr: %s" % (' '.join(cmd), out, err))
         self.assertEqual(p.returncode, expected, msg)
+        return out, err
 
     def test_tempest_run_passes(self):
-        # Git init is required for the pbr testr command. pbr requires a git
-        # version or an sdist to work. so make the test directory a git repo
-        # too.
-        subprocess.call(['git', 'init'], stderr=DEVNULL)
         self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0)
 
-    def test_tempest_run_passes_with_testrepository(self):
-        # Git init is required for the pbr testr command. pbr requires a git
-        # version or an sdist to work. so make the test directory a git repo
-        # too.
-        subprocess.call(['git', 'init'], stderr=DEVNULL)
-        subprocess.call(['testr', 'init'])
+    def test_tempest_run_passes_with_stestr_repository(self):
+        subprocess.call(['stestr', 'init'])
         self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0)
 
+    def test_tempest_run_failing(self):
+        self.assertRunExit(['tempest', 'run', '--regex', 'failing'], 1)
+
+    def test_tempest_run_failing_with_stestr_repository(self):
+        subprocess.call(['stestr', 'init'])
+        self.assertRunExit(['tempest', 'run', '--regex', 'failing'], 1)
+
+    def test_tempest_run_blackregex_failing(self):
+        self.assertRunExit(['tempest', 'run', '--black-regex', 'failing'], 0)
+
+    def test_tempest_run_blackregex_failing_with_stestr_repository(self):
+        subprocess.call(['stestr', 'init'])
+        self.assertRunExit(['tempest', 'run', '--black-regex', 'failing'], 0)
+
+    def test_tempest_run_blackregex_passing(self):
+        self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1)
+
+    def test_tempest_run_blackregex_passing_with_stestr_repository(self):
+        subprocess.call(['stestr', 'init'])
+        self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1)
+
     def test_tempest_run_fails(self):
-        # Git init is required for the pbr testr command. pbr requires a git
-        # version or an sdist to work. so make the test directory a git repo
-        # too.
-        subprocess.call(['git', 'init'], stderr=DEVNULL)
         self.assertRunExit(['tempest', 'run'], 1)
 
+    def test_run_list(self):
+        subprocess.call(['stestr', 'init'])
+        out, err = self.assertRunExit(['tempest', 'run', '-l'], 0)
+        tests = out.split()
+        tests = sorted([six.text_type(x.rstrip()) for x in tests if x])
+        result = [
+            six.text_type('tests.test_failing.FakeTestClass.test_pass'),
+            six.text_type('tests.test_failing.FakeTestClass.test_pass_list'),
+            six.text_type('tests.test_passing.FakeTestClass.test_pass'),
+            six.text_type('tests.test_passing.FakeTestClass.test_pass_list'),
+        ]
+        # NOTE(mtreinish): on python 3 the subprocess prints b'' around
+        # stdout.
+        if six.PY3:
+            result = ["b\'" + x + "\'" for x in result]
+        self.assertEqual(result, tests)
+
+    def test_tempest_run_with_whitelist(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        whitelist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(whitelist_file.close)
+        whitelist_file.write('passing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path], 0)
+
+    def test_tempest_run_with_whitelist_regex_include_pass_check_fail(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        whitelist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(whitelist_file.close)
+        whitelist_file.write('passing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path,
+                            '--regex', 'fail'], 1)
+
+    def test_tempest_run_with_whitelist_regex_include_pass_check_pass(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        whitelist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(whitelist_file.close)
+        whitelist_file.write('passing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path,
+                            '--regex', 'passing'], 0)
+
+    def test_tempest_run_with_whitelist_regex_include_fail_check_pass(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        whitelist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(whitelist_file.close)
+        whitelist_file.write('failing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path,
+                            '--regex', 'pass'], 1)
+
+    def test_tempest_run_passes_with_config_file(self):
+        self.assertRunExit(['tempest', 'run',
+                            '--config-file', self.stestr_conf_file,
+                            '--regex', 'passing'], 0)
+
+    def test_tempest_run_with_blacklist_failing(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        blacklist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(blacklist_file.close)
+        blacklist_file.write('failing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path], 0)
+
+    def test_tempest_run_with_blacklist_passing(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        blacklist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(blacklist_file.close)
+        blacklist_file.write('passing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path], 1)
+
+    def test_tempest_run_with_blacklist_regex_exclude_fail_check_pass(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        blacklist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(blacklist_file.close)
+        blacklist_file.write('failing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path,
+                            '--regex', 'pass'], 0)
+
+    def test_tempest_run_with_blacklist_regex_exclude_pass_check_pass(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        blacklist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(blacklist_file.close)
+        blacklist_file.write('passing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path,
+                            '--regex', 'pass'], 1)
+
+    def test_tempest_run_with_blacklist_regex_exclude_pass_check_fail(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        blacklist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(blacklist_file.close)
+        blacklist_file.write('passing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path,
+                            '--regex', 'fail'], 1)
+
+
+class TestConfigPathCheck(base.TestCase):
+    def setUp(self):
+        super(TestConfigPathCheck, self).setUp()
+        self.run_cmd = run.TempestRun(None, None)
+
+    def test_tempest_run_set_config_path(self):
+        # Note: (mbindlish) This test is created for the bug id: 1783751
+        # Checking TEMPEST_CONFIG_DIR and TEMPEST_CONFIG is actually
+        # getting set in os environment when some data has passed to
+        # set the environment.
+
+        _, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+
+        self.run_cmd._set_env(path)
+        self.assertEqual(path, CONF._path)
+        self.assertIn('TEMPEST_CONFIG_DIR', os.environ)
+        self.assertEqual(path, os.path.join(os.environ['TEMPEST_CONFIG_DIR'],
+                                            os.environ['TEMPEST_CONFIG']))
+
+    def test_tempest_run_set_config_no_exist_path(self):
+        path = "fake/path"
+        self.assertRaisesRegex(FileNotFoundError,
+                               'Config file: .* doesn\'t exist',
+                               self.run_cmd._set_env, path)
+
+    def test_tempest_run_no_config_path(self):
+        # Note: (mbindlish) This test is created for the bug id: 1783751
+        # Checking TEMPEST_CONFIG_DIR and TEMPEST_CONFIG should have no value
+        # in os environment when no data has passed to set the environment.
+
+        self.run_cmd._set_env("")
+        self.assertFalse(CONF._path)
+        self.assertNotIn('TEMPEST_CONFIG_DIR', os.environ)
+        self.assertNotIn('TEMPEST_CONFIG', os.environ)
+
 
 class TestTakeAction(base.TestCase):
+    def setUp(self):
+        super(TestTakeAction, self).setUp()
+        self.name = data_utils.rand_name('workspace')
+        self.path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.path, ignore_errors=True)
+        store_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, store_dir, ignore_errors=True)
+        self.store_file = os.path.join(store_dir, 'workspace.yaml')
+        self.workspace_manager = workspace.WorkspaceManager(
+            path=self.store_file)
+        self.workspace_manager.register_new_workspace(self.name, self.path)
+
+    def _setup_test_dirs(self):
+        self.directory = tempfile.mkdtemp(prefix='tempest-unit')
+        self.addCleanup(shutil.rmtree, self.directory, ignore_errors=True)
+        self.test_dir = os.path.join(self.directory, 'tests')
+        os.mkdir(self.test_dir)
+        # Change directory, run wrapper and check result
+        self.addCleanup(os.chdir, os.path.abspath(os.curdir))
+        os.chdir(self.directory)
+
     def test_workspace_not_registered(self):
         class Exception_(Exception):
             pass
@@ -183,3 +322,118 @@
         self.assertRaises(Exception_, tempest_run.take_action, parsed_args)
         exit_msg = m_exit.call_args[0][0]
         self.assertIn(workspace, exit_msg)
+
+    def test_config_file_specified(self):
+        self._setup_test_dirs()
+        _, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+
+        parsed_args.workspace = None
+        parsed_args.state = None
+        parsed_args.list_tests = False
+        parsed_args.config_file = path
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+
+    def test_no_config_file_no_workspace_no_state(self):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+
+        parsed_args.workspace = None
+        parsed_args.state = None
+        parsed_args.list_tests = False
+        parsed_args.config_file = ''
+
+        with mock.patch('stestr.commands.run_command'):
+            self.assertRaises(SystemExit, tempest_run.take_action, parsed_args)
+
+    def test_config_file_workspace_registered(self):
+        self._setup_test_dirs()
+        _, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.workspace = self.name
+        parsed_args.workspace_path = self.store_file
+        parsed_args.state = None
+        parsed_args.list_tests = False
+        parsed_args.config_file = path
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+
+    @mock.patch('tempest.cmd.run.TempestRun._init_state')
+    def test_workspace_registered_no_config_no_state(self, mock_init_state):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.workspace = self.name
+        parsed_args.workspace_path = self.store_file
+        parsed_args.state = None
+        parsed_args.list_tests = False
+        parsed_args.config_file = ''
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+        mock_init_state.assert_not_called()
+
+    @mock.patch('tempest.cmd.run.TempestRun._init_state')
+    def test_no_config_file_no_workspace_state_true(self, mock_init_state):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+
+        parsed_args.workspace = None
+        parsed_args.state = True
+        parsed_args.list_tests = False
+        parsed_args.config_file = ''
+
+        with mock.patch('stestr.commands.run_command'):
+            self.assertRaises(SystemExit, tempest_run.take_action, parsed_args)
+        mock_init_state.assert_not_called()
+
+    @mock.patch('tempest.cmd.run.TempestRun._init_state')
+    def test_workspace_registered_no_config_state_true(self, mock_init_state):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.workspace = self.name
+        parsed_args.workspace_path = self.store_file
+        parsed_args.state = True
+        parsed_args.list_tests = False
+        parsed_args.config_file = ''
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+        mock_init_state.assert_called()
+
+    @mock.patch('tempest.cmd.run.TempestRun._init_state')
+    def test_no_workspace_config_file_state_true(self, mock_init_state):
+        self._setup_test_dirs()
+        _, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.workspace = None
+        parsed_args.workspace_path = self.store_file
+        parsed_args.state = True
+        parsed_args.list_tests = False
+        parsed_args.config_file = path
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+        mock_init_state.assert_called()
diff --git a/tempest/tests/cmd/test_saved_state_json.json b/tempest/tests/cmd/test_saved_state_json.json
new file mode 100644
index 0000000..5c55331
--- /dev/null
+++ b/tempest/tests/cmd/test_saved_state_json.json
@@ -0,0 +1,16 @@
+{
+  "domains": {
+    "default": "Default"
+  },
+  "flavors": {
+    "1": "m1.tiny"
+  },
+  "images": {},
+  "projects": {
+    "268bcb63488b4aa2942ecaac0f85ed62": "demo"
+  },
+  "roles": {},
+  "users": {
+    "023e65a5922a454585a91c6af8310968": "demo"
+  }
+}
diff --git a/tempest/tests/cmd/test_subunit_describe_calls.py b/tempest/tests/cmd/test_subunit_describe_calls.py
index 5f3d770..cb34ba6 100644
--- a/tempest/tests/cmd/test_subunit_describe_calls.py
+++ b/tempest/tests/cmd/test_subunit_describe_calls.py
@@ -33,15 +33,42 @@
         p.communicate()
         self.assertEqual(0, p.returncode)
 
+    def test_verbose(self):
+        subunit_file = os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            'sample_streams/calls.subunit')
+        p = subprocess.Popen([
+            'subunit-describe-calls', '-s', subunit_file,
+            '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        stdout = p.communicate()
+        self.assertEqual(0, p.returncode)
+        self.assertIn(b'- request headers:', stdout[0])
+        self.assertIn(b'- request body:', stdout[0])
+        self.assertIn(b'- response headers:', stdout[0])
+        self.assertIn(b'- response body:', stdout[0])
+
     def test_return_code_no_output(self):
         subunit_file = os.path.join(
             os.path.dirname(os.path.abspath(__file__)),
             'sample_streams/calls.subunit')
         p = subprocess.Popen([
             'subunit-describe-calls', '-s', subunit_file],
-            stdin=subprocess.PIPE)
-        p.communicate()
+            stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        stdout = p.communicate()
         self.assertEqual(0, p.returncode)
+        self.assertIn(b'foo', stdout[0])
+        self.assertIn(b'- 200 POST request for Nova to v2.1/<id>/',
+                      stdout[0])
+        self.assertIn(b'- 200 DELETE request for Nova to v2.1/<id>/',
+                      stdout[0])
+        self.assertIn(b'- 200 GET request for Nova to v2.1/<id>/',
+                      stdout[0])
+        self.assertIn(b'- 404 DELETE request for Nova to v2.1/<id>/',
+                      stdout[0])
+        self.assertNotIn(b'- request headers:', stdout[0])
+        self.assertNotIn(b'- request body:', stdout[0])
+        self.assertNotIn(b'- response headers:', stdout[0])
+        self.assertNotIn(b'- response body:', stdout[0])
 
     def test_parse(self):
         subunit_file = os.path.join(
diff --git a/tempest/tests/cmd/test_tempest_init.py b/tempest/tests/cmd/test_tempest_init.py
index 79510be..9042b12 100644
--- a/tempest/tests/cmd/test_tempest_init.py
+++ b/tempest/tests/cmd/test_tempest_init.py
@@ -22,21 +22,21 @@
 
 class TestTempestInit(base.TestCase):
 
-    def test_generate_testr_conf(self):
+    def test_generate_stestr_conf(self):
         # Create fake conf dir
         conf_dir = self.useFixture(fixtures.TempDir())
 
         init_cmd = init.TempestInit(None, None)
-        init_cmd.generate_testr_conf(conf_dir.path)
+        init_cmd.generate_stestr_conf(conf_dir.path)
 
         # Generate expected file contents
         top_level_path = os.path.dirname(os.path.dirname(init.__file__))
         discover_path = os.path.join(top_level_path, 'test_discover')
-        testr_conf_file = init.TESTR_CONF % (top_level_path, discover_path)
+        stestr_conf_file = init.STESTR_CONF % (discover_path, top_level_path)
 
-        conf_path = conf_dir.join('.testr.conf')
+        conf_path = conf_dir.join('.stestr.conf')
         with open(conf_path, 'r') as conf_file:
-            self.assertEqual(conf_file.read(), testr_conf_file)
+            self.assertEqual(conf_file.read(), stestr_conf_file)
 
     def test_generate_sample_config(self):
         local_dir = self.useFixture(fixtures.TempDir())
@@ -125,18 +125,18 @@
         lock_path = os.path.join(fake_local_dir.path, 'tempest_lock')
         etc_dir = os.path.join(fake_local_dir.path, 'etc')
         log_dir = os.path.join(fake_local_dir.path, 'logs')
-        testr_dir = os.path.join(fake_local_dir.path, '.testrepository')
+        stestr_dir = os.path.join(fake_local_dir.path, '.stestr')
         self.assertTrue(os.path.isdir(lock_path))
         self.assertTrue(os.path.isdir(etc_dir))
         self.assertTrue(os.path.isdir(log_dir))
-        self.assertTrue(os.path.isdir(testr_dir))
+        self.assertTrue(os.path.isdir(stestr_dir))
         # Assert file creation
         fake_file_moved = os.path.join(etc_dir, 'conf_file.conf')
         local_conf_file = os.path.join(etc_dir, 'tempest.conf')
-        local_testr_conf = os.path.join(fake_local_dir.path, '.testr.conf')
+        local_stestr_conf = os.path.join(fake_local_dir.path, '.stestr.conf')
         self.assertTrue(os.path.isfile(fake_file_moved))
         self.assertTrue(os.path.isfile(local_conf_file))
-        self.assertTrue(os.path.isfile(local_testr_conf))
+        self.assertTrue(os.path.isfile(local_stestr_conf))
 
     def test_take_action_fails(self):
         class ParsedArgs(object):
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 8641b63..8dbba38 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -12,11 +12,14 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import os
+
 import fixtures
 import mock
 from oslo_serialization import jsonutils as json
 
 from tempest import clients
+from tempest.cmd import init
 from tempest.cmd import verify_tempest_config
 from tempest.common import credentials_factory
 from tempest import config
@@ -225,7 +228,7 @@
         # This test verifies that wrong config api_v2 = True is detected
         class FakeClient(object):
             def get_versions(self):
-                return (None, ['v1.0'])
+                return (None, ['v1.1'])
 
         fake_os = mock.MagicMock()
         fake_module = mock.MagicMock()
@@ -234,8 +237,8 @@
         with mock.patch.object(verify_tempest_config,
                                'print_and_or_update') as print_mock:
             verify_tempest_config.verify_glance_api_versions(fake_os, True)
-        print_mock.assert_called_once_with('api_v2', 'image-feature-enabled',
-                                           False, True)
+        print_mock.assert_called_with('api_v2', 'image-feature-enabled',
+                                      False, True)
 
     def test_verify_glance_version_no_v2_with_v1_0(self):
         # This test verifies that wrong config api_v2 = True is detected
@@ -250,8 +253,8 @@
         with mock.patch.object(verify_tempest_config,
                                'print_and_or_update') as print_mock:
             verify_tempest_config.verify_glance_api_versions(fake_os, True)
-        print_mock.assert_called_once_with('api_v2', 'image-feature-enabled',
-                                           False, True)
+        print_mock.assert_called_with('api_v2', 'image-feature-enabled',
+                                      False, True)
 
     def test_verify_glance_version_no_v1(self):
         # This test verifies that wrong config api_v1 = True is detected
@@ -271,8 +274,7 @@
         with mock.patch.object(verify_tempest_config,
                                'print_and_or_update') as print_mock:
             verify_tempest_config.verify_glance_api_versions(fake_os, True)
-        print_mock.assert_called_once_with('api_v1', 'image-feature-enabled',
-                                           False, True)
+        print_mock.assert_not_called()
 
     def test_verify_glance_version_no_version(self):
         # This test verifies that wrong config api_v1 = True is detected
@@ -343,6 +345,24 @@
         self.assertEqual(sorted(['fake1', 'fake2', 'not_fake']),
                          sorted(results['neutron']['extensions']))
 
+    def test_verify_extensions_neutron_none(self):
+        def fake_list_extensions():
+            return {'extensions': []}
+        fake_os = mock.MagicMock()
+        fake_client = mock.MagicMock()
+        fake_client.list_extensions = fake_list_extensions
+        self.useFixture(fixtures.MockPatchObject(
+            verify_tempest_config, 'get_extension_client',
+            return_value=fake_client))
+        self.useFixture(fixtures.MockPatchObject(
+            verify_tempest_config, 'get_enabled_extensions',
+            return_value=(['all'])))
+        results = verify_tempest_config.verify_extensions(fake_os,
+                                                          'neutron', {})
+        self.assertIn('neutron', results)
+        self.assertIn('extensions', results['neutron'])
+        self.assertEqual([], results['neutron']['extensions'])
+
     def test_verify_extensions_cinder(self):
         def fake_list_extensions():
             return {'extensions': [{'alias': 'fake1'},
@@ -391,6 +411,24 @@
         self.assertEqual(sorted(['fake1', 'fake2', 'not_fake']),
                          sorted(results['cinder']['extensions']))
 
+    def test_verify_extensions_cinder_none(self):
+        def fake_list_extensions():
+            return {'extensions': []}
+        fake_os = mock.MagicMock()
+        fake_client = mock.MagicMock()
+        fake_client.list_extensions = fake_list_extensions
+        self.useFixture(fixtures.MockPatchObject(
+            verify_tempest_config, 'get_extension_client',
+            return_value=fake_client))
+        self.useFixture(fixtures.MockPatchObject(
+            verify_tempest_config, 'get_enabled_extensions',
+            return_value=(['all'])))
+        results = verify_tempest_config.verify_extensions(fake_os,
+                                                          'cinder', {})
+        self.assertIn('cinder', results)
+        self.assertIn('extensions', results['cinder'])
+        self.assertEqual([], results['cinder']['extensions'])
+
     def test_verify_extensions_nova(self):
         def fake_list_extensions():
             return ([{'alias': 'fake1'}, {'alias': 'fake2'},
@@ -437,6 +475,24 @@
         self.assertEqual(sorted(['fake1', 'fake2', 'not_fake']),
                          sorted(results['nova']['extensions']))
 
+    def test_verify_extensions_nova_none(self):
+        def fake_list_extensions():
+            return ({'extensions': []})
+        fake_os = mock.MagicMock()
+        fake_client = mock.MagicMock()
+        fake_client.list_extensions = fake_list_extensions
+        self.useFixture(fixtures.MockPatchObject(
+            verify_tempest_config, 'get_extension_client',
+            return_value=fake_client))
+        self.useFixture(fixtures.MockPatchObject(
+            verify_tempest_config, 'get_enabled_extensions',
+            return_value=(['all'])))
+        results = verify_tempest_config.verify_extensions(fake_os,
+                                                          'nova', {})
+        self.assertIn('nova', results)
+        self.assertIn('extensions', results['nova'])
+        self.assertEqual([], results['nova']['extensions'])
+
     def test_verify_extensions_swift(self):
         def fake_list_extensions():
             return {'fake1': 'metadata',
@@ -485,6 +541,24 @@
         self.assertEqual(sorted(['not_fake', 'fake1', 'fake2']),
                          sorted(results['swift']['extensions']))
 
+    def test_verify_extensions_swift_none(self):
+        def fake_list_extensions():
+            return {'swift': 'metadata'}
+        fake_os = mock.MagicMock()
+        fake_client = mock.MagicMock()
+        fake_client.list_capabilities = fake_list_extensions
+        self.useFixture(fixtures.MockPatchObject(
+            verify_tempest_config, 'get_extension_client',
+            return_value=fake_client))
+        self.useFixture(fixtures.MockPatchObject(
+            verify_tempest_config, 'get_enabled_extensions',
+            return_value=(['all'])))
+        results = verify_tempest_config.verify_extensions(fake_os,
+                                                          'swift', {})
+        self.assertIn('swift', results)
+        self.assertIn('extensions', results['swift'])
+        self.assertEqual([], results['swift']['extensions'])
+
     def test_get_extension_client(self):
         creds = credentials_factory.get_credentials(
             fill_in=False, username='fake_user', project_name='fake_project',
@@ -494,3 +568,64 @@
             extensions_client = verify_tempest_config.get_extension_client(
                 os, service)
             self.assertIsInstance(extensions_client, rest_client.RestClient)
+
+    def test_get_extension_client_sysexit(self):
+        creds = credentials_factory.get_credentials(
+            fill_in=False, username='fake_user', project_name='fake_project',
+            password='fake_password')
+        os = clients.Manager(creds)
+        self.assertRaises(SystemExit,
+                          verify_tempest_config.get_extension_client,
+                          os, 'fakeservice')
+
+    def test_get_config_file(self):
+        conf_dir = os.path.join(os.getcwd(), 'etc/')
+        conf_file = "tempest.conf.sample"
+        local_sample_conf_file = os.path.join(conf_dir, conf_file)
+
+        def fake_environ_get(key, default=None):
+            if key == 'TEMPEST_CONFIG_DIR':
+                return conf_dir
+            elif key == 'TEMPEST_CONFIG':
+                return 'tempest.conf.sample'
+            return default
+
+        with mock.patch('os.environ.get', side_effect=fake_environ_get,
+                        autospec=True):
+            init_cmd = init.TempestInit(None, None)
+            init_cmd.generate_sample_config(os.path.join(conf_dir, os.pardir))
+            self.assertTrue(os.path.isfile(local_sample_conf_file),
+                            local_sample_conf_file)
+
+            file_pointer = verify_tempest_config._get_config_file()
+            self.assertEqual(local_sample_conf_file, file_pointer.name)
+
+            with open(local_sample_conf_file, 'r+') as f:
+                local_sample_conf_contents = f.read()
+            self.assertEqual(local_sample_conf_contents, file_pointer.read())
+
+            if file_pointer:
+                file_pointer.close()
+
+    def test_print_and_or_update_true(self):
+        with mock.patch.object(
+            verify_tempest_config, 'change_option') as test_mock:
+            verify_tempest_config.print_and_or_update(
+                'fakeservice', 'fake-service-available', False, True)
+            test_mock.assert_called_once_with(
+                'fakeservice', 'fake-service-available', False)
+
+    def test_print_and_or_update_false(self):
+        with mock.patch.object(
+            verify_tempest_config, 'change_option') as test_mock:
+            verify_tempest_config.print_and_or_update(
+                'fakeservice', 'fake-service-available', False, False)
+            test_mock.assert_not_called()
+
+    def test_contains_version_positive_data(self):
+        self.assertTrue(
+            verify_tempest_config.contains_version('v1.', ['v1.0', 'v2.0']))
+
+    def test_contains_version_negative_data(self):
+        self.assertFalse(
+            verify_tempest_config.contains_version('v5.', ['v1.0', 'v2.0']))
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index a1c8c53..7a6b576 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -17,6 +17,11 @@
 import subprocess
 import tempfile
 
+from mock import patch
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
 from tempest.cmd import workspace
 from tempest.lib.common.utils import data_utils
 from tempest.tests import base
@@ -43,7 +48,7 @@
         stdout, stderr = process.communicate()
         return_code = process.returncode
         msg = ("%s failed with:\nstdout: %s\nstderr: %s" % (' '.join(cmd),
-               stdout, stderr))
+                                                            stdout, stderr))
         self.assertEqual(return_code, expected, msg)
 
     def test_run_workspace_list(self):
@@ -117,22 +122,157 @@
         self.assertIsNone(self.workspace_manager.get_workspace(self.name))
         self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
 
+    def test_workspace_manager_rename_no_name_exist(self):
+        no_name = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.rename_workspace,
+                                   self.name, no_name)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty name is specified."
+                         " Please specify correct name for workspace.\n")
+
+    def test_workspace_manager_rename_with_existing_name(self):
+        new_name = self.name
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.rename_workspace,
+                                   self.name, new_name)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "A workspace already exists with name: %s.\n"
+                         % new_name)
+
+    def test_workspace_manager_rename_no_exist_old_name(self):
+        old_name = ""
+        new_name = data_utils.rand_uuid()
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.rename_workspace,
+                                   old_name, new_name)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "A workspace was not found with name: %s\n"
+                         % old_name)
+
+    def test_workspace_manager_rename_integer_data(self):
+        old_name = self.name
+        new_name = 12345
+        self.workspace_manager.rename_workspace(old_name, new_name)
+        self.assertIsNone(self.workspace_manager.get_workspace(old_name))
+        self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
+
+    def test_workspace_manager_rename_alphanumeric_data(self):
+        old_name = self.name
+        new_name = 'abc123'
+        self.workspace_manager.rename_workspace(old_name, new_name)
+        self.assertIsNone(self.workspace_manager.get_workspace(old_name))
+        self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
+
     def test_workspace_manager_move(self):
         new_path = tempfile.mkdtemp()
         self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
         self.workspace_manager.move_workspace(self.name, new_path)
         self.assertEqual(
             self.workspace_manager.get_workspace(self.name), new_path)
+        # NOTE(mbindlish): Also checking for the workspace that it
+        # shouldn't exist in old path
+        self.assertNotEqual(
+            self.workspace_manager.get_workspace(self.name), self.path)
+
+    def test_workspace_manager_move_wrong_path(self):
+        new_path = 'wrong/path'
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.move_workspace,
+                                   self.name, new_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "Path does not exist.\n")
+
+    def test_workspace_manager_move_wrong_workspace(self):
+        workspace_name = "wrong_workspace_name"
+        new_path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.move_workspace,
+                                   workspace_name, new_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "A workspace was not found with name: %s\n"
+                         % workspace_name)
+
+    def test_workspace_manager_move_no_workspace_name(self):
+        workspace_name = ""
+        new_path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.move_workspace,
+                                   workspace_name, new_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "A workspace was not found with name: %s\n"
+                         % workspace_name)
+
+    def test_workspace_manager_move_no_workspace_path(self):
+        new_path = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.move_workspace,
+                                   self.name, new_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty path is specified for workspace."
+                         " Please specify correct workspace path.\n")
 
     def test_workspace_manager_remove_entry(self):
         self.workspace_manager.remove_workspace_entry(self.name)
         self.assertIsNone(self.workspace_manager.get_workspace(self.name))
 
+    def test_workspace_manager_remove_entry_no_name(self):
+        no_name = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   remove_workspace_entry,
+                                   no_name)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "A workspace was not found with name: %s\n"
+                         % no_name)
+
+    def test_workspace_manager_remove_entry_wrong_name(self):
+        wrong_name = "wrong_name"
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   remove_workspace_entry,
+                                   wrong_name)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "A workspace was not found with name: %s\n"
+                         % wrong_name)
+
     def test_workspace_manager_remove_directory(self):
         path = self.workspace_manager.remove_workspace_entry(self.name)
         self.workspace_manager.remove_workspace_directory(path)
         self.assertIsNone(self.workspace_manager.get_workspace(self.name))
 
+    def test_workspace_manager_remove_directory_no_path(self):
+        no_path = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   remove_workspace_directory,
+                                   no_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty path is specified for workspace."
+                         " Please specify correct workspace path.\n")
+
     def test_path_expansion(self):
         name = data_utils.rand_uuid()
         path = os.path.join("~", name)
@@ -140,3 +280,97 @@
         self.addCleanup(shutil.rmtree, path, ignore_errors=True)
         self.workspace_manager.register_new_workspace(name, path)
         self.assertIsNotNone(self.workspace_manager.get_workspace(name))
+
+    def test_workspace_name_not_exists(self):
+        nonexistent_name = data_utils.rand_uuid()
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager._name_exists,
+                                   nonexistent_name)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "A workspace was not found with name: %s\n"
+                         % nonexistent_name)
+
+    def test_workspace_name_exists(self):
+        self.assertIsNone(self.workspace_manager._name_exists(self.name))
+
+    def test_workspace_name_already_exists(self):
+        duplicate_name = self.name
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   _workspace_name_exists,
+                                   duplicate_name)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "A workspace already exists with name: %s.\n"
+                         % duplicate_name)
+
+    def test_workspace_name_exists_check_new_name(self):
+        new_name = "fake_name"
+        self.assertIsNone(self.workspace_manager.
+                          _workspace_name_exists(new_name))
+
+    def test_workspace_manager_path_not_exist(self):
+        fake_path = "fake_path"
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager._validate_path,
+                                   fake_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "Path does not exist.\n")
+
+    def test_validate_path_exists(self):
+        new_path = self.path
+        self.assertIsNone(self.workspace_manager.
+                          _validate_path(new_path))
+
+    def test_workspace_manager_list_workspaces(self):
+        listed = self.workspace_manager.list_workspaces()
+        self.assertEqual(1, len(listed))
+        self.assertIn(self.name, listed)
+        self.assertEqual(self.path, listed.get(self.name))
+
+    def test_register_new_workspace_no_name(self):
+        no_name = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   register_new_workspace,
+                                   no_name, self.path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty name is specified."
+                         " Please specify correct name for workspace.\n")
+
+    def test_register_new_workspace_no_path(self):
+        no_path = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   register_new_workspace,
+                                   self.name, no_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty path is specified for workspace."
+                         " Please specify correct workspace path.\n")
+
+    def test_register_new_workspace_integer_data(self):
+        workspace_name = 12345
+        self.workspace_manager.register_new_workspace(
+            workspace_name, self.path)
+        self.assertIsNotNone(
+            self.workspace_manager.get_workspace(workspace_name))
+        self.assertEqual(
+            self.workspace_manager.get_workspace(workspace_name), self.path)
+
+    def test_register_new_workspace_alphanumeric_data(self):
+        workspace_name = 'abc123'
+        self.workspace_manager.register_new_workspace(
+            workspace_name, self.path)
+        self.assertIsNotNone(
+            self.workspace_manager.get_workspace(workspace_name))
+        self.assertEqual(
+            self.workspace_manager.get_workspace(workspace_name), self.path)
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index bc197b5..d56e8a4 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -72,3 +72,144 @@
         mock_show.assert_has_calls([mock.call(volume_id),
                                     mock.call(volume_id)])
         mock_sleep.assert_called_once_with(1)
+
+
+class TestInterfaceWaiters(base.TestCase):
+
+    build_timeout = 1.
+    build_interval = 1
+    port_down = {'interfaceAttachment': {'port_state': 'DOWN'}}
+    port_active = {'interfaceAttachment': {'port_state': 'ACTIVE'}}
+
+    def mock_client(self, **kwargs):
+        return mock.MagicMock(
+            build_timeout=self.build_timeout,
+            build_interval=self.build_interval,
+            **kwargs)
+
+    def test_wait_for_interface_status(self):
+        show_interface = mock.Mock(
+            side_effect=[self.port_down, self.port_active])
+        client = self.mock_client(show_interface=show_interface)
+        self.patch('time.time', return_value=0.)
+        sleep = self.patch('time.sleep')
+
+        result = waiters.wait_for_interface_status(
+            client, 'server_id', 'port_id', 'ACTIVE')
+
+        self.assertIs(self.port_active['interfaceAttachment'], result)
+        show_interface.assert_has_calls([mock.call('server_id', 'port_id'),
+                                         mock.call('server_id', 'port_id')])
+        sleep.assert_called_once_with(client.build_interval)
+
+    def test_wait_for_interface_status_timeout(self):
+        show_interface = mock.MagicMock(return_value=self.port_down)
+        client = self.mock_client(show_interface=show_interface)
+        self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+        sleep = self.patch('time.sleep')
+
+        self.assertRaises(lib_exc.TimeoutException,
+                          waiters.wait_for_interface_status,
+                          client, 'server_id', 'port_id', 'ACTIVE')
+
+        show_interface.assert_has_calls([mock.call('server_id', 'port_id'),
+                                         mock.call('server_id', 'port_id')])
+        sleep.assert_called_once_with(client.build_interval)
+
+    one_interface = {'interfaceAttachments': [{'port_id': 'port_one'}]}
+    two_interfaces = {'interfaceAttachments': [{'port_id': 'port_one'},
+                                               {'port_id': 'port_two'}]}
+
+    def test_wait_for_interface_detach(self):
+        list_interfaces = mock.MagicMock(
+            side_effect=[self.two_interfaces, self.one_interface])
+        client = self.mock_client(list_interfaces=list_interfaces)
+        self.patch('time.time', return_value=0.)
+        sleep = self.patch('time.sleep')
+
+        result = waiters.wait_for_interface_detach(
+            client, 'server_id', 'port_two')
+
+        self.assertIs(self.one_interface['interfaceAttachments'], result)
+        list_interfaces.assert_has_calls([mock.call('server_id'),
+                                          mock.call('server_id')])
+        sleep.assert_called_once_with(client.build_interval)
+
+    def test_wait_for_interface_detach_timeout(self):
+        list_interfaces = mock.MagicMock(return_value=self.one_interface)
+        client = self.mock_client(list_interfaces=list_interfaces)
+        self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+        sleep = self.patch('time.sleep')
+
+        self.assertRaises(lib_exc.TimeoutException,
+                          waiters.wait_for_interface_detach,
+                          client, 'server_id', 'port_one')
+
+        list_interfaces.assert_has_calls([mock.call('server_id'),
+                                          mock.call('server_id')])
+        sleep.assert_called_once_with(client.build_interval)
+
+
+class TestVolumeWaiters(base.TestCase):
+    vol_migrating_src_host = {
+        'volume': {'migration_status': 'migrating',
+                   'os-vol-host-attr:host': 'src_host@backend#type'}}
+    vol_migrating_dst_host = {
+        'volume': {'migration_status': 'migrating',
+                   'os-vol-host-attr:host': 'dst_host@backend#type'}}
+    vol_migration_success = {
+        'volume': {'migration_status': 'success',
+                   'os-vol-host-attr:host': 'dst_host@backend#type'}}
+    vol_migration_error = {
+        'volume': {'migration_status': 'error',
+                   'os-vol-host-attr:host': 'src_host@backend#type'}}
+
+    def test_wait_for_volume_migration_timeout(self):
+        show_volume = mock.MagicMock(return_value=self.vol_migrating_src_host)
+        client = mock.Mock(spec=volumes_client.VolumesClient,
+                           resource_type="volume",
+                           build_interval=1,
+                           build_timeout=1,
+                           show_volume=show_volume)
+        self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+        self.patch('time.sleep')
+        self.assertRaises(lib_exc.TimeoutException,
+                          waiters.wait_for_volume_migration,
+                          client, mock.sentinel.volume_id, 'dst_host')
+
+    def test_wait_for_volume_migration_error(self):
+        show_volume = mock.MagicMock(side_effect=[
+            self.vol_migrating_src_host,
+            self.vol_migrating_src_host,
+            self.vol_migration_error])
+        client = mock.Mock(spec=volumes_client.VolumesClient,
+                           resource_type="volume",
+                           build_interval=1,
+                           build_timeout=1,
+                           show_volume=show_volume)
+        self.patch('time.time', return_value=0.)
+        self.patch('time.sleep')
+        self.assertRaises(lib_exc.TempestException,
+                          waiters.wait_for_volume_migration,
+                          client, mock.sentinel.volume_id, 'dst_host')
+
+    def test_wait_for_volume_migration_success_and_dst(self):
+        show_volume = mock.MagicMock(side_effect=[
+            self.vol_migrating_src_host,
+            self.vol_migrating_dst_host,
+            self.vol_migration_success])
+        client = mock.Mock(spec=volumes_client.VolumesClient,
+                           resource_type="volume",
+                           build_interval=1,
+                           build_timeout=1,
+                           show_volume=show_volume)
+        self.patch('time.time', return_value=0.)
+        self.patch('time.sleep')
+        waiters.wait_for_volume_migration(
+            client, mock.sentinel.volume_id, 'dst_host')
+
+        # Assert that we wait until migration_status is success and dst_host is
+        # part of the returned os-vol-host-attr:host.
+        show_volume.assert_has_calls([mock.call(mock.sentinel.volume_id),
+                                      mock.call(mock.sentinel.volume_id),
+                                      mock.call(mock.sentinel.volume_id)])
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 739357b..1f0080f 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -77,7 +77,7 @@
 
     def test_write_to_console_special_chars(self):
         self._test_write_to_console_helper(
-            '\`',
+            r'\`',
             'sudo sh -c "echo \\"\\\\\\`\\" >/dev/console"')
         self.conn.write_to_console('$')
         self._assert_exec_called_with(
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index ee63684..25e99d5 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -32,6 +32,7 @@
         super(ConfigFixture, self).setUp()
         self.conf.set_default('build_interval', 10, group='compute')
         self.conf.set_default('build_timeout', 10, group='compute')
+        self.conf.set_default('image_ref', 'fake_image_id', group='compute')
         self.conf.set_default('disable_ssl_certificate_validation', True,
                               group='identity')
         self.conf.set_default('uri', 'http://fake_uri.com/auth',
@@ -39,11 +40,12 @@
         self.conf.set_default('uri_v3', 'http://fake_uri_v3.com/auth',
                               group='identity')
         self.conf.set_default('neutron', True, group='service_available')
-        self.conf.set_default('heat', True, group='service_available')
-        if not os.path.exists(str(os.environ.get('OS_TEST_LOCK_PATH'))):
-            os.mkdir(str(os.environ.get('OS_TEST_LOCK_PATH')))
+        lock_path = str(os.environ.get('OS_TEST_LOCK_PATH',
+                                       os.environ.get('TMPDIR', '/tmp')))
+        if not os.path.exists(lock_path):
+            os.mkdir(lock_path)
         lockutils.set_defaults(
-            lock_path=str(os.environ.get('OS_TEST_LOCK_PATH')),
+            lock_path=lock_path,
         )
         self.conf.set_default('auth_version', 'v2', group='identity')
         for config_option in ['username', 'password', 'project_name']:
@@ -58,6 +60,7 @@
         self._set_attrs()
         self.lock_path = cfg.CONF.oslo_concurrency.lock_path
 
+
 fake_service1_group = cfg.OptGroup(name='fake-service1', title='Fake service1')
 
 FakeService1Group = [
diff --git a/tempest/tests/files/setup.cfg b/tempest/tests/files/setup.cfg
index f6f9f73..a81d31e 100644
--- a/tempest/tests/files/setup.cfg
+++ b/tempest/tests/files/setup.cfg
@@ -3,8 +3,8 @@
 version = 1
 summary = Fake Project for testing wrapper scripts
 author = OpenStack
-author-email = openstack-dev@lists.openstack.org
-home-page = http://www.openstack.org/
+author-email = openstack-discuss@lists.openstack.org
+home-page = https://docs.openstack.org/tempest/latest/
 classifier =
     Intended Audience :: Information Technology
     Intended Audience :: System Administrators
diff --git a/tempest/tests/files/testr-conf b/tempest/tests/files/testr-conf
index d5ad083..63b3c44 100644
--- a/tempest/tests/files/testr-conf
+++ b/tempest/tests/files/testr-conf
@@ -1,5 +1,3 @@
 [DEFAULT]
-test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
-test_id_option=--load-list $IDFILE
-test_list_option=--list
+test_path=./tests
 group_regex=([^\.]*\.)*
diff --git a/tempest/tests/lib/cli/test_execute.py b/tempest/tests/lib/cli/test_execute.py
index c276386..c069af5 100644
--- a/tempest/tests/lib/cli/test_execute.py
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -125,3 +125,27 @@
                          mock_execute.call_args[0][2])
         self.assertNotIn('--os-project-domain-name',
                          mock_execute.call_args[0][2])
+
+    @mock.patch.object(cli_base, 'execute')
+    def test_execute_with_default_api_version(self, mock_execute):
+        cli = cli_base.CLIClient()
+        cli.openstack('action')
+        self.assertEqual(mock_execute.call_count, 1)
+        self.assertNotIn('--os-identity-api-version ',
+                         mock_execute.call_args[0][2])
+
+    @mock.patch.object(cli_base, 'execute')
+    def test_execute_with_empty_api_version(self, mock_execute):
+        cli = cli_base.CLIClient(identity_api_version='')
+        cli.openstack('action')
+        self.assertEqual(mock_execute.call_count, 1)
+        self.assertNotIn('--os-identity-api-version ',
+                         mock_execute.call_args[0][2])
+
+    @mock.patch.object(cli_base, 'execute')
+    def test_execute_with_explicit_api_version(self, mock_execute):
+        cli = cli_base.CLIClient(identity_api_version='0.0')
+        cli.openstack('action')
+        self.assertEqual(mock_execute.call_count, 1)
+        self.assertIn('--os-identity-api-version 0.0 ',
+                      mock_execute.call_args[0][2])
diff --git a/tempest/tests/lib/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
index ebcf5d1..4723458 100644
--- a/tempest/tests/lib/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -109,8 +109,8 @@
             return_value=(rest_client.ResponseBody
                           (200,
                            {'roles': [{'id': id, 'name': name},
-                            {'id': '1', 'name': 'FakeRole'},
-                            {'id': '2', 'name': 'Member'}]}))))
+                                      {'id': '1', 'name': 'FakeRole'},
+                                      {'id': '2', 'name': 'Member'}]}))))
         return roles_fix
 
     def _mock_list_2_roles(self):
@@ -120,8 +120,8 @@
             return_value=(rest_client.ResponseBody
                           (200,
                            {'roles': [{'id': '1234', 'name': 'role1'},
-                            {'id': '1', 'name': 'FakeRole'},
-                            {'id': '12345', 'name': 'role2'}]}))))
+                                      {'id': '1', 'name': 'FakeRole'},
+                                      {'id': '12345', 'name': 'role2'}]}))))
         return roles_fix
 
     def _mock_assign_user_role(self):
diff --git a/tempest/tests/lib/common/test_http.py b/tempest/tests/lib/common/test_http.py
index a292209..a19153f 100644
--- a/tempest/tests/lib/common/test_http.py
+++ b/tempest/tests/lib/common/test_http.py
@@ -12,57 +12,179 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import urllib3
+
 from tempest.lib.common import http
 from tempest.tests import base
 
 
+CERT_NONE = 'CERT_NONE'
+CERT_REQUIRED = 'CERT_REQUIRED'
+CERT_LOCATION = '/etc/ssl/certs/ca-certificates.crt'
+PROXY_URL = 'http://myproxy:3128'
+REQUEST_URL = 'http://10.0.0.107:5000/v2.0'
+REQUEST_METHOD = 'GET'
+
+
 class TestClosingHttp(base.TestCase):
-    def setUp(self):
-        super(TestClosingHttp, self).setUp()
-        self.cert_none = "CERT_NONE"
-        self.cert_location = "/etc/ssl/certs/ca-certificates.crt"
 
-    def test_constructor_invalid_ca_certs_and_timeout(self):
-        connection = http.ClosingHttp(
-            disable_ssl_certificate_validation=False,
-            ca_certs=None,
-            timeout=None)
-        for attr in ('cert_reqs', 'ca_certs', 'timeout'):
-            self.assertNotIn(attr, connection.connection_pool_kw)
+    def closing_http(self, **kwargs):
+        return http.ClosingHttp(**kwargs)
 
-    def test_constructor_valid_ca_certs(self):
-        cert_required = 'CERT_REQUIRED'
-        connection = http.ClosingHttp(
-            disable_ssl_certificate_validation=False,
-            ca_certs=self.cert_location,
-            timeout=None)
-        self.assertEqual(cert_required,
+    def test_closing_http(self):
+        connection = self.closing_http()
+
+        self.assertNotIn('cert_reqs', connection.connection_pool_kw)
+        self.assertNotIn('ca_certs', connection.connection_pool_kw)
+        self.assertNotIn('timeout', connection.connection_pool_kw)
+
+    def test_closing_http_with_ca_certs(self):
+        connection = self.closing_http(ca_certs=CERT_LOCATION)
+
+        self.assertEqual(CERT_REQUIRED,
                          connection.connection_pool_kw['cert_reqs'])
-        self.assertEqual(self.cert_location,
+        self.assertEqual(CERT_LOCATION,
                          connection.connection_pool_kw['ca_certs'])
-        self.assertNotIn('timeout',
+
+    def test_closing_http_with_dscv(self):
+        connection = self.closing_http(
+            disable_ssl_certificate_validation=True)
+
+        self.assertEqual(CERT_NONE,
+                         connection.connection_pool_kw['cert_reqs'])
+        self.assertNotIn('ca_certs',
                          connection.connection_pool_kw)
 
-    def test_constructor_ssl_cert_validation_disabled(self):
-        connection = http.ClosingHttp(
+    def test_closing_http_with_ca_certs_and_dscv(self):
+        connection = self.closing_http(
             disable_ssl_certificate_validation=True,
-            ca_certs=None,
-            timeout=30)
-        self.assertEqual(self.cert_none,
+            ca_certs=CERT_LOCATION)
+
+        self.assertEqual(CERT_NONE,
                          connection.connection_pool_kw['cert_reqs'])
-        self.assertEqual(30,
+        self.assertNotIn('ca_certs',
+                         connection.connection_pool_kw)
+
+    def test_closing_http_with_timeout(self):
+        timeout = 30
+        connection = self.closing_http(timeout=timeout)
+        self.assertEqual(timeout,
                          connection.connection_pool_kw['timeout'])
-        self.assertNotIn('ca_certs',
-                         connection.connection_pool_kw)
 
-    def test_constructor_ssl_cert_validation_disabled_and_ca_certs(self):
-        connection = http.ClosingHttp(
-            disable_ssl_certificate_validation=True,
-            ca_certs=self.cert_location,
-            timeout=None)
-        self.assertNotIn('timeout',
-                         connection.connection_pool_kw)
-        self.assertEqual(self.cert_none,
-                         connection.connection_pool_kw['cert_reqs'])
-        self.assertNotIn('ca_certs',
-                         connection.connection_pool_kw)
+    def test_request(self):
+        # Given
+        connection = self.closing_http()
+        http_response = urllib3.HTTPResponse()
+        request = self.patch('urllib3.PoolManager.request',
+                             return_value=http_response)
+        retry = self.patch('urllib3.util.Retry')
+
+        # When
+        response, data = connection.request(
+            method=REQUEST_METHOD,
+            url=REQUEST_URL)
+
+        # Then
+        request.assert_called_once_with(
+            REQUEST_METHOD,
+            REQUEST_URL,
+            headers={'connection': 'close'},
+            retries=retry(raise_on_redirect=False, redirect=5))
+        self.assertEqual(
+            {'content-location': REQUEST_URL,
+             'status': str(http_response.status)},
+            response)
+        self.assertEqual(http_response.status, response.status)
+        self.assertEqual(http_response.reason, response.reason)
+        self.assertEqual(http_response.version, response.version)
+        self.assertEqual(http_response.data, data)
+
+    def test_request_with_fields(self):
+        # Given
+        connection = self.closing_http()
+        http_response = urllib3.HTTPResponse()
+        request = self.patch('urllib3.PoolManager.request',
+                             return_value=http_response)
+        retry = self.patch('urllib3.util.Retry')
+        fields = object()
+
+        # When
+        connection.request(
+            method=REQUEST_METHOD,
+            url=REQUEST_URL,
+            fields=fields)
+
+        # Then
+        request.assert_called_once_with(
+            REQUEST_METHOD,
+            REQUEST_URL,
+            fields=fields,
+            headers=dict(connection='close'),
+            retries=retry(raise_on_redirect=False, redirect=5))
+
+    def test_request_with_headers(self):
+        # Given
+        connection = self.closing_http()
+        headers = {'Xtra Key': 'Xtra Value'}
+        http_response = urllib3.HTTPResponse(headers=headers)
+        request = self.patch('urllib3.PoolManager.request',
+                             return_value=http_response)
+        retry = self.patch('urllib3.util.Retry')
+
+        # When
+        response, _ = connection.request(
+            method=REQUEST_METHOD,
+            url=REQUEST_URL,
+            headers=headers)
+
+        # Then
+        request.assert_called_once_with(
+            REQUEST_METHOD,
+            REQUEST_URL,
+            headers=dict(headers, connection='close'),
+            retries=retry(raise_on_redirect=False, redirect=5))
+        self.assertEqual(
+            {'content-location': REQUEST_URL,
+             'status': str(http_response.status),
+             'xtra key': 'Xtra Value'},
+            response)
+
+
+class TestClosingProxyHttp(TestClosingHttp):
+
+    def closing_http(self, proxy_url=PROXY_URL, **kwargs):
+        connection = http.ClosingProxyHttp(proxy_url=proxy_url, **kwargs)
+        self.assertHasProxy(connection, proxy_url)
+        return connection
+
+    def test_class_without_proxy_url(self):
+        self.assertRaises(ValueError, http.ClosingProxyHttp, None)
+
+    def assertHasProxy(self, connection, proxy_url):
+        self.assertIsInstance(connection, http.ClosingProxyHttp)
+        proxy = connection.proxy
+        self.assertEqual(proxy_url,
+                         '%s://%s:%i' % (proxy.scheme,
+                                         proxy.host,
+                                         proxy.port))
+
+
+class TestClosingHttpRedirects(base.TestCase):
+    def test_redirect_default(self):
+        connection = http.ClosingHttp()
+        self.assertTrue(connection.follow_redirects)
+
+    def test_redirect_off(self):
+        connection = http.ClosingHttp(follow_redirects=False)
+        self.assertFalse(connection.follow_redirects)
+
+
+class TestClosingProxyHttpRedirects(base.TestCase):
+    def test_redirect_default(self):
+        connection = http.ClosingProxyHttp(proxy_url=PROXY_URL)
+        self.assertTrue(connection.follow_redirects)
+
+    def test_redirect_off(self):
+        connection = http.ClosingProxyHttp(follow_redirects=False,
+                                           proxy_url=PROXY_URL)
+        self.assertFalse(connection.follow_redirects)
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index 9b10159..25df2a7 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -339,7 +339,7 @@
             return_value=test_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
-        with mock.patch('tempest.lib.services.compute.networks_client.'
+        with mock.patch('tempest.lib.services.network.networks_client.'
                         'NetworksClient.list_networks',
                         return_value={'networks': [{'name': 'network-2',
                                                     'id': 'fake-id',
diff --git a/tempest/tests/lib/common/test_profiler.py b/tempest/tests/lib/common/test_profiler.py
new file mode 100644
index 0000000..59fa0364
--- /dev/null
+++ b/tempest/tests/lib/common/test_profiler.py
@@ -0,0 +1,63 @@
+#    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 mock
+import testtools
+
+from tempest.lib.common import profiler
+
+
+class TestProfiler(testtools.TestCase):
+
+    def test_serialize(self):
+        key = 'SECRET_KEY'
+        pm = {'key': key, 'uuid': 'ID'}
+
+        with mock.patch('tempest.lib.common.profiler._profiler', pm):
+            with mock.patch('json.dumps') as jdm:
+                jdm.return_value = '{"base_id": "ID", "parent_id": "ID"}'
+
+                expected = {
+                    'X-Trace-HMAC':
+                        '887292df9f13b8b5ecd6bbbd2e16bfaaa4d914b0',
+                    'X-Trace-Info':
+                        b'eyJiYXNlX2lkIjogIklEIiwgInBhcmVudF9pZCI6ICJJRCJ9'
+                }
+
+                self.assertEqual(expected,
+                                 profiler.serialize_as_http_headers())
+
+    def test_profiler_lifecycle(self):
+        key = 'SECRET_KEY'
+        uuid = 'ID'
+
+        self.assertEqual({}, profiler._profiler)
+
+        profiler.enable(key, uuid)
+        self.assertEqual({'key': key, 'uuid': uuid}, profiler._profiler)
+
+        profiler.disable()
+        self.assertEqual({}, profiler._profiler)
+
+    @mock.patch('oslo_utils.uuidutils.generate_uuid')
+    def test_profiler_lifecycle_generate_trace_id(self, generate_uuid_mock):
+        key = 'SECRET_KEY'
+        uuid = 'ID'
+        generate_uuid_mock.return_value = uuid
+
+        self.assertEqual({}, profiler._profiler)
+
+        profiler.enable(key)
+        self.assertEqual({'key': key, 'uuid': uuid}, profiler._profiler)
+
+        profiler.disable()
+        self.assertEqual({}, profiler._profiler)
diff --git a/tempest/tests/lib/common/test_rest_client.py b/tempest/tests/lib/common/test_rest_client.py
index 4c0bb57..b861582 100644
--- a/tempest/tests/lib/common/test_rest_client.py
+++ b/tempest/tests/lib/common/test_rest_client.py
@@ -13,10 +13,10 @@
 #    under the License.
 
 import copy
-import json
 
 import fixtures
 import jsonschema
+from oslo_serialization import jsonutils as json
 import six
 
 from tempest.lib.common import http
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
index b8385b2..a0267d0 100644
--- a/tempest/tests/lib/common/utils/test_data_utils.py
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -88,7 +88,7 @@
     def test_rand_url(self):
         actual = data_utils.rand_url()
         self.assertIsInstance(actual, str)
-        self.assertRegex(actual, "^https://url-[0-9]*\.com$")
+        self.assertRegex(actual, r"^https://url-[0-9]*\.com$")
         actual2 = data_utils.rand_url()
         self.assertNotEqual(actual, actual2)
 
diff --git a/tempest/tests/lib/common/utils/test_test_utils.py b/tempest/tests/lib/common/utils/test_test_utils.py
index f638ba6..865767b 100644
--- a/tempest/tests/lib/common/utils/test_test_utils.py
+++ b/tempest/tests/lib/common/utils/test_test_utils.py
@@ -12,12 +12,15 @@
 #    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 time
+
 import mock
 
+from tempest.lib.common import thread
 from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions
 from tempest.tests import base
-from tempest.tests import utils
 
 
 class TestTestUtils(base.TestCase):
@@ -78,47 +81,126 @@
             42, test_utils.call_and_ignore_notfound_exc(m, *args, **kwargs))
         m.assert_called_once_with(*args, **kwargs)
 
-    @mock.patch('time.sleep')
-    @mock.patch('time.time')
-    def test_call_until_true_when_f_never_returns_true(self, m_time, m_sleep):
-        def set_value(bool_value):
-            return bool_value
-        timeout = 42  # The value doesn't matter as we mock time.time()
-        sleep = 60  # The value doesn't matter as we mock time.sleep()
-        m_time.side_effect = utils.generate_timeout_series(timeout)
-        self.assertEqual(
-            False, test_utils.call_until_true(set_value, timeout, sleep, False)
-        )
-        m_sleep.call_args_list = [mock.call(sleep)] * 2
-        m_time.call_args_list = [mock.call()] * 2
 
-    @mock.patch('time.sleep')
-    @mock.patch('time.time')
-    def test_call_until_true_when_f_returns_true(self, m_time, m_sleep):
-        def set_value(bool_value=False):
-            return bool_value
-        timeout = 42  # The value doesn't matter as we mock time.time()
-        sleep = 60  # The value doesn't matter as we mock time.sleep()
-        m_time.return_value = 0
-        self.assertEqual(
-            True, test_utils.call_until_true(set_value, timeout, sleep,
-                                             bool_value=True)
-        )
-        self.assertEqual(0, m_sleep.call_count)
-        # when logging cost time we need to acquire current time.
-        self.assertEqual(2, m_time.call_count)
+class TestCallUntilTrue(base.TestCase):
 
-    @mock.patch('time.sleep')
-    @mock.patch('time.time')
-    def test_call_until_true_when_f_returns_true_no_param(
-            self, m_time, m_sleep):
-        def set_value(bool_value=False):
-            return bool_value
-        timeout = 42  # The value doesn't matter as we mock time.time()
-        sleep = 60  # The value doesn't matter as we mock time.sleep()
-        m_time.side_effect = utils.generate_timeout_series(timeout)
-        self.assertEqual(
-            False, test_utils.call_until_true(set_value, timeout, sleep)
-        )
-        m_sleep.call_args_list = [mock.call(sleep)] * 2
-        m_time.call_args_list = [mock.call()] * 2
+    def test_call_until_true_when_true_at_first_call(self):
+        """func returns True at first call
+
+        """
+        self._test_call_until_true(return_values=[True],
+                                   duration=30.,
+                                   time_sequence=[10., 60.])
+
+    def test_call_until_true_when_true_before_timeout(self):
+        """func returns false at first call, then True before timeout
+
+        """
+        self._test_call_until_true(return_values=[False, True],
+                                   duration=30.,
+                                   time_sequence=[10., 39., 41.])
+
+    def test_call_until_true_when_never_true_before_timeout(self):
+        """func returns false, then false, just before timeout
+
+        """
+        self._test_call_until_true(return_values=[False, False],
+                                   duration=30.,
+                                   time_sequence=[10., 39., 41.])
+
+    def test_call_until_true_with_params(self):
+        """func is called using given parameters
+
+        """
+        self._test_call_until_true(return_values=[False, True],
+                                   duration=30.,
+                                   time_sequence=[10., 30., 60.],
+                                   args=(1, 2),
+                                   kwargs=dict(foo='bar', bar='foo'))
+
+    def _test_call_until_true(self, return_values, duration, time_sequence,
+                              args=None, kwargs=None):
+        """Test call_until_true function
+
+        :param return_values: list of booleans values to be returned
+        each time given function is called. If any of these values
+        is not consumed by calling the function the test fails.
+        The list must contain a sequence of False items terminated
+        by a single True or False
+        :param duration: parameter passed to call_until_true function
+        (a floating point value).
+        :param time_sequence: sequence of time values returned by
+        mocked time.time function used to trigger call_until_true
+        behavior when handling timeout condition. The sequence must
+        contain the exact number of values expected to be consumed by
+        each time call_until_true calls time.time function.
+        :param args: sequence of positional arguments to be passed
+        to call_until_true function.
+        :param kwargs: sequence of named arguments to be passed
+        to call_until_true function.
+        """
+
+        # all values except the last are False
+        self.assertEqual([False] * len(return_values[:-1]), return_values[:-1])
+        # last value can be True or False
+        self.assertIn(return_values[-1], [True, False])
+
+        # GIVEN
+        func = mock.Mock(side_effect=return_values)
+        sleep = 10.  # this value has no effect as time.sleep is being mocked
+        sleep_func = self.patch('time.sleep')
+        time_func = self._patch_time(time_sequence)
+        args = args or tuple()
+        kwargs = kwargs or dict()
+
+        # WHEN
+        result = test_utils.call_until_true(func, duration, sleep,
+                                            *args, **kwargs)
+        # THEN
+
+        # It must return last returned value
+        self.assertIs(return_values[-1], result)
+
+        self._test_func_calls(func, return_values, *args, **kwargs)
+        self._test_sleep_calls(sleep_func, return_values, sleep)
+        # The number of times time.time is called is not relevant as a
+        # requirement of call_until_true. What is instead relevant is that
+        # call_until_true use a mocked function to make the test reliable
+        # and the test actually provide the right sequence of numbers to
+        # reproduce the behavior has to be tested
+        self._assert_called_n_times(time_func, len(time_sequence))
+
+    def _patch_time(self, time_sequence):
+        # Iterator over time sequence
+        time_iterator = iter(time_sequence)
+        # Preserve original time.time() behavior for other threads
+        original_time = time.time
+        thread_id = thread.get_ident()
+
+        def mocked_time():
+            if thread.get_ident() == thread_id:
+                # Test thread => return time sequence values
+                return next(time_iterator)
+            else:
+                # Other threads => call original time function
+                return original_time()
+
+        return self.patch('time.time', side_effect=mocked_time)
+
+    def _test_func_calls(self, func, return_values, *args, **kwargs):
+        self._assert_called_n_times(func, len(return_values), *args, **kwargs)
+
+    def _test_sleep_calls(self, sleep_func, return_values, sleep):
+        # count first consecutive False
+        expected_count = 0
+        for value in return_values:
+            if value:
+                break
+            expected_count += 1
+        self._assert_called_n_times(sleep_func, expected_count, sleep)
+
+    def _assert_called_n_times(self, mock_func, expected_count, *args,
+                               **kwargs):
+        calls = [mock.call(*args, **kwargs)] * expected_count
+        self.assertEqual(expected_count, mock_func.call_count)
+        mock_func.assert_has_calls(calls)
diff --git a/tempest/tests/lib/services/compute/test_flavors_client.py b/tempest/tests/lib/services/compute/test_flavors_client.py
index cbd17c6..5325036 100644
--- a/tempest/tests/lib/services/compute/test_flavors_client.py
+++ b/tempest/tests/lib/services/compute/test_flavors_client.py
@@ -17,6 +17,7 @@
 import fixtures
 from oslo_serialization import jsonutils as json
 
+from tempest.api.compute import api_microversion_fixture
 from tempest.lib.services.compute import flavors_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib import fake_http
@@ -39,6 +40,21 @@
         "vcpus": 1
     }
 
+    FAKE_FLAVOR_UPDATE = {
+        "disk": 1,
+        "id": "1",
+        "links": [{
+            "href": "http://openstack.example.com/v2/openstack/flavors/1",
+            "rel": "self"}, {
+            "href": "http://openstack.example.com/openstack/flavors/1",
+            "rel": "bookmark"}],
+        "name": "m1.tiny",
+        "ram": 512,
+        "swap": 1,
+        "vcpus": 1,
+        "description": 'new'
+    }
+
     EXTRA_SPECS = {"extra_specs": {
         "key1": "value1",
         "key2": "value2"}
@@ -106,6 +122,25 @@
     def test_create_flavor__byte_body(self):
         self._test_create_flavor(bytes_body=True)
 
+    def _test_update_flavor(self, bytes_body=False):
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            '2.55'))
+        expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR_UPDATE}
+        request = {"flavor": {"description": "updated description"}}
+        self.check_service_client_function(
+            self.client.update_flavor,
+            'tempest.lib.common.rest_client.RestClient.put',
+            expected,
+            bytes_body,
+            flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+            **request)
+
+    def test_update_flavor_str_body(self):
+        self._test_update_flavor(bytes_body=False)
+
+    def test_update_flavor__byte_body(self):
+        self._test_update_flavor(bytes_body=True)
+
     def test_delete_flavor(self):
         self.check_service_client_function(
             self.client.delete_flavor,
diff --git a/tempest/tests/lib/services/compute/test_images_client.py b/tempest/tests/lib/services/compute/test_images_client.py
index c2c3b76..d1500e5 100644
--- a/tempest/tests/lib/services/compute/test_images_client.py
+++ b/tempest/tests/lib/services/compute/test_images_client.py
@@ -186,15 +186,19 @@
     def _test_resource_deleted(self, bytes_body=False):
         params = {"id": self.FAKE_IMAGE_ID}
         expected_op = self.FAKE_IMAGE_DATA['show']
-        self.useFixture(fixtures.MockPatch('tempest.lib.services.compute'
-                        '.images_client.ImagesClient.show_image',
-                                           side_effect=lib_exc.NotFound))
+        self.useFixture(
+            fixtures.MockPatch(
+                'tempest.lib.services.compute'
+                '.images_client.ImagesClient.show_image',
+                side_effect=lib_exc.NotFound))
         self.assertEqual(True, self.client.is_resource_deleted(**params))
         tempdata = copy.deepcopy(self.FAKE_IMAGE_DATA['show'])
         tempdata['image']['id'] = None
-        self.useFixture(fixtures.MockPatch('tempest.lib.services.compute'
-                        '.images_client.ImagesClient.show_image',
-                                           return_value=expected_op))
+        self.useFixture(
+            fixtures.MockPatch(
+                'tempest.lib.services.compute'
+                '.images_client.ImagesClient.show_image',
+                return_value=expected_op))
         self.assertEqual(False, self.client.is_resource_deleted(**params))
 
     def test_list_images_with_str_body(self):
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
index 2dd981c..ba432e3 100644
--- a/tempest/tests/lib/services/compute/test_services_client.py
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -56,6 +56,20 @@
         }
     }
 
+    FAKE_UPDATE_SERVICE = {
+        "service": {
+            "id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
+            "binary": "nova-compute",
+            "disabled_reason": "test2",
+            "host": "host1",
+            "state": "down",
+            "status": "disabled",
+            "updated_at": "2012-10-29T13:42:05.000000",
+            "forced_down": False,
+            "zone": "nova"
+        }
+    }
+
     def setUp(self):
         super(TestServicesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -119,6 +133,28 @@
             binary="controller",
             disabled_reason='test reason')
 
+    def _test_update_service(self, bytes_body=False, status=None,
+                             disabled_reason=None, forced_down=None):
+        resp_body = copy.deepcopy(self.FAKE_UPDATE_SERVICE)
+        kwargs = {}
+
+        if status is not None:
+            kwargs['status'] = status
+        if disabled_reason is not None:
+            kwargs['disabled_reason'] = disabled_reason
+        if forced_down is not None:
+            kwargs['forced_down'] = forced_down
+
+        resp_body['service'].update(kwargs)
+
+        self.check_service_client_function(
+            self.client.update_service,
+            'tempest.lib.common.rest_client.RestClient.put',
+            resp_body,
+            bytes_body,
+            service_id=resp_body['service']['id'],
+            **kwargs)
+
     def test_log_reason_disabled_service_with_str_body(self):
         self._test_log_reason_disabled_service()
 
@@ -144,3 +180,36 @@
                        new_callable=mock.PropertyMock(return_value='2.11'))
     def test_update_forced_down_with_bytes_body(self, _):
         self._test_update_forced_down(bytes_body=True)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_disable_scheduling_with_str_body(self, _):
+        self._test_update_service(status='disabled',
+                                  disabled_reason='maintenance')
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_disable_scheduling_with_bytes_body(self, _):
+        self._test_update_service(status='disabled',
+                                  disabled_reason='maintenance',
+                                  bytes_body=True)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_enable_scheduling_with_str_body(self, _):
+        self._test_update_service(status='enabled')
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_enable_scheduling_with_bytes_body(self, _):
+        self._test_update_service(status='enabled', bytes_body=True)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_forced_down_with_str_body(self, _):
+        self._test_update_service(forced_down=True)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_forced_down_with_bytes_body(self, _):
+        self._test_update_service(forced_down=True, bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v2/test_token_client.py b/tempest/tests/lib/services/identity/v2/test_token_client.py
index dfce9b3..a592ada 100644
--- a/tempest/tests/lib/services/identity/v2/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v2/test_token_client.py
@@ -12,9 +12,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-import json
-
 import mock
+from oslo_serialization import jsonutils as json
 
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions
diff --git a/tempest/tests/lib/services/identity/v3/test_application_credentials_client.py b/tempest/tests/lib/services/identity/v3/test_application_credentials_client.py
new file mode 100644
index 0000000..9bf9b68
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_application_credentials_client.py
@@ -0,0 +1,156 @@
+# Copyright 2018 SUSE Linux GmbH
+#
+# 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 application_credentials_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestApplicationCredentialsClient(base.BaseServiceTest):
+    FAKE_CREATE_APP_CRED = {
+        "application_credential": {
+            "description": "fake application credential",
+            "roles": [
+                {
+                    "id": "c60fdd45",
+                    "domain_id": None,
+                    "name": "Member"
+                }
+            ],
+            "expires_at": "2019-02-27T18:30:59.999999Z",
+            "secret": "_BVq0xU5L",
+            "unrestricted": None,
+            "project_id": "ddef321",
+            "id": "5499a186",
+            "name": "one"
+        }
+    }
+
+    FAKE_LIST_APP_CREDS = {
+        "application_credentials": [
+            {
+                "description": "fake application credential",
+                "roles": [
+                    {
+                        "domain_id": None,
+                        "name": "Member",
+                        "id": "c60fdd45",
+                    }
+                ],
+                "expires_at": "2018-02-27T18:30:59.999999Z",
+                "unrestricted": None,
+                "project_id": "ddef321",
+                "id": "5499a186",
+                "name": "one"
+            },
+            {
+                "description": None,
+                "roles": [
+                    {
+                        "id": "0f1837c8",
+                        "domain_id": None,
+                        "name": "anotherrole"
+                    },
+                    {
+                        "id": "c60fdd45",
+                        "domain_id": None,
+                        "name": "Member"
+                    }
+                ],
+                "expires_at": None,
+                "unrestricted": None,
+                "project_id": "c5403d938",
+                "id": "d441c904f",
+                "name": "two"
+            }
+        ]
+    }
+
+    FAKE_APP_CRED_INFO = {
+        "application_credential": {
+            "description": None,
+            "roles": [
+                {
+                    "domain_id": None,
+                    "name": "Member",
+                    "id": "c60fdd45",
+                }
+            ],
+            "expires_at": None,
+            "unrestricted": None,
+            "project_id": "ddef321",
+            "id": "5499a186",
+            "name": "one"
+        }
+    }
+
+    def setUp(self):
+        super(TestApplicationCredentialsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = \
+            application_credentials_client.ApplicationCredentialsClient(
+                fake_auth, 'identity', 'regionOne')
+
+    def _test_create_app_cred(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_application_credential,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_APP_CRED,
+            bytes_body,
+            status=201,
+            user_id="123456")
+
+    def _test_show_app_cred(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_application_credential,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_APP_CRED_INFO,
+            bytes_body,
+            user_id="123456",
+            application_credential_id="5499a186")
+
+    def _test_list_app_creds(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_application_credentials,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_APP_CREDS,
+            bytes_body,
+            user_id="123456")
+
+    def test_create_application_credential_with_str_body(self):
+        self._test_create_app_cred()
+
+    def test_create_application_credential_with_bytes_body(self):
+        self._test_create_app_cred(bytes_body=True)
+
+    def test_show_application_credential_with_str_body(self):
+        self._test_show_app_cred()
+
+    def test_show_application_credential_with_bytes_body(self):
+        self._test_show_app_cred(bytes_body=True)
+
+    def test_list_application_credential_with_str_body(self):
+        self._test_list_app_creds()
+
+    def test_list_application_credential_with_bytes_body(self):
+        self._test_list_app_creds(bytes_body=True)
+
+    def test_delete_trust(self):
+        self.check_service_client_function(
+            self.client.delete_application_credential,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            user_id="123456",
+            application_credential_id="5499a186",
+            status=204)
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 66c3d65..0237475 100644
--- a/tempest/tests/lib/services/identity/v3/test_policies_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_policies_client.py
@@ -81,6 +81,10 @@
                 }
             ]
     }
+    FAKE_ENDPOINT_ID = "234789"
+    FAKE_SERVICE_ID = "556782"
+    FAKE_POLICY_ID = "717273"
+    FAKE_REGION_ID = "73"
 
     def setUp(self):
         super(TestPoliciesClient, self).setUp()
@@ -150,3 +154,87 @@
             {},
             policy_id="717273",
             status=204)
+
+    def test_update_policy_association_for_endpoint(self):
+        self.check_service_client_function(
+            self.client.update_policy_association_for_endpoint,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            status=204)
+
+    def test_show_policy_association_for_endpoint(self):
+        self.check_service_client_function(
+            self.client.show_policy_association_for_endpoint,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            status=204)
+
+    def test_delete_policy_association_for_endpoint(self):
+        self.check_service_client_function(
+            self.client.delete_policy_association_for_endpoint,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            status=204)
+
+    def test_update_policy_association_for_service(self):
+        self.check_service_client_function(
+            self.client.update_policy_association_for_service,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            status=204)
+
+    def test_show_policy_association_for_service(self):
+        self.check_service_client_function(
+            self.client.show_policy_association_for_service,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            status=204)
+
+    def test_delete_policy_association_for_service(self):
+        self.check_service_client_function(
+            self.client.delete_policy_association_for_service,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            status=204)
+
+    def test_update_policy_association_for_region_and_service(self):
+        self.check_service_client_function(
+            self.client.update_policy_association_for_region_and_service,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            region_id=self.FAKE_REGION_ID,
+            status=204)
+
+    def test_show_policy_association_for_region_and_service(self):
+        self.check_service_client_function(
+            self.client.show_policy_association_for_region_and_service,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            region_id=self.FAKE_REGION_ID,
+            status=204)
+
+    def test_delete_policy_association_for_region_and_service(self):
+        self.check_service_client_function(
+            self.client.delete_policy_association_for_region_and_service,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            region_id=self.FAKE_REGION_ID,
+            status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_project_tags_client.py b/tempest/tests/lib/services/identity/v3/test_project_tags_client.py
new file mode 100644
index 0000000..2d65a29
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_project_tags_client.py
@@ -0,0 +1,104 @@
+# Copyright 2018 AT&T Corporation.
+#
+# 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 project_tags_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestProjectTagsClient(base.BaseServiceTest):
+
+    FAKE_PROJECT_ID = "0c4e939acacf4376bdcd1129f1a054ad"
+
+    FAKE_PROJECT_TAG = "foo"
+
+    FAKE_PROJECT_TAGS = ["foo", "bar"]
+
+    def setUp(self):
+        super(TestProjectTagsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = project_tags_client.ProjectTagsClient(fake_auth,
+                                                            'identity',
+                                                            'regionOne')
+
+    def _test_update_project_tag(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_project_tag,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            project_id=self.FAKE_PROJECT_ID,
+            tag=self.FAKE_PROJECT_TAG,
+            status=201)
+
+    def _test_list_project_tags(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_project_tags,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {"tags": self.FAKE_PROJECT_TAGS},
+            bytes_body,
+            project_id=self.FAKE_PROJECT_ID)
+
+    def _test_update_all_project_tags(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_all_project_tags,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {"tags": self.FAKE_PROJECT_TAGS},
+            bytes_body,
+            project_id=self.FAKE_PROJECT_ID,
+            tags=self.FAKE_PROJECT_TAGS)
+
+    def test_update_project_tag_with_str_body(self):
+        self._test_update_project_tag()
+
+    def test_update_project_tag_with_bytes_body(self):
+        self._test_update_project_tag(bytes_body=True)
+
+    def test_list_project_tags_with_str_body(self):
+        self._test_list_project_tags()
+
+    def test_list_project_tags_with_bytes_body(self):
+        self._test_list_project_tags(bytes_body=True)
+
+    def test_update_all_project_tags_with_str_body(self):
+        self._test_update_all_project_tags()
+
+    def test_update_all_project_tags_with_bytes_body(self):
+        self._test_update_all_project_tags(bytes_body=True)
+
+    def test_check_project_project_tag_existence(self):
+        self.check_service_client_function(
+            self.client.check_project_tag_existence,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            project_id=self.FAKE_PROJECT_ID,
+            tag=self.FAKE_PROJECT_TAG,
+            status=204)
+
+    def test_delete_project_tag(self):
+        self.check_service_client_function(
+            self.client.delete_project_tag,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            project_id=self.FAKE_PROJECT_ID,
+            tag=self.FAKE_PROJECT_TAG,
+            status=204)
+
+    def test_delete_all_project_tags(self):
+        self.check_service_client_function(
+            self.client.delete_all_project_tags,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            project_id=self.FAKE_PROJECT_ID,
+            status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_token_client.py b/tempest/tests/lib/services/identity/v3/test_token_client.py
index 38e8c4a..a9c58df 100644
--- a/tempest/tests/lib/services/identity/v3/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_token_client.py
@@ -12,9 +12,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-import json
-
 import mock
+from oslo_serialization import jsonutils as json
 
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions
diff --git a/tempest/tests/lib/services/image/v2/test_resource_types_client.py b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
index 2e3b117..741b4eb 100644
--- a/tempest/tests/lib/services/image/v2/test_resource_types_client.py
+++ b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
@@ -17,7 +17,7 @@
 from tempest.tests.lib.services import base
 
 
-class TestResouceTypesClient(base.BaseServiceTest):
+class TestResourceTypesClient(base.BaseServiceTest):
     FAKE_LIST_RESOURCETYPES = {
         "resource_types": [
             {
@@ -49,21 +49,21 @@
     }
 
     def setUp(self):
-        super(TestResouceTypesClient, self).setUp()
+        super(TestResourceTypesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
         self.client = resource_types_client.ResourceTypesClient(fake_auth,
                                                                 'image',
                                                                 'regionOne')
 
-    def _test_list_resouce_types(self, bytes_body=False):
+    def _test_list_resource_types(self, bytes_body=False):
         self.check_service_client_function(
             self.client.list_resource_types,
             'tempest.lib.common.rest_client.RestClient.get',
             self.FAKE_LIST_RESOURCETYPES,
             bytes_body)
 
-    def test_list_resouce_types_with_str_body(self):
-        self._test_list_resouce_types()
+    def test_list_resource_types_with_str_body(self):
+        self._test_list_resource_types()
 
-    def test_list_resouce_types_with_bytes_body(self):
-        self._test_list_resouce_types(bytes_body=True)
+    def test_list_resource_types_with_bytes_body(self):
+        self._test_list_resource_types(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_agents_client.py b/tempest/tests/lib/services/network/test_agents_client.py
new file mode 100644
index 0000000..aabc6ce
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_agents_client.py
@@ -0,0 +1,37 @@
+# Copyright 2018 AT&T Corporation.
+# All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.services.network import agents_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestAgentsClient(base.BaseServiceTest):
+
+    FAKE_AGENT_ID = "d32019d3-bc6e-4319-9c1d-6123f4135a88"
+
+    def setUp(self):
+        super(TestAgentsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.agents_client = agents_client.AgentsClient(
+            fake_auth, "network", "regionOne")
+
+    def test_delete_agent(self):
+        self.check_service_client_function(
+            self.agents_client.delete_agent,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            agent_id=self.FAKE_AGENT_ID)
diff --git a/tempest/tests/lib/services/network/test_networks_client.py b/tempest/tests/lib/services/network/test_networks_client.py
new file mode 100644
index 0000000..078f4b0
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_networks_client.py
@@ -0,0 +1,242 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+
+from tempest.lib.services.network import networks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestNetworksClient(base.BaseServiceTest):
+
+    FAKE_NETWORKS = {
+        "networks": [
+            {
+                "admin_state_up": True,
+                "availability_zone_hints": [],
+                "availability_zones": [
+                    "nova"
+                ],
+                "created_at": "2016-03-08T20:19:41",
+                "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+                "mtu": 0,
+                "name": "net1",
+                "port_security_enabled": True,
+                "project_id": "4fd44f30292945e481c7b8a0c8908869",
+                "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
+                "router:external": False,
+                "shared": False,
+                "status": "ACTIVE",
+                "subnets": [
+                        "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+                ],
+                "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+                "updated_at": "2016-03-08T20:19:41",
+                "vlan_transparent": True,
+                "description": ""
+            },
+            {
+                "admin_state_up": True,
+                "availability_zone_hints": [],
+                "availability_zones": [
+                    "nova"
+                ],
+                "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+                "mtu": 0,
+                "name": "net2",
+                "port_security_enabled": True,
+                "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819",
+                "router:external": False,
+                "shared": False,
+                "status": "ACTIVE",
+                "subnets": [
+                        "08eae331-0402-425a-923c-34f7cfe39c1b"
+                ],
+                "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "updated_at": "2016-03-08T20:19:41",
+                "vlan_transparent": False,
+                "description": ""
+            }
+        ]
+    }
+
+    FAKE_NETWORK_ID = "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+
+    FAKE_NETWORK1 = {
+        "name": "net1",
+        "admin_state_up": True,
+        "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e"
+    }
+
+    FAKE_NETWORK2 = {
+        "name": "net2",
+        "admin_state_up": True,
+        "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819"
+    }
+
+    FAKE_NETWORKS_REQ = {
+        "networks": [
+            FAKE_NETWORK1,
+            FAKE_NETWORK2
+        ]
+    }
+
+    FAKE_DHCP_AGENT_NETWORK_ID = "80515c45-651f-4f9a-b82b-2ca8a7301a8d"
+
+    FAKE_DHCP_AGENTS = {
+        "agents": [
+            {
+                "binary": "neutron-dhcp-agent",
+                "description": None,
+                "admin_state_up": True,
+                "heartbeat_timestamp": "2017-06-22 18:29:50",
+                "availability_zone": "nova",
+                "alive": True,
+                "topic": "dhcp_agent",
+                "host": "osboxes",
+                "agent_type": "DHCP agent",
+                "resource_versions": {},
+                "created_at": "2017-06-19 21:39:51",
+                "started_at": "2017-06-19 21:39:51",
+                "id": "b6cfb7a1-6ac4-4980-993c-9d295d37062e",
+                "configurations": {
+                    "subnets": 2,
+                    "dhcp_lease_duration": 86400,
+                    "dhcp_driver": "neutron.agent.linux.dhcp.Dnsmasq",
+                    "networks": 1,
+                    "log_agent_heartbeats": False,
+                    "ports": 3
+                }
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestNetworksClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.networks_client = networks_client.NetworksClient(
+            fake_auth, "network", "regionOne")
+
+    def _test_list_networks(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.list_networks,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_NETWORKS,
+            bytes_body,
+            200)
+
+    def _test_create_network(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.create_network,
+            "tempest.lib.common.rest_client.RestClient.post",
+            {"network": self.FAKE_NETWORKS["networks"][0]},
+            bytes_body,
+            201,
+            **self.FAKE_NETWORK1)
+
+    def _test_create_bulk_networks(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.create_bulk_networks,
+            "tempest.lib.common.rest_client.RestClient.post",
+            self.FAKE_NETWORKS,
+            bytes_body,
+            201,
+            networks=self.FAKE_NETWORKS_REQ)
+
+    def _test_show_network(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.show_network,
+            "tempest.lib.common.rest_client.RestClient.get",
+            {"network": self.FAKE_NETWORKS["networks"][0]},
+            bytes_body,
+            200,
+            network_id=self.FAKE_NETWORK_ID)
+
+    def _test_update_network(self, bytes_body=False):
+        update_kwargs = {
+            "name": "sample_network_5_updated",
+            "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e"
+        }
+
+        resp_body = {
+            "network": copy.deepcopy(
+                self.FAKE_NETWORKS["networks"][0]
+            )
+        }
+        resp_body["network"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.networks_client.update_network,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            network_id=self.FAKE_NETWORK_ID,
+            **update_kwargs)
+
+    def _test_list_dhcp_agents_on_hosting_network(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.list_dhcp_agents_on_hosting_network,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_DHCP_AGENTS,
+            bytes_body,
+            200,
+            network_id=self.FAKE_DHCP_AGENT_NETWORK_ID)
+
+    def test_delete_network(self):
+        self.check_service_client_function(
+            self.networks_client.delete_network,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            network_id=self.FAKE_NETWORK_ID)
+
+    def test_list_networks_with_str_body(self):
+        self._test_list_networks()
+
+    def test_list_networks_with_bytes_body(self):
+        self._test_list_networks(bytes_body=True)
+
+    def test_create_network_with_str_body(self):
+        self._test_create_network()
+
+    def test_create_network_with_bytes_body(self):
+        self._test_create_network(bytes_body=True)
+
+    def test_create_bulk_network_with_str_body(self):
+        self._test_create_bulk_networks()
+
+    def test_create_bulk_network_with_bytes_body(self):
+        self._test_create_bulk_networks(bytes_body=True)
+
+    def test_show_network_with_str_body(self):
+        self._test_show_network()
+
+    def test_show_network_with_bytes_body(self):
+        self._test_show_network(bytes_body=True)
+
+    def test_update_network_with_str_body(self):
+        self._test_update_network()
+
+    def test_update_network_with_bytes_body(self):
+        self._test_update_network(bytes_body=True)
+
+    def test_list_dhcp_agents_on_hosting_network_with_str_body(self):
+        self._test_list_dhcp_agents_on_hosting_network()
+
+    def test_list_dhcp_agents_on_hosting_network_with_bytes_body(self):
+        self._test_list_dhcp_agents_on_hosting_network(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_quotas_client.py b/tempest/tests/lib/services/network/test_quotas_client.py
index e76bc9c..aa6c1a1 100644
--- a/tempest/tests/lib/services/network/test_quotas_client.py
+++ b/tempest/tests/lib/services/network/test_quotas_client.py
@@ -38,8 +38,62 @@
         ]
     }
 
+    FAKE_PROJECT_QUOTAS = {
+        "quota": {
+            "floatingip": 50,
+            "network": 10,
+            "port": 50,
+            "rbac_policy": -1,
+            "router": 10,
+            "security_group": 10,
+            "security_group_rule": 100,
+            "subnet": 10,
+            "subnetpool": -1
+        }
+    }
+
     FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978"
 
+    FAKE_QUOTA_DETAILS = {
+        "quota": {
+            "rbac_policy": {
+                "used": 4,
+                "limit": 10,
+                "reserved": 0
+            },
+            "subnetpool": {
+                "used": 2,
+                "limit": -1,
+                "reserved": 0
+            },
+            "security_group_rule": {
+                "used": 10,
+                "limit": 100,
+                "reserved": 1
+            },
+            "security_group": {
+                "used": 3,
+                "limit": 10,
+                "reserved": 0
+            },
+            "subnet": {
+                "used": 3,
+                "limit": 100,
+                "reserved": 0
+            },
+            "port": {
+                "used": 21,
+                "limit": 500,
+                "reserved": 3
+            },
+            "network": {
+                "used": 9,
+                "limit": 100,
+                "reserved": 2
+            }
+        }
+    }
+
     def setUp(self):
         super(TestQuotasClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -58,7 +112,16 @@
         self.check_service_client_function(
             self.quotas_client.show_quotas,
             "tempest.lib.common.rest_client.RestClient.get",
-            {"quota": self.FAKE_QUOTAS["quotas"][0]},
+            self.FAKE_PROJECT_QUOTAS,
+            bytes_body,
+            200,
+            tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+    def _test_show_default_quotas(self, bytes_body=False):
+        self.check_service_client_function(
+            self.quotas_client.show_default_quotas,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_PROJECT_QUOTAS,
             bytes_body,
             200,
             tenant_id=self.FAKE_QUOTA_TENANT_ID)
@@ -67,7 +130,16 @@
         self.check_service_client_function(
             self.quotas_client.update_quotas,
             "tempest.lib.common.rest_client.RestClient.put",
-            {"quota": self.FAKE_QUOTAS["quotas"][0]},
+            self.FAKE_PROJECT_QUOTAS,
+            bytes_body,
+            200,
+            tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+    def _test_show_quota_details(self, bytes_body=False):
+        self.check_service_client_function(
+            self.quotas_client.show_quota_details,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_QUOTA_DETAILS,
             bytes_body,
             200,
             tenant_id=self.FAKE_QUOTA_TENANT_ID)
@@ -92,8 +164,20 @@
     def test_show_quotas_with_bytes_body(self):
         self._test_show_quotas(bytes_body=True)
 
+    def test_show_default_quotas_with_str_body(self):
+        self._test_show_default_quotas()
+
+    def test_show_default_quotas_with_bytes_body(self):
+        self._test_show_default_quotas(bytes_body=True)
+
     def test_update_quotas_with_str_body(self):
         self._test_update_quotas()
 
     def test_update_quotas_with_bytes_body(self):
         self._test_update_quotas(bytes_body=True)
+
+    def test_show_quota_details_with_str_body(self):
+        self._test_show_quota_details()
+
+    def test_show_quota_details_with_bytes_body(self):
+        self._test_show_quota_details(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_versions_client.py b/tempest/tests/lib/services/network/test_versions_client.py
index 026dc6d..188fc31 100644
--- a/tempest/tests/lib/services/network/test_versions_client.py
+++ b/tempest/tests/lib/services/network/test_versions_client.py
@@ -12,63 +12,92 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import copy
-
 from tempest.lib.services.network import versions_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
 
 class TestNetworkVersionsClient(base.BaseServiceTest):
-
-    FAKE_INIT_VERSION = {
-        "version": {
-            "id": "v2.0",
-            "links": [
-                {
-                    "href": "http://openstack.example.com/v2.0/",
-                    "rel": "self"
-                },
-                {
-                    "href": "http://docs.openstack.org/",
-                    "rel": "describedby",
-                    "type": "text/html"
-                }
-            ],
-            "status": "CURRENT"
-            }
-        }
+    VERSION = "v2.0"
 
     FAKE_VERSIONS_INFO = {
-        "versions": [FAKE_INIT_VERSION["version"]]
-        }
-
-    FAKE_VERSION_INFO = copy.deepcopy(FAKE_INIT_VERSION)
-
-    FAKE_VERSION_INFO["version"]["media-types"] = [
-        {
-            "base": "application/json",
-            "type": "application/vnd.openstack.network+json;version=2.0"
-        }
+        "versions": [
+            {
+                "id": "v2.0",
+                "links": [
+                    {
+                        "href": "http://openstack.example.com/%s/" % VERSION,
+                        "rel": "self"
+                    }
+                ],
+                "status": "CURRENT"
+            }
         ]
+    }
+
+    FAKE_VERSION_DETAILS = {
+        "resources": [
+            {
+                "collection": "subnets",
+                "links": [
+                    {
+                        "href": "http://openstack.example.com:9696/"
+                                "%s/subnets" % VERSION,
+                        "rel": "self"
+                    }
+                ],
+                "name": "subnet"
+            },
+            {
+                "collection": "networks",
+                "links": [
+                    {
+                        "href": "http://openstack.example.com:9696/"
+                                "%s/networks" % VERSION,
+                        "rel": "self"
+                    }
+                ],
+                "name": "network"
+            },
+            {
+                "collection": "ports",
+                "links": [
+                    {
+                        "href": "http://openstack.example.com:9696/"
+                                "%s/ports" % VERSION,
+                        "rel": "self"
+                    }
+                ],
+                "name": "port"
+            }
+        ]
+    }
 
     def setUp(self):
         super(TestNetworkVersionsClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.versions_client = (
-            versions_client.NetworkVersionsClient
-            (fake_auth, 'compute', 'regionOne'))
+        self.versions_client = versions_client.NetworkVersionsClient(
+            fake_auth, 'compute', 'regionOne')
 
-    def _test_versions_client(self, bytes_body=False):
+    def _test_versions_client(self, func, body, bytes_body=False, **kwargs):
         self.check_service_client_function(
-            self.versions_client.list_versions,
-            'tempest.lib.common.rest_client.RestClient.raw_request',
-            self.FAKE_VERSIONS_INFO,
-            bytes_body,
-            200)
+            func, 'tempest.lib.common.rest_client.RestClient.raw_request',
+            body, bytes_body, 200, **kwargs)
 
     def test_list_versions_client_with_str_body(self):
-        self._test_versions_client()
+        self._test_versions_client(self.versions_client.list_versions,
+                                   self.FAKE_VERSIONS_INFO)
 
     def test_list_versions_client_with_bytes_body(self):
-        self._test_versions_client(bytes_body=True)
+        self._test_versions_client(self.versions_client.list_versions,
+                                   self.FAKE_VERSIONS_INFO, bytes_body=True)
+
+    def test_show_version_client_with_str_body(self):
+        self._test_versions_client(self.versions_client.show_version,
+                                   self.FAKE_VERSION_DETAILS,
+                                   version=self.VERSION)
+
+    def test_show_version_client_with_bytes_body(self):
+        self._test_versions_client(self.versions_client.show_version,
+                                   self.FAKE_VERSION_DETAILS, bytes_body=True,
+                                   version=self.VERSION)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/tests/lib/services/placement/__init__.py
similarity index 100%
rename from tempest/tests/lib/services/volume/v2/__init__.py
rename to tempest/tests/lib/services/placement/__init__.py
diff --git a/tempest/tests/lib/services/placement/test_placement_client.py b/tempest/tests/lib/services/placement/test_placement_client.py
new file mode 100644
index 0000000..1396a85
--- /dev/null
+++ b/tempest/tests/lib/services/placement/test_placement_client.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2019 Ericsson
+#
+#    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 placement_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestPlacementClient(base.BaseServiceTest):
+    FAKE_ALLOCATION_CANDIDATES = {
+        'allocation_requests': [
+            {'allocations': {
+                'rp-uuid': {'resources': {'VCPU': 42}}
+            }}
+        ],
+        'provider_summaries': {
+            'rp-uuid': {
+                'resources': {
+                    'VCPU': {'used': 0, 'capacity': 64},
+                    'MEMORY_MB': {'capacity': 11196, 'used': 0},
+                    'DISK_GB': {'capacity': 19, 'used': 0}
+                },
+                'traits': ["HW_CPU_X86_SVM"],
+            }
+        }
+    }
+
+    FAKE_ALLOCATIONS = {
+        'allocations': {
+            'rp-uuid-1': {
+                'resources': {
+                    'NET_BW_IGR_KILOBIT_PER_SEC': 1
+                },
+                'generation': 14
+            },
+            'rp-uuid2': {
+                'resources': {
+                    'MEMORY_MB': 256,
+                    'VCPU': 1
+                },
+                'generation': 9
+            }
+        }
+    }
+
+    def setUp(self):
+        super(TestPlacementClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = placement_client.PlacementClient(
+            fake_auth, 'placement', 'regionOne')
+
+    def _test_list_allocation_candidates(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_allocation_candidates,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ALLOCATION_CANDIDATES,
+            to_utf=bytes_body,
+            **{'resources1': 'NET_BW_IGR_KILOBIT_PER_SEC:1'})
+
+    def test_list_allocation_candidates_with_str_body(self):
+        self._test_list_allocation_candidates()
+
+    def test_list_allocation_candidates_with_bytes_body(self):
+        self._test_list_allocation_candidates(bytes_body=True)
+
+    def _test_list_allocations(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_allocations,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ALLOCATIONS,
+            to_utf=bytes_body,
+            **{'consumer_uuid': 'foo-bar'})
+
+    def test_list_allocations_with_str_body(self):
+        self._test_list_allocations()
+
+    def test_list_allocations_with_bytes_body(self):
+        self._test_list_allocations(bytes_body=True)
diff --git a/tempest/tests/lib/services/registry_fixture.py b/tempest/tests/lib/services/registry_fixture.py
index 1da2112..07af68a 100644
--- a/tempest/tests/lib/services/registry_fixture.py
+++ b/tempest/tests/lib/services/registry_fixture.py
@@ -37,8 +37,9 @@
     def __init__(self):
         """Initialise the registry fixture"""
         self.services = set(['compute', 'identity.v2', 'identity.v3',
-                             'image.v1', 'image.v2', 'network', 'volume.v1',
-                             'volume.v2', 'volume.v3', 'object-storage'])
+                             'image.v1', 'image.v2', 'network', 'placement',
+                             'volume.v1', 'volume.v2', 'volume.v3',
+                             'object-storage'])
 
     def _setUp(self):
         # Cleanup the registry
diff --git a/tempest/tests/lib/services/volume/v2/test_backups_client.py b/tempest/tests/lib/services/volume/v2/test_backups_client.py
deleted file mode 100644
index 14e5fb0..0000000
--- a/tempest/tests/lib/services/volume/v2/test_backups_client.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from tempest.lib.services.volume.v2 import backups_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestBackupsClient(base.BaseServiceTest):
-
-    FAKE_BACKUP_LIST = {
-        "backups": [
-            {
-                "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
-                "links": [
-                    {
-                        "href": "fake-url-1",
-                        "rel": "self"
-                    },
-                    {
-                        "href": "fake-url-2",
-                        "rel": "bookmark"
-                    }
-                ],
-                "name": "backup001"
-            }
-        ]
-    }
-
-    FAKE_BACKUP_LIST_WITH_DETAIL = {
-        "backups": [
-            {
-                "availability_zone": "az1",
-                "container": "volumebackups",
-                "created_at": "2013-04-02T10:35:27.000000",
-                "description": None,
-                "fail_reason": None,
-                "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
-                "links": [
-                    {
-                        "href": "fake-url-1",
-                        "rel": "self"
-                    },
-                    {
-                        "href": "fake-url-2",
-                        "rel": "bookmark"
-                    }
-                ],
-                "name": "backup001",
-                "object_count": 22,
-                "size": 1,
-                "status": "available",
-                "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
-                "is_incremental": True,
-                "has_dependent_backups": False
-            }
-        ]
-    }
-
-    def setUp(self):
-        super(TestBackupsClient, self).setUp()
-        fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.client = backups_client.BackupsClient(fake_auth,
-                                                   'volume',
-                                                   'regionOne')
-
-    def _test_list_backups(self, detail=False, mock_args='backups',
-                           bytes_body=False, **params):
-        if detail:
-            resp_body = self.FAKE_BACKUP_LIST_WITH_DETAIL
-        else:
-            resp_body = self.FAKE_BACKUP_LIST
-        self.check_service_client_function(
-            self.client.list_backups,
-            'tempest.lib.common.rest_client.RestClient.get',
-            resp_body,
-            to_utf=bytes_body,
-            mock_args=[mock_args],
-            detail=detail,
-            **params)
-
-    def test_list_backups_with_str_body(self):
-        self._test_list_backups()
-
-    def test_list_backups_with_bytes_body(self):
-        self._test_list_backups(bytes_body=True)
-
-    def test_list_backups_with_detail_with_str_body(self):
-        mock_args = "backups/detail"
-        self._test_list_backups(detail=True, mock_args=mock_args)
-
-    def test_list_backups_with_detail_with_bytes_body(self):
-        mock_args = "backups/detail"
-        self._test_list_backups(detail=True, mock_args=mock_args,
-                                bytes_body=True)
-
-    def test_list_backups_with_params(self):
-        # Run the test separately for each param, to avoid assertion error
-        # resulting from randomized params order.
-        mock_args = 'backups?sort_key=name'
-        self._test_list_backups(mock_args=mock_args, sort_key='name')
-
-        mock_args = 'backups/detail?limit=10'
-        self._test_list_backups(detail=True, mock_args=mock_args,
-                                bytes_body=True, limit=10)
diff --git a/tempest/tests/lib/services/volume/v2/test_volumes_client.py b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
deleted file mode 100644
index d7b042e..0000000
--- a/tempest/tests/lib/services/volume/v2/test_volumes_client.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.services.volume.v2 import volumes_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestVolumesClient(base.BaseServiceTest):
-
-    FAKE_VOLUME_METADATA_ITEM = {
-        "meta": {
-            "key1": "value1"
-        }
-    }
-
-    FAKE_VOLUME_IMAGE_METADATA = {
-        "metadata": {
-            "container_format": "bare",
-            "min_ram": "0",
-            "disk_format": "raw",
-            "image_name": "xly-ubuntu16-server",
-            "image_id": "3e087b0c-10c5-4255-b147-6e8e9dbad6fc",
-            "checksum": "008f5d22fe3cb825d714da79607a90f9",
-            "min_disk": "0",
-            "size": "8589934592"
-        }
-    }
-
-    def setUp(self):
-        super(TestVolumesClient, self).setUp()
-        fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.client = volumes_client.VolumesClient(fake_auth,
-                                                   'volume',
-                                                   'regionOne')
-
-    def _test_retype_volume(self, bytes_body=False):
-        kwargs = {
-            "new_type": "dedup-tier-replication",
-            "migration_policy": "never"
-        }
-
-        self.check_service_client_function(
-            self.client.retype_volume,
-            'tempest.lib.common.rest_client.RestClient.post',
-            {},
-            to_utf=bytes_body,
-            status=202,
-            volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
-            **kwargs
-        )
-
-    def _test_force_detach_volume(self, bytes_body=False):
-        kwargs = {
-            'attachment_id': '6980e295-920f-412e-b189-05c50d605acd',
-            'connector': {
-                'initiator': 'iqn.2017-04.org.fake:01'
-            }
-        }
-
-        self.check_service_client_function(
-            self.client.force_detach_volume,
-            'tempest.lib.common.rest_client.RestClient.post',
-            {},
-            to_utf=bytes_body,
-            status=202,
-            volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
-            **kwargs
-        )
-
-    def _test_show_volume_metadata_item(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.show_volume_metadata_item,
-            'tempest.lib.common.rest_client.RestClient.get',
-            self.FAKE_VOLUME_METADATA_ITEM,
-            to_utf=bytes_body,
-            volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
-            id="key1")
-
-    def _test_show_volume_image_metadata(self, bytes_body=False):
-        fake_volume_id = "a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8"
-        self.check_service_client_function(
-            self.client.show_volume_image_metadata,
-            'tempest.lib.common.rest_client.RestClient.post',
-            self.FAKE_VOLUME_IMAGE_METADATA,
-            to_utf=bytes_body,
-            mock_args=['volumes/%s/action' % fake_volume_id,
-                       json.dumps({"os-show_image_metadata": {}})],
-            volume_id=fake_volume_id)
-
-    def test_force_detach_volume_with_str_body(self):
-        self._test_force_detach_volume()
-
-    def test_force_detach_volume_with_bytes_body(self):
-        self._test_force_detach_volume(bytes_body=True)
-
-    def test_show_volume_metadata_item_with_str_body(self):
-        self._test_show_volume_metadata_item()
-
-    def test_show_volume_metadata_item_with_bytes_body(self):
-        self._test_show_volume_metadata_item(bytes_body=True)
-
-    def test_show_volume_image_metadata_with_str_body(self):
-        self._test_show_volume_image_metadata()
-
-    def test_show_volume_image_metadata_with_bytes_body(self):
-        self._test_show_volume_image_metadata(bytes_body=True)
-
-    def test_retype_volume_with_str_body(self):
-        self._test_retype_volume()
-
-    def test_retype_volume_with_bytes_body(self):
-        self._test_retype_volume(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py b/tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
similarity index 96%
rename from tempest/tests/lib/services/volume/v2/test_availability_zone_client.py
rename to tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
index 770565c..4827326 100644
--- a/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import availability_zone_client
+from tempest.lib.services.volume.v3 import availability_zone_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v3/test_backups_client.py b/tempest/tests/lib/services/volume/v3/test_backups_client.py
index f1ce987..5412064 100644
--- a/tempest/tests/lib/services/volume/v3/test_backups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_backups_client.py
@@ -20,6 +20,55 @@
 
 class TestBackupsClient(base.BaseServiceTest):
 
+    FAKE_BACKUP_LIST = {
+        "backups": [
+            {
+                "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
+                "links": [
+                    {
+                        "href": "fake-url-1",
+                        "rel": "self"
+                    },
+                    {
+                        "href": "fake-url-2",
+                        "rel": "bookmark"
+                    }
+                ],
+                "name": "backup001"
+            }
+        ]
+    }
+
+    FAKE_BACKUP_LIST_WITH_DETAIL = {
+        "backups": [
+            {
+                "availability_zone": "az1",
+                "container": "volumebackups",
+                "created_at": "2013-04-02T10:35:27.000000",
+                "description": None,
+                "fail_reason": None,
+                "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
+                "links": [
+                    {
+                        "href": "fake-url-1",
+                        "rel": "self"
+                    },
+                    {
+                        "href": "fake-url-2",
+                        "rel": "bookmark"
+                    }
+                ],
+                "name": "backup001",
+                "object_count": 22,
+                "size": 1,
+                "status": "available",
+                "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
+                "is_incremental": True,
+                "has_dependent_backups": False
+            }
+        ]
+    }
+
     FAKE_BACKUP_UPDATE = {
         "backup": {
             "id": "4c65c15f-a5c5-464b-b92a-90e4c04636a7",
@@ -35,6 +84,46 @@
                                                    'volume',
                                                    'regionOne')
 
+    def _test_list_backups(self, detail=False, mock_args='backups',
+                           bytes_body=False, **params):
+        if detail:
+            resp_body = self.FAKE_BACKUP_LIST_WITH_DETAIL
+        else:
+            resp_body = self.FAKE_BACKUP_LIST
+        self.check_service_client_function(
+            self.client.list_backups,
+            'tempest.lib.common.rest_client.RestClient.get',
+            resp_body,
+            to_utf=bytes_body,
+            mock_args=[mock_args],
+            detail=detail,
+            **params)
+
+    def test_list_backups_with_str_body(self):
+        self._test_list_backups()
+
+    def test_list_backups_with_bytes_body(self):
+        self._test_list_backups(bytes_body=True)
+
+    def test_list_backups_with_detail_with_str_body(self):
+        mock_args = "backups/detail"
+        self._test_list_backups(detail=True, mock_args=mock_args)
+
+    def test_list_backups_with_detail_with_bytes_body(self):
+        mock_args = "backups/detail"
+        self._test_list_backups(detail=True, mock_args=mock_args,
+                                bytes_body=True)
+
+    def test_list_backups_with_params(self):
+        # Run the test separately for each param, to avoid assertion error
+        # resulting from randomized params order.
+        mock_args = 'backups?sort_key=name'
+        self._test_list_backups(mock_args=mock_args, sort_key='name')
+
+        mock_args = 'backups/detail?limit=10'
+        self._test_list_backups(detail=True, mock_args=mock_args,
+                                bytes_body=True, limit=10)
+
     def _test_update_backup(self, bytes_body=False):
         self.check_service_client_function(
             self.client.update_backup,
diff --git a/tempest/tests/lib/services/volume/v2/test_capabilities_client.py b/tempest/tests/lib/services/volume/v3/test_capabilities_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_capabilities_client.py
rename to tempest/tests/lib/services/volume/v3/test_capabilities_client.py
index 3d3f1e1..7efe1ff 100644
--- a/tempest/tests/lib/services/volume/v2/test_capabilities_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_capabilities_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import capabilities_client
+from tempest.lib.services.volume.v3 import capabilities_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
similarity index 81%
rename from tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
rename to tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
index d029091..c788181 100644
--- a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.lib.services.volume.v2 import encryption_types_client
+from tempest.lib.services.volume.v3 import encryption_types_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
@@ -43,6 +43,10 @@
         }
     }
 
+    FAKE_ENCRYPTION_SPECS_ITEM = {
+        "cipher": "aes-xts-plain64"
+    }
+
     def setUp(self):
         super(TestEncryptionTypesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -65,6 +69,13 @@
             self.FAKE_INFO_ENCRYPTION_TYPE,
             bytes_body, volume_type_id="cbc36478b0bd8e67e89")
 
+    def _test_show_encryption_specs_item(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_encryption_specs_item,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ENCRYPTION_SPECS_ITEM,
+            bytes_body, volume_type_id="cbc36478b0bd8e67e89", key="cipher")
+
     def test_create_encryption_type_with_str_body(self):
         self._test_create_encryption()
 
@@ -77,6 +88,12 @@
     def test_show_encryption_type_with_bytes_body(self):
         self._test_show_encryption_type(bytes_body=True)
 
+    def test_show_encryption_specs_item_with_str_body(self):
+        self._test_show_encryption_specs_item()
+
+    def test_show_encryption_specs_item_with_bytes_body(self):
+        self._test_show_encryption_specs_item(bytes_body=True)
+
     def test_delete_encryption_type(self):
         self.check_service_client_function(
             self.client.delete_encryption_type,
diff --git a/tempest/tests/lib/services/volume/v2/test_extensions_client.py b/tempest/tests/lib/services/volume/v3/test_extensions_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_extensions_client.py
rename to tempest/tests/lib/services/volume/v3/test_extensions_client.py
index c0ee421..a8bbffd 100644
--- a/tempest/tests/lib/services/volume/v2/test_extensions_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_extensions_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import extensions_client
+from tempest.lib.services.volume.v3 import extensions_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v3/test_group_types_client.py b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
index 0f456a2..c60cc36 100644
--- a/tempest/tests/lib/services/volume/v3/test_group_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
@@ -12,6 +12,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import copy
+
 from tempest.lib.services.volume.v3 import group_types_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
@@ -67,6 +69,28 @@
         ]
     }
 
+    FAKE_CREATE_GROUP_TYPE_SPECS = {
+        "group_specs": {
+            "key1": "value1",
+            "key2": "value2"
+        }
+    }
+
+    FAKE_LIST_GROUP_TYPE_SPECS = {
+        "group_specs": {
+            "key1": "value1",
+            "key2": "value2"
+        }
+    }
+
+    FAKE_SHOW_GROUP_TYPE_SPECS_ITEM = {
+        "key1": "value1"
+    }
+
+    FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM = {
+        "key2": "value2-updated"
+    }
+
     def setUp(self):
         super(TestGroupTypesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -97,6 +121,57 @@
             self.FAKE_LIST_GROUP_TYPES,
             bytes_body)
 
+    def _test_update_group_types(self, bytes_body=False):
+        resp_body = copy.deepcopy(self.FAKE_INFO_GROUP_TYPE)
+        resp_body['group_type'].pop('created_at')
+
+        self.check_service_client_function(
+            self.client.update_group_type,
+            'tempest.lib.common.rest_client.RestClient.put',
+            resp_body,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+            name='updated-group-type-name')
+
+    def _test_create_or_update_group_type_specs(self, bytes_body=False):
+        group_specs = self.FAKE_CREATE_GROUP_TYPE_SPECS['group_specs']
+        self.check_service_client_function(
+            self.client.create_or_update_group_type_specs,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_GROUP_TYPE_SPECS,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+            group_specs=group_specs,
+            status=202)
+
+    def _test_list_group_type_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_group_type_specs,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_GROUP_TYPE_SPECS,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
+
+    def _test_show_group_type_specs_item(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_group_type_specs_item,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_GROUP_TYPE_SPECS_ITEM,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+            spec_id="key1")
+
+    def _test_update_group_type_specs_item(self, bytes_body=False):
+        spec = self.FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM
+        self.check_service_client_function(
+            self.client.update_group_type_specs_item,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+            spec_id="key2",
+            spec=spec)
+
     def test_create_group_type_with_str_body(self):
         self._test_create_group_type()
 
@@ -122,3 +197,42 @@
 
     def test_list_group_types_with_bytes_body(self):
         self._test_list_group_types(bytes_body=True)
+
+    def test_update_group_types_with_str_body(self):
+        self._test_update_group_types()
+
+    def test_update_group_types_with_bytes_body(self):
+        self._test_update_group_types(bytes_body=True)
+
+    def test_create_or_update_group_type_specs_with_str_body(self):
+        self._test_create_or_update_group_type_specs()
+
+    def test_create_or_update_group_type_specs_with_bytes_body(self):
+        self._test_create_or_update_group_type_specs(bytes_body=True)
+
+    def test_list_group_type_specs_with_str_body(self):
+        self._test_list_group_type_specs()
+
+    def test_list_group_type_specs_with_bytes_body(self):
+        self._test_list_group_type_specs(bytes_body=True)
+
+    def test_show_group_type_specs_item_with_str_body(self):
+        self._test_show_group_type_specs_item()
+
+    def test_show_group_type_specs_item_with_bytes_body(self):
+        self._test_show_group_type_specs_item(bytes_body=True)
+
+    def test_update_group_type_specs_item_with_str_body(self):
+        self._test_update_group_type_specs_item()
+
+    def test_update_group_type_specs_item_with_bytes_body(self):
+        self._test_update_group_type_specs_item(bytes_body=True)
+
+    def test_delete_group_type_specs_item(self):
+        self.check_service_client_function(
+            self.client.delete_group_type_specs_item,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            group_type_id='0e58433f-d108-4bf3-a22c-34e6b71ef86b',
+            spec_id='key1',
+            status=202)
diff --git a/tempest/tests/lib/services/volume/v2/test_hosts_client.py b/tempest/tests/lib/services/volume/v3/test_hosts_client.py
similarity index 95%
rename from tempest/tests/lib/services/volume/v2/test_hosts_client.py
rename to tempest/tests/lib/services/volume/v3/test_hosts_client.py
index e107910..09bc0b1 100644
--- a/tempest/tests/lib/services/volume/v2/test_hosts_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_hosts_client.py
@@ -13,12 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import hosts_client
+from tempest.lib.services.volume.v3 import hosts_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
 
-class TestQuotasClient(base.BaseServiceTest):
+class TestHostsClient(base.BaseServiceTest):
     FAKE_LIST_HOSTS = {
         "hosts": [
             {
@@ -66,7 +66,7 @@
     }
 
     def setUp(self):
-        super(TestQuotasClient, self).setUp()
+        super(TestHostsClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
         self.client = hosts_client.HostsClient(fake_auth,
                                                'volume',
diff --git a/tempest/tests/lib/services/volume/v2/test_limits_client.py b/tempest/tests/lib/services/volume/v3/test_limits_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_limits_client.py
rename to tempest/tests/lib/services/volume/v3/test_limits_client.py
index 202054c..f94fbe1 100644
--- a/tempest/tests/lib/services/volume/v2/test_limits_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_limits_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import limits_client
+from tempest.lib.services.volume.v3 import limits_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py b/tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
rename to tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
index e715fcc..6190733 100644
--- a/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
@@ -15,7 +15,7 @@
 
 import copy
 
-from tempest.lib.services.volume.v2 import quota_classes_client
+from tempest.lib.services.volume.v3 import quota_classes_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_quotas_client.py b/tempest/tests/lib/services/volume/v3/test_quotas_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_quotas_client.py
rename to tempest/tests/lib/services/volume/v3/test_quotas_client.py
index 6384350..aa5d251 100644
--- a/tempest/tests/lib/services/volume/v2/test_quotas_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_quotas_client.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.lib.services.volume.v2 import quotas_client
+from tempest.lib.services.volume.v3 import quotas_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py b/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
similarity index 95%
rename from tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
rename to tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
index 8a5f25f..84c7589 100644
--- a/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.lib.services.volume.v2 import scheduler_stats_client
+from tempest.lib.services.volume.v3 import scheduler_stats_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
@@ -62,7 +62,7 @@
             resp_body = self.FAKE_POOLS_LIST
         else:
             resp_body = {'pools': [{'name': pool['name']}
-                         for pool in self.FAKE_POOLS_LIST['pools']]}
+                                   for pool in self.FAKE_POOLS_LIST['pools']]}
         self.check_service_client_function(
             self.client.list_pools,
             'tempest.lib.common.rest_client.RestClient.get',
diff --git a/tempest/tests/lib/services/volume/v3/test_services_client.py b/tempest/tests/lib/services/volume/v3/test_services_client.py
new file mode 100644
index 0000000..f65228f
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_services_client.py
@@ -0,0 +1,214 @@
+# Copyright 2018 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+
+import mock
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.services.volume.v3 import services_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestServicesClient(base.BaseServiceTest):
+
+    FAKE_SERVICE_LIST = {
+        "services": [
+            {
+                "status": "enabled",
+                "binary": "cinder-backup",
+                "zone": "nova",
+                "state": "up",
+                "updated_at": "2017-07-20T07:20:17.000000",
+                "host": "fake-host",
+                "disabled_reason": None
+            },
+            {
+                "status": "enabled",
+                "binary": "cinder-scheduler",
+                "zone": "nova",
+                "state": "up",
+                "updated_at": "2017-07-20T07:20:24.000000",
+                "host": "fake-host",
+                "disabled_reason": None
+            },
+            {
+                "status": "enabled",
+                "binary": "cinder-volume",
+                "zone": "nova",
+                "frozen": False,
+                "state": "up",
+                "updated_at": "2017-07-20T07:20:20.000000",
+                "host": "fake-host@lvm",
+                "replication_status": "disabled",
+                "active_backend_id": None,
+                "disabled_reason": None
+            }
+        ]
+    }
+
+    FAKE_SERVICE_REQUEST = {
+        "host": "fake-host",
+        "binary": "cinder-volume"
+    }
+
+    FAKE_SERVICE_RESPONSE = {
+        "disabled": False,
+        "status": "enabled",
+        "host": "fake-host@lvm",
+        "service": "",
+        "binary": "cinder-volume",
+        "disabled_reason": None
+    }
+
+    def setUp(self):
+        super(TestServicesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = services_client.ServicesClient(fake_auth,
+                                                     'volume',
+                                                     'regionOne')
+
+    def _test_list_services(self, bytes_body=False,
+                            mock_args='os-services', **params):
+        self.check_service_client_function(
+            self.client.list_services,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SERVICE_LIST,
+            to_utf=bytes_body,
+            mock_args=[mock_args],
+            **params)
+
+    def _test_enable_service(self, bytes_body=False):
+        resp_body = self.FAKE_SERVICE_RESPONSE
+        kwargs = self.FAKE_SERVICE_REQUEST
+        payload = json.dumps(kwargs, sort_keys=True)
+        json_dumps = json.dumps
+
+        # NOTE: Use sort_keys for json.dumps so that the expected and actual
+        # payloads are guaranteed to be identical for mock_args assert check.
+        with mock.patch.object(services_client.json, 'dumps') as mock_dumps:
+            mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+            self.check_service_client_function(
+                self.client.enable_service,
+                'tempest.lib.common.rest_client.RestClient.put',
+                resp_body,
+                to_utf=bytes_body,
+                mock_args=['os-services/enable', payload],
+                **kwargs)
+
+    def _test_disable_service(self, bytes_body=False):
+        resp_body = copy.deepcopy(self.FAKE_SERVICE_RESPONSE)
+        resp_body.pop('disabled_reason')
+        resp_body['disabled'] = True
+        resp_body['status'] = 'disabled'
+        kwargs = self.FAKE_SERVICE_REQUEST
+        payload = json.dumps(kwargs, sort_keys=True)
+        json_dumps = json.dumps
+
+        # NOTE: Use sort_keys for json.dumps so that the expected and actual
+        # payloads are guaranteed to be identical for mock_args assert check.
+        with mock.patch.object(services_client.json, 'dumps') as mock_dumps:
+            mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+            self.check_service_client_function(
+                self.client.disable_service,
+                'tempest.lib.common.rest_client.RestClient.put',
+                resp_body,
+                to_utf=bytes_body,
+                mock_args=['os-services/disable', payload],
+                **kwargs)
+
+    def _test_disable_log_reason(self, bytes_body=False):
+        resp_body = copy.deepcopy(self.FAKE_SERVICE_RESPONSE)
+        resp_body['disabled_reason'] = "disabled for test"
+        resp_body['disabled'] = True
+        resp_body['status'] = 'disabled'
+        kwargs = copy.deepcopy(self.FAKE_SERVICE_REQUEST)
+        kwargs.update({"disabled_reason": "disabled for test"})
+        payload = json.dumps(kwargs, sort_keys=True)
+        json_dumps = json.dumps
+
+        # NOTE: Use sort_keys for json.dumps so that the expected and actual
+        # payloads are guaranteed to be identical for mock_args assert check.
+        with mock.patch.object(services_client.json, 'dumps') as mock_dumps:
+            mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+            self.check_service_client_function(
+                self.client.disable_log_reason,
+                'tempest.lib.common.rest_client.RestClient.put',
+                resp_body,
+                to_utf=bytes_body,
+                mock_args=['os-services/disable-log-reason', payload],
+                **kwargs)
+
+    def _test_freeze_host(self, bytes_body=False):
+        kwargs = {'host': 'host1@lvm'}
+        self.check_service_client_function(
+            self.client.freeze_host,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            **kwargs)
+
+    def _test_thaw_host(self, bytes_body=False):
+        kwargs = {'host': 'host1@lvm'}
+        self.check_service_client_function(
+            self.client.thaw_host,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            **kwargs)
+
+    def test_list_services_with_str_body(self):
+        self._test_list_services()
+
+    def test_list_services_with_bytes_body(self):
+        self._test_list_services(bytes_body=True)
+
+    def test_list_services_with_params(self):
+        mock_args = 'os-services?host=fake-host'
+        self._test_list_services(mock_args=mock_args, host='fake-host')
+
+    def test_enable_service_with_str_body(self):
+        self._test_enable_service()
+
+    def test_enable_service_with_bytes_body(self):
+        self._test_enable_service(bytes_body=True)
+
+    def test_disable_service_with_str_body(self):
+        self._test_disable_service()
+
+    def test_disable_service_with_bytes_body(self):
+        self._test_disable_service(bytes_body=True)
+
+    def test_disable_log_reason_with_str_body(self):
+        self._test_disable_log_reason()
+
+    def test_disable_log_reason_with_bytes_body(self):
+        self._test_disable_log_reason(bytes_body=True)
+
+    def test_freeze_host_with_str_body(self):
+        self._test_freeze_host()
+
+    def test_freeze_host_with_bytes_body(self):
+        self._test_freeze_host(bytes_body=True)
+
+    def test_thaw_host_with_str_body(self):
+        self._test_thaw_host()
+
+    def test_thaw_host_with_bytes_body(self):
+        self._test_thaw_host(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py b/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
rename to tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
index 3fe8970..1b88020 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
@@ -17,7 +17,7 @@
 
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.services.volume.v2 import snapshot_manage_client
+from tempest.lib.services.volume.v3 import snapshot_manage_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py b/tempest/tests/lib/services/volume/v3/test_snapshots_client.py
similarity index 99%
rename from tempest/tests/lib/services/volume/v2/test_snapshots_client.py
rename to tempest/tests/lib/services/volume/v3/test_snapshots_client.py
index c9f57a0..2efd2e6 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_snapshots_client.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.lib.services.volume.v2 import snapshots_client
+from tempest.lib.services.volume.v3 import snapshots_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v2/test_transfers_client.py b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
similarity index 98%
rename from tempest/tests/lib/services/volume/v2/test_transfers_client.py
rename to tempest/tests/lib/services/volume/v3/test_transfers_client.py
index 84f4992..d631fe7 100644
--- a/tempest/tests/lib/services/volume/v2/test_transfers_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
@@ -18,7 +18,7 @@
 import mock
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.services.volume.v2 import transfers_client
+from tempest.lib.services.volume.v3 import transfers_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v3/test_types_client.py b/tempest/tests/lib/services/volume/v3/test_types_client.py
new file mode 100644
index 0000000..7021a3f
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_types_client.py
@@ -0,0 +1,281 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.services.volume.v3 import types_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTypesClient(base.BaseServiceTest):
+    FAKE_CREATE_VOLUME_TYPE = {
+        'volume_type': {
+            'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+            'name': 'vol-type-001',
+            'description': 'volume type 0001',
+            'is_public': True,
+            'os-volume-type-access:is_public': True,
+            'extra_specs': {
+                'volume_backend_name': 'rbd'
+            }
+        }
+    }
+
+    FAKE_DEFAULT_VOLUME_TYPE_INFO = {
+        'volume_type': {
+            'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+            'qos_specs_id': None,
+            'name': 'volume-type-test',
+            'description': 'default volume type',
+            'is_public': True,
+            'os-volume-type-access:is_public': True,
+            'extra_specs': {
+                'volume_backend_name': 'rbd'
+            }
+        }
+    }
+
+    FAKE_UPDATE_VOLUME_TYPE = {
+        'volume_type': {
+            'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+            'qos_specs_id': None,
+            'name': 'volume-type-test',
+            'description': 'default volume type',
+            'os-volume-type-access:is_public': True,
+            'is_public': True,
+            'extra_specs': {
+                'volume_backend_name': 'rbd'
+            }
+        }
+    }
+
+    FAKE_VOLUME_TYPES = {
+        'volume_types': [
+            {
+                'name': 'volume_type01',
+                'qos_specs_id': None,
+                'extra_specs': {
+                    'volume_backend_name': 'lvmdriver-1'
+                },
+                'os-volume-type-access:is_public': True,
+                'is_public': True,
+                'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+                'description': None
+            },
+            {
+                'name': 'volume_type02',
+                'qos_specs_id': None,
+                'extra_specs': {
+                    'volume_backend_name': 'lvmdriver-1'
+                },
+                'os-volume-type-access:is_public': True,
+                'is_public': True,
+                'id': '8eb69a46-df97-4e41-9586-9a40a7533803',
+                'description': None
+            }
+        ]
+    }
+
+    FAKE_VOLUME_TYPE_EXTRA_SPECS = {
+        'extra_specs': {
+            'capabilities': 'gpu'
+        }
+    }
+
+    FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS = {
+        'capabilities': 'gpu'
+    }
+
+    FAKE_VOLUME_TYPE_ACCESS = {
+        'volume_type_access': [{
+            'volume_type_id': '3c67e124-39ad-4ace-a507-8bb7bf510c26',
+            'project_id': 'f270b245cb11498ca4031deb7e141cfa'
+        }]
+    }
+
+    def setUp(self):
+        super(TestTypesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = types_client.TypesClient(fake_auth,
+                                               'volume',
+                                               'regionOne')
+
+    def _test_list_volume_types(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_volume_types,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VOLUME_TYPES,
+            bytes_body)
+
+    def _test_show_volume_type(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_volume_type,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_DEFAULT_VOLUME_TYPE_INFO,
+            to_utf=bytes_body,
+            volume_type_id="6685584b-1eac-4da6-b5c3-555430cf68ff")
+
+    def _test_create_volume_type(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_volume_type,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_VOLUME_TYPE,
+            to_utf=bytes_body,
+            name='volume-type-test')
+
+    def _test_delete_volume_type(self):
+        self.check_service_client_function(
+            self.client.delete_volume_type,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {}, status=202,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+    def _test_list_volume_types_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_volume_types_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+            to_utf=bytes_body,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+    def _test_show_volume_type_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_volume_type_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+            extra_specs_name='capabilities',
+            to_utf=bytes_body)
+
+    def _test_create_volume_type_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_volume_type_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+            volume_type_id="6685584b-1eac-4da6-b5c3-555430cf68ff",
+            extra_specs=self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+            to_utf=bytes_body)
+
+    def _test_delete_volume_type_extra_specs(self):
+        self.check_service_client_function(
+            self.client.delete_volume_type_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {}, status=202,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+            extra_spec_name='volume_backend_name')
+
+    def _test_update_volume_type(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_volume_type,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_UPDATE_VOLUME_TYPE,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+            to_utf=bytes_body,
+            name='update-volume-type-test',
+            description='test update volume type description')
+
+    def _test_update_volume_type_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_volume_type_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+            extra_spec_name='capabilities',
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+            extra_specs=self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+            to_utf=bytes_body)
+
+    def _test_add_type_access(self):
+        self.check_service_client_function(
+            self.client.add_type_access,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {}, status=202,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+    def _test_remove_type_access(self):
+        self.check_service_client_function(
+            self.client.remove_type_access,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {}, status=202,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+    def _test_list_type_access(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_type_access,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VOLUME_TYPE_ACCESS,
+            volume_type_id='3c67e124-39ad-4ace-a507-8bb7bf510c26',
+            to_utf=bytes_body)
+
+    def test_list_volume_types_with_str_body(self):
+        self._test_list_volume_types()
+
+    def test_list_volume_types_with_bytes_body(self):
+        self._test_list_volume_types(bytes_body=True)
+
+    def test_show_volume_type_with_str_body(self):
+        self._test_show_volume_type()
+
+    def test_show_volume_type_with_bytes_body(self):
+        self._test_show_volume_type(bytes_body=True)
+
+    def test_create_volume_type_str_body(self):
+        self._test_create_volume_type()
+
+    def test_create_volume_type_with_bytes_body(self):
+        self._test_create_volume_type(bytes_body=True)
+
+    def test_list_volume_types_extra_specs_with_str_body(self):
+        self._test_list_volume_types_extra_specs()
+
+    def test_list_volume_types_extra_specs_with_bytes_body(self):
+        self._test_list_volume_types_extra_specs(bytes_body=True)
+
+    def test_show_volume_type_extra_specs_with_str_body(self):
+        self._test_show_volume_type_extra_specs()
+
+    def test_show_volume_type_extra_specs_with_bytes_body(self):
+        self._test_show_volume_type_extra_specs(bytes_body=True)
+
+    def test_create_volume_type_extra_specs_with_str_body(self):
+        self._test_create_volume_type_extra_specs()
+
+    def test_create_volume_type_extra_specs_with_bytes_body(self):
+        self._test_create_volume_type_extra_specs(bytes_body=True)
+
+    def test_delete_volume_type_extra_specs(self):
+        self._test_delete_volume_type_extra_specs()
+
+    def test_update_volume_type_with_str_body(self):
+        self._test_update_volume_type()
+
+    def test_update_volume_type_with_bytes_body(self):
+        self._test_update_volume_type(bytes_body=True)
+
+    def test_delete_volume_type(self):
+        self._test_delete_volume_type()
+
+    def test_update_volume_type_extra_specs_with_str_body(self):
+        self._test_update_volume_type_extra_specs()
+
+    def test_update_volume_type_extra_specs_with_bytes_body(self):
+        self._test_update_volume_type_extra_specs(bytes_body=True)
+
+    def test_add_type_access(self):
+        self._test_add_type_access()
+
+    def test_remove_type_access(self):
+        self._test_remove_type_access()
+
+    def test_list_type_access_with_str_body(self):
+        self._test_list_type_access()
+
+    def test_list_type_access_with_bytes_body(self):
+        self._test_list_type_access(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
similarity index 98%
rename from tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
rename to tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
index ea4a9f9..902f027 100644
--- a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
@@ -17,7 +17,7 @@
 
 from oslo_serialization import jsonutils as json
 
-from tempest.lib.services.volume.v2 import volume_manage_client
+from tempest.lib.services.volume.v3 import volume_manage_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/volume/v3/test_volumes_client.py b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
index a515fd3..1250536 100644
--- a/tempest/tests/lib/services/volume/v3/test_volumes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from oslo_serialization import jsonutils as json
+
 from tempest.lib.services.volume.v3 import volumes_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
@@ -27,6 +29,25 @@
         }
     }
 
+    FAKE_VOLUME_METADATA_ITEM = {
+        "meta": {
+            "key1": "value1"
+        }
+    }
+
+    FAKE_VOLUME_IMAGE_METADATA = {
+        "metadata": {
+            "container_format": "bare",
+            "min_ram": "0",
+            "disk_format": "raw",
+            "image_name": "xly-ubuntu16-server",
+            "image_id": "3e087b0c-10c5-4255-b147-6e8e9dbad6fc",
+            "checksum": "008f5d22fe3cb825d714da79607a90f9",
+            "min_disk": "0",
+            "size": "8589934592"
+        }
+    }
+
     def setUp(self):
         super(TestVolumesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -34,6 +55,60 @@
                                                    'volume',
                                                    'regionOne')
 
+    def _test_retype_volume(self, bytes_body=False):
+        kwargs = {
+            "new_type": "dedup-tier-replication",
+            "migration_policy": "never"
+        }
+
+        self.check_service_client_function(
+            self.client.retype_volume,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {},
+            to_utf=bytes_body,
+            status=202,
+            volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+            **kwargs
+        )
+
+    def _test_force_detach_volume(self, bytes_body=False):
+        kwargs = {
+            'attachment_id': '6980e295-920f-412e-b189-05c50d605acd',
+            'connector': {
+                'initiator': 'iqn.2017-04.org.fake:01'
+            }
+        }
+
+        self.check_service_client_function(
+            self.client.force_detach_volume,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {},
+            to_utf=bytes_body,
+            status=202,
+            volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+            **kwargs
+        )
+
+    def _test_show_volume_metadata_item(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_volume_metadata_item,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VOLUME_METADATA_ITEM,
+            to_utf=bytes_body,
+            volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+            id="key1")
+
+    def _test_show_volume_image_metadata(self, bytes_body=False):
+        fake_volume_id = "a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8"
+        self.check_service_client_function(
+            self.client.show_volume_image_metadata,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_VOLUME_IMAGE_METADATA,
+            to_utf=bytes_body,
+            mock_args=['volumes/%s/action' % fake_volume_id,
+                       json.dumps({"os-show_image_metadata": {}})],
+            volume_id=fake_volume_id)
+
     def _test_show_volume_summary(self, bytes_body=False):
         self.check_service_client_function(
             self.client.show_volume_summary,
@@ -41,6 +116,30 @@
             self.FAKE_VOLUME_SUMMARY,
             bytes_body)
 
+    def test_force_detach_volume_with_str_body(self):
+        self._test_force_detach_volume()
+
+    def test_force_detach_volume_with_bytes_body(self):
+        self._test_force_detach_volume(bytes_body=True)
+
+    def test_show_volume_metadata_item_with_str_body(self):
+        self._test_show_volume_metadata_item()
+
+    def test_show_volume_metadata_item_with_bytes_body(self):
+        self._test_show_volume_metadata_item(bytes_body=True)
+
+    def test_show_volume_image_metadata_with_str_body(self):
+        self._test_show_volume_image_metadata()
+
+    def test_show_volume_image_metadata_with_bytes_body(self):
+        self._test_show_volume_image_metadata(bytes_body=True)
+
+    def test_retype_volume_with_str_body(self):
+        self._test_retype_volume()
+
+    def test_retype_volume_with_bytes_body(self):
+        self._test_retype_volume(bytes_body=True)
+
     def test_show_volume_summary_with_str_body(self):
         self._test_show_volume_summary()
 
diff --git a/tempest/tests/lib/test_api_microversion_fixture.py b/tempest/tests/lib/test_api_microversion_fixture.py
new file mode 100644
index 0000000..ad98ed0
--- /dev/null
+++ b/tempest/tests/lib/test_api_microversion_fixture.py
@@ -0,0 +1,58 @@
+# Copyright 2019 NEC Corporation.
+#
+#    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.common import api_microversion_fixture
+from tempest.lib.services.compute import base_compute_client
+from tempest.lib.services.placement import base_placement_client
+from tempest.lib.services.volume import base_client
+from tempest.tests import base
+
+
+class TestAPIMicroversionFixture(base.TestCase):
+    def setUp(self):
+        super(TestAPIMicroversionFixture, self).setUp()
+        # Verify that all the microversion are reset back to None
+        # by Fixture.
+        self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION)
+        self.assertIsNone(base_client.VOLUME_MICROVERSION)
+        self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_compute_microversion(self):
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            compute_microversion='2.10'))
+        self.assertEqual('2.10', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertIsNone(base_client.VOLUME_MICROVERSION)
+        self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_volume_microversion(self):
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            volume_microversion='3.10'))
+        self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.10', base_client.VOLUME_MICROVERSION)
+        self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_placement_microversion(self):
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            placement_microversion='1.10'))
+        self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION)
+        self.assertIsNone(base_client.VOLUME_MICROVERSION)
+        self.assertEqual('1.10', base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_multiple_service_microversion(self):
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            compute_microversion='2.10', volume_microversion='3.10',
+            placement_microversion='1.10'))
+        self.assertEqual('2.10', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.10', base_client.VOLUME_MICROVERSION)
+        self.assertEqual('1.10', base_placement_client.PLACEMENT_MICROVERSION)
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index bbebcd3..9c6cac7 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -13,12 +13,16 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import abc
+
 import mock
+import six
 import testtools
 
 from tempest.lib import base as test
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
 from tempest.tests import base
 
 
@@ -31,9 +35,17 @@
         # By our decorators.attr decorator the attribute __testtools_attrs
         # will be set only for 'type' argument, so we test it first.
         if 'type' in decorator_args:
-            # this is what testtools sets
-            self.assertEqual(getattr(foo, '__testtools_attrs'),
-                             set(expected_attrs))
+            if 'condition' in decorator_args:
+                if decorator_args['condition']:
+                    # The expected attrs should be in the function.
+                    self.assertEqual(set(expected_attrs),
+                                     getattr(foo, '__testtools_attrs'))
+                else:
+                    # The expected attrs should not be in the function.
+                    self.assertNotIn('__testtools_attrs', foo)
+            else:
+                self.assertEqual(set(expected_attrs),
+                                 getattr(foo, '__testtools_attrs'))
 
     def test_attr_without_type(self):
         self._test_attr_helper(expected_attrs='baz', bar='baz')
@@ -49,10 +61,44 @@
     def test_attr_decorator_with_duplicated_type(self):
         self._test_attr_helper(expected_attrs=['foo'], type=['foo', 'foo'])
 
+    def test_attr_decorator_condition_false(self):
+        self._test_attr_helper(None, type='slow', condition=False)
 
-class TestSkipBecauseDecorator(base.TestCase):
-    def _test_skip_because_helper(self, expected_to_skip=True,
-                                  **decorator_args):
+    def test_attr_decorator_condition_true(self):
+        self._test_attr_helper(expected_attrs=['slow'], type='slow',
+                               condition=True)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class BaseSkipDecoratorTests(object):
+
+    @abc.abstractmethod
+    def _test_skip_helper(self, raise_exception=True, expected_to_skip=True,
+                          **decorator_args):
+        return
+
+    def test_skip_launchpad_bug(self):
+        self._test_skip_helper(bug='12345')
+
+    def test_skip_storyboard_bug(self):
+        self._test_skip_helper(bug='1992', bug_type='storyboard')
+
+    def test_skip_bug_without_bug_never_skips(self):
+        """Never skip without a bug parameter."""
+        self._test_skip_helper(
+            raise_exception=False, expected_to_skip=False, condition=True)
+        self._test_skip_helper(
+            raise_exception=False, expected_to_skip=False)
+
+    def test_skip_invalid_bug_number(self):
+        """Raise InvalidParam if with an invalid bug number"""
+        self.assertRaises(lib_exc.InvalidParam, self._test_skip_helper,
+                          bug='critical_bug')
+
+
+class TestSkipBecauseDecorator(base.TestCase, BaseSkipDecoratorTests):
+    def _test_skip_helper(self, raise_exception=True, expected_to_skip=True,
+                          **decorator_args):
         class TestFoo(test.BaseTestCase):
             _interface = 'json'
 
@@ -62,31 +108,68 @@
 
         t = TestFoo('test_bar')
         if expected_to_skip:
-            self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+            e = self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+            bug = decorator_args['bug']
+            bug_type = decorator_args.get('bug_type', 'launchpad')
+            self.assertRegex(
+                str(e),
+                r'Skipped until bug\: %s.*' % decorators._get_bug_url(
+                    bug, bug_type)
+            )
         else:
             # assert that test_bar returned 0
             self.assertEqual(TestFoo('test_bar').test_bar(), 0)
 
-    def test_skip_because_bug(self):
-        self._test_skip_because_helper(bug='12345')
+    def test_skip_because_launchpad_bug_and_condition_true(self):
+        self._test_skip_helper(bug='12348', condition=True)
 
-    def test_skip_because_bug_and_condition_true(self):
-        self._test_skip_because_helper(bug='12348', condition=True)
+    def test_skip_because_launchpad_bug_and_condition_false(self):
+        self._test_skip_helper(expected_to_skip=False,
+                               bug='12349', condition=False)
 
-    def test_skip_because_bug_and_condition_false(self):
-        self._test_skip_because_helper(expected_to_skip=False,
-                                       bug='12349', condition=False)
+    def test_skip_because_storyboard_bug_and_condition_false(self):
+        self._test_skip_helper(expected_to_skip=False,
+                               bug='1992', bug_type='storyboard',
+                               condition=False)
 
-    def test_skip_because_bug_without_bug_never_skips(self):
-        """Never skip without a bug parameter."""
-        self._test_skip_because_helper(expected_to_skip=False,
-                                       condition=True)
-        self._test_skip_because_helper(expected_to_skip=False)
+    def test_skip_because_storyboard_bug_and_condition_true(self):
+        self._test_skip_helper(bug='1992', bug_type='storyboard',
+                               condition=True)
 
-    def test_skip_because_invalid_bug_number(self):
-        """Raise ValueError if with an invalid bug number"""
-        self.assertRaises(ValueError, self._test_skip_because_helper,
-                          bug='critical_bug')
+
+class TestUnstableTestDecorator(base.TestCase, BaseSkipDecoratorTests):
+
+    def _test_skip_helper(self, raise_exception=True, expected_to_skip=True,
+                          **decorator_args):
+        fail_test_reason = "test_bar failed"
+
+        class TestFoo(test.BaseTestCase):
+
+            @decorators.unstable_test(**decorator_args)
+            def test_bar(self):
+                if raise_exception:
+                    raise Exception(fail_test_reason)
+                else:
+                    return 0
+
+        t = TestFoo('test_bar')
+        if expected_to_skip:
+            e = self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+            bug = decorator_args['bug']
+            bug_type = decorator_args.get('bug_type', 'launchpad')
+            self.assertRegex(
+                str(e),
+                r'Marked as unstable and skipped because of bug\: %s.*, '
+                'failure was: %s' % (decorators._get_bug_url(bug, bug_type),
+                                     fail_test_reason)
+            )
+        else:
+            # assert that test_bar returned 0
+            self.assertEqual(TestFoo('test_bar').test_bar(), 0)
+
+    def test_skip_bug_given_exception_not_raised(self):
+        self._test_skip_helper(raise_exception=False, expected_to_skip=False,
+                               bug='1234')
 
 
 class TestIdempotentIdDecorator(base.TestCase):
@@ -125,36 +208,14 @@
         self.assertRaises(ValueError, self._test_helper, _id)
 
 
-class TestSkipUnlessAttrDecorator(base.TestCase):
-    def _test_skip_unless_attr(self, attr, expected_to_skip=True):
-        class TestFoo(test.BaseTestCase):
-            expected_attr = not expected_to_skip
-
-            @decorators.skip_unless_attr(attr)
-            def test_foo(self):
-                pass
-
-        t = TestFoo('test_foo')
-        if expected_to_skip:
-            self.assertRaises(testtools.TestCase.skipException,
-                              t.test_foo)
-        else:
-            try:
-                t.test_foo()
-            except Exception:
-                raise testtools.TestCase.failureException()
-
-    def test_skip_attr_does_not_exist(self):
-        self._test_skip_unless_attr('unexpected_attr')
-
-    def test_skip_attr_false(self):
-        self._test_skip_unless_attr('expected_attr')
-
-    def test_no_skip_for_attr_exist_and_true(self):
-        self._test_skip_unless_attr('expected_attr', expected_to_skip=False)
-
-
 class TestRelatedBugDecorator(base.TestCase):
+
+    def _get_my_exception(self):
+        class MyException(Exception):
+            def __init__(self, status_code):
+                self.status_code = status_code
+        return MyException
+
     def test_relatedbug_when_no_exception(self):
         f = mock.Mock()
         sentinel = object()
@@ -166,10 +227,9 @@
         test_foo(sentinel)
         f.assert_called_once_with(sentinel)
 
-    def test_relatedbug_when_exception(self):
-        class MyException(Exception):
-            def __init__(self, status_code):
-                self.status_code = status_code
+    def test_relatedbug_when_exception_with_launchpad_bug_type(self):
+        """Validate related_bug decorator with bug_type == 'launchpad'"""
+        MyException = self._get_my_exception()
 
         def f(self):
             raise MyException(status_code=500)
@@ -181,4 +241,53 @@
         with mock.patch.object(decorators.LOG, 'error') as m_error:
             self.assertRaises(MyException, test_foo, object())
 
-        m_error.assert_called_once_with(mock.ANY, '1234', '1234')
+        m_error.assert_called_once_with(
+            mock.ANY, '1234', 'https://launchpad.net/bugs/1234')
+
+    def test_relatedbug_when_exception_with_storyboard_bug_type(self):
+        """Validate related_bug decorator with bug_type == 'storyboard'"""
+        MyException = self._get_my_exception()
+
+        def f(self):
+            raise MyException(status_code=500)
+
+        @decorators.related_bug(bug="1234", status_code=500,
+                                bug_type='storyboard')
+        def test_foo(self):
+            f(self)
+
+        with mock.patch.object(decorators.LOG, 'error') as m_error:
+            self.assertRaises(MyException, test_foo, object())
+
+        m_error.assert_called_once_with(
+            mock.ANY, '1234', 'https://storyboard.openstack.org/#!/story/1234')
+
+    def test_relatedbug_when_exception_invalid_bug_type(self):
+        """Check related_bug decorator raises exc when bug_type is not valid"""
+        MyException = self._get_my_exception()
+
+        def f(self):
+            raise MyException(status_code=500)
+
+        @decorators.related_bug(bug="1234", status_code=500,
+                                bug_type=mock.sentinel.invalid)
+        def test_foo(self):
+            f(self)
+
+        with mock.patch.object(decorators.LOG, 'error'):
+            self.assertRaises(lib_exc.InvalidParam, test_foo, object())
+
+    def test_relatedbug_when_exception_invalid_bug_number(self):
+        """Check related_bug decorator raises exc when bug_number != digit"""
+        MyException = self._get_my_exception()
+
+        def f(self):
+            raise MyException(status_code=500)
+
+        @decorators.related_bug(bug="not a digit", status_code=500,
+                                bug_type='launchpad')
+        def test_foo(self):
+            f(self)
+
+        with mock.patch.object(decorators.LOG, 'error'):
+            self.assertRaises(lib_exc.InvalidParam, test_foo, object())
diff --git a/tempest/tests/services/object_storage/test_object_client.py b/tempest/tests/services/object_storage/test_object_client.py
deleted file mode 100644
index 86535f9..0000000
--- a/tempest/tests/services/object_storage/test_object_client.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# Copyright 2016 IBM Corp.
-# 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.
-
-
-import mock
-
-from tempest.lib import exceptions
-from tempest.services.object_storage import object_client
-from tempest.tests import base
-from tempest.tests.lib import fake_auth_provider
-
-
-class TestObjectClient(base.TestCase):
-
-    def setUp(self):
-        super(TestObjectClient, self).setUp()
-        self.fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.url = self.fake_auth.base_url(None)
-        self.object_client = object_client.ObjectClient(self.fake_auth,
-                                                        'swift', 'region1')
-
-    @mock.patch.object(object_client, '_create_connection')
-    def test_create_object_continue_no_data(self, mock_poc):
-        self._validate_create_object_continue(None, mock_poc)
-
-    @mock.patch.object(object_client, '_create_connection')
-    def test_create_object_continue_with_data(self, mock_poc):
-        self._validate_create_object_continue('hello', mock_poc)
-
-    @mock.patch.object(object_client, '_create_connection')
-    def test_create_continue_with_no_continue_received(self, mock_poc):
-        self._validate_create_object_continue('hello', mock_poc,
-                                              initial_status=201)
-
-    def _validate_create_object_continue(self, req_data,
-                                         mock_poc, initial_status=100):
-
-        expected_hdrs = {
-            'X-Auth-Token': self.fake_auth.get_token(),
-            'content-length': 0 if req_data is None else len(req_data),
-            'Expect': '100-continue'}
-
-        # Setup the Mocks prior to invoking the object creation
-        mock_resp_cls = mock.Mock()
-        mock_resp_cls._read_status.return_value = ("1", initial_status, "OK")
-
-        mock_poc.return_value.response_class.return_value = mock_resp_cls
-
-        # This is the final expected return value
-        mock_poc.return_value.getresponse.return_value.status = 201
-        mock_poc.return_value.getresponse.return_value.reason = 'OK'
-
-        # Call method to PUT object using expect:100-continue
-        cnt = "container1"
-        obj = "object1"
-        path = "/%s/%s" % (cnt, obj)
-
-        # If the expected initial status is not 100, then an exception
-        # should be thrown and the connection closed
-        if initial_status is 100:
-            status, reason = \
-                self.object_client.create_object_continue(cnt, obj, req_data)
-        else:
-            self.assertRaises(exceptions.UnexpectedResponseCode,
-                              self.object_client.create_object_continue, cnt,
-                              obj, req_data)
-            mock_poc.return_value.close.assert_called_once_with()
-
-        # Verify that putrequest is called 1 time with the appropriate values
-        mock_poc.return_value.putrequest.assert_called_once_with('PUT', path)
-
-        # Verify that headers were written, including "Expect:100-continue"
-        calls = []
-
-        for header, value in expected_hdrs.items():
-            calls.append(mock.call(header, value))
-
-        mock_poc.return_value.putheader.assert_has_calls(calls, False)
-        mock_poc.return_value.endheaders.assert_called_once_with()
-
-        # The following steps are only taken if the initial status is 100
-        if initial_status is 100:
-            # Verify that the method returned what it was supposed to
-            self.assertEqual(status, 201)
-
-            # Verify that _safe_read was called once to remove the CRLF
-            # after the 100 response
-            mock_rc = mock_poc.return_value.response_class.return_value
-            mock_rc._safe_read.assert_called_once_with(2)
-
-            # Verify the actual data was written via send
-            mock_poc.return_value.send.assert_called_once_with(req_data)
-
-            # Verify that the getresponse method was called to receive
-            # the final
-            mock_poc.return_value.getresponse.assert_called_once_with()
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
index 011bc9b..2b5a947 100644
--- a/tempest/tests/test_base_test.py
+++ b/tempest/tests/test_base_test.py
@@ -41,7 +41,7 @@
     def test_get_tenant_network(self, mock_gtn, mock_gprov, mock_gcm):
         net_client = mock.Mock()
         mock_prov = mock.Mock()
-        mock_gcm.return_value.compute_networks_client = net_client
+        mock_gcm.return_value.networks_client = net_client
         mock_gprov.return_value = mock_prov
 
         test.BaseTestCase.get_tenant_network()
@@ -85,7 +85,7 @@
                                                mock_gcm):
         net_client = mock.Mock()
         mock_prov = mock.Mock()
-        mock_gcm.return_value.compute_networks_client = net_client
+        mock_gcm.return_value.networks_client = net_client
         mock_gprov.return_value = mock_prov
 
         test.BaseTestCase.get_tenant_network(credentials_type='alt')
@@ -102,7 +102,7 @@
                                                 mock_gcm):
         net_client = mock.Mock()
         mock_prov = mock.Mock()
-        mock_gcm.return_value.compute_networks_client = net_client
+        mock_gcm.return_value.networks_client = net_client
         mock_gprov.return_value = mock_prov
         creds = ['foo_type', 'role1']
 
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index bc3a753..83c1abb 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -48,6 +48,7 @@
     just assertTrue if the check is expected to fail and assertFalse if it
     should pass.
     """
+
     def test_no_setup_teardown_class_for_tests(self):
         self.assertTrue(checks.no_setup_teardown_class_for_tests(
             "  def setUpClass(cls):", './tempest/tests/fake_test.py'))
@@ -193,3 +194,60 @@
             "raise TestCase.failureException(exception.message)"))), 1)
         self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3(
             "raise TestCase.failureException(ee.message)"))), 0)
+
+    def _test_no_negatve_test_attribute_applied_to_negative_test(
+            self, filename, with_other_decorators=False,
+            with_negative_decorator=True, expected_success=True):
+        check = checks.negative_test_attribute_always_applied_to_negative_tests
+        other_decorators = [
+            "@decorators.idempotent_id(123)",
+            "@utils.requires_ext(extension='ext', service='svc')"
+        ]
+
+        if with_other_decorators:
+            # Include multiple decorators to verify that this check works with
+            # arbitrarily many decorators. These insert decorators above the
+            # @decorators.attr(type=['negative']) decorator.
+            for decorator in other_decorators:
+                self.assertIsNone(check(" %s" % decorator, filename))
+        if with_negative_decorator:
+            self.assertIsNone(
+                check("@decorators.attr(type=['negative'])", filename))
+        if with_other_decorators:
+            # Include multiple decorators to verify that this check works with
+            # arbitrarily many decorators. These insert decorators between
+            # the test and the @decorators.attr(type=['negative']) decorator.
+            for decorator in other_decorators:
+                self.assertIsNone(check(" %s" % decorator, filename))
+        final_result = check(" def test_some_negative_case", filename)
+        if expected_success:
+            self.assertIsNone(final_result)
+        else:
+            self.assertIsInstance(final_result, tuple)
+            self.assertFalse(final_result[0])
+
+    def test_no_negatve_test_attribute_applied_to_negative_test(self):
+        # Check negative filename, negative decorator passes
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something_negative.py")
+        # Check negative filename, negative decorator, other decorators passes
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something_negative.py",
+            with_other_decorators=True)
+
+        # Check non-negative filename skips check, causing pass
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something.py")
+
+        # Check negative filename, no negative decorator fails
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something_negative.py",
+            with_negative_decorator=False,
+            expected_success=False)
+        # Check negative filename, no negative decorator, other decorators
+        # fails
+        self._test_no_negatve_test_attribute_applied_to_negative_test(
+            "./tempest/api/test_something_negative.py",
+            with_other_decorators=True,
+            with_negative_decorator=False,
+            expected_success=False)
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
index 4af7463..1cc9c9a 100644
--- a/tempest/tests/test_list_tests.py
+++ b/tempest/tests/test_list_tests.py
@@ -34,7 +34,7 @@
                          "error on import %s" % ids)
         ids = six.text_type(ids).split('\n')
         for test_id in ids:
-            if re.match('(\w+\.){3}\w+', test_id):
+            if re.match(r'(\w+\.){3}\w+', test_id):
                 if not test_id.startswith('tempest.'):
                     parts = test_id.partition('tempest')
                     fail_id = parts[1] + parts[2]
diff --git a/test-requirements.txt b/test-requirements.txt
index 37644d0..196387c 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,12 +1,8 @@
 # The order of packages is significant, because pip processes them in the order
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
-hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
-# needed for doc build
-sphinx>=1.6.2 # BSD
-openstackdocstheme>=1.17.0 # Apache-2.0
-reno>=2.5.0 # Apache-2.0
+hacking>=1.1.0,<1.2.0 # Apache-2.0
 mock>=2.0.0 # BSD
 coverage!=4.4,>=4.0 # Apache-2.0
-oslotest>=1.10.0 # Apache-2.0
+oslotest>=3.2.0 # Apache-2.0
 flake8-import-order==0.11 # LGPLv3
diff --git a/tools/check_logs.py b/tools/check_logs.py
index fc21f75..de7e41d 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -62,7 +62,7 @@
     for (name, filename) in file_specs:
         whitelist = whitelists.get(name, [])
         with open(filename) as content:
-            if scan_content(name, content, regexp, whitelist):
+            if scan_content(content, regexp, whitelist):
                 logs_with_errors.append(name)
     for (name, url) in url_specs:
         whitelist = whitelists.get(name, [])
@@ -71,12 +71,12 @@
         page = urlreq.urlopen(req)
         buf = six.StringIO(page.read())
         f = gzip.GzipFile(fileobj=buf)
-        if scan_content(name, f.read().splitlines(), regexp, whitelist):
+        if scan_content(f.read().splitlines(), regexp, whitelist):
             logs_with_errors.append(name)
     return logs_with_errors
 
 
-def scan_content(name, content, regexp, whitelist):
+def scan_content(content, regexp, whitelist):
     had_errors = False
     for line in content:
         if not line.startswith("Stderr:") and regexp.match(line):
@@ -96,7 +96,7 @@
 def collect_url_logs(url):
     page = urlreq.urlopen(url)
     content = page.read()
-    logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
+    logs = re.findall(r'(screen-[\w-]+\.txt\.gz)</a>', content)
     return logs
 
 
@@ -162,6 +162,7 @@
     print("ok")
     return 0
 
+
 usage = """
 Find non-white-listed log errors in log files from a devstack-gate run.
 Log files will be searched for ERROR or CRITICAL messages. If any
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
deleted file mode 100755
index 1f2b88b..0000000
--- a/tools/find_stack_traces.py
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2013 IBM Corp.
-# 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.
-
-import gzip
-import pprint
-import re
-import sys
-
-import six
-import six.moves.urllib.request as urlreq
-
-
-pp = pprint.PrettyPrinter()
-
-NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d"
-
-NOVA_REGEX = r"(?P<timestamp>%s) (?P<pid>\d+ )?(?P<level>(ERROR|TRACE)) " \
-    "(?P<module>[\w\.]+) (?P<msg>.*)" % (NOVA_TIMESTAMP)
-
-
-class StackTrace(object):
-    timestamp = None
-    pid = None
-    level = ""
-    module = ""
-    msg = ""
-
-    def __init__(self, timestamp=None, pid=None, level="", module="",
-                 msg=""):
-        self.timestamp = timestamp
-        self.pid = pid
-        self.level = level
-        self.module = module
-        self.msg = msg
-
-    def append(self, msg):
-        self.msg = self.msg + msg
-
-    def is_same(self, data):
-        return (data['timestamp'] == self.timestamp and
-                data['level'] == self.level)
-
-    def not_none(self):
-        return self.timestamp is not None
-
-    def __str__(self):
-        buff = "<%s %s %s>\n" % (self.timestamp, self.level, self.module)
-        for line in self.msg.splitlines():
-            buff = buff + line + "\n"
-        return buff
-
-
-def hunt_for_stacktrace(url):
-    """Return TRACE or ERROR lines out of logs."""
-    req = urlreq.Request(url)
-    req.add_header('Accept-Encoding', 'gzip')
-    page = urlreq.urlopen(req)
-    buf = six.StringIO(page.read())
-    f = gzip.GzipFile(fileobj=buf)
-    content = f.read()
-
-    traces = []
-    trace = StackTrace()
-    for line in content.splitlines():
-        m = re.match(NOVA_REGEX, line)
-        if m:
-            data = m.groupdict()
-            if trace.not_none() and trace.is_same(data):
-                trace.append(data['msg'] + "\n")
-            else:
-                trace = StackTrace(
-                    timestamp=data.get('timestamp'),
-                    pid=data.get('pid'),
-                    level=data.get('level'),
-                    module=data.get('module'),
-                    msg=data.get('msg'))
-
-        else:
-            if trace.not_none():
-                traces.append(trace)
-                trace = StackTrace()
-
-    # once more at the end to pick up any stragglers
-    if trace.not_none():
-        traces.append(trace)
-
-    return traces
-
-
-def log_url(url, log):
-    return "%s/%s" % (url, log)
-
-
-def collect_logs(url):
-    page = urlreq.urlopen(url)
-    content = page.read()
-    logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
-    return logs
-
-
-def usage():
-    print("""
-Usage: find_stack_traces.py <logurl>
-
-Hunts for stack traces in a devstack run. Must provide it a base log url
-from a tempest devstack run. Should start with http and end with /logs/.
-
-Returns a report listing stack traces out of the various files where
-they are found.
-""")
-    sys.exit(0)
-
-
-def print_stats(items, fname, verbose=False):
-    errors = len([x for x in items if x.level == "ERROR"])
-    traces = len([x for x in items if x.level == "TRACE"])
-    print("%d ERRORS found in %s" % (errors, fname))
-    print("%d TRACES found in %s" % (traces, fname))
-
-    if verbose:
-        for item in items:
-            print(item)
-        print("\n\n")
-
-
-def main():
-    if len(sys.argv) == 2:
-        url = sys.argv[1]
-        loglist = collect_logs(url)
-
-        # probably wrong base url
-        if not loglist:
-            usage()
-
-        for log in loglist:
-            logurl = log_url(url, log)
-            traces = hunt_for_stacktrace(logurl)
-
-            if traces:
-                print_stats(traces, log, verbose=True)
-
-    else:
-        usage()
-
-if __name__ == '__main__':
-    main()
diff --git a/tools/format.sh b/tools/format.sh
new file mode 100755
index 0000000..dec8f1c
--- /dev/null
+++ b/tools/format.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+cd $(dirname "$(readlink -f "$0")")
+
+AUTOPEP8=`which autopep8 2>/dev/null`
+
+if [[ -z "$AUTOPEP8" ]]; then
+    AUTOPEP8=`which autopep8-3`
+fi
+
+if [[ -z "$AUTOPEP8" ]]; then
+    echo "Unable to locate autopep8" >&2
+    exit 2
+fi
+
+# isort is not compatible with the default flake8 (H306), maybe flake8-isort
+# isort -rc -sl -fss ../tempest ../setup.py
+$AUTOPEP8 --exit-code --max-line-length=79 --experimental --in-place -r ../tempest ../setup.py
+ERROR=$?
+
+if [[ $ERROR -eq 0 ]]; then
+    echo "Formatting was not needed." >&2
+    exit 0
+elif [[ $ERROR -eq 1 ]]; then
+    echo "Formatting failed.." >&2
+    exit 1
+else
+    echo "done" >&2
+fi
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 99df0d1..746cb34 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -19,24 +19,24 @@
 #
 # In order to function correctly, the environment in which the
 # script runs must have
-#   * network access to the review.openstack.org Gerrit API
+#   * network access to the review.opendev.org Gerrit API
 #     working directory
-#   * network access to https://git.openstack.org/cgit
+#   * network access to https://opendev.org/openstack
 
 import json
 import re
 
 try:
     # For Python 3.0 and later
-    from urllib.error import HTTPError as HTTPError
+    from urllib.error import HTTPError
     import urllib.request as urllib
 except ImportError:
     # Fall back to Python 2's urllib2
     import urllib2 as urllib
-    from urllib2 import HTTPError as HTTPError
+    from urllib2 import HTTPError
 
 
-url = 'https://review.openstack.org/projects/'
+url = 'https://review.opendev.org/projects/'
 
 # This is what a project looks like
 '''
@@ -59,27 +59,32 @@
 def has_tempest_plugin(proj):
     try:
         r = urllib.urlopen(
-            "https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj)
+            "https://opendev.org/%s/raw/branch/"
+            "master/setup.cfg" % proj)
     except HTTPError as err:
         if err.code == 404:
             return False
-    p = re.compile('^tempest\.test_plugins', re.M)
+    p = re.compile(r'^tempest\.test_plugins', re.M)
     if p.findall(r.read().decode('utf-8')):
         return True
     else:
         False
 
+
 r = urllib.urlopen(url)
 # Gerrit prepends 4 garbage octets to the JSON, in order to counter
 # cross-site scripting attacks.  Therefore we must discard it so the
 # json library won't choke.
-projects = sorted(filter(is_in_openstack_namespace, json.loads(r.read()[4:])))
+content = r.read().decode('utf-8')[4:]
+projects = sorted(filter(is_in_openstack_namespace, json.loads(content)))
 
-# Retrieve projects having no deb, ui or spec namespace as those namespaces
-# do not contains tempest plugins.
-projects_list = [i for i in projects if not (i.startswith('openstack/deb-') or
-                                             i.endswith('-ui') or
-                                             i.endswith('-specs'))]
+# Retrieve projects having no deb, puppet, ui or spec namespace as those
+# namespaces do not contains tempest plugins.
+projects_list = [i for i in projects if not (
+    i.startswith('openstack/deb-') or
+    i.startswith('openstack/puppet-') or
+    i.endswith('-ui') or
+    i.endswith('-specs'))]
 
 found_plugins = list(filter(has_tempest_plugin, projects_list))
 
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index 20c99b2..b4e5430 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -28,9 +28,9 @@
 #   * the environment variable git_dir pointing to the location
 #   * of said git repositories
 #   ) OR (
-#   * network access to the review.openstack.org Gerrit API
+#   * network access to the review.opendev.org Gerrit API
 #     working directory
-#   * network access to https://git.openstack.org/cgit
+#   * network access to https://opendev.org/openstack
 #   ))
 #
 # If a file named doc/source/data/tempest-plugins-registry.header or
@@ -41,21 +41,43 @@
 set -ex
 
 (
-declare -A plugins
-
 if [[ -r doc/source/data/tempest-plugins-registry.header ]]; then
     cat doc/source/data/tempest-plugins-registry.header
 fi
 
 sorted_plugins=$(python tools/generate-tempest-plugins-list.py)
 
-for k in ${sorted_plugins}; do
-    project=${k:0:28}
-    giturl="git://git.openstack.org/openstack/${k:0:26}"
-    printf "|%-28s|%-73s|\n" "${project}" "${giturl}"
-    printf "+----------------------------+-------------------------------------------------------------------------+\n"
+name_col_len=$(echo "${sorted_plugins}" | wc -L)
+name_col_len=$(( name_col_len + 20 ))
+
+# Print the title underline for a RST table.
+function title_underline {
+    printf "== "
+    local len=$1
+    while [[ $len -gt 0 ]]; do
+        printf "="
+        len=$(( len - 1))
+    done
+    printf " ===\n"
+}
+
+printf "\n\n"
+title_underline ${name_col_len}
+printf "%-3s %-${name_col_len}s %s\n" "SR" "Plugin Name" "URL"
+title_underline ${name_col_len}
+
+i=0
+for plugin in ${sorted_plugins}; do
+    i=$((i+1))
+    giturl="https://opendev.org/openstack/${plugin}"
+    gitlink="https://opendev.org/openstack/${plugin}"
+    printf "%-3s %-${name_col_len}s %s\n" "$i" "${plugin}" "\`${giturl} <${gitlink}>\`__"
 done
 
+title_underline ${name_col_len}
+
+printf "\n\n"
+
 if [[ -r doc/source/data/tempest-plugins-registry.footer ]]; then
     cat doc/source/data/tempest-plugins-registry.footer
 fi
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index 44bf840..b291fcc 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -18,20 +18,17 @@
 # This script is intended to check the sanity of tempest plugins against
 # tempest master.
 # What it does:
-# * Creates the virtualenv
-# * Install tempest
 # * Retrieve the project lists having tempest plugin if project name is
 #   given.
-# * For each project in a list, It does:
+# * For each project in a list, it does:
+#   * Create virtualenv and install tempest in it
 #   * Clone the Project
 #   * Install the Project and also installs dependencies from
 #     test-requirements.txt.
 #   * Create Tempest workspace
 #   * List tempest plugins
 #   * List tempest plugins tests
-#   * Uninstall the project and its dependencies
-#   * Again Install tempest
-#   * Again repeat the step from cloning project
+#   * Delete virtualenv and project repo
 #
 # If one of the step fails, The script will exit with failure.
 
@@ -46,31 +43,63 @@
 
 # retrieve a list of projects having tempest plugins
 PROJECT_LIST="$(python tools/generate-tempest-plugins-list.py)"
-# List of projects having tempest plugin stale or unmaintained from long time
-BLACKLIST="trio2o"
+# List of projects having tempest plugin stale or unmaintained for a long time
+# (6 months or more)
+# TODO(masayukig): Some of these can be removed from BLACKLIST in the future.
+# barbican-tempest-plugin: https://review.opendev.org/#/c/634631/
+# cyborg-tempest-plugin: https://review.opendev.org/659687
+# intel-nfv-ci-tests: https://review.opendev.org/#/c/634640/
+# networking-ansible: https://review.opendev.org/#/c/634647/
+# networking-generic-switch: https://review.opendev.org/#/c/634846/
+# networking-l2gw-tempest-plugin: https://review.opendev.org/#/c/635093/
+# networking-midonet: https://review.opendev.org/#/c/635096/
+# networking-plumgrid: https://review.opendev.org/#/c/635096/
+# networking-spp: https://review.opendev.org/#/c/635098/
+# neutron-dynamic-routing: https://review.opendev.org/#/c/637718/
+# neutron-vpnaas: https://review.opendev.org/#/c/637719/
+# nova-lxd: https://review.opendev.org/#/c/638334/
+# valet: https://review.opendev.org/#/c/638339/
+
+BLACKLIST="
+barbican-tempest-plugin
+cyborg-tempest-plugin
+intel-nfv-ci-tests
+networking-ansible
+networking-generic-switch
+networking-l2gw-tempest-plugin
+networking-midonet
+networking-plumgrid
+networking-spp
+neutron-dynamic-routing
+neutron-vpnaas
+nova-lxd
+valet
+"
 
 # Function to clone project using zuul-cloner or from git
 function clone_project() {
     if [ -e /usr/zuul-env/bin/zuul-cloner ]; then
         /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \
-        git://git.openstack.org \
+        https://opendev.org \
         openstack/"$1"
 
     elif [ -e /usr/bin/git ]; then
-        /usr/bin/git clone git://git.openstack.org/openstack/"$1" \
+        /usr/bin/git clone https://opendev.org/openstack/"$1" \
         openstack/"$1"
 
     fi
 }
 
-# Create virtualenv to perform sanity operation
-SANITY_DIR=$(pwd)
-virtualenv "$SANITY_DIR"/.venv
-export TVENV="$SANITY_DIR/tools/with_venv.sh"
-cd "$SANITY_DIR"
+# function to create virtualenv to perform sanity operation
+function prepare_workspace() {
+    SANITY_DIR=$(pwd)
+    virtualenv -p python3 --clear "$SANITY_DIR"/.venv
+    export TVENV="$SANITY_DIR/tools/with_venv.sh"
+    cd "$SANITY_DIR"
 
-# Install tempest in a venv
-"$TVENV" pip install .
+    # Install tempest with test dependencies in a venv
+    "$TVENV" pip install -e . -r test-requirements.txt
+}
 
 # Function to install project
 function install_project() {
@@ -83,30 +112,31 @@
 
 # Function to perform sanity checking on Tempest plugin
 function tempest_sanity() {
-    "$TVENV" tempest init "$SANITY_DIR"/tempest_sanity
-    cd "$SANITY_DIR"/tempest_sanity
-    "$TVENV" tempest list-plugins
+    "$TVENV" tempest init "$SANITY_DIR"/tempest_sanity && \
+    cd "$SANITY_DIR"/tempest_sanity && \
+    "$TVENV" tempest list-plugins && \
     "$TVENV" tempest run -l
+    retval=$?
     # Delete tempest workspace
+    # NOTE: Cleaning should be done even if an error occurs.
     "$TVENV" tempest workspace remove --name tempest_sanity --rmdir
     cd "$SANITY_DIR"
-}
-
-# Function to uninstall project
-function uninstall_project() {
-    "$TVENV" pip uninstall -y "$SANITY_DIR"/openstack/"$1"
-    # Check for *requirements.txt file in a project then uninstall it.
-    if [ -e "$SANITY_DIR"/openstack/"$1"/*requirements.txt ]; then
-        "$TVENV" pip uninstall -y -r "$SANITY_DIR"/openstack/"$1"/*requirements.txt
-    fi
+    # Remove the sanity workspace in case of remaining
+    rm -fr "$SANITY_DIR"/tempest_sanity
     # Remove the project directory after sanity run
     rm -fr "$SANITY_DIR"/openstack/"$1"
+
+    return $retval
 }
 
 # Function to run sanity check on each project
 function plugin_sanity_check() {
-        clone_project "$1"  &&  install_project "$1"  &&  tempest_sanity "$1" \
-        &&  uninstall_project "$1"  &&  "$TVENV" pip install .
+    prepare_workspace && \
+    clone_project "$1" && \
+    install_project "$1" && \
+    tempest_sanity "$1"
+
+    return $?
 }
 
 # Log status
@@ -117,6 +147,12 @@
     # Remove blacklisted tempest plugins
     if ! [[ `echo $BLACKLIST | grep -c $project ` -gt 0 ]]; then
         plugin_sanity_check $project && passed_plugin+=", $project" || \
-        failed_plugin+=", $project"
+        failed_plugin+="$project, " > $SANITY_DIR/$project.txt
     fi
 done
+
+# Check for failed status
+if [[ -n $failed_plugin ]]; then
+    echo "Failed Plugins: $failed_plugin"
+    exit 1
+fi
diff --git a/tools/tox_install.sh b/tools/tox_install.sh
deleted file mode 100755
index 43468e4..0000000
--- a/tools/tox_install.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-
-# Client constraint file contains this client version pin that is in conflict
-# with installing the client from source. We should remove the version pin in
-# the constraints file before applying it for from-source installation.
-
-CONSTRAINTS_FILE=$1
-shift 1
-
-set -e
-
-# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
-# published to logs.openstack.org for easy debugging.
-localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
-
-if [[ $CONSTRAINTS_FILE != http* ]]; then
-    CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE
-fi
-# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
-curl $CONSTRAINTS_FILE --insecure --progress-bar --output $localfile
-
-pip install -c$localfile openstack-requirements
-
-# This is the main purpose of the script: Allow local installation of
-# the current repo. It is listed in constraints file and thus any
-# install will be constrained and we need to unconstrain it.
-edit-constraints $localfile -- $CLIENT_NAME
-
-pip install -c$localfile -U $*
-exit $?
diff --git a/tox.ini b/tox.ini
index 21696eb..48a2baa 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = pep8,py35,py27,pip-check-reqs
+envlist = pep8,py36,py37,py27,pip-check-reqs
 minversion = 2.3.1
 skipsdist = True
 
@@ -8,24 +8,24 @@
 setenv =
     VIRTUAL_ENV={envdir}
     OS_TEST_PATH=./tempest/test_discover
-    BRANCH_NAME=master
-    CLIENT_NAME=tempest
 deps =
+    -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
     -r{toxinidir}/requirements.txt
 
 [testenv]
 setenv =
     VIRTUAL_ENV={envdir}
     OS_LOG_CAPTURE=1
-    PYTHONWARNINGS=default::DeprecationWarning
-    BRANCH_NAME=master
-    CLIENT_NAME=tempest
-passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST
+    OS_STDOUT_CAPTURE=1
+    OS_STDERR_CAPTURE=1
+    OS_TEST_TIMEOUT=160
+    PYTHONWARNINGS=default::DeprecationWarning,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:site
+passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST GABBI_TEMPEST_PATH
 usedevelop = True
-install_command =
-    {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
+install_command = pip install {opts} {packages}
 whitelist_externals = *
 deps =
+    -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
     -r{toxinidir}/requirements.txt
     -r{toxinidir}/test-requirements.txt
 commands =
@@ -36,7 +36,21 @@
 commands = oslo-config-generator --config-file tempest/cmd/config-generator.tempest.conf
 
 [testenv:cover]
-commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}'
+setenv =
+  {[testenv]setenv}
+  PYTHON=coverage run --source tempest --parallel-mode
+commands =
+  coverage erase
+  find . -type f -name "*.pyc" -delete
+  stestr --test-path ./tempest/tests run {posargs}
+  coverage combine
+  coverage html -d cover
+  coverage xml -o cover/coverage.xml
+  coverage report
+
+[testenv:debug]
+basepython = python3
+commands = oslo_debug_helper -t tempest/tests {posargs}
 
 [testenv:all]
 envdir = .tox/tempest
@@ -50,18 +64,27 @@
     find . -type f -name "*.pyc" -delete
     tempest run --regex {posargs}
 
-[testenv:ostestr]
-sitepackages = {[tempestenv]sitepackages}
+[testenv:all-plugin]
+# DEPRECATED
+# NOTE(andreaf) The all-plugin tox env uses sitepackages
+# so that plugins installed outsite of Tempest virtual environment
+# can be discovered. After the implementation during the Queens
+# release cycle of the goal of moving Tempest plugins in dedicated
+# git repos, this environment should not be used anymore. "all"
+# should be used instead with the appropriate regex filtering.
+sitepackages = True
 # 'all' includes slow tests
 setenv =
     {[tempestenv]setenv}
     OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
 deps = {[tempestenv]deps}
 commands =
+    echo "WARNING: The all-plugin env is deprecated and will be removed"
+    echo "WARNING  Please use the 'all' environment for Tempest plugins."
     find . -type f -name "*.pyc" -delete
-    ostestr {posargs}
+    tempest run --regex {posargs}
 
-[testenv:all-plugin]
+[testenv:all-site-packages]
 sitepackages = True
 # 'all' includes slow tests
 setenv =
@@ -79,11 +102,22 @@
 deps = {[tempestenv]deps}
 # The regex below is used to select which tests to run and exclude the slow tag:
 # See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
+# FIXME: We can replace it with the `--black-regex` option to exclude tests now.
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
     tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' {posargs}
 
+[testenv:full-parallel]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select all tempest scenario and including the non slow api tests
+commands =
+    find . -type f -name "*.pyc" -delete
+    tempest run --regex '(^tempest\.scenario.*)|(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
+
 [testenv:full-serial]
 envdir = .tox/tempest
 sitepackages = {[tempestenv]sitepackages}
@@ -91,6 +125,7 @@
 deps = {[tempestenv]deps}
 # The regex below is used to select which tests to run and exclude the slow tag:
 # See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
+# FIXME: We can replace it with the `--black-regex` option to exclude tests now.
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' {posargs}
@@ -126,7 +161,21 @@
     find . -type f -name "*.pyc" -delete
     tempest run --serial --regex '\[.*\bsmoke\b.*\]' {posargs}
 
+[testenv:slow-serial]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select the slow tagged tests to run serially:
+commands =
+    find . -type f -name "*.pyc" -delete
+    tempest run --serial --regex '\[.*\bslow\b.*\]' {posargs}
+
 [testenv:venv]
+deps =
+  -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+  -r{toxinidir}/requirements.txt
+  -r{toxinidir}/doc/requirements.txt
 commands = {posargs}
 
 [testenv:venv-tempest]
@@ -137,15 +186,32 @@
 commands = {posargs}
 
 [testenv:docs]
+basepython = python3
+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 doc/build
-    python setup.py build_sphinx {posargs}
+  rm -rf doc/build
+  sphinx-build -W -b html doc/source doc/build/html
+whitelist_externals = rm
 
 [testenv:pep8]
+deps =
+    -r{toxinidir}/test-requirements.txt
+    autopep8
+basepython = python3
 commands =
+    autopep8 --exit-code --max-line-length=79 --experimental --diff -r tempest setup.py
     flake8 {posargs}
     check-uuid
 
+[testenv:autopep8]
+deps = autopep8
+basepython = python3
+commands =
+    {toxinidir}/tools/format.sh
+
 [testenv:uuidgen]
 commands =
     check-uuid --fix
@@ -155,19 +221,27 @@
 import_exceptions = tempest.services
 
 [flake8]
-# E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved.  For further detail see https://review.openstack.org/#/c/36788/
+# E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved.  For further detail see https://review.opendev.org/#/c/36788/
 # E123 skipped because it is ignored by default in the default pep8
 # E129 skipped because it is too limiting when combined with other rules
-ignore = E125,E123,E129
+# W504 skipped because it is overeager and unnecessary
+ignore = E125,E123,E129,W504
 show-source = True
 exclude = .git,.venv,.tox,dist,doc,*egg,build
 enable-extensions = H106,H203,H904
 import-order-style = pep8
 
 [testenv:releasenotes]
+basepython = python3
+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
-    sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+  rm -rf releasenotes/build
+  sphinx-build -a -E -W -d releasenotes/build/doctrees \
+         -b html releasenotes/source releasenotes/build/html
+whitelist_externals = rm
 
 [testenv:pip-check-reqs]
 # Do not install test-requirements as that will pollute the virtualenv for
@@ -191,6 +265,7 @@
 
 [testenv:plugin-sanity-check]
 # perform tempest plugin sanity
+basepython = python3
 whitelist_externals = bash
 commands =
   bash tools/tempest-plugin-sanity.sh