Merge "edit check result and make sure the query result correct"
diff --git a/.zuul.yaml b/.zuul.yaml
deleted file mode 100644
index 2fa0fe1..0000000
--- a/.zuul.yaml
+++ /dev/null
@@ -1,780 +0,0 @@
-- job:
-    name: devstack-tempest
-    parent: devstack
-    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: &base_required-projects
-      - opendev.org/openstack/tempest
-    timeout: 7200
-    roles: &base_roles
-      - zuul: opendev.org/openstack/devstack
-    vars: &base_vars
-      # TODO(gmann): Remove these test skip once nova bug #1882521 is solved
-      tempest_black_regex: "(tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume|tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON|tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached)"
-      devstack_services:
-        tempest: true
-      devstack_local_conf:
-        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. This job is derived from 'devstack-ipv6'
-      which set the IPv6-only setting for OpenStack services. As part of
-      run phase, this job will verify the IPv6 setting and check the services
-      endpoints and listen addresses are IPv6. Basically it will run the script
-      ./tool/verify-ipv6-only-deployments.sh
-
-      Child jobs of this job can run their own set of tests and can
-      add post-run playebooks to extend the IPv6 verification specific
-      to their deployed services.
-      Check the wiki page for more details about project jobs setup
-      - https://wiki.openstack.org/wiki/Goal-IPv6-only-deployments-and-testing
-    required-projects: *base_required-projects
-    timeout: 7200
-    roles: *base_roles
-    vars: *base_vars
-    run: playbooks/devstack-tempest-ipv6.yaml
-    post-run: playbooks/post-tempest.yaml
-
-- job:
-    name: tempest-ipv6-only
-    parent: devstack-tempest-ipv6
-    # This currently works from stable/pike on.
-    branches: ^(?!stable/ocata).*$
-    description: |
-      Integration test of IPv6-only deployments. This job runs
-      smoke and IPv6 relates tests only. Basic idea is to test
-      whether OpenStack Services listen on IPv6 addrress or not.
-    timeout: 10800
-    vars:
-      tox_envlist: ipv6-only
-
-- 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
-        USE_PYTHON3: False
-      devstack_services:
-        # NOTE(mriedem): Disable the cinder-backup service from tempest-full
-        # since tempest-full is in the integrated-gate project template but
-        # the backup tests do not really involve other services so they should
-        # be run in some more cinder-specific job, especially because the
-        # tests fail at a high rate (see bugs 1483434, 1813217, 1745168)
-        c-bak: false
-
-- 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
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: True
-
-- job:
-    name: tempest-full-parallel
-    parent: tempest-full-py3
-    voting: false
-    branches:
-      - master
-    description: |
-      Base integration test with Neutron networking.
-      It includes all scenarios as it was in the past.
-      This job runs all scenario tests in parallel!
-    timeout: 9000
-    vars:
-      tox_envlist: full-parallel
-      run_tempest_cleanup: true
-      run_tempest_dry_cleanup: true
-
-- 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
-        GLANCE_USE_IMPORT_WORKFLOW: 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)
-        # NOTE(mriedem): Disable the cinder-backup service from
-        # tempest-full-py3 since tempest-full-py3 is in the integrated-gate-py3
-        # project template but the backup tests do not really involve other
-        # services so they should be run in some more cinder-specific job,
-        # especially because the tests fail at a high rate (see bugs 1483434,
-        # 1813217, 1745168)
-        c-bak: false
-
-- job:
-    name: tempest-integrated-networking
-    parent: devstack-tempest
-    branches: ^(?!stable/ocata).*$
-    description: |
-      This  job runs integration tests for networking. This is subset of
-      'tempest-full' job and run only Neutron and Nova related tests.
-      This is meant to be run on neutron gate only.
-    vars:
-      tox_envlist: integrated-network
-      devstack_localrc:
-        USE_PYTHON3: true
-        FORCE_CONFIG_DRIVE: true
-      devstack_services:
-        s-account: false
-        s-container: false
-        s-object: false
-        s-proxy: false
-        c-bak: false
-
-- job:
-    name: tempest-integrated-compute
-    parent: devstack-tempest
-    branches: ^(?!stable/ocata).*$
-    description: |
-      This job runs integration tests for compute. This is
-      subset of 'tempest-full' job and run Nova, Neutron, Cinder (except backup tests)
-      and Glance related tests. This is meant to be run on Nova gate only.
-    vars:
-      tox_envlist: integrated-compute
-      tempest_black_regex: ""
-      devstack_localrc:
-        USE_PYTHON3: true
-        FORCE_CONFIG_DRIVE: true
-        ENABLE_VOLUME_MULTIATTACH: true
-      devstack_services:
-        s-account: false
-        s-container: false
-        s-object: false
-        s-proxy: false
-        c-bak: false
-
-- job:
-    name: tempest-integrated-placement
-    parent: devstack-tempest
-    branches: ^(?!stable/ocata).*$
-    description: |
-      This job runs integration tests for placement. This is
-      subset of 'tempest-full' job and run Nova and Neutron
-      related tests. This is meant to be run on Placement gate only.
-    vars:
-      tox_envlist: integrated-placement
-      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
-        c-bak: false
-
-- job:
-    name: tempest-integrated-storage
-    parent: devstack-tempest
-    branches: ^(?!stable/ocata).*$
-    description: |
-      This job runs integration tests for image & block storage. This is
-      subset of 'tempest-full' job and run Cinder, Glance, Swift and Nova
-      related tests. This is meant to be run on Cinder and Glance gate only.
-    vars:
-      tox_envlist: integrated-storage
-      devstack_localrc:
-        USE_PYTHON3: true
-        FORCE_CONFIG_DRIVE: true
-        ENABLE_VOLUME_MULTIATTACH: true
-        GLANCE_USE_IMPORT_WORKFLOW: True
-
-- job:
-    name: tempest-integrated-object-storage
-    parent: devstack-tempest
-    branches: ^(?!stable/ocata).*$
-    description: |
-      This job runs integration tests for object storage. This is
-      subset of 'tempest-full' job and run Swift, Cinder and Glance
-      related tests. This is meant to be run on Swift gate only.
-    vars:
-      tox_envlist: integrated-object-storage
-      devstack_localrc:
-        # NOTE(gmann): swift is not ready on python3 yet and devstack
-        # install it on python2.7 only. But settting the USE_PYTHON3
-        # for future once swift is ready on py3.
-        USE_PYTHON3: true
-
-- 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-focal
-    # This job runs on Focal from stable/victoria on.
-    branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train|ussuri)).*$
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: False
-    group-vars:
-      subnode:
-        devstack_localrc:
-          USE_PYTHON3: False
-
-- job:
-    name: tempest-multinode-full
-    parent: tempest-multinode-full-base
-    nodeset: openstack-two-node-bionic
-    # This job runs on Bionic and on python2. This is for stable/stein and stable/train.
-    # This job is prepared to make sure all stable branches from stable/stein till stable/train
-    # will keep running on bionic. This can be removed once stable/train is EOL.
-    branches:
-      - stable/stein
-      - stable/train
-      - stable/ussuri
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: False
-    group-vars:
-      subnode:
-        devstack_localrc:
-          USE_PYTHON3: False
-
-- 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
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: False
-    group-vars:
-      subnode:
-        devstack_localrc:
-          USE_PYTHON3: False
-
-- job:
-    name: tempest-multinode-full-py3
-    parent: tempest-multinode-full
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: true
-    group-vars:
-      subnode:
-        devstack_localrc:
-          USE_PYTHON3: true
-
-- job:
-    name: tempest-full-py3-opensuse15
-    parent: tempest-full-py3
-    nodeset: devstack-single-node-opensuse-15
-    description: |
-      Base integration test with Neutron networking and py36 running
-      on openSUSE Leap 15.x
-    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
-      devstack_plugins:
-        neutron: https://opendev.org/openstack/neutron
-      devstack_services:
-        neutron-placement: true
-        neutron-qos: true
-      devstack_local_conf:
-        post-config:
-          "/$NEUTRON_CORE_PLUGIN_CONF":
-            ovs:
-              bridge_mappings: public:br-ex
-              resource_provider_bandwidths: br-ex:1000000:1000000
-        test-config:
-          $TEMPEST_CONFIG:
-            network-feature-enabled:
-              qos_placement_physnet: public
-      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
-    group-vars:
-      subnode:
-        devstack_localrc:
-          USE_PYTHON3: true
-
-- job:
-    name: tempest-full-victoria-py3
-    parent: tempest-full-py3
-    override-checkout: stable/victoria
-
-- job:
-    name: tempest-full-ussuri-py3
-    parent: tempest-full-py3
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-
-- job:
-    name: tempest-full-train-py3
-    parent: tempest-full-py3
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-
-- job:
-    name: tempest-full-stein-py3
-    parent: tempest-full-py3
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/stein
-
-- job:
-    name: tempest-tox-plugin-sanity-check
-    parent: tox
-    description: |
-      Run tempest plugin sanity check script using tox.
-    nodeset: ubuntu-focal
-    vars:
-      tox_envlist: plugin-sanity-check
-    timeout: 5000
-
-- 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 PostgreSQL.
-      Former name for this job was legacy-tempest-dsvm-neutron-pg-full.
-    vars:
-      devstack_localrc:
-        ENABLE_FILE_INJECTION: true
-        DATABASE_TYPE: postgresql
-        USE_PYTHON3: True
-
-- project-template:
-    name: integrated-gate-networking
-    description: |
-      Run the python3 Tempest network integration tests (Nova and Neutron related)
-      in check and gate for the neutron integrated gate. This is meant to be
-      run on neutron gate only.
-    check:
-      jobs:
-        - grenade
-        - tempest-integrated-networking
-    gate:
-      jobs:
-        - grenade
-        - tempest-integrated-networking
-
-- project-template:
-    name: integrated-gate-compute
-    description: |
-      Run the python3 Tempest compute integration tests
-      (Nova, Neutron, Cinder and Glance related) in check and gate
-      for the Nova integrated gate. This is meant to be
-      run on Nova gate only.
-    check:
-      jobs:
-        - grenade
-        - tempest-integrated-compute
-    gate:
-      jobs:
-        - grenade
-        - tempest-integrated-compute
-
-- project-template:
-    name: integrated-gate-placement
-    description: |
-      Run the python3 Tempest placement integration tests
-      (Nova and Neutron related) in check and gate
-      for the Placement integrated gate. This is meant to be
-      run on Placement gate only.
-    check:
-      jobs:
-        - grenade
-        - tempest-integrated-placement
-    gate:
-      jobs:
-        - grenade
-        - tempest-integrated-placement
-
-- project-template:
-    name: integrated-gate-storage
-    description: |
-      Run the python3 Tempest image & block storage integration tests
-      (Cinder, Glance, Swift and Nova related) in check and gate
-      for the neutron integrated gate. This is meant to be
-      run on Cinder and Glance gate only.
-    check:
-      jobs:
-        - grenade
-        - tempest-integrated-storage
-    gate:
-      jobs:
-        - grenade
-        - tempest-integrated-storage
-
-- project-template:
-    name: integrated-gate-object-storage
-    description: |
-      Run the python3 Tempest object storage integration tests
-      (Swift, Cinder and Glance related) in check and gate
-      for the swift integrated gate. This is meant to be
-      run on swift gate only.
-    check:
-      jobs:
-        - grenade
-        - tempest-integrated-object-storage
-    gate:
-      jobs:
-        - grenade
-        - tempest-integrated-object-storage
-
-- project:
-    templates:
-      - check-requirements
-      - integrated-gate-py3
-      - openstack-cover-jobs
-      - openstack-python3-victoria-jobs
-      - publish-openstack-docs-pti
-      - release-notes-jobs-python3
-    check:
-      jobs:
-        - devstack-tempest:
-            files:
-              - ^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/.*$
-              - ^.coveragerc$
-              - ^.gitignore$
-              - ^.gitreview$
-              - ^.mailmap$
-        - tempest-full-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-py3-ipv6:
-            voting: false
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-victoria-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-ussuri-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-train-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-stein-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-multinode-full-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-tox-plugin-sanity-check:
-            irrelevant-files: &tempest-irrelevant-files-2
-              - ^.*\.rst$
-              - ^doc/.*$
-              - ^etc/.*$
-              - ^releasenotes/.*$
-              - ^setup.cfg$
-              - ^tempest/hacking/.*$
-              - ^tempest/tests/.*$
-              - ^.coveragerc$
-              - ^.gitignore$
-              - ^.gitreview$
-              - ^.mailmap$
-              # tools/ is not here since this relies on a script in tools/.
-        - tempest-ipv6-only:
-            irrelevant-files: *tempest-irrelevant-files-2
-        - tempest-slow-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - nova-live-migration:
-            voting: false
-            irrelevant-files: *tempest-irrelevant-files
-        - devstack-plugin-ceph-tempest-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - neutron-grenade-multinode:
-            irrelevant-files: *tempest-irrelevant-files
-        - grenade:
-            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:
-            voting: false
-            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
-        - openstack-tox-bashate:
-            irrelevant-files: *tempest-irrelevant-files-2
-    gate:
-      jobs:
-        - tempest-slow-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - neutron-grenade-multinode:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - grenade:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-ipv6-only:
-            irrelevant-files: *tempest-irrelevant-files-2
-        - devstack-plugin-ceph-tempest-py3:
-            irrelevant-files: *tempest-irrelevant-files
-    experimental:
-      jobs:
-        - tempest-cinder-v2-api:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-all:
-            irrelevant-files: *tempest-irrelevant-files
-        - neutron-tempest-dvr-ha-multinode-full:
-            irrelevant-files: *tempest-irrelevant-files
-        - nova-tempest-v2-api:
-            irrelevant-files: *tempest-irrelevant-files
-        - cinder-tempest-lvm-multibackend:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-pg-full:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-py3-opensuse15:
-            irrelevant-files: *tempest-irrelevant-files
-    periodic-stable:
-      jobs:
-        - tempest-full-victoria-py3
-        - tempest-full-ussuri-py3
-        - tempest-full-train-py3
-        - tempest-full-stein-py3
-    periodic:
-      jobs:
-        - tempest-all
-        - tempest-full-oslo-master
diff --git a/REVIEWING.rst b/REVIEWING.rst
index e07e358..4c63aa0 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -160,13 +160,11 @@
 When to approve
 ---------------
 * 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:
+* Every patch needs at least single +2's before being approved. A single
+  Tempest core reviewer can approve patches but can always wait for another
+  +2 in any case. Following cases where single +2 can be used without any
+  issue:
 
-  * 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
@@ -187,7 +185,4 @@
     voting ``tempest-tox-plugin-sanity-check`` job) and unblock the
     tempest gate
 
-  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/source/conf.py b/doc/source/conf.py
index 59a2f64..b738fde 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -86,7 +86,7 @@
 master_doc = 'index'
 
 # General information about the project.
-copyright = u'2013, OpenStack QA Team'
+copyright = '2013, OpenStack QA Team'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -206,8 +206,8 @@
 # (source start file, target name, title, author, documentclass
 # [howto/manual]).
 latex_documents = [
-    ('index', 'doc-tempest.tex', u'Tempest Testing Project',
-     u'OpenStack Foundation', 'manual'),
+    ('index', 'doc-tempest.tex', 'Tempest Testing Project',
+     'OpenStack Foundation', 'manual'),
 ]
 
 latex_use_xindy = False
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index c43e420..c790c5f 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -153,6 +153,11 @@
 
 Pre-Provisioned Credentials are also known as accounts.yaml or accounts file.
 
+Keystone Scopes & Roles Support in Tempest
+""""""""""""""""""""""""""""""""""""""""""
+For details on scope and roles support in Tempest,
+please refer to :doc:`this document <keystone_scopes_and_roles_support>`
+
 Compute
 -------
 
diff --git a/doc/source/contributor/contributing.rst b/doc/source/contributor/contributing.rst
index 9c79a1f..139f0b7 100644
--- a/doc/source/contributor/contributing.rst
+++ b/doc/source/contributor/contributing.rst
@@ -13,7 +13,7 @@
 
 Communication
 ~~~~~~~~~~~~~
-* IRC channel ``#openstack-qa`` at FreeNode
+* IRC channel ``#openstack-qa`` at OFTC
 * Mailing list (prefix subjects with ``[qa]`` for faster responses)
   http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
 
@@ -43,10 +43,9 @@
 
 Getting Your Patch Merged
 ~~~~~~~~~~~~~~~~~~~~~~~~~
-All changes proposed to the Tempest require two ``Code-Review +2`` votes from
-Tempest core reviewers before one of the core reviewers can approve the patch by
-giving ``Workflow +1`` vote. More detailed guidelines for reviewers are available
-at :doc:`../REVIEWING`.
+All changes proposed to the Tempest require single ``Code-Review +2`` votes from
+Tempest core reviewers by giving ``Workflow +1`` vote. More detailed guidelines
+for reviewers are available at :doc:`../REVIEWING`.
 
 Project Team Lead Duties
 ~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/source/data/tempest-blacklisted-plugins-registry.header b/doc/source/data/tempest-non-active-plugins-registry.header
similarity index 67%
rename from doc/source/data/tempest-blacklisted-plugins-registry.header
rename to doc/source/data/tempest-non-active-plugins-registry.header
index 6b6af11..06d8eaa 100644
--- a/doc/source/data/tempest-blacklisted-plugins-registry.header
+++ b/doc/source/data/tempest-non-active-plugins-registry.header
@@ -1,7 +1,7 @@
-Blacklisted Plugins
+Non Active Plugins
 ===================
 
 List of Tempest plugin projects that are stale or unmaintained for a long
-time (6 months or more). They can be moved out of blacklist state once one
+time (6 months or more). They can be moved out of nonactivelist state once one
 of the relevant patches gets merged:
 https://review.opendev.org/#/q/topic:tempest-sanity-gate+%28status:open%29
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 66e68ea..2f29cf2 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -105,6 +105,14 @@
 
    tempest_and_plugins_compatible_version_policy
 
+Keystone Scopes & Roles Support in Tempest
+------------------------------------------
+
+.. toctree::
+   :maxdepth: 2
+
+   keystone_scopes_and_roles_support
+
 Stable Branch Support Policy
 ----------------------------
 
diff --git a/doc/source/keystone_scopes_and_roles_support.rst b/doc/source/keystone_scopes_and_roles_support.rst
new file mode 100644
index 0000000..f446f8c
--- /dev/null
+++ b/doc/source/keystone_scopes_and_roles_support.rst
@@ -0,0 +1,286 @@
+Keystone Scopes & Roles Support in Tempest
+==========================================
+
+OpenStack Keystone supports different scopes in token, refer to the
+`Keystone doc <https://docs.openstack.org/keystone/latest/admin/tokens-overview.html#authorization-scopes>`_.
+Along with the scopes, keystone supports default roles, one of which
+is a reader role, for details refer to
+`this keystone document <https://docs.openstack.org/keystone/latest/admin/service-api-protection.html>`_.
+
+Tempest supports those scopes and roles credentials that can be used
+to test APIs under different scope and roles.
+
+Dynamic Credentials
+-------------------
+
+Dynamic credential supports all the below set of personas and allows
+you to generate credentials tailored to a specific persona that you
+can use in your test.
+
+Domain scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+  #. Domain Admin: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['domain_admin']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_d_admin_client = (
+                     cls.os_domain_admin.availability_zone_client)
+
+  #. Domain Member: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['domain_member']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_d_member_client = (
+                     cls.os_domain_member.availability_zone_client)
+
+  #. Domain Reader: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['domain_reader']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_d_reader_client = (
+                     cls.os_domain_reader.availability_zone_client)
+
+  #. Domain other roles: This is supported and can be requested and used from
+     the test as below:
+
+     You need to use the ``domain`` as the prefix in credentials type, and
+     based on that, Tempest will create test users under 'domain' scope.
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = [['domain_my_role1', 'my_own_role1', 'admin']
+                            ['domain_my_role2', 'my_own_role2']]
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_d_role1_client = (
+                     cls.os_domain_my_role1.availability_zone_client)
+                 cls.az_d_role2_client = (
+                     cls.os_domain_my_role2.availability_zone_client)
+
+System scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^
+
+  #. System Admin: This is supported and can be requested and used from the
+     test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['system_admin']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_s_admin_client = (
+                     cls.os_system_admin.availability_zone_client)
+
+  #. System Member: This is supported and can be requested and used from the
+     test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['system_member']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_s_member_client = (
+                     cls.os_system_member.availability_zone_client)
+
+  #. System Reader: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['system_reader']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_s_reader_client = (
+                     cls.os_system_reader.availability_zone_client)
+
+  #. System other roles: This is supported and can be requested and used from
+     the test as below:
+
+     You need to use the ``system`` as the prefix in credentials type, and
+     based on that, Tempest will create test users under 'project' scope.
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = [['system_my_role1', 'my_own_role1', 'admin']
+                            ['system_my_role2', 'my_own_role2']]
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_s_role1_client = (
+                     cls.os_system_my_role1.availability_zone_client)
+                 cls.az_s_role2_client = (
+                     cls.os_system_my_role2.availability_zone_client)
+
+Project scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+  #. Project Admin: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_admin']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_admin_client = (
+                     cls.os_project_admin.availability_zone_client)
+
+  #. Project Member: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_member']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_member_client = (
+                     cls.os_project_member.availability_zone_client)
+
+  #. Project Reader: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_reader']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_reader_client = (
+                     cls.os_project_reader.availability_zone_client)
+
+  #. Project alternate Admin: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_alt_admin']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_alt_admin_client = (
+                     cls.os_project_alt_admin.availability_zone_client)
+
+  #. Project alternate Member: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_alt_member']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_alt_member_client = (
+                     cls.os_project_alt_member.availability_zone_client)
+
+  #. Project alternate Reader: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_alt_reader']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_alt_reader_client = (
+                     cls.os_project_alt_reader.availability_zone_client)
+
+  #. Project other roles: This is supported and can be requested and used from
+     the test as below:
+
+     You need to use the ``project`` as the prefix in credentials type, and
+     based on that, Tempest will create test users under 'project' scope.
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = [['project_my_role1', 'my_own_role1', 'admin']
+                            ['project_my_role2', 'my_own_role2']]
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_role1_client = (
+                     cls.os_project_my_role1.availability_zone_client)
+                 cls.az_role2_client = (
+                     cls.os_project_my_role2.availability_zone_client)
+
+Pre-Provisioned Credentials
+---------------------------
+
+Pre-Provisioned credentials support the below set of personas and can be
+used in the test as shown above in the ``Dynamic Credentials`` Section.
+
+* Domain Admin
+* Domain Member
+* Domain Reader
+* System Admin
+* System Member
+* System Reader
+* Project Admin
+* Project Member
+* Project Reader
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 5bc0eac..ecf2930 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -126,16 +126,16 @@
 
 .. code-block:: python
 
-    class BaseTestCase1(api_version_utils.BaseMicroversionTest):
+   class BaseTestCase1(api_version_utils.BaseMicroversionTest):
 
-        [..]
-    @classmethod
-    def skip_checks(cls):
-        super(BaseTestCase1, cls).skip_checks()
-        api_version_utils.check_skip_with_microversion(cls.min_microversion,
-                                                       cls.max_microversion,
-                                                       CONF.compute.min_microversion,
-                                                       CONF.compute.max_microversion)
+       [..]
+       @classmethod
+       def skip_checks(cls):
+           super(BaseTestCase1, cls).skip_checks()
+           api_version_utils.check_skip_with_microversion(cls.min_microversion,
+                                                          cls.max_microversion,
+                                                          CONF.compute.min_microversion,
+                                                          CONF.compute.max_microversion)
 
 Skip logic can be added in tests base class or any specific test class depends on
 tests class structure.
@@ -302,6 +302,10 @@
 
   .. _2.2: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2
 
+  * `2.3`_
+
+  .. _2.3: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-kilo
+
   * `2.6`_
 
   .. _2.6: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id5
@@ -346,21 +350,25 @@
 
   .. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
 
+  * `2.33`_
+
+  .. _2.33: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id30
+
   * `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.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id35
 
   * `2.39`_
 
-  .. _2.39: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id35
+  .. _2.39: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id36
 
   * `2.41`_
 
-  .. _2.41: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id37
+  .. _2.41: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id38
 
   * `2.42`_
 
@@ -368,15 +376,15 @@
 
   * `2.47`_
 
-  .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id42
+  .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
 
   * `2.48`_
 
-  .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
+  .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
 
   * `2.49`_
 
-  .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
+  .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id45
 
   * `2.53`_
 
@@ -384,15 +392,15 @@
 
   * `2.54`_
 
-  .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49
+  .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id50
 
   * `2.55`_
 
-  .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id50
+  .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id51
 
   * `2.57`_
 
-  .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id52
+  .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id53
 
   * `2.59`_
 
@@ -404,24 +412,32 @@
 
   * `2.61`_
 
-  .. _2.61: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id55
+  .. _2.61: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id56
 
   * `2.63`_
 
-  .. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id57
+  .. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id58
 
   * `2.70`_
 
-  .. _2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id63
+  .. _2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id64
 
   * `2.71`_
 
-  .. _2.71: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id64
+  .. _2.71: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id65
 
   * `2.73`_
 
   .. _2.73: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id66
 
+  * `2.79`_
+
+  .. _2.79: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-train
+
+  * `2.86`_
+
+  .. _2.86: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id79
+
 * Volume
 
   * `3.3`_
@@ -455,3 +471,7 @@
   * `3.20`_
 
   .. _3.20:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id19
+
+  * `3.55`_
+
+  .. _3.55:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#maximum-in-rocky
diff --git a/doc/source/overview.rst b/doc/source/overview.rst
index e51b90b..2eaf72f 100644
--- a/doc/source/overview.rst
+++ b/doc/source/overview.rst
@@ -113,7 +113,7 @@
    There is also the option to use `stestr`_ directly. For example, from
    the workspace dir run::
 
-    $ stestr run --black-regex '\[.*\bslow\b.*\]' '^tempest\.(api|scenario)'
+    $ stestr run --exclude-regex '\[.*\bslow\b.*\]' '^tempest\.(api|scenario)'
 
    will run the same set of tests as the default gate jobs. Or you can
    use `unittest`_ compatible test runners such as `stestr`_, `pytest`_ etc.
diff --git a/doc/source/plugins/plugin.rst b/doc/source/plugins/plugin.rst
index ab1b0b1..b1fd6f8 100644
--- a/doc/source/plugins/plugin.rst
+++ b/doc/source/plugins/plugin.rst
@@ -31,6 +31,7 @@
 * tempest.common.credentials_factory
 * tempest.clients
 * tempest.test
+* tempest.scenario.manager
 
 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
@@ -268,12 +269,12 @@
 
    class MyAPIClient(rest_client.RestClient):
 
-    def __init__(self, auth_provider, service, region,
-                 my_arg, my_arg2=True, **kwargs):
-        super(MyAPIClient, self).__init__(
-            auth_provider, service, region, **kwargs)
-        self.my_arg = my_arg
-        self.my_args2 = my_arg
+       def __init__(self, auth_provider, service, region,
+                    my_arg, my_arg2=True, **kwargs):
+           super(MyAPIClient, self).__init__(
+               auth_provider, service, region, **kwargs)
+           self.my_arg = my_arg
+           self.my_args2 = my_arg
 
 Finally the service client should be structured in a python module, so that all
 service client classes are importable from it. Each major API version should
diff --git a/doc/source/stable_branch_support_policy.rst b/doc/source/stable_branch_support_policy.rst
index 87e3ad1..9c2d1ed 100644
--- a/doc/source/stable_branch_support_policy.rst
+++ b/doc/source/stable_branch_support_policy.rst
@@ -20,7 +20,7 @@
 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
+switch to running Tempest from a tag with support for the branch, or exclude
 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
diff --git a/doc/source/supported_version.rst b/doc/source/supported_version.rst
index 388b4cd..4ca7f0d 100644
--- a/doc/source/supported_version.rst
+++ b/doc/source/supported_version.rst
@@ -9,9 +9,9 @@
 
 Tempest master supports the below OpenStack Releases:
 
+* Victoria
 * Ussuri
 * Train
-* Stein
 
 For older OpenStack Release:
 
@@ -34,3 +34,4 @@
 
 * Python 3.6
 * Python 3.7
+* Python 3.8
diff --git a/doc/source/test_removal.rst b/doc/source/test_removal.rst
index ff4fa09..a3bb645 100644
--- a/doc/source/test_removal.rst
+++ b/doc/source/test_removal.rst
@@ -105,16 +105,16 @@
 value.
 
 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
+intent of this prong was mostly for interop/refstack 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
+interop/refstack is depending on being in Tempest. It's worth pointing out
+that if a test is used in `interop_wg`_ 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.
 
-.. _defcore: https://wiki.openstack.org/wiki/Governance/InteropWG
+.. _interop_wg: https://docs.opendev.org/openinfra/interop/latest/
 .. _interop: https://www.openstack.org/brand/interop
 
 Discussing the 3rd prong
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
index 0a29b7b..34df089 100644
--- a/doc/source/write_tests.rst
+++ b/doc/source/write_tests.rst
@@ -76,54 +76,54 @@
 
   class TestExampleCase(test.BaseTestCase):
 
-    @classmethod
-    def skip_checks(cls):
-        """This section is used to evaluate config early and skip all test
-           methods based on these checks
-        """
-        super(TestExampleCase, cls).skip_checks()
-        if not CONF.section.foo
-            cls.skip('A helpful message')
+      @classmethod
+      def skip_checks(cls):
+          """This section is used to evaluate config early and skip all test
+             methods based on these checks
+          """
+          super(TestExampleCase, cls).skip_checks()
+          if not CONF.section.foo
+              cls.skip('A helpful message')
 
-    @classmethod
-    def setup_credentials(cls):
-        """This section is used to do any manual credential allocation and also
-           in the case of dynamic credentials to override the default network
-           resource creation/auto allocation
-        """
-        # This call is used to tell the credential allocator to not create any
-        # network resources for this test case. It also enables selective
-        # creation of other neutron resources. NOTE: it must go before the
-        # super call
-        cls.set_network_resources()
-        super(TestExampleCase, cls).setup_credentials()
+      @classmethod
+      def setup_credentials(cls):
+          """This section is used to do any manual credential allocation and also
+             in the case of dynamic credentials to override the default network
+             resource creation/auto allocation
+          """
+          # This call is used to tell the credential allocator to not create any
+          # network resources for this test case. It also enables selective
+          # creation of other neutron resources. NOTE: it must go before the
+          # super call
+          cls.set_network_resources()
+          super(TestExampleCase, cls).setup_credentials()
 
-    @classmethod
-    def setup_clients(cls):
-        """This section is used to setup client aliases from the manager object
-           or to initialize any additional clients. Except in a few very
-           specific situations you should not need to use this.
-        """
-        super(TestExampleCase, cls).setup_clients()
-        cls.servers_client = cls.os_primary.servers_client
+      @classmethod
+      def setup_clients(cls):
+          """This section is used to setup client aliases from the manager object
+             or to initialize any additional clients. Except in a few very
+             specific situations you should not need to use this.
+          """
+          super(TestExampleCase, cls).setup_clients()
+          cls.servers_client = cls.os_primary.servers_client
 
-    @classmethod
-    def resource_setup(cls):
-        """This section is used to create any resources or objects which are
-           going to be used and shared by **all** test methods in the
-           TestCase. Note then anything created in this section must also be
-           destroyed in the corresponding resource_cleanup() method (which will
-           be run during tearDownClass())
-        """
-        super(TestExampleCase, cls).resource_setup()
-        cls.shared_server = cls.servers_client.create_server(...)
-        cls.addClassResourceCleanup(waiters.wait_for_server_termination,
-                                    cls.servers_client,
-                                    cls.shared_server['id'])
-        cls.addClassResourceCleanup(
-            test_utils.call_and_ignore_notfound_exc(
-                cls.servers_client.delete_server,
-                cls.shared_server['id']))
+      @classmethod
+      def resource_setup(cls):
+          """This section is used to create any resources or objects which are
+             going to be used and shared by **all** test methods in the
+             TestCase. Note then anything created in this section must also be
+             destroyed in the corresponding resource_cleanup() method (which will
+             be run during tearDownClass())
+          """
+          super(TestExampleCase, cls).resource_setup()
+          cls.shared_server = cls.servers_client.create_server(...)
+          cls.addClassResourceCleanup(waiters.wait_for_server_termination,
+                                      cls.servers_client,
+                                      cls.shared_server['id'])
+          cls.addClassResourceCleanup(
+              test_utils.call_and_ignore_notfound_exc(
+                  cls.servers_client.delete_server,
+                  cls.shared_server['id']))
 
 .. _credentials:
 
@@ -150,9 +150,9 @@
 
         credentials = ['primary', 'admin']
 
-    @classmethod
-    def skip_checks(cls):
-    ...
+        @classmethod
+        def skip_checks(cls):
+            ...
 
 In this example the ``TestExampleAdmin`` TestCase will allocate 2 sets of
 credentials, one regular user and one admin user. The corresponding manager
@@ -225,10 +225,10 @@
 
   class TestExampleCase(test.BaseTestCase):
 
-  @classmethod
-  def setup_credentials(cls):
-      cls.set_network_resources(network=True, subnet=True, router=False)
-      super(TestExampleCase, cls).setup_credentials()
+      @classmethod
+      def setup_credentials(cls):
+          cls.set_network_resources(network=True, subnet=True, router=False)
+          super(TestExampleCase, cls).setup_credentials()
 
 There are 2 quirks with the usage here. First for the set_network_resources
 function to work properly it **must be called before super()**. This is so
@@ -242,10 +242,10 @@
 
   class TestExampleCase(test.BaseTestCase):
 
-  @classmethod
-  def setup_credentials(cls):
-      cls.set_network_resources()
-      super(TestExampleCase, cls).setup_credentials()
+      @classmethod
+      def setup_credentials(cls):
+          cls.set_network_resources()
+          super(TestExampleCase, cls).setup_credentials()
 
 This will not allocate any networking resources. This is because by default all
 the arguments default to False.
@@ -282,8 +282,8 @@
 
 
   class TestExampleCase(test.BaseTestCase):
-    def test_example_create_server(self):
-      self.os_primary.servers_client.create_server(...)
+      def test_example_create_server(self):
+          self.os_primary.servers_client.create_server(...)
 
 is all you need to do. As described previously, in the above example the
 ``self.os_primary`` is created automatically because the base test class sets the
@@ -305,8 +305,8 @@
 
 
   class TestExampleCase(test.BaseTestCase):
-    def test_example_create_server(self):
-      credentials = self.os_primary.credentials
+      def test_example_create_server(self):
+          credentials = self.os_primary.credentials
 
 The credentials object provides access to all of the credential information you
 would need to make API requests. For example, building off the previous
@@ -316,9 +316,9 @@
 
 
   class TestExampleCase(test.BaseTestCase):
-    def test_example_create_server(self):
-      credentials = self.os_primary.credentials
-      username = credentials.username
-      user_id = credentials.user_id
-      password = credentials.password
-      tenant_id = credentials.tenant_id
+      def test_example_create_server(self):
+          credentials = self.os_primary.credentials
+          username = credentials.username
+          user_id = credentials.user_id
+          password = credentials.password
+          tenant_id = credentials.tenant_id
diff --git a/etc/whitelist.yaml b/etc/allow-list.yaml
similarity index 100%
rename from etc/whitelist.yaml
rename to etc/allow-list.yaml
diff --git a/etc/rbac-persona-accounts.yaml.sample b/etc/rbac-persona-accounts.yaml.sample
new file mode 100644
index 0000000..0b59538
--- /dev/null
+++ b/etc/rbac-persona-accounts.yaml.sample
@@ -0,0 +1,108 @@
+- user_domain_name: Default
+  password: password
+  roles:
+    - admin
+  username: tempest-system-admin-1
+  system: all
+- user_domain_name: Default
+  password: password
+  username: tempest-system-member-1
+  roles:
+    - member
+  system: all
+- user_domain_name: Default
+  password: password
+  username: tempest-system-reader-1
+  roles:
+    - reader
+  system: all
+- user_domain_name: Default
+  password: password
+  domain_name: tempest-test-domain
+  username: tempest-domain-admin-1
+  roles:
+    - admin
+- user_domain_name: Default
+  password: password
+  domain_name: tempest-test-domain
+  username: tempest-domain-member-1
+  roles:
+    - member
+- user_domain_name: Default
+  password: password
+  domain_name: tempest-test-domain
+  username: tempest-domain-reader-1
+  roles:
+    - reader
+- user_domain_name: Default
+  password: password
+  project_name: tempest-test-project
+  username: tempest-project-admin-1
+  roles:
+    - admin
+- user_domain_name: Default
+  password: password
+  project_name: tempest-test-project
+  username: tempest-project-member-1
+  roles:
+    - member
+- user_domain_name: Default
+  password: password
+  project_name: tempest-test-project
+  username: tempest-project-reader-1
+  roles:
+    - reader
+- user_domain_name: Default
+  password: password
+  username: tempest-system-admin-2
+  roles:
+    - admin
+  system: all
+- user_domain_name: Default
+  password: password
+  username: tempest-system-member-2
+  roles:
+    - member
+  system: all
+- user_domain_name: Default
+  password: password
+  system: all
+  username: tempest-system-reader-2
+  roles:
+    - reader
+- user_domain_name: Default
+  password: password
+  domain_name: tempest-test-domain
+  username: tempest-domain-admin-2
+  roles:
+    - admin
+- user_domain_name: Default
+  password: password
+  domain_name: tempest-test-domain
+  username: tempest-domain-member-2
+  roles:
+    - member
+- user_domain_name: Default
+  password: password
+  domain_name: tempest-test-domain
+  username: tempest-domain-reader-2
+  roles:
+    - reader
+- user_domain_name: Default
+  password: password
+  project_name: tempest-test-project
+  username: tempest-project-admin-2
+  roles:
+    - admin
+- user_domain_name: Default
+  password: password
+  project_name: tempest-test-project
+  username: tempest-project-member-2
+  roles:
+    - member
+- user_domain_name: Default
+  password: password
+  project_name: tempest-test-project
+  username: tempest-project-reader-2
+  roles:
+    - reader
diff --git a/playbooks/devstack-tempest-ipv6.yaml b/playbooks/devstack-tempest-ipv6.yaml
index 5f72345..568077e 100644
--- a/playbooks/devstack-tempest-ipv6.yaml
+++ b/playbooks/devstack-tempest-ipv6.yaml
@@ -7,11 +7,6 @@
 
 # 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
@@ -20,5 +15,18 @@
     # IPv6 only env for example Devstack IPv6 settings and services listen
     # address is IPv6 etc. This is invoked before tests are run so that we can
     # fail early if anything missing the IPv6 settings or deployments.
-    - ipv6-only-deployments-verification
-    - run-tempest
+    - devstack-ipv6-only-deployments-verification
+  tasks:
+    - name: Run Tempest version <= 26.0.0
+      include_role:
+        name: run-tempest-26
+      when:
+        - zuul.branch is defined
+        - zuul.branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]
+
+    - name: Run Tempest
+      include_role:
+        name: run-tempest
+      when:
+        - zuul.branch is defined
+        - zuul.branch not in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]
diff --git a/playbooks/devstack-tempest.yaml b/playbooks/devstack-tempest.yaml
index 7ee7411..269999c 100644
--- a/playbooks/devstack-tempest.yaml
+++ b/playbooks/devstack-tempest.yaml
@@ -7,11 +7,6 @@
 
 # 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('') }}"
   tasks:
     - name: Setup Tempest Run Directory
       include_role:
@@ -30,23 +25,30 @@
         name: tempest-cleanup
       vars:
         init_saved_state: true
-      when:
-        - run_tempest_dry_cleanup is defined
-        - run_tempest_cleanup is defined
+      when: (run_tempest_dry_cleanup is defined and run_tempest_dry_cleanup | bool) or
+            (run_tempest_cleanup is defined and run_tempest_cleanup | bool) or
+            (run_tempest_fail_if_leaked_resources is defined and run_tempest_fail_if_leaked_resources | bool)
+
+    - name: Run Tempest version <= 26.0.0
+      include_role:
+        name: run-tempest-26
+      when: (zuul.branch is defined and zuul.branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]) or
+            (zuul.override_checkout is defined and zuul.override_checkout in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"])
 
     - name: Run Tempest
       include_role:
         name: run-tempest
+      when: (zuul.branch is defined and zuul.branch not in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"] and zuul.override_checkout is not defined) or
+            (zuul.override_checkout is defined and zuul.override_checkout not in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"])
 
     - name: Run tempest cleanup dry-run
       include_role:
         name: tempest-cleanup
       vars:
         dry_run: true
-      when:
-        - run_tempest_dry_cleanup is defined
+      when: run_tempest_dry_cleanup is defined and run_tempest_dry_cleanup | bool
 
     - name: Run tempest cleanup
       include_role:
         name: tempest-cleanup
-      when: run_tempest_cleanup is defined
+      when: run_tempest_cleanup is defined and run_tempest_cleanup | bool
diff --git a/playbooks/enable-fips.yaml b/playbooks/enable-fips.yaml
new file mode 100644
index 0000000..c8f042d
--- /dev/null
+++ b/playbooks/enable-fips.yaml
@@ -0,0 +1,4 @@
+- hosts: all
+  tasks:
+    - include_role:
+        name: enable-fips
diff --git a/releasenotes/notes/Add-keystone-v3-OS_FEDERATION-APIs-as-tempest-clients-fe9e10a0fe5f09d4.yaml b/releasenotes/notes/Add-keystone-v3-OS_FEDERATION-APIs-as-tempest-clients-fe9e10a0fe5f09d4.yaml
new file mode 100644
index 0000000..33df7c4
--- /dev/null
+++ b/releasenotes/notes/Add-keystone-v3-OS_FEDERATION-APIs-as-tempest-clients-fe9e10a0fe5f09d4.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    The following tempest clients for keystone v3 OS_FEDERATION API were
+    implemented in this release
+
+    * identity_providers
+    * protocols
+    * mappings
+    * service_providers
diff --git a/releasenotes/notes/Add-volume_size_extend-config--opt-for-volume-tests-041f7d25fc2f3e05.yaml b/releasenotes/notes/Add-volume_size_extend-config--opt-for-volume-tests-041f7d25fc2f3e05.yaml
new file mode 100644
index 0000000..8069bd3
--- /dev/null
+++ b/releasenotes/notes/Add-volume_size_extend-config--opt-for-volume-tests-041f7d25fc2f3e05.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Adding new config option for volume tests which allows to specify the size
+    a volume will be extended by (if a test does extend a volume or needs
+    a new bigger volume). The option is beneficial in case such tests are
+    executed on systems where the chunk size (the minimum size a volume can be
+    extended by) is other than 1 (originally hardcoded in the tests).:
+
+    CONF.volume.volume_size_extend
diff --git a/releasenotes/notes/Inclusive-jargon-17621346744f0cf4.yaml b/releasenotes/notes/Inclusive-jargon-17621346744f0cf4.yaml
new file mode 100644
index 0000000..089569e
--- /dev/null
+++ b/releasenotes/notes/Inclusive-jargon-17621346744f0cf4.yaml
@@ -0,0 +1,13 @@
+---
+deprecations:
+  - |
+    In this release the following tempest arguments are deprecated and
+    replaced by new ones which are functionally equivalent:
+
+     * --black-regex is replaced by --exclude-regex
+     * --blacklist-file is replaced by --exclude-list
+     * --whitelist-file is replaced by --include-list
+
+    For now Tempest supports both (new and old ones) in order to make the
+    transition for all consumers smoother. However, that's just a temporary
+    case and the old options will be removed soon.
diff --git a/releasenotes/notes/Remove-deprecated-scenario.img_dir-option-da626e6153181e16.yaml b/releasenotes/notes/Remove-deprecated-scenario.img_dir-option-da626e6153181e16.yaml
new file mode 100644
index 0000000..2514e48
--- /dev/null
+++ b/releasenotes/notes/Remove-deprecated-scenario.img_dir-option-da626e6153181e16.yaml
@@ -0,0 +1,8 @@
+---
+upgrade:
+  - |
+    img_dir scenario option has been deprecated and it's being removed.
+    Starting Tempest 25.0.0 release, CONF.scenario.img_file needs a full path
+    for the image. Until this release, old behavior was maintained and kept
+    working however a user needs to specify the full path in
+    CONF.scenario.img_file config option from now on.
diff --git a/releasenotes/notes/Remove-manager-2e0b0af48f01294a.yaml b/releasenotes/notes/Remove-manager-2e0b0af48f01294a.yaml
new file mode 100644
index 0000000..822df7d
--- /dev/null
+++ b/releasenotes/notes/Remove-manager-2e0b0af48f01294a.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - |
+    In this release tempest/manager.py is removed after more than 4 years
+    of deprecation.
diff --git a/releasenotes/notes/Stabilize-scenario-manager-adf36d21b08e31a4.yaml b/releasenotes/notes/Stabilize-scenario-manager-adf36d21b08e31a4.yaml
new file mode 100644
index 0000000..8df5f3c
--- /dev/null
+++ b/releasenotes/notes/Stabilize-scenario-manager-adf36d21b08e31a4.yaml
@@ -0,0 +1,31 @@
+---
+prelude: >
+    Tempest.scenario.manager is now a stable interface for Tempest plugins.
+features:
+  - |
+    In this release, we made tempest/scenario/manager.py a stable interface
+    ready to be consumed by all tempest plugins. The effort was tracked in
+    the following etherpad [1], and the related review can be listed via [2]:
+
+    * [1] https://etherpad.opendev.org/p/tempest-scenario-manager
+    * [2] https://review.opendev.org/#/q/topic:bp/tempest-scenario-manager-stable(status:open+OR+status:merged)
+
+    Some time ago, tempest/scenario/manager.py got copied to most of the plugins
+    and therefore, it diverged - every plugin's copy had slight differences.
+    In this release, we pushed changes to unify the manager's methods and
+    improved their APIs in order to have them easier consumable:
+
+    * we added implementations of methods that were often used in plugins'
+      manager.py however were not implemented in our manager
+    * we improved APIs by f.e. adding a kwargs argument so that the consumers
+      are more in control of the data that are passed to tempest clients
+    * we modified logic of a few methods so that it complies with the plugins'
+      manager versions in order to prepare for a situation when the plugins
+      can reuse Tempest manager as much as possible rather than keeping their
+      own copy
+    * we made methods consistent w.r.t. names and parameters
+    * we split the lengthy methods to have more readable code
+    * previously private methods which had a potential to be reused were
+      made public so that it's clear they are expected to be used in tempest
+      plugins
+    * missing docstrings have been added
diff --git a/releasenotes/notes/add-alt-project-dynamic-creds-1a3bc543e65d9433.yaml b/releasenotes/notes/add-alt-project-dynamic-creds-1a3bc543e65d9433.yaml
new file mode 100644
index 0000000..de81b2b
--- /dev/null
+++ b/releasenotes/notes/add-alt-project-dynamic-creds-1a3bc543e65d9433.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add project alternate admin, member and reader role for dynamic credentials.
diff --git a/releasenotes/notes/add-assisted-volume-snapshot-client-737f5cb35d58c1b6.yaml b/releasenotes/notes/add-assisted-volume-snapshot-client-737f5cb35d58c1b6.yaml
new file mode 100644
index 0000000..5498688
--- /dev/null
+++ b/releasenotes/notes/add-assisted-volume-snapshot-client-737f5cb35d58c1b6.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add a new client to handle requests to create and delete assisted volume snapshots.
diff --git a/releasenotes/notes/add-compute-feature-ide-bus-b63802502c378083.yaml b/releasenotes/notes/add-compute-feature-ide-bus-b63802502c378083.yaml
new file mode 100644
index 0000000..43a0e8c
--- /dev/null
+++ b/releasenotes/notes/add-compute-feature-ide-bus-b63802502c378083.yaml
@@ -0,0 +1,10 @@
+---
+other:
+  - |
+    A new ``[compute-feature-enabled]ide_bus`` config option has been
+    introduced to indicate if the environment supports attaching disks to an
+    instance using an ``IDE`` bus.
+
+    This currently defaults to ``True`` but should be set to ``False`` when
+    using the libvirt OpenStack Nova virt driver *and* the ``q35`` machine type
+    as support for this bus is no longer provided.
diff --git a/releasenotes/notes/add-compute-feature-shelve-migrate-fdbd3633abe65c4e.yaml b/releasenotes/notes/add-compute-feature-shelve-migrate-fdbd3633abe65c4e.yaml
new file mode 100644
index 0000000..121e060
--- /dev/null
+++ b/releasenotes/notes/add-compute-feature-shelve-migrate-fdbd3633abe65c4e.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add a new config option ``[compute-feature-enabled] shelve_migrate``
+    which enable test for environment that support cold migration of qcow2
+    unshelved instance.
diff --git a/releasenotes/notes/add-identity-roles-system-methods-519dc144231993a3.yaml b/releasenotes/notes/add-identity-roles-system-methods-519dc144231993a3.yaml
new file mode 100644
index 0000000..1840c10
--- /dev/null
+++ b/releasenotes/notes/add-identity-roles-system-methods-519dc144231993a3.yaml
@@ -0,0 +1,13 @@
+---
+features:
+  - |
+    Added methods to the identity v3 roles client to support:
+
+    - PUT /v3/system/users/{user}/roles/{role}
+    - GET /v3/system/users/{user}/roles
+    - GET /v3/system/users/{user}/roles/{role}
+    - DELETE /v3/system/users/{user}/roles/{role}
+    - PUT /v3/system/groups/{group}/roles/{role}
+    - GET /v3/system/groups/{group}/roles
+    - GET /v3/system/groups/{group}/roles/{role}
+    - DELETE /v3/system/groups/{group}/roles/{role}
diff --git a/releasenotes/notes/add-image-alt-ssh-user-config-option-1b775af2f468aa5b.yaml b/releasenotes/notes/add-image-alt-ssh-user-config-option-1b775af2f468aa5b.yaml
new file mode 100644
index 0000000..159bbe8
--- /dev/null
+++ b/releasenotes/notes/add-image-alt-ssh-user-config-option-1b775af2f468aa5b.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - A new config option in the validation section, image_alt_ssh_user,
+    to specify the user name used to authenticate to an alternative
+    instance (instance using image_ref_alt) in tests. By default this
+    is set to root.
+  - A new config option in the validation section, image_alt_ssh_password,
+    to specify the password used to authenticate to an alternative
+    instance (instance using image_ref_alt) in tests. By default this
+    is set to password.
diff --git a/releasenotes/notes/add-placement-usage-client-method-8b6015cbd8a5e0f6.yaml b/releasenotes/notes/add-placement-usage-client-method-8b6015cbd8a5e0f6.yaml
new file mode 100644
index 0000000..d31a33c
--- /dev/null
+++ b/releasenotes/notes/add-placement-usage-client-method-8b6015cbd8a5e0f6.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Add ``placement`` API usage method for evaluating resource class
+    utilization of the resource provider. The following API call is available
+    for tempest from now in the resource_providers_client:
+
+    * GET /resource_providers/{uuid}/usages
diff --git a/releasenotes/notes/add-qos-apis-for-bandwidth-limit-rules-cc144660fcaa419a.yaml b/releasenotes/notes/add-qos-apis-for-bandwidth-limit-rules-cc144660fcaa419a.yaml
new file mode 100644
index 0000000..da58ba3
--- /dev/null
+++ b/releasenotes/notes/add-qos-apis-for-bandwidth-limit-rules-cc144660fcaa419a.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    Add "QoS bandwidth limit rules" APIs to:
+    "tempest/tests/lib/services/network/test_qos_limit_bandwidth_rules_client.py"  module.
+
+    * List bandwidth limit rules for QoS policy
+    * Create bandwidth limit rule
+    * Show bandwidth limit rule details
+    * Update bandwidth limit rule
+    * Delete bandwidth limit rule
\ No newline at end of file
diff --git a/releasenotes/notes/add-qos-minimum-packet-rule-client-c8bfe09873032d4a.yaml b/releasenotes/notes/add-qos-minimum-packet-rule-client-c8bfe09873032d4a.yaml
new file mode 100644
index 0000000..b65b164
--- /dev/null
+++ b/releasenotes/notes/add-qos-minimum-packet-rule-client-c8bfe09873032d4a.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Added QoS minimum packet rate rule client:
+
+    * create_minimum_packet_rate_rule
+    * update_minimum_packet_rate_rule
+    * show_minimum_packet_rate_rule
+    * list_minimum_packet_rate_rules
+    * delete_minimum_packet_rate_rule also
diff --git a/releasenotes/notes/add-ssh-key-type-38d7a2f900d79842.yaml b/releasenotes/notes/add-ssh-key-type-38d7a2f900d79842.yaml
new file mode 100644
index 0000000..fef3004
--- /dev/null
+++ b/releasenotes/notes/add-ssh-key-type-38d7a2f900d79842.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add parameter to specify the SSH key type.  Current options are 'rsa'
+    (which is the default) and 'ecdsa'.  Tempest now supports the importing
+    and generation of both 'rsa' and 'ecdsa' SSH key types.
diff --git a/releasenotes/notes/add-volume-transfers-v3.55-73f75073ad2c4091.yaml b/releasenotes/notes/add-volume-transfers-v3.55-73f75073ad2c4091.yaml
new file mode 100644
index 0000000..c35dd67
--- /dev/null
+++ b/releasenotes/notes/add-volume-transfers-v3.55-73f75073ad2c4091.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Add a ``TransfersV355Client`` to the volume v3 ``transfer_client`` library
+    supporting create, list, show, delete, and accept operations for the `new
+    Volume Transfers API
+    <https://docs.openstack.org/api-ref/block-storage/v3/#volume-transfers-volume-transfers-3-55-or-later>`_
+    of the Block Storage API v3.  The min_microversion of this API is 3.55.
diff --git a/releasenotes/notes/associate-disassociate-floating_ip-0b6cfebeef1304b0.yaml b/releasenotes/notes/associate-disassociate-floating_ip-0b6cfebeef1304b0.yaml
new file mode 100644
index 0000000..8e42e85
--- /dev/null
+++ b/releasenotes/notes/associate-disassociate-floating_ip-0b6cfebeef1304b0.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Added associate_floating_ip() and dissociate_floating_ip() methods
+    to the scenario manager.
diff --git a/releasenotes/notes/create_loginable_secgroup_rule-73722fd4b4eb12d0.yaml b/releasenotes/notes/create_loginable_secgroup_rule-73722fd4b4eb12d0.yaml
new file mode 100644
index 0000000..e53411d
--- /dev/null
+++ b/releasenotes/notes/create_loginable_secgroup_rule-73722fd4b4eb12d0.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Added public interface create_loginable_secgroup_rule().
+    Since this interface is meant to be used by tempest plugins,
+    It doesn't neccessarily require to be private api.
diff --git a/releasenotes/notes/create_security_group_rule-16d58a8f0f0ff262.yaml b/releasenotes/notes/create_security_group_rule-16d58a8f0f0ff262.yaml
new file mode 100644
index 0000000..3354f65
--- /dev/null
+++ b/releasenotes/notes/create_security_group_rule-16d58a8f0f0ff262.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Added public interface create_security_group_rule().
+    Since this interface is meant to be used by tempest plugins,
+    It doesn't neccessarily require to be private api.
diff --git a/releasenotes/notes/deprecate-and-enable-identity-application_credentials-1d4eaef4c3c9dcba.yaml b/releasenotes/notes/deprecate-and-enable-identity-application_credentials-1d4eaef4c3c9dcba.yaml
new file mode 100644
index 0000000..4b31ff8
--- /dev/null
+++ b/releasenotes/notes/deprecate-and-enable-identity-application_credentials-1d4eaef4c3c9dcba.yaml
@@ -0,0 +1,17 @@
+---
+upgrade:
+  - |
+    Application credentials are supported by Keystone since Queens.
+    As Tempest currently supports only much newer OpenStack versions
+    (Ussuri or later), the configuration option which enables
+    application credentials testing
+    (``CONF.identity-feature-enabled.application_credentials``)
+    is now enabled by default.
+deprecations:
+  - |
+    Application credentials are supported by Keystone since Queens.
+    As Tempest currently supports only much newer OpenStack versions
+    (Ussuri or later), the configuration option which enables
+    application credentials testing
+    (``CONF.identity-feature-enabled.application_credentials``)
+    is now deprecated.
diff --git a/releasenotes/notes/deprecate-and-enable-identity-project-tags-23b87518888e563a.yaml b/releasenotes/notes/deprecate-and-enable-identity-project-tags-23b87518888e563a.yaml
new file mode 100644
index 0000000..be2df6b
--- /dev/null
+++ b/releasenotes/notes/deprecate-and-enable-identity-project-tags-23b87518888e563a.yaml
@@ -0,0 +1,17 @@
+---
+upgrade:
+  - |
+    Project tags are supported by Keystone since Queens.
+    As Tempest currently supports only much newer OpenStack versions
+    (Ussuri or later), the configuration option which enables
+    project tags testing
+    (``CONF.identity-feature-enabled.project_tags``)
+    is now enabled by default.
+deprecations:
+  - |
+    Project tags are supported by Keystone since Queens.
+    As Tempest currently supports only much newer OpenStack versions
+    (Ussuri or later), the configuration option which enables
+    project tags testing
+    (``CONF.identity-feature-enabled.project_tags``)
+    is now deprecated.
diff --git a/releasenotes/notes/deprecate-image-v1-service-clients-d12ed42210bb76b5.yaml b/releasenotes/notes/deprecate-image-v1-service-clients-d12ed42210bb76b5.yaml
new file mode 100644
index 0000000..4a22f8e
--- /dev/null
+++ b/releasenotes/notes/deprecate-image-v1-service-clients-d12ed42210bb76b5.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+  - |
+    Tempest service clients for image v1 APIs (tempest.lib.services.image.v1 module)
+    is deprecated and will be removed once Tempest stop supporting stable Ussuri release
+    which is last release Image v1 APIs are present in glance.
diff --git a/releasenotes/notes/deprecate-old-api-microversion-fixture-a471aac985c0f3fb.yaml b/releasenotes/notes/deprecate-old-api-microversion-fixture-a471aac985c0f3fb.yaml
new file mode 100644
index 0000000..652f7fa
--- /dev/null
+++ b/releasenotes/notes/deprecate-old-api-microversion-fixture-a471aac985c0f3fb.yaml
@@ -0,0 +1,7 @@
+---
+deprecations:
+  - |
+    Old APIMicroversionFixture classes ``tempest.api.compute.api_microversion_fixture.APIMicroversionFixture``
+    and ``tempest.api.volume.api_microversion_fixture.APIMicroversionFixture``
+    has been deprecated for removal in favor of new location
+    ``tempest.lib.common.api_microversion_fixture.APIMicroversionFixture``
diff --git a/releasenotes/notes/deprecate-volume-v2-service-clients-ff8a2a7be1797eb5.yaml b/releasenotes/notes/deprecate-volume-v2-service-clients-ff8a2a7be1797eb5.yaml
new file mode 100644
index 0000000..de05679
--- /dev/null
+++ b/releasenotes/notes/deprecate-volume-v2-service-clients-ff8a2a7be1797eb5.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+  - |
+    Tempest service clients for volume v2 APIs (tempest.lib.services.volume.v2 module)
+    is deprecated and will be removed once Tempest stop supporting stable wallaby release
+    which is last release volume v2 APIs are present in cinder.
diff --git a/releasenotes/notes/end-of-support-for-stein-f795b968d83497a9.yaml b/releasenotes/notes/end-of-support-for-stein-f795b968d83497a9.yaml
new file mode 100644
index 0000000..fd7a874
--- /dev/null
+++ b/releasenotes/notes/end-of-support-for-stein-f795b968d83497a9.yaml
@@ -0,0 +1,12 @@
+---
+prelude: |
+    This is an intermediate release during the Wallaby development cycle to
+    mark the end of support for EM Stein release in Tempest.
+    After this release, Tempest will support below OpenStack Releases:
+
+    * Victoria
+    * Ussuri
+    * Train
+
+    Current development of Tempest is for OpenStack Wallaby development
+    cycle.
diff --git a/releasenotes/notes/end-of-support-for-train-83369468215d7485.yaml b/releasenotes/notes/end-of-support-for-train-83369468215d7485.yaml
new file mode 100644
index 0000000..36681c7
--- /dev/null
+++ b/releasenotes/notes/end-of-support-for-train-83369468215d7485.yaml
@@ -0,0 +1,12 @@
+---
+prelude: |
+    This is an intermediate release during the Xena development cycle to
+    mark the end of support for EM Train release in Tempest.
+    After this release, Tempest will support below OpenStack Releases:
+
+    * Wallaby
+    * Victoria
+    * Ussuri
+
+    Current development of Tempest is for OpenStack Xena development
+    cycle.
diff --git a/releasenotes/notes/floating-ips-port-forwarding-client-cf8820b910bd7f4d.yaml b/releasenotes/notes/floating-ips-port-forwarding-client-cf8820b910bd7f4d.yaml
new file mode 100644
index 0000000..3aaec69
--- /dev/null
+++ b/releasenotes/notes/floating-ips-port-forwarding-client-cf8820b910bd7f4d.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add a new client to lists, creates, shows information for,
+    updates and deletes neutron floating ips port forwarding
+    resource.
diff --git a/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml b/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml
new file mode 100644
index 0000000..fde6193
--- /dev/null
+++ b/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Adds a method to images_client to get tasks relevant to a given image. Also adds
+    has_version() method to image versions_client to probe for availability of a given
+    API version.
diff --git a/releasenotes/notes/intermediate-wallaby-release-55a0b31b1dee7b23.yaml b/releasenotes/notes/intermediate-wallaby-release-55a0b31b1dee7b23.yaml
new file mode 100644
index 0000000..cda3b89
--- /dev/null
+++ b/releasenotes/notes/intermediate-wallaby-release-55a0b31b1dee7b23.yaml
@@ -0,0 +1,4 @@
+---
+prelude: >
+    This is an intermediate release during the Wallaby development cycle to
+    make new functionality available to plugins and other consumers.
diff --git a/releasenotes/notes/limits-client-d656f16a3d3e84fc.yaml b/releasenotes/notes/limits-client-d656f16a3d3e84fc.yaml
new file mode 100644
index 0000000..311eca3
--- /dev/null
+++ b/releasenotes/notes/limits-client-d656f16a3d3e84fc.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add a new client for keystone's unified limits API to create and update limits.
diff --git a/releasenotes/notes/log-resource-client-20e58a295f729902.yaml b/releasenotes/notes/log-resource-client-20e58a295f729902.yaml
new file mode 100644
index 0000000..405fc5f
--- /dev/null
+++ b/releasenotes/notes/log-resource-client-20e58a295f729902.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add a new client to lists, creates, shows information for,
+    and updates neutron log resource.
diff --git a/releasenotes/notes/log_console_output-dae6b8740b5a5821.yaml b/releasenotes/notes/log_console_output-dae6b8740b5a5821.yaml
new file mode 100644
index 0000000..2779b26
--- /dev/null
+++ b/releasenotes/notes/log_console_output-dae6b8740b5a5821.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Added public interface log_console_output().
+    It used to be a private method with name _log_console_output().
+    Since this interface is meant to be used by tempest plugins,
+    It doesn't neccessarily require to be private api.
+
diff --git a/releasenotes/notes/loggable-resource-client-5977d46a7ea52199.yaml b/releasenotes/notes/loggable-resource-client-5977d46a7ea52199.yaml
new file mode 100644
index 0000000..ac83eaf
--- /dev/null
+++ b/releasenotes/notes/loggable-resource-client-5977d46a7ea52199.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Lists neutron's Loggable resources API service clients are available in
+    ``tempest/lib/services/network/loggable_resource_client.py`` module.
\ No newline at end of file
diff --git a/releasenotes/notes/make-create-user-domain-aware-for-v3-creds-client-5054f58e715adc0c.yaml b/releasenotes/notes/make-create-user-domain-aware-for-v3-creds-client-5054f58e715adc0c.yaml
new file mode 100644
index 0000000..8931f09
--- /dev/null
+++ b/releasenotes/notes/make-create-user-domain-aware-for-v3-creds-client-5054f58e715adc0c.yaml
@@ -0,0 +1,9 @@
+---
+fixes:
+  - |
+    [`bug 1613819 <https://bugs.launchpad.net/tempest/+bug/1613819>`_]
+    admin_domain_name and default_credentials_domain_name parameters
+    under [auth] now affect a domain used for creating test users just
+    as they affect it for projects. Previously a domain with an id set
+    to "default" had to be present in order for test user creation to
+    succeed with Keystone v3.
diff --git a/releasenotes/notes/merge-tempest-horizon-plugin-39d555339ab8c7ce.yaml b/releasenotes/notes/merge-tempest-horizon-plugin-39d555339ab8c7ce.yaml
new file mode 100644
index 0000000..ff406fb
--- /dev/null
+++ b/releasenotes/notes/merge-tempest-horizon-plugin-39d555339ab8c7ce.yaml
@@ -0,0 +1,6 @@
+---
+prelude: >
+    The integrated horizon dashboard test is now moved
+    from tempest-horizon plugin into Tempest. You do not need
+    to install tempest-horizon to run the horizon test which
+    can be run using Tempest itself.
diff --git a/releasenotes/notes/random-bytes-size-limit-ee94a8c6534fe916.yaml b/releasenotes/notes/random-bytes-size-limit-ee94a8c6534fe916.yaml
new file mode 100644
index 0000000..42322e4
--- /dev/null
+++ b/releasenotes/notes/random-bytes-size-limit-ee94a8c6534fe916.yaml
@@ -0,0 +1,9 @@
+---
+upgrade:
+  - |
+    The ``tempest.lib.common.utils.data_utils.random_bytes()`` helper
+    function will no longer allow a ``size`` of more than 1MiB. Tests
+    generally do not need to generate and use large payloads for
+    feature verification and it is easy to lose track of and duplicate
+    large buffers. The sum total of such errors can become problematic
+    in paralllelized and constrained CI environments.
diff --git a/releasenotes/notes/remove-deprecated-old-token-clients-e4c2e654132f1130.yaml b/releasenotes/notes/remove-deprecated-old-token-clients-e4c2e654132f1130.yaml
new file mode 100644
index 0000000..9acb873
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-old-token-clients-e4c2e654132f1130.yaml
@@ -0,0 +1,9 @@
+---
+prelude: >
+    Tempest's identity service client TokenClientJSON and V3TokenClientJSON
+    has been removed.
+upgrade:
+  - |
+    Tempest's identity service client TokenClientJSON and V3TokenClientJSON
+    were deprecated since long which are not removed. Please use new service
+    clients TokenClient and V3TokenClient instead.
diff --git a/releasenotes/notes/remove-deprecated-volume-config-options-4b7ea93b88e5b982.yaml b/releasenotes/notes/remove-deprecated-volume-config-options-4b7ea93b88e5b982.yaml
new file mode 100644
index 0000000..f3002f9
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-volume-config-options-4b7ea93b88e5b982.yaml
@@ -0,0 +1,9 @@
+---
+upgrade:
+  - |
+    Deprecated config options to select the Volume API version have been
+    removed. Use ``CONF.volume.catalog_type`` to run volume tests under v3
+    or v2 APIs.
+
+    * ``CONF.volume-feature-enabled.api_v2``
+    * ``CONF.volume-feature-enabled.api_v3``
\ No newline at end of file
diff --git a/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml b/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml
new file mode 100644
index 0000000..ac20340
--- /dev/null
+++ b/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    The old deprecated ``data-ultis`` from ``tempest.common.utils`` has been
+    removed. If you are still using this, use the stable version
+    of ``data-utils`` from new location ``tempest.lib.common.utils``.
diff --git a/releasenotes/notes/remove-volume-v1-service-clients-9235e3a965f93c09.yaml b/releasenotes/notes/remove-volume-v1-service-clients-9235e3a965f93c09.yaml
new file mode 100644
index 0000000..3c90f81
--- /dev/null
+++ b/releasenotes/notes/remove-volume-v1-service-clients-9235e3a965f93c09.yaml
@@ -0,0 +1,8 @@
+---
+prelude: >
+    Tempest Service clients for volume v1 APIs are removed.
+upgrade:
+  - |
+    Cinder removed the volume v1 APIs in queens release and Tempest
+    now support only stable train onwards release so all the Tempest
+    service clients for volume v1 APIs are now removed.
diff --git a/releasenotes/notes/set-default-value-of-concurrency-to-2-d916d5c31e3725d5.yaml b/releasenotes/notes/set-default-value-of-concurrency-to-2-d916d5c31e3725d5.yaml
new file mode 100644
index 0000000..0d964a9
--- /dev/null
+++ b/releasenotes/notes/set-default-value-of-concurrency-to-2-d916d5c31e3725d5.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    [`bug 1948935 <https://bugs.launchpad.net/tempest/+bug/1948935>`_]
+    The default value of account-generator --concurrency parameter is now
+    set to 2 instead of 1.
diff --git a/releasenotes/notes/support-for-rbac-new-scope-6ec8164ce1e7288c.yaml b/releasenotes/notes/support-for-rbac-new-scope-6ec8164ce1e7288c.yaml
new file mode 100644
index 0000000..af7df93
--- /dev/null
+++ b/releasenotes/notes/support-for-rbac-new-scope-6ec8164ce1e7288c.yaml
@@ -0,0 +1,13 @@
+---
+prelude: >
+    Support for RBAC new system scope is added in Tempest.
+features:
+  - |
+    Keystone provides the new scoped token called ``system`` which
+    can be used to query the system scoped API operation. Projects
+    are moving towards the policy with new scope types, Keystone, Nova
+    already provide the new policy for RBAC checks. Tempest has added
+    the support to query the system scoped token from keystone to test
+    the new policy.
+    As next step, we will be moving all the Tempest tests on the project's
+    new policy.
diff --git a/releasenotes/notes/support-scope-in-get-roles-dynamic-creds-90bfab163c1c289a.yaml b/releasenotes/notes/support-scope-in-get-roles-dynamic-creds-90bfab163c1c289a.yaml
new file mode 100644
index 0000000..26282f0
--- /dev/null
+++ b/releasenotes/notes/support-scope-in-get-roles-dynamic-creds-90bfab163c1c289a.yaml
@@ -0,0 +1,36 @@
+---
+features:
+  - |
+    Dynamic credentials now support the scope type for specific roles
+    too along with ``admin``, ``member``, ``reader`` role.
+    Test can specify the scope in the prefix of ``cls.credentials`` name.
+    If ``system`` is prefix in ``cls.credentials`` name then creds will
+    be created with scope as ``system``. If ``domain`` is prefix in
+    ``cls.credentials`` name then creds will be created with scope as
+    ``domain`` otherwise default ``project`` scope will be used.
+    For Example::
+
+        credentials = [['my_role', 'role1'], # this will be old style and project scoped
+                       ['project_my_role', 'role1'], # this will be project scoped
+                       ['domain_my_role', 'role1'], # this will be domain scoped
+                       ['system_my_role', 'role1']] # this will be system scoped
+
+    And below is how test can access the credential manager of respective
+    credentials type::
+
+        cls.os_my_role.any_client
+        cls.os_project_my_role.any_client
+        cls.os_domain_my_role.any_client
+        cls.os_system_my_role.any_client
+
+
+    For backward compatibility, we set the credentials manager class attribute
+    in old style form too which is prefix with ``os_roles_*``, example
+    ``cls.os_roles_my_role`` but we recommend to use the new style attribute
+    as shown above.
+issues:
+  - |
+    Scope support for specific role is not yet added for pre-provisioned credentials.
+fixes:
+  - |
+    Fixes the `bug# 1917168 <https://bugs.launchpad.net/tempest/+bug/1917168>`_
diff --git a/releasenotes/notes/swift-improve-cleanup-63cfc21ffb3aff6f.yaml b/releasenotes/notes/swift-improve-cleanup-63cfc21ffb3aff6f.yaml
new file mode 100644
index 0000000..9e48510
--- /dev/null
+++ b/releasenotes/notes/swift-improve-cleanup-63cfc21ffb3aff6f.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+  - |
+    Improve cleanup after Swift testing. Ensures containers are empty before
+    deleting to prevent errors due to delayed execution.
diff --git a/releasenotes/notes/system-scope-44244cc955a7825f.yaml b/releasenotes/notes/system-scope-44244cc955a7825f.yaml
new file mode 100644
index 0000000..969a71f
--- /dev/null
+++ b/releasenotes/notes/system-scope-44244cc955a7825f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Adds new personas that can be used to test service policies for all
+    default scopes (project, domain, and system) and roles (reader, member,
+    and admin). Both dynamic credentials and pre-provisioned credentials are
+    supported.
diff --git a/releasenotes/notes/tempest-wallaby-release-0f2cea5ccf63485e.yaml b/releasenotes/notes/tempest-wallaby-release-0f2cea5ccf63485e.yaml
new file mode 100644
index 0000000..25fb1f2
--- /dev/null
+++ b/releasenotes/notes/tempest-wallaby-release-0f2cea5ccf63485e.yaml
@@ -0,0 +1,17 @@
+---
+prelude: |
+    This release is to tag Tempest for OpenStack Wallaby release.
+    This release marks the start of Wallaby release support in Tempest.
+    After this release, Tempest will support below OpenStack Releases:
+
+    * Wallaby
+    * Victoria
+    * Ussuri
+    * Train
+
+    Current development of Tempest is for OpenStack Xena development
+    cycle. Every Tempest commit is also tested against master during
+    the Xena cycle. However, this does not necessarily mean that using
+    Tempest as of this tag will work against a Xena (or future release)
+    cloud.
+    To be on safe side, use this tag to test the OpenStack Wallaby release.
diff --git a/releasenotes/notes/tempest-xena-release-3ffb30eb59e49f2c.yaml b/releasenotes/notes/tempest-xena-release-3ffb30eb59e49f2c.yaml
new file mode 100644
index 0000000..218d8ca
--- /dev/null
+++ b/releasenotes/notes/tempest-xena-release-3ffb30eb59e49f2c.yaml
@@ -0,0 +1,18 @@
+---
+prelude: |
+    This release is to tag Tempest for OpenStack Xena release.
+    This release marks the start of Xena release support in Tempest.
+    After this release, Tempest will support below OpenStack Releases:
+
+    * Xena
+    * Wallaby
+    * Victoria
+    * Ussuri
+
+    Current development of Tempest is for OpenStack Yoga development
+    cycle. Every Tempest commit is also tested against master during
+    the Yoga cycle. However, this does not necessarily mean that using
+    Tempest as of this tag will work against a Yoga (or future release)
+    cloud.
+    To be on safe side, use this tag to test the OpenStack Xena release.
+
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 9a9de43..b353a18 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -59,7 +59,7 @@
 master_doc = 'index'
 
 # General information about the project.
-copyright = u'2016, tempest Developers'
+copyright = '2016, tempest Developers'
 
 # Release do not need a version number in the title, they
 # cover multiple versions.
@@ -194,8 +194,8 @@
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
     ('index', 'olso.configReleaseNotes.tex',
-     u'olso.config Release Notes Documentation',
-     u'tempest Developers', 'manual'),
+     'olso.config Release Notes Documentation',
+     'tempest Developers', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
@@ -225,8 +225,8 @@
 # (source start file, name, description, authors, manual section).
 man_pages = [
     ('index', 'olso.configreleasenotes',
-     u'tempest Release Notes Documentation',
-     [u'tempest Developers'], 1)
+     'tempest Release Notes Documentation',
+     ['tempest Developers'], 1)
 ]
 
 # If true, show URL addresses after external links.
@@ -240,8 +240,8 @@
 #  dir menu entry, description, category)
 texinfo_documents = [
     ('index', 'tempestReleaseNotes',
-     u'tempest Release Notes Documentation',
-     u'tempest Developers', 'olso.configReleaseNotes',
+     'tempest Release Notes Documentation',
+     'tempest Developers', 'olso.configReleaseNotes',
      'An OpenStack library for parsing configuration options from the command'
      ' line and configuration files.',
      'Miscellaneous'),
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index d8702f9..6a1f8b4 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,12 @@
    :maxdepth: 1
 
    unreleased
+   v29.0.0
+   v28.1.0
+   v28.0.0
+   v27.0.0
+   v26.1.0
+   v26.0.0
    v24.0.0
    v23.0.0
    v22.1.0
diff --git a/releasenotes/source/v26.0.0.rst b/releasenotes/source/v26.0.0.rst
new file mode 100644
index 0000000..4161f89
--- /dev/null
+++ b/releasenotes/source/v26.0.0.rst
@@ -0,0 +1,5 @@
+=====================
+v26.0.0 Release Notes
+=====================
+.. release-notes:: 26.0.0 Release Notes
+   :version: 26.0.0
diff --git a/releasenotes/source/v26.1.0.rst b/releasenotes/source/v26.1.0.rst
new file mode 100644
index 0000000..554cf1f
--- /dev/null
+++ b/releasenotes/source/v26.1.0.rst
@@ -0,0 +1,5 @@
+=====================
+v26.1.0 Release Notes
+=====================
+.. release-notes:: 26.1.0 Release Notes
+   :version: 26.1.0
diff --git a/releasenotes/source/v27.0.0.rst b/releasenotes/source/v27.0.0.rst
new file mode 100644
index 0000000..0009124
--- /dev/null
+++ b/releasenotes/source/v27.0.0.rst
@@ -0,0 +1,5 @@
+=====================
+v27.0.0 Release Notes
+=====================
+.. release-notes:: 27.0.0 Release Notes
+   :version: 27.0.0
diff --git a/releasenotes/source/v28.0.0.rst b/releasenotes/source/v28.0.0.rst
new file mode 100644
index 0000000..19d4218
--- /dev/null
+++ b/releasenotes/source/v28.0.0.rst
@@ -0,0 +1,5 @@
+=====================
+v28.0.0 Release Notes
+=====================
+.. release-notes:: 28.0.0 Release Notes
+   :version: 28.0.0
diff --git a/releasenotes/source/v28.1.0.rst b/releasenotes/source/v28.1.0.rst
new file mode 100644
index 0000000..3cc3478
--- /dev/null
+++ b/releasenotes/source/v28.1.0.rst
@@ -0,0 +1,5 @@
+=====================
+v28.1.0 Release Notes
+=====================
+.. release-notes:: 28.1.0 Release Notes
+   :version: 28.1.0
diff --git a/releasenotes/source/v29.0.0.rst b/releasenotes/source/v29.0.0.rst
new file mode 100644
index 0000000..d367a59
--- /dev/null
+++ b/releasenotes/source/v29.0.0.rst
@@ -0,0 +1,5 @@
+=====================
+v29.0.0 Release Notes
+=====================
+.. release-notes:: 29.0.0 Release Notes
+   :version: 29.0.0
diff --git a/requirements.txt b/requirements.txt
index abc0bce..bc8358b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,19 +6,19 @@
 jsonschema>=3.2.0 # MIT
 testtools>=2.2.0 # MIT
 paramiko>=2.7.0 # LGPLv2.1+
+cryptography>=2.1 # BSD/Apache-2.0
 netaddr>=0.7.18 # BSD
 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.33.0 # Apache-2.0
-six>=1.10.0 # MIT
+oslo.utils>=4.7.0 # Apache-2.0
 fixtures>=3.0.0 # 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
+PrettyTable>=0.7.1 # BSD
 urllib3>=1.21.1 # MIT
 debtcollector>=1.2.0 # Apache-2.0
 unittest2>=1.1.0 # BSD
diff --git a/roles/ipv6-only-deployments-verification/README.rst b/roles/ipv6-only-deployments-verification/README.rst
deleted file mode 100644
index 400a8da..0000000
--- a/roles/ipv6-only-deployments-verification/README.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-Verify the IPv6-only deployments
-
-This role needs to be invoked from a playbook that
-run tests. This role verifies the IPv6 setting on
-devstack side and devstack deploy services on IPv6.
-This role is invoked before tests are run so that
-if any missing IPv6 setting or deployments can fail
-the job early.
-
-
-**Role Variables**
-
-.. zuul:rolevar:: devstack_base_dir
-   :default: /opt/stack
-
-   The devstack base directory.
diff --git a/roles/ipv6-only-deployments-verification/defaults/main.yaml b/roles/ipv6-only-deployments-verification/defaults/main.yaml
deleted file mode 100644
index fea05c8..0000000
--- a/roles/ipv6-only-deployments-verification/defaults/main.yaml
+++ /dev/null
@@ -1 +0,0 @@
-devstack_base_dir: /opt/stack
diff --git a/roles/ipv6-only-deployments-verification/tasks/main.yaml b/roles/ipv6-only-deployments-verification/tasks/main.yaml
deleted file mode 100644
index d73c79c..0000000
--- a/roles/ipv6-only-deployments-verification/tasks/main.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-- name: Verify the ipv6-only deployments
-  become: true
-  become_user: stack
-  shell: "{{ devstack_base_dir }}/tempest/tools/verify-ipv6-only-deployments.sh"
diff --git a/roles/run-tempest-26/README.rst b/roles/run-tempest-26/README.rst
new file mode 100644
index 0000000..3643edb
--- /dev/null
+++ b/roles/run-tempest-26/README.rst
@@ -0,0 +1,83 @@
+Run Tempest
+
+The result of the tempest run is stored in the `tempest_run_result`
+variable (through the `register` statement).
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: tempest_concurrency
+   :default: 0
+
+   The number of parallel test processes.
+
+.. 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'.)
+
+   In the following example only api scenario and third party tests
+   will be executed.
+
+       ::
+           vars:
+             tempest_test_regex: (tempest\.(api|scenario|thirdparty)).*$
+
+.. 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'.)
+
+       ::
+           vars:
+             tempest_black_regex: (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
+
+.. zuul:rolevar:: tempest_test_timeout
+   :default: ''
+
+   The timeout (in seconds) for each test.
+
+.. zuul:rolevar:: stable_constraints_file
+   :default: ''
+
+   Upper constraints file to be used for stable branch till stable/rocky.
+
+.. zuul:rolevar:: tempest_tox_environment
+   :default: ''
+
+   Environment variable to set for run-tempst task.
+
+   Env variables set in this variable will be combined with some more
+   defaults env variable set at runtime.
diff --git a/roles/run-tempest-26/defaults/main.yaml b/roles/run-tempest-26/defaults/main.yaml
new file mode 100644
index 0000000..cbac76d
--- /dev/null
+++ b/roles/run-tempest-26/defaults/main.yaml
@@ -0,0 +1,12 @@
+devstack_base_dir: /opt/stack
+tempest_test_regex: ''
+tox_envlist: smoke
+tempest_black_regex: ''
+tox_extra_args: ''
+tempest_test_timeout: ''
+stable_constraints_file: "{{ devstack_base_dir }}/requirements/upper-constraints.txt"
+target_branch: "{{ zuul.branch }}"
+tempest_tox_environment: {}
+# NOTE(gmann): external_bridge_mtu shows as undefined for run-tempest role
+# defining default value here to avoid that error.
+external_bridge_mtu: 0
\ No newline at end of file
diff --git a/roles/run-tempest-26/tasks/main.yaml b/roles/run-tempest-26/tasks/main.yaml
new file mode 100644
index 0000000..f846006
--- /dev/null
+++ b/roles/run-tempest-26/tasks/main.yaml
@@ -0,0 +1,73 @@
+# NOTE(andreaf) The number of vcpus is not available on all systems.
+# See https://github.com/ansible/ansible/issues/30688
+# When not available, we fall back to ansible_processor_cores
+- name: Get hw.logicalcpu from sysctl
+  shell: sysctl hw.logicalcpu | cut -d' ' -f2
+  register: sysctl_hw_logicalcpu
+  when: ansible_processor_vcpus is not defined
+
+- name: Number of cores
+  set_fact:
+    num_cores: "{{ansible_processor_vcpus|default(sysctl_hw_logicalcpu.stdout)}}"
+
+- name: Set concurrency for cores == 3 or less
+  set_fact:
+    default_concurrency: "{{ num_cores }}"
+  when: num_cores|int <= 3
+
+- name: Limit max concurrency when more than 3 vcpus are available
+  set_fact:
+    default_concurrency: "{{ num_cores|int // 2 }}"
+  when: num_cores|int > 3
+
+- name: Override target branch
+  set_fact:
+    target_branch: "{{ zuul.override_checkout }}"
+  when: zuul.override_checkout is defined
+
+- name: Use stable branch upper-constraints till stable/stein
+  set_fact:
+    # TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
+    tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': stable_constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': stable_constraints_file}) }}"
+  when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]
+
+- name: Use Configured upper-constraints for non-master Tempest
+  set_fact:
+    # TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
+    tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS']}) | combine({'TOX_CONSTRAINTS_FILE': devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS']}) }}"
+  when:
+    - devstack_localrc is defined
+    - "'TEMPEST_BRANCH' in devstack_localrc"
+    - "'TEMPEST_VENV_UPPER_CONSTRAINTS' in devstack_localrc"
+    - devstack_localrc['TEMPEST_BRANCH'] != 'master'
+    - devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS'] != 'default'
+
+- name: Set OS_TEST_TIMEOUT if requested
+  set_fact:
+    tempest_tox_environment: "{{ tempest_tox_environment | combine({'OS_TEST_TIMEOUT': tempest_test_timeout}) }}"
+  when: tempest_test_timeout != ''
+
+- 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_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"
+  register: tempest_run_result
+  become: true
+  become_user: tempest
+  environment: "{{ tempest_tox_environment }}"
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 3643edb..0c72b69 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -32,7 +32,11 @@
 
 .. zuul:rolevar:: tempest_test_blacklist
 
-   Specifies a blacklist file to skip tests that are not needed.
+   DEPRECATED option, please use tempest_test_exclude_list instead.
+
+.. zuul:rolevar:: tempest_test_exclude_list
+
+   Specifies an excludelist file to skip tests that are not needed.
 
    Pass a full path to the file.
 
@@ -44,6 +48,11 @@
 .. zuul:rolevar:: tempest_black_regex
    :default: ''
 
+   DEPRECATED option, please use tempest_exclude_regex instead.
+
+.. zuul:rolevar:: tempest_exclude_regex
+   :default: ''
+
    A regular expression used to skip the tests.
 
    It works only when used with some specific tox environments
@@ -51,7 +60,7 @@
 
        ::
            vars:
-             tempest_black_regex: (tempest.api.identity).*$
+             tempest_exclude_regex: (tempest.api.identity).*$
 
 .. zuul:rolevar:: tox_extra_args
    :default: ''
@@ -72,7 +81,7 @@
 .. zuul:rolevar:: stable_constraints_file
    :default: ''
 
-   Upper constraints file to be used for stable branch till stable/rocky.
+   Upper constraints file to be used for stable branch till stable/stein.
 
 .. zuul:rolevar:: tempest_tox_environment
    :default: ''
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index 5867b6c..5f13883 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -1,9 +1,11 @@
 devstack_base_dir: /opt/stack
 tempest_test_regex: ''
 tox_envlist: smoke
-tempest_black_regex: ''
 tox_extra_args: ''
 tempest_test_timeout: ''
 stable_constraints_file: "{{ devstack_base_dir }}/requirements/upper-constraints.txt"
 target_branch: "{{ zuul.branch }}"
 tempest_tox_environment: {}
+# NOTE(gmann): external_bridge_mtu shows as undefined for run-tempest role
+# defining default value here to avoid that error.
+external_bridge_mtu: 0
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 1de3725..a8b3ede 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -25,17 +25,31 @@
     target_branch: "{{ zuul.override_checkout }}"
   when: zuul.override_checkout is defined
 
-- name: Use stable branch upper-constraints till stable/rocky
+- name: Use stable branch upper-constraints till stable/stein
   set_fact:
     # TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
     tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': stable_constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': stable_constraints_file}) }}"
-  when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky"]
+  when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]
+
+- name: Use Configured upper-constraints for non-master Tempest
+  set_fact:
+    # TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
+    tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS']}) | combine({'TOX_CONSTRAINTS_FILE': devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS']}) }}"
+  when:
+    - devstack_localrc is defined
+    - "'TEMPEST_BRANCH' in devstack_localrc"
+    - "'TEMPEST_VENV_UPPER_CONSTRAINTS' in devstack_localrc"
+    - devstack_localrc['TEMPEST_BRANCH'] != 'master'
+    - devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS'] != 'master'
 
 - name: Set OS_TEST_TIMEOUT if requested
   set_fact:
     tempest_tox_environment: "{{ tempest_tox_environment | combine({'OS_TEST_TIMEOUT': tempest_test_timeout}) }}"
   when: tempest_test_timeout != ''
 
+# TODO(kopecmartin) remove the following 'when block' after all consumers of
+# the role have switched to tempest_test_exclude_list option, until then it's
+# kept here for backward compatibility
 - when:
     - tempest_test_blacklist is defined
   block:
@@ -50,10 +64,42 @@
         blacklist_option: "--blacklist-file={{ tempest_test_blacklist|quote }}"
       when: blacklist_stat.stat.exists
 
+- when:
+    - tempest_test_exclude_list is defined
+  block:
+    - name: Check for test exclude list file
+      stat:
+        path: "{{ tempest_test_exclude_list }}"
+      register:
+        exclude_list_stat
+
+    - name: Build exclude list option
+      set_fact:
+        exclude_list_option: "--exclude-list={{ tempest_test_exclude_list|quote }}"
+      when: exclude_list_stat.stat.exists
+
+# TODO(kopecmartin) remove this after all consumers of the role have switched
+# to tempest_exclude_regex option, until then it's kept here for the backward
+# compatibility
+- name: Build exclude regex (old param)
+  set_fact:
+    tempest_test_exclude_regex: "--black-regex={{tempest_black_regex|quote}}"
+  when:
+    - tempest_black_regex is defined
+    - tempest_exclude_regex is not defined
+
+- name: Build exclude regex (new param)
+  set_fact:
+    tempest_test_exclude_regex: "--exclude-regex={{tempest_exclude_regex|quote}}"
+  when:
+    - tempest_black_regex is not defined
+    - tempest_exclude_regex is defined
+
 - name: Run Tempest
-  command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} \
+  command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} \
+           {{blacklist_option|default('')}}  {{exclude_list_option|default('')}} \
             --concurrency={{tempest_concurrency|default(default_concurrency)}} \
-            --black-regex={{tempest_black_regex|quote}}
+           {{tempest_test_exclude_regex|default('')}}
   args:
     chdir: "{{devstack_base_dir}}/tempest"
   register: tempest_run_result
diff --git a/roles/tempest-cleanup/README.rst b/roles/tempest-cleanup/README.rst
index 70719ca..d1fad90 100644
--- a/roles/tempest-cleanup/README.rst
+++ b/roles/tempest-cleanup/README.rst
@@ -31,3 +31,31 @@
    When true, tempest cleanup creates a report (./dry_run.json) of the
    resources that would be cleaned up if the role was ran with dry_run option
    set to false.
+
+.. zuul:rolevar:: run_tempest_fail_if_leaked_resources
+   :default: false
+
+   When true, the role will fail if any leaked resources are detected.
+   The detection is done via dry_run.json file which if contains any resources,
+   some must have been leaked. This can be also used to verify that tempest
+   cleanup was successful.
+
+
+Role usage
+----------
+
+The role can be also used for verification that tempest tests don't leak any
+resources or to test that 'tempest cleanup' command deleted all leaked
+resources as expected.
+Either way the role needs to be run first with init_saved_state variable set
+to true prior any tempest tests got executed.
+Then, after tempest tests got executed this role needs to be run again with
+role variables set according to the desired outcome:
+
+1. to verify that tempest tests don't leak any resources
+   run_tempest_dry_cleanup and run_tempest_fail_if_leaked_resources have to
+   be set to true.
+
+2. to check that 'tempest cleanup' command deleted all the leaked resources
+   run_tempest_cleanup and run_tempest_fail_if_leaked_resources have to be set
+   to true.
diff --git a/roles/tempest-cleanup/defaults/main.yaml b/roles/tempest-cleanup/defaults/main.yaml
index fc1948a..ce78bdb 100644
--- a/roles/tempest-cleanup/defaults/main.yaml
+++ b/roles/tempest-cleanup/defaults/main.yaml
@@ -1,3 +1,4 @@
 devstack_base_dir: /opt/stack
 init_saved_state: false
 dry_run: false
+run_tempest_fail_if_leaked_resources: false
diff --git a/roles/tempest-cleanup/tasks/dry_run.yaml b/roles/tempest-cleanup/tasks/dry_run.yaml
new file mode 100644
index 0000000..46749ab
--- /dev/null
+++ b/roles/tempest-cleanup/tasks/dry_run.yaml
@@ -0,0 +1,7 @@
+---
+- name: Run tempest cleanup dry-run
+  become: yes
+  become_user: tempest
+  command: tox -evenv-tempest -- tempest cleanup --dry-run --debug
+  args:
+    chdir: "{{ devstack_base_dir }}/tempest"
diff --git a/roles/tempest-cleanup/tasks/dry_run_checker.py b/roles/tempest-cleanup/tasks/dry_run_checker.py
new file mode 100644
index 0000000..9cd9a85
--- /dev/null
+++ b/roles/tempest-cleanup/tasks/dry_run_checker.py
@@ -0,0 +1,71 @@
+# Copyright 2020 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.
+
+"""
+Utility for content checking of a given dry_run.json file.
+"""
+
+import argparse
+import json
+import sys
+
+
+def get_parser():
+    parser = argparse.ArgumentParser(__doc__)
+    parser.add_argument('--is-empty', action="store_true", dest='is_empty',
+                        default=False,
+                        help="""Are values of a given dry_run.json empty?""")
+    parser.add_argument('--file', dest='file', default=None, metavar='PATH',
+                        help="A path to a dry_run.json file.")
+    return parser
+
+
+def parse_arguments():
+    parser = get_parser()
+    args = parser.parse_args()
+    if not args.file:
+        sys.stderr.write('Path to a dry_run.json must be specified.\n')
+        sys.exit(1)
+    return args
+
+
+def load_json(path):
+    """Load json content from file addressed by path."""
+    try:
+        with open(path, 'rb') as json_file:
+            json_data = json.load(json_file)
+    except Exception as ex:
+        sys.exit(ex)
+    return json_data
+
+
+def are_values_empty(dry_run_content):
+    """Return true if values of dry_run.json are empty."""
+    for value in dry_run_content.values():
+        if value:
+            return False
+    return True
+
+
+def main():
+    args = parse_arguments()
+    content = load_json(args.file)
+    if args.is_empty:
+        if not are_values_empty(content):
+            sys.exit(1)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/roles/tempest-cleanup/tasks/main.yaml b/roles/tempest-cleanup/tasks/main.yaml
index 5444afc..c1d63f0 100644
--- a/roles/tempest-cleanup/tasks/main.yaml
+++ b/roles/tempest-cleanup/tasks/main.yaml
@@ -12,20 +12,35 @@
 
 - when: dry_run
   block:
-    - name: Run tempest cleanup dry-run
-      become: yes
-      become_user: tempest
-      command: tox -evenv-tempest -- tempest cleanup --dry-run --debug
-      args:
-        chdir: "{{ devstack_base_dir }}/tempest"
+    - import_tasks: dry_run.yaml
 
     - name: Cat dry_run.json
       command: cat "{{ devstack_base_dir }}/tempest/dry_run.json"
 
-- name: Run tempest cleanup
-  become: yes
-  become_user: tempest
-  command: tox -evenv-tempest -- tempest cleanup --debug
-  args:
-    chdir: "{{ devstack_base_dir }}/tempest"
-  when: not dry_run and not init_saved_state
+- when:
+    - not dry_run
+    - not init_saved_state
+  block:
+    - name: Run tempest cleanup
+      become: yes
+      become_user: tempest
+      command: tox -evenv-tempest -- tempest cleanup --debug
+      args:
+        chdir: "{{ devstack_base_dir }}/tempest"
+
+- when:
+    - run_tempest_fail_if_leaked_resources
+    - not init_saved_state
+  block:
+    # let's run dry run again, if haven't already, to check no leftover
+    # resources were left behind after the cleanup in the previous task
+    - import_tasks: dry_run.yaml
+      when: not dry_run
+
+    - name: Fail if any resources are leaked
+      become: yes
+      become_user: tempest
+      shell: |
+        python3 roles/tempest-cleanup/tasks/dry_run_checker.py --file {{ devstack_base_dir }}/tempest/dry_run.json --is-empty
+      args:
+        chdir: "{{ devstack_base_dir }}/tempest"
diff --git a/setup.cfg b/setup.cfg
index 18427a2..a41eccf 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,12 +1,12 @@
 [metadata]
 name = tempest
 summary = OpenStack Integration Testing
-description-file =
+description_file =
     README.rst
 author = OpenStack
-author-email = openstack-discuss@lists.openstack.org
-home-page = https://docs.openstack.org/tempest/latest/
-python-requires = >=3.6
+author_email = openstack-discuss@lists.openstack.org
+home_page = https://docs.openstack.org/tempest/latest/
+python_requires = >=3.6
 classifier =
     Intended Audience :: Information Technology
     Intended Audience :: System Administrators
@@ -18,6 +18,7 @@
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3 :: Only
     Programming Language :: Python :: Implementation :: CPython
 
@@ -48,4 +49,3 @@
     run = tempest.cmd.run:TempestRun
 oslo.config.opts =
     tempest.config = tempest.config:list_opts
-
diff --git a/tempest/api/compute/admin/test_create_server.py b/tempest/api/compute/admin/test_create_server.py
index c4a8bf5..ccdfbf3 100644
--- a/tempest/api/compute/admin/test_create_server.py
+++ b/tempest/api/compute/admin/test_create_server.py
@@ -42,6 +42,8 @@
     @decorators.idempotent_id('b3c7bcfc-bb5b-4e22-b517-c7f686b802ca')
     @testtools.skipUnless(CONF.validation.run_validation,
                           'Instance validation tests are disabled.')
+    @testtools.skipIf("aarch64" in CONF.scenario.img_file,
+                      "Aarch64 does not support ephemeral disk test")
     def test_verify_created_server_ephemeral_disk(self):
         """Verify that the ephemeral disk is created when creating server"""
         flavor_base = self.flavors_client.show_flavor(
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 4c531b3..10018fe 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -127,3 +127,34 @@
             self.flavor['id'], 'hw:numa_nodes')
         self.assertEqual(body['hw:numa_nodes'], '1')
         self.assertNotIn('hw:cpu_policy', body)
+
+
+class FlavorMetadataValidation(base.BaseV2ComputeAdminTest):
+
+    min_microversion = '2.86'
+
+    @classmethod
+    def resource_setup(cls):
+        super(FlavorMetadataValidation, cls).resource_setup()
+        cls.flavor_name_prefix = 'test_flavor_validate_metadata_'
+        cls.ram = 512
+        cls.vcpus = 1
+        cls.disk = 10
+        cls.ephemeral = 10
+        cls.swap = 1024
+        cls.rxtx = 2
+
+    @decorators.idempotent_id('d3114f03-b0f2-4dc7-be11-70c0abc178b3')
+    def test_flavor_update_with_custom_namespace(self):
+        """Test flavor creation with a custom namespace, key and value"""
+        flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+        flavor_id = self.create_flavor(ram=self.ram,
+                                       vcpus=self.vcpus,
+                                       disk=self.disk,
+                                       name=flavor_name)['id']
+        specs = {'hw:cpu_policy': 'shared', 'foo:bar': 'baz'}
+        body = self.admin_flavors_client.set_flavor_extra_spec(
+            flavor_id,
+            **specs)['extra_specs']
+        self.assertEqual(body['foo:bar'], 'baz')
+        self.assertEqual(body['hw:cpu_policy'], 'shared')
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 347193d..c7a1201 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -145,3 +145,26 @@
         hypers = self.client.search_hypervisor(
             hypers[0]['hypervisor_hostname'])['hypervisors']
         self.assertNotEmpty(hypers, "No hypervisors found.")
+
+
+class HypervisorAdminV253TestBase(base.BaseV2ComputeAdminTest):
+    """Tests Hypervisors API above 2.53 that require admin privileges"""
+
+    min_microversion = '2.53'
+
+    @classmethod
+    def setup_clients(cls):
+        super(HypervisorAdminV253TestBase, cls).setup_clients()
+        cls.client = cls.os_admin.hypervisor_client
+
+    @decorators.idempotent_id('4ab54a14-77a2-4e39-b9d2-1306d157c705')
+    def test_list_show_detail_hypervisors(self):
+        """Verify the list, list details, and show hypevisors
+
+        This verify the Hypervisor API response schema with v2.53 microversion
+        """
+        self.client.list_hypervisors(
+            detail=True, with_servers=True)['hypervisors']
+        hypers = self.client.list_hypervisors(with_servers=True)['hypervisors']
+        self.client.show_hypervisor(
+            hypers[0]['id'], with_servers=True)['hypervisor']
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 4dcbb3b..c4c0542 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log.py
@@ -14,8 +14,7 @@
 #    under the License.
 
 import datetime
-
-from six.moves.urllib import parse as urllib
+from urllib import parse as urllib
 
 from tempest.api.compute import base
 from tempest.lib import decorators
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
index 84d18c4..c115451 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
@@ -14,8 +14,7 @@
 #    under the License.
 
 import datetime
-
-from six.moves.urllib import parse as urllib
+from urllib import parse as urllib
 
 from tempest.api.compute import base
 from tempest.lib import decorators
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 941315e..c91b557 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -23,6 +23,8 @@
 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.common.utils import test_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
@@ -55,6 +57,10 @@
     def setup_clients(cls):
         super(LiveMigrationTestBase, cls).setup_clients()
         cls.admin_migration_client = cls.os_admin.migrations_client
+        cls.networks_client = cls.os_primary.networks_client
+        cls.subnets_client = cls.os_primary.subnets_client
+        cls.ports_client = cls.os_primary.ports_client
+        cls.trunks_client = cls.os_primary.trunks_client
 
     def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
         kwargs = dict()
@@ -169,8 +175,13 @@
                       block_migrate_cinder_iscsi,
                       'Block Live migration not configured for iSCSI')
     @utils.services('volume')
-    def test_iscsi_volume(self):
-        """Test live migrating a server with volume attached"""
+    def test_live_block_migration_with_attached_volume(self):
+        """Test the live-migration of an instance with an attached volume.
+
+        This tests the live-migration of an instance with both a local disk and
+        attach volume. This differs from test_volume_backed_live_migration
+        above that tests live-migration with only an attached volume.
+        """
         server = self.create_test_server(wait_until="ACTIVE")
         server_id = server['id']
         if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
@@ -192,6 +203,90 @@
 
         self.assertEqual(volume_id1, volume_id2)
 
+    def _create_net_subnet(self, name, cidr):
+        net_name = data_utils.rand_name(name=name)
+        net = self.networks_client.create_network(name=net_name)['network']
+        self.addClassResourceCleanup(
+            self.networks_client.delete_network, net['id'])
+
+        subnet = self.subnets_client.create_subnet(
+            network_id=net['id'],
+            cidr=cidr,
+            ip_version=4)
+        self.addClassResourceCleanup(self.subnets_client.delete_subnet,
+                                     subnet['subnet']['id'])
+        return net
+
+    def _create_port(self, network_id, name):
+        name = data_utils.rand_name(name=name)
+        port = self.ports_client.create_port(name=name,
+                                             network_id=network_id)['port']
+        self.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                     self.ports_client.delete_port,
+                                     port_id=port['id'])
+        return port
+
+    def _create_trunk_with_subport(self):
+        tenant_network = self.get_tenant_network()
+        parent = self._create_port(network_id=tenant_network['id'],
+                                   name='parent')
+        net = self._create_net_subnet(name='subport_net', cidr='19.80.0.0/24')
+        subport = self._create_port(network_id=net['id'], name='subport')
+
+        trunk = self.trunks_client.create_trunk(
+            name=data_utils.rand_name('trunk'),
+            port_id=parent['id'],
+            sub_ports=[{"segmentation_id": 42, "port_id": subport['id'],
+                        "segmentation_type": "vlan"}]
+        )['trunk']
+        self.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                     self.trunks_client.delete_trunk,
+                                     trunk['id'])
+        return trunk, parent, subport
+
+    def _is_port_status_active(self, port_id):
+        port = self.ports_client.show_port(port_id)['port']
+        return port['status'] == 'ACTIVE'
+
+    @decorators.idempotent_id('0022c12e-a482-42b0-be2d-396b5f0cffe3')
+    @utils.requires_ext(service='network', extension='trunk')
+    @utils.services('network')
+    def test_live_migration_with_trunk(self):
+        """Test live migration with trunk and subport"""
+        trunk, parent, subport = self._create_trunk_with_subport()
+
+        server = self.create_test_server(
+            wait_until="ACTIVE", networks=[{'port': parent['id']}])
+
+        # Wait till subport status is ACTIVE
+        self.assertTrue(
+            test_utils.call_until_true(
+                self._is_port_status_active, CONF.validation.connect_timeout,
+                5, subport['id']))
+        self.assertTrue(
+            test_utils.call_until_true(
+                self._is_port_status_active, CONF.validation.connect_timeout,
+                5, parent['id']))
+        subport = self.ports_client.show_port(subport['id'])['port']
+
+        if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
+            # not to specify a host so that the scheduler will pick one
+            target_host = None
+        else:
+            target_host = self.get_host_other_than(server['id'])
+
+        self._live_migrate(server['id'], target_host, 'ACTIVE')
+
+        # Wait till subport status is ACTIVE
+        self.assertTrue(
+            test_utils.call_until_true(
+                self._is_port_status_active, CONF.validation.connect_timeout,
+                5, subport['id']))
+        self.assertTrue(
+            test_utils.call_until_true(
+                self._is_port_status_active, CONF.validation.connect_timeout,
+                5, parent['id']))
+
 
 class LiveMigrationRemoteConsolesV26Test(LiveMigrationTestBase):
     min_microversion = '2.6'
diff --git a/tempest/api/compute/admin/test_volume.py b/tempest/api/compute/admin/test_volume.py
new file mode 100644
index 0000000..549d4fb
--- /dev/null
+++ b/tempest/api/compute/admin/test_volume.py
@@ -0,0 +1,116 @@
+# Copyright 2020 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.
+
+import io
+
+from tempest.api.compute import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class BaseAttachSCSIVolumeTest(base.BaseV2ComputeAdminTest):
+    """Base class for the admin volume tests in this module."""
+    create_default_network = True
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaseAttachSCSIVolumeTest, 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)
+
+    @classmethod
+    def setup_credentials(cls):
+        cls.prepare_instance_network()
+        super(BaseAttachSCSIVolumeTest, cls).setup_credentials()
+
+    def _create_image_with_custom_property(self, **kwargs):
+        """Wrapper utility that returns the custom image.
+
+        Creates a new image by downloading the default image's bits and
+        uploading them to a new image. Any kwargs are set as image properties
+        on the new image.
+
+        :param return image_id: The UUID of the newly created image.
+        """
+        image = self.admin_image_client.show_image(CONF.compute.image_ref)
+        image_data = self.admin_image_client.show_image_file(
+            CONF.compute.image_ref).data
+        image_file = io.BytesIO(image_data)
+        create_dict = {
+            'container_format': image['container_format'],
+            'disk_format': image['disk_format'],
+            'min_disk': image['min_disk'],
+            'min_ram': image['min_ram'],
+            'visibility': 'public',
+        }
+        create_dict.update(kwargs)
+        new_image = self.admin_image_client.create_image(**create_dict)
+        self.addCleanup(self.admin_image_client.wait_for_resource_deletion,
+                        new_image['id'])
+        self.addCleanup(self.admin_image_client.delete_image, new_image['id'])
+        self.admin_image_client.store_image_file(new_image['id'], image_file)
+
+        return new_image['id']
+
+
+class AttachSCSIVolumeTestJSON(BaseAttachSCSIVolumeTest):
+    """Test attaching scsi volume to server"""
+
+    @decorators.idempotent_id('777e468f-17ca-4da4-b93d-b7dbf56c0494')
+    def test_attach_scsi_disk_with_config_drive(self):
+        """Test the attach/detach volume with config drive/scsi disk
+
+        Enable the config drive, followed by booting an instance
+        from an image with meta properties hw_cdrom: scsi and use
+        virtio-scsi mode with further asserting list volume attachments
+        in instance after attach and detach of the volume.
+        """
+        custom_img = self._create_image_with_custom_property(
+            hw_scsi_model='virtio-scsi',
+            hw_disk_bus='scsi',
+            hw_cdrom_bus='scsi')
+        server = self.create_test_server(image_id=custom_img,
+                                         config_drive=True,
+                                         wait_until='ACTIVE')
+
+        # NOTE(lyarwood): self.create_test_server delete the server
+        # at class level cleanup so add server cleanup to ensure that
+        # the instance is deleted first before created image. This
+        # avoids failures when using the rbd backend is used for both
+        # Glance and Nova ephemeral storage. Also wait until server is
+        # deleted otherwise image deletion can start before server is
+        # deleted.
+        self.addCleanup(waiters.wait_for_server_termination,
+                        self.servers_client, server['id'])
+        self.addCleanup(self.servers_client.delete_server, server['id'])
+
+        volume = self.create_volume()
+        attachment = self.attach_volume(server, volume)
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, attachment['volumeId'], 'in-use')
+        volume_after_attach = self.servers_client.list_volume_attachments(
+            server['id'])['volumeAttachments']
+        self.assertEqual(1, len(volume_after_attach),
+                         "Failed to attach volume")
+        self.servers_client.detach_volume(
+            server['id'], attachment['volumeId'])
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, attachment['volumeId'], 'available')
+        waiters.wait_for_volume_attachment_remove_from_server(
+            self.servers_client, server['id'], attachment['volumeId'])
diff --git a/tempest/api/compute/api_microversion_fixture.py b/tempest/api/compute/api_microversion_fixture.py
index 695af52..1f55a65 100644
--- a/tempest/api/compute/api_microversion_fixture.py
+++ b/tempest/api/compute/api_microversion_fixture.py
@@ -13,14 +13,23 @@
 # under the License.
 
 import fixtures
+from oslo_log import log as logging
 
 from tempest.lib.services.compute import base_compute_client
 
+LOG = logging.getLogger(__name__)
+
 
 class APIMicroversionFixture(fixtures.Fixture):
 
     def __init__(self, compute_microversion):
         self.compute_microversion = compute_microversion
+        new_fixture = (
+            'tempest.lib.common.api_microversion_fixture.'
+            'APIMicroversionFixture')
+        LOG.warning("%s class is deprecated and moved to %s. It"
+                    " will be removed in Z cycle.",
+                    self.__class__.__name__, new_fixture)
 
     def _setUp(self):
         super(APIMicroversionFixture, self)._setUp()
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 8b847fc..ed50282 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -21,7 +21,6 @@
 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
@@ -164,6 +163,11 @@
             api_version_utils.select_request_microversion(
                 cls.placement_min_microversion,
                 CONF.placement.min_microversion))
+        cls.setup_api_microversion_fixture(
+            compute_microversion=cls.request_microversion,
+            volume_microversion=cls.volume_request_microversion,
+            placement_microversion=cls.placement_request_microversion)
+
         cls.build_interval = CONF.compute.build_interval
         cls.build_timeout = CONF.compute.build_timeout
         cls.image_ref = CONF.compute.image_ref
@@ -171,8 +175,11 @@
         cls.flavor_ref = CONF.compute.flavor_ref
         cls.flavor_ref_alt = CONF.compute.flavor_ref_alt
         cls.ssh_user = CONF.validation.image_ssh_user
+        cls.ssh_alt_user = CONF.validation.image_alt_ssh_user
         cls.image_ssh_user = CONF.validation.image_ssh_user
+        cls.image_alt_ssh_user = CONF.validation.image_alt_ssh_user
         cls.image_ssh_password = CONF.validation.image_ssh_password
+        cls.image_alt_ssh_password = CONF.validation.image_alt_ssh_password
 
     @classmethod
     def is_requested_microversion_compatible(cls, max_version):
@@ -451,6 +458,12 @@
         server = self.servers_client.show_server(server_id)['server']
         self.assert_flavor_equal(new_flavor_id, server['flavor'])
 
+    def reboot_server(self, server_id, type):
+        """Reboot a server and wait for it to be ACTIVE."""
+        self.servers_client.reboot_server(server_id, type=type)
+        waiters.wait_for_server_status(
+            self.servers_client, server_id, 'ACTIVE')
+
     @classmethod
     def delete_volume(cls, volume_id):
         """Deletes the given volume and waits for it to be gone."""
@@ -490,13 +503,6 @@
         else:
             raise lib_exc.InvalidConfiguration()
 
-    def setUp(self):
-        super(BaseV2ComputeTest, self).setUp()
-        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
-            compute_microversion=self.request_microversion,
-            volume_microversion=self.volume_request_microversion,
-            placement_microversion=self.placement_request_microversion))
-
     @classmethod
     def create_volume(cls, image_ref=None, **kwargs):
         """Create a volume and wait for it to become 'available'.
@@ -512,6 +518,12 @@
             kwargs['display_name'] = vol_name
         if image_ref is not None:
             kwargs['imageRef'] = image_ref
+        if CONF.volume.volume_type and 'volume_type' not in kwargs:
+            # If volume_type is not provided in config then no need to
+            # add a volume type and
+            # if volume_type has already been added by child class then
+            # no need to override.
+            kwargs['volume_type'] = CONF.volume.volume_type
         if CONF.compute.compute_volume_common_az:
             kwargs.setdefault('availability_zone',
                               CONF.compute.compute_volume_common_az)
@@ -561,24 +573,33 @@
 
         attachment = self.servers_client.attach_volume(
             server['id'], **attach_kwargs)['volumeAttachment']
-        # On teardown detach the volume and for multiattach volumes wait for
-        # the attachment to be removed. For non-multiattach volumes wait for
-        # the state of the volume to change to available. This is so we don't
-        # error out when trying to delete the volume during teardown.
-        if volume['multiattach']:
-            att = waiters.wait_for_volume_attachment_create(
-                self.volumes_client, volume['id'], server['id'])
-            self.addCleanup(waiters.wait_for_volume_attachment_remove,
-                            self.volumes_client, volume['id'],
-                            att['attachment_id'])
-        else:
-            self.addCleanup(waiters.wait_for_volume_resource_status,
-                            self.volumes_client, volume['id'], 'available')
-            waiters.wait_for_volume_resource_status(self.volumes_client,
-                                                    volume['id'], 'in-use')
-        # Ignore 404s on detach in case the server is deleted or the volume
-        # is already detached.
+
+        # NOTE(lyarwood): During attach we initially wait for the volume
+        # attachment and then check the volume state.
+        waiters.wait_for_volume_attachment_create(
+            self.volumes_client, volume['id'], server['id'])
+        # TODO(lyarwood): Remove the following volume status checks and move to
+        # attachment status checks across all volumes now with the 3.27
+        # microversion somehow.
+        if not volume['multiattach']:
+            waiters.wait_for_volume_resource_status(
+                self.volumes_client, volume['id'], 'in-use')
+
+        # NOTE(lyarwood): On teardown (LIFO) initially wait for the volume
+        # attachment in Nova to be removed. While this technically happens last
+        # we want this to be the first waiter as if it fails we can then dump
+        # the contents of the console log. The final check of the volume state
+        # should be a no-op by this point and is just added for completeness
+        # when detaching non-multiattach volumes.
+        if not volume['multiattach']:
+            self.addCleanup(
+                waiters.wait_for_volume_resource_status, self.volumes_client,
+                volume['id'], 'available')
+        self.addCleanup(
+            waiters.wait_for_volume_attachment_remove_from_server,
+            self.servers_client, server['id'], volume['id'])
         self.addCleanup(self._detach_volume, server, volume)
+
         return attachment
 
     def create_volume_snapshot(self, volume_id, name=None, description=None,
@@ -634,6 +655,9 @@
             cls.os_admin.availability_zone_client)
         cls.admin_flavors_client = cls.os_admin.flavors_client
         cls.admin_servers_client = cls.os_admin.servers_client
+        cls.admin_image_client = cls.os_admin.image_client_v2
+        cls.admin_assisted_volume_snapshots_client = \
+            cls.os_admin.assisted_volume_snapshots_client
 
     def create_flavor(self, ram, vcpus, disk, name=None,
                       is_public='True', **kwargs):
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index 235049a..5d6a7d7 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -13,10 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import io
 import random
 
-import six
-
 from tempest.api.compute import base
 from tempest.common import image as common_image
 from tempest.common import utils
@@ -44,7 +43,7 @@
             CONF.compute.flavor_ref)['flavor']
         min_img_ram = flavor['ram'] + 1
         size = random.randint(1024, 4096)
-        image_file = six.BytesIO(data_utils.random_bytes(size))
+        image_file = io.BytesIO(data_utils.random_bytes(size))
         params = {
             'name': data_utils.rand_name('image'),
             'container_format': CONF.image.container_formats[0],
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 561265f..ece983d 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
+import io
 
 from tempest.api.compute import base
 from tempest.common import image as common_image
@@ -77,7 +77,7 @@
         cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
                                     cls.glance_client.delete_image,
                                     cls.image_id)
-        image_file = six.BytesIO((b'*' * 1024))
+        image_file = io.BytesIO((b'*' * 1024))
         if CONF.image_feature_enabled.api_v1:
             cls.glance_client.update_image(cls.image_id, data=image_file)
         else:
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 0296220..275a26f 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -110,20 +110,30 @@
         Creating another server image when first image is being saved is
         not allowed.
         """
-        # Create first snapshot
-        image = self.create_image_from_server(self.server_id)
-        self.addCleanup(self._reset_server)
+        try:
+            # Create first snapshot
+            image = self.create_image_from_server(self.server_id)
+            self.addCleanup(self._reset_server)
 
-        # Create second snapshot
-        self.assertRaises(lib_exc.Conflict, self.create_image_from_server,
-                          self.server_id)
+            # Create second snapshot
+            self.assertRaises(lib_exc.Conflict, self.create_image_from_server,
+                              self.server_id)
 
-        if api_version_utils.compare_version_header_to_response(
-            "OpenStack-API-Version", "compute 2.45", image.response, "lt"):
-            image_id = image['image_id']
-        else:
-            image_id = data_utils.parse_image_id(image.response['location'])
-        self.client.delete_image(image_id)
+            if api_version_utils.compare_version_header_to_response(
+                "OpenStack-API-Version", "compute 2.45", image.response, "lt"):
+                image_id = image['image_id']
+            else:
+                image_id = data_utils.parse_image_id(
+                    image.response['location'])
+            self.client.delete_image(image_id)
+
+        except lib_exc.TimeoutException as ex:
+            # Test cannot capture the image saving state.
+            # If timeout is reached, we don't need to check state,
+            # since, it wouldn't be a 'SAVING' state atleast and apart from
+            # it, this testcase doesn't have scope for other state transition
+            # Hence, skip the test.
+            raise self.skipException("This test is skipped because " + str(ex))
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('084f0cbc-500a-4963-8a4e-312905862581')
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 7930c67..15b8a00 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -15,7 +15,7 @@
 
 import time
 
-import six
+import io
 import testtools
 
 from tempest.api.compute import base
@@ -85,7 +85,7 @@
             # Wait 1 second between creation and upload to ensure a delta
             # between created_at and updated_at.
             time.sleep(1)
-            image_file = six.BytesIO((b'*' * 1024))
+            image_file = io.BytesIO((b'*' * 1024))
             if CONF.image_feature_enabled.api_v1:
                 cls.glance_client.update_image(image_id, data=image_file)
             else:
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index 59848f6..3c4daf6 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -35,16 +35,16 @@
         cls.from_port = 22
         cls.to_port = 22
 
-    def setUp(cls):
-        super(SecurityGroupRulesTestJSON, cls).setUp()
+    def setUp(self):
+        super(SecurityGroupRulesTestJSON, self).setUp()
 
-        from_port = cls.from_port
-        to_port = cls.to_port
+        from_port = self.from_port
+        to_port = self.to_port
         group = {}
         ip_range = {}
-        cls.expected = {
+        self.expected = {
             'parent_group_id': None,
-            'ip_protocol': cls.ip_protocol,
+            'ip_protocol': self.ip_protocol,
             'from_port': from_port,
             'to_port': to_port,
             'ip_range': ip_range,
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 671a779..a1f3514 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -109,9 +109,7 @@
                           sg['id'])
 
         # Reboot and add the other security group
-        self.servers_client.reboot_server(server_id, type='HARD')
-        waiters.wait_for_server_status(self.servers_client, server_id,
-                                       'ACTIVE')
+        self.reboot_server(server_id, type='HARD')
         self.servers_client.add_security_group(server_id, name=sg2['name'])
 
         # Check that we are not able to delete the other security
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 0601bbe..efecd6c 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -16,7 +16,6 @@
 import time
 
 from oslo_log import log
-import six
 
 from tempest.api.compute import base
 from tempest.common import compute
@@ -69,7 +68,8 @@
             self.image_ssh_password,
             validation_resources['keypair']['private_key'],
             server=server,
-            servers_client=self.servers_client)
+            servers_client=self.servers_client,
+            ssh_key_type=CONF.validation.ssh_key_type)
         linux_client.validate_authentication()
 
     def _create_server_get_interfaces(self):
@@ -241,7 +241,7 @@
         except lib_exc.BadRequest as e:
             msg = ('Multiple possible networks found, use a Network ID to be '
                    'more specific.')
-            if not CONF.compute.fixed_network_name and six.text_type(e) == msg:
+            if not CONF.compute.fixed_network_name and str(e) == msg:
                 raise
         else:
             ifs.append(iface)
@@ -427,3 +427,33 @@
                 CONF.compute.build_interval, original_ip_count):
             raise lib_exc.TimeoutException(
                 'Timed out while waiting for IP count to decrease.')
+
+
+class AttachInterfacesV270Test(AttachInterfacesTestBase):
+    """Test interface API with microversion greater than 2.69"""
+    min_microversion = '2.70'
+
+    @decorators.idempotent_id('2853f095-8277-4067-92bd-9f10bd4f8e0c')
+    @utils.services('network')
+    def test_create_get_list_interfaces(self):
+        """Test interface API with microversion greater than 2.69
+
+        Checking create, get, list interface APIs response schema.
+        """
+        server = self.create_test_server(wait_until='ACTIVE')
+        try:
+            iface = self.interfaces_client.create_interface(server['id'])[
+                'interfaceAttachment']
+            iface = waiters.wait_for_interface_status(
+                self.interfaces_client, server['id'], iface['port_id'],
+                'ACTIVE')
+        except lib_exc.BadRequest as e:
+            msg = ('Multiple possible networks found, use a Network ID to be '
+                   'more specific.')
+            if not CONF.compute.fixed_network_name and str(e) == msg:
+                raise
+        else:
+            # just to check the response schema
+            self.interfaces_client.show_interface(
+                server['id'], iface['port_id'])
+            self.interfaces_client.list_interfaces(server['id'])
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 48f32a8..c9aec62 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -180,3 +180,56 @@
         if not utils.get_service_list()['volume']:
             msg = "Volume service not enabled."
             raise cls.skipException(msg)
+
+
+class ServersTestFqdnHostnames(base.BaseV2ComputeTest):
+    """Test creating server with FQDN hostname and verifying atrributes
+
+    Starting Wallaby release, Nova sanitizes freeform characters in
+    server hostname with dashes. This test verifies the same.
+    """
+
+    @classmethod
+    def setup_credentials(cls):
+        cls.prepare_instance_network()
+        super(ServersTestFqdnHostnames, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        super(ServersTestFqdnHostnames, cls).setup_clients()
+        cls.client = cls.servers_client
+
+    @decorators.idempotent_id('622066d2-39fc-4c09-9eeb-35903c114a0a')
+    @testtools.skipUnless(
+        CONF.compute_feature_enabled.hostname_fqdn_sanitization,
+        'FQDN hostname sanitization is not supported.')
+    @testtools.skipUnless(CONF.validation.run_validation,
+                          'Instance validation tests are disabled.')
+    def test_create_server_with_fqdn_name(self):
+        """Test to create an instance with FQDN type name scheme"""
+        validation_resources = self.get_class_validation_resources(
+            self.os_primary)
+        self.server_name = 'guest-instance-1.domain.com'
+        self.password = data_utils.rand_password()
+        self.accessIPv4 = '2.2.2.2'
+        test_server = self.create_test_server(
+            validatable=True,
+            validation_resources=validation_resources,
+            wait_until='ACTIVE',
+            adminPass=self.password,
+            name=self.server_name,
+            accessIPv4=self.accessIPv4)
+
+        """Verify the hostname within the instance is sanitized
+
+        Freeform characters in the hostname are replaced with dashes
+        """
+        linux_client = remote_client.RemoteClient(
+            self.get_server_ip(test_server, validation_resources),
+            self.ssh_user,
+            self.password,
+            validation_resources['keypair']['private_key'],
+            server=test_server,
+            servers_client=self.client)
+        hostname = linux_client.exec_command("hostname").rstrip()
+        self.assertEqual('guest-instance-1-domain-com', hostname)
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index a7e2187..56456f4 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -35,6 +35,8 @@
 
 class DeviceTaggingBase(base.BaseV2ComputeTest):
 
+    credentials = ['primary', 'admin']
+
     @classmethod
     def skip_checks(cls):
         super(DeviceTaggingBase, cls).skip_checks()
@@ -54,6 +56,7 @@
         cls.ports_client = cls.os_primary.ports_client
         cls.subnets_client = cls.os_primary.subnets_client
         cls.interfaces_client = cls.os_primary.interfaces_client
+        cls.servers_admin_client = cls.os_admin.servers_client
 
     @classmethod
     def setup_credentials(cls):
@@ -338,7 +341,9 @@
         found_devices = [d['tags'][0] for d in md_dict['devices']
                          if d.get('tags')]
         try:
-            self.assertItemsEqual(found_devices, ['nic-tag', 'volume-tag'])
+            self.assertEqual(
+                sorted(found_devices),
+                sorted(['nic-tag', 'volume-tag']))
             return True
         except Exception:
             return False
@@ -420,11 +425,13 @@
         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,
+        req_id = self.interfaces_client.delete_interface(
+            server['id'], interface['port_id']
+        ).response['x-openstack-request-id']
+        waiters.wait_for_interface_detach(self.servers_admin_client,
                                           server['id'],
-                                          interface['port_id'])
+                                          interface['port_id'],
+                                          req_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.
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index 5ab592a..028da68 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -37,9 +37,7 @@
     @decorators.idempotent_id('77ca5cc5-9990-45e0-ab98-1de8fead201a')
     def test_list_instance_actions(self):
         """Test listing actions of the provided server"""
-        self.client.reboot_server(self.server['id'], type='HARD')
-        waiters.wait_for_server_status(self.client,
-                                       self.server['id'], 'ACTIVE')
+        self.reboot_server(self.server['id'], type='HARD')
 
         body = (self.client.list_instance_actions(self.server['id'])
                 ['instanceActions'])
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index 7931ca9..1308b19 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -14,9 +14,7 @@
 #    under the License.
 
 import struct
-
-import six
-import six.moves.urllib.parse as urlparse
+import urllib.parse as urlparse
 import urllib3
 
 from tempest.api.compute import base
@@ -26,11 +24,6 @@
 
 CONF = config.CONF
 
-if six.PY2:
-    ord_func = ord
-else:
-    ord_func = int
-
 
 class NoVNCConsoleTestJSON(base.BaseV2ComputeTest):
     """Test novnc console"""
@@ -74,7 +67,7 @@
         resp = urllib3.PoolManager().request('GET', vnc_url)
         # Make sure that the GET request was accepted by the novncproxy
         self.assertEqual(resp.status, 200, 'Got a Bad HTTP Response on the '
-                         'initial call: ' + six.text_type(resp.status))
+                         'initial call: ' + str(resp.status))
         # Do some basic validation to make sure it is an expected HTML document
         resp_data = resp.data.decode()
         # This is needed in the case of example: <html lang="en">
@@ -116,18 +109,18 @@
             # single word(4 bytes).
             self.assertEqual(
                 data_length, 4, 'Expected authentication type None.')
-            self.assertIn(1, [ord_func(data[i]) for i in (0, 3)],
+            self.assertIn(1, [int(data[i]) for i in (0, 3)],
                           'Expected authentication type None.')
         else:
             self.assertGreaterEqual(
                 len(data), 2, 'Expected authentication type None.')
             self.assertIn(
                 1,
-                [ord_func(data[i + 1]) for i in range(ord_func(data[0]))],
+                [int(data[i + 1]) for i in range(int(data[0]))],
                 'Expected authentication type None.')
             # Send to the server that we only support authentication
             # type None
-            self._websocket.send_frame(six.int2byte(1))
+            self._websocket.send_frame(bytes((1,)))
 
             # The server should send 4 bytes of 0's if security
             # handshake succeeded
@@ -136,11 +129,11 @@
                 len(data), 4,
                 'Server did not think security was successful.')
             self.assertEqual(
-                [ord_func(i) for i in data], [0, 0, 0, 0],
+                [int(i) for i in data], [0, 0, 0, 0],
                 'Server did not think security was successful.')
 
         # Say to leave the desktop as shared as part of client initialization
-        self._websocket.send_frame(six.int2byte(1))
+        self._websocket.send_frame(bytes((1,)))
         # Get the server initialization packet back and make sure it is the
         # right structure where bytes 20-24 is the name length and
         # 24-N is the name
@@ -170,11 +163,11 @@
             self._websocket.response.startswith(b'HTTP/1.1 101 Switching '
                                                 b'Protocols'),
             'Incorrect HTTP return status code: {}'.format(
-                six.text_type(self._websocket.response)
+                str(self._websocket.response)
             )
         )
         _required_header = 'upgrade: websocket'
-        _response = six.text_type(self._websocket.response).lower()
+        _response = str(self._websocket.response).lower()
         self.assertIn(
             _required_header,
             _response,
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 343d3ee..152e7e8 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urlparse
+
 from oslo_log import log as logging
-from six.moves.urllib import parse as urlparse
 import testtools
 
 from tempest.api.compute import base
@@ -135,8 +136,7 @@
             # in a server
             linux_client.exec_command("sync")
 
-        self.client.reboot_server(self.server_id, type=reboot_type)
-        waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+        self.reboot_server(self.server_id, type=reboot_type)
 
         if CONF.validation.run_validation:
             # Log in and verify the boot time has changed
@@ -228,7 +228,7 @@
             # 4.Plain username/password auth, if a password was given.
             linux_client = remote_client.RemoteClient(
                 self.get_server_ip(rebuilt_server, validation_resources),
-                self.ssh_user,
+                self.ssh_alt_user,
                 password,
                 validation_resources['keypair']['private_key'],
                 server=rebuilt_server,
@@ -310,7 +310,7 @@
                 self.os_primary)
             linux_client = remote_client.RemoteClient(
                 self.get_server_ip(server, validation_resources),
-                self.ssh_user,
+                self.ssh_alt_user,
                 password=None,
                 pkey=validation_resources['keypair']['private_key'],
                 server=server,
@@ -606,8 +606,7 @@
         # log file is truncated and we cannot get any console log through
         # "console-log" API.
         # The detail is https://bugs.launchpad.net/nova/+bug/1251920
-        self.client.reboot_server(self.server_id, type='HARD')
-        waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+        self.reboot_server(self.server_id, type='HARD')
         self.wait_for(self._get_output)
 
     @decorators.idempotent_id('89104062-69d8-4b19-a71b-f47b7af093d7')
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 5445113..354e3b9 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -16,6 +16,7 @@
 import testtools
 
 from tempest.api.compute import base
+from tempest.common import utils
 from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
@@ -157,16 +158,28 @@
             self.servers_client, server_id, 'ACTIVE')
 
 
-class ServerStableDeviceRescueTest(BaseServerStableDeviceRescueTest):
-    """Test rescuing server specifying type of device for the rescue disk"""
+class ServerStableDeviceRescueTestIDE(BaseServerStableDeviceRescueTest):
+    """Test rescuing server using an IDE device for the rescue disk"""
+
+    @classmethod
+    def skip_checks(cls):
+        super().skip_checks()
+        if not CONF.compute_feature_enabled.ide_bus:
+            raise cls.skipException("IDE bus not available.")
 
     @decorators.idempotent_id('947004c3-e8ef-47d9-9f00-97b74f9eaf96')
+    @testtools.skipIf("aarch64" in CONF.scenario.img_file,
+                      "Aarch64 does not support ide bus for cdrom")
     def test_stable_device_rescue_cdrom_ide(self):
         """Test rescuing server with cdrom and ide as the rescue disk"""
         server_id, rescue_image_id = self._create_server_and_rescue_image(
             hw_rescue_device='cdrom', hw_rescue_bus='ide')
         self._test_stable_device_rescue(server_id, rescue_image_id)
 
+
+class ServerStableDeviceRescueTest(BaseServerStableDeviceRescueTest):
+    """Test rescuing server specifying type of device for the rescue disk"""
+
     @decorators.idempotent_id('16865750-1417-4854-bcf7-496e6753c01e')
     def test_stable_device_rescue_disk_virtio(self):
         """Test rescuing server with disk and virtio as the rescue disk"""
@@ -189,6 +202,7 @@
         self._test_stable_device_rescue(server_id, rescue_image_id)
 
     @decorators.idempotent_id('a3772b42-00bf-4310-a90b-1cc6fd3e7eab')
+    @utils.services('volume')
     def test_stable_device_rescue_disk_virtio_with_volume_attached(self):
         """Test rescuing server with volume attached
 
@@ -214,6 +228,13 @@
 
     min_microversion = '2.87'
 
+    @classmethod
+    def skip_checks(cls):
+        super(ServerBootFromVolumeStableRescueTest, 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)
+
     @decorators.attr(type='slow')
     @decorators.idempotent_id('48f123cb-922a-4065-8db6-b9a9074a556b')
     def test_stable_device_rescue_bfv_blank_volume(self):
diff --git a/tempest/api/compute/servers/test_server_tags.py b/tempest/api/compute/servers/test_server_tags.py
index 619f480..c988788 100644
--- a/tempest/api/compute/servers/test_server_tags.py
+++ b/tempest/api/compute/servers/test_server_tags.py
@@ -13,8 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
-
 from tempest.api.compute import base
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
@@ -84,11 +82,11 @@
         new_tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
         replaced_tags = self.client.update_all_tags(
             self.server['id'], new_tags)['tags']
-        six.assertCountEqual(self, new_tags, replaced_tags)
+        self.assertCountEqual(new_tags, replaced_tags)
 
         # List the tags and check that the tags were replaced.
         fetched_tags = self.client.list_tags(self.server['id'])['tags']
-        six.assertCountEqual(self, new_tags, fetched_tags)
+        self.assertCountEqual(new_tags, fetched_tags)
 
     @decorators.idempotent_id('a63b2a74-e918-4b7c-bcab-10c855f3a57e')
     def test_delete_all_tags(self):
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index cc013e3..1c839eb 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -103,7 +103,7 @@
         server = self.create_test_server(wait_until='ACTIVE')
         self.addCleanup(self.delete_server, server['id'])
         # Update instance name with non-ASCII characters
-        prefix_name = u'\u00CD\u00F1st\u00E1\u00F1c\u00E9'
+        prefix_name = '\u00CD\u00F1st\u00E1\u00F1c\u00E9'
         self._update_server_name(server['id'], 'ACTIVE', prefix_name)
 
         # stop server and check server name update again
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index d85e4f7..4c7c234 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -200,6 +200,10 @@
         super(AttachVolumeShelveTestJSON, cls).skip_checks()
         if not CONF.compute_feature_enabled.shelve:
             raise cls.skipException('Shelve is not available.')
+        if CONF.compute.compute_volume_common_az:
+            # assuming cross_az_attach is set to false in nova.conf
+            # per the compute_volume_common_az option description
+            raise cls.skipException('Cross AZ attach not available.')
 
     def _count_volumes(self, server, validation_resources):
         # Count number of volumes on an instance
@@ -451,6 +455,8 @@
 
     @utils.services('image')
     @decorators.idempotent_id('885ac48a-2d7a-40c5-ae8b-1993882d724c')
+    @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+                          'Snapshotting is not available.')
     def test_snapshot_volume_backed_multiattach(self):
         """Boots a server from a multiattach volume and snapshots the server.
 
diff --git a/tempest/api/identity/admin/v3/test_application_credentials.py b/tempest/api/identity/admin/v3/test_application_credentials.py
index c9cafd8..f5b0356 100644
--- a/tempest/api/identity/admin/v3/test_application_credentials.py
+++ b/tempest/api/identity/admin/v3/test_application_credentials.py
@@ -37,7 +37,7 @@
         secret = app_cred['secret']
 
         # Check that the application credential is functional
-        token_id, resp = self.non_admin_token.get_token(
+        _, resp = self.non_admin_token.get_token(
             app_cred_id=app_cred['id'],
             app_cred_secret=secret,
             auth_data=True
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 32ccb9e..419c6c7 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -157,4 +157,4 @@
         self.addCleanup(self.delete_domain, domain['id'])
         expected_data = {'name': d_name, 'enabled': True}
         self.assertEqual('', domain['description'])
-        self.assertDictContainsSubset(expected_data, domain)
+        self.assertLessEqual(expected_data.items(), domain.items())
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index dd7d5af..e5137f4 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -142,6 +142,26 @@
         self.roles_client.delete_role_from_user_on_domain(
             self.domain['id'], self.user_body['id'], self.role['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('e5a81737-d294-424d-8189-8664858aae4c')
+    def test_grant_list_revoke_role_to_user_on_system(self):
+        self.roles_client.create_user_role_on_system(
+            self.user_body['id'], self.role['id'])
+
+        roles = self.roles_client.list_user_roles_on_system(
+            self.user_body['id'])['roles']
+
+        self.assertEqual(1, len(roles))
+        self.assertEqual(self.role['id'], roles[0]['id'])
+
+        self.roles_client.check_user_role_existence_on_system(
+            self.user_body['id'], self.role['id'])
+
+        self.roles_client.delete_role_from_user_on_system(
+            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 '
@@ -197,6 +217,23 @@
         self.roles_client.delete_role_from_group_on_domain(
             self.domain['id'], self.group_body['id'], self.role['id'])
 
+    @decorators.idempotent_id('c888fe4f-8018-48db-b959-542225c1b4b6')
+    def test_grant_list_revoke_role_to_group_on_system(self):
+        self.roles_client.create_group_role_on_system(
+            self.group_body['id'], self.role['id'])
+
+        roles = self.roles_client.list_group_roles_on_system(
+            self.group_body['id'])['roles']
+
+        self.assertEqual(1, len(roles))
+        self.assertEqual(self.role['id'], roles[0]['id'])
+
+        self.roles_client.check_role_from_group_on_system_existence(
+            self.group_body['id'], self.role['id'])
+
+        self.roles_client.delete_role_from_group_on_system(
+            self.group_body['id'], self.role['id'])
+
     @decorators.idempotent_id('f5654bcc-08c4-4f71-88fe-05d64e06de94')
     def test_list_roles(self):
         """Test listing roles"""
diff --git a/tempest/api/identity/admin/v3/test_services.py b/tempest/api/identity/admin/v3/test_services.py
index a649d27..fb3b03e 100644
--- a/tempest/api/identity/admin/v3/test_services.py
+++ b/tempest/api/identity/admin/v3/test_services.py
@@ -44,7 +44,7 @@
 
         # Verifying response body of create service
         expected_data = {'name': name, 'type': serv_type, 'description': desc}
-        self.assertDictContainsSubset(expected_data, create_service)
+        self.assertLessEqual(expected_data.items(), create_service.items())
 
         # Update description
         s_id = create_service['id']
@@ -61,7 +61,7 @@
         resp3_desc = fetched_service['description']
 
         self.assertEqual(resp2_desc, resp3_desc)
-        self.assertDictContainsSubset(update_service, fetched_service)
+        self.assertLessEqual(update_service.items(), fetched_service.items())
 
     @decorators.idempotent_id('d1dcb1a1-2b6b-4da8-bbb8-5532ef6e8269')
     def test_create_service_without_description(self):
@@ -72,7 +72,7 @@
             type=serv_type, name=name)['service']
         self.addCleanup(self.services_client.delete_service, service['id'])
         expected_data = {'name': name, 'type': serv_type}
-        self.assertDictContainsSubset(expected_data, service)
+        self.assertLessEqual(expected_data.items(), service.items())
 
     @decorators.idempotent_id('e55908e8-360e-439e-8719-c3230a3e179e')
     def test_list_services(self):
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index f3a7471..e191979 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -13,8 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
-
 from tempest.api.identity import base
 from tempest import config
 from tempest.lib.common.utils import data_utils
@@ -40,14 +38,17 @@
 
         # Create a user.
         user_password = data_utils.rand_password()
-        user = self.create_test_user(password=user_password)
+        user = self.create_test_user(password=user_password,
+                                     domain_id=CONF.identity.default_domain_id)
 
         # Create a couple projects
         project1_name = data_utils.rand_name(name=self.__class__.__name__)
-        project1 = self.setup_test_project(name=project1_name)
+        project1 = self.setup_test_project(
+            name=project1_name, domain_id=CONF.identity.default_domain_id)
 
         project2_name = data_utils.rand_name(name=self.__class__.__name__)
-        project2 = self.setup_test_project(name=project2_name)
+        project2 = self.setup_test_project(
+            name=project2_name, domain_id=CONF.identity.default_domain_id)
         self.addCleanup(self.projects_client.delete_project, project2['id'])
 
         # Create a role
@@ -70,8 +71,8 @@
         orig_expires_at = token_auth['token']['expires_at']
         orig_user = token_auth['token']['user']
 
-        self.assertIsInstance(token_auth['token']['expires_at'], six.text_type)
-        self.assertIsInstance(token_auth['token']['issued_at'], six.text_type)
+        self.assertIsInstance(token_auth['token']['expires_at'], str)
+        self.assertIsInstance(token_auth['token']['issued_at'], str)
         self.assertEqual(['password'], token_auth['token']['methods'])
         self.assertEqual(user['id'], token_auth['token']['user']['id'])
         self.assertEqual(user['name'], token_auth['token']['user']['name'])
@@ -91,7 +92,7 @@
 
         self.assertEqual(orig_expires_at, token_auth['token']['expires_at'],
                          'Expiration time should match original token')
-        self.assertIsInstance(token_auth['token']['issued_at'], six.text_type)
+        self.assertIsInstance(token_auth['token']['issued_at'], str)
         self.assertEqual(set(['password', 'token']),
                          set(token_auth['token']['methods']))
         self.assertEqual(orig_user, token_auth['token']['user'],
diff --git a/tempest/api/identity/v2/test_tokens.py b/tempest/api/identity/v2/test_tokens.py
index a928ad9..d3776b8 100644
--- a/tempest/api/identity/v2/test_tokens.py
+++ b/tempest/api/identity/v2/test_tokens.py
@@ -14,7 +14,6 @@
 #    under the License.
 
 from oslo_utils import timeutils
-import six
 from tempest.api.identity import base
 from tempest.lib import decorators
 
@@ -36,7 +35,7 @@
         body = token_client.auth(username, password, tenant_name)
 
         self.assertNotEmpty(body['token']['id'])
-        self.assertIsInstance(body['token']['id'], six.string_types)
+        self.assertIsInstance(body['token']['id'], str)
 
         now = timeutils.utcnow()
         expires_at = timeutils.normalize_time(
diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py
index 77ad720..06734aa 100644
--- a/tempest/api/identity/v3/test_application_credentials.py
+++ b/tempest/api/identity/v3/test_application_credentials.py
@@ -51,7 +51,7 @@
         self.assertNotIn('secret', app_cred)
 
         # Check that the application credential is functional
-        token_id, resp = self.non_admin_token.get_token(
+        _, resp = self.non_admin_token.get_token(
             app_cred_id=app_cred['id'],
             app_cred_secret=secret,
             auth_data=True
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index b201285..55fcead 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -16,7 +16,6 @@
 import operator
 
 from oslo_utils import timeutils
-import six
 
 from tempest.api.identity import base
 from tempest.lib import decorators
@@ -88,7 +87,7 @@
             auth_data=True)
 
         self.assertNotEmpty(token_id)
-        self.assertIsInstance(token_id, six.string_types)
+        self.assertIsInstance(token_id, str)
 
         now = timeutils.utcnow()
         expires_at = timeutils.normalize_time(
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 6425ea9..dc6dd4a 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -77,6 +77,8 @@
         time.sleep(1)
         self.non_admin_users_client.auth_provider.set_auth()
 
+    @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
+                          'Security compliance not available.')
     @decorators.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
     @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
                       'Skipped because environment has an '
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index d3dc19a..23e7fd8 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
+import io
 
 from tempest.common import image as common_image
 from tempest import config
@@ -113,7 +113,7 @@
         cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
 
     def _create_image(self):
-        image_file = six.BytesIO(data_utils.random_bytes())
+        image_file = io.BytesIO(data_utils.random_bytes())
         image = self.create_image(container_format='bare',
                                   disk_format='raw',
                                   is_public=False,
@@ -134,12 +134,6 @@
     def setup_clients(cls):
         super(BaseV2ImageTest, cls).setup_clients()
         cls.client = cls.os_primary.image_client_v2
-        cls.namespaces_client = cls.os_primary.namespaces_client
-        cls.resource_types_client = cls.os_primary.resource_types_client
-        cls.namespace_properties_client =\
-            cls.os_primary.namespace_properties_client
-        cls.namespace_objects_client = cls.os_primary.namespace_objects_client
-        cls.namespace_tags_client = cls.os_primary.namespace_tags_client
         cls.schemas_client = cls.os_primary.schemas_client
         cls.versions_client = cls.os_primary.image_versions_client
 
@@ -204,3 +198,9 @@
     def setup_clients(cls):
         super(BaseV2ImageAdminTest, cls).setup_clients()
         cls.admin_client = cls.os_admin.image_client_v2
+        cls.namespaces_client = cls.os_admin.namespaces_client
+        cls.resource_types_client = cls.os_admin.resource_types_client
+        cls.namespace_properties_client =\
+            cls.os_admin.namespace_properties_client
+        cls.namespace_objects_client = cls.os_admin.namespace_objects_client
+        cls.namespace_tags_client = cls.os_admin.namespace_tags_client
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 595717e..6fd6c4e 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
+import io
 
 from tempest.api.image import base
 from tempest.common import image as common_image
@@ -72,7 +72,7 @@
             self.assertEqual(val, image.get('properties')[key])
 
         # Now try uploading an image file
-        image_file = six.BytesIO(data_utils.random_bytes())
+        image_file = io.BytesIO(data_utils.random_bytes())
         body = self.client.update_image(image['id'], data=image_file)['image']
         self.assertIn('size', body)
         self.assertEqual(1024, body.get('size'))
@@ -204,7 +204,7 @@
         Note that the size of the new image is a random number between
         1024 and 4096
         """
-        image_file = six.BytesIO(data_utils.random_bytes(size))
+        image_file = io.BytesIO(data_utils.random_bytes(size))
         name = 'New Standard Image %s' % name
         image = cls.create_image(name=name,
                                  container_format=container_format,
@@ -306,7 +306,7 @@
                                disk_format, size):
         """Create a new standard image and return newly-registered image-id"""
 
-        image_file = six.BytesIO(data_utils.random_bytes(size))
+        image_file = io.BytesIO(data_utils.random_bytes(size))
         name = 'New Standard Image %s' % name
         image = cls.create_image(name=name,
                                  container_format=container_format,
diff --git a/tempest/api/image/v2/admin/test_images.py b/tempest/api/image/v2/admin/test_images.py
index 7e13d7f..733c778 100644
--- a/tempest/api/image/v2/admin/test_images.py
+++ b/tempest/api/image/v2/admin/test_images.py
@@ -13,10 +13,16 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import io
+
 from tempest.api.image import base
+from tempest.common import waiters
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
+CONF = config.CONF
+
 
 class BasicOperationsImagesAdminTest(base.BaseV2ImageAdminTest):
     """"Test image operations about image owner"""
@@ -52,3 +58,65 @@
         self.assertEqual(random_id_2, updated_image_info['owner'])
         self.assertNotEqual(created_image_info['owner'],
                             updated_image_info['owner'])
+
+
+class ImportCopyImagesTest(base.BaseV2ImageAdminTest):
+    """Test the import copy-image operations"""
+
+    @classmethod
+    def skip_checks(cls):
+        super(ImportCopyImagesTest, cls).skip_checks()
+        if not CONF.image_feature_enabled.import_image:
+            skip_msg = (
+                "%s skipped as image import is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @decorators.idempotent_id('9b3b644e-03d1-11eb-a036-fa163e2eaf49')
+    def test_image_copy_image_import(self):
+        """Test 'copy-image' import functionalities
+
+        Create image, import image with copy-image method and
+        verify that import succeeded.
+        """
+        available_stores = self.get_available_stores()
+        available_import_methods = self.client.info_import()[
+            'import-methods']['value']
+        # NOTE(gmann): Skip if copy-image import method and multistore
+        # are not available.
+        if ('copy-image' not in available_import_methods or
+            not available_stores):
+            raise self.skipException('Either copy-image import method or '
+                                     'multistore is not available')
+        uuid = data_utils.rand_uuid()
+        image_name = data_utils.rand_name('copy-image')
+        container_format = CONF.image.container_formats[0]
+        disk_format = CONF.image.disk_formats[0]
+        image = self.create_image(name=image_name,
+                                  container_format=container_format,
+                                  disk_format=disk_format,
+                                  visibility='private',
+                                  ramdisk_id=uuid)
+        self.assertEqual('queued', image['status'])
+
+        file_content = data_utils.random_bytes()
+        image_file = io.BytesIO(file_content)
+        self.client.store_image_file(image['id'], image_file)
+
+        body = self.client.show_image(image['id'])
+        self.assertEqual(image['id'], body['id'])
+        self.assertEqual(len(file_content), body.get('size'))
+        self.assertEqual('active', body['status'])
+
+        # Copy image to all the stores. In case of all_stores request
+        # glance will skip the stores where image is already available.
+        self.admin_client.image_import(image['id'], method='copy-image',
+                                       all_stores=True,
+                                       all_stores_must_succeed=False)
+
+        # Wait for copy to finished on all stores.
+        failed_stores = waiters.wait_for_image_copied_to_stores(
+            self.client, image['id'])
+        # Assert if copy is failed on any store.
+        self.assertEqual(0, len(failed_stores),
+                         "Failed to copy the following stores: %s" %
+                         str(failed_stores))
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_objects.py b/tempest/api/image/v2/admin/test_images_metadefs_namespace_objects.py
similarity index 98%
rename from tempest/api/image/v2/test_images_metadefs_namespace_objects.py
rename to tempest/api/image/v2/admin/test_images_metadefs_namespace_objects.py
index 32b81b1..9222920 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_objects.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_namespace_objects.py
@@ -16,7 +16,7 @@
 from tempest.lib import decorators
 
 
-class MetadataNamespaceObjectsTest(base.BaseV2ImageTest):
+class MetadataNamespaceObjectsTest(base.BaseV2ImageAdminTest):
     """Test the Metadata definition namespace objects basic functionality"""
 
     def _create_namespace_object(self, namespace):
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_properties.py b/tempest/api/image/v2/admin/test_images_metadefs_namespace_properties.py
similarity index 97%
rename from tempest/api/image/v2/test_images_metadefs_namespace_properties.py
rename to tempest/api/image/v2/admin/test_images_metadefs_namespace_properties.py
index 1d4f0a6..10dfba1 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_properties.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_namespace_properties.py
@@ -15,7 +15,7 @@
 from tempest.lib import decorators
 
 
-class MetadataNamespacePropertiesTest(base.BaseV2ImageTest):
+class MetadataNamespacePropertiesTest(base.BaseV2ImageAdminTest):
     """Test the Metadata definition namespace property basic functionality"""
 
     @decorators.idempotent_id('b1a3765e-3a5d-4f6d-a3a7-3ca3476ae768')
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py b/tempest/api/image/v2/admin/test_images_metadefs_namespace_tags.py
similarity index 98%
rename from tempest/api/image/v2/test_images_metadefs_namespace_tags.py
rename to tempest/api/image/v2/admin/test_images_metadefs_namespace_tags.py
index dc64185..9e88e03 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_namespace_tags.py
@@ -16,7 +16,7 @@
 from tempest.lib import decorators
 
 
-class MetadataNamespaceTagsTest(base.BaseV2ImageTest):
+class MetadataNamespaceTagsTest(base.BaseV2ImageAdminTest):
     """Test the Metadata definition namespace tags basic functionality"""
 
     tags = [
diff --git a/tempest/api/image/v2/test_images_metadefs_namespaces.py b/tempest/api/image/v2/admin/test_images_metadefs_namespaces.py
similarity index 98%
rename from tempest/api/image/v2/test_images_metadefs_namespaces.py
rename to tempest/api/image/v2/admin/test_images_metadefs_namespaces.py
index 502949f..0fe49f9 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespaces.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_namespaces.py
@@ -20,7 +20,7 @@
 from tempest.lib import exceptions as lib_exc
 
 
-class MetadataNamespacesTest(base.BaseV2ImageTest):
+class MetadataNamespacesTest(base.BaseV2ImageAdminTest):
     """Test the Metadata definition Namespaces basic functionality"""
 
     @decorators.idempotent_id('319b765e-7f3d-4b3d-8b37-3ca3876ee768')
diff --git a/tempest/api/image/v2/test_images_metadefs_resource_types.py b/tempest/api/image/v2/admin/test_images_metadefs_resource_types.py
similarity index 97%
rename from tempest/api/image/v2/test_images_metadefs_resource_types.py
rename to tempest/api/image/v2/admin/test_images_metadefs_resource_types.py
index 6867f2d..e533c43 100644
--- a/tempest/api/image/v2/test_images_metadefs_resource_types.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_resource_types.py
@@ -17,7 +17,7 @@
 from tempest.lib import decorators
 
 
-class MetadataResourceTypesTest(base.BaseV2ImageTest):
+class MetadataResourceTypesTest(base.BaseV2ImageAdminTest):
     """Test the Metadata definition resource types basic functionality"""
 
     @decorators.idempotent_id('6f358a4e-5ef0-11e6-a795-080027d0d606')
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 28299a4..d283ab3 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -14,10 +14,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import io
 import random
 
-import six
-
 from oslo_log import log as logging
 from tempest.api.image import base
 from tempest.common import waiters
@@ -69,6 +68,23 @@
         self.assertEqual('queued', image['status'])
         return image
 
+    def _require_import_method(self, method):
+        if method not in self.available_import_methods:
+            raise self.skipException('Server does not support '
+                                     '%s import method' % method)
+
+    def _stage_and_check(self):
+        image = self._create_image()
+        # Stage image data
+        file_content = data_utils.random_bytes()
+        image_file = io.BytesIO(file_content)
+        self.client.stage_image_file(image['id'], image_file)
+        # Check image status is 'uploading'
+        body = self.client.show_image(image['id'])
+        self.assertEqual(image['id'], body['id'])
+        self.assertEqual('uploading', body['status'])
+        return image['id']
+
     @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2')
     def test_image_glance_direct_import(self):
         """Test 'glance-direct' import functionalities
@@ -76,21 +92,27 @@
         Create image, stage image data, import image and verify
         that import succeeded.
         """
-        if 'glance-direct' not in self.available_import_methods:
-            raise self.skipException('Server does not support '
-                                     'glance-direct import method')
-        image = self._create_image()
-        # Stage image data
-        file_content = data_utils.random_bytes()
-        image_file = six.BytesIO(file_content)
-        self.client.stage_image_file(image['id'], image_file)
-        # Check image status is 'uploading'
-        body = self.client.show_image(image['id'])
-        self.assertEqual(image['id'], body['id'])
-        self.assertEqual('uploading', body['status'])
+        self._require_import_method('glance-direct')
+
+        image_id = self._stage_and_check()
         # import image from staging to backend
-        self.client.image_import(image['id'], method='glance-direct')
-        self.client.wait_for_resource_activation(image['id'])
+        resp = self.client.image_import(image_id, method='glance-direct')
+        waiters.wait_for_image_imported_to_stores(self.client, image_id)
+
+        if not self.versions_client.has_version('2.12'):
+            # API is not new enough to support image/tasks API
+            LOG.info('Glance does not support v2.12, so I am unable to '
+                     'validate the image/tasks API.')
+            return
+
+        tasks = waiters.wait_for_image_tasks_status(
+            self.client, image_id, 'success')
+        self.assertEqual(1, len(tasks))
+        task = tasks[0]
+        self.assertEqual(resp.response['x-openstack-request-id'],
+                         task['request_id'])
+        self.assertEqual('glance-direct',
+                         task['input']['import_req']['method']['name'])
 
     @decorators.idempotent_id('f6feb7a4-b04f-4706-a011-206129f83e62')
     def test_image_web_download_import(self):
@@ -99,9 +121,8 @@
         Create image, import image and verify that import
         succeeded.
         """
-        if 'web-download' not in self.available_import_methods:
-            raise self.skipException('Server does not support '
-                                     'web-download import method')
+        self._require_import_method('web-download')
+
         image = self._create_image()
         # Now try to get image details
         body = self.client.show_image(image['id'])
@@ -111,7 +132,48 @@
         image_uri = CONF.image.http_image
         self.client.image_import(image['id'], method='web-download',
                                  image_uri=image_uri)
-        self.client.wait_for_resource_activation(image['id'])
+        waiters.wait_for_image_imported_to_stores(self.client, image['id'])
+
+    @decorators.idempotent_id('e04761a1-22af-42c2-b8bc-a34a3f12b585')
+    def test_remote_import(self):
+        """Test image import against a different worker than stage.
+
+        This creates and stages an image against the primary API worker,
+        but then calls import on a secondary worker (if available) to
+        test that distributed image import works (i.e. proxies the import
+        request to the proper worker).
+        """
+        self._require_import_method('glance-direct')
+
+        if not CONF.image.alternate_image_endpoint:
+            raise self.skipException('No image_remote service to test '
+                                     'against')
+
+        image_id = self._stage_and_check()
+        # import image from staging to backend, but on the alternate worker
+        self.os_primary.image_client_remote.image_import(
+            image_id, method='glance-direct')
+        waiters.wait_for_image_imported_to_stores(self.client, image_id)
+
+    @decorators.idempotent_id('44d60544-1524-42f7-8899-315301105dd8')
+    def test_remote_delete(self):
+        """Test image delete against a different worker than stage.
+
+        This creates and stages an image against the primary API worker,
+        but then calls delete on a secondary worker (if available) to
+        test that distributed image import works (i.e. proxies the delete
+        request to the proper worker).
+        """
+        self._require_import_method('glance-direct')
+
+        if not CONF.image.alternate_image_endpoint:
+            raise self.skipException('No image_remote service to test '
+                                     'against')
+
+        image_id = self._stage_and_check()
+        # delete image from staging to backend, but on the alternate worker
+        self.os_primary.image_client_remote.delete_image(image_id)
+        self.client.wait_for_resource_deletion(image_id)
 
 
 class MultiStoresImportImagesTest(base.BaseV2ImageTest):
@@ -158,7 +220,7 @@
 
         self.client.stage_image_file(
             image['id'],
-            six.BytesIO(data_utils.random_bytes(10485760)))
+            io.BytesIO(data_utils.random_bytes()))
         # Check image status is 'uploading'
         body = self.client.show_image(image['id'])
         self.assertEqual(image['id'], body['id'])
@@ -240,7 +302,7 @@
 
         # Now try uploading an image file
         file_content = data_utils.random_bytes()
-        image_file = six.BytesIO(file_content)
+        image_file = io.BytesIO(file_content)
         self.client.store_image_file(image['id'], image_file)
 
         # Now try to get image details
@@ -317,7 +379,7 @@
 
         # Upload an image file
         content = data_utils.random_bytes()
-        image_file = six.BytesIO(content)
+        image_file = io.BytesIO(content)
         self.client.store_image_file(image['id'], image_file)
 
         # Deactivate image
@@ -367,7 +429,7 @@
         1024 and 4096
         """
         size = random.randint(1024, 4096)
-        image_file = six.BytesIO(data_utils.random_bytes(size))
+        image_file = io.BytesIO(data_utils.random_bytes(size))
         tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
         image = cls.create_image(container_format=container_format,
                                  disk_format=disk_format,
@@ -402,7 +464,8 @@
         # Validate that the list was fetched sorted accordingly
         msg = 'No images were found that met the filter criteria.'
         self.assertNotEmpty(images_list, msg)
-        sorted_list = [image['size'] for image in images_list]
+        sorted_list = [image['size'] for image in images_list
+                       if image['size'] is not None]
         msg = 'The list of images was not sorted correctly.'
         self.assertEqual(sorted(sorted_list, reverse=desc), sorted_list, msg)
 
@@ -549,7 +612,7 @@
     def test_list_images_param_member_status(self):
         """Test listing images by member_status and visibility"""
         # Create an image to be shared using default visibility
-        image_file = six.BytesIO(data_utils.random_bytes(2048))
+        image_file = io.BytesIO(data_utils.random_bytes(2048))
         container_format = CONF.image.container_formats[0]
         disk_format = CONF.image.disk_formats[0]
         image = self.create_image(container_format=container_format,
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index dc2bb96..a3802a9 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -14,11 +14,16 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import time
+
 from tempest.api.image 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 ImagesNegativeTest(base.BaseV2ImageTest):
 
@@ -114,3 +119,102 @@
         self.assertRaises(lib_exc.Forbidden,
                           self.client.delete_image,
                           image['id'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('a0ae75d4-ce9c-4576-b823-aba04c8acabd')
+    def test_update_image_reserved_property(self):
+        """Attempt to add a reserved property to an image.
+
+        Glance bans some internal-use-only properties such that they will
+        cause a PATCH to fail. Since os_glance_* is banned, we can use a
+        key in that namespace here.
+        """
+        if not CONF.image_feature_enabled.os_glance_reserved:
+            raise self.skipException('os_glance_reserved is not enabled')
+
+        image = self.create_image(name='test',
+                                  container_format='bare',
+                                  disk_format='raw')
+        self.assertRaises(lib_exc.Forbidden,
+                          self.client.update_image,
+                          image['id'], [{'add': '/os_glance_foo',
+                                         'value': 'bar'}])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('e3fb7df8-2742-4143-8976-f1b26870f0a0')
+    def test_create_image_reserved_property(self):
+        """Attempt to create an image with a reserved property.
+
+        Glance bans some internal-use-only properties such that they will
+        cause an image create to fail. Since os_glance_* is banned, we can
+        use a key in that namespace here.
+        """
+        if not CONF.image_feature_enabled.os_glance_reserved:
+            raise self.skipException('os_glance_reserved is not enabled')
+
+        self.assertRaises(lib_exc.Forbidden,
+                          self.create_image,
+                          name='test',
+                          container_format='bare',
+                          disk_format='raw',
+                          os_glance_foo='bar')
+
+
+class ImportImagesNegativeTest(base.BaseV2ImageTest):
+    """Here we test the import operations for image"""
+
+    @classmethod
+    def skip_checks(cls):
+        super(ImportImagesNegativeTest, cls).skip_checks()
+        if not CONF.image_feature_enabled.import_image:
+            skip_msg = (
+                "%s skipped as image import is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(ImportImagesNegativeTest, cls).resource_setup()
+        cls.available_import_methods = cls.client.info_import()[
+            'import-methods']['value']
+        if not cls.available_import_methods:
+            raise cls.skipException('Server does not support '
+                                    'any import method')
+
+        cls.available_stores = cls.get_available_stores()
+        if not len(cls.available_stores) > 0:
+            raise cls.skipException(
+                'No stores configured %s' % cls.available_stores)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('c52f6a77-f522-4411-8dbe-9d14f2b1ba6b')
+    def test_image_web_download_import_with_bad_url(self):
+        """Test 'web-download' import functionalities
+
+        Make sure that web-download with invalid URL fails properly.
+        """
+        if 'web-download' not in self.available_import_methods:
+            raise self.skipException('Server does not support '
+                                     'web-download import method')
+        image = self.client.create_image(name='test',
+                                         container_format='bare',
+                                         disk_format='raw')
+        # Now try to get image details
+        body = self.client.show_image(image['id'])
+        self.assertEqual(image['id'], body['id'])
+        self.assertEqual('queued', body['status'])
+        stores = self.get_available_stores()
+        # import image from web to backend
+        image_uri = 'http://does-not.exist/no/possible/way'
+        self.client.image_import(image['id'], method='web-download',
+                                 image_uri=image_uri,
+                                 stores=[stores[0]['id']])
+
+        start_time = int(time.time())
+        while int(time.time()) - start_time < self.client.build_timeout:
+            body = self.client.show_image(image['id'])
+            if body.get('os_glance_failed_import'):
+                # Store ended up in failed list, which is good
+                return
+            time.sleep(self.client.build_interval)
+
+        self.fail('Image never reported failed store')
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 190d9e3..1ce9f47 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -45,11 +45,17 @@
         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(
+        self.creds_client = identity.identity_utils(self.os_admin)
+        self.project = self.creds_client.create_project(
             name=name, description=description)
         self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
                         self.project['id'])
 
+    def tearDown(self):
+        super(QuotasNegativeTest, self).tearDown()
+        self.credentials_provider.cleanup_default_secgroup(
+            self.os_admin.security_groups_client, self.project['id'])
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('644f4e1b-1bf9-4af0-9fd8-eb56ac0f51cf')
     def test_network_quota_exceeding(self):
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index b6bf369..696d68d 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -76,6 +76,8 @@
         cls.subnetpools_client = cls.os_primary.subnetpools_client
         cls.subnets_client = cls.os_primary.subnets_client
         cls.ports_client = cls.os_primary.ports_client
+        cls.floating_ips_port_forwarding_client =\
+            cls.os_primary.floating_ips_port_forwarding_client
         cls.quotas_client = cls.os_primary.network_quotas_client
         cls.floating_ips_client = cls.os_primary.floating_ips_client
         cls.security_groups_client = cls.os_primary.security_groups_client
@@ -84,6 +86,8 @@
         cls.network_versions_client = cls.os_primary.network_versions_client
         cls.service_providers_client = cls.os_primary.service_providers_client
         cls.tags_client = cls.os_primary.tags_client
+        cls.log_resource_client = cls.os_primary.log_resource_client
+        cls.loggable_resource_client = cls.os_primary.loggable_resource_client
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index 0b9d381..bf9eae6 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -13,8 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
-
 from tempest.api.network import base
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
@@ -99,8 +97,19 @@
         body = self.ports_client.update_port(
             port_id, allowed_address_pairs=allowed_address_pairs)
         allowed_address_pair = body['port']['allowed_address_pairs']
-        six.assertCountEqual(self, allowed_address_pair,
-                             allowed_address_pairs)
+        # NOTE(slaweq): Attribute "active" is added to the
+        # allowed_address_pairs in the Xena release.
+        # To make our existing allowed_address_pairs API tests to be passing in
+        # both cases, with and without that "active" attribute, we need to
+        # removes that field from the allowed_address_pairs which are returned
+        # by the Neutron server.
+        # We could make expected results of those tests to be dependend on the
+        # available Neutron's API extensions but in that case existing tests
+        # may fail randomly as all tests are always using same IP addresses
+        # thus allowed_address_pair may be active=True or active=False.
+        for pair in allowed_address_pair:
+            pair.pop('active', None)
+        self.assertCountEqual(allowed_address_pair, allowed_address_pairs)
 
     @decorators.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4')
     def test_update_port_with_address_pair(self):
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index c32d3c1..64f6e80 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -15,9 +15,9 @@
 
 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 data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
@@ -66,7 +66,7 @@
         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):
+        for _ in range(2):
             port = cls.create_port(cls.network)
             cls.ports.append(port)
 
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 7646b63..caaf964 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 import netaddr
-import six
 import testtools
 
 from tempest.api.network import base
@@ -595,9 +594,9 @@
         subnets = [sub['id'] for sub in body['subnets']
                    if sub['network_id'] == network['id']]
         test_subnet_ids = [sub['id'] for sub in (subnet1, subnet2)]
-        six.assertCountEqual(self, subnets,
-                             test_subnet_ids,
-                             'Subnet are not in the same network')
+        self.assertCountEqual(subnets,
+                              test_subnet_ids,
+                              'Subnet are not in the same network')
 
 
 class NetworksIpV6TestAttrs(BaseNetworkTestResources):
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 479578d..190f7e0 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -16,7 +16,6 @@
 import ipaddress
 
 import netaddr
-import six
 import testtools
 
 from tempest.api.network import base_security_groups as sec_base
@@ -52,7 +51,8 @@
 
     def _create_subnet(self, network, gateway='',
                        cidr=None, mask_bits=None, **kwargs):
-        subnet = self.create_subnet(network, gateway, cidr, mask_bits)
+        subnet = self.create_subnet(
+            network, gateway, cidr, mask_bits, **kwargs)
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.subnets_client.delete_subnet, subnet['id'])
         return subnet
@@ -234,15 +234,15 @@
         # Get two IP addresses
         ip_address_1 = None
         ip_address_2 = None
-        ip_network = ipaddress.ip_network(six.text_type(subnet['cidr']))
+        ip_network = ipaddress.ip_network(str(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)
+                ip_address_1 = str(ip)
             else:
                 ip_address_2 = ip_address_1
-                ip_address_1 = six.text_type(ip)
+                ip_address_1 = str(ip)
                 # Make sure these two IP addresses have different substring
                 if ip_address_1[:-1] != ip_address_2[:-1]:
                     break
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index d75acfc..532ef65 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -110,6 +110,33 @@
         # Delete security group
         self._delete_security_group(group_create_body['security_group']['id'])
 
+    @decorators.idempotent_id('fd1ea1c5-eedc-403f-898d-2b562e853f2e')
+    def test_delete_security_group_clear_associated_rules(self):
+        """Verify delete security group.
+
+        its associated security group rules are also deleted
+        """
+        group_create_body, _ = self._create_security_group()
+
+        # Create rules for tcp protocol
+        client = self.security_group_rules_client
+        rule_create_body = client.create_security_group_rule(
+            security_group_id=group_create_body['security_group']['id'],
+            protocol='tcp',
+            direction='ingress',
+            ethertype=self.ethertype
+        )
+        rule_id = rule_create_body['security_group_rule']['id']
+        # Delete security group
+        self._delete_security_group(group_create_body['security_group']['id'])
+
+        # List rules and verify created rule is not in response
+        rule_list_body = (
+            self.security_group_rules_client.list_security_group_rules())
+        rule_list = [rule['id']
+                     for rule in rule_list_body['security_group_rules']]
+        self.assertNotIn(rule_id, rule_list)
+
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('cfb99e0e-7410-4a3d-8a0c-959a63ee77e9')
     def test_create_show_delete_security_group_rule(self):
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index e8f3f8b..8d8039b 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -18,7 +18,6 @@
 from tempest.common import custom_matchers
 from tempest import config
 from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions as lib_exc
 import tempest.test
 
@@ -30,11 +29,6 @@
 
     The containers should be visible from the container_client given.
     Will not throw any error if the containers don't exist.
-    Will not check that object and container deletions succeed.
-    After delete all the objects from a container, it will wait 2
-    seconds before delete the container itself, in order to deployments
-    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(or string of a container)
                        to be deleted
@@ -50,12 +44,11 @@
             _, objlist = container_client.list_container_objects(cont, params)
             # delete every object in the container
             for obj in objlist:
-                test_utils.call_and_ignore_notfound_exc(
-                    object_client.delete_object, cont, obj['name'])
-            # sleep 2 seconds to sync the deletion of the objects
-            # in HA deployment
-            time.sleep(2)
+                object_client.delete_object(cont, obj['name'])
+                object_client.wait_for_resource_deletion(obj['name'], cont)
+            # Verify resource deletion
             container_client.delete_container(cont)
+            container_client.wait_for_resource_deletion(cont)
         except lib_exc.NotFound:
             pass
 
@@ -123,12 +116,20 @@
             object_name = data_utils.rand_name(name='TestObject')
         if data is None:
             data = data_utils.random_bytes()
-        cls.object_client.create_object(container_name,
-                                        object_name,
-                                        data,
-                                        metadata=metadata)
 
-        return object_name, data
+        err = Exception()
+        for _ in range(5):
+            try:
+                cls.object_client.create_object(container_name,
+                                                object_name,
+                                                data,
+                                                metadata=metadata)
+                return object_name, data
+            # after bucket creation we might see Conflict
+            except lib_exc.Conflict as e:
+                err = e
+                time.sleep(2)
+        raise err
 
     @classmethod
     def delete_containers(cls, container_client=None, object_client=None):
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index da8ad66..4966ec4 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -14,8 +14,6 @@
 #    under the License.
 
 import random
-
-import six
 import testtools
 
 from tempest.api.object_storage import base
@@ -43,7 +41,7 @@
     def resource_setup(cls):
         super(AccountTest, cls).resource_setup()
         for i in range(ord('a'), ord('f') + 1):
-            name = data_utils.rand_name(name='%s-' % six.int2byte(i))
+            name = data_utils.rand_name(name='%s-' % bytes((i,)))
             cls.container_client.update_container(name)
             cls.addClassResourceCleanup(base.delete_containers,
                                         [name],
@@ -62,7 +60,7 @@
         self.assertIsNotNone(container_list)
 
         for container_name in self.containers:
-            self.assertIn(six.text_type(container_name).encode('utf-8'),
+            self.assertIn(str(container_name).encode('utf-8'),
                           container_list)
 
     @decorators.idempotent_id('884ec421-fbad-4fcc-916b-0580f2699565')
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index c8731fe..0259373 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -31,9 +31,10 @@
         super(ObjectTestACLs, self).setUp()
         self.container_name = self.create_container()
 
-    def tearDown(self):
-        self.delete_containers()
-        super(ObjectTestACLs, self).tearDown()
+    @classmethod
+    def resource_cleanup(cls):
+        cls.delete_containers()
+        super(ObjectTestACLs, cls).resource_cleanup()
 
     @decorators.idempotent_id('a3270f3f-7640-4944-8448-c7ea783ea5b6')
     def test_read_object_with_rights(self):
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 73d7f27..85e6ddb 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -42,9 +42,10 @@
         self.container_name = data_utils.rand_name(name='TestContainer')
         self.container_client.update_container(self.container_name)
 
-    def tearDown(self):
-        self.delete_containers([self.container_name])
-        super(ObjectACLsNegativeTest, self).tearDown()
+    @classmethod
+    def resource_cleanup(cls):
+        cls.delete_containers()
+        super(ObjectACLsNegativeTest, cls).resource_cleanup()
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('af587587-0c24-4e15-9822-8352ce711013')
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index fcd9a7c..7977a7a 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -44,10 +44,10 @@
         self.container_client.create_update_or_delete_container_metadata(
             self.container_name, create_update_metadata=metadata)
 
-    def tearDown(self):
-        """Cleans the container of any object after each test."""
-        self.delete_containers()
-        super(ContainerQuotasTest, self).tearDown()
+    @classmethod
+    def resource_cleanup(cls):
+        cls.delete_containers()
+        super(ContainerQuotasTest, cls).resource_cleanup()
 
     @decorators.idempotent_id('9a0fb034-86af-4df0-86fa-f8bd7db21ae0')
     @utils.requires_ext(extension='container_quotas', service='object')
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 7ad6f6f..085b8ab 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -21,9 +21,10 @@
 class ContainerTest(base.BaseObjectTest):
     """Test containers"""
 
-    def tearDown(self):
-        self.delete_containers()
-        super(ContainerTest, self).tearDown()
+    @classmethod
+    def resource_cleanup(cls):
+        cls.delete_containers()
+        super(ContainerTest, cls).resource_cleanup()
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('92139d73-7819-4db1-85f8-3f2f22a8d91f')
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
index 31c33db..51c711f 100644
--- a/tempest/api/object_storage/test_container_services_negative.py
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -36,6 +36,11 @@
             body = cls.capabilities_client.list_capabilities()
             cls.constraints = body['swift']
 
+    @classmethod
+    def resource_cleanup(cls):
+        cls.delete_containers()
+        super(ContainerNegativeTest, cls).resource_cleanup()
+
     @decorators.attr(type=["negative"])
     @decorators.idempotent_id('30686921-4bed-4764-a038-40d741ed4e78')
     @testtools.skipUnless(
@@ -167,11 +172,7 @@
         # create a container and an object within it
         # attempt to delete a container that isn't empty.
         container_name = self.create_container()
-        self.addCleanup(self.container_client.delete_container,
-                        container_name)
         object_name, _ = self.create_object(container_name)
-        self.addCleanup(self.object_client.delete_object,
-                        container_name, object_name)
 
         ex = self.assertRaises(exceptions.Conflict,
                                self.container_client.delete_container,
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index c5334a9..6b1f849 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -14,8 +14,8 @@
 #    under the License.
 
 import time
+from urllib import parse as urlparse
 
-from six.moves.urllib import parse as urlparse
 import testtools
 
 from tempest.api.object_storage import base
@@ -58,6 +58,7 @@
 
         # Default container-server config only allows localhost
         cls.local_ip = '127.0.0.1'
+        cls.local_ip_v6 = '[::1]'
 
         # Must be configure according to container-sync interval
         container_sync_timeout = CONF.object_storage.container_sync_timeout
@@ -92,7 +93,7 @@
             # create object in container
             object_name = data_utils.rand_name(name='TestSyncObject')
             data = object_name[::-1].encode()  # Raw data, we need bytes
-            resp, _ = obj_client[0].create_object(cont[0], object_name, data)
+            obj_client[0].create_object(cont[0], object_name, data)
             self.objects.append(object_name)
 
         # wait until container contents list is not empty
@@ -134,11 +135,18 @@
         """Test container synchronization"""
         def make_headers(cont, cont_client):
             # tell first container to synchronize to a second
-            client_proxy_ip = \
-                urlparse.urlparse(cont_client.base_url).netloc.split(':')[0]
-            client_base_url = \
-                cont_client.base_url.replace(client_proxy_ip,
-                                             self.local_ip)
+            # use rsplit with a maxsplit of 1 to ensure ipv6 adresses are
+            # handled properly as well
+            client_proxy_ip = urlparse.urlparse(
+                cont_client.base_url).netloc.rsplit(':', 1)[0]
+            if client_proxy_ip.startswith("["):  # lazy check
+                client_base_url = \
+                    cont_client.base_url.replace(client_proxy_ip,
+                                                 self.local_ip_v6)
+            else:
+                client_base_url = \
+                    cont_client.base_url.replace(client_proxy_ip,
+                                                 self.local_ip)
             headers = {'X-Container-Sync-Key': 'sync_key',
                        'X-Container-Sync-To': "%s/%s" %
                        (client_base_url, str(cont))}
diff --git a/tempest/api/object_storage/test_crossdomain.py b/tempest/api/object_storage/test_crossdomain.py
index c611ed6..365dc78 100644
--- a/tempest/api/object_storage/test_crossdomain.py
+++ b/tempest/api/object_storage/test_crossdomain.py
@@ -32,9 +32,6 @@
 
         cls.xml_end = "</cross-domain-policy>"
 
-    def setUp(self):
-        super(CrossdomainTest, self).setUp()
-
     @decorators.idempotent_id('d1b8b031-b622-4010-82f9-ff78a9e915c7')
     @utils.requires_ext(extension='crossdomain', service='object')
     def test_get_crossdomain_policy(self):
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
index f5e2443..d4a6a9f 100644
--- a/tempest/api/object_storage/test_healthcheck.py
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -21,9 +21,6 @@
 class HealthcheckTest(base.BaseObjectTest):
     """Test healthcheck"""
 
-    def setUp(self):
-        super(HealthcheckTest, self).setUp()
-
     @decorators.idempotent_id('db5723b1-f25c-49a9-bfeb-7b5640caf337')
     def test_get_healthcheck(self):
         """Test getting healthcheck"""
diff --git a/tempest/api/object_storage/test_object_formpost.py b/tempest/api/object_storage/test_object_formpost.py
index d857d3b..39e895e 100644
--- a/tempest/api/object_storage/test_object_formpost.py
+++ b/tempest/api/object_storage/test_object_formpost.py
@@ -15,8 +15,7 @@
 import hashlib
 import hmac
 import time
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
 
 from tempest.api.object_storage import base
 from tempest.common import utils
diff --git a/tempest/api/object_storage/test_object_formpost_negative.py b/tempest/api/object_storage/test_object_formpost_negative.py
index 0499eef..971a223 100644
--- a/tempest/api/object_storage/test_object_formpost_negative.py
+++ b/tempest/api/object_storage/test_object_formpost_negative.py
@@ -15,8 +15,7 @@
 import hashlib
 import hmac
 import time
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
 
 from tempest.api.object_storage import base
 from tempest.common import utils
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 4ecbcad..2823185 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -13,12 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import hashlib
 import random
 import re
 import time
 import zlib
 
+from oslo_utils.secretutils import md5
 from tempest.api.object_storage import base
 from tempest.common import custom_matchers
 from tempest import config
@@ -151,8 +151,8 @@
         """Test creating object with Etag"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
-        md5 = hashlib.md5(data).hexdigest()
-        metadata = {'Etag': md5}
+        create_md5 = md5(data, usedforsecurity=False).hexdigest()
+        metadata = {'Etag': create_md5}
         resp, _ = self.object_client.create_object(
             self.container_name,
             object_name,
@@ -182,6 +182,7 @@
         self.assertEqual(data, body)
 
     @decorators.idempotent_id('4f84422a-e2f2-4403-b601-726a4220b54e')
+    @decorators.unstable_test(bug='1905432')
     def test_create_object_with_transfer_encoding(self):
         """Test creating object with transfer_encoding"""
         object_name = data_utils.rand_name(name='TestObject')
@@ -640,7 +641,7 @@
         """Test getting object with if_match"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes(10)
-        create_md5 = hashlib.md5(data).hexdigest()
+        create_md5 = md5(data, usedforsecurity=False).hexdigest()
         create_metadata = {'Etag': create_md5}
         self.object_client.create_object(self.container_name,
                                          object_name,
@@ -680,7 +681,7 @@
         """Test getting object with if_none_match"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
-        create_md5 = hashlib.md5(data).hexdigest()
+        create_md5 = md5(data, usedforsecurity=False).hexdigest()
         create_metadata = {'Etag': create_md5}
         self.object_client.create_object(self.container_name,
                                          object_name,
@@ -688,7 +689,7 @@
                                          metadata=create_metadata)
 
         list_data = data_utils.random_bytes()
-        list_md5 = hashlib.md5(list_data).hexdigest()
+        list_md5 = md5(list_data, usedforsecurity=False).hexdigest()
         list_metadata = {'If-None-Match': list_md5}
         resp, body = self.object_client.get_object(
             self.container_name,
@@ -770,11 +771,11 @@
         headers = {}
         headers['X-Copy-From'] = "%s/%s" % (str(self.container_name),
                                             str(object_name))
-        resp, body = self.object_client.create_object(self.container_name,
-                                                      object_name,
-                                                      data=None,
-                                                      metadata=metadata,
-                                                      headers=headers)
+        resp, _ = self.object_client.create_object(self.container_name,
+                                                   object_name,
+                                                   data=None,
+                                                   metadata=metadata,
+                                                   headers=headers)
         self.assertHeaders(resp, 'Object', 'PUT')
 
         # check the content type
@@ -977,8 +978,8 @@
         """
         object_name, data = self.create_object(self.container_name)
         # local copy is identical, no download
-        md5 = hashlib.md5(data).hexdigest()
-        headers = {'If-None-Match': md5}
+        object_md5 = md5(data, usedforsecurity=False).hexdigest()
+        headers = {'If-None-Match': object_md5}
         url = "%s/%s" % (self.container_name, object_name)
         resp, _ = self.object_client.get(url, headers=headers)
         self.assertEqual(resp['status'], '304')
@@ -992,8 +993,8 @@
 
         # local copy is different, download
         local_data = "something different"
-        md5 = hashlib.md5(local_data.encode()).hexdigest()
-        headers = {'If-None-Match': md5}
+        other_md5 = md5(local_data.encode(), usedforsecurity=False).hexdigest()
+        headers = {'If-None-Match': other_md5}
         resp, _ = self.object_client.get(url, headers=headers)
         self.assertHeaders(resp, 'Object', 'GET')
 
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 7e553ca..22d12ce 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -12,14 +12,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import hashlib
-
 from oslo_serialization import jsonutils as json
 
+from oslo_utils.secretutils import md5
 from tempest.api.object_storage 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
 
 # Each segment, except for the final one, must be at least 1 megabyte
@@ -35,11 +33,7 @@
         self.objects = []
 
     def tearDown(self):
-        for obj in self.objects:
-            test_utils.call_and_ignore_notfound_exc(
-                self.object_client.delete_object,
-                self.container_name, obj)
-        self.container_client.delete_container(self.container_name)
+        self.delete_containers()
         super(ObjectSloTest, self).tearDown()
 
     def _create_object(self, container_name, object_name, data, params=None):
@@ -70,10 +64,12 @@
         path_object_2 = '/%s/%s' % (self.container_name,
                                     object_name_base_2)
         data_manifest = [{'path': path_object_1,
-                          'etag': hashlib.md5(self.content).hexdigest(),
+                          'etag': md5(self.content,
+                                      usedforsecurity=False).hexdigest(),
                           'size_bytes': data_size},
                          {'path': path_object_2,
-                          'etag': hashlib.md5(self.content).hexdigest(),
+                          'etag': md5(self.content,
+                                      usedforsecurity=False).hexdigest(),
                           'size_bytes': data_size}]
 
         return json.dumps(data_manifest)
@@ -165,6 +161,6 @@
 
         self.assertHeaders(resp, 'Object', 'DELETE')
 
-        resp, body = self.container_client.list_container_objects(
+        resp, _ = self.container_client.list_container_objects(
             self.container_name)
         self.assertEqual(int(resp['x-container-object-count']), 0)
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index 29354b6..e75e22a 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -15,8 +15,7 @@
 import hashlib
 import hmac
 import time
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
 
 from tempest.api.object_storage import base
 from tempest.common import utils
diff --git a/tempest/api/object_storage/test_object_temp_url_negative.py b/tempest/api/object_storage/test_object_temp_url_negative.py
index bbb4827..4ad8428 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -15,8 +15,7 @@
 import hashlib
 import hmac
 import time
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
 
 from tempest.api.object_storage import base
 from tempest.common import utils
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index 0a8b56d..73903cf 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -64,9 +64,8 @@
 class GroupSnapshotsTest(BaseGroupSnapshotsTest):
     """Test group snapshot"""
 
-    _api_version = 3
-    min_microversion = '3.14'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.14'
+    volume_max_microversion = 'latest'
 
     @decorators.idempotent_id('1298e537-f1f0-47a3-a1dd-8adec8168897')
     def test_group_snapshot_create_show_list_delete(self):
@@ -253,12 +252,10 @@
 class GroupSnapshotsV319Test(BaseGroupSnapshotsTest):
     """Test group snapshot with volume microversion greater than 3.18"""
 
-    _api_version = 3
-    min_microversion = '3.19'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.19'
+    volume_max_microversion = 'latest'
 
     @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
-    @decorators.skip_because(bug='1770179')
     def test_reset_group_snapshot_status(self):
         """Test resetting group snapshot status to creating/available/error"""
         # Create volume type
diff --git a/tempest/api/volume/admin/test_group_type_specs.py b/tempest/api/volume/admin/test_group_type_specs.py
index 159c6fb..181926e 100644
--- a/tempest/api/volume/admin/test_group_type_specs.py
+++ b/tempest/api/volume/admin/test_group_type_specs.py
@@ -21,9 +21,8 @@
 class GroupTypeSpecsTest(base.BaseVolumeAdminTest):
     """Test group type specs"""
 
-    _api_version = 3
-    min_microversion = '3.11'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.11'
+    volume_max_microversion = 'latest'
 
     @decorators.idempotent_id('bb4e30d0-de6e-4f4d-866c-dcc48d023b4e')
     def test_group_type_specs_create_show_update_list_delete(self):
@@ -74,10 +73,11 @@
         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)
+        delete_keys = ['key1', 'key2', 'key3']
+        for it in delete_keys:
+            self.admin_group_types_client.delete_group_type_specs_item(
+                group_type['id'], it)
+            self.assertRaises(
+                lib_exc.NotFound,
+                self.admin_group_types_client.show_group_type_specs_item,
+                group_type['id'], it)
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
index 3993020..8154682 100644
--- a/tempest/api/volume/admin/test_group_types.py
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -21,13 +21,12 @@
 class GroupTypesTest(base.BaseVolumeAdminTest):
     """Test group types"""
 
-    _api_version = 3
-    min_microversion = '3.11'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.11'
+    volume_max_microversion = 'latest'
 
     @decorators.idempotent_id('dd71e5f9-393e-4d4f-90e9-fa1b8d278864')
-    def test_group_type_create_list_update_show(self):
-        """Test create/list/update/show group type"""
+    def test_group_type_create_list_update_show_delete(self):
+        """Test create/list/update/show/delete group type"""
         name = data_utils.rand_name(self.__class__.__name__ + '-group-type')
         description = data_utils.rand_name("group-type-description")
         group_specs = {"consistent_group_snapshot_enabled": "<is> False"}
@@ -35,7 +34,8 @@
                   'description': description,
                   'group_specs': group_specs,
                   'is_public': True}
-        body = self.create_group_type(**params)
+        body = self.admin_group_types_client.create_group_type(
+            **params)['group_type']
         self.assertIn('name', body)
         err_msg = ("The created group_type %(var)s is not equal to the "
                    "requested %(var)s")
@@ -65,3 +65,9 @@
             self.assertEqual(params[key], fetched_group_type[key],
                              '%s of the fetched group_type is different '
                              'from the created group_type' % key)
+
+        self.admin_group_types_client.delete_group_type(body['id'])
+        group_list = (
+            self.admin_group_types_client.list_group_types()['group_types'])
+        group_ids = [it['id'] for it in group_list]
+        self.assertNotIn(body['id'], group_ids)
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
index e67b985..f16e4d2 100644
--- a/tempest/api/volume/admin/test_groups.py
+++ b/tempest/api/volume/admin/test_groups.py
@@ -25,9 +25,8 @@
 class GroupsTest(base.BaseVolumeAdminTest):
     """Tests of volume groups with microversion greater than 3.12"""
 
-    _api_version = 3
-    min_microversion = '3.13'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.13'
+    volume_max_microversion = 'latest'
 
     @decorators.idempotent_id('4b111d28-b73d-4908-9bd2-03dc2992e4d4')
     def test_group_create_show_list_delete(self):
@@ -156,9 +155,8 @@
 class GroupsV314Test(base.BaseVolumeAdminTest):
     """Tests of volume groups with microversion greater than 3.13"""
 
-    _api_version = 3
-    min_microversion = '3.14'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.14'
+    volume_max_microversion = 'latest'
 
     @decorators.idempotent_id('2424af8c-7851-4888-986a-794b10c3210e')
     def test_create_group_from_group(self):
@@ -194,9 +192,8 @@
 class GroupsV320Test(base.BaseVolumeAdminTest):
     """Tests of volume groups with microversion greater than 3.19"""
 
-    _api_version = 3
-    min_microversion = '3.20'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.20'
+    volume_max_microversion = 'latest'
 
     @decorators.idempotent_id('b20c696b-0cbc-49a5-8b3a-b1fb9338f45c')
     def test_reset_group_status(self):
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index a5de987..83733bd 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -10,7 +10,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
 from tempest.api.volume import base
 from tempest.common import waiters
 from tempest import config
@@ -149,4 +148,4 @@
         # assert that volumes are each created on separate hosts:
         msg = ("volumes %s were created in the same backend" % ", "
                .join(volume_hosts))
-        six.assertCountEqual(self, volume_hosts, set(volume_hosts), msg)
+        self.assertCountEqual(volume_hosts, set(volume_hosts), msg)
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 096709c..00b7f3a 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -24,9 +24,8 @@
 class UserMessagesTest(base.BaseVolumeAdminTest):
     """Test volume messages with microversion greater than 3.2"""
 
-    _api_version = 3
-    min_microversion = '3.3'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.3'
+    volume_max_microversion = 'latest'
 
     def _create_user_message(self):
         """Trigger a 'no valid host' situation to generate a message."""
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 5ab8e87..6b58189 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -87,7 +87,7 @@
         # 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.
-        self.assertDictContainsSubset(new_quota_set, quota_set)
+        self.assertLessEqual(new_quota_set.items(), quota_set.items())
 
     @decorators.idempotent_id('18c51ae9-cb03-48fc-b234-14a19374dbed')
     def test_show_quota_usage(self):
diff --git a/tempest/api/volume/admin/test_volume_retype.py b/tempest/api/volume/admin/test_volume_retype.py
index 5c14d52..4a3f494 100644
--- a/tempest/api/volume/admin/test_volume_retype.py
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -13,6 +13,7 @@
 import abc
 
 from oslo_log import log as logging
+import testtools
 
 from tempest.api.volume import base
 from tempest.common import waiters
@@ -146,6 +147,8 @@
         self._retype_volume(src_vol, migration_policy='on-demand')
 
     @decorators.idempotent_id('d0d9554f-e7a5-4104-8973-f35b27ccb60d')
+    @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+                          "Cinder volume snapshots are disabled.")
     def test_volume_from_snapshot_retype_with_migration(self):
         """Test volume created from snapshot retype with migration
 
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index ebcd3b7..98ae83b 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -121,6 +121,8 @@
     def test_volume_type_encryption_create_get_update_delete(self):
         """Test create/get/update/delete volume encryption type"""
         create_kwargs = {'provider': 'LuksEncryptor',
+                         'key_size': 256,
+                         'cipher': 'aes-xts-plain64',
                          'control_location': 'front-end'}
         volume_type_id = self.create_volume_type()['id']
 
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index 6b2a278..70a62ff 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -77,6 +77,21 @@
             extra_spec)
 
     @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('474090d2-0824-eb3b-9335-f506b4aa49d8')
+    def test_update_nonexistent_type_id(self):
+        """Test update volume type extra specs for non existent volume type
+
+        Update volume type extra specs for non existent volume type should
+        fail.
+        """
+        spec_key = "spec1"
+        extra_spec = {spec_key: "val5"}
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.admin_volume_types_client.update_volume_type_extra_specs,
+            data_utils.rand_uuid(), spec_key, extra_spec)
+
+    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('49d5472c-a53d-4eab-a4d3-450c4db1c545')
     def test_create_nonexistent_type_id(self):
         """Test creating volume type extra specs for non existent volume type
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index 174cf9e..f37c427 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -54,3 +54,28 @@
         volume_type = self.create_volume_type(**params)
         self.assertRaises(lib_exc.NotFound,
                           self.create_volume, volume_type=volume_type['id'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('a5924b5f-b6c1-49ba-994c-b4af55d26e52')
+    def test_create_volume_type_encryption_nonexistent_type_id(self):
+        """Test create encryption with nonexistent type id will fail"""
+        create_kwargs = {
+            'type_id': data_utils.rand_uuid(),
+            'provider': 'LuksEncryptor',
+            'key_size': 256,
+            'cipher': 'aes-xts-plain64',
+            'control_location': 'front-end'
+            }
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.create_encryption_type, **create_kwargs)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('969b10c7-3d77-4e1b-a4f2-2d265980f7e5')
+    def test_create_with_repeated_name(self):
+        """Test creating volume type with a repeated name will fail"""
+        volume_type_name = self.create_volume_type()['name']
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.admin_volume_types_client.create_volume_type,
+            name=volume_type_name)
diff --git a/tempest/api/volume/api_microversion_fixture.py b/tempest/api/volume/api_microversion_fixture.py
index 7bbe674..219fde8 100644
--- a/tempest/api/volume/api_microversion_fixture.py
+++ b/tempest/api/volume/api_microversion_fixture.py
@@ -12,14 +12,23 @@
 # under the License.
 
 import fixtures
+from oslo_log import log as logging
 
 from tempest.lib.services.volume import base_client
 
+LOG = logging.getLogger(__name__)
+
 
 class APIMicroversionFixture(fixtures.Fixture):
 
     def __init__(self, volume_microversion):
         self.volume_microversion = volume_microversion
+        new_fixture = (
+            'tempest.lib.common.api_microversion_fixture.'
+            'APIMicroversionFixture')
+        LOG.warning("%s class is deprecated and moved to %s. It"
+                    " will be removed in Z cycle.",
+                    self.__class__.__name__, new_fixture)
 
     def _setUp(self):
         super(APIMicroversionFixture, self)._setUp()
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index c538e60..b90b5bb 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -13,14 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.volume import api_microversion_fixture
 from tempest.common import compute
 from tempest.common import waiters
 from tempest import config
 from tempest.lib.common import api_version_utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
-from tempest.lib import exceptions
 import tempest.test
 
 CONF = config.CONF
@@ -33,12 +31,6 @@
     # Set this to True in subclasses to create a default network. See
     # https://bugs.launchpad.net/tempest/+bug/1844568
     create_default_network = False
-    _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
@@ -48,20 +40,9 @@
         if not CONF.service_available.cinder:
             skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
-        if cls._api_version == 2:
-            if not CONF.volume_feature_enabled.api_v2:
-                msg = "Volume API v2 is disabled"
-                raise cls.skipException(msg)
-        elif cls._api_version == 3:
-            if not CONF.volume_feature_enabled.api_v3:
-                msg = "Volume API v3 is disabled"
-                raise cls.skipException(msg)
-        else:
-            msg = ("Invalid Cinder API version (%s)" % cls._api_version)
-            raise exceptions.InvalidConfiguration(msg)
 
         api_version_utils.check_skip_with_microversion(
-            cls.min_microversion, cls.max_microversion,
+            cls.volume_min_microversion, cls.volume_max_microversion,
             CONF.volume.min_microversion, CONF.volume.max_microversion)
 
     @classmethod
@@ -93,18 +74,20 @@
             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()
-        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
-            self.request_microversion))
-
     @classmethod
     def resource_setup(cls):
         super(BaseVolumeTest, cls).resource_setup()
-        cls.request_microversion = (
+        cls.volume_request_microversion = (
+            api_version_utils.select_request_microversion(
+                cls.volume_min_microversion,
+                CONF.volume.min_microversion))
+        cls.compute_request_microversion = (
             api_version_utils.select_request_microversion(
                 cls.min_microversion,
-                CONF.volume.min_microversion))
+                CONF.compute.min_microversion))
+        cls.setup_api_microversion_fixture(
+            compute_microversion=cls.compute_request_microversion,
+            volume_microversion=cls.volume_request_microversion)
 
         cls.image_ref = CONF.compute.image_ref
         cls.flavor_ref = CONF.compute.flavor_ref
@@ -129,6 +112,13 @@
             name = data_utils.rand_name(cls.__name__ + '-Volume')
             kwargs['name'] = name
 
+        if CONF.volume.volume_type and 'volume_type' not in kwargs:
+            # If volume_type is not provided in config then no need to
+            # add a volume type and
+            # if volume_type has already been added by child class then
+            # no need to override.
+            kwargs['volume_type'] = CONF.volume.volume_type
+
         if CONF.compute.compute_volume_common_az:
             kwargs.setdefault('availability_zone',
                               CONF.compute.compute_volume_common_az)
@@ -166,6 +156,10 @@
 
         backup = backup_client.create_backup(
             volume_id=volume_id, **kwargs)['backup']
+        # addCleanup uses list pop to cleanup. Wait should be added before
+        # the backup is deleted
+        self.addCleanup(backup_client.wait_for_resource_deletion,
+                        backup['id'])
         self.addCleanup(backup_client.delete_backup, backup['id'])
         waiters.wait_for_volume_resource_status(backup_client, backup['id'],
                                                 'available')
diff --git a/tempest/api/volume/test_versions.py b/tempest/api/volume/test_versions.py
index e065bdf..578be58 100644
--- a/tempest/api/volume/test_versions.py
+++ b/tempest/api/volume/test_versions.py
@@ -19,8 +19,6 @@
 class VersionsTest(base.BaseVolumeTest):
     """Test volume versions"""
 
-    _api_version = 3
-
     @decorators.idempotent_id('77838fc4-b49b-4c64-9533-166762517369')
     @decorators.attr(type='smoke')
     def test_list_versions(self):
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 3eb81f5..f1dec06 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -104,3 +104,30 @@
         self.client.delete_volume_transfer(transfer_id)
         waiters.wait_for_volume_resource_status(
             self.volumes_client, volume['id'], 'available')
+
+
+class VolumesTransfersV355Test(VolumesTransfersTest):
+    """Test volume transfer for the "new" Transfers API mv 3.55"""
+
+    volume_min_microversion = '3.55'
+    volume_max_microversion = 'latest'
+
+    credentials = ['primary', 'alt', 'admin']
+
+    @classmethod
+    def setup_clients(cls):
+        super(VolumesTransfersV355Test, cls).setup_clients()
+        cls.client = cls.os_primary.volume_transfers_mv355_client_latest
+        cls.alt_client = cls.os_alt.volume_transfers_mv355_client_latest
+
+    @decorators.idempotent_id('9f36bb2b-619f-4507-b246-76aeb9a28851')
+    def test_create_get_list_accept_volume_transfer(self):
+        """Test create, get, list, accept with volume-transfers API mv 3.55"""
+        super(VolumesTransfersV355Test, self). \
+            test_create_get_list_accept_volume_transfer()
+
+    @decorators.idempotent_id('af4a5b97-0859-4f31-aa3c-85b05bb63322')
+    def test_create_list_delete_volume_transfer(self):
+        """Test create, list, delete with volume-transfers API mv 3.55"""
+        super(VolumesTransfersV355Test, self). \
+            test_create_list_delete_volume_transfer()
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 2e78114..138d120 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -164,9 +164,8 @@
 class VolumesBackupsV39Test(base.BaseVolumeTest):
     """Test volumes backup with volume microversion greater than 3.8"""
 
-    _api_version = 3
-    min_microversion = '3.9'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.9'
+    volume_max_microversion = 'latest'
 
     @classmethod
     def skip_checks(cls):
diff --git a/tempest/api/volume/test_volumes_clone.py b/tempest/api/volume/test_volumes_clone.py
index eb54426..9ca1c5e 100644
--- a/tempest/api/volume/test_volumes_clone.py
+++ b/tempest/api/volume/test_volumes_clone.py
@@ -49,13 +49,14 @@
         # Creates a volume from another volume passing a size different from
         # the source volume.
         src_size = CONF.volume.volume_size
+        extend_size = CONF.volume.volume_size_extend
 
         src_vol = self.create_volume(size=src_size)
         # Destination volume bigger than source
         dst_vol = self.create_volume(source_volid=src_vol['id'],
-                                     size=src_size + 1)
+                                     size=src_size + extend_size)
 
-        self._verify_volume_clone(src_vol, dst_vol, extra_size=1)
+        self._verify_volume_clone(src_vol, dst_vol, extra_size=extend_size)
 
     @decorators.idempotent_id('cbbcd7c6-5a6c-481a-97ac-ca55ab715d16')
     @utils.services('image')
diff --git a/tempest/api/volume/test_volumes_clone_negative.py b/tempest/api/volume/test_volumes_clone_negative.py
index 4bfb166..115465c 100644
--- a/tempest/api/volume/test_volumes_clone_negative.py
+++ b/tempest/api/volume/test_volumes_clone_negative.py
@@ -36,11 +36,11 @@
         """Test cloning a volume with decreasing size will fail"""
         # Creates a volume from another volume passing a size different from
         # the source volume.
-        src_size = CONF.volume.volume_size + 1
+        src_size = CONF.volume.volume_size + CONF.volume.volume_size_extend
         src_vol = self.create_volume(size=src_size)
 
         # Destination volume smaller than source
         self.assertRaises(exceptions.BadRequest,
                           self.volumes_client.create_volume,
-                          size=src_size - 1,
+                          size=src_size - CONF.volume.volume_size_extend,
                           source_volid=src_vol['id'])
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 7441f1d..fcbc982 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -77,11 +77,10 @@
     # details once that microversion is available in Nova.
     credentials = ['primary', 'admin']
 
-    _api_version = 3
     # NOTE(mriedem): The minimum required volume API version is 3.42 and the
     # minimum required compute API microversion is 2.51, but the compute call
     # is implicit - Cinder calls Nova at that microversion, Tempest does not.
-    min_microversion = '3.42'
+    volume_min_microversion = '3.42'
 
     def _find_extend_volume_instance_action(self, server_id):
         actions = self.servers_client.list_instance_actions(
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 91728ab..2009970 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -143,9 +143,8 @@
 class VolumesSummaryTest(base.BaseVolumeTest):
     """Test volume summary"""
 
-    _api_version = 3
-    min_microversion = '3.12'
-    max_microversion = 'latest'
+    volume_min_microversion = '3.12'
+    volume_max_microversion = 'latest'
 
     @decorators.idempotent_id('c4f2431e-4920-4736-9e00-4040386b6feb')
     def test_show_volume_summary(self):
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 60f85a4..1d1981c 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -16,8 +16,8 @@
 
 import operator
 import random
+from urllib.parse import urlparse
 
-from six.moves.urllib.parse import urlparse
 from testtools import matchers
 
 from tempest.api.volume import base
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 389d3be..d9b8430 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
+import io
 
 from tempest.api.volume import base
 from tempest.common import utils
@@ -45,12 +45,12 @@
             container_format=CONF.image.container_formats[0],
             disk_format=CONF.image.disk_formats[0],
             visibility='private',
-            min_disk=CONF.volume.volume_size + 1)
+            min_disk=CONF.volume.volume_size + CONF.volume.volume_size_extend)
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.images_client.delete_image, image['id'])
 
         # Upload image with 1KB data
-        image_file = six.BytesIO(data_utils.random_bytes())
+        image_file = io.BytesIO(data_utils.random_bytes())
         self.images_client.store_image_file(image['id'], image_file)
         waiters.wait_for_image_status(self.images_client,
                                       image['id'], 'active')
@@ -223,7 +223,7 @@
     @decorators.idempotent_id('8f05a943-013c-4063-ac71-7baf561e82eb')
     def test_volume_extend_with_nonexistent_volume_id(self):
         """Test extending non existent volume should fail"""
-        extend_size = self.volume['size'] + 1
+        extend_size = self.volume['size'] + CONF.volume.volume_size_extend
         self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
                           data_utils.rand_uuid(), new_size=extend_size)
 
@@ -231,7 +231,7 @@
     @decorators.idempotent_id('aff8ba64-6d6f-4f2e-bc33-41a08ee9f115')
     def test_volume_extend_without_passing_volume_id(self):
         """Test extending volume without passing volume id should fail"""
-        extend_size = self.volume['size'] + 1
+        extend_size = self.volume['size'] + CONF.volume.volume_size_extend
         self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
                           None, new_size=extend_size)
 
@@ -336,6 +336,9 @@
 
         # Deactivate the image
         self.images_client.deactivate_image(image['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.images_client.reactivate_image, image['id'])
+
         body = self.images_client.show_image(image['id'])
         self.assertEqual("deactivated", body['status'])
         # Try creating a volume from deactivated image
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index fd2e7c4..a58da7e 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -167,7 +167,8 @@
     @decorators.idempotent_id('677863d1-3142-456d-b6ac-9924f667a7f4')
     def test_volume_from_snapshot(self):
         """Test creating volume from snapshot with extending size"""
-        self._create_volume_from_snapshot(extra_size=1)
+        self._create_volume_from_snapshot(
+            extra_size=CONF.volume.volume_size_extend)
 
     @decorators.idempotent_id('053d8870-8282-4fff-9dbb-99cb58bb5e0a')
     def test_volume_from_snapshot_no_size(self):
diff --git a/tempest/clients.py b/tempest/clients.py
index 6d19a0c..4c3d875 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -59,6 +59,8 @@
         self.ports_client = self.network.PortsClient()
         self.network_quotas_client = self.network.QuotasClient()
         self.floating_ips_client = self.network.FloatingIPsClient()
+        self.floating_ips_port_forwarding_client =\
+            self.network.FloatingIpsPortForwardingClient()
         self.metering_labels_client = self.network.MeteringLabelsClient()
         self.metering_label_rules_client = (
             self.network.MeteringLabelRulesClient())
@@ -71,7 +73,13 @@
         self.tags_client = self.network.TagsClient()
         self.qos_client = self.network.QosClient()
         self.qos_min_bw_client = self.network.QosMinimumBandwidthRulesClient()
+        self.qos_limit_bw_client = self.network.QosLimitBandwidthRulesClient()
+        self.qos_min_pps_client = (
+            self.network.QosMinimumPacketRateRulesClient())
         self.segments_client = self.network.SegmentsClient()
+        self.trunks_client = self.network.TrunksClient()
+        self.log_resource_client = self.network.LogResourceClient()
+        self.loggable_resource_client = self.network.LoggableResourceClient()
 
     def _set_image_clients(self):
         if CONF.service_available.glance:
@@ -88,6 +96,14 @@
                 self.image_v2.NamespacePropertiesClient()
             self.namespace_tags_client = self.image_v2.NamespaceTagsClient()
             self.image_versions_client = self.image_v2.VersionsClient()
+            # NOTE(danms): If no alternate endpoint is configured,
+            # this client will work the same as the base self.images_client.
+            # If your test needs to know if these are different, check the
+            # config option to see if the alternate_image_endpoint is set.
+            self.image_client_remote = self.image_v2.ImagesClient(
+                service=CONF.image.alternate_image_endpoint,
+                endpoint_type=CONF.image.alternate_image_endpoint_type,
+                region=CONF.image.region)
 
     def _set_compute_clients(self):
         self.agents_client = self.compute.AgentsClient()
@@ -102,7 +118,8 @@
         self.server_groups_client = self.compute.ServerGroupsClient()
         self.limits_client = self.compute.LimitsClient()
         self.compute_images_client = self.compute.ImagesClient()
-        self.keypairs_client = self.compute.KeyPairsClient()
+        self.keypairs_client = self.compute.KeyPairsClient(
+            ssh_key_type=CONF.validation.ssh_key_type)
         self.quotas_client = self.compute.QuotasClient()
         self.quota_classes_client = self.compute.QuotaClassesClient()
         self.flavors_client = self.compute.FlavorsClient()
@@ -125,6 +142,8 @@
         self.instance_usages_audit_log_client = (
             self.compute.InstanceUsagesAuditLogClient())
         self.tenant_networks_client = self.compute.TenantNetworksClient()
+        self.assisted_volume_snapshots_client = (
+            self.compute.AssistedVolumeSnapshotsClient())
 
         # NOTE: The following client needs special timeout values because
         # the API is a proxy for the other component.
@@ -210,6 +229,8 @@
             self.identity_v3.ApplicationCredentialsClient(**params_v3)
         self.access_rules_client = \
             self.identity_v3.AccessRulesClient(**params_v3)
+        self.identity_limits_client = \
+            self.identity_v3.LimitsClient(**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
@@ -231,89 +252,87 @@
 
     def _set_volume_clients(self):
 
-        # 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())
-            self.attachments_client_latest = (
-                self.volume_v3.AttachmentsClient())
+        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_transfers_mv355_client_latest = \
+            self.volume_v3.TransfersV355Client()
+        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())
+        self.attachments_client_latest = (
+            self.volume_v3.AttachmentsClient())
 
-            # 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_v3.EncryptionTypesClient()
-            self.snapshot_manage_v2_client = \
-                self.volume_v3.SnapshotManageClient()
-            self.snapshots_v2_client = self.volume_v3.SnapshotsClient()
-            self.volume_capabilities_v2_client = \
-                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_v3.QuotaClassesClient()
-            self.volume_scheduler_stats_v2_client = \
-                self.volume_v3.SchedulerStatsClient()
-            self.volume_transfers_v2_client = self.volume_v3.TransfersClient()
-            self.volume_v2_availability_zone_client = \
-                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_v3.ExtensionsClient()
+        # 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_v3.EncryptionTypesClient()
+        self.snapshot_manage_v2_client = \
+            self.volume_v3.SnapshotManageClient()
+        self.snapshots_v2_client = self.volume_v3.SnapshotsClient()
+        self.volume_capabilities_v2_client = \
+            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_v3.QuotaClassesClient()
+        self.volume_scheduler_stats_v2_client = \
+            self.volume_v3.SchedulerStatsClient()
+        self.volume_transfers_v2_client = self.volume_v3.TransfersClient()
+        self.volume_v2_availability_zone_client = \
+            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_v3.ExtensionsClient()
 
-        if CONF.volume_feature_enabled.api_v3:
-            self.backups_v3_client = self.volume_v3.BackupsClient()
-            self.group_types_v3_client = self.volume_v3.GroupTypesClient()
-            self.groups_v3_client = self.volume_v3.GroupsClient()
-            self.group_snapshots_v3_client = \
-                self.volume_v3.GroupSnapshotsClient()
-            self.snapshots_v3_client = self.volume_v3.SnapshotsClient()
-            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()
-            # ****************Deprecated alias end here***********************
+        self.backups_v3_client = self.volume_v3.BackupsClient()
+        self.group_types_v3_client = self.volume_v3.GroupTypesClient()
+        self.groups_v3_client = self.volume_v3.GroupsClient()
+        self.group_snapshots_v3_client = \
+            self.volume_v3.GroupSnapshotsClient()
+        self.snapshots_v3_client = self.volume_v3.SnapshotsClient()
+        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()
+        # ****************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 ff552a1..ad0b547 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -81,11 +81,11 @@
   will have the prefix with the given TAG in its name. Using tag is recommended
   for the further using, cleaning resources.
 
-* ``-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.
+* ``-r, --concurrency CONCURRENCY`` (Optional) Concurrency count (default: 2).
+  The number of accounts generated will be same as CONCURRENCY. The higher the
+  number, the more tests will run in parallel. If you want to run tests
+  sequentially then use 1 as value for concurrency (beware that tests that need
+  more credentials will fail).
 
 * ``--with-admin`` (Optional) Creates admin for each concurrent group
   (default: False).
@@ -236,7 +236,7 @@
                         dest='tag',
                         help='Resources tag')
     parser.add_argument('-r', '--concurrency',
-                        default=1,
+                        default=2,
                         type=positive_int,
                         required=False,
                         dest='concurrency',
@@ -270,7 +270,7 @@
                 config.CONF.set_config_path(parsed_args.config_file)
             setup_logging()
             resources = []
-            for count in range(parsed_args.concurrency):
+            for _ in range(parsed_args.concurrency):
                 # Use N different cred_providers to obtain different
                 # sets of creds
                 cred_provider = get_credential_provider(parsed_args)
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 84d2492..f2370f3 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -12,8 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_log import log as logging
-from six.moves.urllib import parse as urllib
 
 from tempest import clients
 from tempest.common import credentials_factory as credentials
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 6e93d69..3eae552 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -12,6 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import configparser
 import os
 import shutil
 import sys
@@ -19,7 +20,6 @@
 from cliff import command
 from oslo_config import generator
 from oslo_log import log as logging
-from six import moves
 from stestr import commands
 
 from tempest.cmd import workspace
@@ -92,7 +92,7 @@
             stestr_conf_file.write(stestr_conf)
 
     def get_configparser(self, conf_path):
-        config_parse = moves.configparser.ConfigParser()
+        config_parse = configparser.ConfigParser()
         config_parse.optionxform = str
         # get any existing values if a config file already exists
         if os.path.isfile(conf_path):
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index d82b6df..2669ff7 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -22,10 +22,10 @@
 * ``--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
+* ``--exclude-regex``: It allows to do simple test exclusion via passing a
+  rejection/exclude regexp
 
-There are also the ``--blacklist-file`` and ``--whitelist-file`` options that
+There are also the ``--exclude-list`` and ``--include-list`` 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::
@@ -128,8 +128,8 @@
 import sys
 
 from cliff import command
+from oslo_log import log
 from oslo_serialization import jsonutils as json
-import six
 from stestr import commands
 
 from tempest import clients
@@ -139,13 +139,11 @@
 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"
 
+LOG = log.getLogger(__name__)
+
 
 class TempestRun(command.Command):
 
@@ -167,7 +165,7 @@
         # 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:
+        if 'PYTHON' not in os.environ:
             os.environ['PYTHON'] = sys.executable
 
     def _create_stestr_conf(self):
@@ -206,23 +204,71 @@
             self._init_state()
 
         regex = self._build_regex(parsed_args)
+
+        # temporary method for parsing deprecated and new stestr options
+        # and showing warning messages in order to make the transition
+        # smoother for all tempest consumers
+        # TODO(kopecmartin) remove this after stestr>=3.1.0 is used
+        # in all supported OpenStack releases
+        def parse_dep(old_o, old_v, new_o, new_v):
+            ret = ''
+            if old_v:
+                LOG.warning("'%s' option is deprecated, use '%s' instead "
+                            "which is functionally equivalent. Right now "
+                            "Tempest still supports this option for "
+                            "backward compatibility, however, it will be "
+                            "removed soon.",
+                            old_o, new_o)
+                ret = old_v
+            if old_v and new_v:
+                # both options are specified
+                LOG.warning("'%s' and '%s' are specified at the same time, "
+                            "'%s' takes precedence over '%s'",
+                            new_o, old_o, new_o, old_o)
+            if new_v:
+                ret = new_v
+            return ret
+        ex_regex = parse_dep('--black-regex', parsed_args.black_regex,
+                             '--exclude-regex', parsed_args.exclude_regex)
+        ex_list = parse_dep('--blacklist-file', parsed_args.blacklist_file,
+                            '--exclude-list', parsed_args.exclude_list)
+        in_list = parse_dep('--whitelist-file', parsed_args.whitelist_file,
+                            '--include-list', parsed_args.include_list)
+
         return_code = 0
         if parsed_args.list_tests:
-            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)
+            try:
+                return_code = commands.list_command(
+                    filters=regex, include_list=in_list,
+                    exclude_list=ex_list, exclude_regex=ex_regex)
+            except TypeError:
+                # exclude_list, include_list and exclude_regex are defined only
+                # in stestr >= 3.1.0, this except block catches the case when
+                # tempest is executed with an older stestr
+                return_code = commands.list_command(
+                    filters=regex, whitelist_file=in_list,
+                    blacklist_file=ex_list, black_regex=ex_regex)
 
         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,
-                worker_path=parsed_args.worker_file,
-                load_list=parsed_args.load_list, combine=parsed_args.combine)
+            params = {
+                'filters': regex, 'subunit_out': parsed_args.subunit,
+                'serial': serial, 'concurrency': parsed_args.concurrency,
+                'worker_path': parsed_args.worker_file,
+                'load_list': parsed_args.load_list,
+                'combine': parsed_args.combine
+            }
+            try:
+                return_code = commands.run_command(
+                    **params, exclude_list=ex_list,
+                    include_list=in_list, exclude_regex=ex_regex)
+            except TypeError:
+                # exclude_list, include_list and exclude_regex are defined only
+                # in stestr >= 3.1.0, this except block catches the case when
+                # tempest is executed with an older stestr
+                return_code = commands.run_command(
+                    **params, blacklist_file=ex_list,
+                    whitelist_file=in_list, black_regex=ex_regex)
             if return_code > 0:
                 sys.exit(return_code)
         return return_code
@@ -276,15 +322,38 @@
                            help='A normal stestr selection regex used to '
                                 'specify a subset of tests to run')
         parser.add_argument('--black-regex', dest='black_regex',
+                            help='DEPRECATED: This option is deprecated and '
+                                 'will be removed soon, use --exclude-regex '
+                                 'which is functionally equivalent. If this '
+                                 'is specified at the same time as '
+                                 '--exclude-regex, this flag will be ignored '
+                                 'and --exclude-regex will be used')
+        parser.add_argument('--exclude-regex', dest='exclude_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.")
+                            help='DEPRECATED: This option is deprecated and '
+                                 'will be removed soon, use --include-list '
+                                 'which is functionally equivalent. If this '
+                                 'is specified at the same time as '
+                                 '--include-list, this flag will be ignored '
+                                 'and --include-list will be used')
+        parser.add_argument('--include-list', '--include_list',
+                            help="Path to an include file which contains the "
+                                 "regex for tests to be included in tempest "
+                                 "run, 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')
+                            help='DEPRECATED: This option is deprecated and '
+                                 'will be removed soon, use --exclude-list '
+                                 'which is functionally equivalent. If this '
+                                 'is specified at the same time as '
+                                 '--exclude-list, this flag will be ignored '
+                                 'and --exclude-list will be used')
+        parser.add_argument('--exclude-list', '--exclude_list',
+                            help='Path to an exclude file which contains the '
+                                 'regex for tests to be excluded in tempest '
+                                 'run, this file contains a separate regex on '
+                                 'each newline.')
         parser.add_argument('--load-list', '--load_list',
                             help='Path to a non-regex whitelist file, '
                                  'this file contains a separate test '
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index 172fbaa..6c36d82 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -40,12 +40,14 @@
 
 subunit-describe-calls will take in either stdin subunit v1 or v2 stream or a
 file path which contains either a subunit v1 or v2 stream passed via the
---subunit parameter. This is then parsed checking for details contained in the
-file_bytes of the --non-subunit-name parameter (the default is pythonlogging
-which is what Tempest uses to store logs). By default the OpenStack Kilo
-release port defaults (http://bit.ly/22jpF5P) are used unless a file is
-provided via the --ports option. The resulting output is dumped in JSON output
-to the path provided in the --output-file option.
+``--subunit`` parameter. This is then parsed checking for details contained in
+the file_bytes of the ``--non-subunit-name`` parameter (the default is
+pythonlogging which is what Tempest uses to store logs). By default `the
+OpenStack default ports
+<https://docs.openstack.org/install-guide/firewalls-default-ports.html>`_
+are used unless a file is provided via the ``--ports`` option. The resulting
+output is dumped in JSON output to the path provided in the ``--output-file``
+option.
 
 Ports file JSON structure
 ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -110,9 +112,8 @@
     response_re = re.compile(r'.* Response - Headers: (?P<headers>.*)')
     body_re = re.compile(r'.*Body: (?P<body>.*)')
 
-    # Based on newton defaults:
-    # http://docs.openstack.org/newton/config-reference/
-    # firewalls-default-ports.html
+    # Based on OpenStack default ports:
+    # https://docs.openstack.org/install-guide/firewalls-default-ports.html
     services = {
         "8776": "Block Storage",
         "8774": "Nova",
@@ -131,7 +132,10 @@
         "3260": "iSCSI",
         "3306": "MySQL",
         "5672": "AMQP",
-        "8082": "murano"}
+        "8082": "murano",
+        "8778": "Clustering",
+        "8999": "Vitrage",
+        "8989": "Mistral"}
 
     def __init__(self, services=None):
         super(UrlParser, self).__init__()
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 235d8e3..0db1ab1 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -202,22 +202,8 @@
                             not CONF.identity_feature_enabled.api_v3, update)
 
 
-def verify_cinder_api_versions(os, update):
-    # Check cinder api versions
-    versions = _get_api_versions(os, 'cinder')
-    if (CONF.volume_feature_enabled.api_v2 !=
-            contains_version('v2.', versions)):
-        print_and_or_update('api_v2', 'volume-feature-enabled',
-                            not CONF.volume_feature_enabled.api_v2, update)
-    if (CONF.volume_feature_enabled.api_v3 !=
-            contains_version('v3.', versions)):
-        print_and_or_update('api_v3', 'volume-feature-enabled',
-                            not CONF.volume_feature_enabled.api_v3, update)
-
-
 def verify_api_versions(os, service, update):
     verify = {
-        'cinder': verify_cinder_api_versions,
         'glance': verify_glance_api_versions,
         'keystone': verify_keystone_api_versions,
     }
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index edb9d16..2443a67 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -18,9 +18,7 @@
 import ssl
 import struct
 import textwrap
-
-import six
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
 
 from oslo_log import log as logging
 from oslo_utils import excutils
@@ -31,11 +29,6 @@
 from tempest.lib.common import rest_client
 from tempest.lib.common.utils import data_utils
 
-if six.PY2:
-    ord_func = ord
-else:
-    ord_func = int
-
 CONF = config.CONF
 
 LOG = logging.getLogger(__name__)
@@ -64,7 +57,7 @@
 def create_test_server(clients, validatable=False, validation_resources=None,
                        tenant_network=None, wait_until=None,
                        volume_backed=False, name=None, flavor=None,
-                       image_id=None, **kwargs):
+                       image_id=None, wait_for_sshable=True, **kwargs):
     """Common wrapper utility returning a test server.
 
     This method is a common wrapper returning a test server that can be
@@ -100,6 +93,8 @@
         CONF.compute.flavor_ref will be used instead.
     :param image_id: ID of the image to be used to provision the server. If not
         defined, CONF.compute.image_ref will be used instead.
+    :param wait_for_sshable: Check server's console log and wait until it will
+        be ready to login.
     :returns: a tuple
     """
 
@@ -202,6 +197,7 @@
     body = clients.servers_client.create_server(name=name, imageRef=image_id,
                                                 flavorRef=flavor,
                                                 **kwargs)
+    request_id = body.response['x-openstack-request-id']
 
     # handle the case of multiple servers
     if multiple_create_request:
@@ -239,7 +235,8 @@
         for server in servers:
             try:
                 waiters.wait_for_server_status(
-                    clients.servers_client, server['id'], wait_until)
+                    clients.servers_client, server['id'], wait_until,
+                    request_id=request_id)
 
                 # Multiple validatable servers are not supported for now. Their
                 # creation will fail with the condition above.
@@ -270,6 +267,10 @@
                             LOG.exception('Server %s failed to delete in time',
                                           server['id'])
 
+    if (validatable and CONF.compute_feature_enabled.console_output and
+            wait_for_sshable):
+        waiters.wait_for_guest_os_boot(clients.servers_client, server['id'])
+
     return body, servers
 
 
@@ -365,8 +366,8 @@
             # frames less than 125 bytes here (for the negotiation) and
             # that only the 2nd byte contains the length, and since the
             # server doesn't do masking, we can just read the data length
-            if ord_func(header[1]) & 127 > 0:
-                return self._recv(ord_func(header[1]) & 127)
+            if int(header[1]) & 127 > 0:
+                return self._recv(int(header[1]) & 127)
 
     def send_frame(self, data):
         """Wrapper for sending data to add in the WebSocket frame format."""
@@ -383,7 +384,7 @@
             frame_bytes.append(mask[i])
         # Mask each of the actual data bytes that we are going to send
         for i in range(len(data)):
-            frame_bytes.append(ord_func(data[i]) ^ mask[i % 4])
+            frame_bytes.append(int(data[i]) ^ mask[i % 4])
         # Convert our integer list to a binary array of bytes
         frame_bytes = struct.pack('!%iB' % len(frame_bytes), * frame_bytes)
         self._socket.sendall(frame_bytes)
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index c6e5dcb..2d486a7 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -245,6 +245,9 @@
 
     if identity_version == 'v3':
         conf_attributes.append('domain_name')
+        conf_attributes.append('user_domain_name')
+        conf_attributes.append('project_domain_name')
+        conf_attributes.append('system')
     # Read the parts of credentials from config
     params = config.service_client_config()
     for attr in conf_attributes:
@@ -284,7 +287,8 @@
     if identity_version == 'v3':
         domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
                             if 'domain' in x)
-        if not domain_fields.intersection(kwargs.keys()):
+        if (not params.get('system') and
+                not domain_fields.intersection(kwargs.keys())):
             domain_name = CONF.auth.default_credentials_domain_name
             # NOTE(andreaf) Setting domain_name implicitly sets user and
             # project domain names, if they are None
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 914acf7..88a16b7 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -13,37 +13,17 @@
 #    under the License.
 
 import functools
-from functools import partial
 
 import testtools
 
 from tempest import config
 from tempest.exceptions import InvalidServiceTag
-from tempest.lib.common.utils import data_utils as lib_data_utils
 from tempest.lib import decorators
 
 
 CONF = config.CONF
 
 
-class DataUtils(object):
-    def __getattr__(self, attr):
-
-        if attr == 'rand_name':
-            # NOTE(flwang): This is a proxy to generate a random name that
-            # includes a random number and a prefix 'tempest'
-            attr_obj = partial(lib_data_utils.rand_name,
-                               prefix='tempest')
-        else:
-            attr_obj = getattr(lib_data_utils, attr)
-
-        self.__dict__[attr] = attr_obj
-        return attr_obj
-
-
-data_utils = DataUtils()
-
-
 def get_service_list():
     service_list = {
         'compute': CONF.service_available.nova,
@@ -59,6 +39,7 @@
         # So we should set this True here.
         'identity': True,
         'object_storage': CONF.service_available.swift,
+        'dashboard': CONF.service_available.horizon,
     }
     return service_list
 
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index b68a879..9d9fab7 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -48,7 +48,8 @@
             console_output_enabled=CONF.compute_feature_enabled.console_output,
             ssh_shell_prologue=CONF.validation.ssh_shell_prologue,
             ping_count=CONF.validation.ping_count,
-            ping_size=CONF.validation.ping_size)
+            ping_size=CONF.validation.ping_size,
+            ssh_key_type=CONF.validation.ssh_key_type)
 
     # Note that this method will not work on SLES11 guests, as they do
     # not support the TYPE column on lsblk
@@ -108,7 +109,7 @@
         LOG.debug('(get_nic_name_by_ip) Command result: %s', nic)
         return nic.strip().strip(":").split('@')[0].lower()
 
-    def get_dns_servers(self):
+    def _get_dns_servers(self):
         cmd = 'cat /etc/resolv.conf'
         resolve_file = self.exec_command(cmd).strip().split('\n')
         entries = (l.split() for l in resolve_file)
@@ -116,6 +117,19 @@
                        if len(l) and l[0] == 'nameserver']
         return dns_servers
 
+    def get_dns_servers(self, timeout=5):
+        start_time = int(time.time())
+        dns_servers = []
+        while True:
+            dns_servers = self._get_dns_servers()
+            if dns_servers:
+                break
+            LOG.debug("DNS Servers list empty.")
+            if int(time.time()) - start_time >= timeout:
+                LOG.debug("DNS Servers list empty after %s.", timeout)
+                break
+        return dns_servers
+
     def _renew_lease_udhcpc(self, fixed_ip=None):
         """Renews DHCP lease via udhcpc client. """
         file_path = '/var/run/udhcpc.'
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index d84b5fe..21d0109 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -32,7 +32,8 @@
 
 # NOTE(afazekas): This function needs to know a token and a subject.
 def wait_for_server_status(client, server_id, status, ready_wait=True,
-                           extra_timeout=0, raise_on_error=True):
+                           extra_timeout=0, raise_on_error=True,
+                           request_id=None):
     """Waits for a server to reach a given status."""
 
     # NOTE(afazekas): UNKNOWN status possible on ERROR
@@ -71,11 +72,12 @@
                      '/'.join((server_status, str(task_state))),
                      time.time() - start_time)
         if (server_status == 'ERROR') and raise_on_error:
+            details = ''
             if 'fault' in body:
-                raise exceptions.BuildErrorException(body['fault'],
-                                                     server_id=server_id)
-            else:
-                raise exceptions.BuildErrorException(server_id=server_id)
+                details += 'Fault: %s.' % body['fault']
+            if request_id:
+                details += ' Server boot request ID: %s.' % request_id
+            raise exceptions.BuildErrorException(details, server_id=server_id)
 
         timed_out = int(time.time()) - start_time >= timeout
 
@@ -88,6 +90,8 @@
                         'status': status,
                         'expected_task_state': expected_task_state,
                         'timeout': timeout})
+            if request_id:
+                message += ' Server boot request ID: %s.' % request_id
             message += ' Current status: %s.' % server_status
             message += ' Current task state: %s.' % task_state
             caller = test_utils.find_test_caller()
@@ -193,22 +197,85 @@
     raise lib_exc.TimeoutException(message)
 
 
-def wait_for_image_imported_to_stores(client, image_id, stores):
+def wait_for_image_tasks_status(client, image_id, status):
+    """Waits for an image tasks to reach a given status."""
+    pending_tasks = []
+    start = int(time.time())
+    while int(time.time()) - start < client.build_timeout:
+        tasks = client.show_image_tasks(image_id)['tasks']
+
+        pending_tasks = [task for task in tasks if task['status'] != status]
+        if not pending_tasks:
+            return tasks
+        time.sleep(client.build_interval)
+
+    message = ('Image %(image_id)s tasks: %(pending_tasks)s '
+               'failed to reach %(status)s state within the required '
+               'time (%(timeout)s s).' % {'image_id': image_id,
+                                          'pending_tasks': pending_tasks,
+                                          'status': status,
+                                          'timeout': client.build_timeout})
+    caller = test_utils.find_test_caller()
+    if caller:
+        message = '(%s) %s' % (caller, message)
+    raise lib_exc.TimeoutException(message)
+
+
+def wait_for_image_imported_to_stores(client, image_id, stores=None):
     """Waits for an image to be imported to all requested stores.
 
+    Short circuits to fail if the serer reports failure of any store.
+    If stores is None, just wait for status==active.
+
     The client should also have build_interval and build_timeout attributes.
     """
 
+    exc_cls = lib_exc.TimeoutException
     start = int(time.time())
     while int(time.time()) - start < client.build_timeout:
         image = client.show_image(image_id)
-        if image['status'] == 'active' and image['stores'] == stores:
+        if image['status'] == 'active' and (stores is None or
+                                            image['stores'] == stores):
             return
+        if image.get('os_glance_failed_import'):
+            exc_cls = lib_exc.OtherRestClientException
+            break
 
         time.sleep(client.build_interval)
 
-    message = ('Image %(image_id)s failed to import '
-               'on stores: %s' % str(image['os_glance_failed_import']))
+    message = ('Image %s failed to import on stores: %s' %
+               (image_id, str(image.get('os_glance_failed_import'))))
+    caller = test_utils.find_test_caller()
+    if caller:
+        message = '(%s) %s' % (caller, message)
+    raise exc_cls(message)
+
+
+def wait_for_image_copied_to_stores(client, image_id):
+    """Waits for an image to be copied on all requested stores.
+
+    The client should also have build_interval and build_timeout attributes.
+    This return the list of stores where copy is failed.
+    """
+
+    start = int(time.time())
+    store_left = []
+    while int(time.time()) - start < client.build_timeout:
+        image = client.show_image(image_id)
+        store_left = image.get('os_glance_importing_to_stores')
+        # NOTE(danms): If os_glance_importing_to_stores is None, then
+        # we've raced with the startup of the task and should continue
+        # to wait.
+        if store_left is not None and not store_left:
+            return image['os_glance_failed_import']
+        if image['status'].lower() == 'killed':
+            raise exceptions.ImageKilledException(image_id=image_id,
+                                                  status=image['status'])
+
+        time.sleep(client.build_interval)
+
+    message = ('Image %s failed to finish the copy operation '
+               'on stores: %s' % (image_id, str(store_left)))
     caller = test_utils.find_test_caller()
     if caller:
         message = '(%s) %s' % (caller, message)
@@ -286,6 +353,45 @@
              'seconds', attachment_id, volume_id, time.time() - start)
 
 
+def wait_for_volume_attachment_remove_from_server(
+        client, server_id, volume_id):
+    """Waits for a volume to be removed from a given server.
+
+    This waiter checks the compute API if the volume attachment is removed.
+    """
+    start = int(time.time())
+
+    try:
+        volumes = client.list_volume_attachments(
+            server_id)['volumeAttachments']
+    except lib_exc.NotFound:
+        # Ignore 404s on detach in case the server is deleted or the volume
+        # is already detached.
+        return
+
+    while any(volume for volume in volumes if volume['volumeId'] == volume_id):
+        time.sleep(client.build_interval)
+
+        timed_out = int(time.time()) - start >= client.build_timeout
+        if timed_out:
+            console_output = client.get_console_output(server_id)['output']
+            LOG.debug('Console output for %s\nbody=\n%s',
+                      server_id, console_output)
+            message = ('Volume %s failed to detach from server %s within '
+                       'the required time (%s s) from the compute API '
+                       'perspective' %
+                       (volume_id, server_id, client.build_timeout))
+            raise lib_exc.TimeoutException(message)
+        try:
+            volumes = client.list_volume_attachments(
+                server_id)['volumeAttachments']
+        except lib_exc.NotFound:
+            # Ignore 404s on detach in case the server is deleted or the volume
+            # is already detached.
+            return
+    return
+
+
 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']
@@ -387,18 +493,34 @@
     return body
 
 
-def wait_for_interface_detach(client, server_id, port_id):
+def wait_for_interface_detach(client, server_id, port_id, detach_request_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]
+    def _get_detach_event_results():
+        # NOTE(gibi): The obvious choice for this waiter would be to wait
+        # until the interface disappears from the client.list_interfaces()
+        # response. However that response is based on the binding status of the
+        # port in Neutron. Nova deallocates the port resources _after the port
+        # was  unbound in Neutron. This can cause that the naive waiter would
+        # return before the port is fully deallocated. Wait instead of the
+        # os-instance-action to succeed as that is recorded after both the
+        # port is fully deallocated.
+        events = client.show_instance_action(
+            server_id, detach_request_id)['instanceAction'].get('events', [])
+        return [
+            event['result'] for event in events
+            if event['event'] == 'compute_detach_interface'
+        ]
+
+    detach_event_results = _get_detach_event_results()
+
     start = int(time.time())
 
-    while port_id in ports:
+    while "Success" not in detach_event_results:
         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
+        detach_event_results = _get_detach_event_results()
+        if "Success" in detach_event_results:
+            return client.show_instance_action(
+                server_id, detach_request_id)['instanceAction']
 
         timed_out = int(time.time()) - start >= client.build_timeout
         if timed_out:
@@ -406,3 +528,62 @@
                        'the required time (%s s)' % (port_id, server_id,
                                                      client.build_timeout))
             raise lib_exc.TimeoutException(message)
+
+
+def wait_for_guest_os_boot(client, server_id):
+    start_time = int(time.time())
+    while True:
+        console_output = client.get_console_output(server_id)['output']
+        for line in console_output.split('\n'):
+            if 'login:' in line.lower():
+                return
+        if int(time.time()) - start_time >= client.build_timeout:
+            LOG.info("Guest OS on server %s probably isn't ready or its "
+                     "console log can't be parsed properly. If guest OS "
+                     "isn't ready, that may cause problems with SSH to "
+                     "the server.",
+                     server_id)
+            return
+        time.sleep(client.build_interval)
+
+
+def wait_for_server_floating_ip(servers_client, server, floating_ip,
+                                wait_for_disassociate=False):
+    """Wait for floating IP association or disassociation.
+
+    :param servers_client: The servers client to use when querying the server's
+    floating IPs.
+    :param server: The server JSON dict on which to wait.
+    :param floating_ip: The floating IP JSON dict on which to wait.
+    :param wait_for_disassociate: Boolean indiating whether to wait for
+    disassociation instead of association.
+    """
+
+    def _get_floating_ip_in_server_addresses(floating_ip, server):
+        for addresses in server['addresses'].values():
+            for address in addresses:
+                if (
+                    address['OS-EXT-IPS:type'] == 'floating' and
+                    address['addr'] == floating_ip['floating_ip_address']
+                ):
+                    return address
+        return None
+
+    start_time = int(time.time())
+    while True:
+        server = servers_client.show_server(server['id'])['server']
+        address = _get_floating_ip_in_server_addresses(floating_ip, server)
+        if address is None and wait_for_disassociate:
+            return None
+        if not wait_for_disassociate and address:
+            return address
+
+        if int(time.time()) - start_time >= servers_client.build_timeout:
+            if wait_for_disassociate:
+                msg = ('Floating ip %s failed to disassociate from server %s '
+                       'in time.' % (floating_ip, server['id']))
+            else:
+                msg = ('Floating ip %s failed to associate with server %s '
+                       'in time.' % (floating_ip, server['id']))
+            raise lib_exc.TimeoutException(msg)
+        time.sleep(servers_client.build_interval)
diff --git a/tempest/config.py b/tempest/config.py
index 28a70c2..03ddbf5 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -92,7 +92,24 @@
     cfg.StrOpt('admin_domain_name',
                default='Default',
                help="Admin domain name for authentication (Keystone V3). "
-                    "The same domain applies to user and project"),
+                    "The same domain applies to user and project if "
+                    "admin_user_domain_name and admin_project_domain_name "
+                    "are not specified"),
+    cfg.StrOpt('admin_user_domain_name',
+               help="Domain name that contains the admin user (Keystone V3). "
+                    "May be different from admin_project_domain_name and "
+                    "admin_domain_name"),
+    cfg.StrOpt('admin_project_domain_name',
+               help="Domain name that contains the project given by "
+                    "admin_project_name (Keystone V3). May be different from "
+                    "admin_user_domain_name and admin_domain_name"),
+    cfg.StrOpt('admin_system',
+               default=None,
+               help="The system scope on which an admin user has an admin "
+                    "role assignment, if any. Valid values are 'all' or None. "
+                    "This must be set to 'all' if using the "
+                    "[oslo_policy]/enforce_scope=true option for the "
+                    "identity service."),
 ]
 
 identity_group = cfg.OptGroup(name='identity',
@@ -242,14 +259,18 @@
                 help='Does the environment have the security compliance '
                      '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.
+                default=True,
+                help='Is the project tags identity v3 API available?',
+                deprecated_for_removal=True,
+                deprecated_reason='Project tags API is a default feature '
+                                  'since Queens'),
     cfg.BoolOpt('application_credentials',
-                default=False,
+                default=True,
                 help='Does the environment have application credentials '
-                     'enabled?'),
+                     'enabled?',
+                deprecated_for_removal=True,
+                deprecated_reason='Application credentials is a default '
+                                  'feature since Queens'),
     # Access rules for application credentials is a default feature in Train.
     # This config option can removed once Stein is EOL.
     cfg.BoolOpt('access_rules',
@@ -420,6 +441,15 @@
     cfg.BoolOpt('disk_config',
                 default=True,
                 help="If false, skip disk config tests"),
+    # TODO(pkesav): Make it True by default once wallaby
+    # is oldest supported stable for Tempest.
+    cfg.BoolOpt('hostname_fqdn_sanitization',
+                default=False,
+                help="If false, skip fqdn instance sanitization tests. "
+                     "Nova started sanitizing the instance name by replacing "
+                     "the '.' with '-' to comply with fqdn hostname. Nova "
+                     "changed that in Wallaby cycle, if your cloud is older "
+                     "than wallaby then you can keep/make it False."),
     cfg.ListOpt('api_extensions',
                 default=['all'],
                 help='A list of enabled compute extensions with a special '
@@ -452,6 +482,10 @@
     cfg.BoolOpt('shelve',
                 default=True,
                 help="Does the test environment support shelving/unshelving?"),
+    cfg.BoolOpt('shelve_migrate',
+                default=False,
+                help="Does the test environment support "
+                     "cold migration of unshelved server?"),
     cfg.BoolOpt('suspend',
                 default=True,
                 help="Does the test environment support suspend/resume?"),
@@ -615,6 +649,10 @@
                                   "APIs that only worked with that driver "
                                   "have been removed and there's nothing to "
                                   "test after Ussuri."),
+    cfg.BoolOpt('ide_bus',
+                default=True,
+                help='Does the test environment support attaching devices '
+                     'using an IDE bus to the instance?'),
 ]
 
 
@@ -636,6 +674,15 @@
                choices=['public', 'admin', 'internal',
                         'publicURL', 'adminURL', 'internalURL'],
                help="The endpoint type to use for the image service."),
+    cfg.StrOpt('alternate_image_endpoint',
+               default=None,
+               help="Alternate endpoint name for cross-worker testing"),
+    cfg.StrOpt('alternate_image_endpoint_type',
+               default='publicURL',
+               choices=['public', 'admin', 'internal',
+                        'publicURL', 'adminURL', 'internalURL'],
+               help=("The endpoint type to use for the alternate image "
+                     "service.")),
     cfg.StrOpt('http_image',
                default='http://download.cirros-cloud.net/0.3.1/'
                'cirros-0.3.1-x86_64-uec.tar.gz',
@@ -685,6 +732,13 @@
     cfg.BoolOpt('import_image',
                 default=False,
                 help="Is image import feature enabled"),
+    # NOTE(danms): Starting mid-Wallaby glance began enforcing the
+    # previously-informal requirement that os_glance_* properties are
+    # reserved for internal use. Thus, we can only run these checks
+    # if we know we are on a new enough glance.
+    cfg.BoolOpt('os_glance_reserved',
+                default=False,
+                help="Should we check that os_glance namespace is reserved"),
 ]
 
 network_group = cfg.OptGroup(name='network',
@@ -757,11 +811,13 @@
                 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'],
+               choices=[None, 'normal', 'direct', 'macvtap', 'direct-physical',
+                        'baremetal', 'virtio-forwarder'],
                help="vnic_type to use when launching instances"
                     " with pre-configured ports."
                     " Supported ports are:"
-                    " ['normal','direct','macvtap']"),
+                    " ['normal', 'direct', 'macvtap', 'direct-physical', "
+                    "'baremetal', 'virtio-forwarder']"),
     cfg.Opt('port_profile',
             type=ProfileType,
             default={},
@@ -783,42 +839,58 @@
 NetworkFeaturesGroup = [
     cfg.BoolOpt('ipv6',
                 default=True,
-                help="Allow the execution of IPv6 tests"),
+                help="Allow the execution of IPv6 tests."),
     cfg.ListOpt('api_extensions',
                 default=['all'],
                 help="A list of enabled network 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: 'neutron ext-list'"),
+                     "To get the list of extensions run: "
+                     "'openstack extension list --network'"),
     cfg.ListOpt('available_features',
                 default=['all'],
                 help="A list of available network features with a special "
                      "entry all that indicates every feature is available. "
-                     "Empty list indicates all features are disabled."
+                     "Empty list indicates all features are disabled. "
                      "This list can contain features that are not "
-                     "discoverable through API."),
+                     "discoverable through the API."),
     cfg.BoolOpt('ipv6_subnet_attributes',
                 default=False,
                 help="Allow the execution of IPv6 subnet tests that use "
                      "the extended IPv6 attributes ipv6_ra_mode "
-                     "and ipv6_address_mode"
+                     "and ipv6_address_mode."
                 ),
     cfg.BoolOpt('port_admin_state_change',
                 default=True,
-                help="Does the test environment support changing"
-                     " port admin state"),
+                help="Does the test environment support changing "
+                     "port admin state?"),
     cfg.BoolOpt('port_security',
                 default=False,
                 help="Does the test environment support port security?"),
     cfg.BoolOpt('floating_ips',
                 default=True,
-                help='Does the test environment support floating_ips'),
+                help='Does the test environment support floating_ips?'),
     cfg.StrOpt('qos_placement_physnet', default=None,
                help='Name of the physnet for placement based minimum '
                     'bandwidth allocation.'),
     cfg.StrOpt('provider_net_base_segmentation_id', default=3000,
                help='Base segmentation ID to create provider networks. '
-                    'This value will be increased in case of conflict.')
+                    'This value will be increased in case of conflict.'),
+    cfg.BoolOpt('qos_min_bw_and_pps', default=False,
+                help='Does the test environment have minimum bandwidth and '
+                     'packet rate inventories configured?'),
+]
+
+dashboard_group = cfg.OptGroup(name="dashboard",
+                               title="Dashboard options")
+
+DashboardGroup = [
+    cfg.StrOpt('dashboard_url',
+               default='http://localhost/',
+               help="Where the dashboard can be found"),
+    cfg.BoolOpt('disable_ssl_certificate_validation',
+                default=False,
+                help="Set to True if using self-signed SSL certificates."),
 ]
 
 validation_group = cfg.OptGroup(name='validation',
@@ -865,10 +937,17 @@
     cfg.StrOpt('image_ssh_user',
                default="root",
                help="User name used to authenticate to an instance."),
+    cfg.StrOpt('image_alt_ssh_user',
+               default="root",
+               help="User name used to authenticate to an alt instance."),
     cfg.StrOpt('image_ssh_password',
                default="password",
                help="Password used to authenticate to an instance.",
                secret=True),
+    cfg.StrOpt('image_alt_ssh_password',
+               default="password",
+               help="Password used to authenticate to an alt instance.",
+               secret=True),
     cfg.StrOpt('ssh_shell_prologue',
                default="set -eu -o pipefail; PATH=$$PATH:/sbin:/usr/sbin;",
                help="Shell fragments to use before executing a command "
@@ -891,6 +970,10 @@
                default='public',
                help="Network used for SSH connections. Ignored if "
                     "connect_method=floating."),
+    cfg.StrOpt('ssh_key_type',
+               default='rsa',
+               help='Type of key to use for ssh connections. '
+                    'Valid types are rsa, ecdsa'),
 ]
 
 volume_group = cfg.OptGroup(name='volume',
@@ -922,6 +1005,9 @@
                 default=['BACKEND_1', 'BACKEND_2'],
                 help='A list of backend names separated by comma. '
                      'The backend name must be declared in cinder.conf'),
+    cfg.StrOpt('volume_type',
+               default='',
+               help='Volume type to be used while creating volume.'),
     cfg.StrOpt('storage_protocol',
                default='iSCSI',
                help='Backend protocol to target when creating volume types'),
@@ -934,6 +1020,11 @@
     cfg.IntOpt('volume_size',
                default=1,
                help='Default size in GB for volumes created by volumes tests'),
+    cfg.IntOpt('volume_size_extend',
+               default=1,
+               help="Size in GB a volume is extended by - if a test "
+                    "extends a volume, the size of the new volume will be "
+                    "volume_size + volume_size_extend."),
     cfg.ListOpt('manage_volume_ref',
                 default=['source-name', 'volume-%s'],
                 help="A reference to existing volume for volume manage. "
@@ -993,31 +1084,6 @@
                 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_v2',
-                default=True,
-                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",
-                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 '
@@ -1107,12 +1173,6 @@
 scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options')
 
 ScenarioGroup = [
-    cfg.StrOpt('img_dir',
-               default='/opt/stack/new/devstack/files/images/'
-               'cirros-0.3.1-x86_64-uec',
-               help='Directory containing image files, this has been '
-                    'deprecated - img_file option contains a full path now.',
-               deprecated_for_removal=True),
     cfg.StrOpt('img_file', deprecated_name='qcow2_img_file',
                default='/opt/stack/new/devstack/files/images'
                '/cirros-0.3.1-x86_64-disk.img',
@@ -1159,6 +1219,42 @@
     cfg.BoolOpt('nova',
                 default=True,
                 help="Whether or not nova is expected to be available"),
+    cfg.BoolOpt('horizon',
+                default=True,
+                help="Whether or not horizon is expected to be available"),
+]
+
+enforce_scope_group = cfg.OptGroup(name="enforce_scope",
+                                   title="OpenStack Services with "
+                                         "enforce scope")
+
+
+EnforceScopeGroup = [
+    cfg.BoolOpt('nova',
+                default=False,
+                help='Does the compute service API policies enforce scope? '
+                     'This configuration value should be same as '
+                     'nova.conf: [oslo_policy].enforce_scope option.'),
+    cfg.BoolOpt('neutron',
+                default=False,
+                help='Does the network service API policies enforce scope? '
+                     'This configuration value should be same as '
+                     'neutron.conf: [oslo_policy].enforce_scope option.'),
+    cfg.BoolOpt('glance',
+                default=False,
+                help='Does the Image service API policies enforce scope? '
+                     'This configuration value should be same as '
+                     'glance.conf: [oslo_policy].enforce_scope option.'),
+    cfg.BoolOpt('cinder',
+                default=False,
+                help='Does the Volume service API policies enforce scope? '
+                     'This configuration value should be same as '
+                     'cinder.conf: [oslo_policy].enforce_scope option.'),
+    cfg.BoolOpt('keystone',
+                default=False,
+                help='Does the Identity service API policies enforce scope? '
+                     'This configuration value should be same as '
+                     'keystone.conf: [oslo_policy].enforce_scope option.'),
 ]
 
 debug_group = cfg.OptGroup(name="debug",
@@ -1222,6 +1318,7 @@
     (image_feature_group, ImageFeaturesGroup),
     (network_group, NetworkGroup),
     (network_feature_group, NetworkFeaturesGroup),
+    (dashboard_group, DashboardGroup),
     (validation_group, ValidationGroup),
     (volume_group, VolumeGroup),
     (volume_feature_group, VolumeFeaturesGroup),
@@ -1229,6 +1326,7 @@
     (object_storage_feature_group, ObjectStoreFeaturesGroup),
     (scenario_group, ScenarioGroup),
     (service_available_group, ServiceAvailableGroup),
+    (enforce_scope_group, EnforceScopeGroup),
     (debug_group, DebugGroup),
     (placement_group, PlacementGroup),
     (profiler_group, ProfilerGroup),
@@ -1289,6 +1387,7 @@
         self.image_feature_enabled = _CONF['image-feature-enabled']
         self.network = _CONF.network
         self.network_feature_enabled = _CONF['network-feature-enabled']
+        self.dashboard = _CONF.dashboard
         self.validation = _CONF.validation
         self.volume = _CONF.volume
         self.volume_feature_enabled = _CONF['volume-feature-enabled']
@@ -1297,6 +1396,7 @@
             'object-storage-feature-enabled']
         self.scenario = _CONF.scenario
         self.service_available = _CONF.service_available
+        self.enforce_scope = _CONF.enforce_scope
         self.debug = _CONF.debug
         logging.tempest_set_log_file('tempest.log')
         # Setting attributes for plugins
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 6a97a00..c1e6b2d 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -140,20 +140,10 @@
                "decorators.skip_because from tempest.lib")
 
 
-def _common_service_clients_check(logical_line, physical_line, filename,
-                                  ignored_list_file=None):
+def _common_service_clients_check(logical_line, physical_line, filename):
     if not re.match('tempest/(lib/)?services/.*', filename):
         return False
 
-    if ignored_list_file is not None:
-        ignored_list = []
-        with open('tempest/hacking/' + ignored_list_file) as f:
-            for line in f:
-                ignored_list.append(line.strip())
-
-        if filename in ignored_list:
-            return False
-
     if not METHOD.match(physical_line):
         return False
 
@@ -171,7 +161,7 @@
     T110
     """
     if not _common_service_clients_check(logical_line, physical_line,
-                                         filename, 'ignored_list_T110.txt'):
+                                         filename):
         return
 
     for line in lines[line_number:]:
@@ -199,7 +189,7 @@
     T111
     """
     if not _common_service_clients_check(logical_line, physical_line,
-                                         filename, 'ignored_list_T111.txt'):
+                                         filename):
         return
 
     for line in lines[line_number:]:
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 fc81ff7..dcd64cf 100644
--- a/tempest/lib/api_schema/response/compute/v2_16/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_16/servers.py
@@ -171,3 +171,4 @@
 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)
+show_instance_action = copy.deepcopy(servers.show_instance_action)
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 b6c3c14..0e4bd5c 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -61,3 +61,4 @@
 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)
+show_instance_action = copy.deepcopy(serversv216.show_instance_action)
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 5a0f987..74c08f1 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -104,3 +104,4 @@
 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)
+show_instance_action = copy.deepcopy(servers219.show_instance_action)
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 1674c1b..435e3ac 100644
--- a/tempest/lib/api_schema/response/compute/v2_3/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_3/servers.py
@@ -176,3 +176,4 @@
 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)
+show_instance_action = copy.deepcopy(servers.show_instance_action)
diff --git a/tempest/tests/lib/services/volume/v1/__init__.py b/tempest/lib/api_schema/response/compute/v2_33/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v1/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_33/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_33/hypervisors.py b/tempest/lib/api_schema/response/compute/v2_33/hypervisors.py
new file mode 100644
index 0000000..9773605
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_33/hypervisors.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_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_28 \
+    import hypervisors as hypervisorsv228
+
+###########################################################################
+#
+# 2.33:
+#
+# hypervisor_links parameter is added to the response body for the following
+# APIs:
+#
+# - GET /os-hypervisors
+# - GET /os-hypervisors/detail
+###########################################################################
+list_search_hypervisors = copy.deepcopy(
+    hypervisorsv228.list_search_hypervisors)
+list_search_hypervisors['response_body']['properties'].update(
+    {'hypervisors_links': parameter_types.links}
+)
+
+list_hypervisors_detail = copy.deepcopy(
+    hypervisorsv228.list_hypervisors_detail)
+list_hypervisors_detail['response_body']['properties'].update(
+    {'hypervisors_links': parameter_types.links}
+)
+
+# 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.28 ***
+get_hypervisor = copy.deepcopy(hypervisorsv228.get_hypervisor)
+hypervisor_detail = copy.deepcopy(hypervisorsv228.hypervisor_detail)
+get_hypervisor_statistics = \
+    copy.deepcopy(hypervisorsv228.get_hypervisor_statistics)
+get_hypervisor_uptime = copy.deepcopy(hypervisorsv228.get_hypervisor_uptime)
+get_hypervisors_servers = copy.deepcopy(
+    hypervisorsv228.get_hypervisors_servers)
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 d580f2c..7050602 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -69,3 +69,4 @@
 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)
+show_instance_action = copy.deepcopy(servers226.show_instance_action)
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 e2e45bc..af6344b 100644
--- a/tempest/lib/api_schema/response/compute/v2_48/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_48/servers.py
@@ -132,3 +132,4 @@
 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)
+show_instance_action = copy.deepcopy(servers247.show_instance_action)
diff --git a/tempest/tests/lib/services/volume/v1/__init__.py b/tempest/lib/api_schema/response/compute/v2_51/__init__.py
similarity index 100%
rename from tempest/tests/lib/services/volume/v1/__init__.py
rename to tempest/lib/api_schema/response/compute/v2_51/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_51/servers.py b/tempest/lib/api_schema/response/compute/v2_51/servers.py
new file mode 100644
index 0000000..e603287
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_51/servers.py
@@ -0,0 +1,42 @@
+#    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
+
+# microversion 2.51 made events a mandatory field in the response
+show_instance_action = copy.deepcopy(servers248.show_instance_action)
+show_instance_action['response_body'][
+    'properties']['instanceAction']['required'].append('events')
+
+
+# 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(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)
+get_server = copy.deepcopy(servers248.get_server)
+list_servers_detail = copy.deepcopy(servers248.list_servers_detail)
+update_server = copy.deepcopy(servers248.update_server)
+rebuild_server = copy.deepcopy(servers248.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers248.rebuild_server_with_admin_pass)
+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/lib/api_schema/response/compute/v2_53/hypervisors.py b/tempest/lib/api_schema/response/compute/v2_53/hypervisors.py
new file mode 100644
index 0000000..e172f1f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_53/hypervisors.py
@@ -0,0 +1,68 @@
+# 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_33 \
+    import hypervisors as hypervisorsv233
+
+###########################################################################
+#
+# 2.53:
+#
+# servers parameter is added to the response body for the following
+# APIs:
+#
+# - GET /os-hypervisor
+# - GET /os-hypervisors
+# - GET /os-hypervisors/detail
+#
+###########################################################################
+
+servers = {
+    'type': 'array',
+    'items': {
+        'type': 'object',
+        'properties': {
+            'uuid': {'type': 'string', 'format': 'uuid'},
+            'name': {'type': 'string'},
+        },
+        'additionalProperties': False,
+    },
+}
+
+hypervisor_detail = copy.deepcopy(hypervisorsv233.hypervisor_detail)
+hypervisor_detail['properties'].update({'servers': servers})
+get_hypervisor = copy.deepcopy(hypervisorsv233.get_hypervisor)
+get_hypervisor['response_body']['properties'].update(
+    {'hypervisor': hypervisor_detail})
+list_hypervisors_detail = copy.deepcopy(
+    hypervisorsv233.list_hypervisors_detail)
+list_hypervisors_detail['response_body']['properties']['hypervisors'].update(
+    {'items': hypervisor_detail})
+
+list_search_hypervisors = copy.deepcopy(
+    hypervisorsv233.list_search_hypervisors)
+list_search_hypervisors['response_body']['properties']['hypervisors'][
+    'items']['properties'].update({'servers': servers})
+
+# 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.33 ***
+get_hypervisor_statistics = \
+    copy.deepcopy(hypervisorsv233.get_hypervisor_statistics)
+get_hypervisor_uptime = copy.deepcopy(hypervisorsv233.get_hypervisor_uptime)
+get_hypervisors_servers = copy.deepcopy(
+    hypervisorsv233.get_hypervisors_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_54/servers.py b/tempest/lib/api_schema/response/compute/v2_54/servers.py
index 2c2bff0..135b381 100644
--- a/tempest/lib/api_schema/response/compute/v2_54/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_54/servers.py
@@ -12,7 +12,7 @@
 
 import copy
 
-from tempest.lib.api_schema.response.compute.v2_48 import servers as servers248
+from tempest.lib.api_schema.response.compute.v2_51 import servers as servers251
 # ****** Schemas changed in microversion 2.54 *****************
 
 # Note(gmann): This is schema for microversion 2.54 which includes the
@@ -26,14 +26,14 @@
     ]
 }
 
-rebuild_server = copy.deepcopy(servers248.rebuild_server)
+rebuild_server = copy.deepcopy(servers251.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)
+    servers251.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'][
@@ -43,18 +43,19 @@
 # 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)
+get_server = copy.deepcopy(servers251.get_server)
+list_servers_detail = copy.deepcopy(servers251.list_servers_detail)
+update_server = copy.deepcopy(servers251.update_server)
+list_servers = copy.deepcopy(servers251.list_servers)
+show_server_diagnostics = copy.deepcopy(servers251.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers251.get_remote_consoles)
+list_tags = copy.deepcopy(servers251.list_tags)
+update_all_tags = copy.deepcopy(servers251.update_all_tags)
+delete_all_tags = copy.deepcopy(servers251.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers251.check_tag_existence)
+update_tag = copy.deepcopy(servers251.update_tag)
+delete_tag = copy.deepcopy(servers251.delete_tag)
+attach_volume = copy.deepcopy(servers251.attach_volume)
+show_volume_attachment = copy.deepcopy(servers251.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers251.list_volume_attachments)
+show_instance_action = copy.deepcopy(servers251.show_instance_action)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/servers.py b/tempest/lib/api_schema/response/compute/v2_57/servers.py
index aa57d25..bdff74b 100644
--- a/tempest/lib/api_schema/response/compute/v2_57/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_57/servers.py
@@ -62,3 +62,4 @@
 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)
+show_instance_action = copy.deepcopy(servers254.show_instance_action)
diff --git a/tempest/tests/lib/services/volume/v1/__init__.py b/tempest/lib/api_schema/response/compute/v2_58/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v1/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_58/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_58/servers.py b/tempest/lib/api_schema/response/compute/v2_58/servers.py
new file mode 100644
index 0000000..62239cf
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_58/servers.py
@@ -0,0 +1,44 @@
+#    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_57 import servers as servers257
+
+# microversion 2.58 added updated_at to the response
+show_instance_action = copy.deepcopy(servers257.show_instance_action)
+show_instance_action['response_body']['properties']['instanceAction'][
+    'properties']['updated_at'] = parameter_types.date_time
+show_instance_action['response_body']['properties']['instanceAction'][
+    'required'].append('updated_at')
+
+# 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(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)
+get_server = copy.deepcopy(servers257.get_server)
+list_servers_detail = copy.deepcopy(servers257.list_servers_detail)
+update_server = copy.deepcopy(servers257.update_server)
+rebuild_server = copy.deepcopy(servers257.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers257.rebuild_server_with_admin_pass)
+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/lib/api_schema/response/compute/v2_6/servers.py b/tempest/lib/api_schema/response/compute/v2_6/servers.py
index 922bf79..6103b7c 100644
--- a/tempest/lib/api_schema/response/compute/v2_6/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_6/servers.py
@@ -31,6 +31,7 @@
 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)
+show_instance_action = copy.deepcopy(servers.show_instance_action)
 
 # NOTE: The consolidated remote console API got introduced with v2.6
 # with bp/consolidate-console-api. See Nova commit 578bafeda
diff --git a/tempest/tests/lib/services/volume/v1/__init__.py b/tempest/lib/api_schema/response/compute/v2_62/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v1/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_62/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_62/servers.py b/tempest/lib/api_schema/response/compute/v2_62/servers.py
new file mode 100644
index 0000000..23eebbb
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_62/servers.py
@@ -0,0 +1,47 @@
+#    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_58 import servers as servers258
+
+# microversion 2.62 added hostId and host to the event, but only hostId is
+# mandatory
+show_instance_action = copy.deepcopy(servers258.show_instance_action)
+show_instance_action['response_body']['properties']['instanceAction'][
+    'properties']['events']['items'][
+    'properties']['hostId'] = {'type': 'string'}
+show_instance_action['response_body']['properties']['instanceAction'][
+    'properties']['events']['items']['properties']['host'] = {'type': 'string'}
+show_instance_action['response_body']['properties']['instanceAction'][
+    'properties']['events']['items']['required'].append('hostId')
+
+# 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(servers258.list_servers)
+show_server_diagnostics = copy.deepcopy(servers258.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers258.get_remote_consoles)
+list_tags = copy.deepcopy(servers258.list_tags)
+update_all_tags = copy.deepcopy(servers258.update_all_tags)
+delete_all_tags = copy.deepcopy(servers258.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers258.check_tag_existence)
+update_tag = copy.deepcopy(servers258.update_tag)
+delete_tag = copy.deepcopy(servers258.delete_tag)
+get_server = copy.deepcopy(servers258.get_server)
+list_servers_detail = copy.deepcopy(servers258.list_servers_detail)
+update_server = copy.deepcopy(servers258.update_server)
+rebuild_server = copy.deepcopy(servers258.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers258.rebuild_server_with_admin_pass)
+attach_volume = copy.deepcopy(servers258.attach_volume)
+show_volume_attachment = copy.deepcopy(servers258.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers258.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_63/servers.py b/tempest/lib/api_schema/response/compute/v2_63/servers.py
index 01910aa..db713b1 100644
--- a/tempest/lib/api_schema/response/compute/v2_63/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_63/servers.py
@@ -12,7 +12,7 @@
 
 import copy
 
-from tempest.lib.api_schema.response.compute.v2_57 import servers as servers257
+from tempest.lib.api_schema.response.compute.v2_62 import servers as servers262
 
 # Nova microversion 2.63 adds 'trusted_image_certificates' (a list of
 # certificate IDs) to the server rebuild and servers details responses.
@@ -29,32 +29,32 @@
     }
 }
 
-list_servers_detail = copy.deepcopy(servers257.list_servers_detail)
+list_servers_detail = copy.deepcopy(servers262.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 = copy.deepcopy(servers262.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)
+    servers262.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 = copy.deepcopy(servers262.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 = copy.deepcopy(servers262.get_server)
 get_server['response_body']['properties']['server'][
     'properties'].update({'trusted_image_certificates': trusted_certs})
 get_server['response_body']['properties']['server'][
@@ -64,15 +64,16 @@
 # 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)
+list_servers = copy.deepcopy(servers262.list_servers)
+show_server_diagnostics = copy.deepcopy(servers262.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers262.get_remote_consoles)
+list_tags = copy.deepcopy(servers262.list_tags)
+update_all_tags = copy.deepcopy(servers262.update_all_tags)
+delete_all_tags = copy.deepcopy(servers262.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers262.check_tag_existence)
+update_tag = copy.deepcopy(servers262.update_tag)
+delete_tag = copy.deepcopy(servers262.delete_tag)
+attach_volume = copy.deepcopy(servers262.attach_volume)
+show_volume_attachment = copy.deepcopy(servers262.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers262.list_volume_attachments)
+show_instance_action = copy.deepcopy(servers262.show_instance_action)
diff --git a/tempest/lib/api_schema/response/compute/v2_70/interfaces.py b/tempest/lib/api_schema/response/compute/v2_70/interfaces.py
new file mode 100644
index 0000000..3160b92
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_70/interfaces.py
@@ -0,0 +1,37 @@
+# Copyright 2014 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 interfaces
+
+# ****** Schemas changed in microversion 2.70 *****************
+#
+# 1. add optional field 'tag' in the Response body of the following APIs:
+#    - GET /servers/{server_id}/os-interface
+#    - POST /servers/{server_id}/os-interface
+#    - GET /servers/{server_id}/os-interface/{port_id}
+
+get_create_interfaces = copy.deepcopy(interfaces.get_create_interfaces)
+get_create_interfaces['response_body']['properties']['interfaceAttachment'][
+    'properties'].update({'tag': {'type': ['string', 'null']}})
+
+list_interfaces = copy.deepcopy(interfaces.list_interfaces)
+list_interfaces['response_body']['properties']['interfaceAttachments'][
+    'items']['properties'].update({'tag': {'type': ['string', 'null']}})
+
+# 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_interface = copy.deepcopy(interfaces.delete_interface)
diff --git a/tempest/lib/api_schema/response/compute/v2_70/servers.py b/tempest/lib/api_schema/response/compute/v2_70/servers.py
index 5ca4cc8..6103923 100644
--- a/tempest/lib/api_schema/response/compute/v2_70/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_70/servers.py
@@ -78,3 +78,4 @@
 check_tag_existence = copy.deepcopy(servers263.check_tag_existence)
 update_tag = copy.deepcopy(servers263.update_tag)
 delete_tag = copy.deepcopy(servers263.delete_tag)
+show_instance_action = copy.deepcopy(servers263.show_instance_action)
diff --git a/tempest/lib/api_schema/response/compute/v2_71/servers.py b/tempest/lib/api_schema/response/compute/v2_71/servers.py
index f4c01ee..3e55c1c 100644
--- a/tempest/lib/api_schema/response/compute/v2_71/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_71/servers.py
@@ -82,3 +82,4 @@
 attach_volume = copy.deepcopy(servers270.attach_volume)
 show_volume_attachment = copy.deepcopy(servers270.show_volume_attachment)
 list_volume_attachments = copy.deepcopy(servers270.list_volume_attachments)
+show_instance_action = copy.deepcopy(servers270.show_instance_action)
diff --git a/tempest/lib/api_schema/response/compute/v2_73/servers.py b/tempest/lib/api_schema/response/compute/v2_73/servers.py
index ae7ebc4..e7a1d87 100644
--- a/tempest/lib/api_schema/response/compute/v2_73/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_73/servers.py
@@ -79,3 +79,4 @@
 attach_volume = copy.deepcopy(servers271.attach_volume)
 show_volume_attachment = copy.deepcopy(servers271.show_volume_attachment)
 list_volume_attachments = copy.deepcopy(servers271.list_volume_attachments)
+show_instance_action = copy.deepcopy(servers271.show_instance_action)
diff --git a/tempest/tests/lib/services/volume/v1/__init__.py b/tempest/lib/api_schema/response/compute/v2_79/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v1/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_79/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_79/servers.py b/tempest/lib/api_schema/response/compute/v2_79/servers.py
new file mode 100644
index 0000000..b5507f9
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_79/servers.py
@@ -0,0 +1,68 @@
+#    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_73 import servers as servers273
+
+
+###########################################################################
+#
+# 2.79:
+#
+# The delete_on_termination parameter is now returned in the response body
+# of the following calls:
+#
+# - GET /servers/{server_id}/os-volume_attachments
+# - GET /servers/{server_id}/os-volume_attachments/{volume_id}
+# - POST /servers/{server_id}/os-volume_attachments
+###########################################################################
+
+attach_volume = copy.deepcopy(servers273.attach_volume)
+attach_volume['response_body']['properties']['volumeAttachment'][
+    'properties'].update({'delete_on_termination': {'type': 'boolean'}})
+attach_volume['response_body']['properties']['volumeAttachment'][
+    'required'].append('delete_on_termination')
+
+show_volume_attachment = copy.deepcopy(servers273.show_volume_attachment)
+show_volume_attachment['response_body']['properties']['volumeAttachment'][
+    'properties'].update({'delete_on_termination': {'type': 'boolean'}})
+show_volume_attachment['response_body']['properties'][
+    'volumeAttachment']['required'].append('delete_on_termination')
+
+list_volume_attachments = copy.deepcopy(servers273.list_volume_attachments)
+list_volume_attachments['response_body']['properties']['volumeAttachments'][
+    'items']['properties'].update(
+        {'delete_on_termination': {'type': 'boolean'}})
+list_volume_attachments['response_body']['properties'][
+    'volumeAttachments']['items']['required'].append('delete_on_termination')
+
+# 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.73 ***
+rebuild_server = copy.deepcopy(servers273.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers273.rebuild_server_with_admin_pass)
+update_server = copy.deepcopy(servers273.update_server)
+get_server = copy.deepcopy(servers273.get_server)
+list_servers_detail = copy.deepcopy(servers273.list_servers_detail)
+list_servers = copy.deepcopy(servers273.list_servers)
+show_server_diagnostics = copy.deepcopy(servers273.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers273.get_remote_consoles)
+list_tags = copy.deepcopy(servers273.list_tags)
+update_all_tags = copy.deepcopy(servers273.update_all_tags)
+delete_all_tags = copy.deepcopy(servers273.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers273.check_tag_existence)
+update_tag = copy.deepcopy(servers273.update_tag)
+delete_tag = copy.deepcopy(servers273.delete_tag)
+show_instance_action = copy.deepcopy(servers273.show_instance_action)
diff --git a/tempest/lib/api_schema/response/compute/v2_8/servers.py b/tempest/lib/api_schema/response/compute/v2_8/servers.py
index 3dbab3f..119d8e2 100644
--- a/tempest/lib/api_schema/response/compute/v2_8/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_8/servers.py
@@ -38,3 +38,4 @@
 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)
+show_instance_action = copy.deepcopy(servers.show_instance_action)
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 ee0313d..9258eec 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -57,3 +57,4 @@
 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)
+show_instance_action = copy.deepcopy(servers.show_instance_action)
diff --git a/tempest/lib/api_schema/response/volume/services.py b/tempest/lib/api_schema/response/volume/services.py
index 70de878..216631c 100644
--- a/tempest/lib/api_schema/response/volume/services.py
+++ b/tempest/lib/api_schema/response/volume/services.py
@@ -33,10 +33,6 @@
                         'frozen': {'type': 'boolean'},
                         'updated_at': parameter_types.date_time,
                         'zone': {'type': 'string'},
-                        # TODO(zhufl): cluster is added in 3.7, we should move
-                        # it to the 3.7 schema file when microversion is
-                        # supported in volume interfaces
-                        'cluster': {'type': 'string'},
                         'replication_status': {'type': 'string'},
                         'active_backend_id': {'type': ['string', 'null']},
                         'backend_state': {'type': 'string'},
diff --git a/tempest/tests/lib/services/volume/v1/__init__.py b/tempest/lib/api_schema/response/volume/v3_7/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v1/__init__.py
copy to tempest/lib/api_schema/response/volume/v3_7/__init__.py
diff --git a/tempest/lib/api_schema/response/volume/v3_7/services.py b/tempest/lib/api_schema/response/volume/v3_7/services.py
new file mode 100644
index 0000000..8d43188
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_7/services.py
@@ -0,0 +1,34 @@
+# Copyright 2020 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.volume import services
+
+# Volume microversion 3.7:
+# 1. New optional attribute in 'services' dict.
+#      'cluster'
+
+list_services = copy.deepcopy(services.list_services)
+list_services['response_body']['properties']['services']['items'][
+    'properties'].update({'cluster': {'type': ['string', 'null']}})
+
+# 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 3.0 ******
+enable_service = copy.deepcopy(services.enable_service)
+disable_service = copy.deepcopy(services.disable_service)
+disable_log_reason = copy.deepcopy(services.disable_log_reason)
+freeze_host = copy.deepcopy(services.freeze_host)
+thaw_host = copy.deepcopy(services.thaw_host)
diff --git a/tempest/lib/api_schema/response/volume/volumes.py b/tempest/lib/api_schema/response/volume/volumes.py
index ffcf488..4f44526 100644
--- a/tempest/lib/api_schema/response/volume/volumes.py
+++ b/tempest/lib/api_schema/response/volume/volumes.py
@@ -21,7 +21,7 @@
     'items': {
         'type': 'object',
         'properties': {
-            'server_id': {'type': 'string', 'format': 'uuid'},
+            'server_id': {'type': ['string', 'null'], 'format': 'uuid'},
             'attachment_id': {'type': 'string', 'format': 'uuid'},
             'attached_at': parameter_types.date_time_or_null,
             'host_name': {'type': ['string', 'null']},
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 3fee489..8bdf98e 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -18,10 +18,9 @@
 import copy
 import datetime
 import re
+from urllib import parse as urlparse
 
 from oslo_log import log as logging
-import six
-from six.moves.urllib import parse as urlparse
 
 from tempest.lib import exceptions
 from tempest.lib.services.identity.v2 import token_client as json_v2id
@@ -65,8 +64,7 @@
     return url
 
 
-@six.add_metaclass(abc.ABCMeta)
-class AuthProvider(object):
+class AuthProvider(object, metaclass=abc.ABCMeta):
     """Provide authentication"""
 
     SCOPES = set(['project'])
@@ -391,7 +389,7 @@
         """
         if auth_data is None:
             auth_data = self.get_auth()
-        token, _auth_data = auth_data
+        _, _auth_data = auth_data
         service = filters.get('service')
         region = filters.get('region')
         name = filters.get('name')
@@ -428,7 +426,7 @@
 class KeystoneV3AuthProvider(KeystoneAuthProvider):
     """Provides authentication based on the Identity V3 API"""
 
-    SCOPES = set(['project', 'domain', 'unscoped', None])
+    SCOPES = set(['system', 'project', 'domain', 'unscoped', None])
 
     def _auth_client(self, auth_url):
         return json_v3id.V3TokenClient(
@@ -441,8 +439,8 @@
 
         Fields available in Credentials are passed to the token request,
         depending on the value of scope. Valid values for scope are: "project",
-        "domain". Any other string (e.g. "unscoped") or None will lead to an
-        unscoped token request.
+        "domain", or "system". Any other string (e.g. "unscoped") or None will
+        lead to an unscoped token request.
         """
 
         auth_params = dict(
@@ -465,12 +463,16 @@
                 domain_id=self.credentials.domain_id,
                 domain_name=self.credentials.domain_name)
 
+        if self.scope == 'system':
+            auth_params.update(system='all')
+
         return auth_params
 
     def _fill_credentials(self, auth_data_body):
-        # project or domain, depending on the scope
+        # project, domain, or system depending on the scope
         project = auth_data_body.get('project', None)
         domain = auth_data_body.get('domain', None)
+        system = auth_data_body.get('system', None)
         # user is always there
         user = auth_data_body['user']
         # Set project fields
@@ -490,6 +492,9 @@
                 self.credentials.domain_id = domain['id']
             if self.credentials.domain_name is None:
                 self.credentials.domain_name = domain['name']
+        # Set system scope
+        if system is not None:
+            self.credentials.system = 'all'
         # Set user fields
         if self.credentials.username is None:
             self.credentials.username = user['name']
@@ -524,7 +529,7 @@
         """
         if auth_data is None:
             auth_data = self.get_auth()
-        token, _auth_data = auth_data
+        _, _auth_data = auth_data
         service = filters.get('service')
         region = filters.get('region')
         name = filters.get('name')
@@ -677,7 +682,8 @@
                 raise exceptions.InvalidCredentials(msg)
         for key in attr:
             if key in self.ATTRIBUTES:
-                setattr(self, key, attr[key])
+                if attr[key] is not None:
+                    setattr(self, key, attr[key])
             else:
                 msg = '%s is not a valid attr for %s' % (key, self.__class__)
                 raise exceptions.InvalidCredentials(msg)
@@ -779,7 +785,7 @@
     ATTRIBUTES = ['domain_id', 'domain_name', 'password', 'username',
                   'project_domain_id', 'project_domain_name', 'project_id',
                   'project_name', 'tenant_id', 'tenant_name', 'user_domain_id',
-                  'user_domain_name', 'user_id']
+                  'user_domain_name', 'user_id', 'system']
     COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
 
     def __setattr__(self, key, value):
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
index d8c776b..c661d21 100644
--- a/tempest/lib/cli/base.py
+++ b/tempest/lib/cli/base.py
@@ -18,7 +18,6 @@
 import subprocess
 
 from oslo_log import log as logging
-import six
 
 from tempest.lib import base
 import tempest.lib.cli.output_parser
@@ -55,8 +54,6 @@
                     flags, action, params])
     cmd = cmd.strip()
     LOG.info("running: '%s'", cmd)
-    if six.PY2:
-        cmd = cmd.encode('utf-8')
     cmd = shlex.split(cmd)
     stdout = subprocess.PIPE
     stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
@@ -67,10 +64,7 @@
                                        cmd,
                                        result,
                                        result_err)
-    if six.PY2:
-        return result
-    else:
-        return os.fsdecode(result)
+    return os.fsdecode(result)
 
 
 class CLIClient(object):
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index b34066f..466222d 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -22,10 +22,10 @@
 import os
 import sys
 import unittest
+import urllib.parse as urlparse
 import uuid
 
 from oslo_utils import uuidutils
-import six.moves.urllib.parse as urlparse
 
 DECORATOR_MODULE = 'decorators'
 DECORATOR_NAME = 'idempotent_id'
@@ -38,7 +38,7 @@
 
 class SourcePatcher(object):
 
-    """"Lazy patcher for python source files"""
+    """Lazy patcher for python source files"""
 
     def __init__(self):
         self.source_files = None
@@ -225,8 +225,7 @@
 
     def _add_import_for_test_uuid(self, patcher, src_parsed, source_path):
         import_list = [node for node in src_parsed.body
-                       if isinstance(node, ast.Import) or
-                       isinstance(node, ast.ImportFrom)]
+                       if isinstance(node, (ast.Import, ast.ImportFrom))]
 
         if not import_list:
             print("(WARNING) %s: The file is not valid as it does not contain "
@@ -432,14 +431,21 @@
                         help='Package with tests')
     parser.add_argument('--fix', action='store_true', dest='fix_tests',
                         help='Attempt to fix tests without UUIDs')
+    parser.add_argument('--libpath', action='store', dest='libpath',
+                        default=".", type=str,
+                        help='Path to package')
+
     args = parser.parse_args()
-    sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+    sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
+    sys.path.insert(0, args.libpath)
     pkg = importlib.import_module(args.package)
+
     checker = TestChecker(pkg)
     errors = False
     tests = checker.get_tests()
     untagged = checker.find_untagged(tests)
     errors = checker.report_collisions(tests) or errors
+
     if args.fix_tests and untagged:
         checker.fix_tests(untagged)
     else:
diff --git a/tempest/lib/common/api_version_utils.py b/tempest/lib/common/api_version_utils.py
index 80dbc1d..db5c8c3 100644
--- a/tempest/lib/common/api_version_utils.py
+++ b/tempest/lib/common/api_version_utils.py
@@ -12,7 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
 import testtools
 
 from tempest.lib.common import api_version_request
@@ -117,7 +116,7 @@
     :param response_header: Response header where microversion is
             expected to be present.
     """
-    if not isinstance(api_microversion, six.string_types):
+    if not isinstance(api_microversion, str):
         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
diff --git a/tempest/lib/common/cred_client.py b/tempest/lib/common/cred_client.py
index a81f53c..f13d6d0 100644
--- a/tempest/lib/common/cred_client.py
+++ b/tempest/lib/common/cred_client.py
@@ -13,7 +13,6 @@
 import abc
 
 from oslo_log import log as logging
-import six
 
 from tempest.lib import auth
 from tempest.lib import exceptions as lib_exc
@@ -22,8 +21,7 @@
 LOG = logging.getLogger(__name__)
 
 
-@six.add_metaclass(abc.ABCMeta)
-class CredsClient(object):
+class CredsClient(object, metaclass=abc.ABCMeta):
     """This class is a wrapper around the identity clients
 
      to provide a single interface for managing credentials in both v2 and v3
@@ -39,11 +37,15 @@
         self.projects_client = projects_client
         self.roles_client = roles_client
 
-    def create_user(self, username, password, project, email):
+    def create_user(self, username, password, project=None, email=None):
         params = {'name': username,
-                  'password': password,
-                  self.project_id_param: project['id'],
-                  'email': email}
+                  'password': password}
+        # with keystone v3, a default project is not required
+        if project:
+            params[self.project_id_param] = project['id']
+        # email is not a first-class attribute of a user
+        if email:
+            params['email'] = email
         user = self.users_client.create_user(**params)
         if 'user' in user:
             user = user['user']
@@ -83,12 +85,15 @@
                       role['id'], project['id'], user['id'])
 
     @abc.abstractmethod
-    def get_credentials(self, user, project, password):
+    def get_credentials(
+            self, user, project, password, domain=None, system=None):
         """Produces a Credentials object from the details provided
 
         :param user: a user dict
-        :param project: a project dict
+        :param project: a project dict or None if using domain or system scope
         :param password: the password as a string
+        :param domain: a domain dict
+        :param system: a system dict
         :return: a Credentials object with all the available credential details
         """
         pass
@@ -116,7 +121,8 @@
     def delete_project(self, project_id):
         self.projects_client.delete_tenant(project_id)
 
-    def get_credentials(self, user, project, password):
+    def get_credentials(
+        self, user, project, password, domain=None, system=None):
         # User and project already include both ID and name here,
         # so there's no need to use the fill_in mode
         return auth.get_credentials(
@@ -156,23 +162,62 @@
     def delete_project(self, project_id):
         self.projects_client.delete_project(project_id)
 
-    def get_credentials(self, user, project, password):
+    def create_domain(self, name, description):
+        domain = self.domains_client.create_domain(
+            name=name, description=description)['domain']
+        return domain
+
+    def delete_domain(self, domain_id):
+        self.domains_client.update_domain(domain_id, enabled=False)
+        self.domains_client.delete_domain(domain_id)
+
+    def create_user(self, username, password, project=None, email=None,
+                    domain_id=None):
+        params = {'name': username,
+                  'password': password,
+                  'domain_id': domain_id or self.creds_domain['id']}
+        # with keystone v3, a default project is not required
+        if project:
+            params[self.project_id_param] = project['id']
+        # email is not a first-class attribute of a user
+        if email:
+            params['email'] = email
+        user = self.users_client.create_user(**params)
+        if 'user' in user:
+            user = user['user']
+        return user
+
+    def get_credentials(
+            self, user, project, password, domain=None, system=None):
         # User, project and domain already include both ID and name here,
         # so there's no need to use the fill_in mode.
         # NOTE(andreaf) We need to set all fields in the returned credentials.
         # Scope is then used to pick only those relevant for the type of
         # token needed by each service client.
+        if project:
+            project_name = project['name']
+            project_id = project['id']
+        else:
+            project_name = None
+            project_id = None
+        if domain:
+            domain_name = domain['name']
+            domain_id = domain['id']
+        else:
+            domain_name = self.creds_domain['name']
+            domain_id = self.creds_domain['id']
         return auth.get_credentials(
             auth_url=None,
             fill_in=False,
             identity_version='v3',
             username=user['name'], user_id=user['id'],
-            project_name=project['name'], project_id=project['id'],
+            project_name=project_name, project_id=project_id,
             password=password,
             project_domain_id=self.creds_domain['id'],
             project_domain_name=self.creds_domain['name'],
-            domain_id=self.creds_domain['id'],
-            domain_name=self.creds_domain['name'])
+            domain_id=domain_id,
+            domain_name=domain_name,
+            system=system)
 
     def assign_user_role_on_domain(self, user, role_name, domain=None):
         """Assign the specified role on a domain
@@ -197,6 +242,23 @@
             LOG.debug("Role %s already assigned on domain %s for user %s",
                       role['id'], domain['id'], user['id'])
 
+    def assign_user_role_on_system(self, user, role_name):
+        """Assign the specified role on the system
+
+        :param user: a user dict
+        :param role_name: name of the role to be assigned
+        """
+        role = self._check_role_exists(role_name)
+        if not role:
+            msg = 'No "%s" role found' % role_name
+            raise lib_exc.NotFound(msg)
+        try:
+            self.roles_client.create_user_role_on_system(
+                user['id'], role['id'])
+        except lib_exc.Conflict:
+            LOG.debug("Role %s already assigned on the system for user %s",
+                      role['id'], user['id'])
+
 
 def get_creds_client(identity_client,
                      projects_client,
diff --git a/tempest/lib/common/cred_provider.py b/tempest/lib/common/cred_provider.py
index 42ed41b..2da206f 100644
--- a/tempest/lib/common/cred_provider.py
+++ b/tempest/lib/common/cred_provider.py
@@ -13,15 +13,15 @@
 #    limitations under the License.
 
 import abc
-
-import six
+from oslo_log import log as logging
 
 from tempest.lib import auth
 from tempest.lib import exceptions
 
+LOG = logging.getLogger(__name__)
 
-@six.add_metaclass(abc.ABCMeta)
-class CredentialProvider(object):
+
+class CredentialProvider(object, metaclass=abc.ABCMeta):
     def __init__(self, identity_version, name=None,
                  network_resources=None, credentials_domain=None,
                  admin_role=None, identity_uri=None):
@@ -60,6 +60,54 @@
         return
 
     @abc.abstractmethod
+    def get_system_admin_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_system_member_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_system_reader_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_domain_admin_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_domain_member_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_domain_reader_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_project_admin_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_project_alt_admin_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_project_member_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_project_alt_member_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_project_reader_creds(self):
+        return
+
+    @abc.abstractmethod
+    def get_project_alt_reader_creds(self):
+        return
+
+    @abc.abstractmethod
     def clear_creds(self):
         return
 
@@ -72,13 +120,25 @@
         return
 
     @abc.abstractmethod
-    def get_creds_by_roles(self, roles, force_new=False):
+    def get_creds_by_roles(self, roles, force_new=False, scope=None):
         return
 
     @abc.abstractmethod
     def is_role_available(self, role):
         return
 
+    def cleanup_default_secgroup(self, security_group_client, tenant):
+        resp_body = security_group_client.list_security_groups(
+            tenant_id=tenant,
+            name="default")
+        secgroups_to_delete = resp_body['security_groups']
+        for secgroup in secgroups_to_delete:
+            try:
+                security_group_client.delete_security_group(secgroup['id'])
+            except exceptions.NotFound:
+                LOG.warning('Security group %s, id %s not found for clean-up',
+                            secgroup['name'], secgroup['id'])
+
 
 class TestResources(object):
     """Readonly Credentials, with network resources added."""
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index 8b82391..be8c0e8 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -16,7 +16,6 @@
 
 import netaddr
 from oslo_log import log as logging
-import six
 
 from tempest.lib.common import cred_client
 from tempest.lib.common import cred_provider
@@ -142,7 +141,14 @@
         else:
             # We use a dedicated client manager for identity client in case we
             # need a different token scope for them.
-            scope = 'domain' if self.identity_admin_domain_scope else 'project'
+            if self.default_admin_creds.system:
+                scope = 'system'
+            elif (self.identity_admin_domain_scope and
+                  (self.default_admin_creds.domain_id or
+                   self.default_admin_creds.domain_name)):
+                scope = 'domain'
+            else:
+                scope = 'project'
             identity_os = clients.ServiceClients(self.default_admin_creds,
                                                  self.identity_uri,
                                                  scope=scope)
@@ -157,62 +163,101 @@
                     os.network.PortsClient(),
                     os.network.SecurityGroupsClient())
 
-    def _create_creds(self, admin=False, roles=None):
+    def _create_creds(self, admin=False, roles=None, scope='project'):
         """Create credentials with random name.
 
-        Creates project and user. When admin flag is True create user
-        with admin role. Assign user with additional roles (for example
-        _member_) and roles requested by caller.
+        Creates user and role assignments on a project, domain, or system. When
+        the admin flag is True, creates user with the admin role on the
+        resource. If roles are provided, assigns those roles on the resource.
+        Otherwise, assigns the user the 'member' role on the resource.
 
         :param admin: Flag if to assign to the user admin role
         :type admin: bool
         :param roles: Roles to assign for the user
         :type roles: list
+        :param str scope: The scope for the role assignment, may be one of
+                          'project', 'domain', or 'system'.
         :return: Readonly Credentials with network resources
+        :raises: Exception if scope is invalid
         """
+        if not roles:
+            roles = []
         root = self.name
 
-        project_name = data_utils.rand_name(root, prefix=self.resource_prefix)
-        project_desc = project_name + "-desc"
-        project = self.creds_client.create_project(
-            name=project_name, description=project_desc)
+        cred_params = {
+            'project': None,
+            'domain': None,
+            'system': None
+        }
+        if scope == 'project':
+            project_name = data_utils.rand_name(
+                root, prefix=self.resource_prefix)
+            project_desc = project_name + '-desc'
+            project = self.creds_client.create_project(
+                name=project_name, description=project_desc)
 
-        # NOTE(andreaf) User and project can be distinguished from the context,
-        # having the same ID in both makes it easier to match them and debug.
-        username = project_name
-        user_password = data_utils.rand_password()
-        email = data_utils.rand_name(
-            root, prefix=self.resource_prefix) + "@example.com"
-        user = self.creds_client.create_user(
-            username, user_password, project, email)
-        role_assigned = False
+            # NOTE(andreaf) User and project can be distinguished from the
+            # context, having the same ID in both makes it easier to match them
+            # and debug.
+            username = project_name + '-project'
+            cred_params['project'] = project
+        elif scope == 'domain':
+            domain_name = data_utils.rand_name(
+                root, prefix=self.resource_prefix)
+            domain_desc = domain_name + '-desc'
+            domain = self.creds_client.create_domain(
+                name=domain_name, description=domain_desc)
+            username = domain_name + '-domain'
+            cred_params['domain'] = domain
+        elif scope == 'system':
+            prefix = data_utils.rand_name(root, prefix=self.resource_prefix)
+            username = prefix + '-system'
+            cred_params['system'] = 'all'
+        else:
+            raise lib_exc.InvalidScopeType(scope=scope)
         if admin:
-            self.creds_client.assign_user_role(user, project, self.admin_role)
-            role_assigned = True
+            username += '-admin'
+        elif roles and len(roles) == 1:
+            username += '-' + roles[0]
+        user_password = data_utils.rand_password()
+        cred_params['password'] = user_password
+        user = self.creds_client.create_user(
+            username, user_password)
+        cred_params['user'] = user
+        roles_to_assign = [r for r in roles]
+        if admin:
+            roles_to_assign.append(self.admin_role)
+            if scope == 'project':
+                self.creds_client.assign_user_role(
+                    user, project, self.identity_admin_role)
             if (self.identity_version == 'v3' and
                     self.identity_admin_domain_scope):
                 self.creds_client.assign_user_role_on_domain(
                     user, self.identity_admin_role)
         # Add roles specified in config file
-        for conf_role in self.extra_roles:
-            self.creds_client.assign_user_role(user, project, conf_role)
-            role_assigned = True
-        # Add roles requested by caller
-        if roles:
-            for role in roles:
-                self.creds_client.assign_user_role(user, project, role)
-                role_assigned = True
+        roles_to_assign.extend(self.extra_roles)
+        # If there are still no roles, default to 'member'
         # NOTE(mtreinish) For a user to have access to a project with v3 auth
         # it must beassigned a role on the project. So we need to ensure that
         # our newly created user has a role on the newly created project.
-        if self.identity_version == 'v3' and not role_assigned:
+        if not roles_to_assign and self.identity_version == 'v3':
+            roles_to_assign = ['member']
             try:
                 self.creds_client.create_user_role('member')
             except lib_exc.Conflict:
                 LOG.warning('member role already exists, ignoring conflict.')
-            self.creds_client.assign_user_role(user, project, 'member')
+        for role in roles_to_assign:
+            if scope == 'project':
+                self.creds_client.assign_user_role(user, project, role)
+            elif scope == 'domain':
+                self.creds_client.assign_user_role_on_domain(
+                    user, role, domain)
+            elif scope == 'system':
+                self.creds_client.assign_user_role_on_system(user, role)
+        LOG.info("Dynamic test user %s is created with scope %s and roles: %s",
+                 user['id'], scope, roles_to_assign)
 
-        creds = self.creds_client.get_credentials(user, project, user_password)
+        creds = self.creds_client.get_credentials(**cred_params)
         return cred_provider.TestResources(creds)
 
     def _create_network_resources(self, tenant_id):
@@ -296,7 +341,7 @@
                             tenant_id=tenant_id,
                             enable_dhcp=self.network_resources['dhcp'],
                             ip_version=(ipaddress.ip_network(
-                                six.text_type(subnet_cidr)).version))
+                                str(subnet_cidr)).version))
                 else:
                     resp_body = self.subnets_admin_client.\
                         create_subnet(network_id=network_id,
@@ -304,7 +349,7 @@
                                       name=subnet_name,
                                       tenant_id=tenant_id,
                                       ip_version=(ipaddress.ip_network(
-                                          six.text_type(subnet_cidr)).version))
+                                          str(subnet_cidr)).version))
                 break
             except lib_exc.BadRequest as e:
                 if 'overlaps with another subnet' not in str(e):
@@ -327,50 +372,127 @@
         self.routers_admin_client.add_router_interface(router_id,
                                                        subnet_id=subnet_id)
 
-    def get_credentials(self, credential_type):
-        if self._creds.get(str(credential_type)):
+    def get_credentials(self, credential_type, scope=None):
+        if not scope and self._creds.get(str(credential_type)):
             credentials = self._creds[str(credential_type)]
+        elif scope and (
+                self._creds.get("%s_%s" % (scope, str(credential_type)))):
+            credentials = self._creds["%s_%s" % (scope, str(credential_type))]
         else:
-            if credential_type in ['primary', 'alt', 'admin']:
+            LOG.debug("Creating new dynamic creds for scope: %s and "
+                      "credential_type: %s", scope, credential_type)
+            if scope:
+                if credential_type in [['admin'], ['alt_admin']]:
+                    credentials = self._create_creds(
+                        admin=True, scope=scope)
+                elif credential_type in [['alt_member'], ['alt_reader']]:
+                    cred_type = credential_type[0][4:]
+                    if isinstance(cred_type, str):
+                        cred_type = [cred_type]
+                    credentials = self._create_creds(
+                        roles=cred_type, scope=scope)
+                else:
+                    credentials = self._create_creds(
+                        roles=credential_type, scope=scope)
+            elif credential_type in ['primary', 'alt', 'admin']:
                 is_admin = (credential_type == 'admin')
                 credentials = self._create_creds(admin=is_admin)
             else:
                 credentials = self._create_creds(roles=credential_type)
-            self._creds[str(credential_type)] = credentials
+            if scope:
+                self._creds["%s_%s" %
+                            (scope, str(credential_type))] = credentials
+            else:
+                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):
-                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)
+            # NOTE(gmann): For 'domain' and 'system' scoped token, there is no
+            # project_id so we are skipping the network creation for both
+            # scope. How these scoped token can create the network, Nova
+            # server or other project mapped resources is one of the open
+            # question and discussed a lot in Xena cycle PTG. Once we sort
+            # out that then if needed we can update the network creation here.
+            if (not scope or scope == 'project'):
+                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)
+            else:
+                LOG.info("Network resources are not created for scope: %s",
+                         scope)
         return credentials
 
+    # TODO(gmann): Remove this method in favor of get_project_member_creds()
+    # after the deprecation phase.
     def get_primary_creds(self):
         return self.get_credentials('primary')
 
+    # TODO(gmann): Remove this method in favor of get_project_admin_creds()
+    # after the deprecation phase.
     def get_admin_creds(self):
         return self.get_credentials('admin')
 
+    # TODO(gmann): Replace this method with more appropriate name.
+    # like get_project_alt_member_creds()
     def get_alt_creds(self):
         return self.get_credentials('alt')
 
-    def get_creds_by_roles(self, roles, force_new=False):
+    def get_system_admin_creds(self):
+        return self.get_credentials(['admin'], scope='system')
+
+    def get_system_member_creds(self):
+        return self.get_credentials(['member'], scope='system')
+
+    def get_system_reader_creds(self):
+        return self.get_credentials(['reader'], scope='system')
+
+    def get_domain_admin_creds(self):
+        return self.get_credentials(['admin'], scope='domain')
+
+    def get_domain_member_creds(self):
+        return self.get_credentials(['member'], scope='domain')
+
+    def get_domain_reader_creds(self):
+        return self.get_credentials(['reader'], scope='domain')
+
+    def get_project_admin_creds(self):
+        return self.get_credentials(['admin'], scope='project')
+
+    def get_project_alt_admin_creds(self):
+        return self.get_credentials(['alt_admin'], scope='project')
+
+    def get_project_member_creds(self):
+        return self.get_credentials(['member'], scope='project')
+
+    def get_project_alt_member_creds(self):
+        return self.get_credentials(['alt_member'], scope='project')
+
+    def get_project_reader_creds(self):
+        return self.get_credentials(['reader'], scope='project')
+
+    def get_project_alt_reader_creds(self):
+        return self.get_credentials(['alt_reader'], scope='project')
+
+    def get_creds_by_roles(self, roles, force_new=False, scope=None):
         roles = list(set(roles))
         # The roles list as a str will become the index as the dict key for
         # the created credentials set in the dynamic_creds dict.
-        exist_creds = self._creds.get(str(roles))
+        creds_name = str(roles)
+        if scope:
+            creds_name = "%s_%s" % (scope, str(roles))
+        exist_creds = self._creds.get(creds_name)
         # If force_new flag is True 2 cred sets with the same roles are needed
         # handle this by creating a separate index for old one to store it
         # separately for cleanup
         if exist_creds and force_new:
-            new_index = str(roles) + '-' + str(len(self._creds))
+            new_index = creds_name + '-' + str(len(self._creds))
             self._creds[new_index] = exist_creds
-            del self._creds[str(roles)]
-        return self.get_credentials(roles)
+            del self._creds[creds_name]
+        return self.get_credentials(roles, scope=scope)
 
     def _clear_isolated_router(self, router_id, router_name):
         client = self.routers_admin_client
@@ -396,18 +518,6 @@
             LOG.warning('network with name: %s not found for delete',
                         network_name)
 
-    def _cleanup_default_secgroup(self, tenant):
-        nsg_client = self.security_groups_admin_client
-        resp_body = nsg_client.list_security_groups(tenant_id=tenant,
-                                                    name="default")
-        secgroups_to_delete = resp_body['security_groups']
-        for secgroup in secgroups_to_delete:
-            try:
-                nsg_client.delete_security_group(secgroup['id'])
-            except lib_exc.NotFound:
-                LOG.warning('Security group %s, id %s not found for clean-up',
-                            secgroup['name'], secgroup['id'])
-
     def _clear_isolated_net_resources(self):
         client = self.routers_admin_client
         for cred in self._creds:
@@ -443,20 +553,21 @@
         if not self._creds:
             return
         self._clear_isolated_net_resources()
-        for creds in six.itervalues(self._creds):
+        for creds in self._creds.values():
             try:
                 self.creds_client.delete_user(creds.user_id)
             except lib_exc.NotFound:
                 LOG.warning("user with name: %s not found for delete",
                             creds.username)
             # NOTE(zhufl): Only when neutron's security_group ext is
-            # enabled, _cleanup_default_secgroup will not raise error. But
+            # enabled, cleanup_default_secgroup will not raise error. But
             # here cannot use test_utils.is_extension_enabled for it will cause
             # "circular dependency". So here just use try...except to
             # ensure tenant deletion without big changes.
             try:
                 if self.neutron_available:
-                    self._cleanup_default_secgroup(creds.tenant_id)
+                    self.cleanup_default_secgroup(
+                        self.security_groups_admin_client, creds.tenant_id)
             except lib_exc.NotFound:
                 LOG.warning("failed to cleanup tenant %s's secgroup",
                             creds.tenant_name)
@@ -465,6 +576,16 @@
             except lib_exc.NotFound:
                 LOG.warning("tenant with name: %s not found for delete",
                             creds.tenant_name)
+
+            # if cred is domain scoped, delete ephemeral domain
+            # do not delete default domain
+            if (hasattr(creds, 'domain_id') and
+                    creds.domain_id != creds.project_domain_id):
+                try:
+                    self.creds_client.delete_domain(creds.domain_id)
+                except lib_exc.NotFound:
+                    LOG.warning("domain with name: %s not found for delete",
+                                creds.domain_name)
         self._creds = {}
 
     def is_multi_user(self):
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
index 8c1a802..33f871b 100644
--- a/tempest/lib/common/http.py
+++ b/tempest/lib/common/http.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
 import urllib3
 
 
@@ -89,7 +88,7 @@
                 for key, value in info.getheaders().items():
                     # We assume HTTP header name to be string, not random
                     # bytes, thus ensure we have string keys.
-                    self[six.u(key).lower()] = value
+                    self[str(key).lower()] = value
                 self.status = info.status
                 self['status'] = str(self.status)
                 self.reason = info.reason
diff --git a/tempest/lib/common/jsonschema_validator.py b/tempest/lib/common/jsonschema_validator.py
index bbf5e89..1618175 100644
--- a/tempest/lib/common/jsonschema_validator.py
+++ b/tempest/lib/common/jsonschema_validator.py
@@ -15,7 +15,6 @@
 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
@@ -32,7 +31,7 @@
 @FORMAT_CHECKER.checks('iso8601-date-time')
 def _validate_datetime_format(instance):
     try:
-        if isinstance(instance, jsonschema.compat.str_types):
+        if instance is not None:
             timeutils.parse_isotime(instance)
     except ValueError:
         return False
@@ -43,7 +42,7 @@
 @jsonschema.FormatChecker.cls_checks('base64')
 def _validate_base64_format(instance):
     try:
-        if isinstance(instance, six.text_type):
+        if isinstance(instance, str):
             instance = instance.encode('utf-8')
         base64.decode_as_bytes(instance)
     except TypeError:
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index 641d727..6d948cf 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -12,12 +12,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import hashlib
 import os
 
 from oslo_concurrency import lockutils
 from oslo_log import log as logging
-import six
+from oslo_utils.secretutils import md5
 import yaml
 
 from tempest.lib import auth
@@ -104,15 +103,24 @@
         return hash_dict
 
     @classmethod
+    def _append_scoped_role(cls, scope, role, account_hash, hash_dict):
+        key = "%s_%s" % (scope, role)
+        hash_dict['scoped_roles'].setdefault(key, [])
+        hash_dict['scoped_roles'][key].append(account_hash)
+        return hash_dict
+
+    @classmethod
     def get_hash_dict(cls, accounts, admin_role,
                       object_storage_operator_role=None,
                       object_storage_reseller_admin_role=None):
-        hash_dict = {'roles': {}, 'creds': {}, 'networks': {}}
+        hash_dict = {'roles': {}, 'creds': {}, 'networks': {},
+                     'scoped_roles': {}}
 
         # Loop over the accounts read from the yaml file
         for account in accounts:
             roles = []
             types = []
+            scope = None
             resources = []
             if 'roles' in account:
                 roles = account.pop('roles')
@@ -120,15 +128,24 @@
                 types = account.pop('types')
             if 'resources' in account:
                 resources = account.pop('resources')
-            temp_hash = hashlib.md5()
+            if 'project_name' in account:
+                scope = 'project'
+            elif 'domain_name' in account:
+                scope = 'domain'
+            elif 'system' in account:
+                scope = 'system'
+            temp_hash = md5(usedforsecurity=False)
             account_for_hash = dict((k, v) for (k, v) in account.items()
                                     if k in cls.HASH_CRED_FIELDS)
-            temp_hash.update(six.text_type(account_for_hash).encode('utf-8'))
+            temp_hash.update(str(account_for_hash).encode('utf-8'))
             temp_hash_key = temp_hash.hexdigest()
             hash_dict['creds'][temp_hash_key] = account
             for role in roles:
                 hash_dict = cls._append_role(role, temp_hash_key,
                                              hash_dict)
+                if scope:
+                    hash_dict = cls._append_scoped_role(
+                        scope, role, temp_hash_key, hash_dict)
             # If types are set for the account append the matching role
             # subdict with the hash
             for type in types:
@@ -201,17 +218,25 @@
                'the credentials for this allocation request' % ','.join(names))
         raise lib_exc.InvalidCredentials(msg)
 
-    def _get_match_hash_list(self, roles=None):
+    def _get_match_hash_list(self, roles=None, scope=None):
         hashes = []
         if roles:
             # Loop over all the creds for each role in the subdict and generate
             # a list of cred lists for each role
             for role in roles:
-                temp_hashes = self.hash_dict['roles'].get(role, None)
-                if not temp_hashes:
-                    raise lib_exc.InvalidCredentials(
-                        "No credentials with role: %s specified in the "
-                        "accounts ""file" % role)
+                if scope:
+                    key = "%s_%s" % (scope, role)
+                    temp_hashes = self.hash_dict['scoped_roles'].get(key)
+                    if not temp_hashes:
+                        raise lib_exc.InvalidCredentials(
+                            "No credentials matching role: %s, scope: %s "
+                            "specified in the accounts file" % (role, scope))
+                else:
+                    temp_hashes = self.hash_dict['roles'].get(role, None)
+                    if not temp_hashes:
+                        raise lib_exc.InvalidCredentials(
+                            "No credentials with role: %s specified in the "
+                            "accounts file" % role)
                 hashes.append(temp_hashes)
             # Take the list of lists and do a boolean and between each list to
             # find the creds which fall under all the specified roles
@@ -239,8 +264,8 @@
         temp_creds.pop('password')
         return temp_creds
 
-    def _get_creds(self, roles=None):
-        useable_hashes = self._get_match_hash_list(roles)
+    def _get_creds(self, roles=None, scope=None):
+        useable_hashes = self._get_match_hash_list(roles, scope)
         if not useable_hashes:
             msg = 'No users configured for type/roles %s' % roles
             raise lib_exc.InvalidCredentials(msg)
@@ -282,6 +307,8 @@
         self.remove_hash(_hash)
         LOG.info("%s returned allocated creds:\n%s", self.name, clean_creds)
 
+    # TODO(gmann): Remove this method in favor of get_project_member_creds()
+    # after the deprecation phase.
     def get_primary_creds(self):
         if self._creds.get('primary'):
             return self._creds.get('primary')
@@ -289,6 +316,8 @@
         self._creds['primary'] = net_creds
         return net_creds
 
+    # TODO(gmann): Replace this method with more appropriate name.
+    # like get_project_alt_member_creds()
     def get_alt_creds(self):
         if self._creds.get('alt'):
             return self._creds.get('alt')
@@ -296,9 +325,84 @@
         self._creds['alt'] = net_creds
         return net_creds
 
-    def get_creds_by_roles(self, roles, force_new=False):
+    def get_system_admin_creds(self):
+        if self._creds.get('system_admin'):
+            return self._creds.get('system_admin')
+        system_admin = self._get_creds(['admin'], scope='system')
+        self._creds['system_admin'] = system_admin
+        return system_admin
+
+    def get_system_member_creds(self):
+        if self._creds.get('system_member'):
+            return self._creds.get('system_member')
+        system_member = self._get_creds(['member'], scope='system')
+        self._creds['system_member'] = system_member
+        return system_member
+
+    def get_system_reader_creds(self):
+        if self._creds.get('system_reader'):
+            return self._creds.get('system_reader')
+        system_reader = self._get_creds(['reader'], scope='system')
+        self._creds['system_reader'] = system_reader
+        return system_reader
+
+    def get_domain_admin_creds(self):
+        if self._creds.get('domain_admin'):
+            return self._creds.get('domain_admin')
+        domain_admin = self._get_creds(['admin'], scope='domain')
+        self._creds['domain_admin'] = domain_admin
+        return domain_admin
+
+    def get_domain_member_creds(self):
+        if self._creds.get('domain_member'):
+            return self._creds.get('domain_member')
+        domain_member = self._get_creds(['member'], scope='domain')
+        self._creds['domain_member'] = domain_member
+        return domain_member
+
+    def get_domain_reader_creds(self):
+        if self._creds.get('domain_reader'):
+            return self._creds.get('domain_reader')
+        domain_reader = self._get_creds(['reader'], scope='domain')
+        self._creds['domain_reader'] = domain_reader
+        return domain_reader
+
+    def get_project_admin_creds(self):
+        if self._creds.get('project_admin'):
+            return self._creds.get('project_admin')
+        project_admin = self._get_creds(['admin'], scope='project')
+        self._creds['project_admin'] = project_admin
+        return project_admin
+
+    def get_project_alt_admin_creds(self):
+        # TODO(gmann): Implement alt admin hash.
+        return
+
+    def get_project_member_creds(self):
+        if self._creds.get('project_member'):
+            return self._creds.get('project_member')
+        project_member = self._get_creds(['member'], scope='project')
+        self._creds['project_member'] = project_member
+        return project_member
+
+    def get_project_alt_member_creds(self):
+        # TODO(gmann): Implement alt member hash.
+        return
+
+    def get_project_reader_creds(self):
+        if self._creds.get('project_reader'):
+            return self._creds.get('project_reader')
+        project_reader = self._get_creds(['reader'], scope='project')
+        self._creds['project_reader'] = project_reader
+        return project_reader
+
+    def get_project_alt_reader_creds(self):
+        # TODO(gmann): Implement alt reader hash.
+        return
+
+    def get_creds_by_roles(self, roles, force_new=False, scope=None):
         roles = list(set(roles))
-        exist_creds = self._creds.get(six.text_type(roles).encode(
+        exist_creds = self._creds.get(str(roles).encode(
             'utf-8'), None)
         # The force kwarg is used to allocate an additional set of creds with
         # the same role list. The index used for the previously allocation
@@ -308,17 +412,19 @@
         elif exist_creds and force_new:
             # NOTE(andreaf) In py3.x encode returns bytes, and b'' is bytes
             # In py2.7 encode returns strings, and b'' is still string
-            new_index = six.text_type(roles).encode('utf-8') + b'-' + \
-                six.text_type(len(self._creds)).encode('utf-8')
+            new_index = str(roles).encode('utf-8') + b'-' + \
+                str(len(self._creds)).encode('utf-8')
             self._creds[new_index] = exist_creds
         net_creds = self._get_creds(roles=roles)
-        self._creds[six.text_type(roles).encode('utf-8')] = net_creds
+        self._creds[str(roles).encode('utf-8')] = net_creds
         return net_creds
 
     def clear_creds(self):
         for creds in self._creds.values():
             self.remove_credentials(creds)
 
+    # TODO(gmann): Remove this method in favor of get_project_admin_creds()
+    # after the deprecation phase.
     def get_admin_creds(self):
         return self.get_creds_by_roles([self.admin_role])
 
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 0513e90..ef14dfc 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -14,17 +14,16 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import collections
+from collections import abc
 import email.utils
 import re
 import time
+import urllib
 
 import jsonschema
 from oslo_log import log as logging
 from oslo_log import versionutils
 from oslo_serialization import jsonutils as json
-import six
-from six.moves import urllib
 
 from tempest.lib.common import http
 from tempest.lib.common import jsonschema_validator
@@ -104,16 +103,18 @@
                                        'location', 'proxy-authenticate',
                                        'retry-after', 'server',
                                        'vary', 'www-authenticate'))
-        dscv = disable_ssl_certificate_validation
+        self.dscv = disable_ssl_certificate_validation
 
         if proxy_url:
             self.http_obj = http.ClosingProxyHttp(
                 proxy_url,
-                disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
+                disable_ssl_certificate_validation=self.dscv,
+                ca_certs=ca_certs,
                 timeout=http_timeout, follow_redirects=follow_redirects)
         else:
             self.http_obj = http.ClosingHttp(
-                disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
+                disable_ssl_certificate_validation=self.dscv,
+                ca_certs=ca_certs,
                 timeout=http_timeout, follow_redirects=follow_redirects)
 
     def get_headers(self, accept_type=None, send_type=None):
@@ -416,7 +417,7 @@
     def _safe_body(self, body, maxlen=4096):
         # convert a structure into a string safely
         try:
-            text = six.text_type(body)
+            text = str(body)
         except UnicodeDecodeError:
             # if this isn't actually text, return marker that
             return "<BinaryData: removed>"
@@ -507,7 +508,7 @@
             if not hasattr(body, "keys") or len(body.keys()) != 1:
                 return body
             # Just return the "wrapped" element
-            first_key, first_item = six.next(six.iteritems(body))
+            _, first_item = tuple(body.items())[0]
             if isinstance(first_item, (dict, list)):
                 return first_item
         except (ValueError, IndexError):
@@ -883,12 +884,12 @@
                                                     resp=resp)
 
     def is_absolute_limit(self, resp, resp_body):
-        if (not isinstance(resp_body, collections.Mapping) or
+        if (not isinstance(resp_body, abc.Mapping) or
                 'retry-after' not in resp):
             return True
         return 'exceed' in resp_body.get('message', 'blabla')
 
-    def wait_for_resource_deletion(self, id):
+    def wait_for_resource_deletion(self, id, *args, **kwargs):
         """Waits for a resource to be deleted
 
         This method will loop over is_resource_deleted until either
@@ -901,13 +902,18 @@
         """
         start_time = int(time.time())
         while True:
-            if self.is_resource_deleted(id):
+            if self.is_resource_deleted(id, *args, **kwargs):
                 return
             if int(time.time()) - start_time >= self.build_timeout:
                 message = ('Failed to delete %(resource_type)s %(id)s within '
-                           'the required time (%(timeout)s s).' %
+                           'the required time (%(timeout)s s). Timer started '
+                           'at %(start_time)s. Timer ended at %(end_time)s. '
+                           'Waited for %(wait_time)s s.' %
                            {'resource_type': self.resource_type, 'id': id,
-                            'timeout': self.build_timeout})
+                            'timeout': self.build_timeout,
+                            'start_time': start_time,
+                            'end_time': int(time.time()),
+                            'wait_time': int(time.time()) - start_time})
                 caller = test_utils.find_test_caller()
                 if caller:
                     message = '(%s) %s' % (caller, message)
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index 3a05f27..eb03faa 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -14,13 +14,13 @@
 #    under the License.
 
 
+import io
 import select
 import socket
 import time
 import warnings
 
 from oslo_log import log as logging
-import six
 
 from tempest.lib import exceptions
 
@@ -37,7 +37,7 @@
 
     def __init__(self, host, username, password=None, timeout=300, pkey=None,
                  channel_timeout=10, look_for_keys=False, key_filename=None,
-                 port=22, proxy_client=None):
+                 port=22, proxy_client=None, ssh_key_type='rsa'):
         """SSH client.
 
         Many of parameters are just passed to the underlying implementation
@@ -59,15 +59,23 @@
         :param proxy_client: Another SSH client to provide a transport
             for ssh-over-ssh.  The default is None, which means
             not to use ssh-over-ssh.
+        :param ssh_key_type: ssh key type (rsa, ecdsa)
         :type proxy_client: ``tempest.lib.common.ssh.Client`` object
         """
         self.host = host
         self.username = username
         self.port = port
         self.password = password
-        if isinstance(pkey, six.string_types):
-            pkey = paramiko.RSAKey.from_private_key(
-                six.StringIO(str(pkey)))
+        if isinstance(pkey, str):
+            if ssh_key_type == 'rsa':
+                pkey = paramiko.RSAKey.from_private_key(
+                    io.StringIO(str(pkey)))
+            elif ssh_key_type == 'ecdsa':
+                pkey = paramiko.ECDSAKey.from_private_key(
+                    io.StringIO(str(pkey)))
+            else:
+                raise exceptions.SSHClientUnsupportedKeyType(
+                    key_type=ssh_key_type)
         self.pkey = pkey
         self.look_for_keys = look_for_keys
         self.key_filename = key_filename
diff --git a/tempest/lib/common/thread.py b/tempest/lib/common/thread.py
index b47d40d..ef0ec73 100644
--- a/tempest/lib/common/thread.py
+++ b/tempest/lib/common/thread.py
@@ -13,13 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-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
+# 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 7f94612..1e94f86 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -19,7 +19,6 @@
 import uuid
 
 from oslo_utils import uuidutils
-import six.moves
 
 
 def rand_uuid():
@@ -129,7 +128,7 @@
     :rtype: string
     """
     guid = []
-    for i in range(8):
+    for _ in range(8):
         guid.append("%02x" % random.randint(0x00, 0xff))
     return ':'.join(guid)
 
@@ -169,7 +168,9 @@
     :return: size randomly bytes
     :rtype: string
     """
-    return b''.join([six.int2byte(random.randint(0, 255))
+    if size > 1 << 20:
+        raise RuntimeError('Size should be less than 1MiB')
+    return b''.join([bytes((random.randint(0, 255),))
                      for i in range(size)])
 
 
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index 71fed02..224f3bf 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -15,7 +15,6 @@
 
 import netaddr
 from oslo_log import log as logging
-import six
 
 from tempest.lib.common import ssh
 from tempest.lib.common.utils import test_utils
@@ -55,8 +54,8 @@
                             except Exception:
                                 msg = 'Could not get console_log for server %s'
                                 LOG.debug(msg, self.server['id'])
-                    # re-raise the original ssh timeout exception
-                    six.reraise(*original_exception)
+                    # raise the original ssh timeout exception
+                    raise
                 finally:
                     # Delete the traceback to avoid circular references
                     _, _, trace = original_exception
@@ -70,7 +69,7 @@
                  server=None, servers_client=None, ssh_timeout=300,
                  connect_timeout=60, console_output_enabled=True,
                  ssh_shell_prologue="set -eu -o pipefail; PATH=$PATH:/sbin;",
-                 ping_count=1, ping_size=56):
+                 ping_count=1, ping_size=56, ssh_key_type='rsa'):
         """Executes commands in a VM over ssh
 
         :param ip_address: IP address to ssh to
@@ -85,6 +84,7 @@
         :param ssh_shell_prologue: Shell fragments to use before command
         :param ping_count: Number of ping packets
         :param ping_size: Packet size for ping packets
+        :param ssh_key_type: ssh key type (rsa, ecdsa)
         """
         self.server = server
         self.servers_client = servers_client
@@ -93,10 +93,12 @@
         self.ssh_shell_prologue = ssh_shell_prologue
         self.ping_count = ping_count
         self.ping_size = ping_size
+        self.ssh_key_type = ssh_key_type
 
         self.ssh_client = ssh.Client(ip_address, username, password,
                                      ssh_timeout, pkey=pkey,
-                                     channel_timeout=connect_timeout)
+                                     channel_timeout=connect_timeout,
+                                     ssh_key_type=ssh_key_type)
 
     @debug_ssh
     def exec_command(self, cmd):
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index ebe2d61..a4633ca 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -16,7 +16,6 @@
 import uuid
 
 from oslo_log import log as logging
-import six
 import testtools
 
 from tempest.lib import exceptions as lib_exc
@@ -72,19 +71,13 @@
     def decorator(f):
         @functools.wraps(f)
         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:
-                bug = kwargs['bug']
+            condition = kwargs.get('condition', True)
+            bug = kwargs.get('bug', None)
+            if bug and condition:
                 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)
+                raise testtools.TestCase.skipException(
+                    "Skipped until bug: %s is resolved." % bug_url)
             return f(*func_args, **func_kwargs)
         return wrapper
     return decorator
@@ -116,7 +109,7 @@
 
 def idempotent_id(id):
     """Stub for metadata decorator"""
-    if not isinstance(id, six.string_types):
+    if not isinstance(id, str):
         raise TypeError('Test idempotent_id must be string not %s'
                         '' % type(id).__name__)
     uuid.UUID(id)
@@ -146,7 +139,7 @@
         # 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):
+        if 'type' in kwargs and isinstance(kwargs['type'], str):
             f = testtools.testcase.attr(kwargs['type'])(f)
         elif 'type' in kwargs and isinstance(kwargs['type'], list):
             for attr in kwargs['type']:
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 84b7ee6..dd7885e 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -256,6 +256,10 @@
                "%(port)s and username: %(username)s as parent")
 
 
+class SSHClientUnsupportedKeyType(TempestException):
+    message = ("SSH client: unsupported key type %(key_type)s")
+
+
 class UnknownServiceClient(TempestException):
     message = "Service clients named %(services)s are not known"
 
@@ -294,3 +298,7 @@
 class ConsistencyGroupSnapshotException(TempestException):
     message = ("Consistency group snapshot %(cgsnapshot_id)s failed and is "
                "in ERROR status")
+
+
+class InvalidScopeType(TempestException):
+    message = "Invalid scope %(scope)s"
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 90debd9..8b5c758 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -52,7 +52,6 @@
         'image.v2': image.v2,
         'network': network,
         'object-storage': object_storage,
-        'volume.v1': volume.v1,
         'volume.v2': volume.v2,
         'volume.v3': volume.v3
     }
@@ -257,7 +256,7 @@
     # class should only be used by tests hosted in Tempest.
 
     @removals.removed_kwarg('client_parameters')
-    def __init__(self, credentials, identity_uri, region=None, scope='project',
+    def __init__(self, credentials, identity_uri, region=None, scope=None,
                  disable_ssl_certificate_validation=True, ca_certs=None,
                  trace_requests='', client_parameters=None, proxy_url=None):
         """Service Clients provider
@@ -348,6 +347,14 @@
         self.ca_certs = ca_certs
         self.trace_requests = trace_requests
         self.proxy_url = proxy_url
+        if self.credentials.project_id or self.credentials.project_name:
+            scope = 'project'
+        elif self.credentials.system:
+            scope = 'system'
+        elif self.credentials.domain_id or self.credentials.domain_name:
+            scope = 'domain'
+        else:
+            scope = 'project'
         # Creates an auth provider for the credentials
         self.auth_provider = auth_provider_class(
             self.credentials, self.identity_uri, scope=scope,
diff --git a/tempest/lib/services/compute/__init__.py b/tempest/lib/services/compute/__init__.py
index 91e896a..8d07a45 100644
--- a/tempest/lib/services/compute/__init__.py
+++ b/tempest/lib/services/compute/__init__.py
@@ -14,6 +14,8 @@
 
 from tempest.lib.services.compute.agents_client import AgentsClient
 from tempest.lib.services.compute.aggregates_client import AggregatesClient
+from tempest.lib.services.compute.assisted_volume_snapshots_client import \
+    AssistedVolumeSnapshotsClient
 from tempest.lib.services.compute.availability_zone_client import \
     AvailabilityZoneClient
 from tempest.lib.services.compute.baremetal_nodes_client import \
@@ -63,9 +65,10 @@
 from tempest.lib.services.compute.volumes_client import \
     VolumesClient
 
-__all__ = ['AgentsClient', 'AggregatesClient', 'AvailabilityZoneClient',
-           'BaremetalNodesClient', 'CertificatesClient', 'ExtensionsClient',
-           'FixedIPsClient', 'FlavorsClient', 'FloatingIPPoolsClient',
+__all__ = ['AgentsClient', 'AggregatesClient', 'AssistedVolumeSnapshotsClient',
+           'AvailabilityZoneClient', 'BaremetalNodesClient',
+           'CertificatesClient', 'ExtensionsClient', 'FixedIPsClient',
+           'FlavorsClient', 'FloatingIPPoolsClient',
            'FloatingIPsBulkClient', 'FloatingIPsClient', 'HostsClient',
            'HypervisorClient', 'ImagesClient', 'InstanceUsagesAuditLogClient',
            'InterfacesClient', 'KeyPairsClient', 'LimitsClient',
diff --git a/tempest/lib/services/compute/agents_client.py b/tempest/lib/services/compute/agents_client.py
index 12b3900..bd973dd 100644
--- a/tempest/lib/services/compute/agents_client.py
+++ b/tempest/lib/services/compute/agents_client.py
@@ -12,8 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import agents as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/assisted_volume_snapshots_client.py b/tempest/lib/services/compute/assisted_volume_snapshots_client.py
new file mode 100644
index 0000000..7a949df
--- /dev/null
+++ b/tempest/lib/services/compute/assisted_volume_snapshots_client.py
@@ -0,0 +1,64 @@
+# Copyright 2017 AT&T 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 urllib import parse as urllib
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute import base_compute_client
+
+
+class AssistedVolumeSnapshotsClient(base_compute_client.BaseComputeClient):
+    """Service client for assisted volume snapshots"""
+
+    def delete_assisted_volume_snapshot(self, volume_id, snapshot_id):
+        """Delete snapshot for the given volume id.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/compute/#delete-assisted-volume-snapshot
+
+        :param volume_id: UUID of the volume
+        :param snapshot_id: The UUID of the snapshot
+        """
+        query_param = {'delete_info': json.dumps({'volume_id': volume_id})}
+        resp, body = self.delete("os-assisted-volume-snapshots/%s?%s"
+                                 % (snapshot_id,
+                                    urllib.urlencode(query_param)))
+        return rest_client.ResponseBody(resp, body)
+
+    def create_assisted_volume_snapshot(self, volume_id, snapshot_id,
+                                        **kwargs):
+        """Create a new assisted volume snapshot.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/compute/#create-assisted-volume-snapshots
+
+        :param volume_id: the source volume ID
+        :param snapshot_id: the UUID for a snapshot
+        :param type: Type of snapshot, such as qcow2
+        :param new_file: The name of image file that will be created
+        """
+        url = "os-assisted-volume-snapshots"
+        info = {"snapshot_id": snapshot_id}
+        if kwargs:
+            info.update(kwargs)
+        body = {"snapshot": {"volume_id": volume_id, "create_info": info}}
+        post_body = json.dumps(body)
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/baremetal_nodes_client.py b/tempest/lib/services/compute/baremetal_nodes_client.py
index 3efdbce..83af451 100644
--- a/tempest/lib/services/compute/baremetal_nodes_client.py
+++ b/tempest/lib/services/compute/baremetal_nodes_client.py
@@ -12,8 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import baremetal_nodes \
     as schema
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index e22b5b2..5282405 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import flavors as schema
 from tempest.lib.api_schema.response.compute.v2_1 import flavors_access \
diff --git a/tempest/lib/services/compute/floating_ip_pools_client.py b/tempest/lib/services/compute/floating_ip_pools_client.py
index d3af050..aa065b8 100644
--- a/tempest/lib/services/compute/floating_ip_pools_client.py
+++ b/tempest/lib/services/compute/floating_ip_pools_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/floating_ips_client.py b/tempest/lib/services/compute/floating_ips_client.py
index d7a1a9b..e6b6916 100644
--- a/tempest/lib/services/compute/floating_ips_client.py
+++ b/tempest/lib/services/compute/floating_ips_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/hosts_client.py b/tempest/lib/services/compute/hosts_client.py
index 743b4ec..bbecc3b 100644
--- a/tempest/lib/services/compute/hosts_client.py
+++ b/tempest/lib/services/compute/hosts_client.py
@@ -12,8 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import hosts as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/hypervisor_client.py b/tempest/lib/services/compute/hypervisor_client.py
index 1cbfcc3..e237845 100644
--- a/tempest/lib/services/compute/hypervisor_client.py
+++ b/tempest/lib/services/compute/hypervisor_client.py
@@ -13,12 +13,18 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
 
 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.api_schema.response.compute.v2_33 \
+    import hypervisors as schemav233
+from tempest.lib.api_schema.response.compute.v2_53 \
+    import hypervisors as schemav253
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
@@ -27,9 +33,11 @@
 
     schema_versions_info = [
         {'min': None, 'max': '2.27', 'schema': schemav21},
-        {'min': '2.28', 'max': None, 'schema': schemav228}]
+        {'min': '2.28', 'max': '2.32', 'schema': schemav228},
+        {'min': '2.33', 'max': '2.52', 'schema': schemav233},
+        {'min': '2.53', 'max': None, 'schema': schemav253}]
 
-    def list_hypervisors(self, detail=False):
+    def list_hypervisors(self, detail=False, **kwargs):
         """List hypervisors information."""
         url = 'os-hypervisors'
         schema = self.get_schema(self.schema_versions_info)
@@ -37,14 +45,19 @@
         if detail:
             url += '/detail'
             _schema = schema.list_hypervisors_detail
+        if kwargs:
+            url += '?%s' % urllib.urlencode(kwargs)
 
         resp, body = self.get(url)
         body = json.loads(body)
         self.validate_response(_schema, resp, body)
         return rest_client.ResponseBody(resp, body)
 
-    def show_hypervisor(self, hypervisor_id):
+    def show_hypervisor(self, hypervisor_id, **kwargs):
         """Display the details of the specified hypervisor."""
+        url = 'os-hypervisors/%s' % hypervisor_id
+        if kwargs:
+            url += '?%s' % urllib.urlencode(kwargs)
         resp, body = self.get('os-hypervisors/%s' % hypervisor_id)
         body = json.loads(body)
         schema = self.get_schema(self.schema_versions_info)
diff --git a/tempest/lib/services/compute/images_client.py b/tempest/lib/services/compute/images_client.py
index b252ee9..b6d8d30 100644
--- a/tempest/lib/services/compute/images_client.py
+++ b/tempest/lib/services/compute/images_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-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
diff --git a/tempest/lib/services/compute/interfaces_client.py b/tempest/lib/services/compute/interfaces_client.py
index e1c02fa..9244a4a 100644
--- a/tempest/lib/services/compute/interfaces_client.py
+++ b/tempest/lib/services/compute/interfaces_client.py
@@ -16,15 +16,22 @@
 from oslo_serialization import jsonutils as json
 
 from tempest.lib.api_schema.response.compute.v2_1 import interfaces as schema
+from tempest.lib.api_schema.response.compute.v2_70 import interfaces as \
+    schemav270
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
 
 class InterfacesClient(base_compute_client.BaseComputeClient):
 
+    schema_versions_info = [
+        {'min': None, 'max': '2.69', 'schema': schema},
+        {'min': '2.70', 'max': None, 'schema': schemav270}]
+
     def list_interfaces(self, server_id):
         resp, body = self.get('servers/%s/os-interface' % server_id)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.list_interfaces, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -40,6 +47,7 @@
         resp, body = self.post('servers/%s/os-interface' % server_id,
                                body=post_body)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_create_interfaces, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -47,6 +55,7 @@
         resp, body = self.get('servers/%s/os-interface/%s' % (server_id,
                                                               port_id))
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.get_create_interfaces, resp, body)
         return rest_client.ResponseBody(resp, body)
 
diff --git a/tempest/lib/services/compute/keypairs_client.py b/tempest/lib/services/compute/keypairs_client.py
index 47cf2d0..51a4583 100644
--- a/tempest/lib/services/compute/keypairs_client.py
+++ b/tempest/lib/services/compute/keypairs_client.py
@@ -13,8 +13,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives import serialization
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import keypairs as schemav21
 from tempest.lib.api_schema.response.compute.v2_2 import keypairs as schemav22
@@ -27,6 +32,12 @@
     schema_versions_info = [{'min': None, 'max': '2.1', 'schema': schemav21},
                             {'min': '2.2', 'max': None, 'schema': schemav22}]
 
+    def __init__(self, auth_provider, service, region,
+                 ssh_key_type='rsa', **kwargs):
+        super(KeyPairsClient, self).__init__(
+            auth_provider, service, region, **kwargs)
+        self.ssh_key_type = ssh_key_type
+
     def list_keypairs(self, **params):
         """Lists keypairs that are associated with the account.
 
@@ -66,12 +77,30 @@
         API reference:
         https://docs.openstack.org/api-ref/compute/#create-or-import-keypair
         """
+        pkey = None
+        if (self.ssh_key_type == 'ecdsa' and 'public_key' not in kwargs and
+            ('type' not in kwargs or kwargs['type'] == 'ssh')):
+            # create a ecdsa key and pass the public key into the request
+            pkey = ec.generate_private_key(ec.SECP384R1(), default_backend())
+            pubkey = pkey.public_key().public_bytes(
+                encoding=serialization.Encoding.OpenSSH,
+                format=serialization.PublicFormat.OpenSSH)
+            kwargs['public_key'] = pubkey
+
         post_body = json.dumps({'keypair': kwargs})
         resp, body = self.post("os-keypairs", body=post_body)
         body = json.loads(body)
         schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.create_keypair, resp, body)
-        return rest_client.ResponseBody(resp, body)
+        resp_body = rest_client.ResponseBody(resp, body)
+        if pkey:
+            # add the privkey to the response as it was generated here
+            privkey = pkey.private_bytes(
+                encoding=serialization.Encoding.PEM,
+                format=serialization.PrivateFormat.TraditionalOpenSSL,
+                encryption_algorithm=serialization.NoEncryption())
+            resp_body['keypair']['private_key'] = privkey.decode('utf-8')
+        return resp_body
 
     def delete_keypair(self, keypair_name, **params):
         """Deletes a keypair.
diff --git a/tempest/lib/services/compute/migrations_client.py b/tempest/lib/services/compute/migrations_client.py
index 812dc96..8a6e62a 100644
--- a/tempest/lib/services/compute/migrations_client.py
+++ b/tempest/lib/services/compute/migrations_client.py
@@ -12,8 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import migrations as schema
 from tempest.lib.api_schema.response.compute.v2_23 import migrations \
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
index 12e865e..dd796aa 100644
--- a/tempest/lib/services/compute/quotas_client.py
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-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
diff --git a/tempest/lib/services/compute/security_groups_client.py b/tempest/lib/services/compute/security_groups_client.py
index 9493144..0bba990 100644
--- a/tempest/lib/services/compute/security_groups_client.py
+++ b/tempest/lib/services/compute/security_groups_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import \
     security_groups as schema
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 6723516..ed3d4c0 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -16,9 +16,9 @@
 #    under the License.
 
 import copy
+from urllib import parse as urllib
 
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import \
     security_groups as security_groups_schema
@@ -29,13 +29,17 @@
 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_51 import servers as schemav251
 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_58 import servers as schemav258
 from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
+from tempest.lib.api_schema.response.compute.v2_62 import servers as schemav262
 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_73 import servers as schemav273
+from tempest.lib.api_schema.response.compute.v2_79 import servers as schemav279
 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
@@ -55,13 +59,17 @@
         {'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': '2.53', 'schema': schemav248},
+        {'min': '2.48', 'max': '2.50', 'schema': schemav248},
+        {'min': '2.51', 'max': '2.53', 'schema': schemav251},
         {'min': '2.54', 'max': '2.56', 'schema': schemav254},
-        {'min': '2.57', 'max': '2.62', 'schema': schemav257},
+        {'min': '2.57', 'max': '2.57', 'schema': schemav257},
+        {'min': '2.58', 'max': '2.61', 'schema': schemav258},
+        {'min': '2.62', 'max': '2.62', 'schema': schemav262},
         {'min': '2.63', 'max': '2.69', 'schema': schemav263},
         {'min': '2.70', 'max': '2.70', 'schema': schemav270},
         {'min': '2.71', 'max': '2.72', 'schema': schemav271},
-        {'min': '2.73', 'max': None, 'schema': schemav273}]
+        {'min': '2.73', 'max': '2.78', 'schema': schemav273},
+        {'min': '2.79', 'max': None, 'schema': schemav279}]
 
     def __init__(self, auth_provider, service, region,
                  enable_instance_password=True, **kwargs):
@@ -646,7 +654,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://docs.openstack.org/api-ref/compute/#create-remote-console
+        https://docs.openstack.org/api-ref/compute/#create-console
         """
         param = {
             'remote_console': {
@@ -713,6 +721,7 @@
         resp, body = self.get("servers/%s/os-instance-actions/%s" %
                               (server_id, request_id))
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.show_instance_action, resp, body)
         return rest_client.ResponseBody(resp, body)
 
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
index 4e3383f..7d9f3e2 100644
--- a/tempest/lib/services/compute/services_client.py
+++ b/tempest/lib/services/compute/services_client.py
@@ -14,8 +14,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import services as schema
 from tempest.lib.api_schema.response.compute.v2_11 import services \
diff --git a/tempest/lib/services/compute/snapshots_client.py b/tempest/lib/services/compute/snapshots_client.py
index 225eb8d..2e6f7cf 100644
--- a/tempest/lib/services/compute/snapshots_client.py
+++ b/tempest/lib/services/compute/snapshots_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import snapshots as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/tenant_usages_client.py b/tempest/lib/services/compute/tenant_usages_client.py
index a34730c..b47d917 100644
--- a/tempest/lib/services/compute/tenant_usages_client.py
+++ b/tempest/lib/services/compute/tenant_usages_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import tenant_usages
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/volumes_client.py b/tempest/lib/services/compute/volumes_client.py
index 11282ee..52172ed 100644
--- a/tempest/lib/services/compute/volumes_client.py
+++ b/tempest/lib/services/compute/volumes_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import volumes as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v2/identity_client.py b/tempest/lib/services/identity/v2/identity_client.py
index d7526f3..6239ba6 100644
--- a/tempest/lib/services/identity/v2/identity_client.py
+++ b/tempest/lib/services/identity/v2/identity_client.py
@@ -10,8 +10,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v2/roles_client.py b/tempest/lib/services/identity/v2/roles_client.py
index a133fc3..1580c33 100644
--- a/tempest/lib/services/identity/v2/roles_client.py
+++ b/tempest/lib/services/identity/v2/roles_client.py
@@ -10,8 +10,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v2/services_client.py b/tempest/lib/services/identity/v2/services_client.py
index fc51cb4..2a0e5ca 100644
--- a/tempest/lib/services/identity/v2/services_client.py
+++ b/tempest/lib/services/identity/v2/services_client.py
@@ -12,8 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v2/tenants_client.py b/tempest/lib/services/identity/v2/tenants_client.py
index 09618ad..3435835 100644
--- a/tempest/lib/services/identity/v2/tenants_client.py
+++ b/tempest/lib/services/identity/v2/tenants_client.py
@@ -12,8 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v2/token_client.py b/tempest/lib/services/identity/v2/token_client.py
index 9f10f58..1191154 100644
--- a/tempest/lib/services/identity/v2/token_client.py
+++ b/tempest/lib/services/identity/v2/token_client.py
@@ -12,7 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from oslo_log import log as logging
 from oslo_serialization import jsonutils as json
 
 from tempest.lib.common import rest_client
@@ -125,15 +124,3 @@
             return body['token']['id'], body
         else:
             return body['token']['id']
-
-
-class TokenClientJSON(TokenClient):
-    LOG = logging.getLogger(__name__)
-
-    def _warn(self):
-        self.LOG.warning("%s class was deprecated and renamed to %s",
-                         self.__class__.__name__, 'TokenClient')
-
-    def __init__(self, *args, **kwargs):
-        self._warn()
-        super(TokenClientJSON, self).__init__(*args, **kwargs)
diff --git a/tempest/lib/services/identity/v2/users_client.py b/tempest/lib/services/identity/v2/users_client.py
index 72f29be..c3217c9 100644
--- a/tempest/lib/services/identity/v2/users_client.py
+++ b/tempest/lib/services/identity/v2/users_client.py
@@ -10,8 +10,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index 86fa991..af09fb1 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -32,6 +32,7 @@
 from tempest.lib.services.identity.v3.identity_client import IdentityClient
 from tempest.lib.services.identity.v3.inherited_roles_client import \
     InheritedRolesClient
+from tempest.lib.services.identity.v3.limits_client import LimitsClient
 from tempest.lib.services.identity.v3.oauth_consumers_client import \
     OAUTHConsumerClient
 from tempest.lib.services.identity.v3.oauth_token_client import \
@@ -55,7 +56,8 @@
            'DomainConfigurationClient', 'EndPointGroupsClient',
            'EndPointsClient', 'EndPointsFilterClient',
            'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
-           'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient',
-           'ProjectsClient', 'ProjectTagsClient', 'RegionsClient',
-           'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
-           'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient']
+           'LimitsClient', 'OAUTHConsumerClient', 'OAUTHTokenClient',
+           'PoliciesClient', 'ProjectsClient', 'ProjectTagsClient',
+           'RegionsClient', 'RoleAssignmentsClient', 'RolesClient',
+           'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient',
+           'VersionsClient']
diff --git a/tempest/lib/services/identity/v3/access_rules_client.py b/tempest/lib/services/identity/v3/access_rules_client.py
index 4f13e47..c3be5df 100644
--- a/tempest/lib/services/identity/v3/access_rules_client.py
+++ b/tempest/lib/services/identity/v3/access_rules_client.py
@@ -18,8 +18,9 @@
 https://docs.openstack.org/api-ref/identity/v3/index.html#application-credentials
 """
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/application_credentials_client.py b/tempest/lib/services/identity/v3/application_credentials_client.py
index be2e172..e7f3ac2 100644
--- a/tempest/lib/services/identity/v3/application_credentials_client.py
+++ b/tempest/lib/services/identity/v3/application_credentials_client.py
@@ -18,8 +18,9 @@
 https://docs.openstack.org/api-ref/identity/v3/index.html#application-credentials
 """
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/credentials_client.py b/tempest/lib/services/identity/v3/credentials_client.py
index 3f4b40e..27f6156 100644
--- a/tempest/lib/services/identity/v3/credentials_client.py
+++ b/tempest/lib/services/identity/v3/credentials_client.py
@@ -17,8 +17,9 @@
 https://docs.openstack.org/api-ref/identity/v3/index.html#credentials
 """
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/domains_client.py b/tempest/lib/services/identity/v3/domains_client.py
index bd32cfc..c1d1980 100644
--- a/tempest/lib/services/identity/v3/domains_client.py
+++ b/tempest/lib/services/identity/v3/domains_client.py
@@ -12,8 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/endpoints_client.py b/tempest/lib/services/identity/v3/endpoints_client.py
index 236b34c..de85388 100644
--- a/tempest/lib/services/identity/v3/endpoints_client.py
+++ b/tempest/lib/services/identity/v3/endpoints_client.py
@@ -17,8 +17,9 @@
 https://docs.openstack.org/api-ref/identity/v3/index.html#service-catalog-and-endpoints
 """
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/groups_client.py b/tempest/lib/services/identity/v3/groups_client.py
index f823b21..6f82067 100644
--- a/tempest/lib/services/identity/v3/groups_client.py
+++ b/tempest/lib/services/identity/v3/groups_client.py
@@ -17,8 +17,9 @@
 https://docs.openstack.org/api-ref/identity/v3/index.html#groups
 """
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
@@ -110,6 +111,6 @@
 
     def check_group_user_existence(self, group_id, user_id):
         """Check user in group."""
-        resp, body = self.head('groups/%s/users/%s' % (group_id, user_id))
+        resp, _ = self.head('groups/%s/users/%s' % (group_id, user_id))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/identity/v3/identity_providers_client.py b/tempest/lib/services/identity/v3/identity_providers_client.py
new file mode 100644
index 0000000..002bc8c
--- /dev/null
+++ b/tempest/lib/services/identity/v3/identity_providers_client.py
@@ -0,0 +1,93 @@
+# Copyright 2020 Samsung Electronics 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 urllib import parse as urllib
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class IdentityProvidersClient(rest_client.RestClient):
+
+    def register_identity_provider(self, identity_provider_id, **kwargs):
+        """Register an identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#register-an-identity-provider
+        """
+        post_body = json.dumps({'identity_provider': kwargs})
+        resp, body = self.put(
+            'OS-FEDERATION/identity_providers/%s' % identity_provider_id,
+            post_body)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_identity_providers(self, **params):
+        """List identity providers.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#list-identity-providers
+        """
+        url = 'identity_providers'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_identity_provider(self, identity_provider_id):
+        """Get identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#get-identity-provider
+        """
+        resp, body = self.get(
+            'OS-FEDERATION/identity_providers/%s' % identity_provider_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_identity_provider(self, identity_provider_id):
+        """Delete identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-identity-provider
+        """
+        resp, body = self.delete(
+            'OS-FEDERATION/identity_providers/%s' % identity_provider_id)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_identity_provider(self, identity_provider_id, **kwargs):
+        """Update identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#update-identity-provider
+        """
+        post_body = json.dumps({'identity_provider': kwargs})
+        resp, body = self.patch(
+            'OS-FEDERATION/identity_providers/%s' % identity_provider_id,
+            post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        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 3949437..f937ed6 100644
--- a/tempest/lib/services/identity/v3/inherited_roles_client.py
+++ b/tempest/lib/services/identity/v3/inherited_roles_client.py
@@ -51,7 +51,7 @@
     def check_user_inherited_project_role_on_domain(
             self, domain_id, user_id, role_id):
         """Checks whether a user has an inherited project role on a domain."""
-        resp, body = self.head(
+        resp, _ = self.head(
             "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
             % (domain_id, user_id, role_id))
         self.expected_success(204, resp.status)
@@ -88,7 +88,7 @@
     def check_group_inherited_project_role_on_domain(
             self, domain_id, group_id, role_id):
         """Checks whether a group has an inherited project role on a domain."""
-        resp, body = self.head(
+        resp, _ = self.head(
             "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
             % (domain_id, group_id, role_id))
         self.expected_success(204, resp.status)
@@ -115,7 +115,7 @@
     def check_user_has_flag_on_inherited_to_project(
             self, project_id, user_id, role_id):
         """Check if user has an inherited project role on project"""
-        resp, body = self.head(
+        resp, _ = self.head(
             "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
             % (project_id, user_id, role_id))
         self.expected_success(204, resp.status)
@@ -142,7 +142,7 @@
     def check_group_has_flag_on_inherited_to_project(
             self, project_id, group_id, role_id):
         """Check if group has an inherited project role on project"""
-        resp, body = self.head(
+        resp, _ = self.head(
             "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
             % (project_id, group_id, role_id))
         self.expected_success(204, resp.status)
diff --git a/tempest/lib/services/identity/v3/limits_client.py b/tempest/lib/services/identity/v3/limits_client.py
new file mode 100644
index 0000000..26d04bc
--- /dev/null
+++ b/tempest/lib/services/identity/v3/limits_client.py
@@ -0,0 +1,57 @@
+# Copyright 2021 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class LimitsClient(rest_client.RestClient):
+    api_version = "v3"
+
+    def get_registered_limits(self):
+        """Lists all registered limits."""
+        resp, body = self.get('registered_limits')
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, json.loads(body))
+
+    def create_limit(self, region_id, service_id, project_id, resource_name,
+                     default_limit, description=None, domain_id=None):
+        """Creates a limit in keystone."""
+        limit = {
+            'service_id': service_id,
+            'project_id': project_id,
+            'resource_name': resource_name,
+            'resource_limit': default_limit,
+            'region_id': region_id,
+            'description': description or '%s limit for %s' % (
+                resource_name, project_id),
+        }
+        if domain_id:
+            limit['domain_id'] = domain_id
+        post_body = json.dumps({'limits': [limit]})
+        resp, body = self.post('limits', post_body)
+        self.expected_success(201, resp.status)
+        return rest_client.ResponseBody(resp, json.loads(body))
+
+    def update_limit(self, limit_id, resource_limit, description=None):
+        """Updates a limit in keystone by id."""
+
+        limit = {'resource_limit': resource_limit}
+        if description:
+            limit['description'] = description
+        patch_body = json.dumps({'limit': limit})
+        resp, body = self.patch('limits/%s' % limit_id, patch_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, json.loads(body))
diff --git a/tempest/lib/services/identity/v3/mappings_client.py b/tempest/lib/services/identity/v3/mappings_client.py
new file mode 100644
index 0000000..a924b33
--- /dev/null
+++ b/tempest/lib/services/identity/v3/mappings_client.py
@@ -0,0 +1,91 @@
+# Copyright 2020 Samsung Electronics 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 urllib import parse as urllib
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class MappingsClient(rest_client.RestClient):
+
+    def create_mapping(self, mapping_id, **kwargs):
+        """Create a mapping.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#create-a-mapping
+        """
+        post_body = json.dumps({'mapping': kwargs})
+        resp, body = self.put(
+            'OS-FEDERATION/mappings/%s' % mapping_id, post_body)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_mapping(self, mapping_id):
+        """Get a mapping.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#get-a-mapping
+        """
+        resp, body = self.get(
+            'OS-FEDERATION/mappings/%s' % mapping_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_mapping(self, mapping_id, **kwargs):
+        """Update a mapping.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#update-a-mapping
+        """
+        post_body = json.dumps({'mapping': kwargs})
+        resp, body = self.patch(
+            'OS-FEDERATION/mappings/%s' % mapping_id, post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_mappings(self, **kwargs):
+        """List mappings.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#list-mappings
+        """
+        url = 'OS-FEDERATION/mappings'
+        if kwargs:
+            url += '?%s' % urllib.urlencode(kwargs)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_mapping(self, mapping_id):
+        """Delete a mapping.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-mapping
+        """
+        resp, body = self.delete(
+            'OS-FEDERATION/mappings/%s' % mapping_id)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/oauth_token_client.py b/tempest/lib/services/identity/v3/oauth_token_client.py
index 6ca401b..564d6d6 100644
--- a/tempest/lib/services/identity/v3/oauth_token_client.py
+++ b/tempest/lib/services/identity/v3/oauth_token_client.py
@@ -18,9 +18,7 @@
 import hmac
 import random
 import time
-
-import six
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
 
 from oslo_serialization import jsonutils as json
 
@@ -33,9 +31,9 @@
     def _escape(self, s):
         """Escape a unicode string in an OAuth-compatible fashion."""
         safe = b'~'
-        s = s.encode('utf-8') if isinstance(s, six.text_type) else s
+        s = s.encode('utf-8') if isinstance(s, str) else s
         s = urlparse.quote(s, safe)
-        if isinstance(s, six.binary_type):
+        if isinstance(s, bytes):
             s = s.decode('utf-8')
         return s
 
@@ -47,8 +45,8 @@
                                         verifier=None,
                                         http_method='GET'):
         """Generate OAUTH params along with signature."""
-        timestamp = six.text_type(int(time.time()))
-        nonce = six.text_type(random.getrandbits(64)) + timestamp
+        timestamp = str(int(time.time()))
+        nonce = str(random.getrandbits(64)) + timestamp
         oauth_params = [
             ('oauth_nonce', nonce),
             ('oauth_timestamp', timestamp),
@@ -71,7 +69,7 @@
         normalized_params = '&'.join(parameter_parts)
 
         # normalize_uri
-        scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
+        scheme, netloc, path, params, _, _ = urlparse.urlparse(uri)
         scheme = scheme.lower()
         netloc = netloc.lower()
         path = path.replace('//', '/')
diff --git a/tempest/lib/services/identity/v3/projects_client.py b/tempest/lib/services/identity/v3/projects_client.py
index b186fba..fffbe7a 100644
--- a/tempest/lib/services/identity/v3/projects_client.py
+++ b/tempest/lib/services/identity/v3/projects_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/protocols_client.py b/tempest/lib/services/identity/v3/protocols_client.py
new file mode 100644
index 0000000..19aa426
--- /dev/null
+++ b/tempest/lib/services/identity/v3/protocols_client.py
@@ -0,0 +1,97 @@
+# Copyright 2020 Samsung Electronics 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 urllib import parse as urllib
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ProtocolsClient(rest_client.RestClient):
+
+    def add_protocol_to_identity_provider(self, idp_id, protocol_id,
+                                          **kwargs):
+        """Add protocol to identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#add-protocol-to-identity-provider
+        """
+        post_body = json.dumps({'protocol': kwargs})
+        resp, body = self.put(
+            'OS-FEDERATION/identity_providers/%s/protocols/%s'
+            % (idp_id, protocol_id), post_body)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_protocols_of_identity_provider(self, idp_id, **kwargs):
+        """List protocols of identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#list-protocols-of-identity-provider
+        """
+        url = 'OS-FEDERATION/identity_providers/%s/protocols' % idp_id
+        if kwargs:
+            url += '?%s' % urllib.urlencode(kwargs)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_protocol_for_identity_provider(self, idp_id, protocol_id):
+        """Get protocol for identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#get-protocol-for-identity-provider
+        """
+        resp, body = self.get(
+            'OS-FEDERATION/identity_providers/%s/protocols/%s'
+            % (idp_id, protocol_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_mapping_for_identity_provider(self, idp_id, protocol_id,
+                                             **kwargs):
+        """Update attribute mapping for identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#update-attribute-mapping-for-identity-provider
+        """
+        post_body = json.dumps({'protocol': kwargs})
+        resp, body = self.patch(
+            'OS-FEDERATION/identity_providers/%s/protocols/%s'
+            % (idp_id, protocol_id), post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_protocol_from_identity_provider(self, idp_id, protocol_id):
+        """Delete a protocol from identity provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-protocol-from-identity-provider
+        """
+        resp, body = self.delete(
+            'OS-FEDERATION/identity_providers/%s/protocols/%s'
+            % (idp_id, protocol_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/regions_client.py b/tempest/lib/services/identity/v3/regions_client.py
index a598c9c..3aed5b8 100644
--- a/tempest/lib/services/identity/v3/regions_client.py
+++ b/tempest/lib/services/identity/v3/regions_client.py
@@ -17,8 +17,9 @@
 https://docs.openstack.org/api-ref/identity/v3/index.html#regions
 """
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/role_assignments_client.py b/tempest/lib/services/identity/v3/role_assignments_client.py
index 51ee8f6..f615709 100644
--- a/tempest/lib/services/identity/v3/role_assignments_client.py
+++ b/tempest/lib/services/identity/v3/role_assignments_client.py
@@ -12,8 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/roles_client.py b/tempest/lib/services/identity/v3/roles_client.py
index f9356be..4836784 100644
--- a/tempest/lib/services/identity/v3/roles_client.py
+++ b/tempest/lib/services/identity/v3/roles_client.py
@@ -12,8 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
@@ -89,6 +90,13 @@
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
+    def create_user_role_on_system(self, user_id, role_id):
+        """Add roles to a user on the system."""
+        resp, body = self.put('system/users/%s/roles/%s' %
+                              (user_id, role_id), None)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
     def list_user_roles_on_project(self, project_id, user_id):
         """list roles of a user on a project."""
         resp, body = self.get('projects/%s/users/%s/roles' %
@@ -105,6 +113,13 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
+    def list_user_roles_on_system(self, user_id):
+        """list roles of a user on the system."""
+        resp, body = self.get('system/users/%s/roles' % user_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
     def delete_role_from_user_on_project(self, project_id, user_id, role_id):
         """Delete role of a user on a project."""
         resp, body = self.delete('projects/%s/users/%s/roles/%s' %
@@ -119,19 +134,32 @@
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
+    def delete_role_from_user_on_system(self, user_id, role_id):
+        """Delete role of a user on the system."""
+        resp, body = self.delete('system/users/%s/roles/%s' %
+                                 (user_id, role_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
     def check_user_role_existence_on_project(self, project_id,
                                              user_id, role_id):
         """Check role of a user on a project."""
-        resp, body = self.head('projects/%s/users/%s/roles/%s' %
-                               (project_id, user_id, role_id))
+        resp, _ = self.head('projects/%s/users/%s/roles/%s' %
+                            (project_id, user_id, role_id))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
 
     def check_user_role_existence_on_domain(self, domain_id,
                                             user_id, role_id):
         """Check role of a user on a domain."""
-        resp, body = self.head('domains/%s/users/%s/roles/%s' %
-                               (domain_id, user_id, role_id))
+        resp, _ = self.head('domains/%s/users/%s/roles/%s' %
+                            (domain_id, user_id, role_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
+
+    def check_user_role_existence_on_system(self, user_id, role_id):
+        """Check role of a user on the system."""
+        resp, body = self.head('system/users/%s/roles/%s' % (user_id, role_id))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
 
@@ -149,6 +177,13 @@
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
+    def create_group_role_on_system(self, group_id, role_id):
+        """Add roles to a group on the system."""
+        resp, body = self.put('system/groups/%s/roles/%s' %
+                              (group_id, role_id), None)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
     def list_group_roles_on_project(self, project_id, group_id):
         """list roles of a group on a project."""
         resp, body = self.get('projects/%s/groups/%s/roles' %
@@ -165,6 +200,13 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
+    def list_group_roles_on_system(self, group_id):
+        """list roles of a group on the system."""
+        resp, body = self.get('system/groups/%s/roles' % group_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
     def delete_role_from_group_on_project(self, project_id, group_id, role_id):
         """Delete role of a group on a project."""
         resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
@@ -179,19 +221,33 @@
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
 
+    def delete_role_from_group_on_system(self, group_id, role_id):
+        """Delete role of a group on the system."""
+        resp, body = self.delete('system/groups/%s/roles/%s' %
+                                 (group_id, role_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
     def check_role_from_group_on_project_existence(self, project_id,
                                                    group_id, role_id):
         """Check role of a group on a project."""
-        resp, body = self.head('projects/%s/groups/%s/roles/%s' %
-                               (project_id, group_id, role_id))
+        resp, _ = self.head('projects/%s/groups/%s/roles/%s' %
+                            (project_id, group_id, role_id))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
 
     def check_role_from_group_on_domain_existence(self, domain_id,
                                                   group_id, role_id):
         """Check role of a group on a domain."""
-        resp, body = self.head('domains/%s/groups/%s/roles/%s' %
-                               (domain_id, group_id, role_id))
+        resp, _ = self.head('domains/%s/groups/%s/roles/%s' %
+                            (domain_id, group_id, role_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
+
+    def check_role_from_group_on_system_existence(self, group_id, role_id):
+        """Check role of a group on the system."""
+        resp, body = self.head('system/groups/%s/roles/%s' %
+                               (group_id, role_id))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
 
@@ -232,14 +288,14 @@
 
     def check_role_inference_rule(self, prior_role, implies_role):
         """Check a role inference rule."""
-        resp, body = self.head('roles/%s/implies/%s' %
-                               (prior_role, implies_role))
+        resp, _ = self.head('roles/%s/implies/%s' %
+                            (prior_role, implies_role))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
 
     def delete_role_inference_rule(self, prior_role, implies_role):
         """Delete a role inference rule."""
-        resp, body = self.delete('roles/%s/implies/%s' %
-                                 (prior_role, implies_role))
+        resp, _ = self.delete('roles/%s/implies/%s' %
+                              (prior_role, implies_role))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/identity/v3/service_providers_client.py b/tempest/lib/services/identity/v3/service_providers_client.py
new file mode 100644
index 0000000..5d4f014
--- /dev/null
+++ b/tempest/lib/services/identity/v3/service_providers_client.py
@@ -0,0 +1,93 @@
+# Copyright 2020 Samsung Electronics 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 urllib import parse as urllib
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ServiceProvidersClient(rest_client.RestClient):
+
+    def register_service_provider(self, service_provider_id, **kwargs):
+        """Register a service provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#register-a-service-provider
+        """
+        post_body = json.dumps({'service_provider': kwargs})
+        resp, body = self.put(
+            'OS-FEDERATION/service_providers/%s' % service_provider_id,
+            post_body)
+        self.expected_success(201, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_service_providers(self, **kwargs):
+        """List service providers.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#list-service-providers
+        """
+        url = 'OS-FEDERATION/service_providers'
+        if kwargs:
+            url += '?%s' % urllib.urlencode(kwargs)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_service_provider(self, service_provider_id):
+        """Get a service provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#get-service-provider
+        """
+        resp, body = self.get(
+            'OS-FEDERATION/service_providers/%s' % service_provider_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_service_provider(self, service_provider_id):
+        """Delete a service provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-service-provider
+        """
+        resp, body = self.delete(
+            'OS-FEDERATION/service_providers/%s' % service_provider_id)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_service_provider(self, service_provider_id, **kwargs):
+        """Update a service provider.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/index.html#update-service-provider
+        """
+        post_body = json.dumps({'service_provider': kwargs})
+        resp, body = self.patch(
+            'OS-FEDERATION/service_providers/%s' % service_provider_id,
+            post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/services_client.py b/tempest/lib/services/identity/v3/services_client.py
index eb961a5..994df2f 100644
--- a/tempest/lib/services/identity/v3/services_client.py
+++ b/tempest/lib/services/identity/v3/services_client.py
@@ -17,8 +17,9 @@
 https://docs.openstack.org/api-ref/identity/v3/index.html#service-catalog-and-endpoints
 """
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
index 6956297..c63966a 100644
--- a/tempest/lib/services/identity/v3/token_client.py
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -12,7 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from oslo_log import log as logging
 from oslo_serialization import jsonutils as json
 
 from tempest.lib.common import rest_client
@@ -51,7 +50,7 @@
     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, app_cred_id=None,
+             domain_name=None, system=None, token=None, app_cred_id=None,
              app_cred_secret=None):
         """Obtains a token from the authentication service
 
@@ -65,6 +64,7 @@
         :param domain_name: a domain name to scope to
         :param project_id: a project id to scope to
         :param project_name: a project name to scope to
+        :param system: whether the token should be scoped to the system
         :param token: a token to re-scope.
 
         Accepts different combinations of credentials.
@@ -74,6 +74,7 @@
         - user_id, password
         - username, password, user_domain_id
         - username, password, project_name, user_domain_id, project_domain_id
+        - username, password, user_domain_id, system
         Validation is left to the server side.
         """
         creds = {
@@ -135,6 +136,8 @@
             creds['auth']['scope'] = dict(domain={'id': domain_id})
         elif domain_name:
             creds['auth']['scope'] = dict(domain={'name': domain_name})
+        elif system:
+            creds['auth']['scope'] = dict(system={system: True})
 
         body = json.dumps(creds, sort_keys=True)
         resp, body = self.post(self.auth_url, body=body)
@@ -191,15 +194,3 @@
             return token, body['token']
         else:
             return token
-
-
-class V3TokenClientJSON(V3TokenClient):
-    LOG = logging.getLogger(__name__)
-
-    def _warn(self):
-        self.LOG.warning("%s class was deprecated and renamed to %s",
-                         self.__class__.__name__, 'V3TokenClient')
-
-    def __init__(self, *args, **kwargs):
-        self._warn()
-        super(V3TokenClientJSON, self).__init__(*args, **kwargs)
diff --git a/tempest/lib/services/identity/v3/trusts_client.py b/tempest/lib/services/identity/v3/trusts_client.py
index f1cc806..48a7956 100644
--- a/tempest/lib/services/identity/v3/trusts_client.py
+++ b/tempest/lib/services/identity/v3/trusts_client.py
@@ -12,8 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/identity/v3/users_client.py b/tempest/lib/services/identity/v3/users_client.py
index bba02a4..771ffea 100644
--- a/tempest/lib/services/identity/v3/users_client.py
+++ b/tempest/lib/services/identity/v3/users_client.py
@@ -12,8 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/image/v1/__init__.py b/tempest/lib/services/image/v1/__init__.py
index 9bd8262..1f33cef 100644
--- a/tempest/lib/services/image/v1/__init__.py
+++ b/tempest/lib/services/image/v1/__init__.py
@@ -12,8 +12,17 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+import warnings
+
 from tempest.lib.services.image.v1.image_members_client import \
     ImageMembersClient
 from tempest.lib.services.image.v1.images_client import ImagesClient
 
 __all__ = ['ImageMembersClient', 'ImagesClient']
+
+
+warnings.warn(
+    "The tempest.lib.services.image.v1 module (Image v1 APIs service "
+    "clients) is deprecated in favor of tempest.lib.services.image.v2 "
+    "(Image v2 APIs service clients) and will be removed once Tempest stop "
+    "supporting stable Ussuri.", DeprecationWarning)
diff --git a/tempest/lib/services/image/v1/images_client.py b/tempest/lib/services/image/v1/images_client.py
index 0e76a63..c9a4a94 100644
--- a/tempest/lib/services/image/v1/images_client.py
+++ b/tempest/lib/services/image/v1/images_client.py
@@ -14,9 +14,9 @@
 #    under the License.
 
 import functools
+from urllib import parse as urllib
 
 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
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index 4713cce..abf427c 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -14,9 +14,9 @@
 #    under the License.
 
 import functools
+from urllib import parse as urllib
 
 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
@@ -121,6 +121,14 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
+    def show_image_tasks(self, image_id):
+        """Show image tasks."""
+        url = 'images/%s/tasks' % image_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
     def is_resource_deleted(self, id):
         try:
             self.show_image(id)
diff --git a/tempest/lib/services/image/v2/namespace_objects_client.py b/tempest/lib/services/image/v2/namespace_objects_client.py
index 0cae816..32f5a2c 100644
--- a/tempest/lib/services/image/v2/namespace_objects_client.py
+++ b/tempest/lib/services/image/v2/namespace_objects_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/image/v2/namespace_tags_client.py b/tempest/lib/services/image/v2/namespace_tags_client.py
index 4315f16..5bca229 100644
--- a/tempest/lib/services/image/v2/namespace_tags_client.py
+++ b/tempest/lib/services/image/v2/namespace_tags_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/image/v2/versions_client.py b/tempest/lib/services/image/v2/versions_client.py
index 1b7f806..98b4fb6 100644
--- a/tempest/lib/services/image/v2/versions_client.py
+++ b/tempest/lib/services/image/v2/versions_client.py
@@ -30,3 +30,13 @@
         self.expected_success(300, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
+
+    def has_version(self, version):
+        """Return True if a version is supported."""
+        version = 'v%s' % version
+        supported = ['SUPPORTED', 'CURRENT']
+        versions = self.list_versions()
+        for version_struct in versions['versions']:
+            if version_struct['id'] == version:
+                return version_struct['status'] in supported
+        return False
diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py
index f7ac046..faf35d1 100644
--- a/tempest/lib/services/network/__init__.py
+++ b/tempest/lib/services/network/__init__.py
@@ -15,6 +15,11 @@
 from tempest.lib.services.network.agents_client import AgentsClient
 from tempest.lib.services.network.extensions_client import ExtensionsClient
 from tempest.lib.services.network.floating_ips_client import FloatingIPsClient
+from tempest.lib.services.network.floating_ips_port_forwarding_client import \
+    FloatingIpsPortForwardingClient
+from tempest.lib.services.network.log_resource_client import LogResourceClient
+from tempest.lib.services.network.loggable_resource_client import \
+    LoggableResourceClient
 from tempest.lib.services.network.metering_label_rules_client import \
     MeteringLabelRulesClient
 from tempest.lib.services.network.metering_labels_client import \
@@ -22,8 +27,12 @@
 from tempest.lib.services.network.networks_client import NetworksClient
 from tempest.lib.services.network.ports_client import PortsClient
 from tempest.lib.services.network.qos_client import QosClient
+from tempest.lib.services.network.qos_limit_bandwidth_rules_client import \
+    QosLimitBandwidthRulesClient
 from tempest.lib.services.network.qos_minimum_bandwidth_rules_client import \
     QosMinimumBandwidthRulesClient
+from tempest.lib.services.network.qos_minimum_packet_rate_rules_client import \
+    QosMinimumPacketRateRulesClient
 from tempest.lib.services.network.quotas_client import QuotasClient
 from tempest.lib.services.network.routers_client import RoutersClient
 from tempest.lib.services.network.security_group_rules_client import \
@@ -36,12 +45,15 @@
 from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient
 from tempest.lib.services.network.subnets_client import SubnetsClient
 from tempest.lib.services.network.tags_client import TagsClient
+from tempest.lib.services.network.trunks_client import TrunksClient
 from tempest.lib.services.network.versions_client import NetworkVersionsClient
 
 __all__ = ['AgentsClient', 'ExtensionsClient', 'FloatingIPsClient',
-           'MeteringLabelRulesClient', 'MeteringLabelsClient',
-           'NetworksClient', 'NetworkVersionsClient', 'PortsClient',
-           'QosClient', 'QosMinimumBandwidthRulesClient', 'QuotasClient',
-           'RoutersClient', 'SecurityGroupRulesClient', 'SecurityGroupsClient',
+           'FloatingIpsPortForwardingClient', 'MeteringLabelRulesClient',
+           'MeteringLabelsClient', 'NetworksClient', 'NetworkVersionsClient',
+           'PortsClient', 'QosClient', 'QosMinimumBandwidthRulesClient',
+           'QosLimitBandwidthRulesClient', 'QuotasClient', 'RoutersClient',
+           'SecurityGroupRulesClient', 'SecurityGroupsClient',
            'SegmentsClient', 'ServiceProvidersClient', 'SubnetpoolsClient',
-           'SubnetsClient', 'TagsClient']
+           'SubnetsClient', 'TagsClient', 'TrunksClient', 'LogResourceClient',
+           'LoggableResourceClient', 'QosMinimumPacketRateRulesClient']
diff --git a/tempest/lib/services/network/base.py b/tempest/lib/services/network/base.py
index fe8b244..ee87dd4 100644
--- a/tempest/lib/services/network/base.py
+++ b/tempest/lib/services/network/base.py
@@ -10,8 +10,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/network/floating_ips_port_forwarding_client.py b/tempest/lib/services/network/floating_ips_port_forwarding_client.py
new file mode 100644
index 0000000..43e24ea
--- /dev/null
+++ b/tempest/lib/services/network/floating_ips_port_forwarding_client.py
@@ -0,0 +1,78 @@
+# Copyright 2021 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.lib.services.network import base
+
+
+class FloatingIpsPortForwardingClient(base.BaseNetworkClient):
+
+    def create_port_forwarding(self, floatingip_id, **kwargs):
+        """Creates a floating IP port forwarding.
+
+        Creates port forwarding by using the configuration that you define in
+        the request object.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#create-port-forwarding
+        """
+        uri = '/floatingips/%s/port_forwardings' % floatingip_id
+        post_data = {'port_forwarding': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_port_forwarding(
+            self, floatingip_id, port_forwarding_id, **kwargs):
+        """Updates a floating IP port_forwarding resource.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#update-a-port-forwarding
+        """
+        uri = '/floatingips/%s/port_forwardings/%s' % (
+            floatingip_id, port_forwarding_id)
+        post_data = {'port_forwarding': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_port_forwarding(
+            self, floatingip_id, port_forwarding_id, **fields):
+        """Shows details for a floating IP port forwarding id.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#show-port-forwarding
+        """
+        uri = '/floatingips/%s/port_forwardings/%s' % (
+            floatingip_id, port_forwarding_id)
+        return self.show_resource(uri, **fields)
+
+    def delete_port_forwarding(self, floatingip_id, port_forwarding_id):
+        """Deletes a floating IP port_forwarding resource.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#delete-a-floating-ip-port-forwarding
+        """
+        uri = '/floatingips/%s/port_forwardings/%s' % (
+            floatingip_id, port_forwarding_id)
+        return self.delete_resource(uri)
+
+    def list_port_forwardings(self, floatingip_id, **filters):
+        """Lists floating Ip port forwardings.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-floating-ip-port-forwardings-detail
+        """
+        uri = '/floatingips/%s/port_forwardings' % floatingip_id
+        return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/log_resource_client.py b/tempest/lib/services/network/log_resource_client.py
new file mode 100644
index 0000000..727b138
--- /dev/null
+++ b/tempest/lib/services/network/log_resource_client.py
@@ -0,0 +1,74 @@
+# Copyright 2021 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.lib.services.network import base
+
+
+class LogResourceClient(base.BaseNetworkClient):
+
+    def create_log(self, **kwargs):
+        """Creates a log resource.
+
+        Creates a log resource by using the configuration that you define in
+        the request object.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#create-log
+        """
+        uri = '/log/logs/'
+        post_data = {'log': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_log(self, log_id, **kwargs):
+        """Updates a log resource.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#update-log
+        """
+        uri = '/log/logs/%s' % log_id
+        post_data = {'log': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_log(self, log_id, **fields):
+        """Shows details for a log id.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#show-log
+        """
+        uri = '/log/logs/%s' % log_id
+        return self.show_resource(uri, **fields)
+
+    def delete_log(self, log_id):
+        """Deletes a log resource.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#delete-log
+        """
+        uri = '/log/logs/%s' % log_id
+        return self.delete_resource(uri)
+
+    def list_logs(self, **filters):
+        """Lists Logs.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-logs
+        """
+        uri = '/log/logs'
+        return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/loggable_resource_client.py b/tempest/lib/services/network/loggable_resource_client.py
new file mode 100644
index 0000000..774046f
--- /dev/null
+++ b/tempest/lib/services/network/loggable_resource_client.py
@@ -0,0 +1,29 @@
+# Copyright 2021 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.lib.services.network import base
+
+
+class LoggableResourceClient(base.BaseNetworkClient):
+
+    def list_loggable_resources(self, **filters):
+        """List Loggable resources.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-loggable-resources
+        """
+        uri = '/log/loggable-resources'
+        return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/qos_limit_bandwidth_rules_client.py b/tempest/lib/services/network/qos_limit_bandwidth_rules_client.py
new file mode 100644
index 0000000..8fd87fe
--- /dev/null
+++ b/tempest/lib/services/network/qos_limit_bandwidth_rules_client.py
@@ -0,0 +1,74 @@
+# Copyright 2021 Red Hat.
+#
+# 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 base
+
+
+class QosLimitBandwidthRulesClient(base.BaseNetworkClient):
+
+    def create_limit_bandwidth_rule(self, qos_policy_id, **kwargs):
+        """Creates a limit bandwidth rule for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#create-bandwidth-limit-rule
+        """
+        uri = '/qos/policies/{}/bandwidth_limit_rules'.format(
+            qos_policy_id)
+        post_data = {'bandwidth_limit_rule': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_limit_bandwidth_rule(self, qos_policy_id, rule_id, **kwargs):
+        """Updates a limit bandwidth rule.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#update-bandwidth-limit-rule
+        """
+        uri = '/qos/policies/{}/bandwidth_limit_rules/{}'.format(
+            qos_policy_id, rule_id)
+        post_data = {'bandwidth_limit_rule': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_limit_bandwidth_rule(self, qos_policy_id, rule_id, **fields):
+        """Show details of a limit bandwidth rule.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#show-bandwidth-limit-rule-details
+        """
+        uri = '/qos/policies/{}/bandwidth_limit_rules/{}'.format(
+            qos_policy_id, rule_id)
+        return self.show_resource(uri, **fields)
+
+    def delete_limit_bandwidth_rule(self, qos_policy_id, rule_id):
+        """Deletes a limit bandwidth rule for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#delete-bandwidth-limit-rule
+        """
+        uri = '/qos/policies/{}/bandwidth_limit_rules/{}'.format(
+            qos_policy_id, rule_id)
+        return self.delete_resource(uri)
+
+    def list_limit_bandwidth_rules(self, qos_policy_id, **filters):
+        """Lists all limit bandwidth rules for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-bandwidth-limit-rules-for-qos-policy
+        """
+        uri = '/qos/policies/{}/bandwidth_limit_rules'.format(qos_policy_id)
+        return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/qos_minimum_packet_rate_rules_client.py b/tempest/lib/services/network/qos_minimum_packet_rate_rules_client.py
new file mode 100644
index 0000000..98bcafe
--- /dev/null
+++ b/tempest/lib/services/network/qos_minimum_packet_rate_rules_client.py
@@ -0,0 +1,73 @@
+#    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 base
+
+
+class QosMinimumPacketRateRulesClient(base.BaseNetworkClient):
+
+    def create_minimum_packet_rate_rule(self, qos_policy_id, **kwargs):
+        """Creates a minimum packet rate rule for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#create-minimum-packet-rate-rule
+        """
+        uri = '/qos/policies/%s/minimum_packet_rate_rules' % qos_policy_id
+        post_data = {'minimum_packet_rate_rule': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_minimum_packet_rate_rule(
+        self, qos_policy_id, rule_id, **kwargs
+    ):
+        """Updates a minimum packet rate rule.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#update-minimum-packet-rate-rule
+        """
+        uri = '/qos/policies/%s/minimum_packet_rate_rules/%s' % (
+            qos_policy_id, rule_id)
+        post_data = {'minimum_packet_rate_rule': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_minimum_packet_rate_rule(self, qos_policy_id, rule_id, **fields):
+        """Show details of a minimum packet rate rule.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#show-minimum-packet-rate-rule-details
+        """
+        uri = '/qos/policies/%s/minimum_packet_rate_rules/%s' % (
+            qos_policy_id, rule_id)
+        return self.show_resource(uri, **fields)
+
+    def delete_minimum_packet_rate_rule(self, qos_policy_id, rule_id):
+        """Deletes a minimum packet rate rule for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#delete-minimum-packet-rate-rule
+        """
+        uri = '/qos/policies/%s/minimum_packet_rate_rules/%s' % (
+            qos_policy_id, rule_id)
+        return self.delete_resource(uri)
+
+    def list_minimum_packet_rate_rules(self, qos_policy_id, **filters):
+        """Lists all minimum packet rate rules for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-minimum-packet-rate-rules-for-qos-policy
+        """
+        uri = '/qos/policies/%s/minimum_packet_rate_rules' % qos_policy_id
+        return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/trunks_client.py b/tempest/lib/services/network/trunks_client.py
new file mode 100644
index 0000000..2fd9e01
--- /dev/null
+++ b/tempest/lib/services/network/trunks_client.py
@@ -0,0 +1,100 @@
+#    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 base
+
+
+class TrunksClient(base.BaseNetworkClient):
+
+    def create_trunk(self, **kwargs):
+        """Creates a trunk.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#create-trunk
+        """
+        uri = '/trunks'
+        post_data = {'trunk': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_trunk(self, trunk_id, **kwargs):
+        """Updates a trunk.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#update-trunk
+        """
+        uri = '/trunks/%s' % trunk_id
+        put_data = {'trunk': kwargs}
+        return self.update_resource(uri, put_data)
+
+    def show_trunk(self, trunk_id):
+        """Shows details for a trunk.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#show-trunk
+        """
+        uri = '/trunks/%s' % trunk_id
+        return self.show_resource(uri)
+
+    def delete_trunk(self, trunk_id):
+        """Deletes a trunk.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#delete-trunk
+        """
+        uri = '/trunks/%s' % trunk_id
+        return self.delete_resource(uri)
+
+    def list_trunks(self, **filters):
+        """Lists trunks to which the tenant has access.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-trunks
+        """
+        uri = '/trunks'
+        return self.list_resources(uri, **filters)
+
+    def add_subports_to_trunk(self, trunk_id, sub_ports):
+        """Add subports to a trunk.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#add-subports-to-trunk
+        """
+        uri = '/trunks/%s/add_subports' % trunk_id
+        put_data = {'sub_ports': sub_ports}
+        return self.update_resource(uri, put_data)
+
+    def delete_subports_from_trunk(self, trunk_id, sub_ports):
+        """Deletes subports from a trunk.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#delete-subports-from-trunk
+        """
+        uri = '/trunks/%s/remove_subports' % trunk_id
+        put_data = {'sub_ports': sub_ports}
+        return self.update_resource(uri, put_data)
+
+    def list_subports_of_trunk(self, trunk_id):
+        """List subports of a trunk.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-subports-for-trunk
+        """
+        uri = '/trunks/%s/get_subports' % trunk_id
+        return self.list_resources(uri)
diff --git a/tempest/lib/services/object_storage/account_client.py b/tempest/lib/services/object_storage/account_client.py
index 8c15a88..52b2534 100644
--- a/tempest/lib/services/object_storage/account_client.py
+++ b/tempest/lib/services/object_storage/account_client.py
@@ -13,10 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
 from xml.etree import ElementTree as etree
 
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.common import rest_client
 
diff --git a/tempest/lib/services/object_storage/container_client.py b/tempest/lib/services/object_storage/container_client.py
index 027fb1f..6d07ec1 100644
--- a/tempest/lib/services/object_storage/container_client.py
+++ b/tempest/lib/services/object_storage/container_client.py
@@ -13,17 +13,25 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
 from xml.etree import ElementTree as etree
 
 import debtcollector.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
 
 
 class ContainerClient(rest_client.RestClient):
 
+    def is_resource_deleted(self, container):
+        try:
+            self.list_container_metadata(container)
+        except exceptions.NotFound:
+            return True
+        return False
+
     def update_container(self, container_name, **headers):
         """Creates or Updates a container
 
diff --git a/tempest/lib/services/object_storage/object_client.py b/tempest/lib/services/object_storage/object_client.py
index 383aff6..65e8227 100644
--- a/tempest/lib/services/object_storage/object_client.py
+++ b/tempest/lib/services/object_storage/object_client.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 ssl
 
-from six.moves import http_client as httplib
-from six.moves.urllib import parse as urlparse
+from http import client as httplib
+from urllib import parse as urlparse
 
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions
@@ -22,6 +23,15 @@
 
 class ObjectClient(rest_client.RestClient):
 
+    def is_resource_deleted(self, object_name, container):
+        try:
+            self.get_object(container, object_name)
+        except exceptions.NotFound:
+            return True
+        except exceptions.Conflict:
+            return False
+        return False
+
     def create_object(self, container, object_name, data,
                       params=None, metadata=None, headers=None,
                       chunked=False):
@@ -118,7 +128,7 @@
         path = str(parsed.path) + "/"
         path += "%s/%s" % (str(container), str(object_name))
 
-        conn = _create_connection(parsed)
+        conn = self._create_connection(parsed)
         # Send the PUT request and the headers including the "Expect" header
         conn.putrequest('PUT', path)
 
@@ -151,15 +161,20 @@
 
         return resp.status, resp.reason
 
+    def _create_connection(self, parsed_url):
+        """Helper function to create connection with httplib
 
-def _create_connection(parsed_url):
-    """Helper function to create connection with httplib
+        :param parsed_url: parsed url of the remote location
+        """
+        context = None
+        # If CONF.identity.disable_ssl_certificate_validation is true,
+        # do not check ssl certification.
+        if self.dscv:
+            context = ssl._create_unverified_context()
+        if parsed_url.scheme == 'https':
+            conn = httplib.HTTPSConnection(parsed_url.netloc,
+                                           context=context)
+        else:
+            conn = httplib.HTTPConnection(parsed_url.netloc)
 
-    :param parsed_url: parsed url of the remote location
-    """
-    if parsed_url.scheme == 'https':
-        conn = httplib.HTTPSConnection(parsed_url.netloc)
-    else:
-        conn = httplib.HTTPConnection(parsed_url.netloc)
-
-    return conn
+        return conn
diff --git a/tempest/lib/services/placement/placement_client.py b/tempest/lib/services/placement/placement_client.py
index b8e91b8..216ac08 100644
--- a/tempest/lib/services/placement/placement_client.py
+++ b/tempest/lib/services/placement/placement_client.py
@@ -12,8 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 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
diff --git a/tempest/lib/services/placement/resource_providers_client.py b/tempest/lib/services/placement/resource_providers_client.py
index 56f6409..3214053 100644
--- a/tempest/lib/services/placement/resource_providers_client.py
+++ b/tempest/lib/services/placement/resource_providers_client.py
@@ -10,8 +10,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 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
@@ -25,6 +26,7 @@
     * resource providers
     * resource provider inventories
     * resource provider aggregates
+    * resource provider usages
     """
 
     def list_resource_providers(self, **params):
@@ -68,6 +70,19 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
+    def list_resource_provider_usages(self, rp_uuid):
+        """List resource provider usages.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/placement/#list-resource-provider-usages
+        """
+        url = '/resource_providers/%s/usages' % rp_uuid
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
     def list_resource_provider_aggregates(self, rp_uuid):
         """List resource provider aggregates.
 
@@ -80,3 +95,29 @@
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
+
+    def update_resource_providers_inventories(self, rp_uuid, **kwargs):
+        """Update resource providers inventories.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/placement/#update-resource-provider-inventories
+        """
+        url = '/resource_providers/{}/inventories'.format(rp_uuid)
+        data = json.dumps(kwargs)
+        resp, body = self.put(url, data)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_resource_providers_inventories(self, rp_uuid):
+        """Delete resource providers inventories.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/placement/#delete-resource-provider-inventories
+        """
+        url = '/resource_providers/{}/inventories'.format(rp_uuid)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/__init__.py b/tempest/lib/services/volume/__init__.py
index 6855d8e..4b47251 100644
--- a/tempest/lib/services/volume/__init__.py
+++ b/tempest/lib/services/volume/__init__.py
@@ -12,8 +12,7 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from tempest.lib.services.volume import v1
 from tempest.lib.services.volume import v2
 from tempest.lib.services.volume import v3
 
-__all__ = ['v1', 'v2', 'v3']
+__all__ = ['v2', 'v3']
diff --git a/tempest/lib/services/volume/base_client.py b/tempest/lib/services/volume/base_client.py
index c7fb21a..c0ac62d 100644
--- a/tempest/lib/services/volume/base_client.py
+++ b/tempest/lib/services/volume/base_client.py
@@ -13,8 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.lib.common import api_version_request
 from tempest.lib.common import api_version_utils
 from tempest.lib.common import rest_client
+from tempest.lib import exceptions
 
 VOLUME_MICROVERSION = None
 
@@ -43,3 +45,39 @@
                 'volume %s' % VOLUME_MICROVERSION,
                 resp)
         return resp, resp_body
+
+    def get_schema(self, schema_versions_info):
+        """Get JSON schema
+
+        This method provides the matching schema for requested
+        microversion.
+
+        :param schema_versions_info: List of dict which provides schema
+                                     information with range of valid versions.
+
+        Example::
+
+         schema_versions_info = [
+             {'min': None, 'max': '2.1', 'schema': schemav21},
+             {'min': '2.2', 'max': '2.9', 'schema': schemav22},
+             {'min': '2.10', 'max': None, 'schema': schemav210}]
+        """
+        schema = None
+        version = api_version_request.APIVersionRequest(VOLUME_MICROVERSION)
+        for items in schema_versions_info:
+            min_version = api_version_request.APIVersionRequest(items['min'])
+            max_version = api_version_request.APIVersionRequest(items['max'])
+            # This is case where COMPUTE_MICROVERSION is None, which means
+            # request without microversion So select base v2.1 schema.
+            if version.is_null() and items['min'] is None:
+                schema = items['schema']
+                break
+            # else select appropriate schema as per COMPUTE_MICROVERSION
+            elif version.matches(min_version, max_version):
+                schema = items['schema']
+                break
+        if schema is None:
+            raise exceptions.JSONSchemaNotFound(
+                version=version.get_string(),
+                schema_versions_info=schema_versions_info)
+        return schema
diff --git a/tempest/lib/services/volume/v1/__init__.py b/tempest/lib/services/volume/v1/__init__.py
deleted file mode 100644
index 7b5991f..0000000
--- a/tempest/lib/services/volume/v1/__init__.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# 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.v1.availability_zone_client \
-    import AvailabilityZoneClient
-from tempest.lib.services.volume.v1.backups_client import BackupsClient
-from tempest.lib.services.volume.v1.encryption_types_client import \
-    EncryptionTypesClient
-from tempest.lib.services.volume.v1.extensions_client import ExtensionsClient
-from tempest.lib.services.volume.v1.hosts_client import HostsClient
-from tempest.lib.services.volume.v1.limits_client import LimitsClient
-from tempest.lib.services.volume.v1.qos_client import QosSpecsClient
-from tempest.lib.services.volume.v1.quotas_client import QuotasClient
-from tempest.lib.services.volume.v1.services_client import ServicesClient
-from tempest.lib.services.volume.v1.snapshots_client import SnapshotsClient
-from tempest.lib.services.volume.v1.types_client import TypesClient
-from tempest.lib.services.volume.v1.volumes_client import VolumesClient
-
-__all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient',
-           'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
-           'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient',
-           'LimitsClient']
diff --git a/tempest/lib/services/volume/v1/availability_zone_client.py b/tempest/lib/services/volume/v1/availability_zone_client.py
deleted file mode 100644
index be4f539..0000000
--- a/tempest/lib/services/volume/v1/availability_zone_client.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2014 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 oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
-
-
-class AvailabilityZoneClient(rest_client.RestClient):
-    """Volume V1 availability zone client."""
-
-    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/v1/backups_client.py b/tempest/lib/services/volume/v1/backups_client.py
deleted file mode 100644
index 2289253..0000000
--- a/tempest/lib/services/volume/v1/backups_client.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# 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 tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-
-
-class BackupsClient(rest_client.RestClient):
-    """Volume V1 Backups client"""
-    api_version = "v1"
-
-    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://docs.openstack.org/api-ref/block-storage/v2/#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://docs.openstack.org/api-ref/block-storage/v2/#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):
-        """Information for all the tenant's backups."""
-        url = "backups"
-        if detail:
-            url += "/detail"
-        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."""
-        # TODO(linanbj): 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.
-        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/v1/encryption_types_client.py b/tempest/lib/services/volume/v1/encryption_types_client.py
deleted file mode 100644
index 8e75ff9..0000000
--- a/tempest/lib/services/volume/v1/encryption_types_client.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# 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 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://docs.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)
diff --git a/tempest/lib/services/volume/v1/extensions_client.py b/tempest/lib/services/volume/v1/extensions_client.py
deleted file mode 100644
index 7b849a8..0000000
--- a/tempest/lib/services/volume/v1/extensions_client.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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
-
-
-class ExtensionsClient(rest_client.RestClient):
-    """Volume V1 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/v1/hosts_client.py b/tempest/lib/services/volume/v1/hosts_client.py
deleted file mode 100644
index f344678..0000000
--- a/tempest/lib/services/volume/v1/hosts_client.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from 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 Host API V1 requests"""
-
-    def list_hosts(self, **params):
-        """Lists all hosts.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://docs.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)
diff --git a/tempest/lib/services/volume/v1/limits_client.py b/tempest/lib/services/volume/v1/limits_client.py
deleted file mode 100644
index e14b2dc..0000000
--- a/tempest/lib/services/volume/v1/limits_client.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# 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 V1 limits client."""
-
-    api_version = "v1"
-
-    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/v1/qos_client.py b/tempest/lib/services/volume/v1/qos_client.py
deleted file mode 100644
index 67f2ead..0000000
--- a/tempest/lib/services/volume/v1/qos_client.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# 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 QosSpecsClient(rest_client.RestClient):
-    """Volume V1 QoS client.
-
-       Client class to send CRUD QoS API requests
-    """
-
-    api_version = "v1"
-
-    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://docs.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:
-        https://docs.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:
-        https://docs.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)
diff --git a/tempest/lib/services/volume/v1/quotas_client.py b/tempest/lib/services/volume/v1/quotas_client.py
deleted file mode 100644
index 7f191ca..0000000
--- a/tempest/lib/services/volume/v1/quotas_client.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
-#
-#    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 V1 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://docs.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)
-        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/v1/services_client.py b/tempest/lib/services/volume/v1/services_client.py
deleted file mode 100644
index d438a34..0000000
--- a/tempest/lib/services/volume/v1/services_client.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2014 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 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):
-    """Volume V1 volume services client"""
-
-    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)
diff --git a/tempest/lib/services/volume/v1/snapshots_client.py b/tempest/lib/services/volume/v1/snapshots_client.py
deleted file mode 100644
index 7dfdcf2..0000000
--- a/tempest/lib/services/volume/v1/snapshots_client.py
+++ /dev/null
@@ -1,187 +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_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(rest_client.RestClient):
-    """Client class to send CRUD Volume V1 API requests."""
-
-    create_resp = 200
-
-    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://docs.openstack.org/api-ref/block-storage/v2/#list-snapshots
-        https://docs.openstack.org/api-ref/block-storage/v2/#list-snapshots-with-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://docs.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:
-        https://docs.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 delete_snapshot(self, snapshot_id):
-        """Delete Snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://docs.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."""
-        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 update_snapshot(self, snapshot_id, **kwargs):
-        """Updates a snapshot.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://docs.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 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://docs.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:
-        https://docs.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 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)
diff --git a/tempest/lib/services/volume/v1/types_client.py b/tempest/lib/services/volume/v1/types_client.py
deleted file mode 100644
index d434e65..0000000
--- a/tempest/lib/services/volume/v1/types_client.py
+++ /dev/null
@@ -1,166 +0,0 @@
-# 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://docs.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://docs.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://docs.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:
-        https://docs.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:
-        https://docs.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.
-
-        :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://docs.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)
diff --git a/tempest/lib/services/volume/v1/volumes_client.py b/tempest/lib/services/volume/v1/volumes_client.py
deleted file mode 100644
index 4ed5eb1..0000000
--- a/tempest/lib/services/volume/v1/volumes_client.py
+++ /dev/null
@@ -1,306 +0,0 @@
-# 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
-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
-
-
-class VolumesClient(rest_client.RestClient):
-    """Client class to send CRUD Volume V1 API requests"""
-
-    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:
-        https://docs.openstack.org/api-ref/block-storage/v2/#list-volumes
-        https://docs.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details
-        """
-        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:
-        https://docs.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(200, 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://docs.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):
-        """Deletes the Specified Volume."""
-        resp, body = self.delete("volumes/%s" % volume_id)
-        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:
-        https://docs.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."""
-        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):
-        try:
-            self.show_volume(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'
-
-    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://docs.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:
-        https://docs.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 create_volume_transfer(self, **kwargs):
-        """Create a volume transfer.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://docs.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, **params):
-        """List all the volume transfers created.
-
-        For a full list of available parameters, please refer to the official
-        API reference:
-        https://docs.openstack.org/api-ref/block-storage/v2/#list-volume-transfers
-        """
-        url = 'os-volume-transfer'
-        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://docs.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)
-
-    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."""
-        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."""
-        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 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."""
-        post_body = json.dumps({'os-retype': kwargs})
-        resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
diff --git a/tempest/lib/services/volume/v2/__init__.py b/tempest/lib/services/volume/v2/__init__.py
index 68982d9..756a41a 100644
--- a/tempest/lib/services/volume/v2/__init__.py
+++ b/tempest/lib/services/volume/v2/__init__.py
@@ -12,6 +12,8 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+import warnings
+
 from tempest.lib.services.volume.v2.availability_zone_client \
     import AvailabilityZoneClient
 from tempest.lib.services.volume.v2.backups_client import BackupsClient
@@ -44,3 +46,9 @@
            'LimitsClient', 'CapabilitiesClient', 'SchedulerStatsClient',
            'SnapshotManageClient', 'VolumeManageClient', 'TransfersClient',
            'QuotaClassesClient']
+
+warnings.warn(
+    "The tempest.lib.services.volume.v2 module (volume v2 APIs service "
+    "clients) is deprecated in favor of tempest.lib.services.volume.v3 "
+    "(volume v3 APIs service clients) and will be removed once Tempest stop "
+    "supporting stable wallaby.", DeprecationWarning)
diff --git a/tempest/lib/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
index e2fa836..039640b 100644
--- a/tempest/lib/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -39,6 +39,7 @@
     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.transfers_client import TransfersV355Client
 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 \
@@ -50,5 +51,6 @@
            'GroupsClient', 'HostsClient', 'LimitsClient', 'MessagesClient',
            'QosSpecsClient', 'QuotaClassesClient', 'QuotasClient',
            'SchedulerStatsClient', 'ServicesClient', 'SnapshotManageClient',
-           'SnapshotsClient', 'TransfersClient', 'TypesClient',
-           'VersionsClient', 'VolumeManageClient', 'VolumesClient']
+           'SnapshotsClient', 'TransfersClient', 'TransfersV355Client',
+           'TypesClient', 'VersionsClient', 'VolumeManageClient',
+           'VolumesClient']
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index 1df45fa..4bf7ffb 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import backups as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/group_snapshots_client.py b/tempest/lib/services/volume/v3/group_snapshots_client.py
index 4051c06..0f36fc9 100644
--- a/tempest/lib/services/volume/v3/group_snapshots_client.py
+++ b/tempest/lib/services/volume/v3/group_snapshots_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import group_snapshots as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index 1dcd508..9de36f4 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import group_types as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index 3d8523d..d1500cf 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import groups as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/hosts_client.py b/tempest/lib/services/volume/v3/hosts_client.py
index 019a852..9c64659 100644
--- a/tempest/lib/services/volume/v3/hosts_client.py
+++ b/tempest/lib/services/volume/v3/hosts_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import hosts as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/quotas_client.py b/tempest/lib/services/volume/v3/quotas_client.py
index 5b1a52c..3f4c4e1 100644
--- a/tempest/lib/services/volume/v3/quotas_client.py
+++ b/tempest/lib/services/volume/v3/quotas_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import quotas as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/services_client.py b/tempest/lib/services/volume/v3/services_client.py
index 8bc82c9..1111f81 100644
--- a/tempest/lib/services/volume/v3/services_client.py
+++ b/tempest/lib/services/volume/v3/services_client.py
@@ -13,16 +13,23 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import services as schema
+from tempest.lib.api_schema.response.volume.v3_7 import services as schemav37
 from tempest.lib.common import rest_client
+from tempest.lib.services.volume import base_client
 
 
-class ServicesClient(rest_client.RestClient):
+class ServicesClient(base_client.BaseClient):
     """Client class to send CRUD Volume Services API requests"""
 
+    schema_versions_info = [
+        {'min': None, 'max': '3.6', 'schema': schema},
+        {'min': '3.7', 'max': None, 'schema': schemav37}]
+
     def list_services(self, **params):
         """List all Cinder services.
 
@@ -36,6 +43,7 @@
 
         resp, body = self.get(url)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.list_services, resp, 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 8ca2044..ae31ee9 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import snapshots as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/transfers_client.py b/tempest/lib/services/volume/v3/transfers_client.py
index f572f95..cc4e1b2 100644
--- a/tempest/lib/services/volume/v3/transfers_client.py
+++ b/tempest/lib/services/volume/v3/transfers_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import transfers as schema
 from tempest.lib.common import rest_client
@@ -23,6 +24,8 @@
 class TransfersClient(rest_client.RestClient):
     """Client class to send CRUD Volume Transfer API requests"""
 
+    resource_path = 'os-volume-transfer'
+
     def create_volume_transfer(self, **kwargs):
         """Create a volume transfer.
 
@@ -31,14 +34,14 @@
         https://docs.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)
+        resp, body = self.post(self.resource_path, post_body)
         body = json.loads(body)
         self.validate_response(schema.create_volume_transfer, resp, body)
         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
+        url = "%s/%s" % (self.resource_path, transfer_id)
         resp, body = self.get(url)
         body = json.loads(body)
         self.validate_response(schema.show_volume_transfer, resp, body)
@@ -52,7 +55,7 @@
         https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-for-a-project
         https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-and-details
         """
-        url = 'os-volume-transfer'
+        url = self.resource_path
         schema_list_transfers = schema.list_volume_transfers_no_detail
         if detail:
             url += '/detail'
@@ -66,7 +69,7 @@
 
     def delete_volume_transfer(self, transfer_id):
         """Delete a volume transfer."""
-        resp, body = self.delete("os-volume-transfer/%s" % transfer_id)
+        resp, body = self.delete("%s/%s" % (self.resource_path, transfer_id))
         self.validate_response(schema.delete_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -77,9 +80,14 @@
         API reference:
         https://docs.openstack.org/api-ref/block-storage/v3/index.html#accept-a-volume-transfer
         """
-        url = 'os-volume-transfer/%s/accept' % transfer_id
+        url = '%s/%s/accept' % (self.resource_path, transfer_id)
         post_body = json.dumps({'accept': kwargs})
         resp, body = self.post(url, post_body)
         body = json.loads(body)
         self.validate_response(schema.accept_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
+
+
+class TransfersV355Client(TransfersClient):
+    """Client class to send CRUD for the "new" Transfers API (mv 3.55)"""
+    resource_path = 'volume-transfers'
diff --git a/tempest/lib/services/volume/v3/types_client.py b/tempest/lib/services/volume/v3/types_client.py
index 1ebd447..9858d87 100644
--- a/tempest/lib/services/volume/v3/types_client.py
+++ b/tempest/lib/services/volume/v3/types_client.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import volume_types as schema
 from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index 4ac4112..0bed827 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from six.moves.urllib.parse import urljoin
+from urllib.parse import urljoin
 
 from oslo_serialization import jsonutils as json
 
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index b8535d8..9c6fe68 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -13,9 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from urllib import parse as urllib
+
 from oslo_serialization import jsonutils as json
-import six
-from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.volume import volumes as schema
 from tempest.lib.common import rest_client
@@ -32,7 +32,7 @@
         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):
+        if isinstance(params, str):
             return params
         return urllib.urlencode(params)
 
diff --git a/tempest/manager.py b/tempest/manager.py
deleted file mode 100644
index b485ef2..0000000
--- a/tempest/manager.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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_log import log as logging
-
-from tempest import clients as tempest_clients
-from tempest import config
-from tempest.lib.services import clients
-
-CONF = config.CONF
-LOG = logging.getLogger(__name__)
-
-
-class Manager(clients.ServiceClients):
-    """Service client manager class for backward compatibility
-
-    The former manager.Manager is not a stable interface in Tempest,
-    nonetheless it is consumed by a number of plugins already. This class
-    exists to provide some grace time for the move to tempest.lib.
-    """
-
-    def __init__(self, credentials, scope='project'):
-        msg = ("tempest.manager.Manager is not a stable interface and as such "
-               "it should not be imported directly. It will be removed as "
-               "soon as the client manager becomes available in tempest.lib.")
-        LOG.warning(msg)
-        dscv = CONF.identity.disable_ssl_certificate_validation
-        _, uri = tempest_clients.get_auth_provider_class(credentials)
-        super(Manager, self).__init__(
-            credentials=credentials, scope=scope,
-            identity_uri=uri,
-            disable_ssl_certificate_validation=dscv,
-            ca_certs=CONF.identity.ca_certificates_file,
-            trace_requests=CONF.debug.trace_requests)
-
-
-def get_auth_provider(credentials, pre_auth=False, scope='project'):
-    """Shim to get_auth_provider in clients.py
-
-    get_auth_provider used to be hosted in this module, but it has been
-    moved to clients.py now as a more permanent location.
-    This module will be removed eventually, and this shim is only
-    maintained for the benefit of plugins already consuming this interface.
-    """
-    msg = ("tempest.manager.get_auth_provider is not a stable interface and "
-           "as such it should not imported directly. It will be removed as "
-           "the client manager becomes available in tempest.lib.")
-    LOG.warning(msg)
-    return tempest_clients.get_auth_provider(credentials=credentials,
-                                             pre_auth=pre_auth, scope=scope)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index ff860d5..7aa96b2 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -18,6 +18,7 @@
 import subprocess
 
 import netaddr
+
 from oslo_log import log
 from oslo_serialization import jsonutils as json
 from oslo_utils import netutils
@@ -29,7 +30,6 @@
 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_utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
@@ -46,7 +46,7 @@
 class ScenarioTest(tempest.test.BaseTestCase):
     """Base class for scenario tests. Uses tempest own clients. """
 
-    credentials = ['primary']
+    credentials = ['primary', 'admin']
 
     compute_min_microversion = None
     compute_max_microversion = LATEST_MICROVERSION
@@ -84,23 +84,18 @@
                 cls.placement_min_microversion,
                 CONF.placement.min_microversion))
 
-    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,
-            placement_microversion=self.placement_request_microversion))
+        cls.setup_api_microversion_fixture(
+            compute_microversion=cls.compute_request_microversion,
+            volume_microversion=cls.volume_request_microversion,
+            placement_microversion=cls.placement_request_microversion)
 
     def setup_compute_client(cls):
-        """Compute and Compute security groups client"""
+        """Compute client"""
         cls.compute_images_client = cls.os_primary.compute_images_client
         cls.keypairs_client = cls.os_primary.keypairs_client
-        cls.compute_security_groups_client = (
-            cls.os_primary.compute_security_groups_client)
-        cls.compute_security_group_rules_client = (
-            cls.os_primary.compute_security_group_rules_client)
         cls.servers_client = cls.os_primary.servers_client
         cls.interface_client = cls.os_primary.interfaces_client
+        cls.flavors_client = cls.os_primary.flavors_client
 
     def setup_network_client(cls):
         """Neutron network client"""
@@ -117,9 +112,6 @@
     def setup_clients(cls):
         """This setup the service clients for the tests"""
         super(ScenarioTest, cls).setup_clients()
-        cls.flavors_client = cls.os_primary.flavors_client
-        cls.compute_floating_ips_client = (
-            cls.os_primary.compute_floating_ips_client)
         if CONF.service_available.glance:
             # Check if glance v1 is available to determine which client to use.
             if CONF.image_feature_enabled.api_v1:
@@ -143,10 +135,20 @@
     # resp part which is not used in scenario tests
 
     def create_port(self, network_id, client=None, **kwargs):
-        """Creates port"""
+        """Creates port for the respective network_id
+
+        :param network_id: the id of the network
+        :param client: the client to use, defaults to self.ports_client
+        :param kwargs: additional arguments such as:
+            - namestart - a string to generate a name for the port from
+                        - default is self.__class__.__name__
+            - 'binding:vnic_type' - defaults to CONF.network.port_vnic_type
+            - 'binding:profile' - defaults to CONF.network.port_profile
+        """
         if not client:
             client = self.ports_client
-        name = data_utils.rand_name(self.__class__.__name__)
+        name = data_utils.rand_name(
+            kwargs.pop('namestart', 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:
@@ -155,12 +157,13 @@
             name=name,
             network_id=network_id,
             **kwargs)
+        self.assertIsNotNone(result, 'Unable to allocate port')
         port = result['port']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         client.delete_port, port['id'])
         return port
 
-    def create_keypair(self, client=None):
+    def create_keypair(self, client=None, **kwargs):
         """Creates keypair
 
         Keypair is a public key of OpenSSH key pair used for accessing
@@ -170,10 +173,11 @@
         """
         if not client:
             client = self.keypairs_client
-        name = data_utils.rand_name(self.__class__.__name__)
+        if not kwargs.get('name'):
+            kwargs['name'] = data_utils.rand_name(self.__class__.__name__)
         # We don't need to create a keypair by pubkey in scenario
-        body = client.create_keypair(name=name)
-        self.addCleanup(client.delete_keypair, name)
+        body = client.create_keypair(**kwargs)
+        self.addCleanup(client.delete_keypair, kwargs['name'])
         return body['keypair']
 
     def create_server(self, name=None, image_id=None, flavor=None,
@@ -199,6 +203,14 @@
                 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.
+                direct-physical: an SR-IOV port that is directly attached to a
+                                 VM using physical instead of virtual
+                                 functions.
+                baremetal: a baremetal port directly attached to a baremetal
+                           node.
+                virtio-forwarder:  an SR-IOV port that is indirectly attached
+                                   to a VM using a low-latency vhost-user
+                                   forwarding process.
               Defaults to ``CONF.network.port_vnic_type``.
             * *port_profile* (``dict``) --
               This attribute is a dictionary that can be used (with admin
@@ -206,6 +218,9 @@
               the port.
               example: port_profile = "capabilities:[switchdev]"
               Defaults to ``CONF.network.port_profile``.
+            * *create_port_body* (``dict``) --
+              This attribute is a dictionary of additional arguments to be
+              passed to create_port method.
         """
 
         # NOTE(jlanoux): As a first step, ssh checks in the scenario
@@ -231,7 +246,7 @@
         # every network
         if vnic_type or profile:
             ports = []
-            create_port_body = {}
+            create_port_body = kwargs.pop('create_port_body', {})
 
             if vnic_type:
                 create_port_body['binding:vnic_type'] = vnic_type
@@ -306,7 +321,7 @@
         return server
 
     def create_volume(self, size=None, name=None, snapshot_id=None,
-                      imageRef=None, volume_type=None):
+                      imageRef=None, volume_type=None, **kwargs):
         """Creates volume
 
         This wrapper utility creates volume and waits for volume to be
@@ -326,11 +341,11 @@
             size = max(size, min_disk)
         if name is None:
             name = data_utils.rand_name(self.__class__.__name__ + "-volume")
-        kwargs = {'display_name': name,
-                  'snapshot_id': snapshot_id,
-                  'imageRef': imageRef,
-                  'volume_type': volume_type,
-                  'size': size}
+        kwargs.update({'name': name,
+                       'snapshot_id': snapshot_id,
+                       'imageRef': imageRef,
+                       'volume_type': volume_type,
+                       'size': size})
 
         if CONF.compute.compute_volume_common_az:
             kwargs.setdefault('availability_zone',
@@ -352,21 +367,35 @@
 
     def create_backup(self, volume_id, name=None, description=None,
                       force=False, snapshot_id=None, incremental=False,
-                      container=None):
-        """Creates backup
+                      container=None, **kwargs):
+        """Creates a backup of the given volume_id or snapshot_id
 
-        This wrapper utility creates backup and waits for backup to be
-        in 'available' state.
+        This wrapper utility creates a backup and waits until it is in
+       'available' state.
+
+        :param volume_id: UUID of the volume to back up
+        :param name: backup name, '$classname-backup' by default
+        :param description: Description of the backup, None by default
+        :param force: boolean whether to backup even if the volume is attached
+            False by default
+        :param snapshot_id: UUID of the source snapshot to back up
+            None by default
+        :param incremental: boolean, False by default
+        :param container: a container name, None by default
+        :param **kwargs: additional parameters per the documentation:
+            https://docs.openstack.org/api-ref/block-storage/v3/
+            #create-a-backup
         """
 
         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}
+        args = {'name': name,
+                'description': description,
+                'force': force,
+                'snapshot_id': snapshot_id,
+                'incremental': incremental,
+                'container': container}
+        args.update(kwargs)
         backup = self.backups_client.create_backup(volume_id=volume_id,
                                                    **kwargs)['backup']
         self.addCleanup(self.backups_client.delete_backup, backup['id'])
@@ -374,14 +403,20 @@
                                                 backup['id'], 'available')
         return backup
 
-    def restore_backup(self, backup_id):
-        """Restore backup
+    def restore_backup(self, backup_id, **kwargs):
+        """Restores a backup given by the backup_id
 
-        This wrapper utility restores backup and waits for backup to be
-        in 'available' state.
+        This wrapper utility restores a backup and waits until it is in
+        'available' state.
+
+        :param backup_id: UUID of a backup to restore
+        :param **kwargs: additional parameters per the documentation:
+            https://docs.openstack.org/api-ref/block-storage/v3/
+            #restore-a-backup
         """
 
-        restore = self.backups_client.restore_backup(backup_id)['restore']
+        body = self.backups_client.restore_backup(backup_id, **kwargs)
+        restore = body['restore']
         self.addCleanup(self.volumes_client.delete_volume,
                         restore['volume_id'])
         waiters.wait_for_volume_resource_status(self.backups_client,
@@ -392,29 +427,36 @@
         self.assertEqual(backup_id, restore['backup_id'])
         return restore
 
-    def rebuild_server(self, server_id, image=None,
-                       preserve_ephemeral=False, wait=True,
-                       rebuild_kwargs=None):
+    def rebuild_server(self, server_id, image=None, preserve_ephemeral=False,
+                       wait=True, **kwargs):
         if image is None:
             image = CONF.compute.image_ref
-        rebuild_kwargs = rebuild_kwargs or {}
         LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
                   server_id, image, preserve_ephemeral)
         self.servers_client.rebuild_server(
             server_id=server_id,
             image_ref=image,
             preserve_ephemeral=preserve_ephemeral,
-            **rebuild_kwargs)
+            **kwargs)
         if wait:
             waiters.wait_for_server_status(self.servers_client,
                                            server_id, 'ACTIVE')
 
     def create_volume_snapshot(self, volume_id, name=None, description=None,
-                               metadata=None, force=False):
-        """Creates volume
+                               metadata=None, force=False, **kwargs):
+        """Creates volume's snapshot
 
-        This wrapper utility creates volume snapshot and waits for backup
-        to be in 'available' state.
+        This wrapper utility creates volume snapshot and waits for it until
+        it is in 'available' state.
+
+        :param volume_id: UUID of a volume to create snapshot of
+        :param name: name of the snapshot, '$classname-snapshot' by default
+        :param description: description of the snapshot
+        :param metadata: metadata key and value pairs for the snapshot
+        :param force: whether snapshot even when the volume is attached
+        :param **kwargs: additional parameters per the doc
+            https://docs.openstack.org/api-ref/block-storage/v3/
+            #create-a-snapshot
         """
 
         name = name or data_utils.rand_name(
@@ -422,9 +464,10 @@
         snapshot = self.snapshots_client.create_snapshot(
             volume_id=volume_id,
             force=force,
-            display_name=name,
+            name=name,
             description=description,
-            metadata=metadata)['snapshot']
+            metadata=metadata,
+            **kwargs)['snapshot']
 
         self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
                         snapshot['id'])
@@ -435,7 +478,7 @@
             snapshot['id'])['snapshot']
         return snapshot
 
-    def _cleanup_volume_type(self, volume_type):
+    def cleanup_volume_type(self, volume_type):
         """Clean up a given volume type.
 
         Ensuring all volumes associated to a type are first removed before
@@ -454,7 +497,8 @@
             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):
+    def create_volume_type(self, client=None, name=None, backend_name=None,
+                           **kwargs):
         """Creates volume type
 
         In a multiple-storage back-end configuration,
@@ -481,69 +525,175 @@
 
         LOG.debug("Creating a volume type: %s on backend %s",
                   randomized_name, backend_name)
-        extra_specs = {}
+        extra_specs = kwargs.pop("extra_specs", {})
         if backend_name:
-            extra_specs = {"volume_backend_name": backend_name}
+            extra_specs.update({"volume_backend_name": backend_name})
 
-        volume_type = client.create_volume_type(
-            name=randomized_name, extra_specs=extra_specs)['volume_type']
+        volume_type_resp = client.create_volume_type(
+            name=randomized_name, extra_specs=extra_specs, **kwargs)
+        volume_type = volume_type_resp['volume_type']
+
         self.assertIn('id', volume_type)
-        self.addCleanup(self._cleanup_volume_type, volume_type)
+        self.addCleanup(self.cleanup_volume_type, volume_type)
         return volume_type
 
-    def _create_loginable_secgroup_rule(self, secgroup_id=None):
-        _client = self.compute_security_groups_client
-        _client_rules = self.compute_security_group_rules_client
-        if secgroup_id is None:
-            sgs = _client.list_security_groups()['security_groups']
-            for sg in sgs:
-                if sg['name'] == 'default':
-                    secgroup_id = sg['id']
-
-        # These rules are intended to permit inbound ssh and icmp
-        # traffic from all sources, so no group_id is provided.
-        # Setting a group_id would only permit traffic from ports
-        # belonging to the same security group.
-        rulesets = [
-            {
-                # ssh
-                'ip_protocol': 'tcp',
-                'from_port': 22,
-                'to_port': 22,
-                'cidr': '0.0.0.0/0',
-            },
-            {
-                # ping
-                'ip_protocol': 'icmp',
-                'from_port': -1,
-                'to_port': -1,
-                'cidr': '0.0.0.0/0',
-            }
-        ]
-        rules = list()
-        for ruleset in rulesets:
-            sg_rule = _client_rules.create_security_group_rule(
-                parent_group_id=secgroup_id, **ruleset)['security_group_rule']
-            rules.append(sg_rule)
-        return rules
-
-    def _create_security_group(self):
-        """Create security group and add rules to security group"""
-        sg_name = data_utils.rand_name(self.__class__.__name__)
-        sg_desc = sg_name + " description"
-        secgroup = self.compute_security_groups_client.create_security_group(
-            name=sg_name, description=sg_desc)['security_group']
-        self.assertEqual(secgroup['name'], sg_name)
-        self.assertEqual(secgroup['description'], sg_desc)
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.compute_security_groups_client.delete_security_group,
-            secgroup['id'])
+    def create_security_group(self, security_group_rules_client=None,
+                              project_id=None,
+                              namestart='secgroup-smoke',
+                              security_groups_client=None):
+        if security_group_rules_client is None:
+            security_group_rules_client = self.security_group_rules_client
+        if security_groups_client is None:
+            security_groups_client = self.security_groups_client
+        if project_id is None:
+            project_id = security_groups_client.project_id
+        secgroup = self.create_empty_security_group(
+            namestart=namestart, client=security_groups_client,
+            project_id=project_id)
 
         # Add rules to the security group
-        self._create_loginable_secgroup_rule(secgroup['id'])
+        rules = self.create_loginable_secgroup_rule(
+            security_group_rules_client=security_group_rules_client,
+            secgroup=secgroup,
+            security_groups_client=security_groups_client)
+        for rule in rules:
+            self.assertEqual(project_id, rule['project_id'])
+            self.assertEqual(secgroup['id'], rule['security_group_id'])
         return secgroup
 
+    def create_empty_security_group(self, client=None, project_id=None,
+                                    namestart='secgroup-smoke'):
+        """Create a security group without rules.
+
+        Default rules will be created:
+         - IPv4 egress to any
+         - IPv6 egress to any
+        :param project_id: secgroup will be created in this project
+        :returns: the created security group
+        """
+
+        if client is None:
+            client = self.security_groups_client
+        if not project_id:
+            project_id = client.project_id
+        sg_name = data_utils.rand_name(namestart)
+        sg_desc = sg_name + " description"
+        sg_dict = dict(name=sg_name,
+                       description=sg_desc)
+        sg_dict['project_id'] = project_id
+        result = client.create_security_group(**sg_dict)
+
+        secgroup = result['security_group']
+        self.assertEqual(secgroup['name'], sg_name)
+        self.assertEqual(project_id, secgroup['project_id'])
+        self.assertEqual(secgroup['description'], sg_desc)
+
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        client.delete_security_group, secgroup['id'])
+        return secgroup
+
+    def create_security_group_rule(self, secgroup=None,
+                                   sec_group_rules_client=None,
+                                   project_id=None,
+                                   security_groups_client=None, **kwargs):
+        """Create a rule from a dictionary of rule parameters.
+
+        Create a rule in a secgroup. if secgroup not defined will search for
+        default secgroup in project_id.
+        :param secgroup: the security group.
+        :param project_id: if secgroup not passed -- the tenant in which to
+            search for default secgroup
+        :param kwargs: a dictionary containing rule parameters:
+            for example, to allow incoming ssh:
+            rule = {
+                    direction: 'ingress'
+                    protocol:'tcp',
+                    port_range_min: 22,
+                    port_range_max: 22
+                    }
+        """
+
+        if sec_group_rules_client is None:
+            sec_group_rules_client = self.security_group_rules_client
+        if security_groups_client is None:
+            security_groups_client = self.security_groups_client
+        if not project_id:
+            project_id = security_groups_client.project_id
+        if secgroup is None:
+            # Get default secgroup for project_id
+            default_secgroups = security_groups_client.list_security_groups(
+                name='default', project_id=project_id)['security_groups']
+            msg = "No default security group for project %s." % (project_id)
+            self.assertNotEmpty(default_secgroups, msg)
+            secgroup = default_secgroups[0]
+
+        ruleset = dict(security_group_id=secgroup['id'],
+                       project_id=secgroup['project_id'])
+        ruleset.update(kwargs)
+
+        sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
+        sg_rule = sg_rule['security_group_rule']
+
+        self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
+        self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
+
+        return sg_rule
+
+    def create_loginable_secgroup_rule(self, security_group_rules_client=None,
+                                       secgroup=None,
+                                       security_groups_client=None):
+        """Create loginable security group rule by neutron clients by default.
+
+        This function will create:
+        1. egress and ingress tcp port 22 allow rule in order to allow ssh
+        access for ipv4.
+        2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
+        3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
+        """
+
+        if security_group_rules_client is None:
+            security_group_rules_client = self.security_group_rules_client
+        if security_groups_client is None:
+            security_groups_client = self.security_groups_client
+        rules = []
+        rulesets = [
+            dict(
+                # ssh
+                protocol='tcp',
+                port_range_min=22,
+                port_range_max=22,
+            ),
+            dict(
+                # ping
+                protocol='icmp',
+            ),
+            dict(
+                # ipv6-icmp for ping6
+                protocol='icmp',
+                ethertype='IPv6',
+            )
+        ]
+        sec_group_rules_client = security_group_rules_client
+        for ruleset in rulesets:
+            for r_direction in ['ingress', 'egress']:
+                ruleset['direction'] = r_direction
+                try:
+                    sg_rule = self.create_security_group_rule(
+                        sec_group_rules_client=sec_group_rules_client,
+                        secgroup=secgroup,
+                        security_groups_client=security_groups_client,
+                        **ruleset)
+                except lib_exc.Conflict as ex:
+                    # if rule already exist - skip rule and continue
+                    msg = 'Security group rule already exists'
+                    if msg not in ex._error_string:
+                        raise ex
+                else:
+                    self.assertEqual(r_direction, sg_rule['direction'])
+                    rules.append(sg_rule)
+
+        return rules
+
     def get_remote_client(self, ip_address, username=None, private_key=None,
                           server=None):
         """Get a SSH client to a remote server
@@ -573,21 +723,16 @@
         linux_client.validate_authentication()
         return linux_client
 
-    def image_create(self, name='scenario-img'):
+    def image_create(self, name='scenario-img', **kwargs):
         img_path = CONF.scenario.img_file
         if not os.path.exists(img_path):
-            # TODO(kopecmartin): replace LOG.warning for rasing
-            # InvalidConfiguration exception after tempest 25.0.0 is
-            # released - there will be one release which accepts both
-            # behaviors in order to avoid many failures across CIs and etc.
-            LOG.warning(
+            lib_exc.InvalidConfiguration(
                 'Starting Tempest 25.0.0 release, CONF.scenario.img_file need '
                 'a full path for the image. CONF.scenario.img_dir was '
                 'deprecated and will be removed in the next release. Till '
-                'Tempest 25.0.0, old behavior is maintained and keep working '
+                'Tempest 25.0.0, old behavior was maintained and kept working '
                 'but starting Tempest 26.0.0, you need to specify the full '
                 'path in CONF.scenario.img_file config option.')
-            img_path = os.path.join(CONF.scenario.img_dir, img_path)
         img_container_format = CONF.scenario.img_container_format
         img_disk_format = CONF.scenario.img_disk_format
         img_properties = CONF.scenario.img_properties
@@ -613,6 +758,7 @@
             # Additional properties are flattened out in the v2 API.
             if img_properties:
                 params.update(img_properties)
+        params.update(kwargs)
         body = self.image_client.create_image(**params)
         image = body['image'] if 'image' in body else body
         self.addCleanup(self.image_client.delete_image, image['id'])
@@ -625,7 +771,7 @@
         LOG.debug("image:%s", image['id'])
         return image['id']
 
-    def _log_console_output(self, servers=None, client=None):
+    def log_console_output(self, servers=None, client=None, **kwargs):
         """Console log output"""
         if not CONF.compute_feature_enabled.console_output:
             LOG.debug('Console output not supported, cannot log')
@@ -637,7 +783,7 @@
         for server in servers:
             try:
                 console_output = client.get_console_output(
-                    server['id'])['output']
+                    server['id'], **kwargs)['output']
                 LOG.debug('Console output for %s\nbody=\n%s',
                           server['id'], console_output)
             except lib_exc.NotFound:
@@ -649,7 +795,7 @@
         if not isinstance(exc, lib_exc.SSHTimeout):
             LOG.debug('Network information on a devstack host')
 
-    def create_server_snapshot(self, server, name=None):
+    def create_server_snapshot(self, server, name=None, **kwargs):
         """Creates server snapshot"""
         # Glance client
         _image_client = self.image_client
@@ -658,7 +804,7 @@
         if name is None:
             name = data_utils.rand_name(self.__class__.__name__ + 'snapshot')
         LOG.debug("Creating a snapshot image for server: %s", server['name'])
-        image = _images_client.create_image(server['id'], name=name)
+        image = _images_client.create_image(server['id'], name=name, **kwargs)
         image_id = image.response['location'].split('images/')[1]
         waiters.wait_for_image_status(_image_client, image_id, 'active')
 
@@ -697,31 +843,32 @@
                   image_name, server['name'])
         return snapshot_image
 
-    def nova_volume_attach(self, server, volume_to_attach):
+    def nova_volume_attach(self, server, volume_to_attach, **kwargs):
         """Compute volume attach
 
         This utility attaches volume from compute and waits for the
         volume status to be 'in-use' state.
         """
         volume = self.servers_client.attach_volume(
-            server['id'], volumeId=volume_to_attach['id'])['volumeAttachment']
+            server['id'], volumeId=volume_to_attach['id'],
+            **kwargs)['volumeAttachment']
         self.assertEqual(volume_to_attach['id'], volume['id'])
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume['id'], 'in-use')
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.nova_volume_detach, server, volume)
         # Return the updated volume after the attachment
         return self.volumes_client.show_volume(volume['id'])['volume']
 
     def nova_volume_detach(self, server, volume):
         """Compute volume detach
 
-        This utility detaches volume from compute and check whether the
-        volume status is 'available' state, and if not, an exception
-        will be thrown.
+        This utility detaches the volume from the server and checks whether the
+        volume attachment has been removed from Nova.
         """
         self.servers_client.detach_volume(server['id'], volume['id'])
-        waiters.wait_for_volume_resource_status(self.volumes_client,
-                                                volume['id'], 'available')
-        volume = self.volumes_client.show_volume(volume['id'])['volume']
+        waiters.wait_for_volume_attachment_remove_from_server(
+            self.servers_client, server['id'], volume['id'])
 
     def ping_ip_address(self, ip_address, should_succeed=True,
                         ping_timeout=None, mtu=None, server=None):
@@ -760,7 +907,7 @@
                       'result': 'expected' if result else 'unexpected'
                   })
         if server:
-            self._log_console_output([server])
+            self.log_console_output([server])
         return result
 
     def check_vm_connectivity(self, ip_address,
@@ -810,33 +957,115 @@
                 LOG.exception(extra_msg)
                 raise
 
-    def create_floating_ip(self, server, pool_name=None):
-        """Create a floating IP and associates to a server on Nova"""
+    def get_server_port_id_and_ip4(self, server, ip_addr=None, **kwargs):
 
-        if not pool_name:
-            pool_name = CONF.network.floating_network_name
-        floating_ip = (self.compute_floating_ips_client.
-                       create_floating_ip(pool=pool_name)['floating_ip'])
+        if ip_addr and not kwargs.get('fixed_ips'):
+            kwargs['fixed_ips'] = 'ip_address=%s' % ip_addr
+        ports = self.os_admin.ports_client.list_ports(
+            device_id=server['id'], **kwargs)['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
+
+        def _is_active(port):
+            # NOTE(vsaienko) With Ironic, instances live on separate hardware
+            # servers. Neutron does not bind ports for Ironic instances, as a
+            # result the port remains in the DOWN state. This has been fixed
+            # with the introduction of the networking-baremetal plugin but
+            # it's not mandatory (and is not used on all stable branches).
+            return (port['status'] == 'ACTIVE' or
+                    port.get('binding:vnic_type') == 'baremetal')
+
+        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
+                        _is_active(p))]
+        inactive = [p for p in ports if p['status'] != 'ACTIVE']
+        if inactive:
+            LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
+
+        self.assertNotEmpty(port_map,
+                            "No IPv4 addresses found in: %s" % ports)
+        self.assertEqual(len(port_map), 1,
+                         "Found multiple IPv4 addresses: %s. "
+                         "Unable to determine which port to target."
+                         % port_map)
+        return port_map[0]
+
+    def create_floating_ip(self, server, external_network_id=None,
+                           port_id=None, client=None, **kwargs):
+        """Create a floating IP and associates to a resource/port on Neutron"""
+
+        if not external_network_id:
+            external_network_id = CONF.network.public_network_id
+        if not client:
+            client = self.floating_ips_client
+        if not port_id:
+            port_id, ip4 = self.get_server_port_id_and_ip4(server)
+        else:
+            ip4 = None
+
+        floatingip_kwargs = {
+            'floating_network_id': external_network_id,
+            'port_id': port_id,
+            'tenant_id': server.get('project_id') or server['tenant_id'],
+            'fixed_ip_address': ip4,
+        }
+        if CONF.network.subnet_id:
+            floatingip_kwargs['subnet_id'] = CONF.network.subnet_id
+
+        floatingip_kwargs.update(kwargs)
+        result = client.create_floatingip(**floatingip_kwargs)
+        floating_ip = result['floatingip']
+
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self.compute_floating_ips_client.delete_floating_ip,
+                        client.delete_floatingip,
                         floating_ip['id'])
-        self.compute_floating_ips_client.associate_floating_ip_to_server(
-            floating_ip['ip'], server['id'])
+        return floating_ip
+
+    def associate_floating_ip(self, floating_ip, server):
+        """Associate floating ip to server
+
+        This wrapper utility attaches the floating_ip for
+        the respective port_id of server
+        """
+        port_id, _ = self.get_server_port_id_and_ip4(server)
+        kwargs = dict(port_id=port_id)
+        floating_ip = self.floating_ips_client.update_floatingip(
+            floating_ip['id'], **kwargs)['floatingip']
+        self.assertEqual(port_id, floating_ip['port_id'])
+        return floating_ip
+
+    def disassociate_floating_ip(self, floating_ip):
+        """Disassociates floating ip
+
+        This wrapper utility disassociates given floating ip.
+        :param floating_ip: a dict which is a return value of
+        floating_ips_client.create_floatingip method
+        """
+        kwargs = dict(port_id=None)
+        floating_ip = self.floating_ips_client.update_floatingip(
+            floating_ip['id'], **kwargs)['floatingip']
+        self.assertIsNone(floating_ip['port_id'])
         return floating_ip
 
     def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
-                         private_key=None, server=None):
+                         private_key=None, server=None, username=None,
+                         fs='ext4'):
         """Creates timestamp
 
         This wrapper utility does ssh, creates timestamp and returns the
         created timestamp.
         """
-
         ssh_client = self.get_remote_client(ip_address,
                                             private_key=private_key,
-                                            server=server)
+                                            server=server,
+                                            username=username)
+
         if dev_name is not None:
-            ssh_client.make_fs(dev_name)
+            ssh_client.make_fs(dev_name, fs=fs)
             ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
                                                                mount_path))
         cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
@@ -848,15 +1077,25 @@
         return timestamp
 
     def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
-                      private_key=None, server=None):
+                      private_key=None, server=None, username=None):
         """Returns timestamp
 
         This wrapper utility does ssh and returns the timestamp.
+
+        :param ip_address: The floating IP or fixed IP of the remote server
+        :param dev_name: Name of the device that stores the timestamp
+        :param mount_path: Path which should be used as mount point for
+                           dev_name
+        :param private_key: The SSH private key to use for authentication
+        :param server: Server dict, used for debugging purposes
+        :param username: Name of the Linux account on the remote server
         """
 
         ssh_client = self.get_remote_client(ip_address,
                                             private_key=private_key,
-                                            server=server)
+                                            server=server,
+                                            username=username)
+
         if dev_name is not None:
             ssh_client.mount(dev_name, mount_path)
         timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
@@ -865,18 +1104,23 @@
             ssh_client.exec_command('sudo umount %s' % mount_path)
         return timestamp
 
-    def get_server_ip(self, server):
+    def get_server_ip(self, server, **kwargs):
         """Get the server fixed or floating IP.
 
         Based on the configuration we're in, return a correct ip
         address for validating that a guest is up.
+
+        If CONF.validation.connect_method is floating, then
+        a floating ip will be created passing kwargs as additional
+        argument.
         """
 
         if CONF.validation.connect_method == 'floating':
             # The tests calling this method don't have a floating IP
             # and can't make use of the validation resources. So the
             # method is creating the floating IP there.
-            return self.create_floating_ip(server)['ip']
+            return self.create_floating_ip(
+                server, **kwargs)['floating_ip_address']
         elif CONF.validation.connect_method == 'fixed':
             # Determine the network name to look for based on config or creds
             # provider network resources.
@@ -916,14 +1160,14 @@
                                     keypair=None,
                                     security_group=None,
                                     delete_on_termination=False,
-                                    name=None):
+                                    name=None, **kwargs):
         """Boot instance from resource
 
         This wrapper utility boots instance from resource with block device
         mapping with source info passed in arguments
         """
 
-        create_kwargs = dict()
+        create_kwargs = dict({'image_id': ''})
         if keypair:
             create_kwargs['key_name'] = keypair['name']
         if security_group:
@@ -935,15 +1179,25 @@
             delete_on_termination=delete_on_termination))
         if name:
             create_kwargs['name'] = name
+        create_kwargs.update(kwargs)
 
-        return self.create_server(image_id='', **create_kwargs)
+        return self.create_server(**create_kwargs)
 
-    def create_volume_from_image(self):
-        """Create volume from image"""
-        img_uuid = CONF.compute.image_ref
-        vol_name = data_utils.rand_name(
-            self.__class__.__name__ + '-volume-origin')
-        return self.create_volume(name=vol_name, imageRef=img_uuid)
+    def create_volume_from_image(self, **kwargs):
+        """Create volume from image.
+
+        :param image_id: ID of the image to create volume from,
+            CONF.compute.image_ref by default
+        :param name: name of the volume,
+            '$classname-volume-origin' by default
+        :param **kwargs: additional parameters
+        """
+        image_id = kwargs.pop('image_id', CONF.compute.image_ref)
+        name = kwargs.pop('name', None)
+        if not name:
+            namestart = self.__class__.__name__ + '-volume-origin'
+            name = data_utils.rand_name(namestart)
+        return self.create_volume(name=name, imageRef=image_id, **kwargs)
 
 
 class NetworkScenarioTest(ScenarioTest):
@@ -958,18 +1212,16 @@
 
     """
 
-    credentials = ['primary', 'admin']
-
     @classmethod
     def skip_checks(cls):
         super(NetworkScenarioTest, cls).skip_checks()
         if not CONF.service_available.neutron:
             raise cls.skipException('Neutron not available')
 
-    def _create_network(self, networks_client=None,
-                        project_id=None,
-                        namestart='network-smoke-',
-                        port_security_enabled=True, **net_dict):
+    def create_network(self, networks_client=None,
+                       project_id=None,
+                       namestart='network-smoke-',
+                       port_security_enabled=True, **net_dict):
         if not networks_client:
             networks_client = self.networks_client
         if not project_id:
@@ -1004,6 +1256,8 @@
         :Keyword Arguments:
 
             * *ip_version = ip version of the given network,
+            use_default_subnetpool = default subnetpool to
+                    manage IPv6 addresses range.
         """
 
         if not subnets_client:
@@ -1012,131 +1266,93 @@
         def cidr_in_use(cidr, project_id):
             """Check cidr existence
 
-            :returns: True if subnet with cidr already exist in tenant
-                  False else
+            :returns: True if subnet with cidr already exist in tenant or
+                  external False else
             """
-            cidr_in_use = self.os_admin.subnets_client.list_subnets(
+            tenant_subnets = self.os_admin.subnets_client.list_subnets(
                 project_id=project_id, cidr=cidr)['subnets']
-            return len(cidr_in_use) != 0
+            external_nets = self.os_admin.networks_client.list_networks(
+                **{"router:external": True})['networks']
+            external_subnets = []
+            for ext_net in external_nets:
+                external_subnets.extend(
+                    self.os_admin.subnets_client.list_subnets(
+                        network_id=ext_net['id'], cidr=cidr)['subnets'])
+            return len(tenant_subnets + external_subnets) != 0
 
-        ip_version = kwargs.pop('ip_version', 4)
-
-        if ip_version == 6:
-            tenant_cidr = netaddr.IPNetwork(
-                CONF.network.project_network_v6_cidr)
-            num_bits = CONF.network.project_network_v6_mask_bits
-        else:
-            tenant_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
-            num_bits = CONF.network.project_network_mask_bits
-
-        result = None
-        str_cidr = None
-        # Repeatedly attempt subnet creation with sequential cidr
-        # blocks until an unallocated block is found.
-        for subnet_cidr in tenant_cidr.subnet(num_bits):
-            str_cidr = str(subnet_cidr)
-            if cidr_in_use(str_cidr, project_id=network['project_id']):
-                continue
+        def _make_create_subnet_request(namestart, network,
+                                        ip_version, subnets_client, **kwargs):
 
             subnet = dict(
                 name=data_utils.rand_name(namestart),
                 network_id=network['id'],
                 project_id=network['project_id'],
-                cidr=str_cidr,
                 ip_version=ip_version,
                 **kwargs
             )
+
+            if ip_version == 6:
+                subnet['ipv6_address_mode'] = 'slaac'
+                subnet['ipv6_ra_mode'] = 'slaac'
+
             try:
-                result = subnets_client.create_subnet(**subnet)
-                break
+                return subnets_client.create_subnet(**subnet)
             except lib_exc.Conflict as e:
-                is_overlapping_cidr = 'overlaps with another subnet' in str(e)
-                if not is_overlapping_cidr:
+                if 'overlaps with another subnet' not in str(e):
                     raise
+
+        result = None
+        str_cidr = None
+
+        use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
+        ip_version = kwargs.pop('ip_version', 4)
+
+        if not use_default_subnetpool:
+
+            if ip_version == 6:
+                tenant_cidr = netaddr.IPNetwork(
+                    CONF.network.project_network_v6_cidr)
+                num_bits = CONF.network.project_network_v6_mask_bits
+            else:
+                tenant_cidr = netaddr.IPNetwork(
+                    CONF.network.project_network_cidr)
+                num_bits = CONF.network.project_network_mask_bits
+
+        # Repeatedly attempt subnet creation with sequential cidr
+        # blocks until an unallocated block is found.
+            for subnet_cidr in tenant_cidr.subnet(num_bits):
+                str_cidr = str(subnet_cidr)
+                if cidr_in_use(str_cidr, project_id=network['project_id']):
+                    continue
+                result = _make_create_subnet_request(
+                    namestart, network, ip_version, subnets_client,
+                    cidr=str_cidr, **kwargs)
+
+                if result is not None:
+                    break
+
+        else:
+            result = _make_create_subnet_request(
+                namestart, network, ip_version, subnets_client,
+                **kwargs)
         self.assertIsNotNone(result, 'Unable to allocate tenant network')
 
         subnet = result['subnet']
-        self.assertEqual(subnet['cidr'], str_cidr)
+        if str_cidr is not None:
+            self.assertEqual(subnet['cidr'], str_cidr)
 
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         subnets_client.delete_subnet, subnet['id'])
 
         return subnet
 
-    def _get_server_port_id_and_ip4(self, server, ip_addr=None):
-        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
-
-        def _is_active(port):
-            # NOTE(vsaienko) With Ironic, instances live on separate hardware
-            # servers. Neutron does not bind ports for Ironic instances, as a
-            # result the port remains in the DOWN state. This has been fixed
-            # with the introduction of the networking-baremetal plugin but
-            # it's not mandatory (and is not used on all stable branches).
-            return (port['status'] == 'ACTIVE' or
-                    port.get('binding:vnic_type') == 'baremetal')
-
-        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
-                        _is_active(p))]
-        inactive = [p for p in ports if p['status'] != 'ACTIVE']
-        if inactive:
-            LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
-
-        self.assertNotEmpty(port_map,
-                            "No IPv4 addresses found in: %s" % ports)
-        self.assertEqual(len(port_map), 1,
-                         "Found multiple IPv4 addresses: %s. "
-                         "Unable to determine which port to target."
-                         % port_map)
-        return port_map[0]
-
-    def _get_network_by_name(self, network_name):
+    def get_network_by_name(self, network_name):
         net = self.os_admin.networks_client.list_networks(
             name=network_name)['networks']
         self.assertNotEmpty(net,
                             "Unable to get network by name: %s" % network_name)
         return net[0]
 
-    def create_floating_ip(self, server, external_network_id=None,
-                           port_id=None, client=None):
-        """Create a floating IP and associates to a resource/port on Neutron"""
-
-        if not external_network_id:
-            external_network_id = CONF.network.public_network_id
-        if not client:
-            client = self.floating_ips_client
-        if not port_id:
-            port_id, ip4 = self._get_server_port_id_and_ip4(server)
-        else:
-            ip4 = None
-
-        kwargs = {
-            'floating_network_id': external_network_id,
-            'port_id': port_id,
-            'tenant_id': server.get('project_id') or server['tenant_id'],
-            'fixed_ip_address': ip4,
-        }
-        if CONF.network.subnet_id:
-            kwargs['subnet_id'] = CONF.network.subnet_id
-        result = client.create_floatingip(**kwargs)
-        floating_ip = result['floatingip']
-
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        client.delete_floatingip,
-                        floating_ip['id'])
-        return floating_ip
-
     def check_floating_ip_status(self, floating_ip, status):
         """Verifies floatingip reaches the given status
 
@@ -1187,7 +1403,7 @@
                                                should_connect=should_connect)
         except Exception as e:
             LOG.exception('Tenant network connectivity check failed')
-            self._log_console_output(servers_for_debug)
+            self.log_console_output(servers_for_debug)
             self._log_net_info(e)
             raise
 
@@ -1230,169 +1446,10 @@
                 % (dest, source_host)
         else:
             msg = "%s is reachable from %s" % (dest, source_host)
-        self._log_console_output()
+        self.log_console_output()
         self.fail(msg)
 
-    def _create_security_group(self, security_group_rules_client=None,
-                               project_id=None,
-                               namestart='secgroup-smoke',
-                               security_groups_client=None):
-        if security_group_rules_client is None:
-            security_group_rules_client = self.security_group_rules_client
-        if security_groups_client is None:
-            security_groups_client = self.security_groups_client
-        if project_id is None:
-            project_id = security_groups_client.project_id
-        secgroup = self._create_empty_security_group(
-            namestart=namestart, client=security_groups_client,
-            project_id=project_id)
-
-        # Add rules to the security group
-        rules = self._create_loginable_secgroup_rule(
-            security_group_rules_client=security_group_rules_client,
-            secgroup=secgroup,
-            security_groups_client=security_groups_client)
-        for rule in rules:
-            self.assertEqual(project_id, rule['project_id'])
-            self.assertEqual(secgroup['id'], rule['security_group_id'])
-        return secgroup
-
-    def _create_empty_security_group(self, client=None, project_id=None,
-                                     namestart='secgroup-smoke'):
-        """Create a security group without rules.
-
-        Default rules will be created:
-         - IPv4 egress to any
-         - IPv6 egress to any
-
-        :param project_id: secgroup will be created in this project
-        :returns: the created security group
-        """
-
-        if client is None:
-            client = self.security_groups_client
-        if not project_id:
-            project_id = client.project_id
-        sg_name = data_utils.rand_name(namestart)
-        sg_desc = sg_name + " description"
-        sg_dict = dict(name=sg_name,
-                       description=sg_desc)
-        sg_dict['project_id'] = project_id
-        result = client.create_security_group(**sg_dict)
-
-        secgroup = result['security_group']
-        self.assertEqual(secgroup['name'], sg_name)
-        self.assertEqual(project_id, secgroup['project_id'])
-        self.assertEqual(secgroup['description'], sg_desc)
-
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        client.delete_security_group, secgroup['id'])
-        return secgroup
-
-    def _create_security_group_rule(self, secgroup=None,
-                                    sec_group_rules_client=None,
-                                    project_id=None,
-                                    security_groups_client=None, **kwargs):
-        """Create a rule from a dictionary of rule parameters.
-
-        Create a rule in a secgroup. if secgroup not defined will search for
-        default secgroup in project_id.
-
-        :param secgroup: the security group.
-        :param project_id: if secgroup not passed -- the tenant in which to
-            search for default secgroup
-        :param kwargs: a dictionary containing rule parameters:
-            for example, to allow incoming ssh:
-            rule = {
-                    direction: 'ingress'
-                    protocol:'tcp',
-                    port_range_min: 22,
-                    port_range_max: 22
-                    }
-        """
-
-        if sec_group_rules_client is None:
-            sec_group_rules_client = self.security_group_rules_client
-        if security_groups_client is None:
-            security_groups_client = self.security_groups_client
-        if not project_id:
-            project_id = security_groups_client.project_id
-        if secgroup is None:
-            # Get default secgroup for project_id
-            default_secgroups = security_groups_client.list_security_groups(
-                name='default', project_id=project_id)['security_groups']
-            msg = "No default security group for project %s." % (project_id)
-            self.assertNotEmpty(default_secgroups, msg)
-            secgroup = default_secgroups[0]
-
-        ruleset = dict(security_group_id=secgroup['id'],
-                       project_id=secgroup['project_id'])
-        ruleset.update(kwargs)
-
-        sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
-        sg_rule = sg_rule['security_group_rule']
-
-        self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
-        self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
-
-        return sg_rule
-
-    def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
-                                        secgroup=None,
-                                        security_groups_client=None):
-        """Create loginable security group rule
-
-        This function will create:
-        1. egress and ingress tcp port 22 allow rule in order to allow ssh
-        access for ipv4.
-        2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
-        3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
-        """
-
-        if security_group_rules_client is None:
-            security_group_rules_client = self.security_group_rules_client
-        if security_groups_client is None:
-            security_groups_client = self.security_groups_client
-        rules = []
-        rulesets = [
-            dict(
-                # ssh
-                protocol='tcp',
-                port_range_min=22,
-                port_range_max=22,
-            ),
-            dict(
-                # ping
-                protocol='icmp',
-            ),
-            dict(
-                # ipv6-icmp for ping6
-                protocol='icmp',
-                ethertype='IPv6',
-            )
-        ]
-        sec_group_rules_client = security_group_rules_client
-        for ruleset in rulesets:
-            for r_direction in ['ingress', 'egress']:
-                ruleset['direction'] = r_direction
-                try:
-                    sg_rule = self._create_security_group_rule(
-                        sec_group_rules_client=sec_group_rules_client,
-                        secgroup=secgroup,
-                        security_groups_client=security_groups_client,
-                        **ruleset)
-                except lib_exc.Conflict as ex:
-                    # if rule already exist - skip rule and continue
-                    msg = 'Security group rule already exists'
-                    if msg not in ex._error_string:
-                        raise ex
-                else:
-                    self.assertEqual(r_direction, sg_rule['direction'])
-                    rules.append(sg_rule)
-
-        return rules
-
-    def _get_router(self, client=None, project_id=None):
+    def get_router(self, client=None, project_id=None, **kwargs):
         """Retrieve a router for the given tenant id.
 
         If a public router has been configured, it will be returned.
@@ -1412,11 +1469,20 @@
             body = client.show_router(router_id)
             return body['router']
         elif network_id:
+            name = kwargs.pop('name', None)
+            if not name:
+                namestart = self.__class__.__name__ + '-router'
+                name = data_utils.rand_name(namestart)
+
+            ext_gw_info = kwargs.pop('external_gateway_info', None)
+            if not ext_gw_info:
+                ext_gw_info = dict(network_id=network_id)
             router = client.create_router(
-                name=data_utils.rand_name(self.__class__.__name__ + '-router'),
-                admin_state_up=True,
+                name=name,
+                admin_state_up=kwargs.get('admin_state_up', True),
                 project_id=project_id,
-                external_gateway_info=dict(network_id=network_id))['router']
+                external_gateway_info=ext_gw_info,
+                **kwargs)['router']
             self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                             client.delete_router, router['id'])
             return router
@@ -1424,10 +1490,11 @@
             raise Exception("Neither of 'public_router_id' or "
                             "'public_network_id' has been defined.")
 
-    def create_networks(self, networks_client=None,
-                        routers_client=None, subnets_client=None,
-                        project_id=None, dns_nameservers=None,
-                        port_security_enabled=True, **net_dict):
+    def setup_network_subnet_with_router(
+            self, networks_client=None,
+            routers_client=None, subnets_client=None,
+            project_id=None, dns_nameservers=None,
+            port_security_enabled=True, **net_dict):
         """Create a network with a subnet connected to a router.
 
         The baremetal driver is a special case since all nodes are
@@ -1452,18 +1519,18 @@
             if not CONF.compute.fixed_network_name:
                 m = 'fixed_network_name must be specified in config'
                 raise lib_exc.InvalidConfiguration(m)
-            network = self._get_network_by_name(
+            network = self.get_network_by_name(
                 CONF.compute.fixed_network_name)
             router = None
             subnet = None
         else:
-            network = self._create_network(
+            network = self.create_network(
                 networks_client=networks_client,
                 project_id=project_id,
                 port_security_enabled=port_security_enabled,
                 **net_dict)
-            router = self._get_router(client=routers_client,
-                                      project_id=project_id)
+            router = self.get_router(client=routers_client,
+                                     project_id=project_id)
             subnet_kwargs = dict(network=network,
                                  subnets_client=subnets_client)
             # use explicit check because empty list is a valid option
@@ -1487,8 +1554,6 @@
 class EncryptionScenarioTest(ScenarioTest):
     """Base class for encryption scenario tests"""
 
-    credentials = ['primary', 'admin']
-
     @classmethod
     def setup_clients(cls):
         super(EncryptionScenarioTest, cls).setup_clients()
@@ -1530,6 +1595,8 @@
     class.
     """
 
+    credentials = ['primary']
+
     @classmethod
     def skip_checks(cls):
         super(ObjectStorageScenarioTest, cls).skip_checks()
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index b515639..58e234f 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -51,10 +51,27 @@
         return aggregate
 
     def _get_host_name(self):
+        # Find a host that has not been added to other availability zone,
+        # for one host can't be added to different availability zones.
         svc_list = self.services_client.list_services(
             binary='nova-compute')['services']
         self.assertNotEmpty(svc_list)
-        return svc_list[0]['host']
+        hosts_available = []
+        for host in svc_list:
+            if (host['state'] == 'up' and host['status'] == 'enabled'):
+                hosts_available.append(host['host'])
+        aggregates = self.aggregates_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 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)
+        return hosts[0]
 
     def _add_host(self, aggregate_id, host):
         aggregate = (self.aggregates_client.add_host(aggregate_id, host=host)
diff --git a/tempest/scenario/test_dashboard_basic_ops.py b/tempest/scenario/test_dashboard_basic_ops.py
new file mode 100644
index 0000000..b1098fa
--- /dev/null
+++ b/tempest/scenario/test_dashboard_basic_ops.py
@@ -0,0 +1,141 @@
+#    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 html.parser
+import ssl
+from urllib import parse
+from urllib import request
+
+from tempest.common import utils
+from tempest import config
+from tempest.lib import decorators
+from tempest import test
+
+CONF = config.CONF
+
+
+class HorizonHTMLParser(html.parser.HTMLParser):
+    csrf_token = None
+    region = None
+    login = None
+
+    def _find_name(self, attrs, name):
+        for attrpair in attrs:
+            if attrpair[0] == 'name' and attrpair[1] == name:
+                return True
+        return False
+
+    def _find_value(self, attrs):
+        for attrpair in attrs:
+            if attrpair[0] == 'value':
+                return attrpair[1]
+        return None
+
+    def _find_attr_value(self, attrs, attr_name):
+        for attrpair in attrs:
+            if attrpair[0] == attr_name:
+                return attrpair[1]
+        return None
+
+    def handle_starttag(self, tag, attrs):
+        if tag == 'input':
+            if self._find_name(attrs, 'csrfmiddlewaretoken'):
+                self.csrf_token = self._find_value(attrs)
+            if self._find_name(attrs, 'region'):
+                self.region = self._find_value(attrs)
+        if tag == 'form':
+            self.login = self._find_attr_value(attrs, 'action')
+
+
+class TestDashboardBasicOps(test.BaseTestCase):
+
+    """The test suite for dashboard basic operations
+
+    This is a basic scenario test:
+    * checks that the login page is available
+    * logs in as a regular user
+    * checks that the user home page loads without error
+    """
+    opener = None
+
+    credentials = ['primary']
+
+    @classmethod
+    def skip_checks(cls):
+        super(TestDashboardBasicOps, cls).skip_checks()
+        if not CONF.service_available.horizon:
+            raise cls.skipException("Horizon support is required")
+
+    @classmethod
+    def setup_credentials(cls):
+        cls.set_network_resources()
+        super(TestDashboardBasicOps, cls).setup_credentials()
+
+    def check_login_page(self):
+        response = self._get_opener().open(CONF.dashboard.dashboard_url).read()
+        self.assertIn("id_username", response.decode("utf-8"))
+
+    def user_login(self, username, password):
+        response = self._get_opener().open(CONF.dashboard.dashboard_url).read()
+
+        # Grab the CSRF token and default region
+        parser = HorizonHTMLParser()
+        parser.feed(response.decode("utf-8"))
+
+        # construct login url for dashboard, discovery accommodates non-/ web
+        # root for dashboard
+        login_url = parse.urljoin(CONF.dashboard.dashboard_url, parser.login)
+
+        # Prepare login form request
+        req = request.Request(login_url)
+        req.add_header('Content-type', 'application/x-www-form-urlencoded')
+        req.add_header('Referer', CONF.dashboard.dashboard_url)
+
+        # Pass the default domain name regardless of the auth version in order
+        # to test the scenario of when horizon is running with keystone v3
+        params = {'username': username,
+                  'password': password,
+                  'region': parser.region,
+                  'domain': CONF.auth.default_credentials_domain_name,
+                  'csrfmiddlewaretoken': parser.csrf_token}
+        self._get_opener().open(req, parse.urlencode(params).encode())
+
+    def check_home_page(self):
+        response = self._get_opener().open(CONF.dashboard.dashboard_url).read()
+        self.assertIn('Overview', response.decode("utf-8"))
+
+    def _get_opener(self):
+        if not self.opener:
+            if (CONF.dashboard.disable_ssl_certificate_validation and
+                    self._ssl_default_context_supported()):
+                ctx = ssl.create_default_context()
+                ctx.check_hostname = False
+                ctx.verify_mode = ssl.CERT_NONE
+                self.opener = request.build_opener(
+                    request.HTTPSHandler(context=ctx),
+                    request.HTTPCookieProcessor())
+            else:
+                self.opener = request.build_opener(
+                    request.HTTPCookieProcessor())
+        return self.opener
+
+    def _ssl_default_context_supported(self):
+        return (hasattr(ssl, 'create_default_context'))
+
+    @decorators.attr(type='smoke')
+    @decorators.idempotent_id('4f8851b1-0e69-482b-b63b-84c6e76f6c80')
+    @utils.services('dashboard')
+    def test_basic_scenario(self):
+        creds = self.os_primary.credentials
+        self.check_login_page()
+        self.user_login(creds.username, creds.password)
+        self.check_home_page()
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index fc93a5e..6ee9f28 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -30,8 +30,7 @@
     For both LUKS and cryptsetup encryption types, this test performs
     the following:
 
-    * Creates an image in Glance
-    * Boots an instance from the image
+    * Boots an instance from an image (CONF.compute.image_ref)
     * 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
@@ -44,10 +43,9 @@
             raise cls.skipException('Encrypted volume attach is not supported')
 
     def launch_instance(self):
-        image = self.image_create()
         keypair = self.create_keypair()
 
-        return self.create_server(image_id=image, key_name=keypair['name'])
+        return self.create_server(key_name=keypair['name'])
 
     def attach_detach_volume(self, server, volume):
         attached_volume = self.nova_volume_attach(server, volume)
diff --git a/tempest/scenario/test_minbw_allocation_placement.py b/tempest/scenario/test_minbw_allocation_placement.py
deleted file mode 100644
index 74d4ed9..0000000
--- a/tempest/scenario/test_minbw_allocation_placement.py
+++ /dev/null
@@ -1,207 +0,0 @@
-# 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_log import log as logging
-
-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.common.utils import test_utils
-from tempest.lib import decorators
-from tempest.scenario import manager
-
-
-LOG = logging.getLogger(__name__)
-CONF = config.CONF
-
-
-class MinBwAllocationPlacementTest(manager.NetworkScenarioTest):
-    credentials = ['primary', 'admin']
-    required_extensions = ['port-resource-request',
-                           'qos',
-                           'qos-bw-minimum-ingress']
-    # The feature QoS minimum bandwidth allocation in Placement API depends on
-    # Granular resource requests to GET /allocation_candidates and Support
-    # allocation candidates with nested resource providers features in
-    # Placement (see: https://specs.openstack.org/openstack/nova-specs/specs/
-    # stein/approved/bandwidth-resource-provider.html#rest-api-impact) and this
-    # means that the minimum placement microversion is 1.29
-    placement_min_microversion = '1.29'
-    placement_max_microversion = 'latest'
-
-    # Nova rejects to boot VM with port which has resource_request field, below
-    # microversion 2.72
-    compute_min_microversion = '2.72'
-    compute_max_microversion = 'latest'
-
-    INGRESS_RESOURCE_CLASS = "NET_BW_IGR_KILOBIT_PER_SEC"
-    INGRESS_DIRECTION = 'ingress'
-
-    SMALLEST_POSSIBLE_BW = 1
-    # For any realistic inventory value (that is inventory != MAX_INT) an
-    # allocation candidate request of MAX_INT is expected to be rejected, see:
-    # https://github.com/openstack/placement/blob/master/placement/
-    # db/constants.py#L16
-    PLACEMENT_MAX_INT = 0x7FFFFFFF
-
-    @classmethod
-    def setup_clients(cls):
-        super(MinBwAllocationPlacementTest, cls).setup_clients()
-        cls.placement_client = cls.os_admin.placement_client
-        cls.networks_client = cls.os_admin.networks_client
-        cls.subnets_client = cls.os_admin.subnets_client
-        cls.routers_client = cls.os_adm.routers_client
-        cls.qos_client = cls.os_admin.qos_client
-        cls.qos_min_bw_client = cls.os_admin.qos_min_bw_client
-
-    @classmethod
-    def skip_checks(cls):
-        super(MinBwAllocationPlacementTest, cls).skip_checks()
-        if not CONF.network_feature_enabled.qos_placement_physnet:
-            msg = "Skipped as no physnet is available in config for " \
-                  "placement based QoS allocation."
-            raise cls.skipException(msg)
-
-    def _create_policy_and_min_bw_rule(self, name_prefix, min_kbps):
-        policy = self.qos_client.create_qos_policy(
-            name=data_utils.rand_name(name_prefix),
-            shared=True)['policy']
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self.qos_client.delete_qos_policy, policy['id'])
-        rule = self.qos_min_bw_client.create_minimum_bandwidth_rule(
-            policy['id'],
-            **{
-                'min_kbps': min_kbps,
-                'direction': self.INGRESS_DIRECTION
-            })['minimum_bandwidth_rule']
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.qos_min_bw_client.delete_minimum_bandwidth_rule, policy['id'],
-            rule['id'])
-
-        return policy
-
-    def _create_qos_policies(self):
-        self.qos_policy_valid = self._create_policy_and_min_bw_rule(
-            name_prefix='test_policy_valid',
-            min_kbps=self.SMALLEST_POSSIBLE_BW)
-        self.qos_policy_not_valid = self._create_policy_and_min_bw_rule(
-            name_prefix='test_policy_not_valid',
-            min_kbps=self.PLACEMENT_MAX_INT)
-
-    def _create_network_and_qos_policies(self):
-        physnet_name = CONF.network_feature_enabled.qos_placement_physnet
-        base_segm = \
-            CONF.network_feature_enabled.provider_net_base_segmentation_id
-
-        self.prov_network, _, _ = self.create_networks(
-            networks_client=self.networks_client,
-            routers_client=self.routers_client,
-            subnets_client=self.subnets_client,
-            **{
-                'shared': True,
-                'provider:network_type': 'vlan',
-                'provider:physical_network': physnet_name,
-                'provider:segmentation_id': base_segm
-            })
-
-        self._create_qos_policies()
-
-    def _check_if_allocation_is_possible(self):
-        alloc_candidates = self.placement_client.list_allocation_candidates(
-            resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
-                                  self.SMALLEST_POSSIBLE_BW))
-        if len(alloc_candidates['provider_summaries']) == 0:
-            # Skip if the backend does not support QoS minimum bandwidth
-            # allocation in Placement API
-            raise self.skipException(
-                'No allocation candidates are available for %s:%s' %
-                (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
-
-        # Just to be sure check with impossible high (placement max_int),
-        # allocation
-        alloc_candidates = self.placement_client.list_allocation_candidates(
-            resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
-                                  self.PLACEMENT_MAX_INT))
-        if len(alloc_candidates['provider_summaries']) != 0:
-            self.fail('For %s:%s there should be no available candidate!' %
-                      (self.INGRESS_RESOURCE_CLASS, self.PLACEMENT_MAX_INT))
-
-    @decorators.idempotent_id('78625d92-212c-400e-8695-dd51706858b8')
-    @decorators.attr(type='slow')
-    @utils.services('compute', 'network')
-    def test_qos_min_bw_allocation_basic(self):
-        """"Basic scenario with QoS min bw allocation in placement.
-
-        Steps:
-        * Create prerequisites:
-        ** VLAN type provider network with subnet.
-        ** valid QoS policy with minimum bandwidth rule with min_kbps=1
-        (This is a simplification to skip the checks in placement for
-        detecting the resource provider tree and inventories, as if
-        bandwidth resource is available 1 kbs will be available).
-        ** invalid QoS policy with minimum bandwidth rule with
-        min_kbs=max integer from placement (this is a simplification again
-        to avoid detection of RP tress and inventories, as placement will
-        reject such big allocation).
-        * Create port with valid QoS policy, and boot VM with that, it should
-        pass.
-        * Create port with invalid QoS policy, and try to boot VM with that,
-        it should fail.
-        """
-
-        self._check_if_allocation_is_possible()
-
-        self._create_network_and_qos_policies()
-
-        valid_port = self.create_port(
-            self.prov_network['id'], qos_policy_id=self.qos_policy_valid['id'])
-
-        server1 = self.create_server(
-            networks=[{'port': valid_port['id']}])
-        allocations = self.placement_client.list_allocations(server1['id'])
-
-        self.assertGreater(len(allocations['allocations']), 0)
-        bw_resource_in_alloc = False
-        for rp, resources in allocations['allocations'].items():
-            if self.INGRESS_RESOURCE_CLASS in resources['resources']:
-                bw_resource_in_alloc = True
-                allocation_rp = rp
-        self.assertTrue(bw_resource_in_alloc)
-        # Check that binding_profile of the port is not empty and equals with
-        # the rp uuid
-        port = self.os_admin.ports_client.show_port(valid_port['id'])
-        self.assertEqual(allocation_rp,
-                         port['port']['binding:profile']['allocation'])
-
-        # boot another vm with max int bandwidth
-        not_valid_port = self.create_port(
-            self.prov_network['id'],
-            qos_policy_id=self.qos_policy_not_valid['id'])
-        server2 = self.create_server(
-            wait_until=None,
-            networks=[{'port': not_valid_port['id']}])
-        waiters.wait_for_server_status(
-            client=self.os_primary.servers_client, server_id=server2['id'],
-            status='ERROR', ready_wait=False, raise_on_error=False)
-        allocations = self.placement_client.list_allocations(server2['id'])
-
-        self.assertEqual(0, len(allocations['allocations']))
-        server2 = self.servers_client.show_server(server2['id'])
-        self.assertIn('fault', server2['server'])
-        self.assertIn('No valid host', server2['server']['fault']['message'])
-        # Check that binding_profile of the port is empty
-        port = self.os_admin.ports_client.show_port(not_valid_port['id'])
-        self.assertEqual(0, len(port['port']['binding:profile']))
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index fe42583..5aac19c 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -78,7 +78,7 @@
         self.assertEqual(1, disks.count(CONF.compute.volume_device_name))
 
     def create_and_add_security_group_to_server(self, server):
-        secgroup = self._create_security_group()
+        secgroup = self.create_security_group()
         self.servers_client.add_security_group(server['id'],
                                                name=secgroup['name'])
         self.addCleanup(self.servers_client.remove_security_group,
@@ -96,13 +96,6 @@
                    '%s' % (secgroup['id'], server['id']))
             raise exceptions.TimeoutException(msg)
 
-    def _get_floating_ip_in_server_addresses(self, floating_ip, server):
-        for addresses in server['addresses'].values():
-            for address in addresses:
-                if (address['OS-EXT-IPS:type'] == 'floating' and
-                        address['addr'] == floating_ip['ip']):
-                    return address
-
     @decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8')
     @utils.services('compute', 'volume', 'image', 'network')
     def test_minimum_basic_scenario(self):
@@ -129,17 +122,12 @@
         server = self.servers_client.show_server(server['id'])['server']
         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']
+            fip = self.create_floating_ip(server)
+            floating_ip = self.associate_floating_ip(
+                fip, server)
+            waiters.wait_for_server_floating_ip(self.servers_client,
+                                                server, floating_ip)
+            ssh_ip = floating_ip['floating_ip_address']
         else:
             ssh_ip = self.get_server_ip(server)
 
@@ -162,20 +150,7 @@
 
         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)
-
-            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)
+            self.disassociate_floating_ip(floating_ip)
+            waiters.wait_for_server_floating_ip(
+                self.servers_client, server, floating_ip,
+                wait_for_disassociate=True)
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index e26dc9d..b48ac3c 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -60,9 +60,9 @@
     def _setup_server(self, keypair):
         security_groups = []
         if utils.is_extension_enabled('security-group', 'network'):
-            security_group = self._create_security_group()
+            security_group = self.create_security_group()
             security_groups = [{'name': security_group['name']}]
-        network, _, _ = self.create_networks()
+        network, _, _ = self.setup_network_subnet_with_router()
         server = self.create_server(
             networks=[{'uuid': network['id']}],
             key_name=keypair['name'],
@@ -80,8 +80,8 @@
         return floating_ip
 
     def _check_network_connectivity(self, server, keypair, floating_ip,
-                                    should_connect=True):
-        username = CONF.validation.image_ssh_user
+                                    should_connect=True,
+                                    username=CONF.validation.image_ssh_user):
         private_key = keypair['private_key']
         self.check_tenant_network_connectivity(
             server, username, private_key,
@@ -95,12 +95,13 @@
                                    'Public network connectivity check failed',
                                    server)
 
-    def _wait_server_status_and_check_network_connectivity(self, server,
-                                                           keypair,
-                                                           floating_ip):
+    def _wait_server_status_and_check_network_connectivity(
+        self, server, keypair, floating_ip,
+        username=CONF.validation.image_ssh_user):
         waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'ACTIVE')
-        self._check_network_connectivity(server, keypair, floating_ip)
+        self._check_network_connectivity(server, keypair, floating_ip,
+                                         username=username)
 
     @decorators.idempotent_id('61f1aa9a-1573-410e-9054-afa557cab021')
     @decorators.attr(type='slow')
@@ -137,10 +138,11 @@
         server = self._setup_server(keypair)
         floating_ip = self._setup_network(server, keypair)
         image_ref_alt = CONF.compute.image_ref_alt
+        username_alt = CONF.validation.image_alt_ssh_user
         self.servers_client.rebuild_server(server['id'],
                                            image_ref=image_ref_alt)
         self._wait_server_status_and_check_network_connectivity(
-            server, keypair, floating_ip)
+            server, keypair, floating_ip, username_alt)
 
     @decorators.idempotent_id('2b2642db-6568-4b35-b812-eceed3fa20ce')
     @testtools.skipUnless(CONF.compute_feature_enabled.pause,
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 6c1b3fa..cbe8c20 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -106,7 +106,8 @@
 
     def _setup_network_and_servers(self, **kwargs):
         boot_with_port = kwargs.pop('boot_with_port', False)
-        self.network, self.subnet, self.router = self.create_networks(**kwargs)
+        self.network, self.subnet, self.router = (
+            self.setup_network_subnet_with_router(**kwargs))
         self.check_networks()
 
         self.ports = []
@@ -159,7 +160,7 @@
         keypair = self.create_keypair()
         self.keypairs[keypair['name']] = keypair
         security_groups = [
-            {'name': self._create_security_group()['name']}
+            {'name': self.create_security_group()['name']}
         ]
         network = {'uuid': network['id']}
         if port_id is not None:
@@ -223,14 +224,14 @@
         floating_ip, server = self.floating_ip_tuple
         # create a new server for the floating ip
         server = self._create_server(self.network)
-        port_id, _ = self._get_server_port_id_and_ip4(server)
+        port_id, _ = self.get_server_port_id_and_ip4(server)
         floating_ip = self.floating_ips_client.update_floatingip(
             floating_ip['id'], port_id=port_id)['floatingip']
         self.assertEqual(port_id, floating_ip['port_id'])
         self.floating_ip_tuple = Floating_IP_tuple(floating_ip, server)
 
     def _create_new_network(self, create_gateway=False):
-        self.new_net = self._create_network()
+        self.new_net = self.create_network()
         if create_gateway:
             self.new_subnet = self.create_subnet(
                 network=self.new_net)
@@ -328,13 +329,16 @@
         floating_ip, server = self.floating_ip_tuple
         # get internal ports' ips:
         # get all network and compute ports in the new network
+        # NOTE(ralonsoh): device_owner="network:distributed" ports are OVN
+        # metadata ports and should be filtered out.
         internal_ips = (
             p['fixed_ips'][0]['ip_address'] for p in
             self.os_admin.ports_client.list_ports(
                 project_id=server['tenant_id'],
                 network_id=network['id'])['ports']
-            if p['device_owner'].startswith('network') or
-            p['device_owner'].startswith('compute')
+            if ((p['device_owner'].startswith('network') and
+                 not p['device_owner'] == 'network:distributed') or
+                p['device_owner'].startswith('compute'))
         )
 
         self._check_server_connectivity(floating_ip,
@@ -623,6 +627,13 @@
         ssh_client = self.get_remote_client(
             ip_address, private_key=private_key, server=server)
 
+        # NOTE: Server needs to renew its dhcp lease in order to get new
+        # definitions from subnet
+        # NOTE(amuller): we are renewing the lease as part of the retry
+        # because Neutron updates dnsmasq asynchronously after the
+        # subnet-update API call returns.
+        ssh_client.renew_lease(fixed_ip=floating_ip['fixed_ip_address'],
+                               dhcp_client=CONF.scenario.dhcp_client)
         dns_servers = [initial_dns_server]
         servers = ssh_client.get_dns_servers()
         self.assertEqual(set(dns_servers), set(servers),
diff --git a/tempest/scenario/test_network_qos_placement.py b/tempest/scenario/test_network_qos_placement.py
new file mode 100644
index 0000000..adb0ee3
--- /dev/null
+++ b/tempest/scenario/test_network_qos_placement.py
@@ -0,0 +1,1072 @@
+# 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.
+
+import testtools
+
+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.common.utils import test_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest.scenario import manager
+
+
+CONF = config.CONF
+
+
+class NetworkQoSPlacementTestBase(manager.NetworkScenarioTest):
+    """Base class for Network QoS testing
+
+    Base class for testing Network QoS scenarios involving placement
+    resource allocations.
+    """
+    credentials = ['primary', 'admin']
+    # The feature QoS minimum bandwidth allocation in Placement API depends on
+    # Granular resource requests to GET /allocation_candidates and Support
+    # allocation candidates with nested resource providers features in
+    # Placement (see: https://specs.openstack.org/openstack/nova-specs/specs/
+    # stein/approved/bandwidth-resource-provider.html#rest-api-impact) and this
+    # means that the minimum placement microversion is 1.29
+    placement_min_microversion = '1.29'
+    placement_max_microversion = 'latest'
+
+    # Nova rejects to boot VM with port which has resource_request field, below
+    # microversion 2.72
+    compute_min_microversion = '2.72'
+    compute_max_microversion = 'latest'
+
+    INGRESS_DIRECTION = 'ingress'
+    EGRESS_DIRECTION = 'egress'
+    ANY_DIRECTION = 'any'
+    INGRESS_RESOURCE_CLASS = "NET_BW_IGR_KILOBIT_PER_SEC"
+    EGRESS_RESOURCE_CLASS = "NET_BW_EGR_KILOBIT_PER_SEC"
+
+    # For any realistic inventory value (that is inventory != MAX_INT) an
+    # allocation candidate request of MAX_INT is expected to be rejected, see:
+    # https://github.com/openstack/placement/blob/master/placement/
+    # db/constants.py#L16
+    PLACEMENT_MAX_INT = 0x7FFFFFFF
+
+    @classmethod
+    def setup_clients(cls):
+        super().setup_clients()
+        cls.placement_client = cls.os_admin.placement_client
+        cls.networks_client = cls.os_admin.networks_client
+        cls.subnets_client = cls.os_admin.subnets_client
+        cls.ports_client = cls.os_primary.ports_client
+        cls.routers_client = cls.os_adm.routers_client
+        cls.qos_client = cls.os_admin.qos_client
+        cls.qos_min_bw_client = cls.os_admin.qos_min_bw_client
+        cls.flavors_client = cls.os_adm.flavors_client
+        cls.servers_client = cls.os_primary.servers_client
+
+    def _create_flavor_to_resize_to(self):
+        old_flavor = self.flavors_client.show_flavor(
+            CONF.compute.flavor_ref)['flavor']
+        new_flavor = self.flavors_client.create_flavor(**{
+            'ram': old_flavor['ram'],
+            'vcpus': old_flavor['vcpus'],
+            'name': old_flavor['name'] + 'extra',
+            'disk': old_flavor['disk'] + 1
+        })['flavor']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.flavors_client.delete_flavor, new_flavor['id'])
+        return new_flavor
+
+
+class MinBwAllocationPlacementTest(NetworkQoSPlacementTestBase):
+
+    required_extensions = ['port-resource-request',
+                           'qos',
+                           'qos-bw-minimum-ingress']
+
+    SMALLEST_POSSIBLE_BW = 1
+    BANDWIDTH_1 = 1000
+    BANDWIDTH_2 = 2000
+
+    @classmethod
+    def skip_checks(cls):
+        super(MinBwAllocationPlacementTest, cls).skip_checks()
+        if not CONF.network_feature_enabled.qos_placement_physnet:
+            msg = "Skipped as no physnet is available in config for " \
+                  "placement based QoS allocation."
+            raise cls.skipException(msg)
+
+    def setUp(self):
+        super(MinBwAllocationPlacementTest, self).setUp()
+        self._check_if_allocation_is_possible()
+
+    def _create_policy_and_min_bw_rule(
+        self, name_prefix, min_kbps, direction="ingress"
+    ):
+        policy = self.qos_client.create_qos_policy(
+            name=data_utils.rand_name(name_prefix),
+            shared=True)['policy']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.qos_client.delete_qos_policy, policy['id'])
+        rule = self.qos_min_bw_client.create_minimum_bandwidth_rule(
+            policy['id'],
+            **{
+                'min_kbps': min_kbps,
+                'direction': direction,
+            })['minimum_bandwidth_rule']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.qos_min_bw_client.delete_minimum_bandwidth_rule, policy['id'],
+            rule['id'])
+
+        return policy
+
+    def _create_qos_basic_policies(self):
+        self.qos_policy_valid = self._create_policy_and_min_bw_rule(
+            name_prefix='test_policy_valid',
+            min_kbps=self.SMALLEST_POSSIBLE_BW)
+        self.qos_policy_not_valid = self._create_policy_and_min_bw_rule(
+            name_prefix='test_policy_not_valid',
+            min_kbps=self.PLACEMENT_MAX_INT)
+
+    def _create_qos_policies_from_life(self):
+        # For tempest-slow the max bandwidth configured is 1000000,
+        # https://opendev.org/openstack/tempest/src/branch/master/
+        # .zuul.yaml#L416-L420
+        self.qos_policy_1 = self._create_policy_and_min_bw_rule(
+            name_prefix='test_policy_1',
+            min_kbps=self.BANDWIDTH_1
+        )
+        self.qos_policy_2 = self._create_policy_and_min_bw_rule(
+            name_prefix='test_policy_2',
+            min_kbps=self.BANDWIDTH_2
+        )
+
+    def _create_network_and_qos_policies(self, policy_method):
+        physnet_name = CONF.network_feature_enabled.qos_placement_physnet
+        base_segm = \
+            CONF.network_feature_enabled.provider_net_base_segmentation_id
+
+        self.prov_network, _, _ = self.setup_network_subnet_with_router(
+            networks_client=self.networks_client,
+            routers_client=self.routers_client,
+            subnets_client=self.subnets_client,
+            **{
+                'shared': True,
+                'provider:network_type': 'vlan',
+                'provider:physical_network': physnet_name,
+                'provider:segmentation_id': base_segm
+            })
+
+        policy_method()
+
+    def _check_if_allocation_is_possible(self):
+        alloc_candidates = self.placement_client.list_allocation_candidates(
+            resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
+                                  self.SMALLEST_POSSIBLE_BW))
+        if len(alloc_candidates['provider_summaries']) == 0:
+            self.fail('No allocation candidates are available for %s:%s' %
+                      (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
+
+        # Just to be sure check with impossible high (placement max_int),
+        # allocation
+        alloc_candidates = self.placement_client.list_allocation_candidates(
+            resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
+                                  self.PLACEMENT_MAX_INT))
+        if len(alloc_candidates['provider_summaries']) != 0:
+            self.fail('For %s:%s there should be no available candidate!' %
+                      (self.INGRESS_RESOURCE_CLASS, self.PLACEMENT_MAX_INT))
+
+    def _boot_vm_with_min_bw(self, qos_policy_id, status='ACTIVE'):
+        wait_until = (None if status == 'ERROR' else status)
+        port = self.create_port(
+            self.prov_network['id'], qos_policy_id=qos_policy_id)
+
+        server = self.create_server(networks=[{'port': port['id']}],
+                                    wait_until=wait_until)
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status=status, ready_wait=False, raise_on_error=False)
+        return server, port
+
+    def _assert_allocation_is_as_expected(
+        self, consumer, port_ids, min_kbps=SMALLEST_POSSIBLE_BW,
+        expected_rc=NetworkQoSPlacementTestBase.INGRESS_RESOURCE_CLASS,
+    ):
+        allocations = self.placement_client.list_allocations(
+            consumer)['allocations']
+        self.assertGreater(len(allocations), 0)
+        bw_resource_in_alloc = False
+        allocation_rp = None
+        for rp, resources in allocations.items():
+            if expected_rc in resources['resources']:
+                self.assertEqual(
+                    min_kbps,
+                    resources['resources'][expected_rc])
+                bw_resource_in_alloc = True
+                allocation_rp = rp
+        if min_kbps:
+            self.assertTrue(
+                bw_resource_in_alloc,
+                f"expected {min_kbps} bandwidth allocation from {expected_rc} "
+                f"but instance has allocation {allocations} instead."
+            )
+
+            # Check binding_profile of the port is not empty and equals with
+            # the rp uuid
+            for port_id in port_ids:
+                port = self.os_admin.ports_client.show_port(port_id)
+                port_binding_alloc = port['port']['binding:profile'][
+                    'allocation']
+                # NOTE(gibi): the format of the allocation key depends on the
+                # existence of port-resource-request-groups API extension.
+                # TODO(gibi): drop the else branch once tempest does not need
+                # to support Xena release any more.
+                if utils.is_extension_enabled(
+                        'port-resource-request-groups', 'network'):
+                    self.assertEqual(
+                        {allocation_rp},
+                        set(port_binding_alloc.values()))
+                else:
+                    self.assertEqual(allocation_rp, port_binding_alloc)
+
+    @decorators.idempotent_id('78625d92-212c-400e-8695-dd51706858b8')
+    @utils.services('compute', 'network')
+    def test_qos_min_bw_allocation_basic(self):
+        """"Basic scenario with QoS min bw allocation in placement.
+
+        Steps:
+        * Create prerequisites:
+        ** VLAN type provider network with subnet.
+        ** valid QoS policy with minimum bandwidth rule with min_kbps=1
+        (This is a simplification to skip the checks in placement for
+        detecting the resource provider tree and inventories, as if
+        bandwidth resource is available 1 kbs will be available).
+        ** invalid QoS policy with minimum bandwidth rule with
+        min_kbs=max integer from placement (this is a simplification again
+        to avoid detection of RP tress and inventories, as placement will
+        reject such big allocation).
+        * Create port with valid QoS policy, and boot VM with that, it should
+        pass.
+        * Create port with invalid QoS policy, and try to boot VM with that,
+        it should fail.
+        """
+        self._create_network_and_qos_policies(self._create_qos_basic_policies)
+        server1, valid_port = self._boot_vm_with_min_bw(
+            qos_policy_id=self.qos_policy_valid['id'])
+        self._assert_allocation_is_as_expected(server1['id'],
+                                               [valid_port['id']])
+
+        server2, not_valid_port = self._boot_vm_with_min_bw(
+            self.qos_policy_not_valid['id'], status='ERROR')
+        allocations = self.placement_client.list_allocations(server2['id'])
+
+        self.assertEqual(0, len(allocations['allocations']))
+        server2 = self.servers_client.show_server(server2['id'])
+        self.assertIn('fault', server2['server'])
+        self.assertIn('No valid host', server2['server']['fault']['message'])
+        # Check that binding_profile of the port is empty
+        port = self.os_admin.ports_client.show_port(not_valid_port['id'])
+        self.assertEqual(0, len(port['port']['binding:profile']))
+
+    @decorators.idempotent_id('8a98150c-a506-49a5-96c6-73a5e7b04ada')
+    @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
+                          'Cold migration is not available.')
+    @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
+                          'Less than 2 compute nodes, skipping multinode '
+                          'tests.')
+    @utils.services('compute', 'network')
+    def test_migrate_with_qos_min_bw_allocation(self):
+        """Scenario to migrate VM with QoS min bw allocation in placement
+
+        Boot a VM like in test_qos_min_bw_allocation_basic, do the same
+        checks, and
+        * migrate the server
+        * confirm the resize, if the VM state is VERIFY_RESIZE
+        * If the VM goes to ACTIVE state check that allocations are as
+        expected.
+        """
+        self._create_network_and_qos_policies(self._create_qos_basic_policies)
+        server, valid_port = self._boot_vm_with_min_bw(
+            qos_policy_id=self.qos_policy_valid['id'])
+        self._assert_allocation_is_as_expected(server['id'],
+                                               [valid_port['id']])
+
+        self.os_adm.servers_client.migrate_server(server_id=server['id'])
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
+
+        # TODO(lajoskatona): Check that the allocations are ok for the
+        #  migration?
+        self._assert_allocation_is_as_expected(server['id'],
+                                               [valid_port['id']])
+
+        self.os_adm.servers_client.confirm_resize_server(
+            server_id=server['id'])
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='ACTIVE', ready_wait=False, raise_on_error=True)
+        self._assert_allocation_is_as_expected(server['id'],
+                                               [valid_port['id']])
+
+    @decorators.idempotent_id('c29e7fd3-035d-4993-880f-70819847683f')
+    @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+                          'Resize not available.')
+    @utils.services('compute', 'network')
+    def test_resize_with_qos_min_bw_allocation(self):
+        """Scenario to resize VM with QoS min bw allocation in placement.
+
+        Boot a VM like in test_qos_min_bw_allocation_basic, do the same
+        checks, and
+        * resize the server with new flavor
+        * confirm the resize, if the VM state is VERIFY_RESIZE
+        * If the VM goes to ACTIVE state check that allocations are as
+        expected.
+        """
+        self._create_network_and_qos_policies(self._create_qos_basic_policies)
+        server, valid_port = self._boot_vm_with_min_bw(
+            qos_policy_id=self.qos_policy_valid['id'])
+        self._assert_allocation_is_as_expected(server['id'],
+                                               [valid_port['id']])
+
+        new_flavor = self._create_flavor_to_resize_to()
+
+        self.servers_client.resize_server(
+            server_id=server['id'], flavor_ref=new_flavor['id'])
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
+
+        # TODO(lajoskatona): Check that the allocations are ok for the
+        #  migration?
+        self._assert_allocation_is_as_expected(server['id'],
+                                               [valid_port['id']])
+
+        self.servers_client.confirm_resize_server(server_id=server['id'])
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='ACTIVE', ready_wait=False, raise_on_error=True)
+        self._assert_allocation_is_as_expected(server['id'],
+                                               [valid_port['id']])
+
+    @decorators.idempotent_id('79fdaa1c-df62-4738-a0f0-1cff9dc415f6')
+    @utils.services('compute', 'network')
+    def test_qos_min_bw_allocation_update_policy(self):
+        """Test the update of QoS policy on bound port
+
+        Related RFE in neutron: #1882804
+        The scenario is the following:
+        * Have a port with QoS policy and minimum bandwidth rule.
+        * Boot a VM with the port.
+        * Update the port with a new policy with different minimum bandwidth
+        values.
+        * The allocation on placement side should be according to the new
+        rules.
+        """
+        if not utils.is_network_feature_enabled('update_port_qos'):
+            raise self.skipException("update_port_qos feature is not enabled")
+
+        self._create_network_and_qos_policies(
+            self._create_qos_policies_from_life)
+
+        port = self.create_port(
+            self.prov_network['id'], qos_policy_id=self.qos_policy_1['id'])
+
+        server1 = self.create_server(
+            networks=[{'port': port['id']}])
+
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+                                               self.BANDWIDTH_1)
+
+        self.ports_client.update_port(
+            port['id'],
+            **{'qos_policy_id': self.qos_policy_2['id']})
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+                                               self.BANDWIDTH_2)
+
+        # I changed my mind
+        self.ports_client.update_port(
+            port['id'],
+            **{'qos_policy_id': self.qos_policy_1['id']})
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+                                               self.BANDWIDTH_1)
+
+        # bad request....
+        self.qos_policy_not_valid = self._create_policy_and_min_bw_rule(
+            name_prefix='test_policy_not_valid',
+            min_kbps=self.PLACEMENT_MAX_INT)
+        port_orig = self.ports_client.show_port(port['id'])['port']
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.ports_client.update_port,
+            port['id'], **{'qos_policy_id': self.qos_policy_not_valid['id']})
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+                                               self.BANDWIDTH_1)
+
+        port_upd = self.ports_client.show_port(port['id'])['port']
+        self.assertEqual(port_orig['qos_policy_id'],
+                         port_upd['qos_policy_id'])
+        self.assertEqual(self.qos_policy_1['id'], port_upd['qos_policy_id'])
+
+    @decorators.idempotent_id('9cfc3bb8-f433-4c91-87b6-747cadc8958a')
+    @utils.services('compute', 'network')
+    def test_qos_min_bw_allocation_update_policy_from_zero(self):
+        """Test port without QoS policy to have QoS policy
+
+        This scenario checks if updating a port without QoS policy to
+        have QoS policy with minimum_bandwidth rule succeeds only on
+        controlplane, but placement allocation remains 0.
+        """
+        if not utils.is_network_feature_enabled('update_port_qos'):
+            raise self.skipException("update_port_qos feature is not enabled")
+
+        self._create_network_and_qos_policies(
+            self._create_qos_policies_from_life)
+
+        port = self.create_port(self.prov_network['id'])
+
+        server1 = self.create_server(
+            networks=[{'port': port['id']}])
+
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']], 0)
+
+        self.ports_client.update_port(
+            port['id'], **{'qos_policy_id': self.qos_policy_2['id']})
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']], 0)
+
+    @decorators.idempotent_id('a9725a70-1d28-4e3b-ae0e-450abc235962')
+    @utils.services('compute', 'network')
+    def test_qos_min_bw_allocation_update_policy_to_zero(self):
+        """Test port with QoS policy to remove QoS policy
+
+        In this scenario port with QoS minimum_bandwidth rule update to
+        remove QoS policy results in 0 placement allocation.
+        """
+        if not utils.is_network_feature_enabled('update_port_qos'):
+            raise self.skipException("update_port_qos feature is not enabled")
+
+        self._create_network_and_qos_policies(
+            self._create_qos_policies_from_life)
+
+        port = self.create_port(
+            self.prov_network['id'], qos_policy_id=self.qos_policy_1['id'])
+
+        server1 = self.create_server(
+            networks=[{'port': port['id']}])
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+                                               self.BANDWIDTH_1)
+
+        self.ports_client.update_port(
+            port['id'],
+            **{'qos_policy_id': None})
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']], 0)
+
+    @decorators.idempotent_id('756ced7f-6f1a-43e7-a851-2fcfc16f3dd7')
+    @utils.services('compute', 'network')
+    def test_qos_min_bw_allocation_update_with_multiple_ports(self):
+        if not utils.is_network_feature_enabled('update_port_qos'):
+            raise self.skipException("update_port_qos feature is not enabled")
+
+        self._create_network_and_qos_policies(
+            self._create_qos_policies_from_life)
+
+        port1 = self.create_port(
+            self.prov_network['id'], qos_policy_id=self.qos_policy_1['id'])
+        port2 = self.create_port(
+            self.prov_network['id'], qos_policy_id=self.qos_policy_2['id'])
+
+        server1 = self.create_server(
+            networks=[{'port': port1['id']}, {'port': port2['id']}])
+        self._assert_allocation_is_as_expected(
+            server1['id'], [port1['id'], port2['id']],
+            self.BANDWIDTH_1 + self.BANDWIDTH_2)
+
+        self.ports_client.update_port(
+            port1['id'],
+            **{'qos_policy_id': self.qos_policy_2['id']})
+        self._assert_allocation_is_as_expected(
+            server1['id'], [port1['id'], port2['id']],
+            2 * self.BANDWIDTH_2)
+
+    @decorators.idempotent_id('0805779e-e03c-44fb-900f-ce97a790653b')
+    @utils.services('compute', 'network')
+    def test_empty_update(self):
+        if not utils.is_network_feature_enabled('update_port_qos'):
+            raise self.skipException("update_port_qos feature is not enabled")
+
+        self._create_network_and_qos_policies(
+            self._create_qos_policies_from_life)
+
+        port = self.create_port(
+            self.prov_network['id'], qos_policy_id=self.qos_policy_1['id'])
+
+        server1 = self.create_server(
+            networks=[{'port': port['id']}])
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+                                               self.BANDWIDTH_1)
+        self.ports_client.update_port(
+            port['id'],
+            **{'description': 'foo'})
+        self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+                                               self.BANDWIDTH_1)
+
+    @decorators.idempotent_id('372b2728-cfed-469a-b5f6-b75779e1ccbe')
+    @utils.services('compute', 'network')
+    def test_qos_min_bw_allocation_update_policy_direction_change(self):
+        """Test QoS min bw direction change on a bound port
+
+        Related RFE in neutron: #1882804
+        The scenario is the following:
+        * Have a port with QoS policy and minimum bandwidth rule with ingress
+        direction
+        * Boot a VM with the port.
+        * Update the port with a new policy to egress direction in
+        minimum bandwidth rule.
+        * The allocation on placement side should be according to the new
+        rules.
+        """
+        if not utils.is_network_feature_enabled('update_port_qos'):
+            raise self.skipException("update_port_qos feature is not enabled")
+
+        def create_policies():
+            self.qos_policy_ingress = self._create_policy_and_min_bw_rule(
+                name_prefix='test_policy_ingress',
+                min_kbps=self.BANDWIDTH_1,
+                direction=self.INGRESS_DIRECTION,
+            )
+            self.qos_policy_egress = self._create_policy_and_min_bw_rule(
+                name_prefix='test_policy_egress',
+                min_kbps=self.BANDWIDTH_1,
+                direction=self.EGRESS_DIRECTION,
+            )
+
+        self._create_network_and_qos_policies(create_policies)
+
+        port = self.create_port(
+            self.prov_network['id'],
+            qos_policy_id=self.qos_policy_ingress['id'])
+
+        server1 = self.create_server(
+            networks=[{'port': port['id']}])
+
+        self._assert_allocation_is_as_expected(
+            server1['id'], [port['id']], self.BANDWIDTH_1,
+            expected_rc=self.INGRESS_RESOURCE_CLASS)
+
+        self.ports_client.update_port(
+            port['id'],
+            qos_policy_id=self.qos_policy_egress['id'])
+
+        self._assert_allocation_is_as_expected(
+            server1['id'], [port['id']], self.BANDWIDTH_1,
+            expected_rc=self.EGRESS_RESOURCE_CLASS)
+        self._assert_allocation_is_as_expected(
+            server1['id'], [port['id']], 0,
+            expected_rc=self.INGRESS_RESOURCE_CLASS)
+
+
+class QoSBandwidthAndPacketRateTests(NetworkQoSPlacementTestBase):
+
+    PPS_RESOURCE_CLASS = "NET_PACKET_RATE_KILOPACKET_PER_SEC"
+
+    @classmethod
+    def skip_checks(cls):
+        super().skip_checks()
+        if not CONF.network_feature_enabled.qos_min_bw_and_pps:
+            msg = (
+                "Skipped as no resource inventories are configured for QoS "
+                "minimum bandwidth and packet rate testing.")
+            raise cls.skipException(msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super().setup_clients()
+        cls.qos_min_pps_client = cls.os_admin.qos_min_pps_client
+
+    def setUp(self):
+        super().setUp()
+        self.network = self._create_network()
+
+    def _create_qos_policy_with_bw_and_pps_rules(self, min_kbps, min_kpps):
+        policy = self.qos_client.create_qos_policy(
+            name=data_utils.rand_name(),
+            shared=True
+        )['policy']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.qos_client.delete_qos_policy,
+            policy['id']
+        )
+
+        if min_kbps > 0:
+            bw_rule = self.qos_min_bw_client.create_minimum_bandwidth_rule(
+                policy['id'],
+                min_kbps=min_kbps,
+                direction=self.INGRESS_DIRECTION
+            )['minimum_bandwidth_rule']
+            self.addCleanup(
+                test_utils.call_and_ignore_notfound_exc,
+                self.qos_min_bw_client.delete_minimum_bandwidth_rule,
+                policy['id'],
+                bw_rule['id']
+            )
+
+        if min_kpps > 0:
+            pps_rule = self.qos_min_pps_client.create_minimum_packet_rate_rule(
+                policy['id'],
+                min_kpps=min_kpps,
+                direction=self.ANY_DIRECTION
+            )['minimum_packet_rate_rule']
+            self.addCleanup(
+                test_utils.call_and_ignore_notfound_exc,
+                self.qos_min_pps_client.delete_minimum_packet_rate_rule,
+                policy['id'],
+                pps_rule['id']
+            )
+
+        return policy
+
+    def _create_network(self):
+        physnet_name = CONF.network_feature_enabled.qos_placement_physnet
+        base_segm = (
+            CONF.network_feature_enabled.provider_net_base_segmentation_id)
+
+        # setup_network_subnet_with_router will add the necessary cleanup calls
+        network, _, _ = self.setup_network_subnet_with_router(
+            networks_client=self.networks_client,
+            routers_client=self.routers_client,
+            subnets_client=self.subnets_client,
+            shared=True,
+            **{
+                'provider:network_type': 'vlan',
+                'provider:physical_network': physnet_name,
+                # +1 to be different from the segmentation_id used in
+                # MinBwAllocationPlacementTest
+                'provider:segmentation_id': int(base_segm) + 1,
+            }
+        )
+        return network
+
+    def _create_port_with_qos_policy(self, policy):
+        port = self.ports_client.create_port(
+            name=data_utils.rand_name(self.__class__.__name__),
+            network_id=self.network['id'],
+            qos_policy_id=policy['id'] if policy else None,
+        )['port']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ports_client.delete_port, port['id']
+        )
+        return port
+
+    def assert_allocations(
+            self, server, port, expected_min_kbps, expected_min_kpps
+    ):
+        allocations = self.placement_client.list_allocations(
+            server['id'])['allocations']
+
+        # one allocation for the flavor related resources on the compute RP
+        expected_allocation = 1
+        # one allocation due to bw rule
+        if expected_min_kbps > 0:
+            expected_allocation += 1
+        # one allocation due to pps rule
+        if expected_min_kpps > 0:
+            expected_allocation += 1
+        self.assertEqual(expected_allocation, len(allocations), allocations)
+
+        expected_rp_uuids_in_binding_allocation = set()
+
+        if expected_min_kbps > 0:
+            bw_rp_allocs = {
+                rp: alloc['resources'][self.INGRESS_RESOURCE_CLASS]
+                for rp, alloc in allocations.items()
+                if self.INGRESS_RESOURCE_CLASS in alloc['resources']
+            }
+            self.assertEqual(1, len(bw_rp_allocs))
+            bw_rp, bw_alloc = list(bw_rp_allocs.items())[0]
+            self.assertEqual(expected_min_kbps, bw_alloc)
+            expected_rp_uuids_in_binding_allocation.add(bw_rp)
+
+        if expected_min_kpps > 0:
+            pps_rp_allocs = {
+                rp: alloc['resources'][self.PPS_RESOURCE_CLASS]
+                for rp, alloc in allocations.items()
+                if self.PPS_RESOURCE_CLASS in alloc['resources']
+            }
+            self.assertEqual(1, len(pps_rp_allocs))
+            pps_rp, pps_alloc = list(pps_rp_allocs.items())[0]
+            self.assertEqual(expected_min_kpps, pps_alloc)
+            expected_rp_uuids_in_binding_allocation.add(pps_rp)
+
+        # Let's check port.binding:profile.allocation points to the two
+        # provider resource allocated from
+        port = self.os_admin.ports_client.show_port(port['id'])
+        port_binding_alloc = port[
+            'port']['binding:profile'].get('allocation', {})
+        self.assertEqual(
+            expected_rp_uuids_in_binding_allocation,
+            set(port_binding_alloc.values())
+        )
+
+    def assert_no_allocation(self, server, port):
+        # check that there are no allocations
+        allocations = self.placement_client.list_allocations(
+            server['id'])['allocations']
+        self.assertEqual(0, len(allocations))
+
+        # check that binding_profile of the port is empty
+        port = self.os_admin.ports_client.show_port(port['id'])
+        self.assertEqual(0, len(port['port']['binding:profile']))
+
+    @decorators.idempotent_id('93d1a88d-235e-4b7b-b44d-2a17dcf4e213')
+    @utils.services('compute', 'network')
+    def test_server_create_delete(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+        port = self._create_port_with_qos_policy(policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        self.servers_client.delete_server(server['id'])
+        waiters.wait_for_server_termination(self.servers_client, server['id'])
+
+        self.assert_no_allocation(server, port)
+
+    def _test_create_server_negative(self, min_kbps=1000, min_kpps=100):
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+        port = self._create_port_with_qos_policy(policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until=None)
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='ERROR', ready_wait=False, raise_on_error=False)
+
+        # check that the creation failed with No valid host
+        server = self.servers_client.show_server(server['id'])['server']
+        self.assertIn('fault', server)
+        self.assertIn('No valid host', server['fault']['message'])
+
+        self.assert_no_allocation(server, port)
+
+    @decorators.idempotent_id('915dd2ce-4890-40c8-9db6-f3e04080c6c1')
+    @utils.services('compute', 'network')
+    def test_server_create_no_valid_host_due_to_bandwidth(self):
+        self._test_create_server_negative(min_kbps=self.PLACEMENT_MAX_INT)
+
+    @decorators.idempotent_id('2d4a755e-10b9-4ac0-bef2-3f89de1f150b')
+    @utils.services('compute', 'network')
+    def test_server_create_no_valid_host_due_to_packet_rate(self):
+        self._test_create_server_negative(min_kpps=self.PLACEMENT_MAX_INT)
+
+    @decorators.idempotent_id('69d93e4f-0dfc-4d17-8d84-cc5c3c842cd5')
+    @testtools.skipUnless(
+        CONF.compute_feature_enabled.resize, 'Resize not available.')
+    @utils.services('compute', 'network')
+    def test_server_resize(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+        port = self._create_port_with_qos_policy(policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        new_flavor = self._create_flavor_to_resize_to()
+
+        self.servers_client.resize_server(
+            server_id=server['id'], flavor_ref=new_flavor['id']
+        )
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        self.servers_client.confirm_resize_server(server_id=server['id'])
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='ACTIVE', ready_wait=False, raise_on_error=True)
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+    @decorators.idempotent_id('d01d4aee-ca06-4e4e-add7-8a47fe0daf96')
+    @testtools.skipUnless(
+        CONF.compute_feature_enabled.resize, 'Resize not available.')
+    @utils.services('compute', 'network')
+    def test_server_resize_revert(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+        port = self._create_port_with_qos_policy(policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        new_flavor = self._create_flavor_to_resize_to()
+
+        self.servers_client.resize_server(
+            server_id=server['id'], flavor_ref=new_flavor['id']
+        )
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        self.servers_client.revert_resize_server(server_id=server['id'])
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='ACTIVE', ready_wait=False, raise_on_error=True)
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+    @decorators.idempotent_id('bdd0b31c-c8b0-4b7b-b80a-545a46b32abe')
+    @testtools.skipUnless(
+        CONF.compute_feature_enabled.cold_migration,
+        'Cold migration is not available.')
+    @testtools.skipUnless(
+        CONF.compute.min_compute_nodes > 1,
+        'Less than 2 compute nodes, skipping multinode tests.')
+    @utils.services('compute', 'network')
+    def test_server_migrate(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+        port = self._create_port_with_qos_policy(policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        self.os_adm.servers_client.migrate_server(server_id=server['id'])
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        self.os_adm.servers_client.confirm_resize_server(
+            server_id=server['id'])
+        waiters.wait_for_server_status(
+            client=self.servers_client, server_id=server['id'],
+            status='ACTIVE', ready_wait=False, raise_on_error=True)
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+    @decorators.idempotent_id('fdb260e3-caa5-482d-ac7c-8c22adf3d750')
+    @utils.services('compute', 'network')
+    def test_qos_policy_update_on_bound_port(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+
+        min_kbps2 = 2000
+        min_kpps2 = 50
+        policy2 = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps2, min_kpps2)
+
+        port = self._create_port_with_qos_policy(policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        self.ports_client.update_port(
+            port['id'],
+            qos_policy_id=policy2['id'])
+
+        self.assert_allocations(server, port, min_kbps2, min_kpps2)
+
+    @decorators.idempotent_id('e6a20125-a02e-49f5-bcf6-894305ee3715')
+    @utils.services('compute', 'network')
+    def test_qos_policy_update_on_bound_port_from_null_policy(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+
+        port = self._create_port_with_qos_policy(policy=None)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, 0, 0)
+
+        self.ports_client.update_port(
+            port['id'],
+            qos_policy_id=policy['id'])
+
+        # NOTE(gibi): This is unintuitive but it is the expected behavior.
+        # If there was no policy attached to the port when the server was
+        # created then neutron still allows adding a policy to the port later
+        # as this operation was support before placement enforcement was added
+        # for the qos minimum bandwidth rule. However neutron cannot create
+        # the placement resource allocation for this port.
+        self.assert_allocations(server, port, 0, 0)
+
+    @decorators.idempotent_id('f5864761-966c-4e49-b430-ac0044b7d658')
+    @utils.services('compute', 'network')
+    def test_qos_policy_update_on_bound_port_additional_rule(self):
+        min_kbps = 1000
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, 0)
+
+        min_kbps2 = 2000
+        min_kpps2 = 50
+        policy2 = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps2, min_kpps2)
+
+        port = self._create_port_with_qos_policy(policy=policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, min_kbps, 0)
+
+        self.ports_client.update_port(
+            port['id'],
+            qos_policy_id=policy2['id'])
+
+        # FIXME(gibi): Agree in the spec: do we ignore the pps request or we
+        # reject the update? It seems current implementation goes with
+        # ignoring the additional pps rule.
+        self.assert_allocations(server, port, min_kbps2, 0)
+
+    @decorators.idempotent_id('fbbb9c81-ed21-48c3-bdba-ce2361e93aad')
+    @utils.services('compute', 'network')
+    def test_qos_policy_update_on_bound_port_to_null_policy(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+
+        port = self._create_port_with_qos_policy(policy=policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        self.ports_client.update_port(
+            port['id'],
+            qos_policy_id=None)
+
+        self.assert_allocations(server, port, 0, 0)
+
+    @decorators.idempotent_id('0393d038-03ad-4844-a0e4-83010f69dabb')
+    @utils.services('compute', 'network')
+    def test_interface_attach_detach(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+
+        port = self._create_port_with_qos_policy(policy=None)
+
+        port2 = self._create_port_with_qos_policy(policy=policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, 0, 0)
+
+        self.interface_client.create_interface(
+            server_id=server['id'],
+            port_id=port2['id'])
+        waiters.wait_for_interface_status(
+            self.interface_client, server['id'], port2['id'], 'ACTIVE')
+
+        self.assert_allocations(server, port2, min_kbps, min_kpps)
+
+        req_id = self.interface_client.delete_interface(
+            server_id=server['id'],
+            port_id=port2['id']).response['x-openstack-request-id']
+        waiters.wait_for_interface_detach(
+            self.servers_client, server['id'], port2['id'], req_id)
+
+        self.assert_allocations(server, port2, 0, 0)
+
+    @decorators.idempotent_id('36ffdb85-6cc2-4cc9-a426-cad5bac8626b')
+    @testtools.skipUnless(
+        CONF.compute.min_compute_nodes > 1,
+        'Less than 2 compute nodes, skipping multinode tests.')
+    @testtools.skipUnless(
+        CONF.compute_feature_enabled.live_migration,
+        'Live migration not available')
+    @utils.services('compute', 'network')
+    def test_server_live_migrate(self):
+        min_kbps = 1000
+        min_kpps = 100
+        policy = self._create_qos_policy_with_bw_and_pps_rules(
+            min_kbps, min_kpps)
+
+        port = self._create_port_with_qos_policy(policy=policy)
+
+        server = self.create_server(
+            networks=[{'port': port['id']}],
+            wait_until='ACTIVE'
+        )
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
+
+        server_details = self.os_adm.servers_client.show_server(server['id'])
+        source_host = server_details['server']['OS-EXT-SRV-ATTR:host']
+
+        self.os_adm.servers_client.live_migrate_server(
+            server['id'], block_migration=True, host=None)
+        waiters.wait_for_server_status(
+            self.servers_client, server['id'], 'ACTIVE')
+
+        server_details = self.os_adm.servers_client.show_server(server['id'])
+        new_host = server_details['server']['OS-EXT-SRV-ATTR:host']
+
+        self.assertNotEqual(source_host, new_host, "Live migration failed")
+
+        self.assert_allocations(server, port, min_kbps, min_kpps)
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 14f24c7..4f5118b 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -66,7 +66,7 @@
     def setUp(self):
         super(TestGettingAddress, self).setUp()
         self.keypair = self.create_keypair()
-        self.sec_grp = self._create_security_group()
+        self.sec_grp = self.create_security_group()
 
     def prepare_network(self, address6_mode, n_subnets6=1, dualnet=False):
         """Prepare network
@@ -77,15 +77,15 @@
         if dualnet - create IPv6 subnets on a different network
         :return: list of created networks
         """
-        network = self._create_network()
+        network = self.create_network()
         if dualnet:
-            network_v6 = self._create_network()
+            network_v6 = self.create_network()
 
         sub4 = self.create_subnet(network=network,
                                   namestart='sub4',
                                   ip_version=4)
 
-        router = self._get_router()
+        router = self.get_router()
         self.routers_client.add_router_interface(router['id'],
                                                  subnet_id=sub4['id'])
 
@@ -218,7 +218,7 @@
                     guest_has_address,
                     CONF.validation.ping_timeout, 1, ssh, ip)
                 if not result:
-                    self._log_console_output(servers=[srv])
+                    self.log_console_output(servers=[srv])
                     self.fail(
                         'Address %s not configured for instance %s, '
                         'ip address output is\n%s' %
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 3fc93e4..aff7509 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -197,14 +197,14 @@
         tenant.keypair = keypair
 
     def _create_tenant_security_groups(self, tenant):
-        access_sg = self._create_empty_security_group(
+        access_sg = self.create_empty_security_group(
             namestart='secgroup_access-',
             project_id=tenant.creds.project_id,
             client=tenant.manager.security_groups_client
         )
 
         # don't use default secgroup since it allows in-project traffic
-        def_sg = self._create_empty_security_group(
+        def_sg = self.create_empty_security_group(
             namestart='secgroup_general-',
             project_id=tenant.creds.project_id,
             client=tenant.manager.security_groups_client
@@ -217,7 +217,7 @@
             direction='ingress',
         )
         sec_group_rules_client = tenant.manager.security_group_rules_client
-        self._create_security_group_rule(
+        self.create_security_group_rule(
             secgroup=access_sg,
             sec_group_rules_client=sec_group_rules_client,
             **ssh_rule)
@@ -326,7 +326,7 @@
         self.floating_ips.setdefault(server['id'], floating_ip)
 
     def _create_tenant_network(self, tenant, port_security_enabled=True):
-        network, subnet, router = self.create_networks(
+        network, subnet, router = self.setup_network_subnet_with_router(
             networks_client=tenant.manager.networks_client,
             routers_client=tenant.manager.routers_client,
             subnets_client=tenant.manager.subnets_client,
@@ -385,7 +385,7 @@
             remote_group_id=tenant.security_groups['default']['id'],
             direction='ingress'
         )
-        self._create_security_group_rule(
+        self.create_security_group_rule(
             secgroup=tenant.security_groups['default'],
             security_groups_client=tenant.manager.security_groups_client,
             **ruleset
@@ -413,7 +413,7 @@
         protocol = ruleset['protocol']
         sec_group_rules_client = (
             dest_tenant.manager.security_group_rules_client)
-        self._create_security_group_rule(
+        self.create_security_group_rule(
             secgroup=dest_tenant.security_groups['default'],
             sec_group_rules_client=sec_group_rules_client,
             **ruleset
@@ -429,7 +429,7 @@
         # allow reverse traffic and check
         sec_group_rules_client = (
             source_tenant.manager.security_group_rules_client)
-        self._create_security_group_rule(
+        self.create_security_group_rule(
             secgroup=source_tenant.security_groups['default'],
             sec_group_rules_client=sec_group_rules_client,
             **ruleset
@@ -464,9 +464,9 @@
     def _log_console_output_for_all_tenants(self):
         for tenant in self.tenants.values():
             client = tenant.manager.servers_client
-            self._log_console_output(servers=tenant.servers, client=client)
+            self.log_console_output(servers=tenant.servers, client=client)
             if tenant.access_point is not None:
-                self._log_console_output(
+                self.log_console_output(
                     servers=[tenant.access_point], client=client)
 
     def _create_protocol_ruleset(self, protocol, port=80):
@@ -534,7 +534,7 @@
         new_tenant = self.primary_tenant
 
         # Create empty security group and add icmp rule in it
-        new_sg = self._create_empty_security_group(
+        new_sg = self.create_empty_security_group(
             namestart='secgroup_new-',
             project_id=new_tenant.creds.project_id,
             client=new_tenant.manager.security_groups_client)
@@ -543,7 +543,7 @@
             direction='ingress',
         )
         sec_group_rules_client = new_tenant.manager.security_group_rules_client
-        self._create_security_group_rule(
+        self.create_security_group_rule(
             secgroup=new_sg,
             sec_group_rules_client=sec_group_rules_client,
             **icmp_rule)
@@ -596,7 +596,7 @@
             protocol='icmp',
             direction='ingress'
         )
-        self._create_security_group_rule(
+        self.create_security_group_rule(
             secgroup=tenant.security_groups['default'],
             **ruleset
         )
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 8aa729b..990b325 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -37,7 +37,7 @@
 
     @classmethod
     def setup_credentials(cls):
-        cls.set_network_resources()
+        cls.set_network_resources(network=True, subnet=True)
         super(TestServerAdvancedOps, cls).setup_credentials()
 
     @decorators.attr(type='slow')
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 02bc692..2a15470 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -52,7 +52,9 @@
             # 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']
+                fip = self.create_floating_ip(self.instance)
+                self.ip = self.associate_floating_ip(
+                    fip, self.instance)['floating_ip_address']
             else:
                 server = self.servers_client.show_server(
                     self.instance['id'])['server']
@@ -67,7 +69,10 @@
     def verify_metadata(self):
         if self.run_ssh and CONF.compute_feature_enabled.metadata_service:
             # Verify metadata service
-            md_url = 'http://169.254.169.254/latest/meta-data/public-ipv4'
+            if CONF.network.public_network_id:
+                md_url = 'http://169.254.169.254/latest/meta-data/public-ipv4'
+            else:
+                md_url = 'http://169.254.169.254/latest/meta-data/local-ipv4'
 
             def exec_cmd_and_verify_output():
                 cmd = 'curl ' + md_url
@@ -125,7 +130,7 @@
     @utils.services('compute', 'network')
     def test_server_basic_ops(self):
         keypair = self.create_keypair()
-        security_group = self._create_security_group()
+        security_group = self.create_security_group()
         self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'}
         self.instance = self.create_server(
             key_name=keypair['name'],
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index d6b6d14..29612ec 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -33,9 +33,18 @@
      * shelve the instance
      * unshelve the instance
      * check the existence of the timestamp file in the unshelved instance
+     * check the existence of the timestamp file in the unshelved instance,
+       after a cold migrate
 
     """
 
+    credentials = ['primary', 'admin']
+
+    @classmethod
+    def setup_clients(cls):
+        super(TestShelveInstance, cls).setup_clients()
+        cls.admin_servers_client = cls.os_admin.servers_client
+
     @classmethod
     def skip_checks(cls):
         super(TestShelveInstance, cls).skip_checks()
@@ -50,10 +59,24 @@
         waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'ACTIVE')
 
-    def _create_server_then_shelve_and_unshelve(self, boot_from_volume=False):
+    def _cold_migrate_server(self, server):
+        src_host = self.get_host_for_server(server['id'])
+
+        self.admin_servers_client.migrate_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'VERIFY_RESIZE')
+        self.servers_client.confirm_resize_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+
+        dst_host = self.get_host_for_server(server['id'])
+        self.assertNotEqual(src_host, dst_host)
+
+    def _create_server_then_shelve_and_unshelve(self, boot_from_volume=False,
+                                                cold_migrate=False):
         keypair = self.create_keypair()
 
-        security_group = self._create_security_group()
+        security_group = self.create_security_group()
         security_groups = [{'name': security_group['name']}]
 
         server = self.create_server(
@@ -71,6 +94,10 @@
         # with the instance snapshot
         self._shelve_then_unshelve_server(server)
 
+        if cold_migrate:
+            # Prevent bug #1732428 from coming back
+            self._cold_migrate_server(server)
+
         timestamp2 = self.get_timestamp(instance_ip,
                                         private_key=keypair['private_key'],
                                         server=server)
@@ -91,3 +118,18 @@
     @utils.services('compute', 'volume', 'network', 'image')
     def test_shelve_volume_backed_instance(self):
         self._create_server_then_shelve_and_unshelve(boot_from_volume=True)
+
+    @decorators.attr(type='slow')
+    @decorators.idempotent_id('1295fd9e-193a-4cf8-b211-55358e021bae')
+    @testtools.skipUnless(CONF.network.public_network_id,
+                          'The public_network_id option must be specified.')
+    @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
+                          'Cold migration not available.')
+    @testtools.skipUnless(CONF.compute_feature_enabled.shelve_migrate,
+                          'Shelve migrate not available.')
+    @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
+                          'Less than 2 compute nodes, skipping multinode '
+                          'tests.')
+    @utils.services('compute', 'network', 'image')
+    def test_cold_migrate_unshelved_instance(self):
+        self._create_server_then_shelve_and_unshelve(cold_migrate=True)
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index a062d40..d04cb9a 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -50,7 +50,7 @@
     def test_snapshot_pattern(self):
         # prepare for booting an instance
         keypair = self.create_keypair()
-        security_group = self._create_security_group()
+        security_group = self.create_security_group()
 
         # boot an instance and create a timestamp file in it
         server = self.create_server(
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index c3b3670..4b81b9e 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from oslo_log import log as logging
 import testtools
 
 from tempest.common import utils
@@ -24,7 +23,6 @@
 from tempest.scenario import manager
 
 CONF = config.CONF
-LOG = logging.getLogger(__name__)
 
 
 class TestStampPattern(manager.ScenarioTest):
@@ -83,7 +81,7 @@
     def test_stamp_pattern(self):
         # prepare for booting an instance
         keypair = self.create_keypair()
-        security_group = self._create_security_group()
+        security_group = self.create_security_group()
 
         # boot an instance and create a timestamp file in it
         volume = self.create_volume()
diff --git a/tempest/scenario/test_unified_limits.py b/tempest/scenario/test_unified_limits.py
new file mode 100644
index 0000000..22256b4
--- /dev/null
+++ b/tempest/scenario/test_unified_limits.py
@@ -0,0 +1,435 @@
+# Copyright 2021 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.
+
+import io
+
+from oslo_utils import units
+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.common.utils import test_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest.scenario import manager
+
+CONF = config.CONF
+
+
+class ImageQuotaTest(manager.ScenarioTest):
+    credentials = ['primary', 'system_admin']
+
+    @classmethod
+    def resource_setup(cls):
+        super(ImageQuotaTest, cls).resource_setup()
+
+        # Figure out and record the glance service id
+        services = cls.os_system_admin.identity_services_v3_client.\
+            list_services()
+        glance_services = [x for x in services['services']
+                           if x['name'] == 'glance']
+        cls.glance_service_id = glance_services[0]['id']
+
+        # Pre-create all the quota limits and record their IDs so we can
+        # update them in-place without needing to know which ones have been
+        # created and in which order.
+        cls.limit_ids = {}
+
+        try:
+            cls.limit_ids['image_size_total'] = cls._create_limit(
+                'image_size_total', 10)
+            cls.limit_ids['image_stage_total'] = cls._create_limit(
+                'image_stage_total', 10)
+            cls.limit_ids['image_count_total'] = cls._create_limit(
+                'image_count_total', 10)
+            cls.limit_ids['image_count_uploading'] = cls._create_limit(
+                'image_count_uploading', 10)
+        except lib_exc.Forbidden:
+            # If we fail to set limits, it means they are not
+            # registered, and thus we will skip these tests once we
+            # have our os_system_admin client and run
+            # check_quotas_enabled().
+            pass
+
+    def setUp(self):
+        super(ImageQuotaTest, self).setUp()
+        self.created_images = []
+
+    def create_image(self, data=None, **kwargs):
+        """Wrapper that returns a test image."""
+
+        if 'name' not in kwargs:
+            name = data_utils.rand_name(self.__name__ + "-image")
+            kwargs['name'] = name
+
+        params = dict(kwargs)
+        if data:
+            # NOTE: On glance v1 API, the data should be passed on
+            # a header. Then here handles the data separately.
+            params['data'] = data
+
+        image = self.image_client.create_image(**params)
+        # Image objects returned by the v1 client have the image
+        # data inside a dict that is keyed against 'image'.
+        if 'image' in image:
+            image = image['image']
+        self.created_images.append(image['id'])
+        self.addCleanup(
+            self.image_client.wait_for_resource_deletion,
+            image['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.image_client.delete_image, image['id'])
+        return image
+
+    def check_quotas_enabled(self):
+        # Check to see if we should even be running these tests. Use
+        # the presence of a registered limit that we recognize as an
+        # indication.  This will be set up by the operator (or
+        # devstack) if glance is configured to use/honor the unified
+        # limits. If one is set, they must all be set, because glance
+        # has a single all-or-nothing flag for whether or not to use
+        # keystone limits. If anything, checking only one helps to
+        # assert the assumption that, if enabled, they must all be at
+        # least registered for proper operation.
+        registered_limits = self.os_system_admin.identity_limits_client.\
+            get_registered_limits()['registered_limits']
+        if 'image_count_total' not in [x['resource_name']
+                                       for x in registered_limits]:
+            raise self.skipException('Target system is not configured with '
+                                     'glance unified limits')
+
+    @classmethod
+    def _create_limit(cls, name, value):
+        return cls.os_system_admin.identity_limits_client.create_limit(
+            CONF.identity.region, cls.glance_service_id,
+            cls.image_client.tenant_id, name, value)['limits'][0]['id']
+
+    def _update_limit(self, name, value):
+        self.os_system_admin.identity_limits_client.update_limit(
+            self.limit_ids[name], value)
+
+    def _cleanup_images(self):
+        while self.created_images:
+            image_id = self.created_images.pop()
+            try:
+                self.image_client.delete_image(image_id)
+            except lib_exc.NotFound:
+                pass
+
+    @decorators.idempotent_id('9b74fe24-183b-41e6-bf42-84c2958a7be8')
+    @utils.services('image', 'identity')
+    def test_image_count_quota(self):
+        self.check_quotas_enabled()
+
+        # Set a quota on the number of images for our tenant to one.
+        self._update_limit('image_count_total', 1)
+
+        # Create one image
+        image = self.create_image(name='first',
+                                  container_format='bare',
+                                  disk_format='raw',
+                                  visibility='private')
+
+        # Second image would put us over quota, so expect failure.
+        self.assertRaises(lib_exc.OverLimit,
+                          self.create_image,
+                          name='second',
+                          container_format='bare',
+                          disk_format='raw',
+                          visibility='private')
+
+        # Update our limit to two.
+        self._update_limit('image_count_total', 2)
+
+        # Now the same create should succeed.
+        self.create_image(name='second',
+                          container_format='bare',
+                          disk_format='raw',
+                          visibility='private')
+
+        # Third image would put us over quota, so expect failure.
+        self.assertRaises(lib_exc.OverLimit,
+                          self.create_image,
+                          name='third',
+                          container_format='bare',
+                          disk_format='raw',
+                          visibility='private')
+
+        # Delete the first image to put us under quota.
+        self.image_client.delete_image(image['id'])
+
+        # Now the same create should succeed.
+        self.create_image(name='third',
+                          container_format='bare',
+                          disk_format='raw',
+                          visibility='private')
+
+        # Delete all the images we created before the next test runs,
+        # so that it starts with full quota.
+        self._cleanup_images()
+
+    @decorators.idempotent_id('b103788b-5329-4aa9-8b0d-97f8733460db')
+    @utils.services('image', 'identity')
+    def test_image_count_uploading_quota(self):
+        if not CONF.image_feature_enabled.import_image:
+            skip_msg = (
+                "%s skipped as image import is not available" % __name__)
+            raise self.skipException(skip_msg)
+
+        self.check_quotas_enabled()
+
+        # Set a quota on the number of images we can have in uploading state.
+        self._update_limit('image_stage_total', 10)
+        self._update_limit('image_size_total', 10)
+        self._update_limit('image_count_total', 10)
+        self._update_limit('image_count_uploading', 1)
+
+        file_content = data_utils.random_bytes(1 * units.Mi)
+
+        # Create and stage an image
+        image1 = self.create_image(name='first',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.image_client.stage_image_file(image1['id'],
+                                           io.BytesIO(file_content))
+
+        # Check that we can not stage another
+        image2 = self.create_image(name='second',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.assertRaises(lib_exc.OverLimit,
+                          self.image_client.stage_image_file,
+                          image2['id'], io.BytesIO(file_content))
+
+        # ... nor upload directly
+        image3 = self.create_image(name='third',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.assertRaises(lib_exc.OverLimit,
+                          self.image_client.store_image_file,
+                          image3['id'],
+                          io.BytesIO(file_content))
+
+        # Update our quota to make room
+        self._update_limit('image_count_uploading', 2)
+
+        # Now our upload should work
+        self.image_client.store_image_file(image3['id'],
+                                           io.BytesIO(file_content))
+
+        # ...and because that is no longer in uploading state, we should be
+        # able to stage our second image from above.
+        self.image_client.stage_image_file(image2['id'],
+                                           io.BytesIO(file_content))
+
+        # Finish our import of image2
+        self.image_client.image_import(image2['id'], method='glance-direct')
+        waiters.wait_for_image_imported_to_stores(self.image_client,
+                                                  image2['id'])
+
+        # Set our quota back to one
+        self._update_limit('image_count_uploading', 1)
+
+        # Since image1 is still staged, we should not be able to upload
+        # an image.
+        image4 = self.create_image(name='fourth',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.assertRaises(lib_exc.OverLimit,
+                          self.image_client.store_image_file,
+                          image4['id'],
+                          io.BytesIO(file_content))
+
+        # Finish our import of image1 to make space in our uploading quota.
+        self.image_client.image_import(image1['id'], method='glance-direct')
+        waiters.wait_for_image_imported_to_stores(self.image_client,
+                                                  image1['id'])
+
+        # Make sure that freed up the one upload quota to complete our upload
+        self.image_client.store_image_file(image4['id'],
+                                           io.BytesIO(file_content))
+
+        # Delete all the images we created before the next test runs,
+        # so that it starts with full quota.
+        self._cleanup_images()
+
+    @decorators.idempotent_id('05e8d064-c39a-4801-8c6a-465df375ec5b')
+    @utils.services('image', 'identity')
+    def test_image_size_quota(self):
+        self.check_quotas_enabled()
+
+        # Set a quota on the image size for our tenant to 1MiB, and allow ten
+        # images.
+        self._update_limit('image_size_total', 1)
+        self._update_limit('image_count_total', 10)
+        self._update_limit('image_count_uploading', 10)
+
+        file_content = data_utils.random_bytes(1 * units.Mi)
+
+        # Create and upload a 1MiB image.
+        image1 = self.create_image(name='first',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.image_client.store_image_file(image1['id'],
+                                           io.BytesIO(file_content))
+
+        # Create and upload a second 1MiB image. This succeeds, but
+        # after completion, we are over quota. Despite us being at
+        # quota above, the initial quota check for the second
+        # operation has no idea what the image size will be, and thus
+        # uses delta=0. This will succeed because we're not
+        # technically over-quota and have not asked for any more (this
+        # is oslo.limit behavior). After the second operation,
+        # however, we will be over-quota regardless of the delta and
+        # subsequent attempts will fail. Because glance goes not
+        # require an image size to be declared before upload, this is
+        # really the best it can do without an API change.
+        image2 = self.create_image(name='second',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.image_client.store_image_file(image2['id'],
+                                           io.BytesIO(file_content))
+
+        # Create and attempt to upload a third 1MiB image. This should fail to
+        # upload (but not create) because we are over quota.
+        image3 = self.create_image(name='third',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.assertRaises(lib_exc.OverLimit,
+                          self.image_client.store_image_file,
+                          image3['id'], io.BytesIO(file_content))
+
+        # Increase our size quota to 2MiB.
+        self._update_limit('image_size_total', 2)
+
+        # Now the upload of the already-created image is allowed, but
+        # after completion, we are over quota again.
+        self.image_client.store_image_file(image3['id'],
+                                           io.BytesIO(file_content))
+
+        # Create and attempt to upload a fourth 1MiB image. This should
+        # fail to upload (but not create) because we are over quota.
+        image4 = self.create_image(name='fourth',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.assertRaises(lib_exc.OverLimit,
+                          self.image_client.store_image_file,
+                          image4['id'], io.BytesIO(file_content))
+
+        # Delete our first image to make space in our existing 2MiB quota.
+        self.image_client.delete_image(image1['id'])
+
+        # Now the upload of the already-created image is allowed.
+        self.image_client.store_image_file(image4['id'],
+                                           io.BytesIO(file_content))
+
+        # Delete all the images we created before the next test runs,
+        # so that it starts with full quota.
+        self._cleanup_images()
+
+    @decorators.idempotent_id('fc76b8d9-aae5-46fb-9285-099e37f311f7')
+    @utils.services('image', 'identity')
+    def test_image_stage_quota(self):
+        if not CONF.image_feature_enabled.import_image:
+            skip_msg = (
+                "%s skipped as image import is not available" % __name__)
+            raise self.skipException(skip_msg)
+
+        self.check_quotas_enabled()
+
+        # Create a staging quota of 1MiB, allow 10MiB of active
+        # images, and a total of ten images.
+        self._update_limit('image_stage_total', 1)
+        self._update_limit('image_size_total', 10)
+        self._update_limit('image_count_total', 10)
+        self._update_limit('image_count_uploading', 10)
+
+        file_content = data_utils.random_bytes(1 * units.Mi)
+
+        # Create and stage a 1MiB image.
+        image1 = self.create_image(name='first',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.image_client.stage_image_file(image1['id'],
+                                           io.BytesIO(file_content))
+
+        # Create and stage a second 1MiB image. This succeeds, but
+        # after completion, we are over quota.
+        image2 = self.create_image(name='second',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.image_client.stage_image_file(image2['id'],
+                                           io.BytesIO(file_content))
+
+        # Create and attempt to stage a third 1MiB image. This should fail to
+        # stage (but not create) because we are over quota.
+        image3 = self.create_image(name='third',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.assertRaises(lib_exc.OverLimit,
+                          self.image_client.stage_image_file,
+                          image3['id'], io.BytesIO(file_content))
+
+        # Make sure that even though we are over our stage quota, we
+        # can still create and upload an image the regular way.
+        image_upload = self.create_image(name='uploaded',
+                                         container_format='bare',
+                                         disk_format='raw',
+                                         visibility='private')
+        self.image_client.store_image_file(image_upload['id'],
+                                           io.BytesIO(file_content))
+
+        # Increase our stage quota to two MiB.
+        self._update_limit('image_stage_total', 2)
+
+        # Now the upload of the already-created image is allowed, but
+        # after completion, we are over quota again.
+        self.image_client.stage_image_file(image3['id'],
+                                           io.BytesIO(file_content))
+
+        # Create and attempt to stage a fourth 1MiB image. This should
+        # fail to stage (but not create) because we are over quota.
+        image4 = self.create_image(name='fourth',
+                                   container_format='bare',
+                                   disk_format='raw',
+                                   visibility='private')
+        self.assertRaises(lib_exc.OverLimit,
+                          self.image_client.stage_image_file,
+                          image4['id'], io.BytesIO(file_content))
+
+        # Finish our import of image1 to make space in our stage quota.
+        self.image_client.image_import(image1['id'], method='glance-direct')
+        waiters.wait_for_image_imported_to_stores(self.image_client,
+                                                  image1['id'])
+
+        # Now the upload of the already-created image is allowed.
+        self.image_client.stage_image_file(image4['id'],
+                                           io.BytesIO(file_content))
+
+        # Delete all the images we created before the next test runs,
+        # so that it starts with full quota.
+        self._cleanup_images()
diff --git a/tempest/scenario/test_volume_backup_restore.py b/tempest/scenario/test_volume_backup_restore.py
index 8a8c54e..d0885cf 100644
--- a/tempest/scenario/test_volume_backup_restore.py
+++ b/tempest/scenario/test_volume_backup_restore.py
@@ -70,7 +70,7 @@
 
         # Create keypair and security group
         keypair = self.create_keypair()
-        security_group = self._create_security_group()
+        security_group = self.create_security_group()
 
         # Boot a server from the restored backup
         bd_map_v2 = [{
@@ -84,11 +84,11 @@
                                     security_groups=[
                                         {'name': security_group['name']}])
 
-        # Create a floating ip
-        floating_ip = self.create_floating_ip(server)
-
+        # Create a floating ip and associate it to server.
+        fip = self.create_floating_ip(server)
+        floating_ip = self.associate_floating_ip(fip, server)
         # Check server connectivity
-        self.check_vm_connectivity(floating_ip['ip'],
+        self.check_vm_connectivity(floating_ip['floating_ip_address'],
                                    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 3b4bbda..5a5cc27 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -64,7 +64,7 @@
 
         LOG.info("Creating keypair and security group")
         keypair = self.create_keypair()
-        security_group = self._create_security_group()
+        security_group = self.create_security_group()
 
         # create an instance from volume
         LOG.info("Booting instance 1 from volume")
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index 106500e..57d2a1a 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -100,7 +100,7 @@
     def test_volume_retype_attached(self):
         LOG.info("Creating keypair and security group")
         keypair = self.create_keypair()
-        security_group = self._create_security_group()
+        security_group = self.create_security_group()
 
         # create volume types
         LOG.info("Creating Volume types")
@@ -156,7 +156,7 @@
     def test_volume_migrate_attached(self):
         LOG.info("Creating keypair and security group")
         keypair = self.create_keypair()
-        security_group = self._create_security_group()
+        security_group = self.create_security_group()
 
         LOG.info("Creating volume")
         # Create a unique volume type to avoid using the backend default
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index 9fec548..0d7720e 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -15,9 +15,9 @@
 
 import re
 import time
+from urllib import parse as urllib
 
 from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
 
 from tempest import exceptions
 from tempest.lib.common import rest_client
diff --git a/tempest/test.py b/tempest/test.py
index f383bc1..bf0aba4 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -20,7 +20,6 @@
 import debtcollector.moves
 import fixtures
 from oslo_log import log as logging
-import six
 import testtools
 
 from tempest import clients
@@ -28,6 +27,7 @@
 from tempest.common import utils
 from tempest import config
 from tempest.lib import base as lib_base
+from tempest.lib.common import api_microversion_fixture
 from tempest.lib.common import fixed_network
 from tempest.lib.common import profiler
 from tempest.lib.common import validation_resources as vr
@@ -38,12 +38,6 @@
 
 CONF = config.CONF
 
-# TODO(oomichi): This test.idempotent_id should be removed after all projects
-# switch to use decorators.idempotent_id.
-idempotent_id = debtcollector.moves.moved_function(
-    decorators.idempotent_id, 'idempotent_id', __name__,
-    version='Mitaka', removal_version='?')
-
 
 attr = debtcollector.moves.moved_function(
     decorators.attr, 'attr', __name__,
@@ -185,7 +179,7 @@
                      etype, cls.__name__)
             cls.tearDownClass()
             try:
-                six.reraise(etype, value, trace)
+                raise value.with_traceback(trace)
             finally:
                 del trace  # to avoid circular refs
         finally:
@@ -239,7 +233,7 @@
         # the first one
         if re_raise and etype is not None:
             try:
-                six.reraise(etype, value, trace)
+                raise value.with_traceback(trace)
             finally:
                 del trace  # to avoid circular refs
 
@@ -302,6 +296,7 @@
         identity_version = cls.get_identity_version()
         # setting force_tenant_isolation to True also needs admin credentials.
         if ('admin' in cls.credentials or
+                'alt_admin' in cls.credentials or
                 getattr(cls, 'force_tenant_isolation', False)):
             if not credentials.is_admin_available(
                     identity_version=identity_version):
@@ -389,7 +384,7 @@
             # This may raise an exception in case credentials are not available
             # In that case we want to let the exception through and the test
             # fail accordingly
-            if isinstance(credentials_type, six.string_types):
+            if isinstance(credentials_type, str):
                 manager = cls.get_client_manager(
                     credential_type=credentials_type)
                 setattr(cls, 'os_%s' % credentials_type, manager)
@@ -420,8 +415,18 @@
                             'alt_manager', 'os_alt', version='Pike',
                             removal_version='Queens')
             elif isinstance(credentials_type, list):
+                scope = 'project'
+                if credentials_type[0].startswith('system'):
+                    scope = 'system'
+                elif credentials_type[0].startswith('domain'):
+                    scope = 'domain'
                 manager = cls.get_client_manager(roles=credentials_type[1:],
-                                                 force_new=True)
+                                                 force_new=True,
+                                                 scope=scope)
+                setattr(cls, 'os_%s' % credentials_type[0], manager)
+                # TODO(gmann): Setting the old style attribute too for
+                # backward compatibility but at some point we should
+                # remove this.
                 setattr(cls, 'os_roles_%s' % credentials_type[0], manager)
 
     @classmethod
@@ -476,6 +481,34 @@
         pass
 
     @classmethod
+    def setup_api_microversion_fixture(
+            cls, compute_microversion=None, volume_microversion=None,
+            placement_microversion=None):
+        """Set up api microversion fixture on service clients.
+
+        `setup_api_microversion_fixture` is used to set the api microversion
+        on service clients. This can be invoked from resource_setup() method.
+
+        Example::
+
+            @classmethod
+            def resource_setup(cls):
+                super(MyTest, cls).resource_setup()
+                cls.setup_api_microversion_fixture(
+                    compute_microversion=cls.compute_request_microversion,
+                    volume_microversion=cls.volume_request_microversion,
+                    placement_microversion=cls.placement_request_microversion)
+
+        """
+
+        api_fixture = api_microversion_fixture.APIMicroversionFixture(
+            compute_microversion=compute_microversion,
+            volume_microversion=volume_microversion,
+            placement_microversion=placement_microversion)
+        api_fixture.setUp()
+        cls.addClassResourceCleanup(api_fixture._reset_microversion)
+
+    @classmethod
     def resource_setup(cls):
         """Class level resource setup for test cases.
 
@@ -663,7 +696,7 @@
 
     @classmethod
     def get_client_manager(cls, credential_type=None, roles=None,
-                           force_new=None):
+                           force_new=None, scope=None):
         """Returns an OpenStack client manager
 
         Returns an OpenStack client manager based on either credential_type
@@ -671,6 +704,7 @@
         credential_type 'primary'
         :param credential_type: string - primary, alt or admin
         :param roles: list of roles
+        :param scope: scope for the test user
 
         :returns: the created client manager
         :raises skipException: if the requested credentials are not available
@@ -689,7 +723,7 @@
                         " is not able to provide credentials with the %s role "
                         "assigned." % (cls.__name__, role))
                     raise cls.skipException(skip_msg)
-            params = dict(roles=roles)
+            params = dict(roles=roles, scope=scope)
             if force_new is not None:
                 params.update(force_new=force_new)
             creds = cred_provider.get_creds_by_roles(**params)
@@ -853,10 +887,16 @@
         """
         # Get a manager for the given credentials_type, but at least
         # always fall back on getting the manager for primary credentials
-        if isinstance(credentials_type, six.string_types):
+        if isinstance(credentials_type, str):
             manager = cls.get_client_manager(credential_type=credentials_type)
         elif isinstance(credentials_type, list):
-            manager = cls.get_client_manager(roles=credentials_type[1:])
+            scope = 'project'
+            if credentials_type[0].startswith('system'):
+                scope = 'system'
+            elif credentials_type[0].startswith('domain'):
+                scope = 'domain'
+            manager = cls.get_client_manager(roles=credentials_type[1:],
+                                             scope=scope)
         else:
             manager = cls.get_client_manager()
 
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index b20b60e..1d69d9d 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -15,7 +15,6 @@
 import abc
 
 from oslo_log import log as logging
-import six
 import stevedore
 
 from tempest.lib.common.utils import misc
@@ -24,8 +23,7 @@
 LOG = logging.getLogger(__name__)
 
 
-@six.add_metaclass(abc.ABCMeta)
-class TempestPlugin(object):
+class TempestPlugin(object, metaclass=abc.ABCMeta):
     """Provide basic hooks for an external plugin
 
     To provide tempest the necessary information to run the plugin.
diff --git a/tempest/tests/api/compute/test_base.py b/tempest/tests/api/compute/test_base.py
index 74d2625..8a1873b 100644
--- a/tempest/tests/api/compute/test_base.py
+++ b/tempest/tests/api/compute/test_base.py
@@ -15,7 +15,6 @@
 from unittest import mock
 
 from oslo_utils import uuidutils
-import six
 
 from tempest.api.compute import base as compute_base
 from tempest.common import waiters
@@ -128,9 +127,9 @@
             mock.sentinel.server_id, wait_until='active')
         # make our assertions
         if fault:
-            self.assertIn(fault, six.text_type(ex))
+            self.assertIn(fault, str(ex))
         else:
-            self.assertNotIn(fault, six.text_type(ex))
+            self.assertNotIn(fault, str(ex))
         if compute_base.BaseV2ComputeTest.is_requested_microversion_compatible(
             '2.35'):
             status = 'ACTIVE'
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
index fc44793..2301be6 100644
--- a/tempest/tests/cmd/test_cleanup_services.py
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -143,40 +143,40 @@
 
     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'},
+        "users": {'32rwef64245tgr20121qw324bgg': 'Lightning'},
+        "flavors": {'42': 'm1.tiny'},
+        "images": {'34yhwr-4t3q': 'stratus-0.3.2-x86_64-disk'},
+        "roles": {'3efrt74r45hn': 'president'},
+        "projects": {'f38ohgp93jj032': 'manhattan'},
+        "domains": {'default': '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'
+        "snapshots": {'1ad4c789-7e8w-4dwg-afc5': 'saved-snapshot'},
+        "servers": {'7a6d4v7w-36ds-4216': 'saved-server'},
+        "server_groups": {'as6d5f7g-46ca-475e': 'saved-server-group'},
+        "keypairs": {'saved-key-pair': {
+            'fingerprint': '7e:eb:ab:24',
+            'name': 'saved-key-pair'
         }},
-        "volumes": {u'aa77asdf-1234': u'saved-volume'},
-        "networks": {u'6722fc13-4319': {
-            u'id': u'6722fc13-4319',
-            u'name': u'saved-network'
+        "volumes": {'aa77asdf-1234': 'saved-volume'},
+        "networks": {'6722fc13-4319': {
+            'id': '6722fc13-4319',
+            'name': 'saved-network'
         }},
-        "floatingips": {u'9e82d248-408a': {
-            u'id': u'9e82d248-408a',
-            u'status': u'ACTIVE'
+        "floatingips": {'9e82d248-408a': {
+            'id': '9e82d248-408a',
+            'status': 'ACTIVE'
         }},
-        "routers": {u'4s5w34hj-id44': u'saved-router'},
-        "metering_label_rules": {u'93a973ce-4dc5': {
-            u'direction': u'ingress',
-            u'id': u'93a973ce-4dc5'
+        "routers": {'4s5w34hj-id44': 'saved-router'},
+        "metering_label_rules": {'93a973ce-4dc5': {
+            'direction': 'ingress',
+            'id': '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'},
-        "regions": {u'RegionOne': {}}
+        "metering_labels": {'723b346ce866-4c7q': 'saved-label'},
+        "ports": {'aa74aa4v-741a': 'saved-port'},
+        "security_groups": {'7q844add-3697': 'saved-sec-group'},
+        "subnets": {'55ttda4a-2584': 'saved-subnet'},
+        "subnetpools": {'8acf64c1-43fc': 'saved-subnet-pool'},
+        "regions": {'RegionOne': {}}
     }
     # Mocked methods
     get_method = 'tempest.lib.common.rest_client.RestClient.get'
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 5d9ddfa..3b5e901 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -21,7 +21,6 @@
 from unittest import mock
 
 import fixtures
-import six
 
 from tempest.cmd import run
 from tempest.cmd import workspace
@@ -29,10 +28,6 @@
 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)
 
@@ -72,6 +67,11 @@
 
 
 class TestRunReturnCode(base.TestCase):
+
+    exclude_regex = '--exclude-regex'
+    exclude_list = '--exclude-list'
+    include_list = '--include-list'
+
     def setUp(self):
         super(TestRunReturnCode, self).setUp()
         # Setup test dirs
@@ -96,6 +96,14 @@
         self.addCleanup(os.chdir, os.path.abspath(os.curdir))
         os.chdir(self.directory)
 
+    def _get_test_list_file(self, content):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        test_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(test_file.close)
+        test_file.write(content.encode('utf-8'))
+        return path
+
     def assertRunExit(self, cmd, expected):
         p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE)
@@ -119,19 +127,23 @@
         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_exclude_regex_failing(self):
+        self.assertRunExit(['tempest', 'run',
+                            self.exclude_regex, 'failing'], 0)
 
-    def test_tempest_run_blackregex_failing_with_stestr_repository(self):
+    def test_tempest_run_exclude_regex_failing_with_stestr_repository(self):
         subprocess.call(['stestr', 'init'])
-        self.assertRunExit(['tempest', 'run', '--black-regex', 'failing'], 0)
+        self.assertRunExit(['tempest', 'run',
+                            self.exclude_regex, 'failing'], 0)
 
-    def test_tempest_run_blackregex_passing(self):
-        self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1)
+    def test_tempest_run_exclude_regex_passing(self):
+        self.assertRunExit(['tempest', 'run',
+                            self.exclude_regex, 'passing'], 1)
 
-    def test_tempest_run_blackregex_passing_with_stestr_repository(self):
+    def test_tempest_run_exclude_regex_passing_with_stestr_repository(self):
         subprocess.call(['stestr', 'init'])
-        self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1)
+        self.assertRunExit(['tempest', 'run',
+                            self.exclude_regex, 'passing'], 1)
 
     def test_tempest_run_fails(self):
         self.assertRunExit(['tempest', 'run'], 1)
@@ -140,61 +152,44 @@
         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])
+        tests = sorted([str(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'),
+            str('tests.test_failing.FakeTestClass.test_pass'),
+            str('tests.test_failing.FakeTestClass.test_pass_list'),
+            str('tests.test_passing.FakeTestClass.test_pass'),
+            str('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]
+        result = ["b\'" + x + "\'" for x in result]
         self.assertEqual(result, tests)
 
     def test_tempest_run_with_worker_file(self):
-        fd, path = tempfile.mkstemp()
-        self.addCleanup(os.remove, path)
-        worker_file = os.fdopen(fd, 'wb', 0)
-        self.addCleanup(worker_file.close)
-        worker_file.write(
-            '- worker:\n  - passing\n  concurrency: 3'.encode('utf-8'))
+        path = self._get_test_list_file(
+            '- worker:\n  - passing\n  concurrency: 3')
         self.assertRunExit(['tempest', 'run', '--worker-file=%s' % path], 0)
 
-    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_include_list(self):
+        path = self._get_test_list_file('passing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.include_list, 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,
+    def test_tempest_run_with_include_regex_include_pass_check_fail(self):
+        path = self._get_test_list_file('passing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.include_list, 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,
+    def test_tempest_run_with_include_regex_include_pass_check_pass(self):
+        path = self._get_test_list_file('passing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.include_list, 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,
+    def test_tempest_run_with_include_regex_include_fail_check_pass(self):
+        path = self._get_test_list_file('failing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.include_list, path),
                             '--regex', 'pass'], 1)
 
     def test_tempest_run_passes_with_config_file(self):
@@ -202,50 +197,75 @@
                             '--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_exclude_list_failing(self):
+        path = self._get_test_list_file('failing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.exclude_list, 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_exclude_list_passing(self):
+        path = self._get_test_list_file('passing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.exclude_list, 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,
+    def test_tempest_run_with_exclude_list_regex_exclude_fail_check_pass(self):
+        path = self._get_test_list_file('failing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.exclude_list, 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,
+    def test_tempest_run_with_exclude_list_regex_exclude_pass_check_pass(self):
+        path = self._get_test_list_file('passing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.exclude_list, 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,
+    def test_tempest_run_with_exclude_list_regex_exclude_pass_check_fail(self):
+        path = self._get_test_list_file('passing')
+        self.assertRunExit(['tempest', 'run',
+                            '%s=%s' % (self.exclude_list, path),
                             '--regex', 'fail'], 1)
 
 
+class TestOldArgRunReturnCode(TestRunReturnCode):
+    """A class for testing deprecated but still supported args.
+
+    This class will be removed once we remove the following arguments:
+      * --black-regex
+      * --blacklist-file
+      * --whitelist-file
+    """
+    exclude_regex = '--black-regex'
+    exclude_list = '--blacklist-file'
+    include_list = '--whitelist-file'
+
+    def _test_args_passing(self, args):
+        self.assertRunExit(['tempest', 'run'] + args, 0)
+
+    def test_tempest_run_new_old_arg_comb(self):
+        path = self._get_test_list_file('failing')
+        self._test_args_passing(['--black-regex', 'failing',
+                                 '--exclude-regex', 'failing'])
+        self._test_args_passing(['--blacklist-file=' + path,
+                                 '--exclude-list=' + path])
+        path = self._get_test_list_file('passing')
+        self._test_args_passing(['--whitelist-file=' + path,
+                                 '--include-list=' + path])
+
+    def _test_args_passing_with_stestr_repository(self, args):
+        subprocess.call(['stestr', 'init'])
+        self.assertRunExit(['tempest', 'run'] + args, 0)
+
+    def test_tempest_run_new_old_arg_comb_with_stestr_repository(self):
+        path = self._get_test_list_file('failing')
+        self._test_args_passing_with_stestr_repository(
+            ['--black-regex', 'failing', '--exclude-regex', 'failing'])
+        self._test_args_passing_with_stestr_repository(
+            ['--blacklist-file=' + path, '--exclude-list=' + path])
+        path = self._get_test_list_file('passing')
+        self._test_args_passing_with_stestr_repository(
+            ['--whitelist-file=' + path, '--include-list=' + path])
+
+
 class TestConfigPathCheck(base.TestCase):
     def setUp(self):
         super(TestConfigPathCheck, self).setUp()
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 277e049..a8a4c0f 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -18,12 +18,9 @@
 import fixtures
 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
-from tempest.lib.common import rest_client
 from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions as lib_exc
 from tempest.tests import base
@@ -97,15 +94,15 @@
         self.useFixture(fixtures.MockPatchObject(
             verify_tempest_config, '_get_unversioned_endpoint',
             return_value='http://fake_endpoint:5000'))
-        fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v2.0'}]}
+        fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]}
         fake_resp = json.dumps(fake_resp)
         self.useFixture(fixtures.MockPatch(
             'tempest.lib.common.http.ClosingHttp.request',
             return_value=(None, fake_resp)))
         fake_os = mock.MagicMock()
         versions = verify_tempest_config._get_api_versions(fake_os, 'cinder')
-        self.assertIn('v1.0', versions)
         self.assertIn('v2.0', versions)
+        self.assertIn('v3.0', versions)
 
     def test_get_nova_versions(self):
         self.useFixture(fixtures.MockPatchObject(
@@ -145,7 +142,7 @@
         self.assertTrue(mock_log_error.called)
 
     def test_verify_api_versions(self):
-        api_services = ['cinder', 'glance', 'keystone']
+        api_services = ['glance', 'keystone']
         fake_os = mock.MagicMock()
         for svc in api_services:
             m = 'verify_%s_api_versions' % svc
@@ -154,7 +151,7 @@
                 verify_mock.assert_called_once_with(fake_os, True)
 
     def test_verify_api_versions_not_implemented(self):
-        api_services = ['cinder', 'glance', 'keystone']
+        api_services = ['glance', 'keystone']
         fake_os = mock.MagicMock()
         for svc in api_services:
             m = 'verify_%s_api_versions' % svc
@@ -178,52 +175,6 @@
                                            'identity-feature-enabled',
                                            False, True)
 
-    @mock.patch('tempest.lib.common.http.ClosingHttp.request')
-    def test_verify_cinder_api_versions_no_v3(self, mock_request):
-        self.useFixture(fixtures.MockPatchObject(
-            verify_tempest_config, '_get_unversioned_endpoint',
-            return_value='http://fake_endpoint:5000'))
-        fake_resp = {'versions': [{'id': 'v2.0'}]}
-        fake_resp = json.dumps(fake_resp)
-        mock_request.return_value = (None, fake_resp)
-        fake_os = mock.MagicMock()
-        with mock.patch.object(verify_tempest_config,
-                               'print_and_or_update') as print_mock:
-            verify_tempest_config.verify_cinder_api_versions(fake_os, True)
-        print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
-                                   False, True)
-        self.assertEqual(1, print_mock.call_count)
-
-    @mock.patch('tempest.lib.common.http.ClosingHttp.request')
-    def test_verify_cinder_api_versions_no_v2(self, mock_request):
-        self.useFixture(fixtures.MockPatchObject(
-            verify_tempest_config, '_get_unversioned_endpoint',
-            return_value='http://fake_endpoint:5000'))
-        fake_resp = {'versions': [{'id': 'v3.0'}]}
-        fake_resp = json.dumps(fake_resp)
-        mock_request.return_value = (None, fake_resp)
-        fake_os = mock.MagicMock()
-        with mock.patch.object(verify_tempest_config,
-                               'print_and_or_update') as print_mock:
-            verify_tempest_config.verify_cinder_api_versions(fake_os, True)
-        print_mock.assert_any_call('api_v2', 'volume-feature-enabled',
-                                   False, True)
-        self.assertEqual(1, print_mock.call_count)
-
-    @mock.patch('tempest.lib.common.http.ClosingHttp.request')
-    def test_verify_cinder_api_versions_no_v1(self, mock_request):
-        self.useFixture(fixtures.MockPatchObject(
-            verify_tempest_config, '_get_unversioned_endpoint',
-            return_value='http://fake_endpoint:5000'))
-        fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]}
-        fake_resp = json.dumps(fake_resp)
-        mock_request.return_value = (None, fake_resp)
-        fake_os = mock.MagicMock()
-        with mock.patch.object(verify_tempest_config,
-                               'print_and_or_update') as print_mock:
-            verify_tempest_config.verify_cinder_api_versions(fake_os, True)
-        print_mock.assert_not_called()
-
     def test_verify_glance_version_no_v2_with_v1_1(self):
         # This test verifies that wrong config api_v2 = True is detected
         class FakeClient(object):
@@ -560,23 +511,24 @@
         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',
-            password='fake_password')
-        os = clients.Manager(creds)
-        for service in ['nova', 'neutron', 'swift', 'cinder']:
+        fake_os = mock.MagicMock()
+        services = {
+            'nova': fake_os.compute.ExtensionsClient(),
+            'neutron': fake_os.network.ExtensionsClient(),
+            'swift': fake_os.object_storage.CapabilitiesClient(),
+            'cinder': fake_os.volume_v2.ExtensionsClient(),
+        }
+        for service in services.keys():
             extensions_client = verify_tempest_config.get_extension_client(
-                os, service)
-            self.assertIsInstance(extensions_client, rest_client.RestClient)
+                fake_os, service)
+            self.assertIsInstance(extensions_client, mock.MagicMock)
+            self.assertEqual(extensions_client, services[service])
 
     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)
+        fake_os = mock.MagicMock()
         self.assertRaises(SystemExit,
                           verify_tempest_config.get_extension_client,
-                          os, 'fakeservice')
+                          fake_os, 'fakeservice')
 
     def test_get_config_file(self):
         conf_dir = os.path.join(os.getcwd(), 'etc')
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index eae6202..f16d533 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -12,15 +12,12 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+from io import StringIO
 import os
 import shutil
 import subprocess
 import tempfile
 from unittest.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
diff --git a/tempest/tests/common/test_compute.py b/tempest/tests/common/test_compute.py
index 45a439c..142bb08 100644
--- a/tempest/tests/common/test_compute.py
+++ b/tempest/tests/common/test_compute.py
@@ -15,7 +15,7 @@
 
 from unittest import mock
 
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
 
 
 from tempest.common import compute
diff --git a/tempest/tests/common/test_credentials_factory.py b/tempest/tests/common/test_credentials_factory.py
index 0ef3742..374474d 100644
--- a/tempest/tests/common/test_credentials_factory.py
+++ b/tempest/tests/common/test_credentials_factory.py
@@ -173,10 +173,15 @@
     @mock.patch.object(cf, 'get_credentials')
     def test_get_configured_admin_credentials(self, mock_get_credentials):
         cfg.CONF.set_default('auth_version', 'v3', 'identity')
-        all_params = [('admin_username', 'username', 'my_name'),
-                      ('admin_password', 'password', 'secret'),
-                      ('admin_project_name', 'project_name', 'my_pname'),
-                      ('admin_domain_name', 'domain_name', 'my_dname')]
+        all_params = [
+            ('admin_username', 'username', 'my_name'),
+            ('admin_user_domain_name', 'user_domain_name', 'my_dname'),
+            ('admin_password', 'password', 'secret'),
+            ('admin_project_name', 'project_name', 'my_pname'),
+            ('admin_project_domain_name', 'project_domain_name', 'my_dname'),
+            ('admin_domain_name', 'domain_name', 'my_dname'),
+            ('admin_system', 'system', None),
+        ]
         expected_result = 'my_admin_credentials'
         mock_get_credentials.return_value = expected_result
         for config_item, _, value in all_params:
@@ -194,10 +199,15 @@
     def test_get_configured_admin_credentials_not_fill_valid(
             self, mock_get_credentials):
         cfg.CONF.set_default('auth_version', 'v2', 'identity')
-        all_params = [('admin_username', 'username', 'my_name'),
-                      ('admin_password', 'password', 'secret'),
-                      ('admin_project_name', 'project_name', 'my_pname'),
-                      ('admin_domain_name', 'domain_name', 'my_dname')]
+        all_params = [
+            ('admin_username', 'username', 'my_name'),
+            ('admin_user_domain_name', 'user_domain_name', 'my_dname'),
+            ('admin_password', 'password', 'secret'),
+            ('admin_project_domain_name', 'project_domain_name', 'my_dname'),
+            ('admin_project_name', 'project_name', 'my_pname'),
+            ('admin_domain_name', 'domain_name', 'my_dname'),
+            ('admin_system', 'system', None),
+        ]
         expected_result = mock.Mock()
         expected_result.is_valid.return_value = True
         mock_get_credentials.return_value = expected_result
@@ -278,3 +288,20 @@
         mock_auth_get_credentials.assert_called_once_with(
             expected_uri, fill_in=False, identity_version='v3',
             **expected_params)
+
+    @mock.patch('tempest.lib.auth.get_credentials')
+    def test_get_credentials_v3_system(self, mock_auth_get_credentials):
+        expected_uri = 'V3_URI'
+        expected_result = 'my_creds'
+        mock_auth_get_credentials.return_value = expected_result
+        cfg.CONF.set_default('uri_v3', expected_uri, 'identity')
+        cfg.CONF.set_default('admin_system', 'all', 'auth')
+        params = {'system': 'all'}
+        expected_params = params.copy()
+        expected_params.update(config.service_client_config())
+        result = cf.get_credentials(fill_in=False, identity_version='v3',
+                                    **params)
+        self.assertEqual(expected_result, result)
+        mock_auth_get_credentials.assert_called_once_with(
+            expected_uri, fill_in=False, identity_version='v3',
+            **expected_params)
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 73924bd..b76a263 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -20,6 +20,7 @@
 from tempest.common import waiters
 from tempest import exceptions
 from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import servers_client
 from tempest.lib.services.volume.v2 import volumes_client
 from tempest.tests import base
 import tempest.tests.utils as utils
@@ -55,6 +56,93 @@
                           waiters.wait_for_image_status,
                           self.client, 'fake_image_id', 'active')
 
+    def test_wait_for_image_imported_to_stores(self):
+        self.client.show_image.return_value = ({'status': 'active',
+                                                'stores': 'fake_store'})
+        start_time = int(time.time())
+        waiters.wait_for_image_imported_to_stores(
+            self.client, 'fake_image_id', 'fake_store')
+        end_time = int(time.time())
+        # Ensure waiter returns before build_timeout
+        self.assertLess((end_time - start_time), 10)
+
+    def test_wait_for_image_imported_to_stores_failure(self):
+        time_mock = self.patch('time.time')
+        client = mock.MagicMock()
+        client.build_timeout = 2
+        self.patch('time.time', side_effect=[0., 1., 2.])
+        time_mock.side_effect = utils.generate_timeout_series(1)
+
+        client.show_image.return_value = ({
+            'status': 'saving',
+            'stores': 'fake_store',
+            'os_glance_failed_import': 'fake_os_glance_failed_import'})
+        self.assertRaises(lib_exc.OtherRestClientException,
+                          waiters.wait_for_image_imported_to_stores,
+                          client, 'fake_image_id', 'fake_store')
+
+    def test_wait_for_image_imported_to_stores_timeout(self):
+        time_mock = self.patch('time.time')
+        client = mock.MagicMock()
+        client.build_timeout = 2
+        self.patch('time.time', side_effect=[0., 1., 2.])
+        time_mock.side_effect = utils.generate_timeout_series(1)
+
+        client.show_image.return_value = ({
+            'status': 'saving',
+            'stores': 'fake_store'})
+        self.assertRaises(lib_exc.TimeoutException,
+                          waiters.wait_for_image_imported_to_stores,
+                          client, 'fake_image_id', 'fake_store')
+
+    def test_wait_for_image_copied_to_stores(self):
+        self.client.show_image.return_value = ({
+            'status': 'active',
+            'os_glance_importing_to_stores': '',
+            'os_glance_failed_import': 'fake_os_glance_failed_import'})
+        start_time = int(time.time())
+        waiters.wait_for_image_copied_to_stores(
+            self.client, 'fake_image_id')
+        end_time = int(time.time())
+        # Ensure waiter returns before build_timeout
+        self.assertLess((end_time - start_time), 10)
+
+    def test_wait_for_image_copied_to_stores_timeout(self):
+        time_mock = self.patch('time.time')
+        self.patch('time.time', side_effect=[0., 1.])
+        time_mock.side_effect = utils.generate_timeout_series(1)
+
+        self.client.show_image.return_value = ({
+            'status': 'active',
+            'os_glance_importing_to_stores': 'processing',
+            'os_glance_failed_import': 'fake_os_glance_failed_import'})
+        self.assertRaises(lib_exc.TimeoutException,
+                          waiters.wait_for_image_copied_to_stores,
+                          self.client, 'fake_image_id')
+
+    def test_wait_for_image_tasks_status(self):
+        self.client.show_image_tasks.return_value = ({
+            'tasks': [{'status': 'success'}]})
+        start_time = int(time.time())
+        waiters.wait_for_image_tasks_status(
+            self.client, 'fake_image_id', 'success')
+        end_time = int(time.time())
+        # Ensure waiter returns before build_timeout
+        self.assertLess((end_time - start_time), 10)
+
+    def test_wait_for_image_tasks_status_timeout(self):
+        time_mock = self.patch('time.time')
+        self.patch('time.time', side_effect=[0., 1.])
+        time_mock.side_effect = utils.generate_timeout_series(1)
+
+        self.client.show_image_tasks.return_value = ({
+            'tasks': [
+                {'status': 'success'},
+                {'status': 'processing'}]})
+        self.assertRaises(lib_exc.TimeoutException,
+                          waiters.wait_for_image_tasks_status,
+                          self.client, 'fake_image_id', 'success')
+
 
 class TestInterfaceWaiters(base.TestCase):
 
@@ -98,39 +186,126 @@
                                          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)
+        no_event = {
+            'instanceAction': {
+                'events': []
+            }
+        }
+        one_event_without_result = {
+            'instanceAction': {
+                'events': [
+                    {
+                        'event': 'compute_detach_interface',
+                        'result': None
+                    }
+
+                ]
+            }
+        }
+        one_event_successful = {
+            'instanceAction': {
+                'events': [
+                    {
+                        'event': 'compute_detach_interface',
+                        'result': 'Success'
+                    }
+                ]
+            }
+        }
+
+        show_instance_action = mock.MagicMock(
+            # there is an extra call to return the result from the waiter
+            side_effect=[
+                no_event,
+                one_event_without_result,
+                one_event_successful,
+                one_event_successful,
+            ]
+        )
+        client = self.mock_client(show_instance_action=show_instance_action)
         self.patch('time.time', return_value=0.)
         sleep = self.patch('time.sleep')
 
         result = waiters.wait_for_interface_detach(
-            client, 'server_id', 'port_two')
+            client, mock.sentinel.server_id, mock.sentinel.port_id,
+            mock.sentinel.detach_request_id
+        )
 
-        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)
+        self.assertIs(one_event_successful['instanceAction'], result)
+        show_instance_action.assert_has_calls(
+            # there is an extra call to return the result from the waiter
+            [
+                mock.call(
+                    mock.sentinel.server_id, mock.sentinel.detach_request_id)
+            ] * 4
+        )
+        sleep.assert_has_calls([mock.call(client.build_interval)] * 2)
 
     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)
+        one_event_without_result = {
+            'instanceAction': {
+                'events': [
+                    {
+                        'event': 'compute_detach_interface',
+                        'result': None
+                    }
+
+                ]
+            }
+        }
+
+        show_instance_action = mock.MagicMock(
+            return_value=one_event_without_result)
+        client = self.mock_client(show_instance_action=show_instance_action)
         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')
+        self.assertRaises(
+            lib_exc.TimeoutException,
+            waiters.wait_for_interface_detach,
+            client, mock.sentinel.server_id, mock.sentinel.port_id,
+            mock.sentinel.detach_request_id
+        )
 
-        list_interfaces.assert_has_calls([mock.call('server_id'),
-                                          mock.call('server_id')])
+        show_instance_action.assert_has_calls(
+            [
+                mock.call(
+                    mock.sentinel.server_id, mock.sentinel.detach_request_id)
+            ] * 2
+        )
         sleep.assert_called_once_with(client.build_interval)
 
+    def test_wait_for_guest_os_boot(self):
+        get_console_output = mock.Mock(
+            side_effect=[
+                {'output': 'os not ready yet\n'},
+                {'output': 'login:\n'}
+            ])
+        client = self.mock_client(get_console_output=get_console_output)
+        self.patch('time.time', return_value=0.)
+        sleep = self.patch('time.sleep')
+
+        with mock.patch.object(waiters.LOG, "info") as log_info:
+            waiters.wait_for_guest_os_boot(client, 'server_id')
+
+        get_console_output.assert_has_calls([
+            mock.call('server_id'), mock.call('server_id')])
+        sleep.assert_called_once_with(client.build_interval)
+        log_info.assert_not_called()
+
+    def test_wait_for_guest_os_boot_timeout(self):
+        get_console_output = mock.Mock(
+            return_value={'output': 'os not ready yet\n'})
+        client = self.mock_client(get_console_output=get_console_output)
+        self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+        self.patch('time.sleep')
+
+        with mock.patch.object(waiters.LOG, "info") as log_info:
+            waiters.wait_for_guest_os_boot(client, 'server_id')
+
+        log_info.assert_called_once()
+
 
 class TestVolumeWaiters(base.TestCase):
     vol_migrating_src_host = {
@@ -304,3 +479,110 @@
                                                   uuids.attachment_id)
         # Assert that show volume is only called once before we return
         show_volume.assert_called_once_with(uuids.volume_id)
+
+    def test_wait_for_volume_attachment_remove_from_server(self):
+        volume_attached = {
+            "volumeAttachments": [{"volumeId": uuids.volume_id}]}
+        volume_not_attached = {"volumeAttachments": []}
+        mock_list_volume_attachments = mock.Mock(
+            side_effect=[volume_attached, volume_not_attached])
+        mock_client = mock.Mock(
+            spec=servers_client.ServersClient,
+            build_interval=1,
+            build_timeout=1,
+            list_volume_attachments=mock_list_volume_attachments)
+        self.patch(
+            'time.time',
+            side_effect=[0., 0.5, mock_client.build_timeout + 1.])
+        self.patch('time.sleep')
+
+        waiters.wait_for_volume_attachment_remove_from_server(
+            mock_client, uuids.server_id, uuids.volume_id)
+
+        # Assert that list_volume_attachments is called until the attachment is
+        # removed.
+        mock_list_volume_attachments.assert_has_calls([
+            mock.call(uuids.server_id),
+            mock.call(uuids.server_id)])
+
+    def test_wait_for_volume_attachment_remove_from_server_timeout(self):
+        volume_attached = {
+            "volumeAttachments": [{"volumeId": uuids.volume_id}]}
+        mock_list_volume_attachments = mock.Mock(
+            side_effect=[volume_attached, volume_attached])
+        mock_get_console_output = mock.Mock(
+            return_value={'output': 'output'})
+        mock_client = mock.Mock(
+            spec=servers_client.ServersClient,
+            build_interval=1,
+            build_timeout=1,
+            list_volume_attachments=mock_list_volume_attachments,
+            get_console_output=mock_get_console_output)
+        self.patch(
+            'time.time',
+            side_effect=[0., 0.5, mock_client.build_timeout + 1.])
+        self.patch('time.sleep')
+
+        self.assertRaises(
+            lib_exc.TimeoutException,
+            waiters.wait_for_volume_attachment_remove_from_server,
+            mock_client, uuids.server_id, uuids.volume_id)
+
+        # Assert that list_volume_attachments is called until the attachment is
+        # removed.
+        mock_list_volume_attachments.assert_has_calls([
+            mock.call(uuids.server_id),
+            mock.call(uuids.server_id)])
+
+        # Assert that we fetch console output
+        mock_get_console_output.assert_called_once_with(uuids.server_id)
+
+    def test_wait_for_volume_attachment_remove_from_server_not_found(self):
+        mock_list_volume_attachments = mock.Mock(
+            side_effect=lib_exc.NotFound)
+        mock_client = mock.Mock(
+            spec=servers_client.ServersClient,
+            list_volume_attachments=mock_list_volume_attachments)
+
+        # Assert that nothing is raised when lib_exc_NotFound is raised
+        # by the client call to list_volume_attachments
+        waiters.wait_for_volume_attachment_remove_from_server(
+            mock_client, mock.sentinel.server_id, mock.sentinel.volume_id)
+
+        # Assert that list_volume_attachments was actually called
+        mock_list_volume_attachments.assert_called_once_with(
+            mock.sentinel.server_id)
+
+
+class TestServerFloatingIPWaiters(base.TestCase):
+
+    def test_wait_for_server_floating_ip_associate_timeout(self):
+        mock_server = {'server': {'id': 'fake_uuid', 'addresses': {}}}
+        mock_client = mock.Mock(
+            spec=servers_client.ServersClient,
+            build_timeout=1, build_interval=1,
+            show_server=lambda id: mock_server)
+
+        fake_server = {'id': 'fake-uuid'}
+        fake_fip = {'floating_ip_address': 'fake_address'}
+        self.assertRaises(
+            lib_exc.TimeoutException,
+            waiters.wait_for_server_floating_ip, mock_client, fake_server,
+            fake_fip)
+
+    def test_wait_for_server_floating_ip_disassociate_timeout(self):
+        mock_addresses = {'shared': [{'OS-EXT-IPS:type': 'floating',
+                                      'addr': 'fake_address'}]}
+        mock_server = {'server': {'id': 'fake_uuid',
+                                  'addresses': mock_addresses}}
+        mock_client = mock.Mock(
+            spec=servers_client.ServersClient,
+            build_timeout=1, build_interval=1,
+            show_server=lambda id: mock_server)
+
+        fake_server = {'id': 'fake-uuid'}
+        fake_fip = {'floating_ip_address': 'fake_address'}
+        self.assertRaises(
+            lib_exc.TimeoutException,
+            waiters.wait_for_server_floating_ip, mock_client, fake_server,
+            fake_fip, wait_for_disassociate=True)
diff --git a/tempest/tests/lib/cmd/test_check_uuid.py b/tempest/tests/lib/cmd/test_check_uuid.py
index 428e047..edfb2c8 100644
--- a/tempest/tests/lib/cmd/test_check_uuid.py
+++ b/tempest/tests/lib/cmd/test_check_uuid.py
@@ -13,12 +13,11 @@
 import ast
 import importlib
 import os
+import shutil
 import sys
 import tempfile
 from unittest import mock
 
-import fixtures
-
 from tempest.lib.cmd import check_uuid
 from tempest.tests import base
 
@@ -29,36 +28,33 @@
            "    def test_tests(self):\n" \
            "        pass"
 
-    def create_tests_file(self, directory):
-        with open(directory + "/__init__.py", "w"):
-            pass
+    def setUp(self):
+        super(TestCLInterface, self).setUp()
+        self.directory = tempfile.mkdtemp(prefix='check-uuid', dir=".")
+        self.addCleanup(shutil.rmtree, self.directory, ignore_errors=True)
 
-        tests_file = directory + "/tests.py"
-        with open(tests_file, "w") as fake_file:
+        init_file = open(self.directory + "/__init__.py", "w")
+        init_file.close()
+
+        self.tests_file = self.directory + "/tests.py"
+        with open(self.tests_file, "w") as fake_file:
             fake_file.write(TestCLInterface.CODE)
-
-        return tests_file
+            fake_file.close()
 
     def test_fix_argument_no(self):
-        temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
-        tests_file = self.create_tests_file(temp_dir.path)
-
         sys.argv = [sys.argv[0]] + ["--package",
-                                    os.path.relpath(temp_dir.path)]
+                                    os.path.relpath(self.directory)]
 
         self.assertRaises(SystemExit, check_uuid.run)
-        with open(tests_file, "r") as f:
+        with open(self.tests_file, "r") as f:
             self.assertTrue(TestCLInterface.CODE == f.read())
 
     def test_fix_argument_yes(self):
-        temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
-        tests_file = self.create_tests_file(temp_dir.path)
 
         sys.argv = [sys.argv[0]] + ["--fix", "--package",
-                                    os.path.relpath(temp_dir.path)]
-
+                                    os.path.relpath(self.directory)]
         check_uuid.run()
-        with open(tests_file, "r") as f:
+        with open(self.tests_file, "r") as f:
             self.assertTrue(TestCLInterface.CODE != f.read())
 
 
diff --git a/tempest/tests/lib/common/test_api_version_utils.py b/tempest/tests/lib/common/test_api_version_utils.py
index b99e8d4..8d5de09 100644
--- a/tempest/tests/lib/common/test_api_version_utils.py
+++ b/tempest/tests/lib/common/test_api_version_utils.py
@@ -12,7 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
 import testtools
 
 from tempest.lib.common import api_version_utils
@@ -31,7 +30,7 @@
                                                            cfg_max_version)
         except testtools.TestCase.skipException as e:
             if not expected_skip:
-                raise testtools.TestCase.failureException(six.text_type(e))
+                raise testtools.TestCase.failureException(str(e))
 
     def test_version_min_in_range(self):
         self._test_version('2.2', '2.10', '2.1', '2.7')
diff --git a/tempest/tests/lib/common/test_cred_client.py b/tempest/tests/lib/common/test_cred_client.py
index 860a465..7ea660b 100644
--- a/tempest/tests/lib/common/test_cred_client.py
+++ b/tempest/tests/lib/common/test_cred_client.py
@@ -43,6 +43,14 @@
         self.projects_client.delete_tenant.assert_called_once_with(
             'fake_id')
 
+    def test_get_credentials(self):
+        ret = self.creds_client.get_credentials(
+            {'name': 'some_user', 'id': 'fake_id'},
+            {'name': 'some_project', 'id': 'fake_id'},
+            'password123')
+        self.assertEqual(ret.username, 'some_user')
+        self.assertEqual(ret.project_name, 'some_project')
+
 
 class TestCredClientV3(base.TestCase):
     def setUp(self):
@@ -53,7 +61,7 @@
         self.roles_client = mock.MagicMock()
         self.domains_client = mock.MagicMock()
         self.domains_client.list_domains.return_value = {
-            'domains': [{'id': 'fake_domain_id'}]
+            'domains': [{'id': 'fake_domain_id', 'name': 'some_domain'}]
         }
         self.creds_client = cred_client.V3CredsClient(self.identity_client,
                                                       self.projects_client,
@@ -75,3 +83,49 @@
         self.creds_client.delete_project('fake_id')
         self.projects_client.delete_project.assert_called_once_with(
             'fake_id')
+
+    def test_get_credentials(self):
+        ret = self.creds_client.get_credentials(
+            {'name': 'some_user', 'id': 'fake_id'},
+            {'name': 'some_project', 'id': 'fake_id'},
+            'password123')
+        self.assertEqual(ret.username, 'some_user')
+        self.assertEqual(ret.project_name, 'some_project')
+        self.assertIsNone(ret.system)
+        self.assertEqual(ret.domain_name, 'some_domain')
+        ret = self.creds_client.get_credentials(
+            {'name': 'some_user', 'id': 'fake_id'},
+            None,
+            'password123',
+            domain={'name': 'another_domain', 'id': 'another_id'})
+        self.assertEqual(ret.username, 'some_user')
+        self.assertIsNone(ret.project_name)
+        self.assertIsNone(ret.system)
+        self.assertEqual(ret.domain_name, 'another_domain')
+        ret = self.creds_client.get_credentials(
+            {'name': 'some_user', 'id': 'fake_id'},
+            None,
+            'password123',
+            system={'system': 'all'})
+        self.assertEqual(ret.username, 'some_user')
+        self.assertIsNone(ret.project_name)
+        self.assertEqual(ret.system, {'system': 'all'})
+        self.assertEqual(ret.domain_name, 'some_domain')
+
+    def test_create_user(self):
+        self.users_client.create_user.return_value = {
+            'user': 'a_user'
+        }
+        fake_project = {
+            'id': 'fake_project_id',
+        }
+        res = self.creds_client.create_user('fake_username',
+                                            'fake_password',
+                                            fake_project,
+                                            'fake_email')
+        self.assertEqual('a_user', res)
+        self.users_client.create_user.assert_called_once_with(
+            name='fake_username', password='fake_password',
+            project_id=fake_project['id'],
+            email='fake_email',
+            domain_id='fake_domain_id')
diff --git a/tempest/tests/lib/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
index e9073cc..b4b1b91 100644
--- a/tempest/tests/lib/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -214,6 +214,56 @@
         self.assertEqual(admin_creds.user_id, '1234')
 
     @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_project_alt_admin_creds(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_roles('1234', 'admin')
+        self._mock_user_create('1234', 'fake_alt_admin_user')
+        self._mock_tenant_create('1234', 'fake_alt_admin')
+
+        user_mock = mock.patch.object(self.roles_client.RolesClient,
+                                      'create_user_role_on_project')
+        user_mock.start()
+        self.addCleanup(user_mock.stop)
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_project') as user_mock:
+            alt_admin_creds = creds.get_project_alt_admin_creds()
+        user_mock.assert_has_calls([
+            mock.call('1234', '1234', '1234')])
+        self.assertEqual(alt_admin_creds.username, 'fake_alt_admin_user')
+        self.assertEqual(alt_admin_creds.project_name, 'fake_alt_admin')
+        # Verify IDs
+        self.assertEqual(alt_admin_creds.project_id, '1234')
+        self.assertEqual(alt_admin_creds.user_id, '1234')
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_project_alt_member_creds(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_assign_user_role()
+        self._mock_list_role()
+        self._mock_tenant_create('1234', 'fake_alt_member')
+        self._mock_user_create('1234', 'fake_alt_user')
+        alt_member_creds = creds.get_project_alt_member_creds()
+        self.assertEqual(alt_member_creds.username, 'fake_alt_user')
+        self.assertEqual(alt_member_creds.project_name, 'fake_alt_member')
+        # Verify IDs
+        self.assertEqual(alt_member_creds.project_id, '1234')
+        self.assertEqual(alt_member_creds.user_id, '1234')
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_project_alt_reader_creds(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_assign_user_role()
+        self._mock_list_roles('1234', 'reader')
+        self._mock_tenant_create('1234', 'fake_alt_reader')
+        self._mock_user_create('1234', 'fake_alt_user')
+        alt_reader_creds = creds.get_project_alt_reader_creds()
+        self.assertEqual(alt_reader_creds.username, 'fake_alt_user')
+        self.assertEqual(alt_reader_creds.project_name, 'fake_alt_reader')
+        # Verify IDs
+        self.assertEqual(alt_reader_creds.project_id, '1234')
+        self.assertEqual(alt_reader_creds.user_id, '1234')
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
     def test_role_creds(self, MockRestClient):
         creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
         self._mock_list_2_roles()
@@ -242,6 +292,100 @@
         self.assertEqual(role_creds.user_id, '1234')
 
     @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_role_creds_with_project_scope(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+        self._mock_tenant_create('1234', 'fake_role_project')
+
+        user_mock = mock.patch.object(self.roles_client.RolesClient,
+                                      'create_user_role_on_project')
+        user_mock.start()
+        self.addCleanup(user_mock.stop)
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_project') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='project')
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        args = map(lambda x: x[1], calls)
+        args = list(args)
+        self.assertIn(('1234', '1234', '1234'), args)
+        self.assertIn(('1234', '1234', '12345'), args)
+        self.assertEqual(role_creds.username, 'fake_role_user')
+        self.assertEqual(role_creds.project_name, 'fake_role_project')
+        # Verify IDs
+        self.assertEqual(role_creds.project_id, '1234')
+        self.assertEqual(role_creds.user_id, '1234')
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def _test_get_same_role_creds_with_project_scope(self, MockRestClient,
+                                                     scope=None):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+        self._mock_tenant_create('1234', 'fake_role_project')
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_project') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope=scope)
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+
+        # Fetch the same creds again
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_project') as user_mock1:
+            role_creds_new = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope=scope)
+        calls = user_mock1.mock_calls
+        # Assert that previously created creds are return and no call to
+        # role creation.
+        self.assertEqual(len(calls), 0)
+        # Check if previously created creds are returned.
+        self.assertEqual(role_creds, role_creds_new)
+
+    def test_get_same_role_creds_with_project_scope(self):
+        self._test_get_same_role_creds_with_project_scope(scope='project')
+
+    def test_get_same_role_creds_with_default_scope(self):
+        self._test_get_same_role_creds_with_project_scope()
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def _test_get_different_role_creds_with_project_scope(
+            self, MockRestClient, scope=None):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+        self._mock_tenant_create('1234', 'fake_role_project')
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_project') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope=scope)
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        # Fetch the creds with one role different
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_project') as user_mock1:
+            role_creds_new = creds.get_creds_by_roles(
+                roles=['role1'], scope=scope)
+        calls = user_mock1.mock_calls
+        # Because one role is different, assert that the role creation
+        # is called with the 1 specified roles
+        self.assertEqual(len(calls), 1)
+        # Check new creds is created for new roles.
+        self.assertNotEqual(role_creds, role_creds_new)
+
+    def test_get_different_role_creds_with_project_scope(self):
+        self._test_get_different_role_creds_with_project_scope(
+            scope='project')
+
+    def test_get_different_role_creds_with_default_scope(self):
+        self._test_get_different_role_creds_with_project_scope()
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
     def test_all_cred_cleanup(self, MockRestClient):
         creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
         self._mock_assign_user_role()
@@ -658,6 +802,232 @@
         return project_fix
 
     @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_role_creds_with_system_scope(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_system') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='system')
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        args = map(lambda x: x[1], calls)
+        args = list(args)
+        self.assertIn(('1234', '1234'), args)
+        self.assertIn(('1234', '12345'), args)
+        self.assertEqual(role_creds.username, 'fake_role_user')
+        self.assertEqual(role_creds.user_id, '1234')
+        # Verify system scope
+        self.assertEqual(role_creds.system, 'all')
+        # Verify domain is default
+        self.assertEqual(role_creds.domain_id, 'default')
+        self.assertEqual(role_creds.domain_name, 'Default')
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_get_same_role_creds_with_system_scope(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_system') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='system')
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+
+        # Fetch the same creds again
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_system') as user_mock1:
+            role_creds_new = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='system')
+        calls = user_mock1.mock_calls
+        # Assert that previously created creds are return and no call to
+        # role creation.
+        self.assertEqual(len(calls), 0)
+        # Verify system scope
+        self.assertEqual(role_creds_new.system, 'all')
+        # Check if previously created creds are returned.
+        self.assertEqual(role_creds, role_creds_new)
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_get_different_role_creds_with_system_scope(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_system') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='system')
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        # Verify system scope
+        self.assertEqual(role_creds.system, 'all')
+        # Fetch the creds with one role different
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_system') as user_mock1:
+            role_creds_new = creds.get_creds_by_roles(
+                roles=['role1'], scope='system')
+        calls = user_mock1.mock_calls
+        # Because one role is different, assert that the role creation
+        # is called with the 1 specified roles
+        self.assertEqual(len(calls), 1)
+        # Verify Scope
+        self.assertEqual(role_creds_new.system, 'all')
+        # Check new creds is created for new roles.
+        self.assertNotEqual(role_creds, role_creds_new)
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_role_creds_with_domain_scope(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+
+        domain = {
+            "id": '12',
+            "enabled": True,
+            "name": "TestDomain"
+        }
+
+        self.useFixture(fixtures.MockPatch(
+            'tempest.lib.common.cred_client.V3CredsClient.create_domain',
+            return_value=domain))
+
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_domain') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='domain')
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        args = map(lambda x: x[1], calls)
+        args = list(args)
+        self.assertIn((domain['id'], '1234', '1234'), args)
+        self.assertIn((domain['id'], '1234', '12345'), args)
+        self.assertEqual(role_creds.username, 'fake_role_user')
+        self.assertEqual(role_creds.user_id, '1234')
+        # Verify creds are under new created domain
+        self.assertEqual(role_creds.domain_id, domain['id'])
+        self.assertEqual(role_creds.domain_name, domain['name'])
+        # Verify that Scope is None
+        self.assertIsNone(role_creds.system)
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_get_same_role_creds_with_domain_scope(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+
+        domain = {
+            "id": '12',
+            "enabled": True,
+            "name": "TestDomain"
+        }
+
+        self.useFixture(fixtures.MockPatch(
+            'tempest.lib.common.cred_client.V3CredsClient.create_domain',
+            return_value=domain))
+
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_domain') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='domain')
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        self.assertEqual(role_creds.user_id, '1234')
+        # Verify Scope
+        self.assertIsNone(role_creds.system)
+        # Fetch the same creds again
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_domain') as user_mock1:
+            role_creds_new = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='domain')
+        calls = user_mock1.mock_calls
+        # Assert that previously created creds are return and no call to
+        # role creation.
+        self.assertEqual(len(calls), 0)
+        # Verify Scope
+        self.assertIsNone(role_creds_new.system)
+        # Check if previously created creds are returned.
+        self.assertEqual(role_creds, role_creds_new)
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_get_different_role_creds_with_domain_scope(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+
+        domain = {
+            "id": '12',
+            "enabled": True,
+            "name": "TestDomain"
+        }
+
+        self.useFixture(fixtures.MockPatch(
+            'tempest.lib.common.cred_client.V3CredsClient.create_domain',
+            return_value=domain))
+
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_domain') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='domain')
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        self.assertEqual(role_creds.user_id, '1234')
+        # Verify Scope
+        self.assertIsNone(role_creds.system)
+        # Fetch the same creds again
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_domain') as user_mock1:
+            role_creds_new = creds.get_creds_by_roles(
+                roles=['role1'], scope='domain')
+        calls = user_mock1.mock_calls
+        # Because one role is different, assert that the role creation
+        # is called with the 1 specified roles
+        self.assertEqual(len(calls), 1)
+        # Verify Scope
+        self.assertIsNone(role_creds_new.system)
+        # Check new creds is created for new roles.
+        self.assertNotEqual(role_creds, role_creds_new)
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
+    def test_get_role_creds_with_different_scope(self, MockRestClient):
+        creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+        self._mock_list_2_roles()
+        self._mock_user_create('1234', 'fake_role_user')
+        self._mock_tenant_create('1234', 'fake_role_project')
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_system') as user_mock:
+            role_creds = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='system')
+        calls = user_mock.mock_calls
+        # Assert that the role creation is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        # Verify Scope
+        self.assertEqual(role_creds.system, 'all')
+
+        # Fetch the same role creds but with different scope
+        with mock.patch.object(self.roles_client.RolesClient,
+                               'create_user_role_on_project') as user_mock1:
+            role_creds_new = creds.get_creds_by_roles(
+                roles=['role1', 'role2'], scope='project')
+        calls = user_mock1.mock_calls
+        # Because scope is different, assert that the role creation
+        # is called with the 2 specified roles
+        self.assertEqual(len(calls), 2)
+        # Verify Scope
+        self.assertIsNone(role_creds_new.system)
+        # Check that created creds are different
+        self.assertNotEqual(role_creds, role_creds_new)
+
+    @mock.patch('tempest.lib.common.rest_client.RestClient')
     def test_member_role_creation_with_duplicate(self, rest_client_mock):
         creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
         creds.creds_client = mock.MagicMock()
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index 579363e..f2131dc 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -12,17 +12,16 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import hashlib
 import os
 import shutil
 from unittest import mock
 
-import six
 import testtools
 
 import fixtures
 from oslo_concurrency.fixture import lockutils as lockutils_fixtures
 from oslo_config import cfg
+from oslo_utils.secretutils import md5
 
 from tempest import config
 from tempest.lib import auth
@@ -106,10 +105,10 @@
         hash_fields = (
             preprov_creds.PreProvisionedCredentialProvider.HASH_CRED_FIELDS)
         for account in accounts_list:
-            hash = hashlib.md5()
+            hash = md5(usedforsecurity=False)
             account_for_hash = dict((k, v) for (k, v) in account.items()
                                     if k in hash_fields)
-            hash.update(six.text_type(account_for_hash).encode('utf-8'))
+            hash.update(str(account_for_hash).encode('utf-8'))
             temp_hash = hash.hexdigest()
             hash_list.append(temp_hash)
         return hash_list
@@ -145,8 +144,7 @@
         # Emulate the lock existing on the filesystem
         self.useFixture(fixtures.MockPatch(
             'os.path.isfile', return_value=True))
-        with mock.patch('six.moves.builtins.open', mock.mock_open(),
-                        create=True):
+        with mock.patch('builtins.open', mock.mock_open(), create=True):
             test_account_class = (
                 preprov_creds.PreProvisionedCredentialProvider(
                     **self.fixed_params))
@@ -158,8 +156,7 @@
         # Emulate the lock not existing on the filesystem
         self.useFixture(fixtures.MockPatch(
             'os.path.isfile', return_value=False))
-        with mock.patch('six.moves.builtins.open', mock.mock_open(),
-                        create=True):
+        with mock.patch('builtins.open', mock.mock_open(), create=True):
             test_account_class = (
                 preprov_creds.PreProvisionedCredentialProvider(
                     **self.fixed_params))
@@ -178,7 +175,7 @@
             'os.path.isfile', return_value=False))
         test_account_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
-        with mock.patch('six.moves.builtins.open', mock.mock_open(),
+        with mock.patch('builtins.open', mock.mock_open(),
                         create=True) as open_mock:
             test_account_class._get_free_hash(hash_list)
             lock_path = os.path.join(self.fixed_params['accounts_lock_dir'],
@@ -197,8 +194,7 @@
             'os.path.isfile', return_value=True))
         test_account_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
-        with mock.patch('six.moves.builtins.open', mock.mock_open(),
-                        create=True):
+        with mock.patch('builtins.open', mock.mock_open(), create=True):
             self.assertRaises(lib_exc.InvalidCredentials,
                               test_account_class._get_free_hash, hash_list)
 
@@ -218,7 +214,7 @@
             return True
 
         self.patchobject(os.path, 'isfile', _fake_is_file)
-        with mock.patch('six.moves.builtins.open', mock.mock_open(),
+        with mock.patch('builtins.open', mock.mock_open(),
                         create=True) as open_mock:
             test_account_class._get_free_hash(hash_list)
             lock_path = os.path.join(self.fixed_params['accounts_lock_dir'],
diff --git a/tempest/tests/lib/common/test_rest_client.py b/tempest/tests/lib/common/test_rest_client.py
index b861582..1dea5f5 100644
--- a/tempest/tests/lib/common/test_rest_client.py
+++ b/tempest/tests/lib/common/test_rest_client.py
@@ -17,7 +17,6 @@
 import fixtures
 import jsonschema
 from oslo_serialization import jsonutils as json
-import six
 
 from tempest.lib.common import http
 from tempest.lib.common import rest_client
@@ -93,7 +92,7 @@
 class TestRestClientHeadersJSON(TestRestClientHTTPMethods):
 
     def _verify_headers(self, resp):
-        resp = dict((k.lower(), v) for k, v in six.iteritems(resp))
+        resp = dict((k.lower(), v) for k, v in resp.items())
         self.assertEqual(self.header_value, resp['accept'])
         self.assertEqual(self.header_value, resp['content-type'])
 
@@ -145,11 +144,11 @@
                                                 extra_headers=True,
                                                 headers=self.headers)
 
-        self.assertDictContainsSubset(
+        self.assertLessEqual(
             {'X-Configuration-Session': 'session_id',
              'Content-Type': 'application/json',
-             'Accept': 'application/json'},
-            return_dict['headers']
+             'Accept': 'application/json'}.items(),
+            return_dict['headers'].items()
         )
 
     def test_get_update_headers(self):
@@ -157,11 +156,11 @@
                                                extra_headers=True,
                                                headers=self.headers)
 
-        self.assertDictContainsSubset(
+        self.assertLessEqual(
             {'X-Configuration-Session': 'session_id',
              'Content-Type': 'application/json',
-             'Accept': 'application/json'},
-            return_dict['headers']
+             'Accept': 'application/json'}.items(),
+            return_dict['headers'].items()
         )
 
     def test_delete_update_headers(self):
@@ -169,11 +168,11 @@
                                                   extra_headers=True,
                                                   headers=self.headers)
 
-        self.assertDictContainsSubset(
+        self.assertLessEqual(
             {'X-Configuration-Session': 'session_id',
              'Content-Type': 'application/json',
-             'Accept': 'application/json'},
-            return_dict['headers']
+             'Accept': 'application/json'}.items(),
+            return_dict['headers'].items()
         )
 
     def test_patch_update_headers(self):
@@ -181,11 +180,11 @@
                                                  extra_headers=True,
                                                  headers=self.headers)
 
-        self.assertDictContainsSubset(
+        self.assertLessEqual(
             {'X-Configuration-Session': 'session_id',
              'Content-Type': 'application/json',
-             'Accept': 'application/json'},
-            return_dict['headers']
+             'Accept': 'application/json'}.items(),
+            return_dict['headers'].items()
         )
 
     def test_put_update_headers(self):
@@ -193,11 +192,11 @@
                                                extra_headers=True,
                                                headers=self.headers)
 
-        self.assertDictContainsSubset(
+        self.assertLessEqual(
             {'X-Configuration-Session': 'session_id',
              'Content-Type': 'application/json',
-             'Accept': 'application/json'},
-            return_dict['headers']
+             'Accept': 'application/json'}.items(),
+            return_dict['headers'].items()
         )
 
     def test_head_update_headers(self):
@@ -208,11 +207,11 @@
                                                 extra_headers=True,
                                                 headers=self.headers)
 
-        self.assertDictContainsSubset(
+        self.assertLessEqual(
             {'X-Configuration-Session': 'session_id',
              'Content-Type': 'application/json',
-             'Accept': 'application/json'},
-            return_dict['headers']
+             'Accept': 'application/json'}.items(),
+            return_dict['headers'].items()
         )
 
     def test_copy_update_headers(self):
@@ -220,11 +219,11 @@
                                                 extra_headers=True,
                                                 headers=self.headers)
 
-        self.assertDictContainsSubset(
+        self.assertLessEqual(
             {'X-Configuration-Session': 'session_id',
              'Content-Type': 'application/json',
-             'Accept': 'application/json'},
-            return_dict['headers']
+             'Accept': 'application/json'}.items(),
+            return_dict['headers'].items()
         )
 
 
@@ -526,9 +525,11 @@
                           self.rest_client.wait_for_resource_deletion,
                           '1234')
 
-        # time.time() should be called twice, first to start the timer
-        # and then to compute the timedelta
-        self.assertEqual(2, time_mock.call_count)
+        # time.time() should be called 4 times,
+        # 1. Start timer
+        # 2. End timer
+        # 3 & 4. To generate timeout exception message
+        self.assertEqual(4, time_mock.call_count)
 
     def test_wait_for_deletion_with_unimplemented_deleted_method(self):
         self.rest_client.is_resource_deleted = self.original_deleted_method
diff --git a/tempest/tests/lib/services/compute/test_aggregates_client.py b/tempest/tests/lib/services/compute/test_aggregates_client.py
index 674d92a..1448a4d 100644
--- a/tempest/tests/lib/services/compute/test_aggregates_client.py
+++ b/tempest/tests/lib/services/compute/test_aggregates_client.py
@@ -37,7 +37,7 @@
     FAKE_CREATE_AGGREGATE = {
         "aggregate":
         {
-            "name": u'\xf4',
+            "name": '\xf4',
             "availability_zone": None,
             "deleted": False,
             "created_at": "2015-07-21T04:11:18.000000",
@@ -50,7 +50,7 @@
     FAKE_UPDATE_AGGREGATE = {
         "aggregate":
         {
-            "name": u'\xe9',
+            "name": '\xe9',
             "availability_zone": None,
             "deleted": False,
             "created_at": "2015-07-16T03:07:32.000000",
@@ -74,7 +74,7 @@
         "metadata": {
             "availability_zone": "nova"
         },
-        "name": u'\xe9',
+        "name": '\xe9',
         "updated_at": None
     }
 
diff --git a/tempest/tests/lib/services/compute/test_assisted_volume_snapshots_client.py b/tempest/tests/lib/services/compute/test_assisted_volume_snapshots_client.py
new file mode 100644
index 0000000..79855ea
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_assisted_volume_snapshots_client.py
@@ -0,0 +1,53 @@
+# Copyright 2017 AT&T.
+#
+#    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.compute import assisted_volume_snapshots_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestVolumesClient(base.BaseServiceTest):
+
+    FAKE_SNAPSHOT = {
+        "id": "bf7b810c-70df-4c64-88a7-8588f7a6739c",
+        "volumeId": "59f17c4f-66d4-4271-be40-f200523423a9"
+    }
+
+    def setUp(self):
+        super(TestVolumesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = assisted_volume_snapshots_client.\
+            AssistedVolumeSnapshotsClient(fake_auth, 'compute', 'regionOne')
+
+    def _test_create_assisted_volume_snapshot(self, bytes_body=False):
+        kwargs = {"type": "qcow2", "new_file": "fake_name"}
+        self.check_service_client_function(
+            self.client.create_assisted_volume_snapshot,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {"snapshot": self.FAKE_SNAPSHOT},
+            bytes_body, status=200, volume_id=self.FAKE_SNAPSHOT['volumeId'],
+            snapshot_id=self.FAKE_SNAPSHOT['id'], **kwargs)
+
+    def test_create_assisted_volume_snapshot_with_str_body(self):
+        self._test_create_assisted_volume_snapshot()
+
+    def test_create_assisted_volume_snapshot_with_byte_body(self):
+        self._test_create_assisted_volume_snapshot(bytes_body=True)
+
+    def test_delete_assisted_volume_snapshot(self):
+        self.check_service_client_function(
+            self.client.delete_assisted_volume_snapshot,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {}, status=204, volume_id=self.FAKE_SNAPSHOT['volumeId'],
+            snapshot_id=self.FAKE_SNAPSHOT['id'])
diff --git a/tempest/tests/lib/services/compute/test_availability_zone_client.py b/tempest/tests/lib/services/compute/test_availability_zone_client.py
index 6608592..aef98fc 100644
--- a/tempest/tests/lib/services/compute/test_availability_zone_client.py
+++ b/tempest/tests/lib/services/compute/test_availability_zone_client.py
@@ -27,7 +27,7 @@
                     "available": True
                 },
                 "hosts": None,
-                "zoneName": u'\xf4'
+                "zoneName": '\xf4'
             }
         ]
     }
diff --git a/tempest/tests/lib/services/compute/test_extensions_client.py b/tempest/tests/lib/services/compute/test_extensions_client.py
index d7e217e..053b84c 100644
--- a/tempest/tests/lib/services/compute/test_extensions_client.py
+++ b/tempest/tests/lib/services/compute/test_extensions_client.py
@@ -27,7 +27,7 @@
             "namespace":
             "http://docs.openstack.org/compute/ext/multinic/api/v1.1",
             "alias": "NMN",
-            "description": u'\u2740(*\xb4\u25e1`*)\u2740'
+            "description": '\u2740(*\xb4\u25e1`*)\u2740'
         }
     }
 
diff --git a/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py b/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
index b0c00f0..6278df4 100644
--- a/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
+++ b/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
@@ -22,8 +22,8 @@
     FAKE_FLOATING_IP_POOLS = {
         "floating_ip_pools":
         [
-            {"name": u'\u3042'},
-            {"name": u'\u3044'}
+            {"name": '\u3042'},
+            {"name": '\u3044'}
         ]
     }
 
diff --git a/tempest/tests/lib/services/compute/test_keypairs_client.py b/tempest/tests/lib/services/compute/test_keypairs_client.py
index ed3b9dd..214d0e5 100644
--- a/tempest/tests/lib/services/compute/test_keypairs_client.py
+++ b/tempest/tests/lib/services/compute/test_keypairs_client.py
@@ -23,7 +23,7 @@
 
     FAKE_KEYPAIR = {"keypair": {
         "public_key": "ssh-rsa foo Generated-by-Nova",
-        "name": u'\u2740(*\xb4\u25e1`*)\u2740',
+        "name": '\u2740(*\xb4\u25e1`*)\u2740',
         "user_id": "525d55f98980415ba98e634972fa4a10",
         "fingerprint": "76:24:66:49:d7:ca:6e:5c:77:ea:8e:bb:9c:15:5f:98"
         }}
diff --git a/tempest/tests/lib/services/compute/test_networks_client.py b/tempest/tests/lib/services/compute/test_networks_client.py
index 1908b57..5a88671 100644
--- a/tempest/tests/lib/services/compute/test_networks_client.py
+++ b/tempest/tests/lib/services/compute/test_networks_client.py
@@ -31,7 +31,7 @@
         "deleted_at": None,
         "gateway": None,
         "rxtx_base": None,
-        "label": u'30d7',
+        "label": '30d7',
         "priority": None,
         "project_id": None,
         "vpn_private_address": None,
diff --git a/tempest/tests/lib/services/compute/test_quota_classes_client.py b/tempest/tests/lib/services/compute/test_quota_classes_client.py
index 22d8b91..6921365 100644
--- a/tempest/tests/lib/services/compute/test_quota_classes_client.py
+++ b/tempest/tests/lib/services/compute/test_quota_classes_client.py
@@ -29,7 +29,7 @@
         "ram": 51200,
         "floating_ips": 10,
         "key_pairs": 100,
-        "id": u'\u2740(*\xb4\u25e1`*)\u2740',
+        "id": '\u2740(*\xb4\u25e1`*)\u2740',
         "instances": 10,
         "security_group_rules": 20,
         "security_groups": 10,
diff --git a/tempest/tests/lib/services/compute/test_tenant_networks_client.py b/tempest/tests/lib/services/compute/test_tenant_networks_client.py
index f71aad9..a042a1a 100644
--- a/tempest/tests/lib/services/compute/test_tenant_networks_client.py
+++ b/tempest/tests/lib/services/compute/test_tenant_networks_client.py
@@ -22,7 +22,7 @@
     FAKE_NETWORK = {
         "cidr": "None",
         "id": "c2329eb4-cc8e-4439-ac4c-932369309e36",
-        "label": u'\u30d7'
+        "label": '\u30d7'
         }
 
     FAKE_NETWORKS = [FAKE_NETWORK]
diff --git a/tempest/tests/lib/services/identity/v3/test_identity_providers_client.py b/tempest/tests/lib/services/identity/v3/test_identity_providers_client.py
new file mode 100644
index 0000000..964c51b
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_identity_providers_client.py
@@ -0,0 +1,142 @@
+# Copyright 2020 Samsung Electronics 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.identity.v3 import identity_providers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestIdentityProvidersClient(base.BaseServiceTest):
+    FAKE_IDENTITY_PROVIDERS_INFO = {
+        "identity_providers": [
+            {
+                "domain_id": "FAKE_DOMAIN_ID",
+                "description": "FAKE IDENTITY PROVIDER",
+                "remote_ids": ["fake_id_1", "fake_id_2"],
+                "enabled": True,
+                "id": "FAKE_ID",
+                "links": {
+                    "protocols": "http://example.com/identity/v3/" +
+                                 "OS-FEDERATION/identity_providers/" +
+                                 "FAKE_ID/protocols",
+                    "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                            "identity_providers/FAKE_ID"
+                }
+            }
+        ],
+        "links": {
+            "next": None,
+            "previous": None,
+            "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                    "identity_providers"
+        }
+    }
+
+    FAKE_IDENTITY_PROVIDER_INFO = {
+        "identity_provider": {
+            "authorization_ttl": None,
+            "domain_id": "FAKE_DOMAIN_ID",
+            "description": "FAKE IDENTITY PROVIDER",
+            "remote_ids": ["fake_id_1", "fake_id_2"],
+            "enabled": True,
+            "id": "ACME",
+            "links": {
+                "protocols": "http://example.com/identity/v3/OS-FEDERATION/" +
+                             "identity_providers/FAKE_ID/protocols",
+                "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                        "identity_providers/FAKE_ID"
+            }
+        }
+    }
+
+    def setUp(self):
+        super(TestIdentityProvidersClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = identity_providers_client.IdentityProvidersClient(
+            fake_auth, 'identity', 'regionOne')
+
+    def _test_register_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.register_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_IDENTITY_PROVIDER_INFO,
+            bytes_body,
+            identity_provider_id="FAKE_ID",
+            status=201)
+
+    def _test_list_identity_providers(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_identity_providers,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_IDENTITY_PROVIDERS_INFO,
+            bytes_body,
+            status=200)
+
+    def _test_get_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.get_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_IDENTITY_PROVIDER_INFO,
+            bytes_body,
+            identity_provider_id="FAKE_ID",
+            status=200)
+
+    def _test_delete_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.delete_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            bytes_body,
+            identity_provider_id="FAKE_ID",
+            status=204)
+
+    def _test_update_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_IDENTITY_PROVIDER_INFO,
+            bytes_body,
+            identity_provider_id="FAKE_ID",
+            status=200)
+
+    def test_register_identity_provider_with_str_body(self):
+        self._test_register_identity_provider()
+
+    def test_register_identity_provider_with_bytes_body(self):
+        self._test_register_identity_provider(bytes_body=True)
+
+    def test_list_identity_providers_with_str_body(self):
+        self._test_list_identity_providers()
+
+    def test_list_identity_providers_with_bytes_body(self):
+        self._test_list_identity_providers(bytes_body=True)
+
+    def test_get_identity_provider_with_str_body(self):
+        self._test_get_identity_provider()
+
+    def test_get_identity_provider_with_bytes_body(self):
+        self._test_get_identity_provider(bytes_body=True)
+
+    def test_delete_identity_provider_with_str_body(self):
+        self._test_delete_identity_provider()
+
+    def test_delete_identity_provider_with_bytes_body(self):
+        self._test_delete_identity_provider(bytes_body=True)
+
+    def test_update_identity_provider_with_str_body(self):
+        self._test_update_identity_provider()
+
+    def test_update_identity_provider_with_bytes_body(self):
+        self._test_update_identity_provider(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_limit_client.py b/tempest/tests/lib/services/identity/v3/test_limit_client.py
new file mode 100644
index 0000000..07ec6cd
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_limit_client.py
@@ -0,0 +1,82 @@
+# Copyright 2021 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.lib.services.identity.v3 import limits_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestLimitsClient(base.BaseServiceTest):
+    def setUp(self):
+        super(TestLimitsClient, self).setUp()
+        self.client = limits_client.LimitsClient(
+            fake_auth_provider.FakeAuthProvider(),
+            'identity', 'regionOne')
+
+    def test_get_registered_limits(self):
+        fake_result = {'foo': 'bar'}
+        self.check_service_client_function(
+            self.client.get_registered_limits,
+            'tempest.lib.common.rest_client.RestClient.get',
+            fake_result,
+            False,
+            status=200)
+
+    def test_create_limit(self):
+        fake_result = {'foo': 'bar'}
+        self.check_service_client_function(
+            self.client.create_limit,
+            'tempest.lib.common.rest_client.RestClient.post',
+            fake_result,
+            False,
+            region_id='regionOne', service_id='image',
+            project_id='project', resource_name='widgets',
+            default_limit=10,
+            description='Spacely Widgets',
+            status=201)
+
+    def test_create_limit_with_domain(self):
+        fake_result = {'foo': 'bar'}
+        self.check_service_client_function(
+            self.client.create_limit,
+            'tempest.lib.common.rest_client.RestClient.post',
+            fake_result,
+            False,
+            region_id='regionOne', service_id='image',
+            project_id='project', resource_name='widgets',
+            default_limit=10,
+            domain_id='foo',
+            description='Spacely Widgets',
+            status=201)
+
+    def test_update_limit(self):
+        fake_result = {'foo': 'bar'}
+        self.check_service_client_function(
+            self.client.update_limit,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            fake_result,
+            False,
+            limit_id='123', resource_limit=20,
+            status=200)
+
+    def test_update_limit_with_description(self):
+        fake_result = {'foo': 'bar'}
+        self.check_service_client_function(
+            self.client.update_limit,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            fake_result,
+            False,
+            limit_id='123', resource_limit=20,
+            description='new description',
+            status=200)
diff --git a/tempest/tests/lib/services/identity/v3/test_mappings_client.py b/tempest/tests/lib/services/identity/v3/test_mappings_client.py
new file mode 100644
index 0000000..845a3f9
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_mappings_client.py
@@ -0,0 +1,183 @@
+# Copyright 2020 Samsung Electronics 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.identity.v3 import mappings_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestMappingsClient(base.BaseServiceTest):
+    FAKE_MAPPING_INFO = {
+        "mapping": {
+            "id": "fake123",
+            "links": {
+                "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                        "mappings/fake123"
+            },
+            "rules": [
+                {
+                    "local": [
+                        {
+                            "user": {
+                                "name": "{0}"
+                            }
+                        },
+                        {
+                            "group": {
+                                "id": "0cd5e9"
+                            }
+                        }
+                    ],
+                    "remote": [
+                        {
+                            "type": "UserName"
+                        },
+                        {
+                            "type": "orgPersonType",
+                            "not_any_of": [
+                                "Contractor",
+                                "Guest"
+                            ]
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+
+    FAKE_MAPPINGS_INFO = {
+        "links": {
+            "next": None,
+            "previous": None,
+            "self": "http://example.com/identity/v3/OS-FEDERATION/mappings"
+        },
+        "mappings": [
+            {
+                "id": "fake123",
+                "links": {
+                    "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                            "mappings/fake123"
+                },
+                "rules": [
+                    {
+                        "local": [
+                            {
+                                "user": {
+                                    "name": "{0}"
+                                }
+                            },
+                            {
+                                "group": {
+                                    "id": "0cd5e9"
+                                }
+                            }
+                        ],
+                        "remote": [
+                            {
+                                "type": "UserName"
+                            },
+                            {
+                                "type": "orgPersonType",
+                                "any_one_of": [
+                                    "Contractor",
+                                    "SubContractor"
+                                ]
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestMappingsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = mappings_client.MappingsClient(
+            fake_auth, 'identity', 'regionOne')
+
+    def _test_create_mapping(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_mapping,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_MAPPING_INFO,
+            bytes_body,
+            mapping_id="fake123",
+            status=201)
+
+    def _test_get_mapping(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.get_mapping,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_MAPPING_INFO,
+            bytes_body,
+            mapping_id="fake123",
+            status=200)
+
+    def _test_update_mapping(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_mapping,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_MAPPING_INFO,
+            bytes_body,
+            mapping_id="fake123",
+            status=200)
+
+    def _test_list_mappings(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_mappings,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_MAPPINGS_INFO,
+            bytes_body,
+            status=200)
+
+    def _test_delete_mapping(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.delete_mapping,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            bytes_body,
+            mapping_id="fake123",
+            status=204)
+
+    def test_create_mapping_with_str_body(self):
+        self._test_create_mapping()
+
+    def test_create_mapping_with_bytes_body(self):
+        self._test_create_mapping(bytes_body=True)
+
+    def test_get_mapping_with_str_body(self):
+        self._test_get_mapping()
+
+    def test_get_mapping_with_bytes_body(self):
+        self._test_get_mapping(bytes_body=True)
+
+    def test_update_mapping_with_str_body(self):
+        self._test_update_mapping()
+
+    def test_update_mapping_with_bytes_body(self):
+        self._test_update_mapping(bytes_body=True)
+
+    def test_list_mappings_with_str_body(self):
+        self._test_list_mappings()
+
+    def test_list_mappings_with_bytes_body(self):
+        self._test_list_mappings(bytes_body=True)
+
+    def test_delete_mapping_with_str_body(self):
+        self._test_delete_mapping()
+
+    def test_delete_mapping_with_bytes_body(self):
+        self._test_delete_mapping(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_protocols_client.py b/tempest/tests/lib/services/identity/v3/test_protocols_client.py
new file mode 100644
index 0000000..c1d04f4
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_protocols_client.py
@@ -0,0 +1,140 @@
+# Copyright 2020 Samsung Electronics 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.identity.v3 import protocols_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestProtocolsClient(base.BaseServiceTest):
+    FAKE_PROTOCOLS_INFO = {
+        "links": {
+            "next": None,
+            "previous": None,
+            "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                    "identity_providers/FAKE_ID/protocols"
+        },
+        "protocols": [
+            {
+                "id": "fake_id1",
+                "links": {
+                    "identity_provider": "http://example.com/identity/v3/" +
+                                         "OS-FEDERATION/identity_providers/" +
+                                         "FAKE_ID",
+                    "self": "http://example.com/identity/v3/OS-FEDERATION/"
+                            "identity_providers/FAKE_ID/protocols/fake_id1"
+                },
+                "mapping_id": "fake123"
+            }
+        ]
+    }
+
+    FAKE_PROTOCOL_INFO = {
+        "protocol": {
+            "id": "fake_id1",
+            "links": {
+                "identity_provider": "http://example.com/identity/v3/OS-" +
+                                     "FEDERATION/identity_providers/FAKE_ID",
+                "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                        "identity_providers/FAKE_ID/protocols/fake_id1"
+            },
+            "mapping_id": "fake123"
+        }
+    }
+
+    def setUp(self):
+        super(TestProtocolsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = protocols_client.ProtocolsClient(
+            fake_auth, 'identity', 'regionOne')
+
+    def _test_add_protocol_to_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.add_protocol_to_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_PROTOCOL_INFO,
+            bytes_body,
+            idp_id="FAKE_ID",
+            protocol_id="fake_id1",
+            status=201)
+
+    def _test_list_protocols_of_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_protocols_of_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_PROTOCOLS_INFO,
+            bytes_body,
+            idp_id="FAKE_ID",
+            status=200)
+
+    def _test_get_protocol_for_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.get_protocol_for_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_PROTOCOL_INFO,
+            bytes_body,
+            idp_id="FAKE_ID",
+            protocol_id="fake_id1",
+            status=200)
+
+    def _test_update_mapping_for_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_mapping_for_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_PROTOCOL_INFO,
+            bytes_body,
+            idp_id="FAKE_ID",
+            protocol_id="fake_id1",
+            status=200)
+
+    def _test_delete_protocol_from_identity_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.delete_protocol_from_identity_provider,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            bytes_body,
+            idp_id="FAKE_ID",
+            protocol_id="fake_id1",
+            status=204)
+
+    def test_add_protocol_to_identity_provider_with_str_body(self):
+        self._test_add_protocol_to_identity_provider()
+
+    def test_add_protocol_to_identity_provider_with_bytes_body(self):
+        self._test_add_protocol_to_identity_provider(bytes_body=True)
+
+    def test_list_protocols_of_identity_provider_with_str_body(self):
+        self._test_list_protocols_of_identity_provider()
+
+    def test_list_protocols_of_identity_provider_with_bytes_body(self):
+        self._test_list_protocols_of_identity_provider(bytes_body=True)
+
+    def test_get_protocol_for_identity_provider_with_str_body(self):
+        self._test_get_protocol_for_identity_provider()
+
+    def test_get_protocol_for_identity_provider_with_bytes_body(self):
+        self._test_get_protocol_for_identity_provider(bytes_body=True)
+
+    def test_update_mapping_for_identity_provider_with_str_body(self):
+        self._test_update_mapping_for_identity_provider()
+
+    def test_update_mapping_for_identity_provider_with_bytes_body(self):
+        self._test_update_mapping_for_identity_provider(bytes_body=True)
+
+    def test_delete_protocol_from_identity_provider_with_str_body(self):
+        self._test_delete_protocol_from_identity_provider()
+
+    def test_delete_protocol_from_identity_provider_with_bytes_body(self):
+        self._test_delete_protocol_from_identity_provider(bytes_body=False)
diff --git a/tempest/tests/lib/services/identity/v3/test_roles_client.py b/tempest/tests/lib/services/identity/v3/test_roles_client.py
index 8d6bb42..e963310 100644
--- a/tempest/tests/lib/services/identity/v3/test_roles_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_roles_client.py
@@ -225,6 +225,16 @@
             role_id="1234",
             status=204)
 
+    def _test_create_user_role_on_system(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_user_role_on_system,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            user_id="123",
+            role_id="1234",
+            status=204)
+
     def _test_list_user_roles_on_project(self, bytes_body=False):
         self.check_service_client_function(
             self.client.list_user_roles_on_project,
@@ -243,6 +253,14 @@
             domain_id="b344506af7644f6794d9cb316600b020",
             user_id="123")
 
+    def _test_list_user_roles_on_system(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_user_roles_on_system,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ROLES,
+            bytes_body,
+            user_id="123")
+
     def _test_create_group_role_on_project(self, bytes_body=False):
         self.check_service_client_function(
             self.client.create_group_role_on_project,
@@ -265,6 +283,16 @@
             role_id="1234",
             status=204)
 
+    def _test_create_group_role_on_system(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_group_role_on_system,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            group_id="123",
+            role_id="1234",
+            status=204)
+
     def _test_list_group_roles_on_project(self, bytes_body=False):
         self.check_service_client_function(
             self.client.list_group_roles_on_project,
@@ -283,6 +311,15 @@
             domain_id="b344506af7644f6794d9cb316600b020",
             group_id="123")
 
+    def _test_list_group_roles_on_system(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_group_roles_on_system,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ROLES,
+            bytes_body,
+            domain_id="b344506af7644f6794d9cb316600b020",
+            group_id="123")
+
     def _test_create_role_inference_rule(self, bytes_body=False):
         self.check_service_client_function(
             self.client.create_role_inference_rule,
@@ -405,6 +442,15 @@
             role_id="1234",
             status=204)
 
+    def test_delete_role_from_user_on_system(self):
+        self.check_service_client_function(
+            self.client.delete_role_from_user_on_system,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            user_id="123",
+            role_id="1234",
+            status=204)
+
     def test_delete_role_from_group_on_project(self):
         self.check_service_client_function(
             self.client.delete_role_from_group_on_project,
@@ -425,6 +471,15 @@
             role_id="1234",
             status=204)
 
+    def test_delete_role_from_group_on_system(self):
+        self.check_service_client_function(
+            self.client.delete_role_from_group_on_system,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            group_id="123",
+            role_id="1234",
+            status=204)
+
     def test_check_user_role_existence_on_project(self):
         self.check_service_client_function(
             self.client.check_user_role_existence_on_project,
@@ -445,6 +500,15 @@
             role_id="1234",
             status=204)
 
+    def test_check_user_role_existence_on_system(self):
+        self.check_service_client_function(
+            self.client.check_user_role_existence_on_system,
+            'tempest.lib.common.rest_client.RestClient.head',
+            {},
+            user_id="123",
+            role_id="1234",
+            status=204)
+
     def test_check_role_from_group_on_project_existence(self):
         self.check_service_client_function(
             self.client.check_role_from_group_on_project_existence,
@@ -465,6 +529,15 @@
             role_id="1234",
             status=204)
 
+    def test_check_role_from_group_on_system_existence(self):
+        self.check_service_client_function(
+            self.client.check_role_from_group_on_system_existence,
+            'tempest.lib.common.rest_client.RestClient.head',
+            {},
+            group_id="123",
+            role_id="1234",
+            status=204)
+
     def test_create_role_inference_rule_with_str_body(self):
         self._test_create_role_inference_rule()
 
diff --git a/tempest/tests/lib/services/identity/v3/test_service_providers_client.py b/tempest/tests/lib/services/identity/v3/test_service_providers_client.py
new file mode 100644
index 0000000..ec908bc
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_service_providers_client.py
@@ -0,0 +1,157 @@
+# Copyright 2020 Samsung Electronics 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.identity.v3 import service_providers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestServiceProvidersClient(base.BaseServiceTest):
+    FAKE_SERVICE_PROVIDER_INFO = {
+        "service_provider": {
+            "auth_url": "https://example.com/identity/v3/OS-FEDERATION/" +
+                        "identity_providers/FAKE_ID/protocols/fake_id1/auth",
+            "description": "Fake Service Provider",
+            "enabled": True,
+            "id": "FAKE_ID",
+            "links": {
+                "self": "https://example.com/identity/v3/OS-FEDERATION/" +
+                        "service_providers/FAKE_ID"
+            },
+            "relay_state_prefix": "ss:mem:",
+            "sp_url": "https://example.com/identity/Shibboleth.sso/" +
+                      "FAKE_ID1/ECP"
+        }
+    }
+
+    FAKE_SERVICE_PROVIDERS_INFO = {
+        "links": {
+            "next": None,
+            "previous": None,
+            "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                    "service_providers"
+        },
+        "service_providers": [
+            {
+                "auth_url": "https://example.com/identity/v3/OS-FEDERATION/" +
+                            "identity_providers/acme/protocols/saml2/auth",
+                "description": "Stores ACME identities",
+                "enabled": True,
+                "id": "ACME",
+                "links": {
+                    "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                            "service_providers/ACME"
+                },
+                "relay_state_prefix": "ss:mem:",
+                "sp_url": "https://example.com/identity/Shibboleth.sso/" +
+                          "SAML2/ECP"
+            },
+            {
+                "auth_url": "https://other.example.com/identity/v3/" +
+                            "OS-FEDERATION/identity_providers/acme/" +
+                            "protocols/saml2/auth",
+                "description": "Stores contractor identities",
+                "enabled": False,
+                "id": "ACME-contractors",
+                "links": {
+                    "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+                            "service_providers/ACME-contractors"
+                },
+                "relay_state_prefix": "ss:mem:",
+                "sp_url": "https://other.example.com/identity/Shibboleth" +
+                          ".sso/SAML2/ECP"
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestServiceProvidersClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = service_providers_client.ServiceProvidersClient(
+            fake_auth, 'identity', 'regionOne')
+
+    def _test_register_service_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.register_service_provider,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_SERVICE_PROVIDER_INFO,
+            bytes_body,
+            service_provider_id="FAKE_ID",
+            status=201)
+
+    def _test_list_service_providers(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_service_providers,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SERVICE_PROVIDERS_INFO,
+            bytes_body,
+            status=200)
+
+    def _test_get_service_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.get_service_provider,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SERVICE_PROVIDER_INFO,
+            bytes_body,
+            service_provider_id="FAKE_ID",
+            status=200)
+
+    def _test_delete_service_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.delete_service_provider,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            bytes_body,
+            service_provider_id="FAKE_ID",
+            status=204)
+
+    def _test_update_service_provider(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_service_provider,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_SERVICE_PROVIDER_INFO,
+            bytes_body,
+            service_provider_id="FAKE_ID",
+            status=200)
+
+    def test_register_service_provider_with_str_body(self):
+        self._test_register_service_provider()
+
+    def test_register_service_provider_with_bytes_body(self):
+        self._test_register_service_provider(bytes_body=True)
+
+    def test_list_service_providers_with_str_body(self):
+        self._test_list_service_providers()
+
+    def test_list_service_providers_with_bytes_body(self):
+        self._test_list_service_providers(bytes_body=True)
+
+    def test_get_service_provider_with_str_body(self):
+        self._test_get_service_provider()
+
+    def test_get_service_provider_with_bytes_body(self):
+        self._test_get_service_provider(bytes_body=True)
+
+    def test_delete_service_provider_with_str_body(self):
+        self._test_delete_service_provider()
+
+    def test_delete_service_provider_with_bytes_body(self):
+        self._test_delete_service_provider(bytes_body=True)
+
+    def test_update_service_provider_with_str_body(self):
+        self._test_update_service_provider()
+
+    def test_update_service_provider_with_bytes_body(self):
+        self._test_update_service_provider(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_trusts_client.py b/tempest/tests/lib/services/identity/v3/test_trusts_client.py
index a1ca020..33dca7d 100644
--- a/tempest/tests/lib/services/identity/v3/test_trusts_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_trusts_client.py
@@ -94,6 +94,35 @@
             }
         }
 
+    FAKE_LIST_TRUSTS_ROLES = {
+        "roles": [
+            {
+                "id": "c1648e",
+                "links": {
+                    "self": "http://example.com/identity/v3/roles/c1648e"
+                    },
+                "name": "manager"
+                },
+            {
+                "id": "ed7b78",
+                "links": {
+                    "self": "http://example.com/identity/v3/roles/ed7b78"
+                    },
+                "name": "member"
+                }
+            ]
+        }
+
+    FAKE_TRUST_ROLE = {
+        "role": {
+            "id": "c1648e",
+            "links": {
+                "self": "http://example.com/identity/v3/roles/c1648e"
+                },
+            "name": "manager"
+            }
+        }
+
     def setUp(self):
         super(TestTrustsClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -123,6 +152,43 @@
             self.FAKE_LIST_TRUSTS,
             bytes_body)
 
+    def _test_list_trust_roles(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_trust_roles,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_TRUSTS_ROLES,
+            bytes_body,
+            trust_id="1ff900")
+
+    def test_check_trust_role(self):
+        self.check_service_client_function(
+            self.client.check_trust_role,
+            'tempest.lib.common.rest_client.RestClient.head',
+            {},
+            trust_id="1ff900",
+            role_id="ed7b78")
+
+    def _check_show_trust_role(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_trust_role,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_TRUST_ROLE,
+            bytes_body,
+            trust_id="1ff900",
+            role_id="ed7b78")
+
+    def test_list_trust_roles_with_str_body(self):
+        self._test_list_trust_roles()
+
+    def test_list_trust_roles_with_bytes_body(self):
+        self._test_list_trust_roles(bytes_body=True)
+
+    def test_check_show_trust_role_with_str_body(self):
+        self._check_show_trust_role()
+
+    def test_check_show_trust_role_with_bytes_body(self):
+        self._check_show_trust_role(bytes_body=True)
+
     def test_create_trust_with_str_body(self):
         self._test_create_trust()
 
diff --git a/tempest/tests/lib/services/image/v2/test_images_client.py b/tempest/tests/lib/services/image/v2/test_images_client.py
index fe671bd..5b162f8 100644
--- a/tempest/tests/lib/services/image/v2/test_images_client.py
+++ b/tempest/tests/lib/services/image/v2/test_images_client.py
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
+import io
 
 from tempest.lib.common.utils import data_utils
 from tempest.lib.services.image.v2 import images_client
@@ -105,6 +105,44 @@
         "first": "/v2/images"
     }
 
+    FAKE_SHOW_IMAGE_TASKS = {
+        "tasks": [
+            {
+                "id": "ee22890e-8948-4ea6-9668-831f973c84f5",
+                "image_id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
+                "request-id": "rrrrrrr-rrrr-rrrr-rrrr-rrrrrrrrrrrr",
+                "user": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu",
+                "type": "api_image_import",
+                "status": "processing",
+                "owner": "64f0efc9955145aeb06f297a8a6fe402",
+                "expires_at": None,
+                "created_at": "2020-12-18T05:20:38.000000",
+                "updated_at": "2020-12-18T05:25:39.000000",
+                "deleted_at": None,
+                "deleted": False,
+                "input": {
+                    "image_id": "829c729b-ebc4-4cc7-a164-6f43f1149b17",
+                    "import_req": {
+                        "method": {
+                            "name": "copy-image",
+                        },
+                        "all_stores": True,
+                        "all_stores_must_succeed": False,
+                    },
+                    "backend": [
+                        "fast",
+                        "cheap",
+                        "slow",
+                        "reliable",
+                        "common",
+                    ]
+                },
+                "result": None,
+                "message": "Copied 15 MiB",
+            }
+        ]
+    }
+
     FAKE_TAG_NAME = "fake tag"
 
     def setUp(self):
@@ -178,7 +216,7 @@
             {}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204)
 
     def test_store_image_file(self):
-        data = six.BytesIO(data_utils.random_bytes())
+        data = io.BytesIO(data_utils.random_bytes())
 
         self.check_service_client_function(
             self.client.store_image_file,
@@ -230,3 +268,11 @@
 
     def test_list_images_with_bytes_body(self):
         self._test_list_images(bytes_body=True)
+
+    def test_show_image_tasks(self):
+        self.check_service_client_function(
+            self.client.show_image_tasks,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_IMAGE_TASKS,
+            True,
+            image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8")
diff --git a/tempest/tests/lib/services/image/v2/test_schemas_client.py b/tempest/tests/lib/services/image/v2/test_schemas_client.py
index 4c4b86a..eef5b41 100644
--- a/tempest/tests/lib/services/image/v2/test_schemas_client.py
+++ b/tempest/tests/lib/services/image/v2/test_schemas_client.py
@@ -81,6 +81,14 @@
         self.client = schemas_client.SchemasClient(fake_auth,
                                                    'image', 'regionOne')
 
+    def _test_show_schema_members(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_schema,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_SCHEMA,
+            bytes_body,
+            schema="members")
+
     def _test_show_schema(self, bytes_body=False):
         self.check_service_client_function(
             self.client.show_schema,
@@ -89,6 +97,12 @@
             bytes_body,
             schema="member")
 
+    def test_show_schema_members_with_str_body(self):
+        self._test_show_schema_members()
+
+    def test_show_schema_members_with_bytes_body(self):
+        self._test_show_schema_members(bytes_body=True)
+
     def test_show_schema_with_str_body(self):
         self._test_show_schema()
 
diff --git a/tempest/tests/lib/services/image/v2/test_versions_client.py b/tempest/tests/lib/services/image/v2/test_versions_client.py
index 6234b06..98c558a 100644
--- a/tempest/tests/lib/services/image/v2/test_versions_client.py
+++ b/tempest/tests/lib/services/image/v2/test_versions_client.py
@@ -12,6 +12,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import fixtures
+
 from tempest.lib.services.image.v2 import versions_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
@@ -92,3 +94,13 @@
 
     def test_list_versions_with_bytes_body(self):
         self._test_list_versions(bytes_body=True)
+
+    def test_has_version(self):
+        mocked_r = self.create_response(self.FAKE_VERSIONS_INFO, False,
+                                        300, None)
+        self.useFixture(fixtures.MockPatch(
+            'tempest.lib.common.rest_client.RestClient.raw_request',
+            return_value=mocked_r))
+
+        self.assertTrue(self.client.has_version('2.1'))
+        self.assertFalse(self.client.has_version('9.9'))
diff --git a/tempest/tests/lib/services/network/test_floating_ips_port_forwarding_client.py b/tempest/tests/lib/services/network/test_floating_ips_port_forwarding_client.py
new file mode 100644
index 0000000..ce068e9
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_floating_ips_port_forwarding_client.py
@@ -0,0 +1,156 @@
+# Copyright 2021 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.
+
+import copy
+
+from tempest.lib.services.network import floating_ips_port_forwarding_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestFloatingIpsPortForwardingClient(base.BaseServiceTest):
+
+    FAKE_PORT_FORWARDING_REQUEST = {
+
+        "port_forwarding": {
+            "protocol": "tcp",
+            "internal_ip_address": "10.0.0.11",
+            "internal_port": 25,
+            "internal_port_id": "1238be08-a2a8-4b8d-addf-fb5e2250e480",
+            "external_port": 2230,
+            "description": "Some description",
+            }
+
+        }
+
+    FAKE_PORT_FORWARDING_RESPONSE = {
+
+        "port_forwarding": {
+            "protocol": "tcp",
+            "internal_ip_address": "10.0.0.12",
+            "internal_port": 26,
+            "internal_port_id": "1238be08-a2a8-4b8d-addf-fb5e2250e480",
+            "external_port": 2130,
+            "description": "Some description",
+            "id": "825ade3c-9760-4880-8080-8fc2dbab9acc"
+        }
+    }
+
+    FAKE_PORT_FORWARDINGS = {
+        "port_forwardings": [
+            FAKE_PORT_FORWARDING_RESPONSE['port_forwarding']
+        ]
+    }
+
+    FAKE_FLOATINGIP_ID = "a6800594-5b7a-4105-8bfe-723b346ce866"
+
+    FAKE_PORT_FORWARDING_ID = "a7800594-5b7a-4105-8bfe-723b346ce866"
+
+    def setUp(self):
+        super(TestFloatingIpsPortForwardingClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.floating_ips_port_forwarding_client = \
+            floating_ips_port_forwarding_client.\
+            FloatingIpsPortForwardingClient(fake_auth,
+                                            "network",
+                                            "regionOne")
+
+    def _test_create_port_forwarding(self, bytes_body=False):
+        self.check_service_client_function(
+            self.floating_ips_port_forwarding_client.
+            create_port_forwarding,
+            "tempest.lib.common.rest_client.RestClient.post",
+            self.FAKE_PORT_FORWARDING_RESPONSE,
+            bytes_body,
+            201,
+            floatingip_id=self.FAKE_FLOATINGIP_ID,
+            **self.FAKE_PORT_FORWARDING_REQUEST)
+
+    def _test_list_port_forwardings(self, bytes_body=False):
+        self.check_service_client_function(
+            self.floating_ips_port_forwarding_client.
+            list_port_forwardings,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_PORT_FORWARDINGS,
+            bytes_body,
+            200,
+            floatingip_id=self.FAKE_FLOATINGIP_ID)
+
+    def _test_show_port_forwardings(self, bytes_body=False):
+        self.check_service_client_function(
+            self.floating_ips_port_forwarding_client.
+            show_port_forwarding,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_PORT_FORWARDING_RESPONSE,
+            bytes_body,
+            200,
+            floatingip_id=self.FAKE_FLOATINGIP_ID,
+            port_forwarding_id=self.FAKE_PORT_FORWARDING_ID)
+
+    def _test_delete_port_forwarding(self):
+        self.check_service_client_function(
+            self.floating_ips_port_forwarding_client.
+            delete_port_forwarding,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            floatingip_id=self.FAKE_FLOATINGIP_ID,
+            port_forwarding_id=self.FAKE_PORT_FORWARDING_ID)
+
+    def _test_update_port_forwarding(self, bytes_body=False):
+        update_kwargs = {
+            "internal_port": "27"
+        }
+
+        resp_body = {
+            "port_forwarding": copy.deepcopy(
+                self.FAKE_PORT_FORWARDING_RESPONSE['port_forwarding']
+            )
+        }
+        resp_body["port_forwarding"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.floating_ips_port_forwarding_client.update_port_forwarding,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            floatingip_id=self.FAKE_FLOATINGIP_ID,
+            port_forwarding_id=self.FAKE_PORT_FORWARDING_ID,
+            **update_kwargs)
+
+    def test_list_port_forwardings_with_str_body(self):
+        self._test_list_port_forwardings()
+
+    def test_list_port_forwardings_with_bytes_body(self):
+        self._test_list_port_forwardings(bytes_body=True)
+
+    def test_show_port_forwardings_with_str_body(self):
+        self._test_show_port_forwardings()
+
+    def test_show_port_forwardings_with_bytes_body(self):
+        self._test_show_port_forwardings(bytes_body=True)
+
+    def test_create_port_forwarding_with_str_body(self):
+        self._test_create_port_forwarding()
+
+    def test_create_port_forwarding_with_bytes_body(self):
+        self._test_create_port_forwarding(bytes_body=True)
+
+    def test_update_port_forwarding_with_str_body(self):
+        self._test_update_port_forwarding()
+
+    def test_update_port_forwarding_with_bytes_body(self):
+        self._test_update_port_forwarding(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_log_resource_client.py b/tempest/tests/lib/services/network/test_log_resource_client.py
new file mode 100644
index 0000000..ef502bc
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_log_resource_client.py
@@ -0,0 +1,145 @@
+# Copyright 2021 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.
+
+import copy
+
+from tempest.lib.services.network import log_resource_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestLogResourceClient(base.BaseServiceTest):
+
+    FAKE_LOGS = {
+        "logs": [
+            {
+                "name": "security group log1",
+                "description": "Log for test demo.",
+                "id": "2f245a7b-796b-4f26-9cf9-9e82d248fda7",
+                "project_id": "92a5a4f4245a4abbafacb7ca73b027b0",
+                "tenant_id": "92a5a4f4245a4abbafacb7ca73b027b0",
+                "created_at": "2018-04-03T21:03:04Z",
+                "updated_at": "2018-04-03T21:03:04Z",
+                "enabled": True,
+                "revision_number": 1,
+                "resource_type": "security_group",
+                "resource_id": None,
+                "target_id": None,
+                "event": "ALL"
+            },
+            {
+                "name": "security group log2",
+                "description": "Log for test demo.",
+                "id": "46ebaec1-0570-43ac-82f6-60d2b03168c4",
+                "project_id": "82a5a4f4245a4abbafacb7ca73b027b0",
+                "tenant_id": "82a5a4f4245a4abbafacb7ca73b027b0",
+                "created_at": "2018-04-03T21:04:04Z",
+                "updated_at": "2018-04-03T21:04:04Z",
+                "enabled": True,
+                "revision_number": 2,
+                "resource_type": "security_group",
+                "resource_id": None,
+                "target_id": None,
+                "event": "ALL"
+            }
+        ]
+    }
+
+    FAKE_LOG_ID = "2f245a7b-796b-4f26-9cf9-9e82d248fda7"
+
+    def setUp(self):
+        super(TestLogResourceClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.log_resource_client = log_resource_client.LogResourceClient(
+            fake_auth, "network", "regionOne")
+
+    def _test_list_logs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.log_resource_client.list_logs,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_LOGS,
+            bytes_body,
+            200)
+
+    def _test_show_log(self, bytes_body=False):
+        self.check_service_client_function(
+            self.log_resource_client.show_log,
+            "tempest.lib.common.rest_client.RestClient.get",
+            {"log": self.FAKE_LOGS["logs"][0]},
+            bytes_body,
+            200,
+            log_id=self.FAKE_LOG_ID)
+
+    def _test_create_log(self, bytes_body=False):
+        self.check_service_client_function(
+            self.log_resource_client.create_log,
+            "tempest.lib.common.rest_client.RestClient.post",
+            {"logs": self.FAKE_LOGS["logs"][1]},
+            bytes_body,
+            201,
+            log_id="2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+
+    def _test_update_log(self, bytes_body=False):
+        update_kwargs = {
+            "tenant_id": "83a5a4f4245a4abbafacb7ca73b027b0"
+        }
+
+        resp_body = {
+            "logs": copy.deepcopy(
+                self.FAKE_LOGS["logs"][0]
+            )
+        }
+        resp_body["logs"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.log_resource_client.update_log,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            log_id=self.FAKE_LOG_ID,
+            **update_kwargs)
+
+    def test_list_logs_with_str_body(self):
+        self._test_list_logs()
+
+    def test_list_logs_with_bytes_body(self):
+        self._test_list_logs(bytes_body=True)
+
+    def test_create_log_with_str_body(self):
+        self._test_create_log()
+
+    def test_create_log_with_bytes_body(self):
+        self._test_create_log(bytes_body=True)
+
+    def test_show_log_with_str_body(self):
+        self._test_show_log()
+
+    def test_show_log_with_bytes_body(self):
+        self._test_show_log(bytes_body=True)
+
+    def test_update_log_with_str_body(self):
+        self._test_update_log()
+
+    def test_update_log_with_bytes_body(self):
+        self._test_update_log(bytes_body=True)
+
+    def test_delete_log(self):
+        self.check_service_client_function(
+            self.log_resource_client.delete_log,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            status=204,
+            log_id=self.FAKE_LOG_ID)
diff --git a/tempest/tests/lib/services/network/test_loggable_resource_client.py b/tempest/tests/lib/services/network/test_loggable_resource_client.py
new file mode 100644
index 0000000..232775b
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_loggable_resource_client.py
@@ -0,0 +1,53 @@
+# Copyright 2021 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.lib.services.network import loggable_resource_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestLoggableResourceClient(base.BaseServiceTest):
+
+    FAKE_LOGS = {
+        "loggable_resources": [
+            {
+                "type": "security_group"
+            },
+            {
+                "type": "none"
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestLoggableResourceClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.loggable_resource_client = \
+            loggable_resource_client.LoggableResourceClient(
+                fake_auth, "network", "regionOne")
+
+    def _test_list_loggable_resources(self, bytes_body=False):
+        self.check_service_client_function(
+            self.loggable_resource_client.list_loggable_resources,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_LOGS,
+            bytes_body,
+            200)
+
+    def test_list_loggable_resources_with_str_body(self):
+        self._test_list_loggable_resources()
+
+    def test_list_loggable_resources_with_bytes_body(self):
+        self._test_list_loggable_resources(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_ports_client.py b/tempest/tests/lib/services/network/test_ports_client.py
index 20ef3f1..9ca9ac6 100644
--- a/tempest/tests/lib/services/network/test_ports_client.py
+++ b/tempest/tests/lib/services/network/test_ports_client.py
@@ -22,53 +22,126 @@
 
 class TestPortsClient(base.BaseServiceTest):
 
+    FAKE_CREATE_PORTS = {
+        "port": {
+            "binding:host_id": "4df8d9ff-6f6f-438f-90a1-ef660d4586ad",
+            "binding:profile": {
+                "local_link_information": [
+                    {
+                        "port_id": "Ethernet3/1",
+                        "switch_id": "0a:1b:2c:3d:4e:5f",
+                        "switch_info": "switch1"
+                    }
+                ]
+            },
+            "binding:vnic_type": "baremetal",
+            "device_id": "d90a13da-be41-461f-9f99-1dbcf438fdf2",
+            "device_owner": "baremetal:none",
+            "dns_domain": "my-domain.org.",
+            "dns_name": "myport",
+            "qos_policy_id": "29d5e02e-d5ab-4929-bee4-4a9fc12e22ae",
+            "uplink_status_propagation": False
+        }
+    }
+
     FAKE_PORTS = {
         "ports": [
             {
                 "admin_state_up": True,
                 "allowed_address_pairs": [],
+                "created_at": "2016-03-08T20:19:41",
                 "data_plane_status": None,
                 "description": "",
                 "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
                 "device_owner": "network:router_gateway",
-                "extra_dhcp_opts": [],
+                "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"
+                        "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": "",
                 "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3",
                 "project_id": "",
+                "revision_number": 1,
                 "security_groups": [],
                 "status": "ACTIVE",
-                "tenant_id": ""
+                "tags": ["tag1,tag2"],
+                "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+                "updated_at": "2016-03-08T20:19:41",
+                "qos_network_policy_id":
+                    "174dd0c1-a4eb-49d4-a807-ae80246d82f4",
+                "qos_policy_id": "29d5e02e-d5ab-4929-bee4-4a9fc12e22ae",
+                "port_security_enabled": False,
+                "uplink_status_propagation": False
             },
             {
                 "admin_state_up": True,
                 "allowed_address_pairs": [],
+                "created_at": "2016-03-08T20:19:41",
                 "data_plane_status": None,
                 "description": "",
                 "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
                 "device_owner": "network:router_interface",
-                "extra_dhcp_opts": [],
+                "dns_assignment": [
+                    {
+                        "hostname": "myport2",
+                        "ip_address": "10.0.0.1",
+                        "fqdn": "myport2.my-domain.org"
+                    }
+                ],
+                "dns_domain": "my-domain.org.",
+                "dns_name": "myport2",
+                "extra_dhcp_opts": [
+                    {
+                        "opt_value": "pxelinux.0",
+                        "ip_version": 4,
+                        "opt_name": "bootfile-name"
+                    }
+                ],
                 "fixed_ips": [
                     {
                         "ip_address": "10.0.0.1",
-                        "subnet_id": "288bf4a1-51ba-43b6-9d0a-520e9005db17"
+                        "subnet_id":
+                            "288bf4a1-51ba-43b6-9d0a-520e9005db17"
                     }
                 ],
                 "id": "f71a6703-d6de-4be1-a91a-a570ede1d159",
+                "ip_allocation": "immediate",
                 "mac_address": "fa:16:3e:bb:3c:e4",
                 "name": "",
                 "network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2",
                 "project_id": "d397de8a63f341818f198abb0966f6f3",
+                "revision_number": 1,
                 "security_groups": [],
                 "status": "ACTIVE",
-                "tenant_id": "d397de8a63f341818f198abb0966f6f3"
+                "tags": ["tag1,tag2"],
+                "tenant_id": "d397de8a63f341818f198abb0966f6f3",
+                "updated_at": "2016-03-08T20:19:41",
+                "qos_network_policy_id": None,
+                "qos_policy_id": None,
+                "port_security_enabled": False,
+                "uplink_status_propagation": False
             }
         ]
     }
@@ -112,7 +185,7 @@
         self.check_service_client_function(
             self.ports_client.create_port,
             "tempest.lib.common.rest_client.RestClient.post",
-            {"port": self.FAKE_PORTS["ports"][0]},
+            self.FAKE_CREATE_PORTS,
             bytes_body,
             201,
             **self.FAKE_PORT1)
diff --git a/tempest/tests/lib/services/network/test_qos_limit_bandwidth_rules_client.py b/tempest/tests/lib/services/network/test_qos_limit_bandwidth_rules_client.py
new file mode 100644
index 0000000..e83792d
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_qos_limit_bandwidth_rules_client.py
@@ -0,0 +1,124 @@
+# Copyright 2021 Red Hat.
+#
+# 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 import decorators
+
+from tempest.lib.services.network import qos_limit_bandwidth_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+from oslo_log import log as logging
+LOG = logging.getLogger('tempest')
+
+
+class TestQosLimitBandwidthRulesClient(base.BaseServiceTest):
+
+    FAKE_QOS_POLICY_ID = "f1011b08-1297-11e9-a1e7-c7e6825a2616"
+    FAKE_MAX_BW_RULE_ID = "e758c89e-1297-11e9-a6cf-cf46a71e6699"
+
+    FAKE_MAX_BW_RULE_REQUEST = {
+        'qos_policy_id': FAKE_QOS_POLICY_ID,
+        'max_kbps': 1000,
+        'max_burst_kbps': 0,
+        'direction': 'ingress'
+    }
+
+    FAKE_MAX_BW_RULE_RESPONSE = {
+        'bandwidth_limit_rule': {
+            'id': FAKE_MAX_BW_RULE_ID,
+            'max_kbps': 10000,
+            'max_burst_kbps': 0,
+            'direction': 'ingress'
+        }
+    }
+
+    FAKE_MAX_BW_RULES = {
+        'bandwidth_limit_rules': [
+            FAKE_MAX_BW_RULE_RESPONSE['bandwidth_limit_rule']
+        ]
+    }
+
+    def setUp(self):
+        super(TestQosLimitBandwidthRulesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.qos_limit_bw_client = qos_limit_bandwidth_rules_client.\
+            QosLimitBandwidthRulesClient(fake_auth, "network", "regionOne")
+
+    @decorators.idempotent_id('cde981fa-e93b-11eb-aacb-74e5f9e2a801')
+    def test_create_limit_bandwidth_rules(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_limit_bw_client.create_limit_bandwidth_rule,
+            "tempest.lib.common.rest_client.RestClient.post",
+            self.FAKE_MAX_BW_RULE_RESPONSE,
+            bytes_body,
+            201,
+            **self.FAKE_MAX_BW_RULE_REQUEST
+        )
+
+    @decorators.idempotent_id('86e6803a-e974-11eb-aacb-74e5f9e2a801')
+    def test_update_limit_bandwidth_rules(self, bytes_body=False):
+        update_kwargs = {
+            "max_kbps": "20000"
+        }
+
+        resp_body = {
+            "bandwidth_limit_rule": copy.deepcopy(
+                self.FAKE_MAX_BW_RULE_RESPONSE['bandwidth_limit_rule']
+            )
+        }
+        resp_body["bandwidth_limit_rule"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.qos_limit_bw_client.update_limit_bandwidth_rule,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MAX_BW_RULE_ID,
+            **update_kwargs)
+
+    @decorators.idempotent_id('be60ae6e-e979-11eb-aacb-74e5f9e2a801')
+    def test_show_limit_bandwidth_rules(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_limit_bw_client.show_limit_bandwidth_rule,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_MAX_BW_RULE_RESPONSE,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MAX_BW_RULE_ID
+        )
+
+    @decorators.idempotent_id('0a7c0964-e97b-11eb-aacb-74e5f9e2a801')
+    def test_delete_limit_bandwidth_rule(self):
+        self.check_service_client_function(
+            self.qos_limit_bw_client.delete_limit_bandwidth_rule,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MAX_BW_RULE_ID)
+
+    @decorators.idempotent_id('08df88ae-e97d-11eb-aacb-74e5f9e2a801')
+    def test_list_minimum_bandwidth_rules(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_limit_bw_client.list_limit_bandwidth_rules,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_MAX_BW_RULES,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID
+        )
diff --git a/tempest/tests/lib/services/network/test_qos_minimum_packet_rate_rules_client.py b/tempest/tests/lib/services/network/test_qos_minimum_packet_rate_rules_client.py
new file mode 100644
index 0000000..3cc3de3
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_qos_minimum_packet_rate_rules_client.py
@@ -0,0 +1,135 @@
+#    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 qos_minimum_packet_rate_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQosMinimumPacketRateRulesClient(base.BaseServiceTest):
+
+    FAKE_QOS_POLICY_ID = "f1011b08-1297-11e9-a1e7-c7e6825a2616"
+    FAKE_MIN_PPS_RULE_ID = "e758c89e-1297-11e9-a6cf-cf46a71e6699"
+
+    FAKE_MIN_PPS_RULE_REQUEST = {
+        'qos_policy_id': FAKE_QOS_POLICY_ID,
+        'min_kpps': 1000,
+        'direction': 'ingress'
+    }
+
+    FAKE_MIN_PPS_RULE_RESPONSE = {
+        'minimum_packet_rate_rule': {
+            'id': FAKE_MIN_PPS_RULE_ID,
+            'min_kpps': 1000,
+            'direction': 'ingress'
+        }
+    }
+
+    FAKE_MIN_PPS_RULES = {
+        'minimum_packet_rate_rules': [
+            FAKE_MIN_PPS_RULE_RESPONSE['minimum_packet_rate_rule']
+        ]
+    }
+
+    def setUp(self):
+        super(TestQosMinimumPacketRateRulesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.qos_min_pps_client = qos_minimum_packet_rate_rules_client.\
+            QosMinimumPacketRateRulesClient(fake_auth, "network", "regionOne")
+
+    def _test_create_minimum_packet_rate_rule(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_min_pps_client.create_minimum_packet_rate_rule,
+            "tempest.lib.common.rest_client.RestClient.post",
+            self.FAKE_MIN_PPS_RULE_RESPONSE,
+            bytes_body,
+            201,
+            **self.FAKE_MIN_PPS_RULE_REQUEST
+        )
+
+    def _test_list_minimum_packet_rate_rules(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_min_pps_client.list_minimum_packet_rate_rules,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_MIN_PPS_RULES,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID
+        )
+
+    def _test_show_minimum_packet_rate_rule(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_min_pps_client.show_minimum_packet_rate_rule,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_MIN_PPS_RULE_RESPONSE,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MIN_PPS_RULE_ID
+        )
+
+    def _test_update_qos_polcy(self, bytes_body=False):
+        update_kwargs = {
+            "min_kpps": "20000"
+        }
+
+        resp_body = {
+            "minimum_packet_rate_rule": copy.deepcopy(
+                self.FAKE_MIN_PPS_RULE_RESPONSE['minimum_packet_rate_rule']
+            )
+        }
+        resp_body["minimum_packet_rate_rule"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.qos_min_pps_client.update_minimum_packet_rate_rule,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MIN_PPS_RULE_ID,
+            **update_kwargs)
+
+    def test_create_minimum_packet_rate_rule_with_str_body(self):
+        self._test_create_minimum_packet_rate_rule()
+
+    def test_create_minimum_packet_rate_rule_with_bytes_body(self):
+        self._test_create_minimum_packet_rate_rule(bytes_body=True)
+
+    def test_update_minimum_packet_rate_rule_with_str_body(self):
+        self._test_update_qos_polcy()
+
+    def test_update_minimum_packet_rate_rule_with_bytes_body(self):
+        self._test_update_qos_polcy(bytes_body=True)
+
+    def test_show_minimum_packet_rate_rule_with_str_body(self):
+        self._test_show_minimum_packet_rate_rule()
+
+    def test_show_minimum_packet_rate_rule_with_bytes_body(self):
+        self._test_show_minimum_packet_rate_rule(bytes_body=True)
+
+    def test_delete_minimum_packet_rate_rule(self):
+        self.check_service_client_function(
+            self.qos_min_pps_client.delete_minimum_packet_rate_rule,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MIN_PPS_RULE_ID)
+
+    def test_list_minimum_packet_rate_rule_with_str_body(self):
+        self._test_list_minimum_packet_rate_rules()
+
+    def test_list_minimum_packet_rate_rule_with_bytes_body(self):
+        self._test_list_minimum_packet_rate_rules(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_routers_client.py b/tempest/tests/lib/services/network/test_routers_client.py
index f5dcc7d..20b6853 100644
--- a/tempest/tests/lib/services/network/test_routers_client.py
+++ b/tempest/tests/lib/services/network/test_routers_client.py
@@ -95,6 +95,67 @@
         }
     }
 
+    FAKE_ROUTER_ID = "f8a44de0-fc8e-45df-93c7-f79bf3b01c95"
+    FAKE_INTERFACE = {
+        "id": "915a14a6-867b-4af7-83d1-70efceb146f9",
+        "network_id": "91c013e2-d65a-474e-9177-c3e1799ca726",
+        "port_id": "2dc46bcc-d1f2-4077-b99e-91ee28afaff0",
+        "subnet_id": "a2f1f29d-571b-4533-907f-5803ab96ead1",
+        "subnet_ids": [
+            "a2f1f29d-571b-4533-907f-5803ab96ead1"
+        ],
+        "project_id": "0bd18306d801447bb457a46252d82d13",
+        "tenant_id": "0bd18306d801447bb457a46252d82d13",
+        "tags": ["tag1,tag2"]
+    }
+    FAKE_INTERFACE_KWARGS = {
+        "subnet_id": "a2f1f29d-571b-4533-907f-5803ab96ead1"
+    }
+    FAKE_SHOW_ROUTER = {
+        "router": {
+            "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.6",
+                        "subnet_id": "b930d7f6-ceb7-40a0-8b81-a425dd994ccf"
+                    },
+                    {
+                        "ip_address": "2001:db8::9",
+                        "subnet_id": "0c56df5d-ace5-46c8-8f4c-45fa4e334d18"
+                    }
+                ],
+                "network_id": "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3"
+            },
+            "flavor_id": "f7b14d9a-b0dc-4fbe-bb14-a0f4970a69e0",
+            "ha": "false",
+            "id": "f8a44de0-fc8e-45df-93c7-f79bf3b01c95",
+            "name": "router1",
+            "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",
+            "service_type_id": "null",
+            "tags": ["tag1,tag2"],
+            "conntrack_helpers": []
+        }
+    }
+
     def setUp(self):
         super(TestRoutersClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -116,6 +177,15 @@
             bytes_body,
             name="another_router", admin_state_up="true", status=201)
 
+    def _test_show_router(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_router,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_SHOW_ROUTER,
+            bytes_body,
+            200,
+            router_id=self.FAKE_ROUTER_ID)
+
     def _test_update_router(self, bytes_body=False):
         self.check_service_client_function(
             self.client.update_router,
@@ -125,6 +195,24 @@
             router_id="8604a0de-7f6b-409a-a47c-a1cc7bc77b2e",
             admin_state_up=False)
 
+    def _test_add_router_interface(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.add_router_interface,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_INTERFACE,
+            bytes_body,
+            router_id=self.FAKE_ROUTER_ID,
+            **self.FAKE_INTERFACE_KWARGS)
+
+    def _test_remove_router_interface(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.remove_router_interface,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_INTERFACE,
+            bytes_body,
+            router_id=self.FAKE_ROUTER_ID,
+            **self.FAKE_INTERFACE_KWARGS)
+
     def test_list_routers_with_str_body(self):
         self._test_list_routers()
 
@@ -148,3 +236,21 @@
 
     def test_update_router_with_bytes_body(self):
         self._test_update_router(bytes_body=True)
+
+    def test_show_router_with_str_body(self):
+        self._test_show_router()
+
+    def test_show_router_with_bytes_body(self):
+        self._test_show_router(bytes_body=True)
+
+    def test_add_router_interface_with_str_body(self):
+        self._test_add_router_interface()
+
+    def test_add_router_interface_with_bytes_body(self):
+        self._test_add_router_interface(bytes_body=True)
+
+    def test_remove_router_interface_with_str_body(self):
+        self._test_remove_router_interface()
+
+    def test_remove_router_interface_with_bytes_body(self):
+        self._test_remove_router_interface(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_subnetpools_client.py b/tempest/tests/lib/services/network/test_subnetpools_client.py
index 3abb438..2dfa25e 100644
--- a/tempest/tests/lib/services/network/test_subnetpools_client.py
+++ b/tempest/tests/lib/services/network/test_subnetpools_client.py
@@ -26,13 +26,13 @@
         "subnetpools": [
             {
                 "min_prefixlen": "64",
-                "address_scope_id": None,
+                "address_scope_id": "null",
                 "default_prefixlen": "64",
                 "id": "03f761e6-eee0-43fc-a921-8acf64c14988",
                 "max_prefixlen": "64",
                 "name": "my-subnet-pool-ipv6",
-                "default_quota": None,
-                "is_default": False,
+                "default_quota": "null",
+                "is_default": "false",
                 "project_id": "9fadcee8aa7c40cdb2114fff7d569c08",
                 "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08",
                 "prefixes": [
@@ -40,19 +40,22 @@
                     "2001:db8::/63"
                 ],
                 "ip_version": 6,
-                "shared": False,
+                "shared": "false",
                 "description": "",
-                "revision_number": 2
+                "created_at": "2016-03-08T20:19:41",
+                "updated_at": "2016-03-08T20:19:41",
+                "revision_number": 2,
+                "tags": ["tag1,tag2"]
             },
             {
                 "min_prefixlen": "24",
-                "address_scope_id": None,
+                "address_scope_id": "null",
                 "default_prefixlen": "25",
                 "id": "f49a1319-423a-4ee6-ba54-1d95a4f6cc68",
                 "max_prefixlen": "30",
                 "name": "my-subnet-pool-ipv4",
-                "default_quota": None,
-                "is_default": False,
+                "default_quota": "null",
+                "is_default": "false",
                 "project_id": "9fadcee8aa7c40cdb2114fff7d569c08",
                 "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08",
                 "prefixes": [
@@ -60,9 +63,12 @@
                     "192.168.0.0/16"
                 ],
                 "ip_version": 4,
-                "shared": False,
+                "shared": "false",
                 "description": "",
-                "revision_number": 2
+                "created_at": "2016-03-08T20:19:41",
+                "updated_at": "2016-03-08T20:19:41",
+                "revision_number": 2,
+                "tags": ["tag1,tag2"]
             }
         ]
     }
diff --git a/tempest/tests/lib/services/network/test_trunks_client.py b/tempest/tests/lib/services/network/test_trunks_client.py
new file mode 100644
index 0000000..b637d5e
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_trunks_client.py
@@ -0,0 +1,201 @@
+#    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 trunks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTrunksClient(base.BaseServiceTest):
+
+    FAKE_TRUNK_ID = "dfbc2103-93cf-4edf-952a-ef6deb32ddc6"
+    FAKE_PORT_ID = "1f04eb36-6c84-11eb-b0ab-4fc62961629d"
+    FAKE_TRUNKS = {
+        "trunks": [
+            {
+                "admin_state_up": True,
+                "description": "",
+                "id": "dfbc2103-93cf-4edf-952a-ef6deb32ddc6",
+                "name": "trunk0",
+                "port_id": "00130aab-bb51-42a1-a7c4-6703a3a43aa5",
+                "project_id": "",
+                "revision_number": 2,
+                "status": "DOWN",
+                "sub_ports": [
+                    {
+                        "port_id": "87d2483d-e5e6-483d-b5f0-81b9ed1d1a91",
+                        "segmentation_id": 101,
+                        "segmentation_type": "vlan"
+                        }
+                    ],
+                "tags": [],
+            },
+            {
+                "admin_state_up": True,
+                "description": "",
+                "id": "9eb0e72e-11d3-4295-bcaf-6c89008d9f0a",
+                "name": "trunk1",
+                "port_id": "035a12bf-2ae3-42ae-8ad6-9f70640cddde",
+                "project_id": "",
+                "revision_number": 2,
+                "status": "DOWN",
+                "sub_ports": [
+                    {
+                        "port_id": "cba839d5-02e2-4e09-b964-81356da78165",
+                        "segmentation_id": 102,
+                        "segmentation_type": "vlan"
+                        }
+                    ],
+                "tags": [],
+            },
+        ]
+    }
+
+    FAKE_TRUNK_1 = {
+        "name": "trunk0",
+        "port_id": "00130aab-bb51-42a1-a7c4-6703a3a43aa5"
+    }
+
+    def setUp(self):
+        super(TestTrunksClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.trunks_client = trunks_client.TrunksClient(
+            fake_auth, "network", "regionOne")
+
+    def _test_create_trunk(self, bytes_body=False):
+        self.check_service_client_function(
+            self.trunks_client.create_trunk,
+            "tempest.lib.common.rest_client.RestClient.post",
+            {"trunk": self.FAKE_TRUNKS["trunks"][0]},
+            bytes_body,
+            201,
+            **self.FAKE_TRUNK_1)
+
+    def _test_list_trunks(self, bytes_body=False):
+        self.check_service_client_function(
+            self.trunks_client.list_trunks,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_TRUNKS,
+            bytes_body,
+            200)
+
+    def _test_show_trunk(self, bytes_body=False):
+        self.check_service_client_function(
+            self.trunks_client.show_trunk,
+            "tempest.lib.common.rest_client.RestClient.get",
+            {"trunk": self.FAKE_TRUNKS["trunks"][0]},
+            bytes_body,
+            200,
+            trunk_id=self.FAKE_TRUNK_ID)
+
+    def _test_update_trunk(self, bytes_body=False):
+        update_kwargs = {
+            "admin_state_up": True,
+            "name": "new_trunk"
+        }
+
+        resp_body = {
+            "trunk": copy.deepcopy(
+                self.FAKE_TRUNKS["trunks"][0]
+            )
+        }
+        resp_body["trunk"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.trunks_client.update_trunk,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            trunk_id=self.FAKE_TRUNK_ID,
+            **update_kwargs)
+
+    def _test_add_subports_to_trunk(self, bytes_body=False):
+        sub_ports = [{
+            "port_id": "f04eb36-6c84-11eb-b0ab-4fc62961629d",
+            "segmentation_type": "vlan",
+            "segmentation_id": "1001"
+        }]
+        resp_body = copy.deepcopy(self.FAKE_TRUNKS["trunks"][0])
+
+        resp_body["sub_ports"].append(sub_ports)
+        self.check_service_client_function(
+            self.trunks_client.add_subports_to_trunk,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            trunk_id=self.FAKE_TRUNK_ID,
+            sub_ports=sub_ports)
+
+    def _test_delete_subports_from_trunk(self, bytes_body=False):
+        fake_sub_ports = self.FAKE_TRUNKS['trunks'][0]['sub_ports']
+        sub_ports = [
+            {"port_id": fake_sub_ports[0]['port_id']}
+        ]
+        resp_body = copy.deepcopy(self.FAKE_TRUNKS["trunks"][0])
+
+        resp_body['sub_ports'] = []
+        self.check_service_client_function(
+            self.trunks_client.delete_subports_from_trunk,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            trunk_id=self.FAKE_TRUNK_ID,
+            sub_ports=sub_ports)
+
+    def test_create_trunk_with_str_body(self):
+        self._test_create_trunk()
+
+    def test_create_trunk_with_bytes_body(self):
+        self._test_create_trunk(bytes_body=True)
+
+    def test_list_trunks_with_str_body(self):
+        self._test_list_trunks()
+
+    def test_list_trunks_with_bytes_body(self):
+        self._test_list_trunks(bytes_body=True)
+
+    def test_show_trunk_with_str_body(self):
+        self._test_show_trunk()
+
+    def test_show_trunk_with_bytes_body(self):
+        self._test_show_trunk(bytes_body=True)
+
+    def test_update_trunk_with_str_body(self):
+        self._test_update_trunk()
+
+    def test_update_trunk_with_bytes_body(self):
+        self._test_update_trunk(bytes_body=True)
+
+    def test_add_subports_to_trunk_str_body(self):
+        self._test_add_subports_to_trunk()
+
+    def test_add_subports_to_trunk_bytes_body(self):
+        self._test_add_subports_to_trunk(bytes_body=True)
+
+    def test_delete_subports_from_trunk_str_body(self):
+        self._test_delete_subports_from_trunk()
+
+    def test_delete_subports_from_trunk_bytes_body(self):
+        self._test_delete_subports_from_trunk(bytes_body=True)
+
+    def test_delete_trunk(self):
+        self.check_service_client_function(
+            self.trunks_client.delete_trunk,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            trunk_id=self.FAKE_TRUNK_ID)
diff --git a/tempest/tests/lib/services/object_storage/test_object_client.py b/tempest/tests/lib/services/object_storage/test_object_client.py
index c646d61..d6df243 100644
--- a/tempest/tests/lib/services/object_storage/test_object_client.py
+++ b/tempest/tests/lib/services/object_storage/test_object_client.py
@@ -31,15 +31,18 @@
         self.object_client = object_client.ObjectClient(self.fake_auth,
                                                         'swift', 'region1')
 
-    @mock.patch.object(object_client, '_create_connection')
+    @mock.patch('tempest.lib.services.object_storage.object_client.'
+                'ObjectClient._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')
+    @mock.patch('tempest.lib.services.object_storage.object_client.'
+                'ObjectClient._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')
+    @mock.patch('tempest.lib.services.object_storage.object_client.'
+                'ObjectClient._create_connection')
     def test_create_continue_with_no_continue_received(self, mock_poc):
         self._validate_create_object_continue('hello', mock_poc,
                                               initial_status=201)
diff --git a/tempest/tests/lib/services/placement/test_resource_providers_client.py b/tempest/tests/lib/services/placement/test_resource_providers_client.py
index 11aeaf2..2871395 100644
--- a/tempest/tests/lib/services/placement/test_resource_providers_client.py
+++ b/tempest/tests/lib/services/placement/test_resource_providers_client.py
@@ -48,6 +48,51 @@
     FAKE_RESOURCE_PROVIDER_AGGREGATES = {
         'aggregates': [FAKE_AGGREGATE_UUID]
     }
+    FAKE_RESOURCE_UPDATE_INVENTORIES_RESPONSE = {
+        "inventories": {
+            "MEMORY_MB": {
+                "allocation_ratio": 2.0,
+                "max_unit": 16,
+                "min_unit": 1,
+                "reserved": 0,
+                "step_size": 4,
+                "total": 128
+            },
+            "VCPU": {
+                "allocation_ratio": 10.0,
+                "max_unit": 2147483647,
+                "min_unit": 1,
+                "reserved": 2,
+                "step_size": 1,
+                "total": 64
+            }
+        },
+        "resource_provider_generation": 2
+    }
+    FAKE_RESOURCE_UPDATE_INVENTORIES_REQUEST = {
+        "inventories": {
+            "MEMORY_MB": {
+                "allocation_ratio": 2.0,
+                "max_unit": 16,
+                "step_size": 4,
+                "total": 128
+            },
+            "VCPU": {
+                "allocation_ratio": 10.0,
+                "reserved": 2,
+                "total": 64
+            }
+        },
+        "resource_provider_generation": 1
+    }
+    FAKE_RESOURCE_PROVIDER_USAGES = {
+        "usages": {
+            "VCPU": 2,
+            "MEMORY_MB": 1024,
+            "DISK_GB": 10
+        },
+        "resource_provider_generation": 3
+    }
 
     def setUp(self):
         super(TestResourceProvidersClient, self).setUp()
@@ -102,6 +147,32 @@
     def test_list_resource_provider_inventories_with_bytes_body(self):
         self._test_list_resource_provider_inventories(bytes_body=True)
 
+    def _test_update_resource_providers_inventories(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_resource_providers_inventories,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_RESOURCE_UPDATE_INVENTORIES_RESPONSE,
+            to_utf=bytes_body,
+            status=200,
+            rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID,
+            **self.FAKE_RESOURCE_UPDATE_INVENTORIES_REQUEST
+        )
+
+    def test_update_resource_providers_inventories_with_str_body(self):
+        self._test_update_resource_providers_inventories()
+
+    def test_update_resource_providers_inventories_with_bytes_body(self):
+        self._test_update_resource_providers_inventories(bytes_body=True)
+
+    def test_delete_resource_providers_inventories(self):
+        self.check_service_client_function(
+            self.client.delete_resource_providers_inventories,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            status=204,
+            rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID,
+        )
+
     def _test_list_resource_provider_aggregates(self, bytes_body=False):
         self.check_service_client_function(
             self.client.list_resource_provider_aggregates,
@@ -117,3 +188,19 @@
 
     def test_list_resource_provider_aggregates_with_bytes_body(self):
         self._test_list_resource_provider_aggregates(bytes_body=True)
+
+    def _test_list_resource_provider_usages(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_resource_provider_usages,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_RESOURCE_PROVIDER_USAGES,
+            to_utf=bytes_body,
+            status=200,
+            rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID
+        )
+
+    def test_show_resource_provider_usages_with_str_body(self):
+        self._test_list_resource_provider_inventories()
+
+    def test_show_resource_provider_usages_with_with_bytes_body(self):
+        self._test_list_resource_provider_inventories(bytes_body=True)
diff --git a/tempest/tests/lib/services/registry_fixture.py b/tempest/tests/lib/services/registry_fixture.py
index 07af68a..a368705 100644
--- a/tempest/tests/lib/services/registry_fixture.py
+++ b/tempest/tests/lib/services/registry_fixture.py
@@ -38,8 +38,7 @@
         """Initialise the registry fixture"""
         self.services = set(['compute', 'identity.v2', 'identity.v3',
                              'image.v1', 'image.v2', 'network', 'placement',
-                             'volume.v1', 'volume.v2', 'volume.v3',
-                             'object-storage'])
+                             'volume.v2', 'volume.v3', 'object-storage'])
 
     def _setUp(self):
         # Cleanup the registry
diff --git a/tempest/tests/lib/services/test_clients.py b/tempest/tests/lib/services/test_clients.py
index f83064a..6c79db6 100644
--- a/tempest/tests/lib/services/test_clients.py
+++ b/tempest/tests/lib/services/test_clients.py
@@ -16,7 +16,6 @@
 from unittest import mock
 
 import fixtures
-import six
 import testtools
 
 from tempest.lib import auth
@@ -270,8 +269,7 @@
                           'module_path': 'This neither',
                           'client_names': ['SomeClient1']}]}
         msg = "(?=.*{0})(?=.*{1})".format(
-            *[x[1][0]['module_path'] for x in six.iteritems(
-                fake_service_clients)])
+            *[x[1][0]['module_path'] for x in fake_service_clients.items()])
         self.useFixture(fixtures.MockPatchObject(
             clients.ClientsRegistry(), 'get_service_clients',
             return_value=fake_service_clients))
@@ -300,8 +298,8 @@
                           'module_path': 'fake_path_2',
                           'client_names': ['SomeClient2']}]}
         msg = "(?=.*{0})(?=.*{1})".format(
-            *[x[1][0]['service_version'] for x in six.iteritems(
-                fake_service_clients)])
+            *[x[1][0]['service_version'] for x in
+                fake_service_clients.items()])
         self.useFixture(fixtures.MockPatchObject(
             clients.ClientsRegistry(), 'get_service_clients',
             return_value=fake_service_clients))
diff --git a/tempest/tests/lib/services/volume/v1/test_encryption_types_client.py b/tempest/tests/lib/services/volume/v1/test_encryption_types_client.py
deleted file mode 100644
index 585904e..0000000
--- a/tempest/tests/lib/services/volume/v1/test_encryption_types_client.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Copyright 2016 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.lib.services.volume.v1 import encryption_types_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestEncryptionTypesClient(base.BaseServiceTest):
-    FAKE_CREATE_ENCRYPTION_TYPE = {
-        "encryption": {
-            "id": "cbc36478b0bd8e67e89",
-            "name": "FakeEncryptionType",
-            "type": "fakeType",
-            "provider": "LuksEncryptor",
-            "cipher": "aes-xts-plain64",
-            "key_size": "512",
-            "control_location": "front-end"
-        }
-    }
-
-    FAKE_INFO_ENCRYPTION_TYPE = {
-        "encryption": {
-            "name": "FakeEncryptionType",
-            "type": "fakeType",
-            "description": "test_description",
-            "volume_type": "fakeType",
-            "provider": "LuksEncryptor",
-            "cipher": "aes-xts-plain64",
-            "key_size": "512",
-            "control_location": "front-end"
-        }
-    }
-
-    def setUp(self):
-        super(TestEncryptionTypesClient, self).setUp()
-        fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.client = encryption_types_client.EncryptionTypesClient(fake_auth,
-                                                                    'volume',
-                                                                    'regionOne'
-                                                                    )
-
-    def _test_create_encryption(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.create_encryption_type,
-            'tempest.lib.common.rest_client.RestClient.post',
-            self.FAKE_CREATE_ENCRYPTION_TYPE,
-            bytes_body, volume_type_id="cbc36478b0bd8e67e89")
-
-    def _test_show_encryption_type(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.show_encryption_type,
-            'tempest.lib.common.rest_client.RestClient.get',
-            self.FAKE_INFO_ENCRYPTION_TYPE,
-            bytes_body, volume_type_id="cbc36478b0bd8e67e89")
-
-    def test_create_encryption_type_with_str_body(self):
-        self._test_create_encryption()
-
-    def test_create_encryption_type_with_bytes_body(self):
-        self._test_create_encryption(bytes_body=True)
-
-    def test_show_encryption_type_with_str_body(self):
-        self._test_show_encryption_type()
-
-    def test_show_encryption_type_with_bytes_body(self):
-        self._test_show_encryption_type(bytes_body=True)
-
-    def test_delete_encryption_type(self):
-        self.check_service_client_function(
-            self.client.delete_encryption_type,
-            'tempest.lib.common.rest_client.RestClient.delete',
-            {},
-            volume_type_id="cbc36478b0bd8e67e89",
-            status=202)
diff --git a/tempest/tests/lib/services/volume/v1/test_quotas_client.py b/tempest/tests/lib/services/volume/v1/test_quotas_client.py
deleted file mode 100644
index f9e76af..0000000
--- a/tempest/tests/lib/services/volume/v1/test_quotas_client.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# Copyright 2016 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.lib.services.volume.v1 import quotas_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestQuotasClient(base.BaseServiceTest):
-    FAKE_QUOTAS = {
-        "quota_set": {
-            "cores": 20,
-            "fixed_ips": -1,
-            "floating_ips": 10,
-            "id": "fake_tenant",
-            "injected_file_content_bytes": 10240,
-            "injected_file_path_bytes": 255,
-            "injected_files": 5,
-            "instances": 10,
-            "key_pairs": 100,
-            "metadata_items": 128,
-            "ram": 51200,
-            "security_group_rules": 20,
-            "security_groups": 10
-        }
-    }
-
-    FAKE_UPDATE_QUOTAS_REQUEST = {
-        "quota_set": {
-            "security_groups": 45
-        }
-    }
-
-    def setUp(self):
-        super(TestQuotasClient, self).setUp()
-        fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.client = quotas_client.QuotasClient(fake_auth,
-                                                 'volume',
-                                                 'regionOne')
-
-    def _test_show_default_quota_set(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.show_default_quota_set,
-            'tempest.lib.common.rest_client.RestClient.get',
-            self.FAKE_QUOTAS,
-            bytes_body, tenant_id="fake_tenant")
-
-    def _test_show_quota_set(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.show_quota_set,
-            'tempest.lib.common.rest_client.RestClient.get',
-            self.FAKE_QUOTAS,
-            bytes_body, tenant_id="fake_tenant")
-
-    def _test_update_quota_set(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.update_quota_set,
-            'tempest.lib.common.rest_client.RestClient.put',
-            self.FAKE_UPDATE_QUOTAS_REQUEST,
-            bytes_body, tenant_id="fake_tenant")
-
-    def test_show_default_quota_set_with_str_body(self):
-        self._test_show_default_quota_set()
-
-    def test_show_default_quota_set_with_bytes_body(self):
-        self._test_show_default_quota_set(bytes_body=True)
-
-    def test_show_quota_set_with_str_body(self):
-        self._test_show_quota_set()
-
-    def test_show_quota_set_with_bytes_body(self):
-        self._test_show_quota_set(bytes_body=True)
-
-    def test_update_quota_set_with_str_body(self):
-        self._test_update_quota_set()
-
-    def test_update_quota_set_with_bytes_body(self):
-        self._test_update_quota_set(bytes_body=True)
-
-    def test_delete_quota_set(self):
-        self.check_service_client_function(
-            self.client.delete_quota_set,
-            'tempest.lib.common.rest_client.RestClient.delete',
-            {},
-            tenant_id="fake_tenant")
diff --git a/tempest/tests/lib/services/volume/v1/test_snapshots_client.py b/tempest/tests/lib/services/volume/v1/test_snapshots_client.py
deleted file mode 100644
index 49191e3..0000000
--- a/tempest/tests/lib/services/volume/v1/test_snapshots_client.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# Copyright 2016 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.lib.services.volume.v1 import snapshots_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestSnapshotsClient(base.BaseServiceTest):
-    FAKE_CREATE_SNAPSHOT = {
-        "snapshot": {
-            "display_name": "snap-001",
-            "display_description": "Daily backup",
-            "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
-            "force": True
-        }
-    }
-
-    FAKE_UPDATE_SNAPSHOT_REQUEST = {
-        "metadata": {
-            "key": "v1"
-        }
-    }
-
-    FAKE_INFO_SNAPSHOT = {
-        "snapshot": {
-            "id": "3fbbcccf-d058-4502-8844-6feeffdf4cb5",
-            "display_name": "snap-001",
-            "display_description": "Daily backup",
-            "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
-            "status": "available",
-            "size": 30,
-            "created_at": "2012-02-29T03:50:07Z"
-        }
-    }
-
-    FAKE_LIST_SNAPSHOTS = {
-        "snapshots": [
-            {
-                "id": "3fbbcccf-d058-4502-8844-6feeffdf4cb5",
-                "display_name": "snap-001",
-                "display_description": "Daily backup",
-                "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
-                "status": "available",
-                "size": 30,
-                "created_at": "2012-02-29T03:50:07Z",
-                "metadata": {
-                    "contents": "junk"
-                }
-            },
-            {
-                "id": "e479997c-650b-40a4-9dfe-77655818b0d2",
-                "display_name": "snap-002",
-                "display_description": "Weekly backup",
-                "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358",
-                "status": "available",
-                "size": 25,
-                "created_at": "2012-03-19T01:52:47Z",
-                "metadata": {}
-            }
-        ]
-    }
-
-    def setUp(self):
-        super(TestSnapshotsClient, self).setUp()
-        fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.client = snapshots_client.SnapshotsClient(fake_auth,
-                                                       'volume',
-                                                       'regionOne')
-
-    def _test_create_snapshot(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.create_snapshot,
-            'tempest.lib.common.rest_client.RestClient.post',
-            self.FAKE_CREATE_SNAPSHOT,
-            bytes_body)
-
-    def _test_show_snapshot(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.show_snapshot,
-            'tempest.lib.common.rest_client.RestClient.get',
-            self.FAKE_INFO_SNAPSHOT,
-            bytes_body,
-            snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
-
-    def _test_list_snapshots(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.list_snapshots,
-            'tempest.lib.common.rest_client.RestClient.get',
-            self.FAKE_LIST_SNAPSHOTS,
-            bytes_body,
-            detail=True)
-
-    def _test_create_snapshot_metadata(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.create_snapshot_metadata,
-            'tempest.lib.common.rest_client.RestClient.post',
-            self.FAKE_INFO_SNAPSHOT,
-            bytes_body,
-            snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
-            metadata={"key": "v1"})
-
-    def _test_update_snapshot(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.update_snapshot,
-            'tempest.lib.common.rest_client.RestClient.put',
-            self.FAKE_UPDATE_SNAPSHOT_REQUEST,
-            bytes_body,
-            snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
-
-    def _test_show_snapshot_metadata(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.show_snapshot_metadata,
-            'tempest.lib.common.rest_client.RestClient.get',
-            self.FAKE_UPDATE_SNAPSHOT_REQUEST,
-            bytes_body,
-            snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
-
-    def _test_update_snapshot_metadata(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.update_snapshot_metadata,
-            'tempest.lib.common.rest_client.RestClient.put',
-            self.FAKE_UPDATE_SNAPSHOT_REQUEST,
-            bytes_body, snapshot_id="cbc36478b0bd8e67e89")
-
-    def _test_update_snapshot_metadata_item(self, bytes_body=False):
-        self.check_service_client_function(
-            self.client.update_snapshot_metadata_item,
-            'tempest.lib.common.rest_client.RestClient.put',
-            self.FAKE_INFO_SNAPSHOT,
-            bytes_body, volume_type_id="cbc36478b0bd8e67e89")
-
-    def test_create_snapshot_with_str_body(self):
-        self._test_create_snapshot()
-
-    def test_create_snapshot_with_bytes_body(self):
-        self._test_create_snapshot(bytes_body=True)
-
-    def test_show_snapshot_with_str_body(self):
-        self._test_show_snapshot()
-
-    def test_show_snapshot_with_bytes_body(self):
-        self._test_show_snapshot(bytes_body=True)
-
-    def test_list_snapshots_with_str_body(self):
-        self._test_list_snapshots()
-
-    def test_list_snapshots_with_bytes_body(self):
-        self._test_list_snapshots(bytes_body=True)
-
-    def test_create_snapshot_metadata_with_str_body(self):
-        self._test_create_snapshot_metadata()
-
-    def test_create_snapshot_metadata_with_bytes_body(self):
-        self._test_create_snapshot_metadata(bytes_body=True)
-
-    def test_update_snapshot_with_str_body(self):
-        self._test_update_snapshot()
-
-    def test_update_snapshot_with_bytes_body(self):
-        self._test_update_snapshot(bytes_body=True)
-
-    def test_show_snapshot_metadata_with_str_body(self):
-        self._test_show_snapshot_metadata()
-
-    def test_show_snapshot_metadata_with_bytes_body(self):
-        self._test_show_snapshot_metadata(bytes_body=True)
-
-    def test_update_snapshot_metadata_with_str_body(self):
-        self._test_update_snapshot_metadata()
-
-    def test_update_snapshot_metadata_with_bytes_body(self):
-        self._test_update_snapshot_metadata(bytes_body=True)
-
-    def test_force_delete_snapshot(self):
-        self.check_service_client_function(
-            self.client.force_delete_snapshot,
-            'tempest.lib.common.rest_client.RestClient.post',
-            {},
-            snapshot_id="521752a6-acf6-4b2d-bc7a-119f9148cd8c",
-            status=202)
-
-    def test_delete_snapshot(self):
-        self.check_service_client_function(
-            self.client.delete_snapshot,
-            'tempest.lib.common.rest_client.RestClient.delete',
-            {},
-            snapshot_id="521752a6-acf6-4b2d-bc7a-119f9148cd8c",
-            status=202)
diff --git a/tempest/tests/lib/services/volume/v3/test_transfers_client.py b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
index 1dfe2df..3626184 100644
--- a/tempest/tests/lib/services/volume/v3/test_transfers_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
@@ -52,6 +52,7 @@
         self.client = transfers_client.TransfersClient(fake_auth,
                                                        'volume',
                                                        'regionOne')
+        self.resource_path = 'os-volume-transfer'
 
     def _test_create_volume_transfer(self, bytes_body=False):
         resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO)
@@ -72,7 +73,7 @@
                 resp_body,
                 to_utf=bytes_body,
                 status=202,
-                mock_args=['os-volume-transfer', payload],
+                mock_args=[self.resource_path, payload],
                 **kwargs)
 
     def _test_accept_volume_transfer(self, bytes_body=False):
@@ -93,8 +94,9 @@
                 resp_body,
                 to_utf=bytes_body,
                 status=202,
-                mock_args=['os-volume-transfer/%s/accept' %
-                           self.FAKE_VOLUME_TRANSFER_ID, payload],
+                mock_args=['%s/%s/accept' % (self.resource_path,
+                                             self.FAKE_VOLUME_TRANSFER_ID),
+                           payload],
                 transfer_id=self.FAKE_VOLUME_TRANSFER_ID,
                 **kwargs)
 
@@ -156,3 +158,14 @@
             {},
             status=202,
             transfer_id="0e89cdd1-6249-421b-96d8-25fac0623d42")
+
+
+class TestTransfersV355Client(TestTransfersClient):
+
+    def setUp(self):
+        super(TestTransfersV355Client, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = transfers_client.TransfersV355Client(fake_auth,
+                                                           'volume',
+                                                           'regionOne')
+        self.resource_path = 'volume-transfers'
diff --git a/tempest/tests/lib/services/volume/v3/test_versions_client.py b/tempest/tests/lib/services/volume/v3/test_versions_client.py
index 575cae3..862fb9b 100644
--- a/tempest/tests/lib/services/volume/v3/test_versions_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_versions_client.py
@@ -22,21 +22,6 @@
     FAKE_VERSIONS_INFO = {
         "versions": [
             {
-                "status": "DEPRECATED", "updated": "2016-05-02T20:25:19Z",
-                "links": [
-                    {"href": "http://docs.openstack.org/", "type": "text/html",
-                     "rel": "describedby"},
-                    {"href": "https://10.30.197.39:8776/v1/", "rel": "self"}
-                ],
-                "min_version": "",
-                "version": "",
-                "media-types": [
-                    {"base": "application/json",
-                     "type": "application/vnd.openstack.volume+json;version=1"}
-                ],
-                "id": "v1.0"
-            },
-            {
                 "status": "DEPRECATED", "updated": "2017-02-25T12:00:00Z",
                 "links": [
                     {"href": "http://docs.openstack.org/", "type": "text/html",
@@ -134,8 +119,6 @@
         self._test_show_version('v3', bytes_body=True)
 
     def test_get_base_version_url_app_name(self):
-        self._test_get_base_version_url('https://bar.org/volume/v1/123',
-                                        'https://bar.org/volume/')
         self._test_get_base_version_url('https://bar.org/volume/v2/123',
                                         'https://bar.org/volume/')
         self._test_get_base_version_url('https://bar.org/volume/v3/123',
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index c3a792f..3edb122 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -786,6 +786,19 @@
                 self.assertIn(attr, auth_params.keys())
                 self.assertEqual(getattr(all_creds, attr), auth_params[attr])
 
+    def test_auth_parameters_with_system_scope(self):
+        all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+        self.auth_provider.credentials = all_creds
+        self.auth_provider.scope = 'system'
+        auth_params = self.auth_provider._auth_params()
+        self.assertNotIn('scope', auth_params.keys())
+        for attr in all_creds.get_init_attributes():
+            if attr.startswith('project_') or attr.startswith('domain_'):
+                self.assertNotIn(attr, auth_params.keys())
+            else:
+                self.assertIn(attr, auth_params.keys())
+                self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
 
 class TestKeystoneV3Credentials(base.TestCase):
     def testSetAttrUserDomain(self):
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index e3c17e8..fc93f76 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -16,7 +16,6 @@
 import abc
 from unittest import mock
 
-import six
 import testtools
 
 from tempest.lib import base as test
@@ -69,8 +68,7 @@
                                condition=True)
 
 
-@six.add_metaclass(abc.ABCMeta)
-class BaseSkipDecoratorTests(object):
+class BaseSkipDecoratorTests(object, metaclass=abc.ABCMeta):
 
     @abc.abstractmethod
     def _test_skip_helper(self, raise_exception=True, expected_to_skip=True,
diff --git a/tempest/tests/lib/test_ssh.py b/tempest/tests/lib/test_ssh.py
index 85048fb..886d99c 100644
--- a/tempest/tests/lib/test_ssh.py
+++ b/tempest/tests/lib/test_ssh.py
@@ -12,11 +12,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from io import StringIO
 import socket
 from unittest import mock
 
-import six
-from six import StringIO
 import testtools
 
 from tempest.lib.common import ssh
@@ -30,7 +29,7 @@
     SELECT_POLLIN = 1
 
     @mock.patch('paramiko.RSAKey.from_private_key')
-    @mock.patch('six.StringIO')
+    @mock.patch('io.StringIO')
     def test_pkey_calls_paramiko_RSAKey(self, cs_mock, rsa_mock):
         cs_mock.return_value = mock.sentinel.csio
         pkey = 'mykey'
@@ -240,7 +239,7 @@
 
         return chan_mock, poll_mock, select_mock, client_mock
 
-    _utf8_string = six.unichr(1071)
+    _utf8_string = chr(1071)
     _utf8_bytes = _utf8_string.encode("utf-8")
 
     @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
@@ -274,7 +273,7 @@
         client = ssh.Client('localhost', 'root', timeout=2)
         exc = self.assertRaises(exceptions.SSHExecCommandFailed,
                                 client.exec_command, "test")
-        self.assertIn('R' + self._utf8_string, six.text_type(exc))
+        self.assertIn('R' + self._utf8_string, str(exc))
 
     def test_exec_command_no_select(self):
         gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
index b154cd5..88c28bf 100644
--- a/tempest/tests/test_base_test.py
+++ b/tempest/tests/test_base_test.py
@@ -109,7 +109,7 @@
 
         test.BaseTestCase.get_tenant_network(credentials_type=creds)
 
-        mock_gcm.assert_called_once_with(roles=['role1'])
+        mock_gcm.assert_called_once_with(roles=['role1'], scope='project')
         mock_gprov.assert_called_once_with()
         mock_gtn.assert_called_once_with(mock_prov, net_client,
                                          self.fixed_network_name)
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index 6018441..1889420 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -19,7 +19,6 @@
 from tempest.common import utils
 from tempest import config
 from tempest import exceptions
-from tempest.lib.common.utils import data_utils
 from tempest import test
 from tempest.tests import base
 from tempest.tests import fake_config
@@ -33,47 +32,6 @@
                          fake_config.FakePrivate)
 
 
-# NOTE: The test module is for tempest.test.idempotent_id.
-# After all projects switch to use decorators.idempotent_id,
-# we can remove tempest.test.idempotent_id as well as this
-# test module
-class TestIdempotentIdDecorator(BaseDecoratorsTest):
-
-    def _test_helper(self, _id, **decorator_args):
-        @test.idempotent_id(_id)
-        def foo():
-            """Docstring"""
-            pass
-
-        return foo
-
-    def _test_helper_without_doc(self, _id, **decorator_args):
-        @test.idempotent_id(_id)
-        def foo():
-            pass
-
-        return foo
-
-    def test_positive(self):
-        _id = data_utils.rand_uuid()
-        foo = self._test_helper(_id)
-        self.assertIn('id-%s' % _id, getattr(foo, '__testtools_attrs'))
-        self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
-
-    def test_positive_without_doc(self):
-        _id = data_utils.rand_uuid()
-        foo = self._test_helper_without_doc(_id)
-        self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
-
-    def test_idempotent_id_not_str(self):
-        _id = 42
-        self.assertRaises(TypeError, self._test_helper, _id)
-
-    def test_idempotent_id_not_valid_uuid(self):
-        _id = '42'
-        self.assertRaises(ValueError, self._test_helper, _id)
-
-
 class TestServicesDecorator(BaseDecoratorsTest):
     def _test_services_helper(self, *decorator_args):
         class TestFoo(test.BaseTestCase):
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
index 1cc9c9a..fe44ef6 100644
--- a/tempest/tests/test_list_tests.py
+++ b/tempest/tests/test_list_tests.py
@@ -16,8 +16,6 @@
 import re
 import subprocess
 
-import six
-
 from tempest.tests import base
 
 
@@ -32,7 +30,7 @@
         self.assertEqual(0, p.returncode,
                          "test discovery failed, one or more files cause an "
                          "error on import %s" % ids)
-        ids = six.text_type(ids).split('\n')
+        ids = str(ids).split('\n')
         for test_id in ids:
             if re.match(r'(\w+\.){3}\w+', test_id):
                 if not test_id.startswith('tempest.'):
diff --git a/tempest/tests/test_microversions.py b/tempest/tests/test_microversions.py
index ee6db71..835f51c 100644
--- a/tempest/tests/test_microversions.py
+++ b/tempest/tests/test_microversions.py
@@ -13,7 +13,6 @@
 #    under the License.
 
 from oslo_config import cfg
-import six
 import testtools
 
 from tempest.api.compute import base as compute_base
@@ -75,7 +74,7 @@
                 self.assertRaises(testtools.TestCase.skipException,
                                   test_class.skip_checks)
         except testtools.TestCase.skipException as e:
-            raise testtools.TestCase.failureException(six.text_type(e))
+            raise testtools.TestCase.failureException(str(e))
 
     def test_config_version_none_none(self):
         expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2]
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index 72e8b6d..a95914a 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -24,6 +24,9 @@
 from tempest import config
 from tempest.lib.common import validation_resources as vr
 from tempest.lib import exceptions as lib_exc
+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
 from tempest import test
 from tempest.tests import base
 from tempest.tests import fake_config
@@ -453,6 +456,130 @@
             expected_creds[1][1:],
             mock_get_client_manager.mock_calls[1][2]['roles'])
 
+    def test_setup_credentials_with_role_and_system_scope(self):
+        expected_creds = [['system_my_role', 'role1', 'role2']]
+
+        class SystemRoleCredentials(self.parent_test):
+            credentials = expected_creds
+
+        expected_clients = 'clients'
+        with mock.patch.object(
+                SystemRoleCredentials,
+                'get_client_manager') as mock_get_client_manager:
+            mock_get_client_manager.return_value = expected_clients
+            sys_creds = SystemRoleCredentials()
+            sys_creds.setup_credentials()
+        self.assertTrue(hasattr(sys_creds, 'os_system_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_system_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role)
+        self.assertEqual(1, mock_get_client_manager.call_count)
+        self.assertEqual(
+            expected_creds[0][1:],
+            mock_get_client_manager.mock_calls[0][2]['roles'])
+        self.assertEqual(
+            'system',
+            mock_get_client_manager.mock_calls[0][2]['scope'])
+
+    def test_setup_credentials_with_multiple_role_and_system_scope(self):
+        expected_creds = [['system_my_role', 'role1', 'role2'],
+                          ['system_my_role2', 'role1', 'role2'],
+                          ['system_my_role3', 'role3']]
+
+        class SystemRoleCredentials(self.parent_test):
+            credentials = expected_creds
+
+        expected_clients = 'clients'
+        with mock.patch.object(
+                SystemRoleCredentials,
+                'get_client_manager') as mock_get_client_manager:
+            mock_get_client_manager.return_value = expected_clients
+            sys_creds = SystemRoleCredentials()
+            sys_creds.setup_credentials()
+        self.assertTrue(hasattr(sys_creds, 'os_system_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_system_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_system_my_role2'))
+        self.assertEqual(expected_clients, sys_creds.os_system_my_role2)
+        self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role2'))
+        self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role2)
+        self.assertTrue(hasattr(sys_creds, 'os_system_my_role3'))
+        self.assertEqual(expected_clients, sys_creds.os_system_my_role3)
+        self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role3'))
+        self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role3)
+        self.assertEqual(3, mock_get_client_manager.call_count)
+        self.assertEqual(
+            expected_creds[0][1:],
+            mock_get_client_manager.mock_calls[0][2]['roles'])
+        self.assertEqual(
+            'system', mock_get_client_manager.mock_calls[0][2]['scope'])
+        self.assertEqual(
+            expected_creds[1][1:],
+            mock_get_client_manager.mock_calls[1][2]['roles'])
+        self.assertEqual(
+            'system', mock_get_client_manager.mock_calls[1][2]['scope'])
+        self.assertEqual(
+            expected_creds[2][1:],
+            mock_get_client_manager.mock_calls[2][2]['roles'])
+        self.assertEqual(
+            'system', mock_get_client_manager.mock_calls[2][2]['scope'])
+
+    def test_setup_credentials_with_role_and_multiple_scope(self):
+        expected_creds = [['my_role', 'role1', 'role2'],
+                          ['project_my_role', 'role1', 'role2'],
+                          ['domain_my_role', 'role1', 'role2'],
+                          ['system_my_role', 'role1', 'role2']]
+
+        class SystemRoleCredentials(self.parent_test):
+            credentials = expected_creds
+
+        expected_clients = 'clients'
+        with mock.patch.object(
+                SystemRoleCredentials,
+                'get_client_manager') as mock_get_client_manager:
+            mock_get_client_manager.return_value = expected_clients
+            sys_creds = SystemRoleCredentials()
+            sys_creds.setup_credentials()
+        self.assertTrue(hasattr(sys_creds, 'os_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_roles_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_roles_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_project_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_project_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_roles_project_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_roles_project_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_domain_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_domain_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_roles_domain_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_roles_domain_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_system_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_system_my_role)
+        self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role'))
+        self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role)
+
+        self.assertEqual(4, mock_get_client_manager.call_count)
+        self.assertEqual(
+            expected_creds[0][1:],
+            mock_get_client_manager.mock_calls[0][2]['roles'])
+        self.assertEqual(
+            'project', mock_get_client_manager.mock_calls[0][2]['scope'])
+        self.assertEqual(
+            expected_creds[1][1:],
+            mock_get_client_manager.mock_calls[1][2]['roles'])
+        self.assertEqual(
+            'project', mock_get_client_manager.mock_calls[1][2]['scope'])
+        self.assertEqual(
+            expected_creds[2][1:],
+            mock_get_client_manager.mock_calls[2][2]['roles'])
+        self.assertEqual(
+            'domain', mock_get_client_manager.mock_calls[2][2]['scope'])
+        self.assertEqual(
+            expected_creds[3][1:],
+            mock_get_client_manager.mock_calls[3][2]['roles'])
+        self.assertEqual(
+            'system', mock_get_client_manager.mock_calls[3][2]['scope'])
+
     def test_setup_class_overwritten(self):
 
         class OverridesSetup(self.parent_test):
@@ -625,3 +752,186 @@
                          self.test.fixtures_invoked)
         found_exc = log[0][1][1]
         self.assertIn(expected_exc, str(found_exc))
+
+
+class TestAPIMicroversionTest1(test.BaseTestCase):
+
+    @classmethod
+    def resource_setup(cls):
+        super(TestAPIMicroversionTest1, cls).resource_setup()
+        # Setting microvesions and checks that every tests
+        # of this class will have those microversion set
+        # on service clients requesting service APIs.
+        cls.setup_api_microversion_fixture(
+            compute_microversion='2.30',
+            volume_microversion='3.10',
+            placement_microversion='3.1')
+        # Check microvesion is set during resource_setup()
+        if base_compute_client.COMPUTE_MICROVERSION != '2.30':
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+        if base_volume_client.VOLUME_MICROVERSION != '3.10':
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+        if base_placement_client.PLACEMENT_MICROVERSION != '3.1':
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+
+    @classmethod
+    def resource_cleanup(cls):
+        super(TestAPIMicroversionTest1, cls).resource_cleanup()
+        # Check microversion is reset back to None in resource_cleanup()
+        if base_compute_client.COMPUTE_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+        if base_volume_client.VOLUME_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+        if base_placement_client.PLACEMENT_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+
+    def setUp(self):
+        super(TestAPIMicroversionTest1, self).setUp()
+        # Check microversion is set in setUp method also.
+        self.assertEqual('2.30', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.10', base_volume_client.VOLUME_MICROVERSION)
+        self.assertEqual('3.1', base_placement_client.PLACEMENT_MICROVERSION)
+
+    def tearDown(self):
+        super(TestAPIMicroversionTest1, self).tearDown()
+        # Check microversion is set in tearDown method also.
+        self.assertEqual('2.30', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.10', base_volume_client.VOLUME_MICROVERSION)
+        self.assertEqual('3.1', base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_1(self):
+        self.assertEqual('2.30', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.10', base_volume_client.VOLUME_MICROVERSION)
+        self.assertEqual('3.1', base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_2(self):
+        self.assertEqual('2.30', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.10', base_volume_client.VOLUME_MICROVERSION)
+        self.assertEqual('3.1', base_placement_client.PLACEMENT_MICROVERSION)
+
+
+class TestAPIMicroversionTest2(test.BaseTestCase):
+
+    @classmethod
+    def resource_setup(cls):
+        super(TestAPIMicroversionTest2, cls).resource_setup()
+        # Setting microvesions different from what set in
+        # MicroversionTest1 and checks that every tests
+        # of this class will have the new microversion set
+        # on service clients requesting service APIs.
+        cls.setup_api_microversion_fixture(
+            compute_microversion='2.80',
+            volume_microversion='3.80',
+            placement_microversion='3.8')
+        # Check microvesion is set during resource_setup()
+        if base_compute_client.COMPUTE_MICROVERSION != '2.80':
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+        if base_volume_client.VOLUME_MICROVERSION != '3.80':
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+        if base_placement_client.PLACEMENT_MICROVERSION != '3.8':
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+
+    @classmethod
+    def resource_cleanup(cls):
+        super(TestAPIMicroversionTest2, cls).resource_cleanup()
+        # Check microversion is reset back to None in resource_cleanup()
+        if base_compute_client.COMPUTE_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+        if base_volume_client.VOLUME_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+        if base_placement_client.PLACEMENT_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+
+    def setUp(self):
+        super(TestAPIMicroversionTest2, self).setUp()
+        # Check microversion is set in setUp method also.
+        self.assertEqual('2.80', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.80', base_volume_client.VOLUME_MICROVERSION)
+        self.assertEqual('3.8', base_placement_client.PLACEMENT_MICROVERSION)
+
+    def tearDown(self):
+        super(TestAPIMicroversionTest2, self).tearDown()
+        # Check microversion is set in tearDown method also.
+        self.assertEqual('2.80', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.80', base_volume_client.VOLUME_MICROVERSION)
+        self.assertEqual('3.8', base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_1(self):
+        self.assertEqual('2.80', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.80', base_volume_client.VOLUME_MICROVERSION)
+        self.assertEqual('3.8', base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_2(self):
+        self.assertEqual('2.80', base_compute_client.COMPUTE_MICROVERSION)
+        self.assertEqual('3.80', base_volume_client.VOLUME_MICROVERSION)
+        self.assertEqual('3.8', base_placement_client.PLACEMENT_MICROVERSION)
+
+
+class TestAPIMicroversionTest3(test.BaseTestCase):
+
+    @classmethod
+    def resource_setup(cls):
+        super(TestAPIMicroversionTest3, cls).resource_setup()
+        # Not setting microversion for this test class so
+        # there should not be any micorversion set on service
+        # clients requesting services APIs.
+        # Check microvesion is not set during resource_setup()
+        if base_compute_client.COMPUTE_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+        if base_volume_client.VOLUME_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+        if base_placement_client.PLACEMENT_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not set in resource_setup method")
+
+    @classmethod
+    def resource_cleanup(cls):
+        super(TestAPIMicroversionTest3, cls).resource_cleanup()
+        # Check microversion is set to None in resource_cleanup()
+        if base_compute_client.COMPUTE_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+        if base_volume_client.VOLUME_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+        if base_placement_client.PLACEMENT_MICROVERSION is not None:
+            raise testtools.TestCase.failureException(
+                "Microversion is not reset to None in resource_cleanup method")
+
+    def setUp(self):
+        super(TestAPIMicroversionTest3, self).setUp()
+        # Check microversion is None in setUp method also.
+        self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION)
+        self.assertIsNone(base_volume_client.VOLUME_MICROVERSION)
+        self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION)
+
+    def tearDown(self):
+        super(TestAPIMicroversionTest3, self).tearDown()
+        # Check microversion is None in tearDown method also.
+        self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION)
+        self.assertIsNone(base_volume_client.VOLUME_MICROVERSION)
+        self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_1(self):
+        self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION)
+        self.assertIsNone(base_volume_client.VOLUME_MICROVERSION)
+        self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION)
+
+    def test_2(self):
+        self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION)
+        self.assertIsNone(base_volume_client.VOLUME_MICROVERSION)
+        self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION)
diff --git a/tools/check_logs.py b/tools/check_logs.py
index de7e41d..8ab3af2 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -17,12 +17,12 @@
 
 import argparse
 import gzip
+import io
 import os
 import re
 import sys
+import urllib.request as urlreq
 
-import six
-import six.moves.urllib.request as urlreq
 import yaml
 
 # DEVSTACK_GATE_GRENADE is either unset if grenade is not running
@@ -56,39 +56,39 @@
     's-proxy'])
 
 
-def process_files(file_specs, url_specs, whitelists):
+def process_files(file_specs, url_specs, allow_lists):
     regexp = re.compile(r"^.* (ERROR|CRITICAL|TRACE) .*\[.*\-.*\]")
     logs_with_errors = []
     for (name, filename) in file_specs:
-        whitelist = whitelists.get(name, [])
+        allow_list = allow_lists.get(name, [])
         with open(filename) as content:
-            if scan_content(content, regexp, whitelist):
+            if scan_content(content, regexp, allow_list):
                 logs_with_errors.append(name)
     for (name, url) in url_specs:
-        whitelist = whitelists.get(name, [])
+        allow_list = allow_lists.get(name, [])
         req = urlreq.Request(url)
         req.add_header('Accept-Encoding', 'gzip')
         page = urlreq.urlopen(req)
-        buf = six.StringIO(page.read())
+        buf = io.StringIO(page.read())
         f = gzip.GzipFile(fileobj=buf)
-        if scan_content(f.read().splitlines(), regexp, whitelist):
+        if scan_content(f.read().splitlines(), regexp, allow_list):
             logs_with_errors.append(name)
     return logs_with_errors
 
 
-def scan_content(content, regexp, whitelist):
+def scan_content(content, regexp, allow_list):
     had_errors = False
     for line in content:
         if not line.startswith("Stderr:") and regexp.match(line):
-            whitelisted = False
-            for w in whitelist:
+            allowed = False
+            for w in allow_list:
                 pat = ".*%s.*%s.*" % (w['module'].replace('.', '\\.'),
                                       w['message'])
                 if re.match(pat, line):
-                    whitelisted = True
+                    allowed = True
                     break
-            if not whitelisted or dump_all_errors:
-                if not whitelisted:
+            if not allowed or dump_all_errors:
+                if not allowed:
                     had_errors = True
     return had_errors
 
@@ -105,9 +105,9 @@
         print("Must provide exactly one of -d or -u")
         return 1
     print("Checking logs...")
-    WHITELIST_FILE = os.path.join(
+    ALLOW_LIST_FILE = os.path.join(
         os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
-        "etc", "whitelist.yaml")
+        "etc", "allow-list.yaml")
 
     file_matcher = re.compile(r".*screen-([\w-]+)\.log")
     files = []
@@ -132,17 +132,17 @@
         if m:
             urls_to_process.append((m.group(1), u))
 
-    whitelists = {}
-    with open(WHITELIST_FILE) as stream:
+    allow_lists = {}
+    with open(ALLOW_LIST_FILE) as stream:
         loaded = yaml.safe_load(stream)
         if loaded:
-            for (name, l) in six.iteritems(loaded):
+            for (name, l) in loaded.values():
                 for w in l:
                     assert 'module' in w, 'no module in %s' % name
                     assert 'message' in w, 'no message in %s' % name
-            whitelists = loaded
+            allow_lists = loaded
     logs_with_errors = process_files(files_to_process, urls_to_process,
-                                     whitelists)
+                                     allow_lists)
 
     failed = False
     if logs_with_errors:
@@ -164,14 +164,14 @@
 
 
 usage = """
-Find non-white-listed log errors in log files from a devstack-gate run.
+Find non-allow-listed log errors in log files from a devstack-gate run.
 Log files will be searched for ERROR or CRITICAL messages. If any
-error messages do not match any of the whitelist entries contained in
-etc/whitelist.yaml, those messages will be printed to the console and
+error messages do not match any of the allow-list entries contained in
+etc/allow-list.yaml, those messages will be printed to the console and
 failure will be returned. A file directory containing logs or a url to the
 log files of an OpenStack gate job can be provided.
 
-The whitelist yaml looks like:
+The allow-list yaml looks like:
 
 log-name:
     - module: "a.b.c"
@@ -179,7 +179,7 @@
     - module: "a.b.c"
       message: "regexp"
 
-repeated for each log file with a whitelist.
+repeated for each log file with an allow-list.
 """
 
 parser = argparse.ArgumentParser(description=usage)
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 618c388..b96bbe4 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -32,28 +32,51 @@
 
 # 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
-# when the patches are merged.
-BLACKLIST = [
-    'x/gce-api',  # It looks gce-api doesn't support python3 yet.
+# TODO(masayukig): Some of these can be removed from NON_ACTIVE_LIST in the
+# future when the patches are merged.
+NON_ACTIVE_LIST = [
+    'x/gce-api',  # It looks gce-api doesn't support python3 yet
+    # https://bugs.launchpad.net/gce-api/+bug/1931094
     'x/glare',  # To avoid sanity-job failure
-    'x/group-based-policy',  # It looks this doesn't support python3 yet.
-    'x/intel-nfv-ci-tests',  # https://review.opendev.org/#/c/634640/
+    'x/group-based-policy',
+    # https://bugs.launchpad.net/group-based-policy/+bug/1931091
+    'x/intel-nfv-ci-tests',  # To avoid sanity-job failure
     'openstack/networking-generic-switch',
+    # This is not a real tempest plugin,
     # https://review.opendev.org/#/c/634846/
-    'x/networking-l2gw-tempest-plugin',
-    # https://review.opendev.org/#/c/635093/
-    'openstack/networking-midonet',  # https://review.opendev.org/#/c/635096/
-    'x/networking-plumgrid',  # https://review.opendev.org/#/c/635096/
+    'x/networking-plumgrid',  # No longer contains tempest tests
     'x/networking-spp',  # https://review.opendev.org/#/c/635098/
+    # networking-spp is missing neutron-tempest-plugin as a dep plus
+    # test-requirements.txt is nested in a openstack dir and sanity script
+    # doesn't count with such scenario yet
     'openstack/neutron-dynamic-routing',
+    # As tests have been migrated to neutron-tempest-plugin:
     # https://review.opendev.org/#/c/637718/
-    'openstack/neutron-vpnaas',  # https://review.opendev.org/#/c/637719/
-    'x/tap-as-a-service',  # To avoid sanity-job failure
-    'x/valet',  # https://review.opendev.org/#/c/638339/
-    'x/kingbird',  # https://bugs.launchpad.net/kingbird/+bug/1869722
-    # vmware-nsx is blacklisted since https://review.opendev.org/#/c/736952
-    'x/vmware-nsx-tempest-plugin',
+    'openstack/neutron-vpnaas',
+    # As tests have been migrated to neutron-tempest-plugin:
+    # https://review.opendev.org/c/openstack/neutron-vpnaas/+/695834
+    'x/valet',  # valet is unmaintained now
+    # https://review.opendev.org/c/x/valet/+/638339
+    'x/kingbird',  # kingbird is unmaintained now
+    # https://bugs.launchpad.net/kingbird/+bug/1869722
+    'x/mogan',
+    # mogan is unmaintained now, remove from the list when this is merged:
+    # https://review.opendev.org/c/x/mogan/+/767718
+    'x/vmware-nsx-tempest-plugin'
+    # Failing since 2021-08-27
+    # https://zuul.opendev.org/t/openstack/build
+    # /45f6c8d3c62d4387a70b7b471ec687c8
+    # Below plugins failing for error in psycopg2 __init__
+    # ImportError: libpq.so.5: cannot open shared object
+    # file: No such file or directory
+    # https://zuul.opendev.org/t/openstack/build
+    # /b61a48196dfa476d83645aea4853e544/log/job-output.txt#271722
+    # Failing since 2021-09-08
+    'x/networking-l2gw-tempest-plugin'
+    'x/novajoin-tempest-plugin'
+    'x/ranger-tempest-plugin'
+    'x/tap-as-a-service-tempest-plugin'
+    'x/trio2o'
 ]
 
 url = 'https://review.opendev.org/projects/'
@@ -86,10 +109,10 @@
         False
 
 
-if len(sys.argv) > 1 and sys.argv[1] == 'blacklist':
-    for black_plugin in BLACKLIST:
-        print(black_plugin)
-    # We just need BLACKLIST when we use this `blacklist` option.
+if len(sys.argv) > 1 and sys.argv[1] == 'nonactivelist':
+    for non_active_plugin in NON_ACTIVE_LIST:
+        print(non_active_plugin)
+    # We just need NON_ACTIVE_LIST when we use this `nonactivelist` option.
     # So, this exits here.
     sys.exit()
 
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index 33675ed..4430bbf 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -81,17 +81,17 @@
 
 printf "\n\n"
 
-# Print BLACKLIST
-if [[ -r doc/source/data/tempest-blacklisted-plugins-registry.header ]]; then
-    cat doc/source/data/tempest-blacklisted-plugins-registry.header
+# Print NON_ACTIVE_LIST
+if [[ -r doc/source/data/tempest-non-active-plugins-registry.header ]]; then
+    cat doc/source/data/tempest-non-active-plugins-registry.header
 fi
 
-blacklist=$(python tools/generate-tempest-plugins-list.py blacklist)
-name_col_len=$(echo "${blacklist}" | wc -L)
+nonactivelist=$(python tools/generate-tempest-plugins-list.py nonactivelist)
+name_col_len=$(echo "${nonactivelist}" | wc -L)
 name_col_len=$(( name_col_len + 20 ))
 
 printf "\n\n"
-print_plugin_table "${blacklist}"
+print_plugin_table "${nonactivelist}"
 
 printf "\n\n"
 
diff --git a/tools/tempest-integrated-gate-compute-blacklist.txt b/tools/tempest-integrated-gate-compute-exclude-list.txt
similarity index 60%
rename from tools/tempest-integrated-gate-compute-blacklist.txt
rename to tools/tempest-integrated-gate-compute-exclude-list.txt
index 2290751..8805262 100644
--- a/tools/tempest-integrated-gate-compute-blacklist.txt
+++ b/tools/tempest-integrated-gate-compute-exclude-list.txt
@@ -11,9 +11,3 @@
 tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_basic_ops
 tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_acl_anonymous_download
 tempest.scenario.test_volume_backup_restore.TestVolumeBackupRestore.test_volume_backup_restore
-
-# Skip test scenario when creating second image from instance
-# https://bugs.launchpad.net/tripleo/+bug/1881592
-# The test is most likely wrong and may fail if the fists image is create quickly.
-# FIXME: Either fix the test so it won't race or consider if we should cover the scenario at all.
-tempest.api.compute.images.test_images_oneserver_negative.ImagesOneServerNegativeTestJSON.test_create_second_image_when_first_image_is_being_saved
diff --git a/tools/tempest-integrated-gate-networking-blacklist.txt b/tools/tempest-integrated-gate-networking-exclude-list.txt
similarity index 72%
rename from tools/tempest-integrated-gate-networking-blacklist.txt
rename to tools/tempest-integrated-gate-networking-exclude-list.txt
index 97808d9..263b2e4 100644
--- a/tools/tempest-integrated-gate-networking-blacklist.txt
+++ b/tools/tempest-integrated-gate-networking-exclude-list.txt
@@ -17,8 +17,3 @@
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
-
-# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
-tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
-tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
-tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-integrated-gate-object-storage-blacklist.txt b/tools/tempest-integrated-gate-object-storage-exclude-list.txt
similarity index 100%
rename from tools/tempest-integrated-gate-object-storage-blacklist.txt
rename to tools/tempest-integrated-gate-object-storage-exclude-list.txt
diff --git a/tools/tempest-integrated-gate-placement-blacklist.txt b/tools/tempest-integrated-gate-placement-exclude-list.txt
similarity index 73%
rename from tools/tempest-integrated-gate-placement-blacklist.txt
rename to tools/tempest-integrated-gate-placement-exclude-list.txt
index 657bda2..efba796 100644
--- a/tools/tempest-integrated-gate-placement-blacklist.txt
+++ b/tools/tempest-integrated-gate-placement-exclude-list.txt
@@ -17,8 +17,3 @@
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
 tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
-
-# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
-tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
-tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
-tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-blacklist.txt
new file mode 120000
index 0000000..2d691f8
--- /dev/null
+++ b/tools/tempest-integrated-gate-storage-blacklist.txt
@@ -0,0 +1 @@
+tempest-integrated-gate-storage-exclude-list.txt
\ No newline at end of file
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-exclude-list.txt
similarity index 60%
rename from tools/tempest-integrated-gate-storage-blacklist.txt
rename to tools/tempest-integrated-gate-storage-exclude-list.txt
index cbd3e9d..1ef6bb5 100644
--- a/tools/tempest-integrated-gate-storage-blacklist.txt
+++ b/tools/tempest-integrated-gate-storage-exclude-list.txt
@@ -12,8 +12,3 @@
 tempest.scenario.test_network_basic_ops.TestNetworkBasicOps
 tempest.scenario.test_network_v6.TestGettingAddress
 tempest.scenario.test_security_groups_basic_ops.TestSecurityGroupsBasicOps
-
-# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
-tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
-tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
-tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index c983da9..106a9c6 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -44,7 +44,7 @@
 # retrieve a list of projects having tempest plugins
 PROJECT_LIST="$(python tools/generate-tempest-plugins-list.py)"
 
-BLACKLIST="$(python tools/generate-tempest-plugins-list.py blacklist)"
+NON_ACTIVE_LIST="$(python tools/generate-tempest-plugins-list.py nonactivelist)"
 
 # Function to clone project using zuul-cloner or from git
 function clone_project {
@@ -117,8 +117,8 @@
 failed_plugin=''
 # Perform sanity on all tempest plugin projects
 for project in $PROJECT_LIST; do
-    # Remove blacklisted tempest plugins
-    if ! [[ `echo $BLACKLIST | grep -c $project ` -gt 0 ]]; then
+    # Remove non-active tempest plugins
+    if ! [[ `echo $NON_ACTIVE_LIST | grep -c $project ` -gt 0 ]]; then
         plugin_sanity_check $project && passed_plugin+=", $project" || \
         failed_plugin+="$project, " > $SANITY_DIR/$project.txt
     fi
diff --git a/tools/verify-ipv6-only-deployments.sh b/tools/verify-ipv6-only-deployments.sh
index 2596395..bfb1403 100755
--- a/tools/verify-ipv6-only-deployments.sh
+++ b/tools/verify-ipv6-only-deployments.sh
@@ -1,92 +1,8 @@
 #!/bin/bash
-#
-#
-# NOTE(gmann): This script is used in 'devstack-tempest-ipv6' zuul job to verify that
-# services are deployed on IPv6 properly or not. This will capture if any devstck or devstack
-# plugins are missing the required setting to listen on IPv6 address. This is run as part of
-# run phase of zuul job and before test run. Child job of 'devstack-tempest-ipv6'
-# can expand the IPv6 verification specific to project by defining the new post-run script which
-# will run along with this base script.
-# If there are more common verification for IPv6 then we can always extent this script.
 
-# Keep track of the DevStack directory
-TOP_DIR=$(cd $(dirname "$0")/../../devstack && pwd)
-source $TOP_DIR/stackrc
-source $TOP_DIR/openrc admin admin
+# NOTE(yoctozepto): This scripts lives now in devstack where it belongs.
+# It is kept here for the legacy (dsvm) jobs which look for it in tempest still.
+# TODO: Drop it when no legacy jobs use the master tempest.
 
-function verify_devstack_ipv6_setting {
-    local _service_host=''
-    _service_host=$(echo $SERVICE_HOST | tr -d [])
-    local _host_ipv6=''
-    _host_ipv6=$(echo $HOST_IPV6 | tr -d [])
-    local _service_listen_address=''
-    _service_listen_address=$(echo $SERVICE_LISTEN_ADDRESS | tr -d [])
-    local _service_local_host=''
-    _service_local_host=$(echo $SERVICE_LOCAL_HOST | tr -d [])
-    if [[ "$SERVICE_IP_VERSION" != 6 ]]; then
-        echo $SERVICE_IP_VERSION "SERVICE_IP_VERSION is not set to 6 which is must for devstack to deploy services with IPv6 address."
-        exit 1
-    fi
-    is_service_host_ipv6=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$_service_host'"))')
-    if [[ "$is_service_host_ipv6" != "True" ]]; then
-        echo $SERVICE_HOST "SERVICE_HOST is not ipv6 which means devstack cannot deploy services on IPv6 address."
-        exit 1
-    fi
-    is_host_ipv6=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$_host_ipv6'"))')
-    if [[ "$is_host_ipv6" != "True" ]]; then
-        echo $HOST_IPV6 "HOST_IPV6 is not ipv6 which means devstack cannot deploy services on IPv6 address."
-        exit 1
-    fi
-    is_service_listen_address=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$_service_listen_address'"))')
-    if [[ "$is_service_listen_address" != "True" ]]; then
-        echo $SERVICE_LISTEN_ADDRESS "SERVICE_LISTEN_ADDRESS is not ipv6 which means devstack cannot deploy services on IPv6 address."
-        exit 1
-    fi
-    is_service_local_host=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$_service_local_host'"))')
-    if [[ "$is_service_local_host" != "True" ]]; then
-        echo $SERVICE_LOCAL_HOST "SERVICE_LOCAL_HOST is not ipv6 which means devstack cannot deploy services on IPv6 address."
-        exit 1
-    fi
-    echo "Devstack is properly configured with IPv6"
-    echo "SERVICE_IP_VERSION: " $SERVICE_IP_VERSION "HOST_IPV6: " $HOST_IPV6 "SERVICE_HOST: " $SERVICE_HOST "SERVICE_LISTEN_ADDRESS: " $SERVICE_LISTEN_ADDRESS "SERVICE_LOCAL_HOST: " $SERVICE_LOCAL_HOST
-}
-
-function sanity_check_system_ipv6_enabled {
-    system_ipv6_enabled=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_ipv6_enabled())')
-    if [[ $system_ipv6_enabled != "True" ]]; then
-        echo "IPv6 is disabled in system"
-        exit 1
-    fi
-    echo "IPv6 is enabled in system"
-}
-
-function verify_service_listen_address_is_ipv6 {
-    local endpoints_verified=False
-    local all_ipv6=True
-    endpoints=$(openstack endpoint list -f value -c URL)
-    for endpoint in ${endpoints}; do
-        local endpoint_address=''
-        endpoint_address=$(echo "$endpoint" | awk -F/ '{print $3}' | awk -F] '{print $1}')
-        endpoint_address=$(echo $endpoint_address | tr -d [])
-        local is_endpoint_ipv6=''
-        is_endpoint_ipv6=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$endpoint_address'"))')
-        if [[ "$is_endpoint_ipv6" != "True" ]]; then
-            all_ipv6=False
-            echo $endpoint ": This is not ipv6 endpoint which means corresponding service is not listening on IPv6 address."
-            continue
-        fi
-        endpoints_verified=True
-    done
-    if [[ "$all_ipv6" == "False"  ]] || [[ "$endpoints_verified" == "False" ]]; then
-        exit 1
-    fi
-    echo "All services deployed by devstack is on IPv6 endpoints"
-    echo $endpoints
-}
-
-#First thing to verify if system has IPv6 enabled or not
-sanity_check_system_ipv6_enabled
-#Verify whether devstack is configured properly with IPv6 setting
-verify_devstack_ipv6_setting
-#Get all registrfed endpoints by devstack in keystone and verify that each endpoints address is IPv6.
-verify_service_listen_address_is_ipv6
+DEVSTACK_DIR=$(cd $(dirname "$0")/../../devstack && pwd)
+$DEVSTACK_DIR/tools/verify-ipv6-only-deployments.sh
diff --git a/tox.ini b/tox.ini
index 2ea8129..18f2aa6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
-envlist = pep8,py36,py38,bashate,pip-check-reqs
-minversion = 3.1.1
+envlist = pep8,py36,py39,bashate,pip-check-reqs
+minversion = 3.18.0
 skipsdist = True
 ignore_basepython_conflict = True
 
@@ -10,6 +10,7 @@
 setenv =
     VIRTUAL_ENV={envdir}
     OS_TEST_PATH=./tempest/test_discover
+    OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
 deps =
     -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
     -r{toxinidir}/requirements.txt
@@ -23,10 +24,10 @@
     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
+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
 usedevelop = True
 install_command = pip install {opts} {packages}
-whitelist_externals = *
+allowlist_externals = *
 deps =
     -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
     -r{toxinidir}/requirements.txt
@@ -61,7 +62,6 @@
 # 'all' includes slow tests
 setenv =
     {[tempestenv]setenv}
-    OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
 deps = {[tempestenv]deps}
 commands =
     find . -type f -name "*.pyc" -delete
@@ -79,7 +79,6 @@
 # 'all' includes slow tests
 setenv =
     {[tempestenv]setenv}
-    OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
 basepython = {[tempestenv]basepython}
 deps = {[tempestenv]deps}
 commands =
@@ -93,7 +92,6 @@
 # 'all' includes slow tests
 setenv =
     {[tempestenv]setenv}
-    OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200}
 basepython = {[tempestenv]basepython}
 deps = {[tempestenv]deps}
 commands =
@@ -108,7 +106,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.
+# FIXME: We can replace it with the `--exclude-regex` option to exclude tests now.
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
@@ -125,6 +123,18 @@
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(^tempest\.scenario.*)|(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
 
+[testenv:api-microversion-tests]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+basepython = {[tempestenv]basepython}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select all tempest api tests for services having API
+# microversion concept.
+commands =
+    find . -type f -name "*.pyc" -delete
+    tempest run --regex '(^tempest\.api\.compute)|(^tempest\.api\.volume)' {posargs}
+
 [testenv:integrated-network]
 envdir = .tox/tempest
 sitepackages = {[tempestenv]sitepackages}
@@ -132,11 +142,11 @@
 setenv = {[tempestenv]setenv}
 deps = {[tempestenv]deps}
 # The regex below is used to select which tests to run and exclude the slow tag and
-# tests listed in blacklist file:
+# tests listed in exclude-list file:
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-networking-blacklist.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-networking-blacklist.txt {posargs}
+    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-networking-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-networking-exclude-list.txt {posargs}
 
 [testenv:integrated-compute]
 envdir = .tox/tempest
@@ -145,11 +155,11 @@
 setenv = {[tempestenv]setenv}
 deps = {[tempestenv]deps}
 # The regex below is used to select which tests to run and exclude the slow tag and
-# tests listed in blacklist file:
+# tests listed in exclude-list file:
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-compute-blacklist.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-compute-blacklist.txt {posargs}
+    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
 
 [testenv:integrated-placement]
 envdir = .tox/tempest
@@ -158,11 +168,11 @@
 setenv = {[tempestenv]setenv}
 deps = {[tempestenv]deps}
 # The regex below is used to select which tests to run and exclude the slow tag and
-# tests listed in blacklist file:
+# tests listed in exclude-list file:
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-placement-blacklist.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-placement-blacklist.txt {posargs}
+    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-placement-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-placement-exclude-list.txt {posargs}
 
 [testenv:integrated-storage]
 envdir = .tox/tempest
@@ -171,11 +181,11 @@
 setenv = {[tempestenv]setenv}
 deps = {[tempestenv]deps}
 # The regex below is used to select which tests to run and exclude the slow tag and
-# tests listed in blacklist file:
+# tests listed in exclude-list file:
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-storage-blacklist.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-storage-blacklist.txt {posargs}
+    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-storage-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-storage-exclude-list.txt {posargs}
 
 [testenv:integrated-object-storage]
 envdir = .tox/tempest
@@ -184,11 +194,11 @@
 setenv = {[tempestenv]setenv}
 deps = {[tempestenv]deps}
 # The regex below is used to select which tests to run and exclude the slow tag and
-# tests listed in blacklist file:
+# tests listed in exclude-list file:
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-object-storage-blacklist.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-object-storage-blacklist.txt {posargs}
+    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-object-storage-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-object-storage-exclude-list.txt {posargs}
 
 [testenv:full-serial]
 envdir = .tox/tempest
@@ -198,7 +208,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.
+# FIXME: We can replace it with the `--exclude-regex` option to exclude tests now.
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' {posargs}
@@ -290,12 +300,12 @@
   sphinx-apidoc -f -o doc/source/tests/volume tempest/api/volume
   rm -rf doc/build
   sphinx-build -W -b html doc/source doc/build/html
-whitelist_externals =
+allowlist_externals =
     rm
 
 [testenv:pdf-docs]
 deps = {[testenv:docs]deps}
-whitelist_externals =
+allowlist_externals =
    rm
    make
 commands =
@@ -335,7 +345,8 @@
 # E123 skipped because it is ignored by default in the default pep8
 # E129 skipped because it is too limiting when combined with other rules
 # W504 skipped because it is overeager and unnecessary
-ignore = E125,E123,E129,W504
+# H405 skipped because it arbitrarily forces doctring "title" lines
+ignore = E125,E123,E129,W504,H405
 show-source = True
 exclude = .git,.venv,.tox,dist,doc,*egg,build
 enable-extensions = H106,H203,H904
@@ -369,7 +380,7 @@
   rm -rf releasenotes/build
   sphinx-build -a -E -W -d releasenotes/build/doctrees \
          -b html releasenotes/source releasenotes/build/html
-whitelist_externals = rm
+allowlist_externals = rm
 
 [testenv:bashate]
 # if you want to test out some changes you have made to bashate
@@ -377,7 +388,7 @@
 # modified bashate tree
 deps =
    {env:BASHATE_INSTALL_PATH:bashate}
-whitelist_externals = bash
+allowlist_externals = bash
 commands = bash -c "find {toxinidir}/tools    \
          -not \( -type d -name .?\* -prune \) \
          -type f                              \
@@ -406,6 +417,18 @@
 
 [testenv:plugin-sanity-check]
 # perform tempest plugin sanity
-whitelist_externals = bash
+allowlist_externals = bash
 commands =
   bash tools/tempest-plugin-sanity.sh
+
+[testenv:stestr-master]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+basepython = {[tempestenv]basepython}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The below command install stestr master version and run smoke tests
+commands =
+    find . -type f -name "*.pyc" -delete
+    pip install -U git+https://github.com/mtreinish/stestr
+    tempest run --regex '\[.*\bsmoke\b.*\]' {posargs}
diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml
new file mode 100644
index 0000000..3deb944
--- /dev/null
+++ b/zuul.d/base.yaml
@@ -0,0 +1,86 @@
+- job:
+    name: devstack-tempest
+    parent: devstack
+    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: &base_required-projects
+      - opendev.org/openstack/tempest
+    timeout: 7200
+    roles: &base_roles
+      - zuul: opendev.org/openstack/devstack
+    vars: &base_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: devstack-tempest-ipv6
+    parent: devstack-ipv6
+    description: |
+      Base Tempest IPv6 job. This job is derived from 'devstack-ipv6'
+      which set the IPv6-only setting for OpenStack services. As part of
+      run phase, this job will verify the IPv6 setting and check the services
+      endpoints and listen addresses are IPv6. Basically it will run the script
+      ./tool/verify-ipv6-only-deployments.sh
+
+      Child jobs of this job can run their own set of tests and can
+      add post-run playebooks to extend the IPv6 verification specific
+      to their deployed services.
+      Check the wiki page for more details about project jobs setup
+      - https://wiki.openstack.org/wiki/Goal-IPv6-only-deployments-and-testing
+    required-projects: *base_required-projects
+    timeout: 7200
+    roles: *base_roles
+    vars: *base_vars
+    run: playbooks/devstack-tempest-ipv6.yaml
+    post-run: playbooks/post-tempest.yaml
+
+- 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
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
new file mode 100644
index 0000000..fad17dd
--- /dev/null
+++ b/zuul.d/integrated-gate.yaml
@@ -0,0 +1,416 @@
+# NOTE(gmann): This file includes all integrated jobs definition which
+# are supposed to be run by Tempest and other projects as
+# integrated testing.
+- 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
+      # TODO(gmann): Enable File injection tests once nova bug is fixed
+      # https://bugs.launchpad.net/nova/+bug/1882421
+      # devstack_localrc:
+      #   ENABLE_FILE_INJECTION: true
+
+- job:
+    name: tempest-ipv6-only
+    parent: devstack-tempest-ipv6
+    # This currently works from stable/pike on.
+    branches: ^(?!stable/ocata).*$
+    description: |
+      Integration test of IPv6-only deployments. This job runs
+      smoke and IPv6 relates tests only. Basic idea is to test
+      whether OpenStack Services listen on IPv6 addrress or not.
+    timeout: 10800
+    vars:
+      tox_envlist: ipv6-only
+
+- 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.
+      This job is supposed to run until stable/train setup only.
+      If you are running it on stable/ussuri gate onwards for python2.7
+      coverage then you need to do override-checkout with any stable
+      branch less than or equal to stable/train.
+      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
+        USE_PYTHON3: False
+      devstack_services:
+        # NOTE(mriedem): Disable the cinder-backup service from tempest-full
+        # since tempest-full is in the integrated-gate project template but
+        # the backup tests do not really involve other services so they should
+        # be run in some more cinder-specific job, especially because the
+        # tests fail at a high rate (see bugs 1483434, 1813217, 1745168)
+        c-bak: false
+
+- job:
+    name: tempest-full-py3
+    parent: devstack-tempest
+    # This job version is with swift enabled on py3
+    # as swift is ready on py3 from stable/ussuri onwards.
+    branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train)).*$
+    description: |
+      Base integration test with Neutron networking, horizon, swift enable,
+      and py3.
+      Former names for this job where:
+        * legacy-tempest-dsvm-py35
+        * gate-tempest-dsvm-py35
+    required-projects:
+      - openstack/horizon
+    vars:
+      tox_envlist: full
+      devstack_localrc:
+        USE_PYTHON3: true
+        FORCE_CONFIG_DRIVE: true
+        ENABLE_VOLUME_MULTIATTACH: true
+        GLANCE_USE_IMPORT_WORKFLOW: True
+      devstack_plugins:
+        neutron: https://opendev.org/openstack/neutron
+      devstack_services:
+        # Enbale horizon so that we can run horizon test.
+        horizon: true
+
+- job:
+    name: tempest-integrated-networking
+    parent: devstack-tempest
+    branches: ^(?!stable/ocata).*$
+    description: |
+      This  job runs integration tests for networking. This is subset of
+      'tempest-full-py3' job and run only Neutron and Nova related tests.
+      This is meant to be run on neutron gate only.
+    vars:
+      tox_envlist: integrated-network
+      devstack_localrc:
+        USE_PYTHON3: true
+        FORCE_CONFIG_DRIVE: true
+      devstack_services:
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        c-bak: false
+
+- job:
+    name: tempest-integrated-compute
+    parent: devstack-tempest
+    branches: ^(?!stable/ocata).*$
+    description: |
+      This job runs integration tests for compute. This is
+      subset of 'tempest-full-py3' job and run Nova, Neutron, Cinder (except backup tests)
+      and Glance related tests. This is meant to be run on Nova gate only.
+    vars:
+      tox_envlist: integrated-compute
+      tempest_exclude_regex: ""
+      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
+        c-bak: false
+
+- job:
+    name: tempest-integrated-compute-centos-8-stream
+    parent: tempest-integrated-compute
+    # TODO(gmann): Make this job non voting until bug#1957941 if fixed.
+    voting: false
+    nodeset: devstack-single-node-centos-8-stream
+    branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train|ussuri|victoria)).*$
+    description: |
+      This job runs integration tests for compute. This is
+      subset of 'tempest-full-py3' job and run Nova, Neutron, Cinder (except backup tests)
+      and Glance related tests. This is meant to be run on Nova gate only.
+      This version of the job also uses CentOS 8 stream.
+    vars:
+      # Required until bug/1949606 is resolved when using libvirt and QEMU
+      # >=5.0.0 with a [libvirt]virt_type of qemu (TCG).
+      configure_swap_size: 4096
+
+- job:
+    name: tempest-integrated-placement
+    parent: devstack-tempest
+    branches: ^(?!stable/ocata).*$
+    description: |
+      This job runs integration tests for placement. This is
+      subset of 'tempest-full-py3' job and run Nova and Neutron
+      related tests. This is meant to be run on Placement gate only.
+    vars:
+      tox_envlist: integrated-placement
+      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
+        c-bak: false
+
+- job:
+    name: tempest-integrated-storage
+    parent: devstack-tempest
+    branches: ^(?!stable/ocata).*$
+    description: |
+      This job runs integration tests for image & block storage. This is
+      subset of 'tempest-full-py3' job and run Cinder, Glance, Swift and Nova
+      related tests. This is meant to be run on Cinder and Glance gate only.
+    vars:
+      tox_envlist: integrated-storage
+      devstack_localrc:
+        USE_PYTHON3: true
+        FORCE_CONFIG_DRIVE: true
+        ENABLE_VOLUME_MULTIATTACH: true
+        GLANCE_USE_IMPORT_WORKFLOW: True
+
+- job:
+    name: tempest-integrated-object-storage
+    parent: devstack-tempest
+    branches: ^(?!stable/ocata).*$
+    description: |
+      This job runs integration tests for object storage. This is
+      subset of 'tempest-full-py3' job and run Swift, Cinder and Glance
+      related tests. This is meant to be run on Swift gate only.
+    vars:
+      tox_envlist: integrated-object-storage
+      devstack_localrc:
+        # NOTE(gmann): swift is not ready on python3 yet and devstack
+        # install it on python2.7 only. But settting the USE_PYTHON3
+        # for future once swift is ready on py3.
+        USE_PYTHON3: true
+
+- job:
+    name: tempest-with-latest-microversion
+    parent: tempest-full-py3
+    description: |
+      This job runs compute, placement and volume API tests with 'latest'
+      API microversion (This can be extended to other services having API
+      microversion concept).
+    vars:
+      tox_envlist: api-microversion-tests
+      devstack_localrc:
+        TEMPEST_COMPUTE_MIN_MICROVERSION: 'latest'
+        TEMPEST_VOLUME_MIN_MICROVERSION: 'latest'
+        TEMPEST_PLACEMENT_MIN_MICROVERSION: 'latest'
+
+- job:
+    name: tempest-multinode-full
+    parent: tempest-multinode-full-base
+    nodeset: openstack-two-node-focal
+    # This job runs on Focal from stable/victoria on.
+    branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train|ussuri)).*$
+    vars:
+      devstack_localrc:
+        USE_PYTHON3: False
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: False
+
+- job:
+    name: tempest-multinode-full-py3
+    parent: tempest-multinode-full
+    vars:
+      devstack_localrc:
+        USE_PYTHON3: true
+      devstack_plugins:
+        neutron: https://opendev.org/openstack/neutron
+      devstack_services:
+        neutron-trunk: true
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: true
+
+- 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
+    # This job runs on stable/stein onwards.
+    branches: ^(?!stable/(ocata|pike|queens|rocky)).*$
+    vars: &tempest_slow_vars
+      tox_envlist: slow-serial
+      devstack_localrc:
+        CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
+        ENABLE_VOLUME_MULTIATTACH: true
+      devstack_plugins:
+        neutron: https://opendev.org/openstack/neutron
+      devstack_services:
+        neutron-placement: true
+        neutron-qos: 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-multinode-full-py3
+    # This job version is with swift enabled on py3
+    # as swift is ready on py3 from stable/ussuri onwards.
+    timeout: 10800
+    branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train)).*$
+    vars: *tempest_slow_vars
+
+- job:
+    name: tempest-cinder-v2-api
+    parent: devstack-tempest
+    # NOTE(gmann): Cinder v2 APIs are available until
+    # stable/wallaby only.
+    override-checkout: stable/wallaby
+    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-centos8-stream-fips
+    parent: devstack-tempest
+    description: |
+      Integration testing for a FIPS enabled Centos 8 system
+    nodeset: devstack-single-node-centos-8-stream
+    pre-run: playbooks/enable-fips.yaml
+    vars:
+      tox_envlist: full
+      configure_swap_size: 4096
+      devstack_local_conf:
+        test-config:
+          "$TEMPEST_CONFIG":
+            validation:
+              ssh_key_type: 'ecdsa'
+
+- job:
+    name: tempest-pg-full
+    parent: tempest-full-py3
+    description: |
+      Base integration test with Neutron networking and PostgreSQL.
+      Former name for this job was legacy-tempest-dsvm-neutron-pg-full.
+    vars:
+      devstack_localrc:
+        # TODO(gmann): Enable File injection tests once nova bug is fixed
+        # https://bugs.launchpad.net/nova/+bug/1882421
+        # ENABLE_FILE_INJECTION: true
+        DATABASE_TYPE: postgresql
+
+- project-template:
+    name: integrated-gate-networking
+    description: |
+      Run the python3 Tempest network integration tests (Nova and Neutron related)
+      in check and gate for the neutron integrated gate. This is meant to be
+      run on neutron gate only.
+    check:
+      jobs:
+        - grenade
+        - tempest-integrated-networking
+        - openstacksdk-functional-devstack
+    gate:
+      jobs:
+        - grenade
+        - tempest-integrated-networking
+        - openstacksdk-functional-devstack
+
+- project-template:
+    name: integrated-gate-compute
+    description: |
+      Run the python3 Tempest compute integration tests
+      (Nova, Neutron, Cinder and Glance related) in check and gate
+      for the Nova integrated gate. This is meant to be
+      run on Nova gate only.
+    check:
+      jobs:
+        - tempest-integrated-compute
+        - tempest-integrated-compute-centos-8-stream
+        - openstacksdk-functional-devstack
+    gate:
+      jobs:
+        - tempest-integrated-compute
+        - tempest-integrated-compute-centos-8-stream
+        - openstacksdk-functional-devstack
+
+- project-template:
+    name: integrated-gate-placement
+    description: |
+      Run the python3 Tempest placement integration tests
+      (Nova and Neutron related) in check and gate
+      for the Placement integrated gate. This is meant to be
+      run on Placement gate only.
+    check:
+      jobs:
+        - grenade
+        - tempest-integrated-placement
+        - openstacksdk-functional-devstack
+    gate:
+      jobs:
+        - grenade
+        - tempest-integrated-placement
+        - openstacksdk-functional-devstack
+
+- project-template:
+    name: integrated-gate-storage
+    description: |
+      Run the python3 Tempest image & block storage integration tests
+      (Cinder, Glance, Swift and Nova related) in check and gate
+      for the neutron integrated gate. This is meant to be
+      run on Cinder and Glance gate only.
+    check:
+      jobs:
+        - grenade
+        - tempest-integrated-storage
+        - openstacksdk-functional-devstack
+    gate:
+      jobs:
+        - grenade
+        - tempest-integrated-storage
+        - openstacksdk-functional-devstack
+
+- project-template:
+    name: integrated-gate-object-storage
+    description: |
+      Run the python3 Tempest object storage integration tests
+      (Swift, Cinder and Glance related) in check and gate
+      for the swift integrated gate. This is meant to be
+      run on swift gate only.
+    check:
+      jobs:
+        - grenade
+        - tempest-integrated-object-storage
+        - openstacksdk-functional-devstack
+    gate:
+      jobs:
+        - grenade
+        - tempest-integrated-object-storage
+        - openstacksdk-functional-devstack
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
new file mode 100644
index 0000000..3f98f7e
--- /dev/null
+++ b/zuul.d/project.yaml
@@ -0,0 +1,176 @@
+- project:
+    templates:
+      - check-requirements
+      - integrated-gate-py3
+      - openstack-cover-jobs
+      - publish-openstack-docs-pti
+      - release-notes-jobs-python3
+    check:
+      jobs:
+        - openstack-tox-pep8
+        - openstack-tox-py36
+        - openstack-tox-py37
+        - openstack-tox-py38
+        - openstack-tox-py39
+        - 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/.*$
+              - ^.coveragerc$
+              - ^.gitignore$
+              - ^.gitreview$
+              - ^.mailmap$
+        - tempest-full-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3-ipv6:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - glance-multistore-cinder-import:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-xena:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-wallaby-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-victoria-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-ussuri-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-multinode-full-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-tox-plugin-sanity-check:
+            irrelevant-files: &tempest-irrelevant-files-2
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+              - ^.coveragerc$
+              - ^.gitignore$
+              - ^.gitreview$
+              - ^.mailmap$
+              - ^tools/check_logs.py
+              - ^tools/format.sh
+              - ^tools/skip_tracker.py
+              - ^tools/tempest-integrated-gate-compute-exclude-list.txt
+              - ^tools/tempest-integrated-gate-networking-exclude-list.txt
+              - ^tools/tempest-integrated-gate-object-storage-exclude-list.txt
+              - ^tools/tempest-integrated-gate-placement-exclude-list.txt
+              - ^tools/tempest-integrated-gate-storage-blacklist.txt
+              - ^tools/tempest-integrated-gate-storage-exclude-list.txt
+              - ^tools/verify-ipv6-only-deployments.sh
+              - ^tools/with_venv.sh
+              # tools/ is not here since this relies on a script in tools/.
+        - tempest-ipv6-only:
+            irrelevant-files: &tempest-irrelevant-files-3
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+              - ^tools/check_logs.py
+              - ^tools/format.sh
+              - ^tools/generate-tempest-plugins-list.py
+              - ^tools/generate-tempest-plugins-list.sh
+              - ^tools/skip_tracker.py
+              - ^tools/tempest-integrated-gate-compute-exclude-list.txt
+              - ^tools/tempest-integrated-gate-networking-exclude-list.txt
+              - ^tools/tempest-integrated-gate-object-storage-exclude-list.txt
+              - ^tools/tempest-integrated-gate-placement-exclude-list.txt
+              - ^tools/tempest-integrated-gate-storage-blacklist.txt
+              - ^tools/tempest-integrated-gate-storage-exclude-list.txt
+              - ^tools/tempest-plugin-sanity.sh
+              - ^tools/with_venv.sh
+              - ^.coveragerc$
+              - ^.gitignore$
+              - ^.gitreview$
+              - ^.mailmap$
+        - tempest-slow-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - nova-live-migration:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - devstack-plugin-ceph-tempest-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-ovs-grenade-multinode:
+            irrelevant-files: *tempest-irrelevant-files
+        - grenade:
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-ovs-tempest-dvr:
+            voting: false
+            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
+        - openstack-tox-bashate:
+            irrelevant-files: *tempest-irrelevant-files-2
+        - tempest-full-py3-centos-8-stream:
+            irrelevant-files: *tempest-irrelevant-files
+    gate:
+      jobs:
+        - openstack-tox-pep8
+        - openstack-tox-py36
+        - openstack-tox-py37
+        - openstack-tox-py38
+        - openstack-tox-py39
+        - tempest-slow-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-ovs-grenade-multinode:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3-centos-8-stream:
+            irrelevant-files: *tempest-irrelevant-files
+        - grenade:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-ipv6-only:
+            irrelevant-files: *tempest-irrelevant-files-3
+        - devstack-plugin-ceph-tempest-py3:
+            irrelevant-files: *tempest-irrelevant-files
+    experimental:
+      jobs:
+        - tempest-with-latest-microversion
+        - tempest-stestr-master
+        - tempest-cinder-v2-api:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-all:
+            irrelevant-files: *tempest-irrelevant-files
+        - neutron-ovs-tempest-dvr-ha-multinode-full:
+            irrelevant-files: *tempest-irrelevant-files
+        - nova-tempest-v2-api:
+            irrelevant-files: *tempest-irrelevant-files
+        - cinder-tempest-lvm-multibackend:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-pg-full:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3-opensuse15:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-centos8-stream-fips:
+            irrelevant-files: *tempest-irrelevant-files
+    periodic-stable:
+      jobs:
+        - tempest-full-xena
+        - tempest-full-wallaby-py3
+        - tempest-full-victoria-py3
+        - tempest-full-ussuri-py3
+    periodic:
+      jobs:
+        - tempest-all
+        - tempest-full-oslo-master
+        - tempest-stestr-master
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
new file mode 100644
index 0000000..da6cc46
--- /dev/null
+++ b/zuul.d/stable-jobs.yaml
@@ -0,0 +1,180 @@
+# NOTE(gmann): This file includes all stable release jobs definition.
+- job:
+    name: tempest-full-xena
+    parent: tempest-full-py3
+    override-checkout: stable/xena
+
+- job:
+    name: tempest-full-wallaby-py3
+    parent: tempest-full-py3
+    override-checkout: stable/wallaby
+
+- job:
+    name: tempest-full-victoria-py3
+    parent: tempest-full-py3
+    override-checkout: stable/victoria
+
+- job:
+    name: tempest-full-ussuri-py3
+    parent: tempest-full-py3
+    nodeset: openstack-single-node-bionic
+    override-checkout: stable/ussuri
+
+- job:
+    name: tempest-full-py3
+    parent: devstack-tempest
+    # This job version is with swift disabled on py3
+    # as swift was not ready on py3 until stable/train.
+    branches:
+      - stable/pike
+      - stable/queens
+      - stable/rocky
+      - stable/stein
+      - stable/train
+    description: |
+      Base integration test with Neutron networking, swift disabled, and py3.
+      Former names for this job where:
+        * legacy-tempest-dsvm-py35
+        * gate-tempest-dsvm-py35
+    required-projects:
+      - openstack/horizon
+    vars:
+      tox_envlist: full
+      devstack_localrc:
+        USE_PYTHON3: true
+        FORCE_CONFIG_DRIVE: true
+        ENABLE_VOLUME_MULTIATTACH: true
+        GLANCE_USE_IMPORT_WORKFLOW: True
+      devstack_plugins:
+        neutron: https://opendev.org/openstack/neutron
+      devstack_local_conf:
+        post-config:
+          "/$NEUTRON_CORE_PLUGIN_CONF":
+            ovs:
+              bridge_mappings: public:br-ex
+              resource_provider_bandwidths: br-ex:1000000:1000000
+        test-config:
+          $TEMPEST_CONFIG:
+            network-feature-enabled:
+              qos_placement_physnet: public
+      devstack_services:
+        # Enbale horizon so that we can run horizon test.
+        horizon: true
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        # without Swift, c-bak cannot run (in the Gate at least)
+        # NOTE(mriedem): Disable the cinder-backup service from
+        # tempest-full-py3 since tempest-full-py3 is in the integrated-gate-py3
+        # project template but the backup tests do not really involve other
+        # services so they should be run in some more cinder-specific job,
+        # especially because the tests fail at a high rate (see bugs 1483434,
+        # 1813217, 1745168)
+        c-bak: false
+        neutron-placement: true
+        neutron-qos: true
+
+- job:
+    name: tempest-multinode-full
+    parent: tempest-multinode-full-base
+    nodeset: openstack-two-node-bionic
+    # This job runs on Bionic and on python2. This is for stable/stein and stable/train.
+    # This job is prepared to make sure all stable branches from stable/stein till stable/train
+    # will keep running on bionic. This can be removed once stable/train is EOL.
+    branches:
+      - stable/stein
+      - stable/train
+      - stable/ussuri
+    vars:
+      devstack_localrc:
+        USE_PYTHON3: False
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: False
+
+- 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
+    vars:
+      devstack_localrc:
+        USE_PYTHON3: False
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: 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
+    branches:
+      - stable/pike
+      - stable/queens
+      - stable/rocky
+    vars:
+      tox_envlist: slow-serial
+      devstack_localrc:
+        CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
+        ENABLE_VOLUME_MULTIATTACH: true
+        # to avoid https://bugs.launchpad.net/neutron/+bug/1914037
+        # as we couldn't backport the fix to rocky and older releases
+        IPV6_PUBLIC_RANGE: 2001:db8:0:10::/64
+        IPV6_PUBLIC_NETWORK_GATEWAY: 2001:db8:0:10::2
+        IPV6_ROUTER_GW_IP: 2001:db8:0:10::1
+      devstack_plugins:
+        neutron: https://opendev.org/openstack/neutron
+      devstack_services:
+        neutron-placement: true
+        neutron-qos: 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
+    # This job version is with swift disabled on py3
+    # as swift was not ready on py3 until stable/train.
+    branches:
+      - stable/pike
+      - stable/queens
+      - stable/rocky
+      - stable/stein
+      - stable/train
+    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
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: true
diff --git a/zuul.d/tempest-specific.yaml b/zuul.d/tempest-specific.yaml
new file mode 100644
index 0000000..a24c73d
--- /dev/null
+++ b/zuul.d/tempest-specific.yaml
@@ -0,0 +1,145 @@
+# NOTE(gmann): This file includes all tempest specific jobs definition which
+# are supposed to be run by Tempest gate only.
+- job:
+    name: tempest-full-oslo-master
+    parent: tempest-full-py3
+    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-py3
+    voting: false
+    branches:
+      - master
+    description: |
+      Base integration test with Neutron networking.
+      It includes all scenarios as it was in the past.
+      This job runs all scenario tests in parallel!
+    timeout: 9000
+    vars:
+      tox_envlist: full-parallel
+      run_tempest_cleanup: true
+      run_tempest_dry_cleanup: true
+      devstack_localrc:
+        DEVSTACK_PARALLEL: True
+
+- job:
+    name: tempest-full-py3-ipv6
+    parent: devstack-tempest-ipv6
+    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-full-py3-opensuse15
+    parent: tempest-full-py3
+    nodeset: devstack-single-node-opensuse-15
+    description: |
+      Base integration test with Neutron networking and py36 running
+      on openSUSE Leap 15.x
+    voting: false
+
+- job:
+    name: tempest-full-py3-centos-8-stream
+    parent: tempest-full-py3
+    # TODO(gmann): Make this job non voting until bug#1957941 if fixed.
+    voting: false
+    nodeset: devstack-single-node-centos-8-stream
+    description: |
+      Base integration test with Neutron networking and py36 running
+      on CentOS 8 stream
+    vars:
+      # Required until bug/1949606 is resolved when using libvirt and QEMU
+      # >=5.0.0 with a [libvirt]virt_type of qemu (TCG).
+      configure_swap_size: 4096
+
+- job:
+    name: tempest-tox-plugin-sanity-check
+    parent: tox
+    description: |
+      Run tempest plugin sanity check script using tox.
+    nodeset: ubuntu-focal
+    vars:
+      tox_envlist: plugin-sanity-check
+    timeout: 5000
+
+- 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-stestr-master
+    parent: devstack-tempest
+    description: |
+      Smoke integration test with stestr master.
+      This ensures that new stestr release does
+      not break Temepst.
+    vars:
+      tox_envlist: stestr-master
+      devstack_services:
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        c-bak: false