Merge "Skip puppet-* projects for plugin search"
diff --git a/.stestr.conf b/.stestr.conf
index e3201c1..818c743 100644
--- a/.stestr.conf
+++ b/.stestr.conf
@@ -1,4 +1,3 @@
 [DEFAULT]
 test_path=./tempest/test_discover
 group_regex=([^\.]*\.)*
-
diff --git a/.testr.conf b/.testr.conf
deleted file mode 100644
index 95a4fb4..0000000
--- a/.testr.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-[DEFAULT]
-test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
-             OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
-             OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \
-             OS_TEST_LOCK_PATH=${OS_TEST_LOCK_PATH:-${TMPDIR:-'/tmp'}} \
-             ${PYTHON:-python} -m subunit.run discover -t ${OS_TOP_LEVEL:-./} ${OS_TEST_PATH:-./tempest/test_discover} $LISTOPT $IDOPTION
-test_id_option=--load-list $IDFILE
-test_list_option=--list
-group_regex=([^\.]*\.)*
diff --git a/.zuul.yaml b/.zuul.yaml
index 9f91455..e4e95f2 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -9,22 +9,92 @@
       - zuul: openstack-dev/devstack
     vars:
       devstack_services:
-        tempest: True
+        tempest: true
+      test_results_stage_name: 'test_results'
+      zuul_copy_output:
+        '{{ devstack_base_dir }}/tempest/etc/tempest.conf': 'logs'
+        '{{ devstack_base_dir }}/tempest/etc/accounts.yaml': 'logs'
+        '{{ devstack_base_dir }}/tempest/tempest.log': 'logs'
+        '{{ stage_dir }}/{{ test_results_stage_name }}.subunit': 'logs'
+        '{{ stage_dir }}/{{ test_results_stage_name }}.html': 'logs'
+        '{{ stage_dir }}/stackviz': 'logs'
+      extensions_to_txt:
+        conf: True
+        log: True
+        yaml: True
+        yml: True
     run: playbooks/devstack-tempest.yaml
     post-run: playbooks/post-tempest.yaml
 
 - job:
     name: tempest-full
     parent: devstack-tempest
+    # This currently works from stable/pike on.
+    branches:
+      - master
+      - stable/queens
+      - stable/pike
     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_venvlist: full
+      tox_envlist: full
       devstack_localrc:
-        ENABLE_FILE_INJECTION: True
+        ENABLE_FILE_INJECTION: true
+
+- job:
+    name: tempest-full-parallel
+    parent: tempest-full
+    voting: false
+    branches:
+      - master
+    description: |
+      Base integration test with Neutron networking and py27.
+      It includes all scenarios as it was in the past.
+      This job runs all scenario tests in parallel!
+    vars:
+      tox_envlist: full-parallel
+
+- job:
+    name: tempest-full-py3
+    parent: devstack-tempest
+    branches:
+      - master
+      - stable/queens
+    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
+      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-queens
+    parent: tempest-full
+    override-checkout: stable/queens
+
+- job:
+    name: tempest-full-queens-py3
+    parent: tempest-full-py3
+    override-checkout: stable/queens
+
+- job:
+    name: tempest-full-pike
+    parent: tempest-full
+    override-checkout: stable/pike
 
 - job:
     name: tempest-tox-plugin-sanity-check
@@ -36,6 +106,13 @@
       tox_envlist: plugin-sanity-check
     voting: false
     timeout: 5000
+    irrelevant-files:
+      - ^.*\.rst$
+      - ^doc/.*$
+      - ^etc/.*$
+      - ^releasenotes/.*$
+      - ^tempest/hacking/.*$
+      - ^tempest/tests/.*$
     required-projects:
       - openstack/almanach
       - openstack/aodh
@@ -82,7 +159,7 @@
       - openstack/neutron-vpnaas
       - openstack/nova-lxd
       - openstack/novajoin-tempest-plugin
-      - openstack/octavia
+      - openstack/octavia-tempest-plugin
       - openstack/oswin-tempest-plugin
       - openstack/panko
       - openstack/patrole
@@ -103,7 +180,6 @@
       - openstack/zun-tempest-plugin
 
 - project:
-    name: openstack/tempest
     check:
       jobs:
         - devstack-tempest:
@@ -111,8 +187,38 @@
               - ^playbooks/
               - ^roles/
               - ^.zuul.yaml$
-        - tempest-full:
-            voting: false
+        - nova-multiattach
+        - tempest-full-parallel:
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+        - tempest-full-queens:
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+        - tempest-full-queens-py3:
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+        - tempest-full-pike:
             irrelevant-files:
               - ^(test-|)requirements.txt$
               - ^.*\.rst$
@@ -123,3 +229,23 @@
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
         - tempest-tox-plugin-sanity-check
+    gate:
+      jobs:
+        - nova-multiattach
+    experimental:
+      jobs:
+        - nova-cells-v1:
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+    periodic-stable:
+      jobs:
+        - tempest-full-queens
+        - tempest-full-queens-py3
+        - tempest-full-pike
diff --git a/HACKING.rst b/HACKING.rst
index a3e9c26..bb55ac5 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -363,13 +363,24 @@
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 When adding tests for new features that were not in previous releases of the
-projects the new test has to be properly skipped with a feature flag. Whether
-this is just as simple as using the @utils.requires_ext() decorator to
-check if the required extension (or discoverable optional API) is enabled or
+projects the new test has to be properly skipped with a feature flag. This can
+be just as simple as using the ``@utils.requires_ext()`` or
+``testtools.skipUnless`` decorators to check if the required extension (or
+discoverable optional API) or feature is enabled or can be as difficult as
 adding a new config option to the appropriate section. If there isn't a method
 of selecting the new **feature** from the config file then there won't be a
-mechanism to disable the test with older stable releases and the new test won't
-be able to merge.
+mechanism to disable the test with older stable releases and the new test
+won't be able to merge.
+
+Introduction of a new feature flag requires specifying a default value for
+the corresponding config option that is appropriate in the latest OpenStack
+release. Because Tempest is branchless, the feature flag's default value will
+need to be overridden to a value that is appropriate in earlier releases
+in which the feature isn't available. In DevStack, this can be accomplished
+by modifying Tempest's `lib installation script`_ for previous branches
+(because DevStack is branched).
+
+.. _lib installation script: http://git.openstack.org/cgit/openstack-dev/devstack/tree/lib/tempest
 
 2. Bug fix on core project needing Tempest changes
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -385,7 +396,7 @@
 
 Otherwise the bug fix won't be able to land in the project.
 
-Handily, `Zuul’s cross-repository dependencies
+Handily, `Zuul's cross-repository dependencies
 <https://docs.openstack.org/infra/zuul/user/gating.html#cross-project-dependencies>`_.
 can be leveraged to do without step 2 and to have steps 3 and 4 happen
 "atomically". To do that, make the patch written in step 1 to depend (refer to
diff --git a/README.rst b/README.rst
index 242f4d5..044ae09 100644
--- a/README.rst
+++ b/README.rst
@@ -2,7 +2,7 @@
 Team and repository tags
 ========================
 
-.. image:: https://governance.openstack.org/badges/tempest.svg
+.. image:: https://governance.openstack.org/tc/badges/tempest.svg
     :target: https://governance.openstack.org/tc/reference/tags/index.html
 
 .. Change things from this point on
@@ -15,7 +15,7 @@
 
 This is a set of integration tests to be run against a live OpenStack
 cluster. Tempest has batteries of tests for OpenStack API validation,
-Scenarios, and other specific tests useful in validating an OpenStack
+scenarios, and other specific tests useful in validating an OpenStack
 deployment.
 
 Design Principles
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..d959d44
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,6 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+openstackdocstheme>=1.18.1 # Apache-2.0
+reno>=2.5.0 # Apache-2.0
+sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 0a061b8..c2ea628 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -64,7 +64,7 @@
 # openstackdocstheme options
 repository_name = 'openstack/tempest'
 bug_project = 'tempest'
-bug_tag = ''
+bug_tag = 'doc'
 
 # Must set this variable to include year, month, day, hours, and minutes.
 html_last_updated_fmt = '%Y-%m-%d %H:%M'
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index e5f70d2..d0d7320 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -400,7 +400,7 @@
     Examples:
 
     * Good - ``http://example.com:1234/v2.0``
-    * Wouldn’t work -  ``http://example.com:1234/xyz/v2.0/``
+    * Wouldn't work -  ``http://example.com:1234/xyz/v2.0/``
       (adding prefix/suffix around version etc)
 
 Service Feature Configuration
diff --git a/doc/source/library/credential_providers.rst b/doc/source/library/credential_providers.rst
index d96c97a..d25f85c 100644
--- a/doc/source/library/credential_providers.rst
+++ b/doc/source/library/credential_providers.rst
@@ -49,7 +49,7 @@
               public_network_id=CONF.network.public_network_id,
               create_networks=(CONF.auth.create_isolated_networks and not
                                CONF.network.shared_physical_network),
-              resource_prefix=CONF.resources_prefix,
+              resource_prefix='tempest',
               credentials_domain=CONF.auth.default_credentials_domain_name,
               admin_role=CONF.identity.admin_role,
               identity_uri=CONF.identity.uri_v3,
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index aca1845..942f969 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -302,6 +302,10 @@
 
   .. _2.2: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2
 
+  * `2.6`_
+
+  .. _2.6: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id5
+
   * `2.10`_
 
   .. _2.10: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9
@@ -310,6 +314,10 @@
 
   .. _2.20: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18
 
+  * `2.21`_
+
+  .. _2.21: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id19
+
   * `2.25`_
 
   .. _2.25: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka
@@ -334,6 +342,10 @@
 
   .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
 
+  * `2.60`_
+
+  .. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id54
+
 * Volume
 
   * `3.3`_
diff --git a/playbooks/post-tempest.yaml b/playbooks/post-tempest.yaml
index 820e4f6..4dde2c9 100644
--- a/playbooks/post-tempest.yaml
+++ b/playbooks/post-tempest.yaml
@@ -1,24 +1,6 @@
 - hosts: all
   become: true
-  vars:
-    logs_root: "{{ devstack_base_dir|default('/opt/stack') }}"
-    stage_dir: "{{ devstack_base_dir|default('/opt/stack') }}"
-    test_results_stage_name: 'test_results'
   roles:
-    - role: process-test-results
-      test_results_dir: '{{ logs_root }}/tempest'
-      tox_envdir: tempest
+    - role: fetch-subunit-output
+      zuul_work_dir: '{{ devstack_base_dir }}/tempest'
     - role: process-stackviz
-    - role: stage-output
-      zuul_copy_output:
-        { '{{ logs_root }}/tempest/etc/tempest.conf': 'logs',
-          '{{ logs_root }}/tempest/etc/accounts.yaml': 'logs',
-          '{{ logs_root }}/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
-        - log
-        - yaml
-        - yml
diff --git a/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml b/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml
index 3e43f9a..985acb0 100644
--- a/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml
+++ b/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml
@@ -3,4 +3,4 @@
   - A new helper method `service_client_config` has been added
     to the stable module config.py that returns extracts from
     configuration into a dictionary the configuration settings
-    relevant for the initisialisation of a service client.
+    relevant for the initialization of a service client.
diff --git a/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml b/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
index 0075a36..fc061bc 100644
--- a/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
+++ b/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
@@ -2,7 +2,7 @@
 upgrade:
   - The ``JSON_ENC`` and ``TXT_ENC`` option in the ``_error_checker``
     section have been added with additional content-type which are
-    defined in RFC7231 but missing in the currnt rest_client.py file.
+    defined in RFC7231 but missing in the current rest_client.py file.
     The lack of these additional content-type will cause defcore test
     to fail for OpenStack public cloud which uses tomcat module in the
     api gateway. The additions are ``application/json;charset=utf-8``,
diff --git a/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml b/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
index 9927971..18ad5b9 100644
--- a/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
+++ b/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
@@ -2,7 +2,7 @@
 features:
   - |
     Add server tags APIs to the servers_client library.
-    This feature enables the possibility of upating, deleting
+    This feature enables the possibility of updating, deleting
     and checking existence of a tag on a server, as well
     as updating and deleting all tags on a server.
 
diff --git a/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml b/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
index f679208..0b45d0d 100644
--- a/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
+++ b/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
@@ -6,5 +6,5 @@
 deprecations:
   - The resources_prefix is marked as deprecated because it is
     enough to set 'tempest' as the prefix on rand_name() to
-    ideintify resources which are created by Tempest and no
+    identify resources which are created by Tempest and no
     projects set this option on OpenStack dev community.
diff --git a/releasenotes/notes/add-additional-methods-to-policy-client-library-b8279c18335588c9.yaml b/releasenotes/notes/add-additional-methods-to-policy-client-library-b8279c18335588c9.yaml
new file mode 100644
index 0000000..cd5284d
--- /dev/null
+++ b/releasenotes/notes/add-additional-methods-to-policy-client-library-b8279c18335588c9.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add ``v3-ext/OS-ENDPOINT-POLICY`` API calls to support creation, deletion and
+    retrieval of associations between service endpoints and policies. Such associations
+    enable an endpoint to request its policy.
diff --git a/releasenotes/notes/add-group-type-specs-apis-to-v3-group-types-client-10390b52dedede54.yaml b/releasenotes/notes/add-group-type-specs-apis-to-v3-group-types-client-10390b52dedede54.yaml
new file mode 100644
index 0000000..404319d
--- /dev/null
+++ b/releasenotes/notes/add-group-type-specs-apis-to-v3-group-types-client-10390b52dedede54.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Add group type specs APIs to v3 group_types_client library.
+
+    * create_or_update_group_type_specs
+    * list_group_type_specs
+    * show_group_type_specs_item
+    * update_group_type_specs_item
+    * delete_group_type_specs_item
diff --git a/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml b/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
new file mode 100644
index 0000000..b54ee8b
--- /dev/null
+++ b/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
@@ -0,0 +1,11 @@
+---
+prelude: >
+    When using OVS HW offload feature we need to create
+    Neutron port with a certain capability. This is done
+    by creating Neutron port with binding profile. To be
+    able to test this we need profile capability support
+    in Tempest as well.
+features:
+  - A new config option 'port_profile' is added to the section
+    'network' to specify capabilities of the port.
+    By default this is set to {}.
diff --git a/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml b/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml
new file mode 100644
index 0000000..6efe7e6
--- /dev/null
+++ b/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add show default quotas API to network quotas_client library.
+    This feature enables the possibility to show default network quotas for
+    a specified project.
diff --git a/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml b/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml
new file mode 100644
index 0000000..9e13afc
--- /dev/null
+++ b/releasenotes/notes/add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add show encryption specs item API to v2 encryption_types_client library.
+    This feature enables the possibility to show specific encryption specs for
+    a volume type.
diff --git a/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml b/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml
new file mode 100644
index 0000000..406e282
--- /dev/null
+++ b/releasenotes/notes/add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Add extension API show quota details to network quotas_client library.
+    This feature enables the possibility to show a quota set for a specified
+    project that includes the quota’s used, limit and reserved counts for per
+    resource
diff --git a/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml b/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
index e23abe3..9c30a0c 100644
--- a/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
+++ b/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
@@ -2,4 +2,4 @@
 features:
   - Add support of args and kwargs when calling func in call_until_true,
     also to log the cost time when call_until_true returns True or False
-    for debuggin.
+    for debugging.
diff --git a/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml b/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml
new file mode 100644
index 0000000..e3443c8
--- /dev/null
+++ b/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml
@@ -0,0 +1,9 @@
+---
+other:
+  - |
+    The CLIClient class, when it calls a command line client, uses
+    --os-project-name instead of --os-tenant-name for the project, and
+    passes --os-identity-api-version (default empty).
+    All CLI clients still available in supported releases of OpenStack
+    which are wrapped by the cmd_with_auth() method support those
+    switches.
diff --git a/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml b/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml
index 305e756..cc6c51b 100644
--- a/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml
+++ b/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml
@@ -3,7 +3,7 @@
   - |
     Add a new function called ``compare_version_header_to_response`` to
     ``tempest.lib.common.api_version_utils``, which compares the API
-    micoversion in the response header to another microversion using the
+    microversion in the response header to another microversion using the
     comparators defined in
     ``tempest.lib.common.api_version_request.APIVersionRequest``.
 
diff --git a/releasenotes/notes/config-volume-multiattach-ea8138dfa4fd308c.yaml b/releasenotes/notes/config-volume-multiattach-ea8138dfa4fd308c.yaml
new file mode 100644
index 0000000..8d53dda
--- /dev/null
+++ b/releasenotes/notes/config-volume-multiattach-ea8138dfa4fd308c.yaml
@@ -0,0 +1,12 @@
+---
+other:
+  - |
+    A new configuration option ``[compute-feature-enabled]/volume_multiattach``
+    has been added which defaults to False. Set this to True to enable volume
+    multiattach testing. These tests require that compute API version 2.60 is
+    available and block storage API version 3.44 is available.
+
+    .. note:: In the Queens release, the only compute driver that supports
+      volume multiattach is the libvirt driver, and only then when qemu<2.10
+      or libvirt>=3.10. The only volume backend in Queens that supports volume
+      multiattach is lvm.
diff --git a/releasenotes/notes/create-mount-config-drive-to-lib-1a6e912b8afbcc7e.yaml b/releasenotes/notes/create-mount-config-drive-to-lib-1a6e912b8afbcc7e.yaml
new file mode 100644
index 0000000..f92cd78
--- /dev/null
+++ b/releasenotes/notes/create-mount-config-drive-to-lib-1a6e912b8afbcc7e.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - A function has been added to the common library to allow mounting and
+    unmounting of the config drive consistently.
diff --git a/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml b/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml
index 775a383..a002fb8 100644
--- a/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml
+++ b/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml
@@ -1,6 +1,6 @@
 ---
 fixes:
   - |
-    Fix list_group_snapshots API in v3 group_snapshots_client: Bug#1715786.
+    Fix list_group_snapshots API in v3 group_snapshots_client: Bug#1715786.
     The url path for list group snapshots with details API is changed from
     ``?detail=True`` to ``/detail``.
diff --git a/releasenotes/notes/fix-show-image-file-expected-code-92d97342d0f6d60e.yaml b/releasenotes/notes/fix-show-image-file-expected-code-92d97342d0f6d60e.yaml
new file mode 100644
index 0000000..a4050a5
--- /dev/null
+++ b/releasenotes/notes/fix-show-image-file-expected-code-92d97342d0f6d60e.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    Fix show_image_file interface in v2 ImagesClient: Bug#1756264.
+    The expected success code of show_image_file is changed from
+    ``200`` to ``[200, 204, 206]``.
diff --git a/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml b/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml
new file mode 100644
index 0000000..dfbcc7d
--- /dev/null
+++ b/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml
@@ -0,0 +1,12 @@
+---
+features:
+  - |
+    Add ``project_tags_client`` to the identity v3 library. This feature
+    enables the possibility of invoking the following API actions:
+
+    * update_project_tag
+    * list_project_tags
+    * update_all_project_tags
+    * check_project_tag_existence
+    * delete_project_tag
+    * delete_all_project_tags
diff --git a/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml b/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml
index dbb6c46..e15d387 100644
--- a/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml
+++ b/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml
@@ -6,3 +6,7 @@
     good to handle them.
 
     * ``[identity-feature-enabled].forbid_global_implied_dsr``
+    * ``[image-feature-enabled].deactivate_image``
+    * ``[default].resources_prefix``
+    * config group ``orchestration``
+    * ``[service_available].heat``
diff --git a/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml b/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml
new file mode 100644
index 0000000..17866e5
--- /dev/null
+++ b/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml
@@ -0,0 +1,9 @@
+---
+upgrade:
+  - |
+    The tox ostestr job (normally invoked with ``tox -eostestr``) has been
+    removed. This was lightly used, and in the near future ostestr will be
+    removed from the tempest requirements file. If you were relying on this
+    functionality you can replicate it by using the venv-tempest tox job. For
+    example, simply running ``tox -evenv-tempest -- ostestr`` will do the same
+    thing the old ostestr job did.
diff --git a/releasenotes/notes/start-of-queens-support-fea9051ba1d85fc7.yaml b/releasenotes/notes/start-of-queens-support-fea9051ba1d85fc7.yaml
new file mode 100644
index 0000000..10b2300
--- /dev/null
+++ b/releasenotes/notes/start-of-queens-support-fea9051ba1d85fc7.yaml
@@ -0,0 +1,12 @@
+---
+prelude: >
+    This release marks the start of Queens release support in Tempest.
+    This release also marks the end of support for Newton in Tempest.
+other:
+    - OpenStack Releases supported after this release are **Queens**, **Pike**,
+      and **Ocata**.
+
+      The release under current development of this tag is Rocky, meaning
+      that every Tempest commit is also tested against master during the Rocky
+      cycle. However, this does not necessarily mean that using Tempest as of
+      this tag will work against a Rocky (or future release) cloud.
diff --git a/releasenotes/notes/switch-to-stestr-8c9f834b3f5a55d8.yaml b/releasenotes/notes/switch-to-stestr-8c9f834b3f5a55d8.yaml
new file mode 100644
index 0000000..9e2f1ba
--- /dev/null
+++ b/releasenotes/notes/switch-to-stestr-8c9f834b3f5a55d8.yaml
@@ -0,0 +1,13 @@
+---
+features:
+- The Tempest CLI commands have switched from calling testrepository internally
+  to use stestr instead. This means that all of the features and bug fixes from
+  moving to stestr are available to the tempest commands.
+
+upgrade:
+- Tempest CLI commands will no long rely on anything from testr. This means any
+  data in existing testr internals that were being exposed are no longer
+  present. For example things like the .testr directories will be silently
+  ignored. There is a potential incompatibility for existing users who are
+  relying on test results being stored by testr. Anything relying on previous
+  testr behavior will need to be updated to handle stestr.
diff --git a/releasenotes/notes/tempest-run-fix-updates-564b41706decbba1.yaml b/releasenotes/notes/tempest-run-fix-updates-564b41706decbba1.yaml
new file mode 100644
index 0000000..265853d
--- /dev/null
+++ b/releasenotes/notes/tempest-run-fix-updates-564b41706decbba1.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    Adds a new CLI arg in tempest run, --black-regex, which is a regex to
+    exclude the tests that match it.
+fixes:
+  - |
+    Fixes tempest run CLI args mutually exclusive behavior which should not
+    be the case anymore (Bug#1751201).
diff --git a/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml b/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml
new file mode 100644
index 0000000..0da2ddc
--- /dev/null
+++ b/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    New string configuration option ``vnc_server_header`` is added
+    to ``compute-feature-enabled`` section. It offers to provide VNC server
+    name that is to be expected in the responce header. For example, obvious
+    at hand names is 'WebSockify', 'nginx'.
+fixes:
+  - |
+    Fix VNC server response header issue when it is behind reverse proxy
diff --git a/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml b/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml
new file mode 100644
index 0000000..ddd1704
--- /dev/null
+++ b/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    A new boolean configuration option
+    ``[compute-feature-enabled]/volume_backed_live_migration`` has been added.
+    If enabled, tests which validate the behavior of Nova's *volume-backed live
+    migration* feature will be executed. The option defaults to ``False``.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index df1de46..2518703 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -2,19 +2,22 @@
  Tempest Release Notes
 ===========================
 
- .. toctree::
-    :maxdepth: 1
+.. toctree::
+   :maxdepth: 1
 
-    unreleased
-    v17.0.0
-    v16.1.0
-    v16.0.0
-    v15.0.0
-    v14.0.0
-    v13.0.0
-    v12.0.0
-    v11.0.0
-    v10.0.0
+   unreleased
+   v18.0.0
+   v17.2.0
+   v17.1.0
+   v17.0.0
+   v16.1.0
+   v16.0.0
+   v15.0.0
+   v14.0.0
+   v13.0.0
+   v12.0.0
+   v11.0.0
+   v10.0.0
 
 Indices and tables
 ==================
diff --git a/releasenotes/source/v17.1.0.rst b/releasenotes/source/v17.1.0.rst
new file mode 100644
index 0000000..b8fd570
--- /dev/null
+++ b/releasenotes/source/v17.1.0.rst
@@ -0,0 +1,6 @@
+=====================
+v17.1.0 Release Notes
+=====================
+
+.. release-notes:: 17.1.0 Release Notes
+   :version: 17.1.0
diff --git a/releasenotes/source/v17.2.0.rst b/releasenotes/source/v17.2.0.rst
new file mode 100644
index 0000000..8566ae4
--- /dev/null
+++ b/releasenotes/source/v17.2.0.rst
@@ -0,0 +1,6 @@
+=====================
+v17.2.0 Release Notes
+=====================
+
+.. release-notes:: 17.2.0 Release Notes
+   :version: 17.2.0
diff --git a/releasenotes/source/v18.0.0.rst b/releasenotes/source/v18.0.0.rst
new file mode 100644
index 0000000..aa05db3
--- /dev/null
+++ b/releasenotes/source/v18.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v18.0.0 Release Notes
+=====================
+
+.. release-notes:: 18.0.0 Release Notes
+   :version: 18.0.0
diff --git a/requirements.txt b/requirements.txt
index 2300214..7520d42 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,19 +7,18 @@
 testtools>=2.2.0 # MIT
 paramiko>=2.0.0 # LGPLv2.1+
 netaddr>=0.7.18 # BSD
-testrepository>=0.0.18 # Apache-2.0/BSD
-oslo.concurrency>=3.20.0 # Apache-2.0
-oslo.config>=5.1.0 # Apache-2.0
-oslo.log>=3.30.0 # Apache-2.0
+oslo.concurrency>=3.26.0 # Apache-2.0
+oslo.config>=5.2.0 # Apache-2.0
+oslo.log>=3.36.0 # Apache-2.0
+stestr>=1.0.0 # Apache-2.0
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
-oslo.utils>=3.31.0 # Apache-2.0
+oslo.utils>=3.33.0 # Apache-2.0
 six>=1.10.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
-PyYAML>=3.10 # MIT
+PyYAML>=3.12 # MIT
 python-subunit>=1.0.0 # Apache-2.0/BSD
 stevedore>=1.20.0 # Apache-2.0
 PrettyTable<0.8,>=0.7.1 # BSD
-os-testr>=1.0.0 # Apache-2.0
 urllib3>=1.21.1 # MIT
 debtcollector>=1.2.0 # Apache-2.0
 unittest2>=1.1.0 # BSD
diff --git a/roles/process-stackviz/README.rst b/roles/process-stackviz/README.rst
index b05326d..a8447d2 100644
--- a/roles/process-stackviz/README.rst
+++ b/roles/process-stackviz/README.rst
@@ -11,12 +11,12 @@
    The devstack base directory.
 
 .. zuul:rolevar:: stage_dir
-   :default: /opt/stack/logs
+   :default: "{{ ansible_user_dir }}"
 
    The stage directory where the input data can be found and
    the output will be produced.
 
-.. zuul:rolevar:: test_results_stage_name
-   :default: test_results
+.. zuul:rolevar:: zuul_work_dir
+   :default: {{ devstack_base_dir }}/tempest
 
-   The name of the subunit file to be used as input.
+   Directory to work in. It has to be a fully qualified path.
diff --git a/roles/process-stackviz/defaults/main.yaml b/roles/process-stackviz/defaults/main.yaml
index b1eb8d9..f3bc32b 100644
--- a/roles/process-stackviz/defaults/main.yaml
+++ b/roles/process-stackviz/defaults/main.yaml
@@ -1,3 +1,3 @@
 devstack_base_dir: /opt/stack
-stage_dir: /opt/stack/
-test_results_stage_name: test_results
+stage_dir: "{{ ansible_user_dir }}"
+zuul_work_dir: "{{ devstack_base_dir }}/tempest"
diff --git a/roles/process-stackviz/tasks/main.yaml b/roles/process-stackviz/tasks/main.yaml
index 09de606..3724e0e 100644
--- a/roles/process-stackviz/tasks/main.yaml
+++ b/roles/process-stackviz/tasks/main.yaml
@@ -9,11 +9,11 @@
 
 - name: Check if subunit data exists
   stat:
-    path: "{{ stage_dir }}/{{ test_results_stage_name }}.subunit"
+    path: "{{ zuul_work_dir }}/testrepository.subunit"
   register: subunit_input
 
 - debug:
-    msg: "Subunit file could not be found at {{ stage_dir }}/{{ test_results_stage_name }}.subunit"
+    msg: "Subunit file could not be found at {{ zuul_work_dir }}/testrepository.subunit"
   when: not subunit_input.stat.exists
 
 - name: Install stackviz
@@ -50,6 +50,7 @@
     - stackviz_archive.stat.exists
     - subunit_input.stat.exists
     - dstat_input.stat.exists
+  failed_when: False
 
 - name: Run stackviz without dstat
   shell: |
@@ -61,3 +62,4 @@
     - stackviz_archive.stat.exists
     - subunit_input.stat.exists
     - not dstat_input.stat.exists
+  failed_when: False
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 001586e..384ca38 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -16,10 +16,26 @@
    :default: ''
 
    A regular expression used to select the tests.
+
    It works only when used with some specific tox environments
    ('all', 'all-plugin'.)
 
-.. zuul:rolevar:: tox_venvlist
+   Multi-line and commented regexs can be achieved by doing this:
+
+       ::
+           vars:
+             tempest_test_regex: |
+               (?x)    # Ignore comments and whitespaces
+               # Line with only a comment.
+               (tempest\.(api|scenario|thirdparty)).*$    # Run only api scenario and third party
+
+.. zuul:rolevar:: tempest_test_blacklist
+
+   Specifies a blacklist file to skip tests that are not needed.
+
+   Pass a full path to the file.
+
+.. zuul:rolevar:: tox_envlist
    :default: smoke
 
    The Tempest tox environment to run.
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index 3e57511..85e94f2 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -1,3 +1,3 @@
 devstack_base_dir: /opt/stack
 tempest_test_regex: ''
-tox_venvlist: smoke
+tox_envlist: smoke
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 297cd72..b68507a 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -20,8 +20,22 @@
     default_concurrency: "{{ num_cores|int // 2 }}"
   when: num_cores|int > 3
 
+- when:
+    - tempest_test_blacklist is defined
+  block:
+    - name: Check for test blacklist file
+      stat:
+        path: "{{ tempest_test_blacklist }}"
+      register:
+        blacklist_stat
+
+    - name: Build blacklist option
+      set_fact:
+        blacklist_option: "--blacklist-file={{ tempest_test_blacklist|quote }}"
+      when: blacklist_stat.stat.exists
+
 - name: Run Tempest
-  command: tox -e {{tox_venvlist}} -- {{tempest_test_regex|quote}} --concurrency={{tempest_concurrency|default(default_concurrency)}}
+  command: tox -e {{tox_envlist}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} --concurrency={{tempest_concurrency|default(default_concurrency)}}
   args:
     chdir: "{{devstack_base_dir}}/tempest"
   become: true
diff --git a/setup.cfg b/setup.cfg
index 04bb29f..c981370 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -49,7 +49,8 @@
 
 [build_sphinx]
 all-files = 1
-warning-is-error = 1
+# warning can be generated by using GENERATE_TEMPEST_PLUGIN_LIST='False'
+warning-is-error = 0
 build-dir = doc/build
 source-dir = doc/source
 
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
index 36ff09e..a6e0efa 100644
--- a/tempest/api/compute/admin/test_aggregates_negative.py
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -27,16 +27,17 @@
     def setup_clients(cls):
         super(AggregatesAdminNegativeTestJSON, cls).setup_clients()
         cls.client = cls.os_admin.aggregates_client
+        cls.services_client = cls.os_admin.services_client
 
     @classmethod
     def resource_setup(cls):
         super(AggregatesAdminNegativeTestJSON, cls).resource_setup()
         cls.aggregate_name_prefix = 'test_aggregate'
 
-        hosts_all = cls.os_admin.hosts_client.list_hosts()['hosts']
-        hosts = ([host['host_name']
-                 for host in hosts_all if host['service'] == 'compute'])
-        cls.host = hosts[0]
+        svc_list = cls.services_client.list_services(
+            binary='nova-compute')['services']
+        cls.hosts = [v['host'] for v in svc_list
+                     if v['status'] == 'enabled' and v['state'] == 'up']
 
     def _create_test_aggregate(self):
         aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -123,11 +124,9 @@
     @decorators.idempotent_id('0ef07828-12b4-45ba-87cc-41425faf5711')
     def test_aggregate_add_non_exist_host(self):
         # Adding a non-exist host to an aggregate should raise exceptions.
-        hosts_all = self.os_admin.hosts_client.list_hosts()['hosts']
-        hosts = map(lambda x: x['host_name'], hosts_all)
         while True:
             non_exist_host = data_utils.rand_name('nonexist_host')
-            if non_exist_host not in hosts:
+            if non_exist_host not in self.hosts:
                 break
         aggregate = self._create_test_aggregate()
         self.assertRaises(lib_exc.NotFound, self.client.add_host,
@@ -140,7 +139,7 @@
         aggregate = self._create_test_aggregate()
         self.assertRaises(lib_exc.Forbidden,
                           self.aggregates_client.add_host,
-                          aggregate['id'], host=self.host)
+                          aggregate['id'], host=self.hosts[0])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('19dd44e1-c435-4ee1-a402-88c4f90b5950')
@@ -148,12 +147,12 @@
         self.useFixture(fixtures.LockFixture('availability_zone'))
         aggregate = self._create_test_aggregate()
 
-        self.client.add_host(aggregate['id'], host=self.host)
+        self.client.add_host(aggregate['id'], host=self.hosts[0])
         self.addCleanup(self.client.remove_host, aggregate['id'],
-                        host=self.host)
+                        host=self.hosts[0])
 
         self.assertRaises(lib_exc.Conflict, self.client.add_host,
-                          aggregate['id'], host=self.host)
+                          aggregate['id'], host=self.hosts[0])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('7a53af20-137a-4e44-a4ae-e19260e626d9')
@@ -162,13 +161,13 @@
         self.useFixture(fixtures.LockFixture('availability_zone'))
         aggregate = self._create_test_aggregate()
 
-        self.client.add_host(aggregate['id'], host=self.host)
+        self.client.add_host(aggregate['id'], host=self.hosts[0])
         self.addCleanup(self.client.remove_host, aggregate['id'],
-                        host=self.host)
+                        host=self.hosts[0])
 
         self.assertRaises(lib_exc.Forbidden,
                           self.aggregates_client.remove_host,
-                          aggregate['id'], host=self.host)
+                          aggregate['id'], host=self.hosts[0])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('95d6a6fa-8da9-4426-84d0-eec0329f2e4d')
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
index a9772c4..e8011a6 100644
--- a/tempest/api/compute/admin/test_auto_allocate_network.py
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -84,8 +84,7 @@
         nets = cls.networks_client.list_networks(
             **search_opts).get('networks', [])
         if nets:
-            raise lib_excs.TempestException(
-                'Found shared networks: %s' % nets)
+            raise cls.skipException('Found shared networks: %s' % nets)
 
     @classmethod
     def resource_cleanup(cls):
@@ -149,9 +148,7 @@
     def test_server_create_no_allocate(self):
         """Tests that no networking is allocated for the server."""
         # create the server with no networking
-        server, _ = compute.create_test_server(
-            self.os_primary, networks='none', wait_until='ACTIVE')
-        self.addCleanup(self.delete_server, server['id'])
+        server = self.create_test_server(networks='none', wait_until='ACTIVE')
         # get the server ips
         addresses = self.servers_client.list_addresses(
             server['id'])['addresses']
diff --git a/tempest/api/compute/admin/test_create_server.py b/tempest/api/compute/admin/test_create_server.py
index 08b2d19..711b441 100644
--- a/tempest/api/compute/admin/test_create_server.py
+++ b/tempest/api/compute/admin/test_create_server.py
@@ -56,6 +56,18 @@
             # Create a flavor with ephemeral disk
             flavor = self.create_flavor(name=flavor_name, ram=ram, vcpus=vcpus,
                                         disk=disk, ephemeral=ephem_disk)
+
+            # Set extra specs same as self.flavor_ref for the created flavor,
+            # because the environment may need some special extra specs to
+            # create server which should have been contained in
+            # self.flavor_ref.
+            extra_spec_keys = \
+                self.admin_flavors_client.list_flavor_extra_specs(
+                    self.flavor_ref)['extra_specs']
+            if extra_spec_keys:
+                self.admin_flavors_client.set_flavor_extra_spec(
+                    flavor['id'], **extra_spec_keys)
+
             return flavor['id']
 
         flavor_with_eph_disk_id = create_flavor_with_ephemeral(ephem_disk=1)
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
index ba19937..72d09ed 100644
--- a/tempest/api/compute/admin/test_floating_ips_bulk.py
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -31,6 +31,7 @@
     API documentation - http://docs.openstack.org/api/openstack-compute/2/
     content/ext-os-floating-ips-bulk.html
     """
+    max_microversion = '2.35'
 
     @classmethod
     def setup_clients(cls):
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 00f3256..c246685 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -20,6 +20,8 @@
 class HostsAdminTestJSON(base.BaseV2ComputeAdminTest):
     """Tests hosts API using admin privileges."""
 
+    max_microversion = '2.42'
+
     @classmethod
     def setup_clients(cls):
         super(HostsAdminTestJSON, cls).setup_clients()
diff --git a/tempest/api/compute/admin/test_hosts_negative.py b/tempest/api/compute/admin/test_hosts_negative.py
index 5bd8104..8a91ae2 100644
--- a/tempest/api/compute/admin/test_hosts_negative.py
+++ b/tempest/api/compute/admin/test_hosts_negative.py
@@ -20,6 +20,8 @@
 class HostsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
     """Tests hosts API using admin privileges."""
 
+    max_microversion = '2.42'
+
     @classmethod
     def setup_clients(cls):
         super(HostsAdminNegativeTestJSON, cls).setup_clients()
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 404fd94..b23c59f 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -17,12 +17,12 @@
 from tempest.lib import decorators
 
 
-class HypervisorAdminTestJSON(base.BaseV2ComputeAdminTest):
+class HypervisorAdminTestBase(base.BaseV2ComputeAdminTest):
     """Tests Hypervisors API that require admin privileges"""
 
     @classmethod
     def setup_clients(cls):
-        super(HypervisorAdminTestJSON, cls).setup_clients()
+        super(HypervisorAdminTestBase, cls).setup_clients()
         cls.client = cls.os_admin.hypervisor_client
 
     def _list_hypervisors(self):
@@ -30,6 +30,10 @@
         hypers = self.client.list_hypervisors()['hypervisors']
         return hypers
 
+
+class HypervisorAdminTestJSON(HypervisorAdminTestBase):
+    """Tests Hypervisors API that require admin privileges"""
+
     @decorators.idempotent_id('7f0ceacd-c64d-4e96-b8ee-d02943142cc5')
     def test_get_hypervisor_list(self):
         # List of hypervisor and available hypervisors hostname
@@ -53,17 +57,6 @@
         self.assertEqual(details['hypervisor_hostname'],
                          hypers[0]['hypervisor_hostname'])
 
-    @decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862')
-    def test_get_hypervisor_show_servers(self):
-        # Show instances about the specific hypervisors
-        hypers = self._list_hypervisors()
-        self.assertNotEmpty(hypers, "No hypervisors found.")
-
-        hostname = hypers[0]['hypervisor_hostname']
-        hypervisors = (self.client.list_servers_on_hypervisor(hostname)
-                       ['hypervisors'])
-        self.assertNotEmpty(hypervisors)
-
     @decorators.idempotent_id('797e4f28-b6e0-454d-a548-80cc77c00816')
     def test_get_hypervisor_stats(self):
         # Verify the stats of the all hypervisor
@@ -110,6 +103,21 @@
             has_valid_uptime,
             "None of the hypervisors had a valid uptime: %s" % hypers)
 
+
+class HypervisorAdminUnderV252Test(HypervisorAdminTestBase):
+    max_microversion = '2.52'
+
+    @decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862')
+    def test_get_hypervisor_show_servers(self):
+        # Show instances about the specific hypervisors
+        hypers = self._list_hypervisors()
+        self.assertNotEmpty(hypers, "No hypervisors found.")
+
+        hostname = hypers[0]['hypervisor_hostname']
+        hypervisors = (self.client.list_servers_on_hypervisor(hostname)
+                       ['hypervisors'])
+        self.assertNotEmpty(hypervisors)
+
     @decorators.idempotent_id('d7e1805b-3b14-4a3b-b6fd-50ec6d9f361f')
     def test_search_hypervisor(self):
         hypers = self._list_hypervisors()
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index 431e823..0056376 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -19,12 +19,12 @@
 from tempest.lib import exceptions as lib_exc
 
 
-class HypervisorAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class HypervisorAdminNegativeTestBase(base.BaseV2ComputeAdminTest):
     """Tests Hypervisors API that require admin privileges"""
 
     @classmethod
     def setup_clients(cls):
-        super(HypervisorAdminNegativeTestJSON, cls).setup_clients()
+        super(HypervisorAdminNegativeTestBase, cls).setup_clients()
         cls.client = cls.os_admin.hypervisor_client
         cls.non_adm_client = cls.hypervisor_client
 
@@ -33,6 +33,10 @@
         hypers = self.client.list_hypervisors()['hypervisors']
         return hypers
 
+
+class HypervisorAdminNegativeTestJSON(HypervisorAdminNegativeTestBase):
+    """Tests Hypervisors API that require admin privileges"""
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('c136086a-0f67-4b2b-bc61-8482bd68989f')
     def test_show_nonexistent_hypervisor(self):
@@ -55,27 +59,6 @@
             hypers[0]['id'])
 
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
-    def test_show_servers_with_non_admin_user(self):
-        hypers = self._list_hypervisors()
-        self.assertNotEmpty(hypers)
-
-        self.assertRaises(
-            lib_exc.Forbidden,
-            self.non_adm_client.list_servers_on_hypervisor,
-            hypers[0]['id'])
-
-    @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
-    def test_show_servers_with_nonexistent_hypervisor(self):
-        nonexistent_hypervisor_id = data_utils.rand_uuid()
-
-        self.assertRaises(
-            lib_exc.NotFound,
-            self.client.list_servers_on_hypervisor,
-            nonexistent_hypervisor_id)
-
-    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('e2b061bb-13f9-40d8-9d6e-d5bf17595849')
     def test_get_hypervisor_stats_with_non_admin_user(self):
         self.assertRaises(
@@ -119,13 +102,30 @@
             lib_exc.Forbidden,
             self.non_adm_client.list_hypervisors, detail=True)
 
+
+class HypervisorAdminNegativeUnderV252Test(HypervisorAdminNegativeTestBase):
+    max_microversion = '2.52'
+
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
-    def test_search_nonexistent_hypervisor(self):
+    @decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924')
+    def test_show_servers_with_non_admin_user(self):
+        hypers = self._list_hypervisors()
+        self.assertNotEmpty(hypers)
+
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.non_adm_client.list_servers_on_hypervisor,
+            hypers[0]['id'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba')
+    def test_show_servers_with_nonexistent_hypervisor(self):
+        nonexistent_hypervisor_id = data_utils.rand_uuid()
+
         self.assertRaises(
             lib_exc.NotFound,
-            self.client.search_hypervisor,
-            'nonexistent_hypervisor_name')
+            self.client.list_servers_on_hypervisor,
+            nonexistent_hypervisor_id)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('5b6a6c79-5dc1-4fa5-9c58-9c8085948e74')
@@ -137,3 +137,11 @@
             lib_exc.Forbidden,
             self.non_adm_client.search_hypervisor,
             hypers[0]['hypervisor_hostname'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
+    def test_search_nonexistent_hypervisor(self):
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.client.search_hypervisor,
+            'nonexistent_hypervisor_name')
diff --git a/tempest/api/compute/admin/test_keypairs_v210.py b/tempest/api/compute/admin/test_keypairs_v210.py
index e24c7c1..24ea8a1 100644
--- a/tempest/api/compute/admin/test_keypairs_v210.py
+++ b/tempest/api/compute/admin/test_keypairs_v210.py
@@ -34,7 +34,8 @@
             k_name = data_utils.rand_name('keypair')
             keypair = self.create_keypair(k_name,
                                           keypair_type='ssh',
-                                          user_id=user_id)
+                                          user_id=user_id,
+                                          client=self.client)
             self.assertEqual(k_name, keypair['name'],
                              "The created keypair name is not equal "
                              "to the requested name!")
@@ -56,7 +57,8 @@
         self.assertEqual(user_id, keypair_detail['user_id'],
                          "The fetched keypair is not for requested user!")
         # Create a admin keypair
-        admin_keypair = self.create_keypair(keypair_type='ssh')
+        admin_keypair = self.create_keypair(keypair_type='ssh',
+                                            client=self.client)
         admin_keypair.pop('private_key', None)
         admin_keypair.pop('user_id')
 
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 411159b..2398cf1 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -132,7 +132,9 @@
     def test_live_block_migration_paused(self):
         self._test_live_migration(state='PAUSED')
 
-    @decorators.skip_because(bug="1524898")
+    @testtools.skipUnless(CONF.compute_feature_enabled.
+                          volume_backed_live_migration,
+                          'Volume-backed live migration not available')
     @decorators.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
     @utils.services('volume')
     def test_volume_backed_live_migration(self):
@@ -156,14 +158,11 @@
         self.attach_volume(server, volume, device='/dev/xvdb')
         server = self.admin_servers_client.show_server(server_id)['server']
         volume_id1 = server["os-extended-volumes:volumes_attached"][0]["id"]
-        self._migrate_server_to(server_id, target_host)
-        waiters.wait_for_server_status(self.servers_client,
-                                       server_id, 'ACTIVE')
+        self._live_migrate(server_id, target_host, 'ACTIVE')
 
         server = self.admin_servers_client.show_server(server_id)['server']
         volume_id2 = server["os-extended-volumes:volumes_attached"][0]["id"]
 
-        self.assertEqual(target_host, self.get_host_for_server(server_id))
         self.assertEqual(volume_id1, volume_id2)
 
 
@@ -202,10 +201,7 @@
         self._verify_console_interaction(server01_id)
         self._verify_console_interaction(server02_id)
 
-        self._migrate_server_to(server01_id, host02_id)
-        waiters.wait_for_server_status(self.servers_client,
-                                       server01_id, 'ACTIVE')
-        self.assertEqual(host02_id, self.get_host_for_server(server01_id))
+        self._live_migrate(server01_id, host02_id, 'ACTIVE')
         self._verify_console_interaction(server01_id)
         # At this point, both instances have a valid serial console
         # connection, which means the ports got updated.
@@ -228,8 +224,8 @@
             while data not in console_output and t <= 120.0:
                 try:
                     ws.send_frame(data)
-                    recieved = ws.receive_frame()
-                    console_output += recieved
+                    received = ws.receive_frame()
+                    console_output += received
                 except Exception:
                     # In case we had an issue with send/receive on the
                     # websocket connection, we create a new one.
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index a626ebb..a6b71b2 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -77,6 +77,16 @@
         )['flavor']
         self.addCleanup(self._flavor_clean_up, flavor['id'])
 
+        # Set extra specs same as self.flavor_ref for the created flavor,
+        # because the environment may need some special extra specs to
+        # create server which should have been contained in
+        # self.flavor_ref.
+        extra_spec_keys = self.admin_flavors_client.list_flavor_extra_specs(
+            self.flavor_ref)['extra_specs']
+        if extra_spec_keys:
+            self.admin_flavors_client.set_flavor_extra_spec(
+                flavor['id'], **extra_spec_keys)
+
         # Now boot a server with the copied flavor.
         server = self.create_test_server(
             wait_until='ACTIVE', flavor=flavor['id'])
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
index acb0d90..87ce39d 100644
--- a/tempest/api/compute/admin/test_networks.py
+++ b/tempest/api/compute/admin/test_networks.py
@@ -24,7 +24,7 @@
     """Tests Nova Networks API that usually requires admin privileges.
 
     API docs:
-    http://developer.openstack.org/api-ref-compute-v2-ext.html#ext-os-networks
+    https://developer.openstack.org/api-ref/compute/#networks-os-networks-deprecated
     """
 
     @classmethod
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index 2e7b07b..d32a5b4 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -43,6 +43,28 @@
         return cls.os_admin.servers_client.show_server(
             server_id)['server']['OS-EXT-SRV-ATTR:host']
 
+    def _create_servers_with_group(self, policy):
+        group_id = self.create_test_server_group(policy=[policy])['id']
+        hints = {'group': group_id}
+        reservation_id = self.create_test_server(
+            scheduler_hints=hints, wait_until='ACTIVE', min_count=2,
+            return_reservation_id=True)['reservation_id']
+
+        # Get the servers using the reservation_id.
+        servers = self.servers_client.list_servers(
+            detail=True, reservation_id=reservation_id)['servers']
+        self.assertEqual(2, len(servers))
+
+        # Assert the servers are in the group.
+        server_group = self.server_groups_client.show_server_group(
+            group_id)['server_group']
+        hosts = {}
+        for server in servers:
+            self.assertIn(server['id'], server_group['members'])
+            hosts[server['id']] = self._get_host(server['id'])
+
+        return hosts
+
     @decorators.idempotent_id('26a9d5df-6890-45f2-abc4-a659290cb130')
     @testtools.skipUnless(
         compute.is_scheduler_filter_enabled("SameHostFilter"),
@@ -87,27 +109,22 @@
         Creates two servers in an anti-affinity server group and
         asserts the servers are in the group and on different hosts.
         """
-        group_id = self.create_test_server_group(
-            policy=['anti-affinity'])['id']
-        hints = {'group': group_id}
-        reservation_id = self.create_test_server(
-            scheduler_hints=hints, wait_until='ACTIVE', min_count=2,
-            return_reservation_id=True)['reservation_id']
-
-        # Get the servers using the reservation_id.
-        servers = self.servers_client.list_servers(
-            detail=True, reservation_id=reservation_id)['servers']
-        self.assertEqual(2, len(servers))
-
-        # Assert the servers are in the group.
-        server_group = self.server_groups_client.show_server_group(
-            group_id)['server_group']
-        hosts = {}
-        for server in servers:
-            self.assertIn(server['id'], server_group['members'])
-            hosts[server['id']] = self._get_host(server['id'])
-
-        # Assert the servers are on different hosts.
-        hostnames = list(hosts.values())
+        hosts = self._create_servers_with_group('anti-affinity')
+        hostnames = hosts.values()
         self.assertNotEqual(hostnames[0], hostnames[1],
                             'Servers are on the same host: %s' % hosts)
+
+    @decorators.idempotent_id('9d2e924a-baf4-11e7-b856-fa163e65f5ce')
+    @testtools.skipUnless(
+        compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"),
+        'ServerGroupAffinityFilter is not available.')
+    def test_create_server_with_scheduler_hint_group_affinity(self):
+        """Tests the ServerGroupAffinityFilter
+
+        Creates two servers in an affinity server group and
+        asserts the servers are in the group and on same host.
+        """
+        hosts = self._create_servers_with_group('affinity')
+        hostnames = hosts.values()
+        self.assertEqual(hostnames[0], hostnames[1],
+                         'Servers are on the different hosts: %s' % hosts)
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index d715a42..ed8cf20 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -22,30 +22,16 @@
 CONF = config.CONF
 
 
-class TestVolumeSwap(base.BaseV2ComputeAdminTest):
-    """The test suite for swapping of volume with admin user.
-
-    The following is the scenario outline:
-    1. Create a volume "volume1" with non-admin.
-    2. Create a volume "volume2" with non-admin.
-    3. Boot an instance "instance1" with non-admin.
-    4. Attach "volume1" to "instance1" with non-admin.
-    5. Swap volume from "volume1" to "volume2" as admin.
-    6. Check the swap volume is successful and "volume2"
-       is attached to "instance1" and "volume1" is in available state.
-    7. Swap volume from "volume2" to "volume1" as admin.
-    8. Check the swap volume is successful and "volume1"
-       is attached to "instance1" and "volume2" is in available state.
-    """
+class TestVolumeSwapBase(base.BaseV2ComputeAdminTest):
 
     @classmethod
     def skip_checks(cls):
-        super(TestVolumeSwap, cls).skip_checks()
+        super(TestVolumeSwapBase, cls).skip_checks()
         if not CONF.compute_feature_enabled.swap_volume:
             raise cls.skipException("Swapping volumes is not supported.")
 
-    def _wait_for_server_volume_swap(self, server_id, old_volume_id,
-                                     new_volume_id):
+    def wait_for_server_volume_swap(self, server_id, old_volume_id,
+                                    new_volume_id):
         """Waits for a server to swap the old volume to a new one."""
         volume_attachments = self.servers_client.list_volume_attachments(
             server_id)['volumeAttachments']
@@ -79,6 +65,23 @@
                             'timeout': self.servers_client.build_timeout})
                 raise lib_exc.TimeoutException(message)
 
+
+class TestVolumeSwap(TestVolumeSwapBase):
+    """The test suite for swapping of volume with admin user.
+
+    The following is the scenario outline:
+    1. Create a volume "volume1" with non-admin.
+    2. Create a volume "volume2" with non-admin.
+    3. Boot an instance "instance1" with non-admin.
+    4. Attach "volume1" to "instance1" with non-admin.
+    5. Swap volume from "volume1" to "volume2" as admin.
+    6. Check the swap volume is successful and "volume2"
+       is attached to "instance1" and "volume1" is in available state.
+    7. Swap volume from "volume2" to "volume1" as admin.
+    8. Check the swap volume is successful and "volume1"
+       is attached to "instance1" and "volume2" is in available state.
+    """
+
     @decorators.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813')
     @utils.services('volume')
     def test_volume_swap(self):
@@ -99,8 +102,8 @@
                                                 volume1['id'], 'available')
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume2['id'], 'in-use')
-        self._wait_for_server_volume_swap(server['id'], volume1['id'],
-                                          volume2['id'])
+        self.wait_for_server_volume_swap(server['id'], volume1['id'],
+                                         volume2['id'])
         # Verify "volume2" is attached to the server
         vol_attachments = self.servers_client.list_volume_attachments(
             server['id'])['volumeAttachments']
@@ -114,10 +117,64 @@
                                                 volume2['id'], 'available')
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume1['id'], 'in-use')
-        self._wait_for_server_volume_swap(server['id'], volume2['id'],
-                                          volume1['id'])
+        self.wait_for_server_volume_swap(server['id'], volume2['id'],
+                                         volume1['id'])
         # Verify "volume1" is attached to the server
         vol_attachments = self.servers_client.list_volume_attachments(
             server['id'])['volumeAttachments']
         self.assertEqual(1, len(vol_attachments))
         self.assertIn(volume1['id'], vol_attachments[0]['volumeId'])
+
+
+class TestMultiAttachVolumeSwap(TestVolumeSwapBase):
+    min_microversion = '2.60'
+    max_microversion = 'latest'
+
+    @classmethod
+    def skip_checks(cls):
+        super(TestMultiAttachVolumeSwap, cls).skip_checks()
+        if not CONF.compute_feature_enabled.volume_multiattach:
+            raise cls.skipException('Volume multi-attach is not available.')
+
+    @decorators.idempotent_id('e8f8f9d1-d7b7-4cd2-8213-ab85ef697b6e')
+    @utils.services('volume')
+    def test_volume_swap_with_multiattach(self):
+        # Create two volumes.
+        # NOTE(gmann): Volumes are created before server creation so that
+        # volumes cleanup can happen successfully irrespective of which volume
+        # is attached to server.
+        volume1 = self.create_volume(multiattach=True)
+        volume2 = self.create_volume(multiattach=True)
+
+        # Boot server1
+        server1 = self.create_test_server(wait_until='ACTIVE')
+        # Attach volume1 to server1
+        self.attach_volume(server1, volume1)
+        # Boot server2
+        server2 = self.create_test_server(wait_until='ACTIVE')
+        # Attach volume1 to server2
+        self.attach_volume(server2, volume1)
+
+        # Swap volume1 to volume2 on server1, volume1 should remain attached
+        # to server 2
+        self.admin_servers_client.update_attached_volume(
+            server1['id'], volume1['id'], volumeId=volume2['id'])
+        # volume1 will return to in-use after the swap
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume1['id'], 'in-use')
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume2['id'], 'in-use')
+        self.wait_for_server_volume_swap(server1['id'], volume1['id'],
+                                         volume2['id'])
+
+        # Verify volume2 is attached to server1
+        vol_attachments = self.servers_client.list_volume_attachments(
+            server1['id'])['volumeAttachments']
+        self.assertEqual(1, len(vol_attachments))
+        self.assertIn(volume2['id'], vol_attachments[0]['volumeId'])
+
+        # Verify volume1 is still attached to server2
+        vol_attachments = self.servers_client.list_volume_attachments(
+            server2['id'])['volumeAttachments']
+        self.assertEqual(1, len(vol_attachments))
+        self.assertIn(volume1['id'], vol_attachments[0]['volumeId'])
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index ac03cdc..760e356 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -99,6 +99,15 @@
         cls.versions_client = cls.os_primary.compute_versions_client
         if CONF.service_available.cinder:
             cls.volumes_client = cls.os_primary.volumes_client_latest
+        if CONF.service_available.glance:
+            if CONF.image_feature_enabled.api_v1:
+                cls.images_client = cls.os_primary.image_client
+            elif CONF.image_feature_enabled.api_v2:
+                cls.images_client = cls.os_primary.image_client_v2
+            else:
+                raise lib_exc.InvalidConfiguration(
+                    'Either api_v1 or api_v2 must be True in '
+                    '[image-feature-enabled].')
 
     @classmethod
     def resource_setup(cls):
@@ -176,11 +185,12 @@
             cls.request_microversion)
         v2_37_version = api_version_request.APIVersionRequest('2.37')
 
+        tenant_network = cls.get_tenant_network()
         # NOTE(snikitin): since microversion v2.37 'networks' field is required
-        if request_version >= v2_37_version and 'networks' not in kwargs:
+        if (request_version >= v2_37_version and 'networks' not in kwargs and
+            not tenant_network):
             kwargs['networks'] = 'none'
 
-        tenant_network = cls.get_tenant_network()
         body, servers = compute.create_test_server(
             cls.os_primary,
             validatable,
@@ -254,7 +264,11 @@
 
     @classmethod
     def create_image_from_server(cls, server_id, **kwargs):
-        """Wrapper utility that returns an image created from the server."""
+        """Wrapper utility that returns an image created from the server.
+
+        If compute microversion >= 2.36, the returned image response will
+        be from the image service API rather than the compute image proxy API.
+        """
         name = kwargs.pop('name',
                           data_utils.rand_name(cls.__name__ + "-image"))
         wait_until = kwargs.pop('wait_until', None)
@@ -267,14 +281,21 @@
             image_id = image['image_id']
         else:
             image_id = data_utils.parse_image_id(image.response['location'])
+
+        # The compute image proxy APIs were deprecated in 2.35 so
+        # use the images client directly if the API microversion being
+        # used is >=2.36.
+        if api_version_utils.compare_version_header_to_response(
+                "OpenStack-API-Version", "compute 2.36", image.response, "lt"):
+            client = cls.images_client
+        else:
+            client = cls.compute_images_client
         cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
-                                    cls.compute_images_client.delete_image,
-                                    image_id)
+                                    client.delete_image, image_id)
 
         if wait_until is not None:
             try:
-                waiters.wait_for_image_status(cls.compute_images_client,
-                                              image_id, wait_until)
+                waiters.wait_for_image_status(client, image_id, wait_until)
             except lib_exc.NotFound:
                 if wait_until.upper() == 'ACTIVE':
                     # If the image is not found after create_image returned
@@ -292,7 +313,11 @@
                             image_id=image_id)
                 else:
                     raise
-            image = cls.compute_images_client.show_image(image_id)['image']
+            image = client.show_image(image_id)
+            # Compute image client returns response wrapped in 'image' element
+            # which is not the case with Glance image client.
+            if 'image' in image:
+                image = image['image']
 
             if wait_until.upper() == 'ACTIVE':
                 if wait_for_server:
@@ -352,6 +377,13 @@
                                        'VERIFY_RESIZE')
         cls.servers_client.confirm_resize_server(server_id)
         waiters.wait_for_server_status(cls.servers_client, server_id, 'ACTIVE')
+        server = cls.servers_client.show_server(server_id)['server']
+        # Nova API > 2.46 no longer includes flavor.id
+        if server['flavor'].get('id'):
+            if new_flavor_id != server['flavor']['id']:
+                msg = ('Flavor id of %s is not equal to new_flavor_id.'
+                       % server_id)
+                raise lib_exc.TempestException(msg)
 
     @classmethod
     def delete_volume(cls, volume_id):
@@ -500,10 +532,10 @@
     def get_host_other_than(self, server_id):
         source_host = self.get_host_for_server(server_id)
 
-        hypers = self.os_admin.hypervisor_client.list_hypervisors(
-            )['hypervisors']
-        hosts = [hyper['hypervisor_hostname'] for hyper in hypers
-                 if hyper['state'] == 'up' and hyper['status'] == 'enabled']
+        svcs = self.os_admin.services_client.list_services(
+            binary='nova-compute')['services']
+        hosts = [svc['host'] for svc in svcs
+                 if svc['state'] == 'up' and svc['status'] == 'enabled']
 
         for target_host in hosts:
             if source_host != target_host:
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index efd4f0e..3a474e6 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -30,18 +30,6 @@
 
 class FlavorsV2NegativeTest(base.BaseV2ComputeTest):
 
-    @classmethod
-    def setup_clients(cls):
-        super(FlavorsV2NegativeTest, cls).setup_clients()
-        if CONF.image_feature_enabled.api_v1:
-            cls.images_client = cls.os_primary.image_client
-        elif CONF.image_feature_enabled.api_v2:
-            cls.images_client = cls.os_primary.image_client_v2
-        else:
-            raise lib_exc.InvalidConfiguration(
-                'Either api_v1 or api_v2 must be True in '
-                '[image-feature-enabled].')
-
     @decorators.attr(type=['negative'])
     @utils.services('image')
     @decorators.idempotent_id('90f0d93a-91c1-450c-91e6-07d18172cefe')
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index b497626..1f3af5f 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -28,6 +28,7 @@
 
 
 class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.38'
 
     @classmethod
     def skip_checks(cls):
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index 03d0789..407fb08 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -20,6 +20,7 @@
 
 
 class ImagesMetadataNegativeTestJSON(base.BaseV2ComputeTest):
+    max_microversion = '2.38'
 
     @classmethod
     def setup_clients(cls):
diff --git a/tempest/api/compute/keypairs/base.py b/tempest/api/compute/keypairs/base.py
index 0051810..44da88c 100644
--- a/tempest/api/compute/keypairs/base.py
+++ b/tempest/api/compute/keypairs/base.py
@@ -20,17 +20,16 @@
 class BaseKeypairTest(base.BaseV2ComputeTest):
     """Base test case class for all keypair API tests."""
 
-    @classmethod
-    def setup_clients(cls):
-        super(BaseKeypairTest, cls).setup_clients()
-        cls.client = cls.keypairs_client
-
-    def _delete_keypair(self, keypair_name, **params):
-        self.client.delete_keypair(keypair_name, **params)
+    def _delete_keypair(self, keypair_name, client=None, **params):
+        if not client:
+            client = self.keypairs_client
+        client.delete_keypair(keypair_name, **params)
 
     def create_keypair(self, keypair_name=None,
                        pub_key=None, keypair_type=None,
-                       user_id=None):
+                       user_id=None, client=None):
+        if not client:
+            client = self.keypairs_client
         if keypair_name is None:
             keypair_name = data_utils.rand_name(
                 self.__class__.__name__ + '-keypair')
@@ -43,6 +42,7 @@
         if user_id:
             kwargs.update({'user_id': user_id})
             delete_params['user_id'] = user_id
-        body = self.client.create_keypair(**kwargs)['keypair']
-        self.addCleanup(self._delete_keypair, keypair_name, **delete_params)
+        body = client.create_keypair(**kwargs)['keypair']
+        self.addCleanup(self._delete_keypair, keypair_name,
+                        client, **delete_params)
         return body
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 3a54d51..66abb21 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -35,7 +35,7 @@
             key_list.append(keypair)
         # Fetch all keypairs and verify the list
         # has all created keypairs
-        fetched_list = self.client.list_keypairs()['keypairs']
+        fetched_list = self.keypairs_client.list_keypairs()['keypairs']
         new_list = list()
         for keypair in fetched_list:
             new_list.append(keypair['keypair'])
@@ -61,7 +61,7 @@
         # Keypair should be created, Got details by name and deleted
         k_name = data_utils.rand_name('keypair')
         self.create_keypair(k_name)
-        keypair_detail = self.client.show_keypair(k_name)['keypair']
+        keypair_detail = self.keypairs_client.show_keypair(k_name)['keypair']
         self.assertEqual(keypair_detail['name'], k_name,
                          "The created keypair name is not equal "
                          "to requested name")
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index 205076c..f9050a8 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -34,7 +34,8 @@
     def test_keypair_delete_nonexistent_key(self):
         # Non-existent key deletion should throw a proper error
         k_name = data_utils.rand_name("keypair-non-existent")
-        self.assertRaises(lib_exc.NotFound, self.client.delete_keypair,
+        self.assertRaises(lib_exc.NotFound,
+                          self.keypairs_client.delete_keypair,
                           k_name)
 
     @decorators.attr(type=['negative'])
@@ -58,11 +59,11 @@
     def test_create_keypair_with_duplicate_name(self):
         # Keypairs with duplicate names should not be created
         k_name = data_utils.rand_name('keypair')
-        self.client.create_keypair(name=k_name)
+        self.keypairs_client.create_keypair(name=k_name)
         # Now try the same keyname to create another key
         self.assertRaises(lib_exc.Conflict, self.create_keypair,
                           k_name)
-        self.client.delete_keypair(k_name)
+        self.keypairs_client.delete_keypair(k_name)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('1398abe1-4a84-45fb-9294-89f514daff00')
diff --git a/tempest/api/compute/keypairs/test_keypairs_v22.py b/tempest/api/compute/keypairs/test_keypairs_v22.py
index f39bb12..1aff262 100644
--- a/tempest/api/compute/keypairs/test_keypairs_v22.py
+++ b/tempest/api/compute/keypairs/test_keypairs_v22.py
@@ -32,9 +32,9 @@
         # Verify whether 'type' is present in keypair create response of
         # version 2.2 and it is with default value 'ssh'.
         self._check_keypair_type(keypair, keypair_type)
-        keypair_detail = self.client.show_keypair(k_name)['keypair']
+        keypair_detail = self.keypairs_client.show_keypair(k_name)['keypair']
         self._check_keypair_type(keypair_detail, keypair_type)
-        fetched_list = self.client.list_keypairs()['keypairs']
+        fetched_list = self.keypairs_client.list_keypairs()['keypairs']
         for keypair in fetched_list:
             # Verify whether 'type' is present in keypair list response of
             # version 2.2 and it is with default value 'ssh'.
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 0e8f681..7509ac6 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -185,7 +185,7 @@
 
     @decorators.idempotent_id('73fe8f02-590d-4bf1-b184-e9ca81065051')
     @utils.services('network')
-    def test_create_list_show_delete_interfaces(self):
+    def test_create_list_show_delete_interfaces_by_network_port(self):
         server, ifs = self._create_server_get_interfaces()
         interface_count = len(ifs)
         self.assertGreater(interface_count, 0)
@@ -206,6 +206,42 @@
         iface = self._test_create_interface_by_port_id(server, ifs)
         ifs.append(iface)
 
+        _ifs = (self.interfaces_client.list_interfaces(server['id'])
+                ['interfaceAttachments'])
+        self._compare_iface_list(ifs, _ifs)
+
+        self._test_show_interface(server, ifs)
+
+        _ifs = self._test_delete_interface(server, ifs)
+        self.assertEqual(len(ifs) - 1, len(_ifs))
+
+    @decorators.idempotent_id('d290c06c-f5b3-11e7-8ec8-002293781009')
+    @utils.services('network')
+    def test_create_list_show_delete_interfaces_by_fixed_ip(self):
+        # NOTE(zhufl) By default only project that is admin or network owner
+        # or project with role advsvc is authorised to create interfaces with
+        # fixed-ip, so if we don't create network for each project, do not
+        # test _test_create_interface_by_fixed_ips.
+        if not (CONF.auth.use_dynamic_credentials and
+                CONF.auth.create_isolated_networks and
+                not CONF.network.shared_physical_network):
+                raise self.skipException("Only owner network supports "
+                                         "creating interface by fixed ip.")
+
+        server, ifs = self._create_server_get_interfaces()
+        interface_count = len(ifs)
+        self.assertGreater(interface_count, 0)
+
+        try:
+            iface = self._test_create_interface(server)
+        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:
+                raise
+        else:
+            ifs.append(iface)
+
         iface = self._test_create_interface_by_fixed_ips(server, ifs)
         ifs.append(iface)
 
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index c660821..122c4f5 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -135,8 +135,13 @@
             servers_client=self.client)
         hostname = linux_client.exec_command("hostname").rstrip()
         msg = ('Failed while verifying servername equals hostname. Expected '
-               'hostname "%s" but got "%s".' % (self.name, hostname))
-        self.assertEqual(self.name.lower(), hostname, msg)
+               'hostname "%s" but got "%s".' %
+               (self.name, hostname.split(".")[0]))
+        # NOTE(zhufl): Some images will add postfix for the hostname, e.g.,
+        # if hostname is "aaa", postfix ".novalocal" may be added to it, and
+        # the hostname will be "aaa.novalocal" then, so we should ignore the
+        # postfix when checking whether hostname equals self.name.
+        self.assertEqual(self.name.lower(), hostname.split(".")[0], msg)
 
 
 class ServersTestManualDisk(ServersTestJSON):
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index a126fd6..b0d527c 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -47,8 +47,8 @@
             raise cls.skipException('Neutron is required')
         if not CONF.validation.run_validation:
             raise cls.skipException('Validation must be enabled')
-        if (not CONF.compute_feature_enabled.config_drive
-            and not CONF.compute_feature_enabled.metadata_service):
+        if (not CONF.compute_feature_enabled.config_drive and
+                not CONF.compute_feature_enabled.metadata_service):
             raise cls.skipException('One of metadata or config drive must be '
                                     'enabled')
 
@@ -82,7 +82,9 @@
             # A hypervisor may present multiple paths to a tagged disk, so
             # there may be duplicated tags in the metadata, use set() to
             # remove duplicated tags.
-            found_devices = [d['tags'][0] for d in md_dict['devices']]
+            # Some hypervisors might report devices with no tags as well.
+            found_devices = [d['tags'][0] for d in md_dict['devices']
+                             if d.get('tags')]
             self.assertEqual(set(found_devices), set(['port-1', 'port-2',
                                                       'net-1', 'net-2-100',
                                                       'net-2-200', 'boot',
@@ -139,6 +141,7 @@
 
         server = self.create_test_server(
             validatable=True,
+            wait_until='ACTIVE',
             validation_resources=validation_resources,
             config_drive=config_drive_enabled,
             adminPass=admin_pass,
@@ -205,6 +208,7 @@
 
         self.addCleanup(self.delete_server, server['id'])
 
+        server = self.servers_client.show_server(server['id'])['server']
         self.ssh_client = remote_client.RemoteClient(
             self.get_server_ip(server, validation_resources),
             CONF.validation.image_ssh_user,
@@ -260,25 +264,13 @@
 
         # Verify metadata on config drive
         if CONF.compute_feature_enabled.config_drive:
-            cmd_blkid = 'blkid -t LABEL=config-2 -o device'
             LOG.info('Attempting to verify tagged devices in server %s via '
                      'the config drive.', server['id'])
-            dev_name = self.ssh_client.exec_command(cmd_blkid)
-            dev_name = dev_name.rstrip()
-            try:
-                self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
-            except exceptions.SSHExecCommandFailed:
-                # So the command failed, let's try to know why and print some
-                # useful information.
-                lsblk = self.ssh_client.exec_command('sudo lsblk --fs --ascii')
-                LOG.error("Mounting %s on /mnt failed. Right after the "
-                          "failure 'lsblk' in the guest reported:\n%s",
-                          dev_name, lsblk)
-                raise
-
+            self.ssh_client.mount_config_drive()
             cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
             md_json = self.ssh_client.exec_command(cmd_md)
             self.verify_device_metadata(md_json)
+            self.ssh_client.unmount_config_drive()
 
 
 class DeviceTaggingTestV2_42(DeviceTaggingTest):
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 6c9b287..393e68f 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -119,8 +119,12 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('74745ad8-b346-45b5-b9b8-509d7447fc1f')
     def test_list_servers_by_changes_since_future_date(self):
-        # Return an empty list when a date in the future is passed
-        changes_since = {'changes-since': '2051-01-01T12:34:00Z'}
+        # Return an empty list when a date in the future is passed.
+        # updated_at field may haven't been set at the point in the boot
+        # process where build_request still exists, so add
+        # {'status': 'ACTIVE'} along with changes-since as filter.
+        changes_since = {'changes-since': '2051-01-01T12:34:00Z',
+                         'status': 'ACTIVE'}
         body = self.client.list_servers(**changes_since)
         self.assertEmpty(body['servers'])
 
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index d9581e3..1dfd0f9 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -151,11 +151,22 @@
         self.assertTrue(
             self._websocket.response.startswith(b'HTTP/1.1 101 Switching '
                                                 b'Protocols\r\n'),
-            'Did not get the expected 101 on the websockify call: '
-            + six.text_type(self._websocket.response))
-        self.assertTrue(
-            self._websocket.response.find(b'Server: WebSockify') > 0,
-            'Did not get the expected WebSocket HTTP Response.')
+            'Did not get the expected 101 on the {} call: {}'.format(
+                CONF.compute_feature_enabled.vnc_server_header,
+                six.text_type(self._websocket.response)
+            )
+        )
+        # Since every other server type returns Headers with different case
+        # (for example 'nginx'), lowercase must be applied to eliminate issues.
+        _desired_header = "server: {0}".format(
+            CONF.compute_feature_enabled.vnc_server_header
+        ).lower()
+        _response = six.text_type(self._websocket.response).lower()
+        self.assertIn(
+            _desired_header,
+            _response,
+            'Did not get the expected WebSocket HTTP Response.'
+        )
 
     @decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
     def test_novnc(self):
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index e2be249..bbec30c 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -582,6 +582,12 @@
         compute.shelve_server(self.client, self.server_id,
                               force_shelve_offload=True)
 
+        def _unshelve_server():
+            server_info = self.client.show_server(self.server_id)['server']
+            if 'SHELVED' in server_info['status']:
+                self.client.unshelve_server(self.server_id)
+        self.addCleanup(_unshelve_server)
+
         server = self.client.show_server(self.server_id)['server']
         image_name = server['name'] + '-shelved'
         params = {'name': image_name}
diff --git a/tempest/api/compute/servers/test_server_password.py b/tempest/api/compute/servers/test_server_password.py
index e7591a5..e6a668a 100644
--- a/tempest/api/compute/servers/test_server_password.py
+++ b/tempest/api/compute/servers/test_server_password.py
@@ -21,19 +21,14 @@
 class ServerPasswordTestJSON(base.BaseV2ComputeTest):
 
     @classmethod
-    def setup_clients(cls):
-        super(ServerPasswordTestJSON, cls).setup_clients()
-        cls.client = cls.servers_client
-
-    @classmethod
     def resource_setup(cls):
         super(ServerPasswordTestJSON, cls).resource_setup()
         cls.server = cls.create_test_server(wait_until="ACTIVE")
 
     @decorators.idempotent_id('f83b582f-62a8-4f22-85b0-0dee50ff783a')
     def test_get_server_password(self):
-        self.client.show_password(self.server['id'])
+        self.servers_client.show_password(self.server['id'])
 
     @decorators.idempotent_id('f8229e8b-b625-4493-800a-bde86ac611ea')
     def test_delete_server_password(self):
-        self.client.delete_password(self.server['id'])
+        self.servers_client.delete_password(self.server['id'])
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index c9ee671..2904976 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -167,6 +167,18 @@
         server = self.client.show_server(server['id'])['server']
         self.assertEqual('2001:2001::3', server['accessIPv6'])
 
+    @decorators.related_bug('1730756')
+    @decorators.idempotent_id('defbaca5-d611-49f5-ae21-56ee25d2db49')
+    def test_create_server_specify_multibyte_character_name(self):
+        # prefix character is:
+        # http://unicode.org/cldr/utility/character.jsp?a=20A1
+
+        # We use a string with 3 byte utf-8 character due to nova
+        # will return 400(Bad Request) if we attempt to send a name which has
+        # 4 byte utf-8 character.
+        utf8_name = data_utils.rand_name(b'\xe2\x82\xa1'.decode('utf-8'))
+        self.create_test_server(name=utf8_name, wait_until='ACTIVE')
+
 
 class ServerShowV247Test(base.BaseV2ComputeTest):
     min_microversion = '2.47'
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index d067bb3..e944c28 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -477,6 +477,12 @@
         # shelve a shelved server.
         compute.shelve_server(self.client, self.server_id)
 
+        def _unshelve_server():
+            server_info = self.client.show_server(self.server_id)['server']
+            if 'SHELVED' in server_info['status']:
+                self.client.unshelve_server(self.server_id)
+        self.addCleanup(_unshelve_server)
+
         server = self.client.show_server(self.server_id)['server']
         image_name = server['name'] + '-shelved'
         params = {'name': image_name}
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
index 20923a8..c4e2400 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -28,11 +28,6 @@
         cls.set_network_resources()
         super(VirtualInterfacesNegativeTestJSON, cls).setup_credentials()
 
-    @classmethod
-    def setup_clients(cls):
-        super(VirtualInterfacesNegativeTestJSON, cls).setup_clients()
-        cls.client = cls.servers_client
-
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('64ebd03c-1089-4306-93fa-60f5eb5c803c')
     @utils.services('network')
@@ -41,5 +36,5 @@
         # for an invalid server_id
         invalid_server_id = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound,
-                          self.client.list_virtual_interfaces,
+                          self.servers_client.list_virtual_interfaces,
                           invalid_server_id)
diff --git a/tempest/api/compute/test_tenant_networks.py b/tempest/api/compute/test_tenant_networks.py
index b55e2c0..f4eada0 100644
--- a/tempest/api/compute/test_tenant_networks.py
+++ b/tempest/api/compute/test_tenant_networks.py
@@ -18,6 +18,7 @@
 
 
 class ComputeTenantNetworksTest(base.BaseV2ComputeTest):
+    max_microversion = '2.35'
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 297e8a8..caa445d 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -13,8 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.api.compute import base
 from tempest.common import compute
+from tempest.common import utils
 from tempest.common.utils.linux import remote_client
 from tempest.common import waiters
 from tempest import config
@@ -23,12 +26,12 @@
 CONF = config.CONF
 
 
-class AttachVolumeTestJSON(base.BaseV2ComputeTest):
-    max_microversion = '2.19'
+class BaseAttachVolumeTest(base.BaseV2ComputeTest):
+    """Base class for the attach volume tests in this module."""
 
     @classmethod
     def skip_checks(cls):
-        super(AttachVolumeTestJSON, cls).skip_checks()
+        super(BaseAttachVolumeTest, cls).skip_checks()
         if not CONF.service_available.cinder:
             skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
@@ -36,11 +39,11 @@
     @classmethod
     def setup_credentials(cls):
         cls.prepare_instance_network()
-        super(AttachVolumeTestJSON, cls).setup_credentials()
+        super(BaseAttachVolumeTest, cls).setup_credentials()
 
     @classmethod
     def resource_setup(cls):
-        super(AttachVolumeTestJSON, cls).resource_setup()
+        super(BaseAttachVolumeTest, cls).resource_setup()
         cls.device = CONF.compute.volume_device_name
 
     def _create_server(self):
@@ -58,6 +61,9 @@
             server['id'])['addresses']
         return server, validation_resources
 
+
+class AttachVolumeTestJSON(BaseAttachVolumeTest):
+
     @decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
     def test_attach_detach_volume(self):
         # Stop and Start a server with an attached volume, ensuring that
@@ -149,7 +155,7 @@
                 self.volumes_client, attachment['volumeId'], 'available')
 
 
-class AttachVolumeShelveTestJSON(AttachVolumeTestJSON):
+class AttachVolumeShelveTestJSON(BaseAttachVolumeTest):
     """Testing volume with shelved instance.
 
     This test checks the attaching and detaching volumes from
@@ -258,3 +264,177 @@
         # volume(s)
         self._unshelve_server_and_check_volumes(
             server, validation_resources, num_vol)
+
+
+class AttachVolumeMultiAttachTest(BaseAttachVolumeTest):
+    min_microversion = '2.60'
+    max_microversion = 'latest'
+
+    @classmethod
+    def skip_checks(cls):
+        super(AttachVolumeMultiAttachTest, cls).skip_checks()
+        if not CONF.compute_feature_enabled.volume_multiattach:
+            raise cls.skipException('Volume multi-attach is not available.')
+
+    def _attach_volume_to_servers(self, volume, servers):
+        """Attaches the given volume to the list of servers.
+
+        :param volume: The multiattach volume to use.
+        :param servers: list of server instances on which the volume will be
+                        attached
+        :returns: dict of server ID to volumeAttachment dict entries
+        """
+        attachments = {}
+        for server in servers:
+            # map the server id to the volume attachment
+            attachments[server['id']] = self.attach_volume(server, volume)
+            # NOTE(mriedem): In the case of multi-attach, after the first
+            # attach the volume will be in-use. On the second attach, nova will
+            # 'reserve' the volume which puts it back into 'attaching' status
+            # and then the volume shouldn't go back to in-use until the compute
+            # actually attaches the server to the volume.
+        return attachments
+
+    def _detach_multiattach_volume(self, volume_id, server_id):
+        """Detaches a multiattach volume from the given server.
+
+        Depending on the number of attachments the volume has, this method
+        will wait for the volume to go to back to 'in-use' status if there are
+        more attachments or 'available' state if there are no more attachments.
+        """
+        # Count the number of attachments before starting the detach.
+        volume = self.volumes_client.show_volume(volume_id)['volume']
+        attachments = volume['attachments']
+        wait_status = 'in-use' if len(attachments) > 1 else 'available'
+        # Now detach the volume from the given server.
+        self.servers_client.detach_volume(server_id, volume_id)
+        # Now wait for the volume status to change.
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, volume_id, wait_status)
+
+    def _create_multiattach_volume(self, bootable=False):
+        kwargs = {}
+        if bootable:
+            kwargs['image_ref'] = CONF.compute.image_ref
+        return self.create_volume(multiattach=True, **kwargs)
+
+    def _create_and_multiattach(self):
+        """Creates two server instances and a volume and attaches to both.
+
+        :returns: A three-item tuple of the list of created servers,
+                  the created volume, and dict of server ID to volumeAttachment
+                  dict entries
+        """
+        servers = []
+        for x in range(2):
+            name = 'multiattach-server-%i' % x
+            servers.append(self.create_test_server(name=name))
+
+        # Now wait for the servers to be ACTIVE.
+        for server in servers:
+            waiters.wait_for_server_status(self.servers_client, server['id'],
+                                           'ACTIVE')
+
+        volume = self._create_multiattach_volume()
+
+        # Attach the volume to the servers
+        attachments = self._attach_volume_to_servers(volume, servers)
+        return servers, volume, attachments
+
+    @decorators.idempotent_id('8d5853f7-56e7-4988-9b0c-48cea3c7049a')
+    def test_list_get_volume_attachments_multiattach(self):
+        # Attach a single volume to two servers.
+        servers, volume, attachments = self._create_and_multiattach()
+
+        # List attachments from the volume and make sure the server uuids
+        # are in that list.
+        vol_attachments = self.volumes_client.show_volume(
+            volume['id'])['volume']['attachments']
+        attached_server_ids = [attachment['server_id']
+                               for attachment in vol_attachments]
+        self.assertEqual(2, len(attached_server_ids))
+
+        # List Volume attachment of the servers
+        for server in servers:
+            self.assertIn(server['id'], attached_server_ids)
+            vol_attachments = self.servers_client.list_volume_attachments(
+                server['id'])['volumeAttachments']
+            self.assertEqual(1, len(vol_attachments))
+            attachment = attachments[server['id']]
+            self.assertDictEqual(attachment, vol_attachments[0])
+            # Detach the volume from this server.
+            self._detach_multiattach_volume(volume['id'], server['id'])
+
+    def _boot_from_multiattach_volume(self):
+        """Boots a server from a multiattach volume.
+
+        The volume will not be deleted when the server is deleted.
+
+        :returns: 2-item tuple of (server, volume)
+        """
+        volume = self._create_multiattach_volume(bootable=True)
+        # Now create a server from the bootable volume.
+        bdm = [{
+            'uuid': volume['id'],
+            'source_type': 'volume',
+            'destination_type': 'volume',
+            'boot_index': 0,
+            'delete_on_termination': False}]
+        server = self.create_test_server(
+            image_id='', block_device_mapping_v2=bdm, wait_until='ACTIVE')
+        # Assert the volume is attached to the server.
+        attachments = self.servers_client.list_volume_attachments(
+            server['id'])['volumeAttachments']
+        self.assertEqual(1, len(attachments))
+        self.assertEqual(volume['id'], attachments[0]['volumeId'])
+        return server, volume
+
+    @decorators.idempotent_id('65e33aa2-185b-44c8-b22e-e524973ed625')
+    def test_boot_from_multiattach_volume(self):
+        """Simple test to boot an instance from a multiattach volume."""
+        self._boot_from_multiattach_volume()
+
+    @utils.services('image')
+    @decorators.idempotent_id('885ac48a-2d7a-40c5-ae8b-1993882d724c')
+    def test_snapshot_volume_backed_multiattach(self):
+        """Boots a server from a multiattach volume and snapshots the server.
+
+        Creating the snapshot of the server will also create a snapshot of
+        the volume.
+        """
+        server, volume = self._boot_from_multiattach_volume()
+        # Create a snapshot of the server (and volume implicitly).
+        self.create_image_from_server(
+            server['id'], name='multiattach-snapshot',
+            wait_until='active', wait_for_server=True)
+        # TODO(mriedem): Make sure the volume snapshot exists. This requires
+        # adding the volume snapshots client to BaseV2ComputeTest.
+        # Delete the server, wait for it to be gone, and make sure the volume
+        # still exists.
+        self.servers_client.delete_server(server['id'])
+        waiters.wait_for_server_termination(self.servers_client, server['id'])
+        # Delete the volume and cascade the delete of the volume snapshot.
+        self.volumes_client.delete_volume(volume['id'], cascade=True)
+        # Now we have to wait for the volume to be gone otherwise the normal
+        # teardown will fail since it will race with our call and the snapshot
+        # might still exist.
+        self.volumes_client.wait_for_resource_deletion(volume['id'])
+
+    @decorators.idempotent_id('f01c7169-a124-4fc7-ae60-5e380e247c9c')
+    @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+                          'Resize not available.')
+    def test_resize_server_with_multiattached_volume(self):
+        # Attach a single volume to multiple servers, then resize the servers
+        servers, volume, _ = self._create_and_multiattach()
+
+        for server in servers:
+            self.resize_server(server['id'], self.flavor_ref_alt)
+
+        for server in servers:
+            self._detach_multiattach_volume(volume['id'], server['id'])
+
+    # TODO(mriedem): Might be interesting to create a bootable multiattach
+    # volume with delete_on_termination=True, create server1 from the
+    # volume, then attach it to server2, and then delete server1 in which
+    # case the volume won't be deleted because it's still attached to
+    # server2 and make sure the volume is still attached to server2.
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index eabb907..7a74869 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -41,3 +41,18 @@
 
         self.assertRaises(lib_exc.BadRequest,
                           self.delete_volume, volume['id'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('aab919e2-d992-4cbb-a4ed-745c2475398c')
+    def test_attach_attached_volume_to_same_server(self):
+        # Test attaching the same volume to the same instance once
+        # it's already attached. The nova/cinder validation for this differs
+        # depending on whether or not cinder v3.27 is being used to attach
+        # the volume to the instance.
+        server = self.create_test_server(wait_until='ACTIVE')
+        volume = self.create_volume()
+
+        self.attach_volume(server, volume)
+
+        self.assertRaises(lib_exc.BadRequest,
+                          self.attach_volume, server, volume)
diff --git a/tempest/api/identity/admin/v2/test_tokens.py b/tempest/api/identity/admin/v2/test_tokens.py
index 6b30d23..6ce1a8b 100644
--- a/tempest/api/identity/admin/v2/test_tokens.py
+++ b/tempest/api/identity/admin/v2/test_tokens.py
@@ -112,6 +112,8 @@
 
     @decorators.idempotent_id('ca3ea6f7-ed08-4a61-adbd-96906456ad31')
     def test_list_endpoints_for_token(self):
+        tempest_services = ['keystone', 'nova', 'neutron', 'swift', 'cinder',
+                            'neutron']
         # get a token for the user
         creds = self.os_primary.credentials
         username = creds.username
@@ -125,9 +127,10 @@
         self.assertIsInstance(endpoints, list)
         # Store list of service names
         service_names = [e['name'] for e in endpoints]
-        # Get the list of available services.
+        # Get the list of available services. Keystone is always available.
         available_services = [s[0] for s in list(
-            CONF.service_available.items()) if s[1] is True]
+            CONF.service_available.items()) if s[1] is True] + ['keystone']
         # Verify that all available services are present.
-        for service in available_services:
-            self.assertIn(service, service_names)
+        for service in tempest_services:
+            if service in available_services:
+                self.assertIn(service, service_names)
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index ca6b03e..97a1f36 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -18,6 +18,7 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions
 
 CONF = config.CONF
 
@@ -115,6 +116,26 @@
         domains_list = [d['id'] for d in body]
         self.assertNotIn(domain['id'], domains_list)
 
+    @decorators.idempotent_id('d8d318b7-d1b3-4c37-94c5-3c5ba0b121ea')
+    def test_domain_delete_cascades_content(self):
+        # Create a domain with a user and a group in it
+        domain = self.setup_test_domain()
+        user = self.create_test_user(domain_id=domain['id'])
+        group = self.groups_client.create_group(
+            name=data_utils.rand_name('group'),
+            domain_id=domain['id'])['group']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.groups_client.delete_group, group['id'])
+        # Delete the domain
+        self.delete_domain(domain['id'])
+        # Check the domain, its users and groups are gone
+        self.assertRaises(exceptions.NotFound,
+                          self.domains_client.show_domain, domain['id'])
+        self.assertRaises(exceptions.NotFound,
+                          self.users_client.show_user, user['id'])
+        self.assertRaises(exceptions.NotFound,
+                          self.groups_client.show_group, group['id'])
+
     @decorators.idempotent_id('036df86e-bb5d-42c0-a7c2-66b9db3a6046')
     def test_create_domain_with_disabled_status(self):
         # Create domain with enabled status as false
diff --git a/tempest/api/identity/admin/v3/test_project_tags.py b/tempest/api/identity/admin/v3/test_project_tags.py
new file mode 100644
index 0000000..d05173b
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_project_tags.py
@@ -0,0 +1,66 @@
+# Copyright 2018 AT&T Corporation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import testtools
+
+from tempest.api.identity import base
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+CONF = config.CONF
+
+
+class IdentityV3ProjectTagsTest(base.BaseIdentityV3AdminTest):
+
+    @decorators.idempotent_id('7c123aac-999d-416a-a0fb-84b915ab10de')
+    @testtools.skipUnless(CONF.identity_feature_enabled.project_tags,
+                          'Project tags not available.')
+    def test_list_update_delete_project_tags(self):
+        project = self.setup_test_project()
+
+        # Create a tag for testing.
+        tag = data_utils.rand_name('tag')
+        # NOTE(felipemonteiro): The response body for create is empty.
+        self.project_tags_client.update_project_tag(project['id'], tag)
+
+        # Verify that the tag was created.
+        self.project_tags_client.check_project_tag_existence(
+            project['id'], tag)
+
+        # Verify that updating the project tags works.
+        tags_to_update = [data_utils.rand_name('tag') for _ in range(3)]
+        updated_tags = self.project_tags_client.update_all_project_tags(
+            project['id'], tags_to_update)['tags']
+        self.assertEqual(sorted(tags_to_update), sorted(updated_tags))
+
+        # Verify that listing project tags works.
+        retrieved_tags = self.project_tags_client.list_project_tags(
+            project['id'])['tags']
+        self.assertEqual(sorted(tags_to_update), sorted(retrieved_tags))
+
+        # Verify that deleting a project tag works.
+        self.project_tags_client.delete_project_tag(
+            project['id'], tags_to_update[0])
+        self.assertRaises(lib_exc.NotFound,
+                          self.project_tags_client.check_project_tag_existence,
+                          project['id'], tags_to_update[0])
+
+        # Verify that deleting all project tags works.
+        self.project_tags_client.delete_all_project_tags(project['id'])
+        retrieved_tags = self.project_tags_client.list_project_tags(
+            project['id'])['tags']
+        self.assertEmpty(retrieved_tags)
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 9edccbb..6edb8f3 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -228,6 +228,7 @@
         cls.domain_config_client = cls.os_admin.domain_config_client
         cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client
         cls.endpoint_groups_client = cls.os_admin.endpoint_groups_client
+        cls.project_tags_client = cls.os_admin.project_tags_client
 
         if CONF.identity.admin_domain_scope:
             # NOTE(andreaf) When keystone policy requires it, the identity
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 7103d56..ae7b3e4 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -142,16 +142,17 @@
         cls.schemas_client = cls.os_primary.schemas_client
         cls.versions_client = cls.os_primary.image_versions_client
 
-    def create_namespace(cls, namespace_name=None, visibility='public',
+    def create_namespace(self, namespace_name=None, visibility='public',
                          description='Tempest', protected=False,
                          **kwargs):
         if not namespace_name:
             namespace_name = data_utils.rand_name('test-ns')
         kwargs.setdefault('display_name', namespace_name)
-        namespace = cls.namespaces_client.create_namespace(
+        namespace = self.namespaces_client.create_namespace(
             namespace=namespace_name, visibility=visibility,
             description=description, protected=protected, **kwargs)
-        cls.addCleanup(cls.namespaces_client.delete_namespace, namespace_name)
+        self.addCleanup(self.namespaces_client.delete_namespace,
+                        namespace_name)
         return namespace
 
 
@@ -185,21 +186,11 @@
         return image['id']
 
 
-class BaseV1ImageAdminTest(BaseImageTest):
-    credentials = ['admin', 'primary']
+class BaseV2ImageAdminTest(BaseV2ImageTest):
 
-    @classmethod
-    def setup_clients(cls):
-        super(BaseV1ImageAdminTest, cls).setup_clients()
-        cls.client = cls.os_primary.image_client
-        cls.admin_client = cls.os_admin.image_client
-
-
-class BaseV2ImageAdminTest(BaseImageTest):
     credentials = ['admin', 'primary']
 
     @classmethod
     def setup_clients(cls):
         super(BaseV2ImageAdminTest, cls).setup_clients()
-        cls.client = cls.os_primary.image_client_v2
         cls.admin_client = cls.os_admin.image_client_v2
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 76723f4..2432c8b 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -132,8 +132,8 @@
     @classmethod
     def skip_checks(cls):
         super(ListImagesTest, cls).skip_checks()
-        if (len(CONF.image.container_formats) < 2
-           or len(CONF.image.disk_formats) < 2):
+        if (len(CONF.image.container_formats) < 2 or
+                len(CONF.image.disk_formats) < 2):
             skip_msg = ("%s skipped as multiple container formats "
                         "or disk formats are not available." % cls.__name__)
             raise cls.skipException(skip_msg)
@@ -227,8 +227,8 @@
             self.assertEqual(image['disk_format'], self.disk_format_alt)
         result_set = set(map(lambda x: x['id'], images_list))
         self.assertTrue(self.same_disk_format_set <= result_set)
-        self.assertFalse(self.created_set - self.same_disk_format_set
-                         <= result_set)
+        self.assertFalse(self.created_set - self.same_disk_format_set <=
+                         result_set)
 
     @decorators.idempotent_id('2143655d-96d9-4bec-9188-8674206b4b3b')
     def test_index_container_format(self):
@@ -238,8 +238,8 @@
             self.assertEqual(image['container_format'], self.container_format)
         result_set = set(map(lambda x: x['id'], images_list))
         self.assertTrue(self.same_container_format_set <= result_set)
-        self.assertFalse(self.created_set - self.same_container_format_set
-                         <= result_set)
+        self.assertFalse(self.created_set - self.same_container_format_set <=
+                         result_set)
 
     @decorators.idempotent_id('feb32ac6-22bb-4a16-afd8-9454bb714b14')
     def test_index_max_size(self):
diff --git a/tempest/api/image/v2/admin/__init__.py b/tempest/api/image/v2/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/image/v2/admin/__init__.py
diff --git a/tempest/api/image/v2/admin/test_images.py b/tempest/api/image/v2/admin/test_images.py
new file mode 100644
index 0000000..dbb8c58
--- /dev/null
+++ b/tempest/api/image/v2/admin/test_images.py
@@ -0,0 +1,51 @@
+# Copyright 2018 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.image import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+
+class BasicOperationsImagesAdminTest(base.BaseV2ImageAdminTest):
+
+    @decorators.related_bug('1420008')
+    @decorators.idempotent_id('646a6eaa-135f-4493-a0af-12583021224e')
+    def test_create_image_owner_param(self):
+        # NOTE: Create image with owner different from tenant owner by
+        # using "owner" parameter requires an admin privileges.
+        random_id = data_utils.rand_uuid_hex()
+        image = self.admin_client.create_image(
+            container_format='bare', disk_format='raw', owner=random_id)
+        self.addCleanup(self.admin_client.delete_image, image['id'])
+        image_info = self.admin_client.show_image(image['id'])
+        self.assertEqual(random_id, image_info['owner'])
+
+    @decorators.related_bug('1420008')
+    @decorators.idempotent_id('525ba546-10ef-4aad-bba1-1858095ce553')
+    def test_update_image_owner_param(self):
+        random_id_1 = data_utils.rand_uuid_hex()
+        image = self.admin_client.create_image(
+            container_format='bare', disk_format='raw', owner=random_id_1)
+        self.addCleanup(self.admin_client.delete_image, image['id'])
+        created_image_info = self.admin_client.show_image(image['id'])
+
+        random_id_2 = data_utils.rand_uuid_hex()
+        self.admin_client.update_image(
+            image['id'], [dict(replace="/owner", value=random_id_2)])
+        updated_image_info = self.admin_client.show_image(image['id'])
+
+        self.assertEqual(random_id_2, updated_image_info['owner'])
+        self.assertNotEqual(created_image_info['owner'],
+                            updated_image_info['owner'])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index c846f88..ce5bd3e 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -18,8 +18,6 @@
 
 import six
 
-import testtools
-
 from oslo_log import log as logging
 from tempest.api.image import base
 from tempest import config
@@ -128,8 +126,6 @@
         self.assertEqual(image['id'], body['id'])
         self.assertEqual(new_image_name, body['name'])
 
-    @testtools.skipUnless(CONF.image_feature_enabled.deactivate_image,
-                          'deactivate-image is not available.')
     @decorators.idempotent_id('951ebe01-969f-4ea9-9898-8a3f1f442ab0')
     def test_deactivate_reactivate_image(self):
         # Create image
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
index 69bebfe..482e808 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
@@ -49,7 +49,7 @@
         # List namespace tags
         body = self.namespace_tags_client.list_namespace_tags(
             namespace['namespace'])
-        self.assertTrue(3, len(body['tags']))
+        self.assertEqual(3, len(body['tags']))
         self.assertIn(body['tags'][0]['name'], self.tag_list)
         self.assertIn(body['tags'][1]['name'], self.tag_list)
         self.assertIn(body['tags'][2]['name'], self.tag_list)
diff --git a/tempest/api/network/admin/test_external_network_extension.py b/tempest/api/network/admin/test_external_network_extension.py
index 4d41e33..49a9cdb 100644
--- a/tempest/api/network/admin/test_external_network_extension.py
+++ b/tempest/api/network/admin/test_external_network_extension.py
@@ -130,5 +130,3 @@
         subnet_list = self.admin_subnets_client.list_subnets()
         self.assertNotIn(subnet['id'],
                          (s['id'] for s in subnet_list))
-        # Removes subnet from the cleanup list
-        self.subnets.remove(subnet)
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 1a7b0ec..206d867 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -51,7 +51,8 @@
         agents = cls.admin_agents_client.list_agents(
             agent_type=AGENT_TYPE)['agents']
         for agent in agents:
-            if agent['configurations']['agent_mode'] in AGENT_MODES:
+            if (agent['configurations']['agent_mode'] in AGENT_MODES and
+                agent['alive']):
                 cls.agent = agent
                 break
         else:
diff --git a/tempest/api/network/admin/test_ports.py b/tempest/api/network/admin/test_ports.py
index 807994b..483b405 100644
--- a/tempest/api/network/admin/test_ports.py
+++ b/tempest/api/network/admin/test_ports.py
@@ -13,8 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import socket
-
 from tempest.api.network import base
 from tempest import config
 from tempest.lib import decorators
@@ -25,10 +23,16 @@
 class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest):
 
     @classmethod
+    def setup_clients(cls):
+        super(PortsAdminExtendedAttrsTestJSON, cls).setup_clients()
+        cls.hyper_client = cls.os_admin.hypervisor_client
+
+    @classmethod
     def resource_setup(cls):
         super(PortsAdminExtendedAttrsTestJSON, cls).resource_setup()
         cls.network = cls.create_network()
-        cls.host_id = socket.gethostname()
+        hyper_list = cls.hyper_client.list_hypervisors()
+        cls.host_id = hyper_list['hypervisors'][0]['hypervisor_hostname']
 
     @decorators.idempotent_id('8e8569c1-9ac7-44db-8bc1-f5fb2814f29b')
     def test_create_port_binding_ext_attr(self):
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index cf4236d..b1e4a58 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.api.network import base
 from tempest.common import identity
 from tempest.common import utils
@@ -80,8 +82,24 @@
         non_default_quotas = self.admin_quotas_client.list_quotas()
         for q in non_default_quotas['quotas']:
             self.assertNotEqual(project_id, q['tenant_id'])
+        quota_set = self.admin_quotas_client.show_quotas(project_id)['quota']
+        default_quotas = self.admin_quotas_client.show_default_quotas(
+            project_id)['quota']
+        self.assertEqual(default_quotas, quota_set)
 
     @decorators.idempotent_id('2390f766-836d-40ef-9aeb-e810d78207fb')
     def test_quotas(self):
         new_quotas = {'network': 0, 'port': 0}
         self._check_quotas(new_quotas)
+
+    @testtools.skipUnless(utils.is_extension_enabled(
+        'quota_details', 'network'), 'Quota details extension not enabled.')
+    @decorators.idempotent_id('7b05ec5f-bf44-43cb-b28f-ddd72a824288')
+    def test_show_quota_details(self):
+        # Show quota details for an existing project
+        quota_details = self.admin_quotas_client.show_quota_details(
+            self.admin_quotas_client.tenant_id)['quota']
+        expected_keys = ['used', 'limit', 'reserved']
+        for resource_type in quota_details:
+            for key in expected_keys:
+                self.assertIn(key, quota_details[resource_type])
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index bdfda0a..8670165 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -88,7 +88,6 @@
     @classmethod
     def resource_setup(cls):
         super(BaseNetworkTest, cls).resource_setup()
-        cls.networks = []
         cls.subnets = []
         cls.ports = []
         cls.routers = []
@@ -101,27 +100,6 @@
             cls.mask_bits = CONF.network.project_network_v6_mask_bits
 
     @classmethod
-    def resource_cleanup(cls):
-        if CONF.service_available.neutron:
-            # Clean up ports
-            for port in cls.ports:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.ports_client.delete_port, port['id'])
-            # Clean up routers
-            for router in cls.routers:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.delete_router, router)
-            # Clean up subnets
-            for subnet in cls.subnets:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.subnets_client.delete_subnet, subnet['id'])
-            # Clean up networks
-            for network in cls.networks:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.networks_client.delete_network, network['id'])
-        super(BaseNetworkTest, cls).resource_cleanup()
-
-    @classmethod
     def create_network(cls, network_name=None, **kwargs):
         """Wrapper utility that returns a test network."""
         network_name = network_name or data_utils.rand_name(
@@ -129,7 +107,9 @@
 
         body = cls.networks_client.create_network(name=network_name, **kwargs)
         network = body['network']
-        cls.networks.append(network)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.networks_client.delete_network,
+                                    network['id'])
         return network
 
     @classmethod
@@ -172,6 +152,9 @@
             message = 'Available CIDR for subnet creation could not be found'
             raise exceptions.BuildErrorException(message)
         subnet = body['subnet']
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.subnets_client.delete_subnet,
+                                    subnet['id'])
         cls.subnets.append(subnet)
         return subnet
 
@@ -181,6 +164,8 @@
         body = cls.ports_client.create_port(network_id=network['id'],
                                             **kwargs)
         port = body['port']
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.ports_client.delete_port, port['id'])
         cls.ports.append(port)
         return port
 
@@ -207,6 +192,8 @@
             name=router_name, external_gateway_info=ext_gw_info,
             admin_state_up=admin_state_up, **kwargs)
         router = body['router']
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.delete_router, router)
         cls.routers.append(router)
         return router
 
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 1c59556..7345fd1 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -104,15 +104,6 @@
         self.assertThat(actual, custom_matchers.MatchesDictExceptForKeys(
                         expected, exclude_keys))
 
-    def _delete_network(self, network):
-        # Deleting network also deletes its subnets if exists
-        self.networks_client.delete_network(network['id'])
-        if network in self.networks:
-            self.networks.remove(network)
-        for subnet in self.subnets:
-            if subnet['network_id'] == network['id']:
-                self.subnets.remove(subnet)
-
     def _create_verify_delete_subnet(self, cidr=None, mask_bits=None,
                                      **kwargs):
         network = self.create_network()
@@ -132,8 +123,6 @@
 
         self._compare_resource_attrs(subnet, compare_args)
         self.networks_client.delete_network(net_id)
-        self.networks.pop()
-        self.subnets.pop()
 
 
 class NetworksTest(BaseNetworkTestResources):
@@ -171,7 +160,7 @@
     def test_create_update_delete_network_subnet(self):
         # Create a network
         network = self.create_network()
-        self.addCleanup(self._delete_network, network)
+        self.addCleanup(self.networks_client.delete_network, network['id'])
         net_id = network['id']
         self.assertEqual('ACTIVE', network['status'])
         # Verify network update
@@ -280,7 +269,7 @@
         network = self.create_network()
         net_id = network['id']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self._delete_network, network)
+                        self.networks_client.delete_network, network['id'])
 
         # Find a cidr that is not in use yet and create a subnet with it
         subnet = self.create_subnet(network)
@@ -324,7 +313,7 @@
     @decorators.idempotent_id('3d3852eb-3009-49ec-97ac-5ce83b73010a')
     def test_update_subnet_gw_dns_host_routes_dhcp(self):
         network = self.create_network()
-        self.addCleanup(self._delete_network, network)
+        self.addCleanup(self.networks_client.delete_network, network['id'])
 
         subnet = self.create_subnet(
             network, **self.subnet_dict(['gateway', 'host_routes',
@@ -622,7 +611,6 @@
         port = self.create_port(slaac_network)
         self.assertIsNotNone(port['fixed_ips'][0]['ip_address'])
         self.subnets_client.delete_subnet(subnet_slaac['id'])
-        self.subnets.pop()
         subnets = self.subnets_client.list_subnets()
         subnet_ids = [subnet['id'] for subnet in subnets['subnets']]
         self.assertNotIn(subnet_slaac['id'], subnet_ids,
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index eb53fbb..5168423 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -13,7 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import ipaddress
+
 import netaddr
+import six
 import testtools
 
 from tempest.api.network import base_security_groups as sec_base
@@ -178,6 +181,83 @@
         self.assertIn(port_1_fixed_ip, port_ips)
         self.assertIn(network['id'], port_net_ids)
 
+    @decorators.idempotent_id('79895408-85d5-460d-94e7-9531c5fd9123')
+    @testtools.skipUnless(
+        utils.is_extension_enabled('ip-substring-filtering', 'network'),
+        'ip-substring-filtering extension not enabled.')
+    def test_port_list_filter_by_ip_substr(self):
+        # Create network and subnet
+        network = self.create_network()
+        subnet = self.create_subnet(network)
+        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+
+        # Get two IP addresses
+        ip_address_1 = None
+        ip_address_2 = None
+        ip_network = ipaddress.ip_network(six.text_type(subnet['cidr']))
+        for ip in ip_network:
+            if ip == ip_network.network_address:
+                continue
+            if ip_address_1 is None:
+                ip_address_1 = six.text_type(ip)
+            else:
+                ip_address_2 = ip_address_1
+                ip_address_1 = six.text_type(ip)
+                # Make sure these two IP addresses have different substring
+                if ip_address_1[:-1] != ip_address_2[:-1]:
+                    break
+
+        # Create two ports
+        fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': ip_address_1}]
+        port_1 = self.ports_client.create_port(network_id=network['id'],
+                                               fixed_ips=fixed_ips)
+        self.addCleanup(self.ports_client.delete_port, port_1['port']['id'])
+        fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': ip_address_2}]
+        port_2 = self.ports_client.create_port(network_id=network['id'],
+                                               fixed_ips=fixed_ips)
+        self.addCleanup(self.ports_client.delete_port, port_2['port']['id'])
+
+        # Scenario 1: List port1 (port2 is filtered out)
+        if ip_address_1[:-1] != ip_address_2[:-1]:
+            ips_filter = 'ip_address_substr=' + ip_address_1[:-1]
+        else:
+            ips_filter = 'ip_address_substr=' + ip_address_1
+        ports = self.ports_client.list_ports(fixed_ips=ips_filter)['ports']
+        # Check that we got the desired port
+        port_ids = [port['id'] for port in ports]
+        fixed_ips = [port['fixed_ips'] for port in ports]
+        port_ips = []
+        for addr in fixed_ips:
+            port_ips.extend([a['ip_address'] for a in addr])
+
+        port_net_ids = [port['network_id'] for port in ports]
+        self.assertIn(network['id'], port_net_ids)
+        self.assertIn(port_1['port']['id'], port_ids)
+        self.assertIn(port_1['port']['fixed_ips'][0]['ip_address'], port_ips)
+        self.assertNotIn(port_2['port']['id'], port_ids)
+        self.assertNotIn(
+            port_2['port']['fixed_ips'][0]['ip_address'], port_ips)
+
+        # Scenario 2: List both port1 and port2
+        substr = ip_address_1
+        while substr not in ip_address_2:
+            substr = substr[:-1]
+        ips_filter = 'ip_address_substr=' + substr
+        ports = self.ports_client.list_ports(fixed_ips=ips_filter)['ports']
+        # Check that we got both port
+        port_ids = [port['id'] for port in ports]
+        fixed_ips = [port['fixed_ips'] for port in ports]
+        port_ips = []
+        for addr in fixed_ips:
+            port_ips.extend([a['ip_address'] for a in addr])
+
+        port_net_ids = [port['network_id'] for port in ports]
+        self.assertIn(network['id'], port_net_ids)
+        self.assertIn(port_1['port']['id'], port_ids)
+        self.assertIn(port_1['port']['fixed_ips'][0]['ip_address'], port_ips)
+        self.assertIn(port_2['port']['id'], port_ids)
+        self.assertIn(port_2['port']['fixed_ips'][0]['ip_address'], port_ips)
+
     @decorators.idempotent_id('5ad01ed0-0e6e-4c5d-8194-232801b15c72')
     def test_port_list_filter_by_router_id(self):
         # Create a router
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index c9ce55c..ddd7d3a 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -84,6 +84,8 @@
     def test_router_remove_interface_in_use_returns_409(self):
         self.routers_client.add_router_interface(self.router['id'],
                                                  subnet_id=self.subnet['id'])
+        self.addCleanup(self.routers_client.remove_router_interface,
+                        self.router['id'], subnet_id=self.subnet['id'])
         self.assertRaises(lib_exc.Conflict,
                           self.routers_client.delete_router,
                           self.router['id'])
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index ee72163..e8f3f8b 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -36,10 +36,14 @@
     using HA proxy sync the deletion properly, otherwise, the container
     might fail to be deleted because it's not empty.
 
-    :param containers: List of containers to be deleted
+    :param containers: List of containers(or string of a container)
+                       to be deleted
     :param container_client: Client to be used to delete containers
     :param object_client: Client to be used to delete objects
     """
+    if isinstance(containers, str):
+        containers = [containers]
+
     for cont in containers:
         try:
             params = {'limit': 9999, 'format': 'json'}
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index d7c85a2..c5c30e3 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -44,14 +44,13 @@
         for i in range(ord('a'), ord('f') + 1):
             name = data_utils.rand_name(name='%s-' % six.int2byte(i))
             cls.container_client.update_container(name)
+            cls.addClassResourceCleanup(base.delete_containers,
+                                        [name],
+                                        cls.container_client,
+                                        cls.object_client)
             cls.containers.append(name)
         cls.containers_count = len(cls.containers)
 
-    @classmethod
-    def resource_cleanup(cls):
-        cls.delete_containers()
-        super(AccountTest, cls).resource_cleanup()
-
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('3499406a-ae53-4f8c-b43a-133d4dc6fe3f')
     def test_list_containers(self):
@@ -242,7 +241,7 @@
     @decorators.idempotent_id('365e6fc7-1cfe-463b-a37c-8bd08d47b6aa')
     def test_list_containers_with_prefix(self):
         # list containers that have a name that starts with a prefix
-        prefix = '{0}-a'.format(CONF.resources_prefix)
+        prefix = 'tempest-a'
         params = {'prefix': prefix}
         resp, container_list = self.account_client.list_account_containers(
             params=params)
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 042d288..322579c 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -33,8 +33,6 @@
 
 
 class ContainerSyncTest(base.BaseObjectTest):
-    clients = {}
-
     credentials = [['operator', CONF.object_storage.operator_role],
                    ['operator_alt', CONF.object_storage.operator_role]]
 
@@ -54,6 +52,7 @@
         super(ContainerSyncTest, cls).resource_setup()
         cls.containers = []
         cls.objects = []
+        cls.clients = {}
 
         # Default container-server config only allows localhost
         cls.local_ip = '127.0.0.1'
@@ -72,14 +71,12 @@
             (cls.container_client_alt, cls.object_client_alt)
         for cont_name, client in cls.clients.items():
             client[0].create_container(cont_name)
+            cls.addClassResourceCleanup(base.delete_containers,
+                                        cont_name,
+                                        client[0],
+                                        client[1])
             cls.containers.append(cont_name)
 
-    @classmethod
-    def resource_cleanup(cls):
-        for client in cls.clients.values():
-            cls.delete_containers(client[0], client[1])
-        super(ContainerSyncTest, cls).resource_cleanup()
-
     def _test_container_synchronization(self, make_headers):
         # container to container synchronization
         # to allow/accept sync requests to/from other accounts
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index 51b0a1d..75111b6 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -24,16 +24,6 @@
 
 
 class ContainerTest(base.BaseObjectTest):
-    @classmethod
-    def resource_setup(cls):
-        super(ContainerTest, cls).resource_setup()
-        cls.containers = []
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.delete_containers()
-        super(ContainerTest, cls).resource_cleanup()
-
     def assertContainer(self, container, count, byte, versioned):
         resp, _ = self.container_client.list_container_metadata(container)
         self.assertHeaders(resp, 'Container', 'HEAD')
@@ -52,7 +42,10 @@
         # create container
         vers_container_name = data_utils.rand_name(name='TestVersionContainer')
         resp, _ = self.container_client.update_container(vers_container_name)
-        self.containers.append(vers_container_name)
+        self.addCleanup(base.delete_containers,
+                        [vers_container_name],
+                        self.container_client,
+                        self.object_client)
         self.assertHeaders(resp, 'Container', 'PUT')
         self.assertContainer(vers_container_name, '0', '0', 'Missing Header')
 
@@ -61,7 +54,10 @@
         resp, _ = self.container_client.update_container(
             base_container_name,
             **headers)
-        self.containers.append(base_container_name)
+        self.addCleanup(base.delete_containers,
+                        [base_container_name],
+                        self.container_client,
+                        self.object_client)
         self.assertHeaders(resp, 'Container', 'PUT')
         self.assertContainer(base_container_name, '0', '0',
                              vers_container_name)
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index 36e0035..45f4caa 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -16,13 +16,22 @@
 
 from tempest.api.volume import base
 from tempest.common import waiters
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
+CONF = config.CONF
+
 
 class BaseGroupSnapshotsTest(base.BaseVolumeAdminTest):
 
+    @classmethod
+    def skip_checks(cls):
+        super(BaseGroupSnapshotsTest, cls).skip_checks()
+        if not CONF.volume_feature_enabled.snapshot:
+            raise cls.skipException("Cinder volume snapshots are disabled")
+
     def _create_group_snapshot(self, **kwargs):
         if 'name' not in kwargs:
             kwargs['name'] = data_utils.rand_name(
diff --git a/tempest/api/volume/admin/test_group_type_specs.py b/tempest/api/volume/admin/test_group_type_specs.py
new file mode 100644
index 0000000..c5e6d1a
--- /dev/null
+++ b/tempest/api/volume/admin/test_group_type_specs.py
@@ -0,0 +1,80 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.volume import base
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class GroupTypeSpecsTest(base.BaseVolumeAdminTest):
+    _api_version = 3
+    min_microversion = '3.11'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('bb4e30d0-de6e-4f4d-866c-dcc48d023b4e')
+    def test_group_type_specs_create_show_update_list_delete(self):
+        # Create new group type
+        group_type = self.create_group_type()
+
+        # Create new group type specs
+        create_specs = {
+            "key1": "value1",
+            "key2": "value2"
+        }
+        body = self.admin_group_types_client.create_or_update_group_type_specs(
+            group_type['id'], create_specs)['group_specs']
+        self.assertEqual(create_specs, body)
+
+        # Create a new group type spec and update an existing group type spec
+        update_specs = {
+            "key2": "value2-updated",
+            "key3": "value3"
+        }
+        body = self.admin_group_types_client.create_or_update_group_type_specs(
+            group_type['id'], update_specs)['group_specs']
+        self.assertEqual(update_specs, body)
+
+        # Show specified item of group type specs
+        spec_keys = ['key2', 'key3']
+        for key in spec_keys:
+            body = self.admin_group_types_client.show_group_type_specs_item(
+                group_type['id'], key)
+            self.assertIn(key, body)
+            self.assertEqual(update_specs[key], body[key])
+
+        # Update specified item of group type specs
+        update_key = 'key3'
+        update_spec = {update_key: "value3-updated"}
+        body = self.admin_group_types_client.update_group_type_specs_item(
+            group_type['id'], update_key, update_spec)
+        self.assertEqual(update_spec, body)
+
+        # List all group type specs that created or updated above
+        list_specs = {}
+        list_specs.update(create_specs)
+        list_specs.update(update_specs)
+        list_specs.update(update_spec)
+        body = self.admin_group_types_client.list_group_type_specs(
+            group_type['id'])['group_specs']
+        self.assertEqual(list_specs, body)
+
+        # Delete specified item of group type specs
+        delete_key = 'key1'
+        self.admin_group_types_client.delete_group_type_specs_item(
+            group_type['id'], delete_key)
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.admin_group_types_client.show_group_type_specs_item,
+            group_type['id'], delete_key)
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index 9ff7160..37a47ec 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -35,6 +35,9 @@
     def skip_checks(cls):
         super(SnapshotManageAdminTest, cls).skip_checks()
 
+        if not CONF.volume_feature_enabled.snapshot:
+            raise cls.skipException("Cinder volume snapshots are disabled")
+
         if not CONF.volume_feature_enabled.manage_snapshot:
             raise cls.skipException("Manage snapshot tests are disabled")
 
@@ -60,7 +63,7 @@
         # Verify the original snapshot does not exist in snapshot list
         params = {'all_tenants': 1}
         all_snapshots = self.admin_snapshots_client.list_snapshots(
-            detail=True, params=params)['snapshots']
+            detail=True, **params)['snapshots']
         self.assertNotIn(snapshot['id'], [v['id'] for v in all_snapshots])
 
         # Manage the snapshot
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index ce0cbd2..7e53ce8 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -46,8 +46,8 @@
         # show host API should fail (return code: 404). The cinder-volume host
         # is presented in format: <host-name>@driver-name.
         c_vol_hosts = [host['host_name'] for host in hosts
-                       if (host['service'] == 'cinder-volume'
-                           and host['service-state'] == 'enabled')]
+                       if (host['service'] == 'cinder-volume' and
+                           host['service-state'] == 'enabled')]
         self.assertNotEmpty(c_vol_hosts,
                             "No available cinder-volume host is found, "
                             "all hosts that found are: %s" % hosts)
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 42bfcd6..6f9daa8 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -38,7 +38,6 @@
     def setup_credentials(cls):
         super(BaseVolumeQuotasAdminTestJSON, cls).setup_credentials()
         cls.demo_tenant_id = cls.os_primary.credentials.tenant_id
-        cls.alt_client = cls.os_alt.volumes_client_latest
 
     @classmethod
     def setup_clients(cls):
@@ -150,7 +149,8 @@
             self.demo_tenant_id, params={'usage': True})['quota_set']
 
         alt_quota = self.admin_quotas_client.show_quota_set(
-            self.alt_client.tenant_id, params={'usage': True})['quota_set']
+            self.os_alt.volumes_client_latest.tenant_id,
+            params={'usage': True})['quota_set']
 
         # Creates a volume transfer
         transfer = self.transfer_client.create_volume_transfer(
@@ -164,14 +164,15 @@
 
         # Verify volume transferred is available
         waiters.wait_for_volume_resource_status(
-            self.alt_client, volume['id'], 'available')
+            self.os_alt.volumes_client_latest, volume['id'], 'available')
 
         # List of tenants quota usage post transfer
         new_primary_quota = self.admin_quotas_client.show_quota_set(
             self.demo_tenant_id, params={'usage': True})['quota_set']
 
         new_alt_quota = self.admin_quotas_client.show_quota_set(
-            self.alt_client.tenant_id, params={'usage': True})['quota_set']
+            self.os_alt.volumes_client_latest.tenant_id,
+            params={'usage': True})['quota_set']
 
         # Verify tenants quota usage was updated
         self.assertEqual(primary_quota['volumes']['in_use'] -
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
index e93bcb5..b64face 100644
--- a/tempest/api/volume/admin/test_volume_type_access.py
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -27,11 +27,6 @@
 
     credentials = ['primary', 'alt', 'admin']
 
-    @classmethod
-    def setup_clients(cls):
-        super(VolumeTypesAccessTest, cls).setup_clients()
-        cls.alt_client = cls.os_alt.volumes_client_latest
-
     @decorators.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184')
     def test_volume_type_access_add(self):
         # Creating a NON public volume type
@@ -70,10 +65,11 @@
 
         # Adding volume type access for alt tenant
         self.admin_volume_types_client.add_type_access(
-            volume_type['id'], project=self.alt_client.tenant_id)
+            volume_type['id'],
+            project=self.os_alt.volumes_client_latest.tenant_id)
         self.addCleanup(self.admin_volume_types_client.remove_type_access,
                         volume_type['id'],
-                        project=self.alt_client.tenant_id)
+                        project=self.os_alt.volumes_client_latest.tenant_id)
 
         # List tenant access for the given volume type
         type_access_list = self.admin_volume_types_client.list_type_access(
@@ -88,5 +84,5 @@
         # Validating the permitted tenants are the expected tenants
         self.assertIn(self.volumes_client.tenant_id,
                       map(operator.itemgetter('project_id'), type_access_list))
-        self.assertIn(self.alt_client.tenant_id,
+        self.assertIn(self.os_alt.volumes_client_latest.tenant_id,
                       map(operator.itemgetter('project_id'), type_access_list))
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index af1024c..1077524 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -161,6 +161,12 @@
                              'The fetched encryption_type %s is different '
                              'from the updated encryption_type' % key)
 
+        # Get encryption specs item
+        key = 'cipher'
+        item = self.admin_encryption_types_client.show_encryption_specs_item(
+            encrypt_type_id, key)
+        self.assertEqual(update_kwargs[key], item[key])
+
         # Delete encryption type
         self.admin_encryption_types_client.delete_encryption_type(
             encrypt_type_id)
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index ea3bb5a..81fd6e6 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -31,6 +31,11 @@
     """Base test case class for all Cinder API tests."""
 
     _api_version = 2
+    # if api_v2 is not enabled while api_v3 is enabled, the volume v2 classes
+    # should be transferred to volume v3 classes.
+    if (not CONF.volume_feature_enabled.api_v2 and
+        CONF.volume_feature_enabled.api_v3):
+        _api_version = 3
     credentials = ['primary']
 
     @classmethod
@@ -101,20 +106,12 @@
                 cls.min_microversion,
                 CONF.volume.min_microversion))
 
-        cls.snapshots = []
-        cls.volumes = []
         cls.image_ref = CONF.compute.image_ref
         cls.flavor_ref = CONF.compute.flavor_ref
         cls.build_interval = CONF.volume.build_interval
         cls.build_timeout = CONF.volume.build_timeout
 
     @classmethod
-    def resource_cleanup(cls):
-        cls.clear_snapshots()
-        cls.clear_volumes()
-        super(BaseVolumeTest, cls).resource_cleanup()
-
-    @classmethod
     def create_volume(cls, wait_until='available', **kwargs):
         """Wrapper utility that returns a test volume.
 
@@ -133,7 +130,9 @@
             kwargs['name'] = name
 
         volume = cls.volumes_client.create_volume(**kwargs)['volume']
-        cls.volumes.append(volume)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.delete_volume, cls.volumes_client,
+                                    volume['id'])
         waiters.wait_for_volume_resource_status(cls.volumes_client,
                                                 volume['id'], wait_until)
         return volume
@@ -147,7 +146,8 @@
 
         snapshot = cls.snapshots_client.create_snapshot(
             volume_id=volume_id, **kwargs)['snapshot']
-        cls.snapshots.append(snapshot['id'])
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.delete_snapshot, snapshot['id'])
         waiters.wait_for_volume_resource_status(cls.snapshots_client,
                                                 snapshot['id'], 'available')
         return snapshot
@@ -176,14 +176,13 @@
         client.delete_volume(volume_id)
         client.wait_for_resource_deletion(volume_id)
 
-    def delete_snapshot(self, snapshot_id, snapshots_client=None):
+    @classmethod
+    def delete_snapshot(cls, snapshot_id, snapshots_client=None):
         """Delete snapshot by the given client"""
         if snapshots_client is None:
-            snapshots_client = self.snapshots_client
+            snapshots_client = cls.snapshots_client
         snapshots_client.delete_snapshot(snapshot_id)
         snapshots_client.wait_for_resource_deletion(snapshot_id)
-        if snapshot_id in self.snapshots:
-            self.snapshots.remove(snapshot_id)
 
     def attach_volume(self, server_id, volume_id):
         """Attach a volume to a server"""
@@ -197,31 +196,6 @@
         self.addCleanup(self.servers_client.detach_volume, server_id,
                         volume_id)
 
-    @classmethod
-    def clear_volumes(cls):
-        for volume in cls.volumes:
-            try:
-                cls.volumes_client.delete_volume(volume['id'])
-            except Exception:
-                pass
-
-        for volume in cls.volumes:
-            try:
-                cls.volumes_client.wait_for_resource_deletion(volume['id'])
-            except Exception:
-                pass
-
-    @classmethod
-    def clear_snapshots(cls):
-        for snapshot in cls.snapshots:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.snapshots_client.delete_snapshot, snapshot)
-
-        for snapshot in cls.snapshots:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.snapshots_client.wait_for_resource_deletion,
-                snapshot)
-
     def create_server(self, wait_until='ACTIVE', **kwargs):
         name = kwargs.pop(
             'name',
@@ -303,26 +277,13 @@
             cls.os_admin.volume_scheduler_stats_v2_client
 
     @classmethod
-    def resource_setup(cls):
-        super(BaseVolumeAdminTest, cls).resource_setup()
-
-        cls.qos_specs = []
-        cls.volume_types = []
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.clear_qos_specs()
-        super(BaseVolumeAdminTest, cls).resource_cleanup()
-        cls.clear_volume_types()
-
-    @classmethod
     def create_test_qos_specs(cls, name=None, consumer=None, **kwargs):
         """create a test Qos-Specs."""
         name = name or data_utils.rand_name(cls.__name__ + '-QoS')
         consumer = consumer or 'front-end'
         qos_specs = cls.admin_volume_qos_client.create_qos(
             name=name, consumer=consumer, **kwargs)['qos_specs']
-        cls.qos_specs.append(qos_specs['id'])
+        cls.addClassResourceCleanup(cls.clear_qos_spec, qos_specs['id'])
         return qos_specs
 
     @classmethod
@@ -331,7 +292,7 @@
         name = name or data_utils.rand_name(cls.__name__ + '-volume-type')
         volume_type = cls.admin_volume_types_client.create_volume_type(
             name=name, **kwargs)['volume_type']
-        cls.volume_types.append(volume_type['id'])
+        cls.addClassResourceCleanup(cls.clear_volume_type, volume_type['id'])
         return volume_type
 
     def create_group_type(self, name=None, **kwargs):
@@ -345,22 +306,18 @@
         return group_type
 
     @classmethod
-    def clear_qos_specs(cls):
-        for qos_id in cls.qos_specs:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.admin_volume_qos_client.delete_qos, qos_id)
+    def clear_qos_spec(cls, qos_id):
+        test_utils.call_and_ignore_notfound_exc(
+            cls.admin_volume_qos_client.delete_qos, qos_id)
 
-        for qos_id in cls.qos_specs:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
+        test_utils.call_and_ignore_notfound_exc(
+            cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
 
     @classmethod
-    def clear_volume_types(cls):
-        for vol_type in cls.volume_types:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.admin_volume_types_client.delete_volume_type, vol_type)
+    def clear_volume_type(cls, vol_type_id):
+        test_utils.call_and_ignore_notfound_exc(
+            cls.admin_volume_types_client.delete_volume_type, vol_type_id)
 
-        for vol_type in cls.volume_types:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.admin_volume_types_client.wait_for_resource_deletion,
-                vol_type)
+        test_utils.call_and_ignore_notfound_exc(
+            cls.admin_volume_types_client.wait_for_resource_deletion,
+            vol_type_id)
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 1e240b8..552b231 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -40,7 +40,7 @@
             backup_id)['restore']
 
         # Delete backup
-        self.addCleanup(self.volumes_client.delete_volume,
+        self.addCleanup(self.delete_volume, self.volumes_client,
                         restored_volume['volume_id'])
         self.assertEqual(backup_id, restored_volume['backup_id'])
         waiters.wait_for_volume_resource_status(self.backups_client,
@@ -59,8 +59,7 @@
                     "vol-meta2": "value2",
                     "vol-meta3": "value3"}
         volume = self.create_volume(metadata=metadata)
-        self.addCleanup(self.volumes_client.delete_volume,
-                        volume['id'])
+        self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
 
         # Create a backup
         backup_name = data_utils.rand_name(
@@ -109,8 +108,7 @@
         """
         # Create a server
         volume = self.create_volume()
-        self.addCleanup(self.volumes_client.delete_volume,
-                        volume['id'])
+        self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
         server = self.create_server()
         # Attach volume to instance
         self.attach_volume(server['id'], volume['id'])
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index b73bdf2..362f4cc 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -32,7 +32,7 @@
     @decorators.idempotent_id('9a36df71-a257-43a5-9555-dc7c88e66e0e')
     def test_volume_extend(self):
         # Extend Volume Test.
-        volume = self.create_volume()
+        volume = self.create_volume(image_ref=self.image_ref)
         extend_size = volume['size'] + 1
         self.volumes_client.extend_volume(volume['id'],
                                           new_size=extend_size)
@@ -80,11 +80,6 @@
     # is implicit - Cinder calls Nova at that microversion, Tempest does not.
     min_microversion = '3.42'
 
-    @classmethod
-    def setup_clients(cls):
-        super(VolumesExtendAttachedTest, cls).setup_clients()
-        cls.admin_servers_client = cls.os_admin.servers_client
-
     def _find_extend_volume_instance_action(self, server_id):
         actions = self.servers_client.list_instance_actions(
             server_id)['instanceActions']
@@ -95,7 +90,7 @@
     def _find_extend_volume_instance_action_finish_event(self, action):
         # This has to be called by an admin client otherwise
         # the events don't show up.
-        action = self.admin_servers_client.show_instance_action(
+        action = self.os_admin.servers_client.show_instance_action(
             action['instance_uuid'], action['request_id'])['instanceAction']
         for event in action['events']:
             if (event['event'] == 'compute_extend_volume' and
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index b5f98ea..d5358ab 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -26,15 +26,28 @@
 
 
 class VolumesListTestJSON(base.BaseVolumeTest):
-    # NOTE: This test creates a number of 1G volumes. To run successfully,
-    # ensure that the backing file for the volume group that Nova uses
+    # NOTE: This test creates a number of 1G volumes. To run it successfully,
+    # ensure that the backing file for the volume group that Cinder uses
     # has space for at least 3 1G volumes!
     # If you are running a Devstack environment, ensure that the
     # VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
 
     VOLUME_FIELDS = ('id', 'name')
 
-    def assertVolumesIn(self, fetched_list, expected_list, fields=None):
+    @classmethod
+    def _remove_volatile_fields(cls, fetched_list):
+        """Remove fields that should not be compared.
+
+        This method makes sure that Tempest does not compare e.g.
+        the volume's "updated_at" field that may change for any reason
+        internal to the operation of Cinder.
+        """
+        for volume in fetched_list:
+            for field in ('updated_at',):
+                if field in volume:
+                    del volume[field]
+
+    def _assert_volumes_in(self, fetched_list, expected_list, fields=None):
         """Check out the list.
 
         This function is aim at check out whether all of the volumes in
@@ -45,6 +58,8 @@
             expected_list = map(fieldsgetter, expected_list)
             fetched_list = [fieldsgetter(item) for item in fetched_list]
 
+        # Hopefully the expected_list has already been cleaned.
+        self._remove_volatile_fields(fetched_list)
         missing_vols = [v for v in expected_list if v not in fetched_list]
         if not missing_vols:
             return
@@ -72,6 +87,7 @@
             volume = cls.volumes_client.show_volume(volume['id'])['volume']
             cls.volume_list.append(volume)
             cls.volume_id_list.append(volume['id'])
+        cls._remove_volatile_fields(cls.volume_list)
 
     def _list_by_param_value_and_assert(self, params, with_detail=False):
         """list or list_details with given params and validates result"""
@@ -103,15 +119,15 @@
         # Get a list of Volumes
         # Fetch all volumes
         fetched_list = self.volumes_client.list_volumes()['volumes']
-        self.assertVolumesIn(fetched_list, self.volume_list,
-                             fields=self.VOLUME_FIELDS)
+        self._assert_volumes_in(fetched_list, self.volume_list,
+                                fields=self.VOLUME_FIELDS)
 
     @decorators.idempotent_id('adcbb5a7-5ad8-4b61-bd10-5380e111a877')
     def test_volume_list_with_details(self):
         # Get a list of Volumes with details
         # Fetch all Volumes
         fetched_list = self.volumes_client.list_volumes(detail=True)['volumes']
-        self.assertVolumesIn(fetched_list, self.volume_list)
+        self._assert_volumes_in(fetched_list, self.volume_list)
 
     @decorators.idempotent_id('a28e8da4-0b56-472f-87a8-0f4d3f819c02')
     def test_volume_list_by_name(self):
@@ -137,8 +153,8 @@
         fetched_list = self.volumes_client.list_volumes(
             params=params)['volumes']
         self._list_by_param_value_and_assert(params)
-        self.assertVolumesIn(fetched_list, self.volume_list,
-                             fields=self.VOLUME_FIELDS)
+        self._assert_volumes_in(fetched_list, self.volume_list,
+                                fields=self.VOLUME_FIELDS)
 
     @decorators.idempotent_id('2943f712-71ec-482a-bf49-d5ca06216b9f')
     def test_volumes_list_details_by_status(self):
@@ -147,7 +163,7 @@
             detail=True, params=params)['volumes']
         for volume in fetched_list:
             self.assertEqual('available', volume['status'])
-        self.assertVolumesIn(fetched_list, self.volume_list)
+        self._assert_volumes_in(fetched_list, self.volume_list)
 
     @decorators.idempotent_id('2016a942-3020-40d7-95ce-7613bf8407ce')
     def test_volumes_list_by_bootable(self):
@@ -160,8 +176,8 @@
         fetched_list = self.volumes_client.list_volumes(
             params=params)['volumes']
         self._list_by_param_value_and_assert(params)
-        self.assertVolumesIn(fetched_list, self.volume_list,
-                             fields=self.VOLUME_FIELDS)
+        self._assert_volumes_in(fetched_list, self.volume_list,
+                                fields=self.VOLUME_FIELDS)
 
     @decorators.idempotent_id('2016a939-72ec-482a-bf49-d5ca06216b9f')
     def test_volumes_list_details_by_bootable(self):
@@ -170,7 +186,7 @@
             detail=True, params=params)['volumes']
         for volume in fetched_list:
             self.assertEqual('false', volume['bootable'])
-        self.assertVolumesIn(fetched_list, self.volume_list)
+        self._assert_volumes_in(fetched_list, self.volume_list)
 
     @decorators.idempotent_id('c0cfa863-3020-40d7-b587-e35f597d5d87')
     def test_volumes_list_by_availability_zone(self):
@@ -180,8 +196,8 @@
         fetched_list = self.volumes_client.list_volumes(
             params=params)['volumes']
         self._list_by_param_value_and_assert(params)
-        self.assertVolumesIn(fetched_list, self.volume_list,
-                             fields=self.VOLUME_FIELDS)
+        self._assert_volumes_in(fetched_list, self.volume_list,
+                                fields=self.VOLUME_FIELDS)
 
     @decorators.idempotent_id('e1b80d13-94f0-4ba2-a40e-386af29f8db1')
     def test_volumes_list_details_by_availability_zone(self):
@@ -192,7 +208,7 @@
             detail=True, params=params)['volumes']
         for volume in fetched_list:
             self.assertEqual(zone, volume['availability_zone'])
-        self.assertVolumesIn(fetched_list, self.volume_list)
+        self._assert_volumes_in(fetched_list, self.volume_list)
 
     @decorators.idempotent_id('b5ebea1b-0603-40a0-bb41-15fcd0a53214')
     def test_volume_list_with_param_metadata(self):
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index dcd3518..52114bc 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -41,16 +41,19 @@
     def test_snapshot_create_delete_with_volume_in_use(self):
         # Create a test instance
         server = self.create_server()
-        self.attach_volume(server['id'], self.volume_origin['id'])
+        # NOTE(zhufl) Here we create volume from self.image_ref for adding
+        # coverage for "creating snapshot from non-blank volume".
+        volume = self.create_volume(image_ref=self.image_ref)
+        self.attach_volume(server['id'], volume['id'])
 
         # Snapshot a volume which attached to an instance with force=False
         self.assertRaises(lib_exc.BadRequest, self.create_snapshot,
-                          self.volume_origin['id'], force=False)
+                          volume['id'], force=False)
 
         # Snapshot a volume attached to an instance
-        snapshot1 = self.create_snapshot(self.volume_origin['id'], force=True)
-        snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True)
-        snapshot3 = self.create_snapshot(self.volume_origin['id'], force=True)
+        snapshot1 = self.create_snapshot(volume['id'], force=True)
+        snapshot2 = self.create_snapshot(volume['id'], force=True)
+        snapshot3 = self.create_snapshot(volume['id'], force=True)
 
         # Delete the snapshots. Some snapshot implementations can take
         # different paths according to order they are deleted.
diff --git a/tempest/api/volume/test_volumes_snapshots_list.py b/tempest/api/volume/test_volumes_snapshots_list.py
index 507df1f..f12bfd8 100644
--- a/tempest/api/volume/test_volumes_snapshots_list.py
+++ b/tempest/api/volume/test_volumes_snapshots_list.py
@@ -28,13 +28,11 @@
     @classmethod
     def resource_setup(cls):
         super(VolumesSnapshotListTestJSON, cls).resource_setup()
-        cls.snapshot_id_list = []
         volume_origin = cls.create_volume()
 
         # Create snapshots with params
         for _ in range(3):
             snapshot = cls.create_snapshot(volume_origin['id'])
-            cls.snapshot_id_list.append(snapshot['id'])
         cls.snapshot = snapshot
 
     def _list_by_param_values_and_assert(self, with_detail=False, **params):
@@ -151,10 +149,14 @@
     @decorators.idempotent_id('05489dde-44bc-4961-a1f5-3ce7ee7824f7')
     def test_snapshot_list_param_marker(self):
         # The list of snapshots should end before the provided marker
-        params = {'marker': self.snapshot_id_list[1]}
+        snap_list = self.snapshots_client.list_snapshots()['snapshots']
+        # list_snapshots will take the reverse order as they are created.
+        snapshot_id_list = [snap['id'] for snap in snap_list][::-1]
+
+        params = {'marker': snapshot_id_list[1]}
         snap_list = self.snapshots_client.list_snapshots(**params)['snapshots']
         fetched_list_id = [snap['id'] for snap in snap_list]
         # Verify the list of snapshots ends before the provided
         # marker(second snapshot), therefore only the first snapshot
         # should displayed.
-        self.assertEqual(self.snapshot_id_list[:1], fetched_list_id)
+        self.assertEqual(snapshot_id_list[:1], fetched_list_id)
diff --git a/tempest/clients.py b/tempest/clients.py
index ca205c8..d75a712 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -197,6 +197,8 @@
         self.endpoint_groups_client = self.identity_v3.EndPointGroupsClient(
             **params_v3)
         self.catalog_client = self.identity_v3.CatalogClient(**params_v3)
+        self.project_tags_client = self.identity_v3.ProjectTagsClient(
+            **params_v3)
 
         # 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
@@ -234,7 +236,9 @@
             self.volumes_client = self.volume_v1.VolumesClient()
             self.volumes_extension_client = self.volume_v1.ExtensionsClient()
 
-        if CONF.volume_feature_enabled.api_v2:
+        # 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_v2_client = self.volume_v2.BackupsClient()
             self.encryption_types_v2_client = \
                 self.volume_v2.EncryptionTypesClient()
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 92eae02..1c671ec 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -162,9 +162,7 @@
     if CONF.service_available.swift:
         spec.append([CONF.object_storage.operator_role])
         spec.append([CONF.object_storage.reseller_admin_role])
-    if CONF.service_available.heat:
-        spec.append([CONF.orchestration.stack_owner_role,
-                     CONF.object_storage.operator_role])
+        spec.append([CONF.object_storage.operator_role])
     if admin:
         spec.append('admin')
     resources = []
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index d1e80f1..27e1bc1 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -37,7 +37,6 @@
 
 IS_CINDER = None
 IS_GLANCE = None
-IS_HEAT = None
 IS_NEUTRON = None
 IS_NOVA = None
 
@@ -60,7 +59,6 @@
 
     IS_CINDER = CONF.service_available.cinder
     IS_GLANCE = CONF.service_available.glance
-    IS_HEAT = CONF.service_available.heat
     IS_NEUTRON = CONF.service_available.neutron
     IS_NOVA = CONF.service_available.nova
 
@@ -106,11 +104,11 @@
             self.tenant_filter['tenant_id'] = self.tenant_id
 
     def _filter_by_tenant_id(self, item_list):
-        if (item_list is None
-                or not item_list
-                or not hasattr(self, 'tenant_id')
-                or self.tenant_id is None
-                or 'tenant_id' not in item_list[0]):
+        if (item_list is None or
+                not item_list or
+                not hasattr(self, 'tenant_id') or
+                self.tenant_id is None or
+                'tenant_id' not in item_list[0]):
             return item_list
 
         return [item for item in item_list
@@ -212,33 +210,6 @@
         self.data['server_groups'] = sgs
 
 
-class StackService(BaseService):
-    def __init__(self, manager, **kwargs):
-        super(StackService, self).__init__(kwargs)
-        params = config.service_client_config('orchestration')
-        self.client = manager.orchestration.OrchestrationClient(
-            manager.auth_provider, **params)
-
-    def list(self):
-        client = self.client
-        stacks = client.list_stacks()['stacks']
-        LOG.debug("List count, %s Stacks", len(stacks))
-        return stacks
-
-    def delete(self):
-        client = self.client
-        stacks = self.list()
-        for stack in stacks:
-            try:
-                client.delete_stack(stack['id'])
-            except Exception:
-                LOG.exception("Delete Stack exception.")
-
-    def dry_run(self):
-        stacks = self.list()
-        self.data['stacks'] = stacks
-
-
 class KeyPairService(BaseService):
     def __init__(self, manager, **kwargs):
         super(KeyPairService, self).__init__(kwargs)
@@ -845,8 +816,8 @@
             if not self.is_save_state:
                 roles = [role for role in roles if
                          (role['id'] not in
-                          self.saved_state_json['roles'].keys()
-                          and role['name'] != CONF.identity.admin_role)]
+                          self.saved_state_json['roles'].keys() and
+                          role['name'] != CONF.identity.admin_role)]
                 LOG.debug("List count, %s Roles after reconcile", len(roles))
             return roles
         except Exception:
@@ -881,13 +852,16 @@
     def list(self):
         projects = self.client.list_projects()['projects']
         if not self.is_save_state:
-            projects = [project for project in projects if (project['id']
-                        not in self.saved_state_json['projects'].keys()
-                        and project['name'] != CONF.auth.admin_project_name)]
+            project_ids = self.saved_state_json['projects']
+            projects = [project
+                        for project in projects
+                        if (project['id'] not in project_ids and
+                            project['name'] != CONF.auth.admin_project_name)]
 
         if self.is_preserve:
-            projects = [project for project in projects if project['name']
-                        not in CONF_PROJECTS]
+            projects = [project
+                        for project in projects
+                        if project['name'] not in CONF_PROJECTS]
 
         LOG.debug("List count, %s Projects after reconcile", len(projects))
         return projects
@@ -960,8 +934,6 @@
         if not IS_NEUTRON:
             project_services.append(FloatingIpService)
         project_services.append(NovaQuotaService)
-    if IS_HEAT:
-        project_services.append(StackService)
     if IS_NEUTRON:
         project_services.append(NetworkFloatingIpService)
         if utils.is_extension_enabled('metering', 'network'):
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 7634d9e..9a85d89 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -20,19 +20,15 @@
 from oslo_config import generator
 from oslo_log import log as logging
 from six import moves
-from testrepository import commands
+from stestr import commands
 
 from tempest.cmd import workspace
 
 LOG = logging.getLogger(__name__)
 
-TESTR_CONF = """[DEFAULT]
-test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \\
-    OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \\
-    OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \\
-    ${PYTHON:-python} -m subunit.run discover -t %s %s $LISTOPT $IDOPTION
-test_id_option=--load-list $IDFILE
-test_list_option=--list
+STESTR_CONF = """[DEFAULT]
+test_path=%s
+top_dir=%s
 group_regex=([^\.]*\.)*
 """
 
@@ -84,13 +80,13 @@
                                  "is ~/.tempest/workspace.yaml")
         return parser
 
-    def generate_testr_conf(self, local_path):
-        testr_conf_path = os.path.join(local_path, '.testr.conf')
+    def generate_stestr_conf(self, local_path):
+        stestr_conf_path = os.path.join(local_path, '.stestr.conf')
         top_level_path = os.path.dirname(os.path.dirname(__file__))
         discover_path = os.path.join(top_level_path, 'test_discover')
-        testr_conf = TESTR_CONF % (top_level_path, discover_path)
-        with open(testr_conf_path, 'w+') as testr_conf_file:
-            testr_conf_file.write(testr_conf)
+        stestr_conf = STESTR_CONF % (discover_path, top_level_path)
+        with open(stestr_conf_path, 'w+') as stestr_conf_file:
+            stestr_conf_file.write(stestr_conf)
 
     def get_configparser(self, conf_path):
         config_parse = moves.configparser.ConfigParser()
@@ -148,7 +144,7 @@
         etc_dir = os.path.join(local_dir, 'etc')
         config_path = os.path.join(etc_dir, 'tempest.conf')
         log_dir = os.path.join(local_dir, 'logs')
-        testr_dir = os.path.join(local_dir, '.testrepository')
+        stestr_dir = os.path.join(local_dir, '.stestr')
         # Create lock dir
         if not os.path.isdir(lock_dir):
             LOG.debug('Creating lock dir: %s', lock_dir)
@@ -163,12 +159,11 @@
         self.generate_sample_config(local_dir)
         # Update local confs to reflect local paths
         self.update_local_conf(config_path, lock_dir, log_dir)
-        # Generate a testr conf file
-        self.generate_testr_conf(local_dir)
-        # setup local testr working dir
-        if not os.path.isdir(testr_dir):
-            commands.run_argv(['testr', 'init', '-d', local_dir], sys.stdin,
-                              sys.stdout, sys.stderr)
+        # Generate a stestr conf file
+        self.generate_stestr_conf(local_dir)
+        # setup local stestr working dir
+        if not os.path.isdir(stestr_dir):
+            commands.init_command(repo_url=local_dir)
 
     def take_action(self, parsed_args):
         workspace_manager = workspace.WorkspaceManager(
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 0d847bd..72ee715 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -19,9 +19,11 @@
 ==============
 Tempest run has several options:
 
-* ``--regex, -r``: This is a selection regex like what testr uses. It will run
-  any tests that match on re.match() with the regex
-* ``--smoke, -s``: Run all the tests tagged as smoke
+ * **--regex/-r**: This is a selection regex like what stestr uses. It will run
+                   any tests that match on re.match() with the regex
+ * **--smoke/-s**: Run all the tests tagged as smoke
+ * **--black-regex**: It allows to do simple test exclusion via passing a
+                      rejection/black regexp
 
 There are also the ``--blacklist-file`` and ``--whitelist-file`` options that
 let you pass a filepath to tempest run with the file format being a line
@@ -32,17 +34,9 @@
     ^regex1 # Match these tests
     .*regex2 # Match those tests
 
-The blacklist file will be used to construct a negative lookahead regex and
-the whitelist file will simply OR all the regexes in the file. The whitelist
-and blacklist file options are mutually exclusive so you can't use them
-together. However, you can combine either with a normal regex or the *--smoke*
-flag. When used with a blacklist file the generated regex will be combined to
-something like::
-
-    ^((?!black_regex1|black_regex2).)*$cli_regex1
-
-When combined with a whitelist file all the regexes from the file and the CLI
-regexes will be ORed.
+These arguments are just passed into stestr, you can refer to the stestr
+selection docs for more details on how these operate:
+http://stestr.readthedocs.io/en/latest/MANUAL.html#test-selection
 
 You can also use the ``--list-tests`` option in conjunction with selection
 arguments to list which tests will be run.
@@ -74,9 +68,9 @@
 ---------------------
 Tempest run provides you with an option to execute tempest from anywhere on
 your system. You are required to provide a config file in this case with the
-``--config-file`` option. When run tempest will create a .testrepository
-directory and a .testr.conf file in your current working directory. This way
-you can use testr commands directly to inspect the state of the previous run.
+``--config-file`` option. When run tempest will create a .stestr
+directory and a .stestr.conf file in your current working directory. This way
+you can use stestr commands directly to inspect the state of the previous run.
 
 Test Output
 ===========
@@ -94,18 +88,13 @@
 the current run's results with the previous runs.
 """
 
-import io
 import os
 import sys
-import tempfile
-import threading
 
 from cliff import command
-from os_testr import regex_builder
-from os_testr import subunit_trace
 from oslo_serialization import jsonutils as json
 import six
-from testrepository.commands import run_argv
+from stestr import commands
 
 from tempest import clients
 from tempest.cmd import cleanup_service
@@ -124,35 +113,27 @@
     def _set_env(self, config_file=None):
         if config_file:
             CONF.set_config_path(os.path.abspath(config_file))
-        # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
+        # NOTE(mtreinish): This is needed so that stestr doesn't gobble up any
         # stacktraces on failure.
         if 'TESTR_PDB' in os.environ:
             return
         else:
             os.environ["TESTR_PDB"] = ""
-        # NOTE(dims): most of our .testr.conf try to test for PYTHON
+        # NOTE(dims): most of our .stestr.conf try to test for PYTHON
         # environment variable and fall back to "python", under python3
         # if it does not exist. we should set it to the python3 executable
         # to deal with this situation better for now.
         if six.PY3 and 'PYTHON' not in os.environ:
             os.environ['PYTHON'] = sys.executable
 
-    def _create_testrepository(self):
-        if not os.path.isdir('.testrepository'):
-            returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
-                                  sys.stderr)
-            if returncode:
-                sys.exit(returncode)
-
-    def _create_testr_conf(self):
+    def _create_stestr_conf(self):
         top_level_path = os.path.dirname(os.path.dirname(__file__))
         discover_path = os.path.join(top_level_path, 'test_discover')
-        file_contents = init.TESTR_CONF % (top_level_path, discover_path)
-        with open('.testr.conf', 'w+') as testr_conf_file:
-                testr_conf_file.write(file_contents)
+        file_contents = init.STESTR_CONF % (discover_path, top_level_path)
+        with open('.stestr.conf', 'w+') as stestr_conf_file:
+            stestr_conf_file.write(file_contents)
 
     def take_action(self, parsed_args):
-        returncode = 0
         if parsed_args.config_file:
             self._set_env(parsed_args.config_file)
         else:
@@ -169,52 +150,39 @@
                     "register the workspace." %
                     (parsed_args.workspace, workspace_mgr.path))
             os.chdir(path)
-            # NOTE(mtreinish): tempest init should create a .testrepository dir
-            # but since workspaces can be imported let's sanity check and
-            # ensure that one is created
-            self._create_testrepository()
-        # Local execution mode
-        elif os.path.isfile('.testr.conf'):
-            # If you're running in local execution mode and there is not a
-            # testrepository dir create one
-            self._create_testrepository()
+            if not os.path.isfile('.stestr.conf'):
+                self._create_stestr_conf()
         # local execution with config file mode
-        elif parsed_args.config_file:
-            self._create_testr_conf()
-            self._create_testrepository()
-        else:
-            print("No .testr.conf file was found for local execution")
+        elif parsed_args.config_file and not os.path.isfile('.stestr.conf'):
+            self._create_stestr_conf()
+        elif not os.path.isfile('.stestr.conf'):
+            print("No .stestr.conf file was found for local execution")
             sys.exit(2)
         if parsed_args.state:
             self._init_state()
         else:
             pass
 
-        if parsed_args.combine:
-            temp_stream = tempfile.NamedTemporaryFile()
-            return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
-                                   temp_stream, sys.stderr)
-            if return_code > 0:
-                sys.exit(return_code)
-
         regex = self._build_regex(parsed_args)
+        return_code = 0
         if parsed_args.list_tests:
-            argv = ['tempest', 'list-tests', regex]
-            returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
-        else:
-            options = self._build_options(parsed_args)
-            returncode = self._run(regex, options)
-            if returncode > 0:
-                sys.exit(returncode)
+            return_code = commands.list_command(
+                filters=regex, whitelist_file=parsed_args.whitelist_file,
+                blacklist_file=parsed_args.blacklist_file,
+                black_regex=parsed_args.black_regex)
 
-        if parsed_args.combine:
-            return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
-                                   temp_stream, sys.stderr)
+        else:
+            serial = not parsed_args.parallel
+            return_code = commands.run_command(
+                filters=regex, subunit_out=parsed_args.subunit,
+                serial=serial, concurrency=parsed_args.concurrency,
+                blacklist_file=parsed_args.blacklist_file,
+                whitelist_file=parsed_args.whitelist_file,
+                black_regex=parsed_args.black_regex,
+                load_list=parsed_args.load_list, combine=parsed_args.combine)
             if return_code > 0:
                 sys.exit(return_code)
-            returncode = run_argv(['tempest', 'load', temp_stream.name],
-                                  sys.stdin, sys.stdout, sys.stderr)
-        sys.exit(returncode)
+        return return_code
 
     def get_description(self):
         return 'Run tempest'
@@ -262,23 +230,24 @@
         regex.add_argument('--smoke', '-s', action='store_true',
                            help="Run the smoke tests only")
         regex.add_argument('--regex', '-r', default='',
-                           help='A normal testr selection regex used to '
+                           help='A normal stestr selection regex used to '
                                 'specify a subset of tests to run')
-        list_selector = parser.add_mutually_exclusive_group()
-        list_selector.add_argument('--whitelist-file', '--whitelist_file',
-                                   help="Path to a whitelist file, this file "
-                                        "contains a separate regex on each "
-                                        "newline.")
-        list_selector.add_argument('--blacklist-file', '--blacklist_file',
-                                   help='Path to a blacklist file, this file '
-                                        'contains a separate regex exclude on '
-                                        'each newline')
-        list_selector.add_argument('--load-list', '--load_list',
-                                   help='Path to a non-regex whitelist file, '
-                                        'this file contains a seperate test '
-                                        'on each newline. This command'
-                                        'supports files created by the tempest'
-                                        'run ``--list-tests`` command')
+        parser.add_argument('--black-regex', dest='black_regex',
+                            help='A regex to exclude tests that match it')
+        parser.add_argument('--whitelist-file', '--whitelist_file',
+                            help="Path to a whitelist file, this file "
+                            "contains a separate regex on each "
+                            "newline.")
+        parser.add_argument('--blacklist-file', '--blacklist_file',
+                            help='Path to a blacklist file, this file '
+                                 'contains a separate regex exclude on '
+                                 'each newline')
+        parser.add_argument('--load-list', '--load_list',
+                            help='Path to a non-regex whitelist file, '
+                                 'this file contains a seperate test '
+                                 'on each newline. This command'
+                                 'supports files created by the tempest'
+                                 'run ``--list-tests`` command')
         # list only args
         parser.add_argument('--list-tests', '-l', action='store_true',
                             help='List tests',
@@ -305,62 +274,15 @@
         parser.add_argument("--combine", action='store_true',
                             help='Combine the output of this run with the '
                                  "previous run's as a combined stream in the "
-                                 "testr repository after it finish")
+                                 "stestr repository after it finish")
 
         parser.set_defaults(parallel=True)
         return parser
 
     def _build_regex(self, parsed_args):
-        regex = ''
+        regex = None
         if parsed_args.smoke:
-            regex = 'smoke'
+            regex = ['smoke']
         elif parsed_args.regex:
-            regex = parsed_args.regex
-        if parsed_args.whitelist_file or parsed_args.blacklist_file:
-            regex = regex_builder.construct_regex(parsed_args.blacklist_file,
-                                                  parsed_args.whitelist_file,
-                                                  regex, False)
+            regex = parsed_args.regex.split()
         return regex
-
-    def _build_options(self, parsed_args):
-        options = []
-        if parsed_args.subunit:
-            options.append("--subunit")
-        if parsed_args.parallel:
-            options.append("--parallel")
-        if parsed_args.concurrency:
-            options.append("--concurrency=%s" % parsed_args.concurrency)
-        if parsed_args.load_list:
-            options.append("--load-list=%s" % parsed_args.load_list)
-        return options
-
-    def _run(self, regex, options):
-        returncode = 0
-        argv = ['tempest', 'run', regex] + options
-        if '--subunit' in options:
-            returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
-        else:
-            argv.append('--subunit')
-            stdin = io.StringIO()
-            stdout_r, stdout_w = os.pipe()
-            subunit_w = os.fdopen(stdout_w, 'wt')
-            subunit_r = os.fdopen(stdout_r)
-            returncodes = {}
-
-            def run_argv_thread():
-                returncodes['testr'] = run_argv(argv, stdin, subunit_w,
-                                                sys.stderr)
-                subunit_w.close()
-
-            run_thread = threading.Thread(target=run_argv_thread)
-            run_thread.start()
-            returncodes['subunit-trace'] = subunit_trace.trace(
-                subunit_r, sys.stdout, post_fails=True, print_failures=True)
-            run_thread.join()
-            subunit_r.close()
-            # python version of pipefail
-            if returncodes['testr']:
-                returncode = returncodes['testr']
-            elif returncodes['subunit-trace']:
-                returncode = returncodes['subunit-trace']
-        return returncode
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index f0ade7e..a4402fe 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -29,6 +29,9 @@
   written to. This contains more information than is present in stdout.
 * ``--ports, -p``: (Optional) The path to a JSON file describing the ports
   being used by different services
+* ``--verbose, -v``: (Optional) Print Request and Response Headers and Body
+  data to stdout
+
 
 Usage
 -----
@@ -262,6 +265,10 @@
             "-p", "--ports", metavar="<ports file>", default=None,
             help="A JSON file describing the ports for each service.")
 
+        self.add_argument(
+            "-v", "--verbose", action='store_true', default=False,
+            help="Add Request and Response header and body data to stdout.")
+
 
 def parse(stream, non_subunit_name, ports):
     if ports is not None and os.path.exists(ports):
@@ -286,7 +293,7 @@
     return url_parser
 
 
-def output(url_parser, output_file):
+def output(url_parser, output_file, verbose):
     if output_file is not None:
         with open(output_file, "w") as outfile:
             outfile.write(json.dumps(url_parser.test_logs))
@@ -302,13 +309,22 @@
             sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format(
                 item.get('status_code'), item.get('verb'),
                 item.get('service'), item.get('url')))
+            if verbose:
+                sys.stdout.write('\t\t- request headers: {0}\n'.format(
+                    item.get('request_headers')))
+                sys.stdout.write('\t\t- request body: {0}\n'.format(
+                    item.get('request_body')))
+                sys.stdout.write('\t\t- response headers: {0}\n'.format(
+                    item.get('response_headers')))
+                sys.stdout.write('\t\t- response body: {0}\n'.format(
+                    item.get('response_body')))
         sys.stdout.write('\n')
 
 
 def entry_point():
     cl_args = ArgumentParser().parse_args()
     parser = parse(cl_args.subunit, cl_args.non_subunit_name, cl_args.ports)
-    output(parser, cl_args.output_file)
+    output(parser, cl_args.output_file, cl_args.verbose)
 
 
 if __name__ == "__main__":
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index fdf28d5..15af271 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -349,7 +349,6 @@
         'image': 'glance',
         'object_storage': 'swift',
         'compute': 'nova',
-        'orchestration': 'heat',
         'baremetal': 'ironic',
         'identity': 'keystone',
     }
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index f0fceb3..68c4a10 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -229,7 +229,7 @@
                     clients.servers_client, server['id'], wait_until)
 
                 # Multiple validatable servers are not supported for now. Their
-                # creation will fail with the condition above (l.58).
+                # creation will fail with the condition above.
                 if CONF.validation.run_validation and validatable:
                     if CONF.validation.connect_method == 'floating':
                         _setup_validation_fip()
@@ -289,7 +289,12 @@
 
 def create_websocket(url):
     url = urlparse.urlparse(url)
-    for res in socket.getaddrinfo(url.hostname, url.port,
+
+    # NOTE(mnaser): It is possible that there is no port specified, so fall
+    #               back to the default port based on the scheme.
+    port = url.port or (443 if url.scheme == 'https' else 80)
+
+    for res in socket.getaddrinfo(url.hostname, port,
                                   socket.AF_UNSPEC, socket.SOCK_STREAM):
         af, socktype, proto, _, sa = res
         client_socket = socket.socket(af, socktype, proto)
@@ -382,7 +387,12 @@
         """Upgrade the HTTP connection to a WebSocket and verify."""
         # The real request goes to the /websockify URI always
         reqdata = 'GET /websockify HTTP/1.1\r\n'
-        reqdata += 'Host: %s:%s\r\n' % (url.hostname, url.port)
+        reqdata += 'Host: %s' % url.hostname
+        # Add port only if we have one specified
+        if url.port:
+            reqdata += ':%s' % url.port
+        # Line-ending for Host header
+        reqdata += '\r\n'
         # Tell the HTTP Server to Upgrade the connection to a WebSocket
         reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
         # The token=xxx is sent as a Cookie not in the URI
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index da34975..75db155 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -86,7 +86,7 @@
         ('public_network_id', CONF.network.public_network_id),
         ('create_networks', (CONF.auth.create_isolated_networks and not
                              CONF.network.shared_physical_network)),
-        ('resource_prefix', CONF.resources_prefix),
+        ('resource_prefix', 'tempest'),
         ('identity_admin_endpoint_type', endpoint_type)
     ]))
 
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index aa81864..225a713 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -31,10 +31,9 @@
 
         if attr == 'rand_name':
             # NOTE(flwang): This is a proxy to generate a random name that
-            # includes a random number and a prefix if one is configured in
-            # CONF.resources_prefix
+            # includes a random number and a prefix 'tempest'
             attr_obj = partial(lib_data_utils.rand_name,
-                               prefix=CONF.resources_prefix)
+                               prefix='tempest')
         else:
             attr_obj = getattr(lib_data_utils, attr)
 
diff --git a/tempest/config.py b/tempest/config.py
index 19ac2fc..a2ccb84 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -65,9 +65,7 @@
                 deprecated_opts=[cfg.DeprecatedOpt('allow_tenant_isolation',
                                                    group='auth'),
                                  cfg.DeprecatedOpt('allow_tenant_isolation',
-                                                   group='compute'),
-                                 cfg.DeprecatedOpt('allow_tenant_isolation',
-                                                   group='orchestration')]),
+                                                   group='compute')]),
     cfg.ListOpt('tempest_roles',
                 help="Roles to assign to all users created by tempest",
                 default=[]),
@@ -106,6 +104,7 @@
                secret=True,
                deprecated_group='identity'),
     cfg.StrOpt('admin_domain_name',
+               default='Default',
                help="Admin domain name for authentication (Keystone V3)."
                     "The same domain applies to user and project",
                deprecated_group='identity'),
@@ -238,7 +237,10 @@
     cfg.BoolOpt('security_compliance',
                 default=False,
                 help='Does the environment have the security compliance '
-                     'settings enabled?')
+                     'settings enabled?'),
+    cfg.BoolOpt('project_tags',
+                default=False,
+                help='Is the project tags identity v3 API available?')
 ]
 
 compute_group = cfg.OptGroup(name='compute',
@@ -405,6 +407,10 @@
                 default=False,
                 help='Enable VNC console. This configuration value should '
                      'be same as [nova.vnc]->vnc_enabled in nova.conf'),
+    cfg.StrOpt('vnc_server_header',
+               default='WebSockify',
+               help='Expected VNC server name (WebSockify, nginx, etc) '
+                    'in response header.'),
     cfg.BoolOpt('spice_console',
                 default=False,
                 help='Enable Spice console. This configuration value should '
@@ -473,6 +479,15 @@
                 default=False,
                 help='Does the test environment support in-place swapping of '
                      'volumes attached to a server instance?'),
+    cfg.BoolOpt('volume_backed_live_migration',
+                default=False,
+                help='Does the test environment support volume-backed live '
+                     'migration?'),
+    cfg.BoolOpt('volume_multiattach',
+                default=False,
+                help='Does the test environment support attaching a volume to '
+                     'more than one instance? This depends on hypervisor and '
+                     'volume backend/type and compute API version 2.60.'),
 ]
 
 
@@ -537,13 +552,6 @@
                                   'are current one. In future, Tempest will '
                                   'test v2 APIs only so this config option '
                                   'will be removed.'),
-    cfg.BoolOpt('deactivate_image',
-                default=False,
-                help="Is the deactivate-image feature enabled."
-                     " The feature has been integrated since Kilo.",
-                deprecated_for_removal=True,
-                deprecated_reason="All supported versions of OpenStack now "
-                                  "support the 'deactivate_image' feature"),
 ]
 
 network_group = cfg.OptGroup(name='network',
@@ -608,10 +616,14 @@
                      " for subnet creation"),
     cfg.StrOpt('port_vnic_type',
                choices=[None, 'normal', 'direct', 'macvtap'],
-               help="vnic_type to use when Launching instances"
+               help="vnic_type to use when launching instances"
                     " with pre-configured ports."
                     " Supported ports are:"
                     " ['normal','direct','macvtap']"),
+    cfg.DictOpt('port_profile',
+                default={},
+                help="port profile to use when launching instances"
+                     " with pre-configured ports."),
     cfg.ListOpt('default_network',
                 default=["1.0.0.0/16", "2.0.0.0/16"],
                 help="List of ip pools"
@@ -912,66 +924,6 @@
                 help="Execute discoverability tests"),
 ]
 
-orchestration_group = cfg.OptGroup(name='orchestration',
-                                   title='Orchestration Service Options')
-
-OrchestrationGroup = [
-    cfg.StrOpt('catalog_type',
-               default='orchestration',
-               help="Catalog type of the Orchestration service.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('region',
-               default='',
-               help="The orchestration region name to use. If empty, the "
-                    "value of identity.region is used instead. If no such "
-                    "region is found in the service catalog, the first found "
-                    "one is used.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('endpoint_type',
-               default='publicURL',
-               choices=['public', 'admin', 'internal',
-                        'publicURL', 'adminURL', 'internalURL'],
-               help="The endpoint type to use for the orchestration service.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('stack_owner_role', default='heat_stack_owner',
-               help='Role required for users to be able to manage stacks',
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.IntOpt('build_interval',
-               default=1,
-               help="Time in seconds between build status checks.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.IntOpt('build_timeout',
-               default=1200,
-               help="Timeout in seconds to wait for a stack to build.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('instance_type',
-               default='m1.micro',
-               help="Instance type for tests. Needs to be big enough for a "
-                    "full OS plus the test workload",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.StrOpt('keypair_name',
-               help="Name of existing keypair to launch servers with.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.IntOpt('max_template_size',
-               default=524288,
-               help="Value must match heat configuration of the same name.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-    cfg.IntOpt('max_resources_per_stack',
-               default=1000,
-               help="Value must match heat configuration of the same name.",
-               deprecated_for_removal=True,
-               deprecated_reason='Heat support will be removed from Tempest'),
-]
-
 
 scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options')
 
@@ -1033,11 +985,6 @@
     cfg.BoolOpt('nova',
                 default=True,
                 help="Whether or not nova is expected to be available"),
-    cfg.BoolOpt('heat',
-                default=False,
-                help="Whether or not Heat is expected to be available",
-                deprecated_for_removal=True,
-                deprecated_reason='Heat support will be removed from Tempest'),
 ]
 
 debug_group = cfg.OptGroup(name="debug",
@@ -1067,17 +1014,6 @@
 ]
 
 DefaultGroup = [
-    cfg.StrOpt('resources_prefix',
-               default='tempest',
-               help="Prefix to be added when generating the name for "
-                    "test resources. It can be used to discover all "
-                    "resources associated with a specific test run when "
-                    "running tempest on a real-life cloud",
-               deprecated_for_removal=True,
-               deprecated_reason="It is enough to add 'tempest' as this "
-                                 "prefix to ideintify resources which are "
-                                 "created by Tempest and no projects set "
-                                 "this option on OpenStack dev community."),
     cfg.BoolOpt('pause_teardown',
                 default=False,
                 help="""Whether to pause a test in global teardown.
@@ -1105,7 +1041,6 @@
     (volume_feature_group, VolumeFeaturesGroup),
     (object_storage_group, ObjectStoreGroup),
     (object_storage_feature_group, ObjectStoreFeaturesGroup),
-    (orchestration_group, OrchestrationGroup),
     (scenario_group, ScenarioGroup),
     (service_available_group, ServiceAvailableGroup),
     (debug_group, DebugGroup),
@@ -1172,7 +1107,6 @@
         self.object_storage = _CONF['object-storage']
         self.object_storage_feature_enabled = _CONF[
             'object-storage-feature-enabled']
-        self.orchestration = _CONF.orchestration
         self.scenario = _CONF.scenario
         self.service_available = _CONF.service_available
         self.debug = _CONF.debug
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index aae685c..b6e7f8c 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -228,12 +228,12 @@
     if 'tempest/lib/' not in filename:
         return
 
-    if not ('from tempest' in logical_line
-            or 'import tempest' in logical_line):
+    if not ('from tempest' in logical_line or
+            'import tempest' in logical_line):
         return
 
-    if ('from tempest.lib' in logical_line
-        or 'import tempest.lib' in logical_line):
+    if ('from tempest.lib' in logical_line or
+            'import tempest.lib' in logical_line):
         return
 
     msg = ("T112: tempest.lib should not import local tempest code to avoid "
@@ -266,9 +266,9 @@
     if 'tempest/lib/' not in filename:
         return
 
-    if ('tempest.config' in logical_line
-        or 'from tempest import config' in logical_line
-        or 'oslo_config' in logical_line):
+    if ('tempest.config' in logical_line or
+            'from tempest import config' in logical_line or
+            'oslo_config' in logical_line):
         msg = ('T114: tempest.lib can not have any dependency on tempest '
                'config.')
         yield(0, msg)
diff --git a/tempest/lib/api_schema/response/compute/v2_45/__init__.py b/tempest/lib/api_schema/response/compute/v2_45/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_45/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_45/images.py b/tempest/lib/api_schema/response/compute/v2_45/images.py
new file mode 100644
index 0000000..8a48f36
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_45/images.py
@@ -0,0 +1,32 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# The 2.45 microversion removes the "location" header and adds "image_id"
+# to the response body.
+create_image = {
+    'status_code': [202],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'image_id': {'type': 'string'}
+        },
+        'additionalProperties': False,
+        'required': ['image_id']
+    }
+}
+
+# NOTE(mriedem): The compute proxy APIs for showing/listing and deleting
+# images were deprecated in microversion 2.35, and the compute proxy APIs for
+# working with image metadata were deprecated in microversion 2.39. Therefore,
+# client-side code shouldn't rely on those APIs in the compute images client
+# past those microversions and should instead use the Glance images client
+# directly.
diff --git a/tempest/lib/base.py b/tempest/lib/base.py
index 33a32ee..3be55c0 100644
--- a/tempest/lib/base.py
+++ b/tempest/lib/base.py
@@ -43,8 +43,7 @@
         super(BaseTestCase, self).setUp()
         if not self.setUpClassCalled:
             raise RuntimeError("setUpClass does not calls the super's "
-                               "setUpClass in the "
-                               + self.__class__.__name__)
+                               "setUpClass in {!r}".format(type(self)))
         test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
         try:
             test_timeout = int(test_timeout)
@@ -62,7 +61,7 @@
             stderr = self.useFixture(fixtures.StringStream('stderr')).stream
             self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
         if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
-            os.environ.get('OS_LOG_CAPTURE') != '0'):
+                os.environ.get('OS_LOG_CAPTURE') != '0'):
             self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
                                                    format=self.log_format,
                                                    level=None))
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
index f39ecbc..3fb56ec 100644
--- a/tempest/lib/cli/base.py
+++ b/tempest/lib/cli/base.py
@@ -58,8 +58,6 @@
     if six.PY2:
         cmd = cmd.encode('utf-8')
     cmd = shlex.split(cmd)
-    result = ''
-    result_err = ''
     stdout = subprocess.PIPE
     stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
     proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
@@ -101,12 +99,15 @@
     :type project_domain_name: string
     :param project_domain_id: Project's domain ID
     :type project_domain_id: string
+    :param identity_api_version: Version of the Identity API
+    :type identity_api_version: string
     """
 
     def __init__(self, username='', password='', tenant_name='', uri='',
                  cli_dir='', insecure=False, prefix='', user_domain_name=None,
                  user_domain_id=None, project_domain_name=None,
-                 project_domain_id=None, *args, **kwargs):
+                 project_domain_id=None, identity_api_version=None, *args,
+                 **kwargs):
         """Initialize a new CLIClient object."""
         super(CLIClient, self).__init__()
         self.cli_dir = cli_dir if cli_dir else '/usr/bin'
@@ -120,6 +121,7 @@
         self.user_domain_id = user_domain_id
         self.project_domain_name = project_domain_name
         self.project_domain_id = project_domain_id
+        self.identity_api_version = identity_api_version
 
     def nova(self, action, flags='', params='', fail_ok=False,
              endpoint_type='publicURL', merge_stderr=False):
@@ -374,12 +376,15 @@
         :param merge_stderr:  if True the stderr buffer is merged into stdout
         :type merge_stderr: boolean
         """
-        creds = ('--os-username %s --os-tenant-name %s --os-password %s '
+        creds = ('--os-username %s --os-project-name %s --os-password %s '
                  '--os-auth-url %s' %
                  (self.username,
                   self.tenant_name,
                   self.password,
                   self.uri))
+        if self.identity_api_version:
+            creds += ' --os-identity-api-version %s' % (
+                self.identity_api_version)
         if self.user_domain_name is not None:
             creds += ' --os-user-domain-name %s' % self.user_domain_name
         if self.user_domain_id is not None:
diff --git a/tempest/lib/cli/output_parser.py b/tempest/lib/cli/output_parser.py
index 2edd5c1..a7d5e49 100644
--- a/tempest/lib/cli/output_parser.py
+++ b/tempest/lib/cli/output_parser.py
@@ -37,8 +37,8 @@
     items = []
     tables_ = tables(output_lines)
     for table_ in tables_:
-        if ('Property' not in table_['headers']
-            or 'Value' not in table_['headers']):
+        if ('Property' not in table_['headers'] or
+                'Value' not in table_['headers']):
             raise exceptions.InvalidStructure()
         item = {}
         for value in table_['values']:
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index 101d692..d1f0888 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -103,7 +103,7 @@
     def _modules_search(self):
         """Recursive search for python modules in base package"""
         modules = []
-        for root, dirs, files in os.walk(self.base_path):
+        for root, _, files in os.walk(self.base_path):
             if not os.path.exists(os.path.join(root, '__init__.py')):
                 continue
             root_package = self._path_to_package(root)
@@ -121,10 +121,10 @@
         idempotent_id = None
         for decorator in test_node.decorator_list:
             if (hasattr(decorator, 'func') and
-                hasattr(decorator.func, 'attr') and
-                decorator.func.attr == DECORATOR_NAME and
-                hasattr(decorator.func, 'value') and
-                decorator.func.value.id == DECORATOR_MODULE):
+                    hasattr(decorator.func, 'attr') and
+                    decorator.func.attr == DECORATOR_NAME and
+                    hasattr(decorator.func, 'value') and
+                    decorator.func.value.id == DECORATOR_MODULE):
                 for arg in decorator.args:
                     idempotent_id = ast.literal_eval(arg)
         return idempotent_id
@@ -165,8 +165,8 @@
 
     @staticmethod
     def _is_test_method(node):
-        return (node.__class__ is ast.FunctionDef
-                and node.name.startswith('test_'))
+        return (node.__class__ is ast.FunctionDef and
+                node.name.startswith('test_'))
 
     @staticmethod
     def _next_node(body, node):
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index 4f1a883..f27e926 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -338,15 +338,15 @@
                 credentials = self._create_creds(roles=credential_type)
             self._creds[str(credential_type)] = credentials
             # Maintained until tests are ported
-            LOG.info("Acquired dynamic creds:\n credentials: %s", credentials)
-            if (self.neutron_available and
-                self.create_networks):
+            LOG.info("Acquired dynamic creds:\n"
+                     " credentials: %s", credentials)
+            if (self.neutron_available and self.create_networks):
                 network, subnet, router = self._create_network_resources(
                     credentials.tenant_id)
                 credentials.set_resources(network=network, subnet=subnet,
                                           router=router)
-                LOG.info("Created isolated network resources for : \n"
-                         + " credentials: %s", credentials)
+                LOG.info("Created isolated network resources for:\n"
+                         " credentials: %s", credentials)
         return credentials
 
     def get_primary_creds(self):
diff --git a/tempest/lib/common/fixed_network.py b/tempest/lib/common/fixed_network.py
index e2054a4..875a79d 100644
--- a/tempest/lib/common/fixed_network.py
+++ b/tempest/lib/common/fixed_network.py
@@ -38,7 +38,12 @@
         raise exceptions.InvalidTestResource(type='network', name=name)
 
     networks = compute_networks_client.list_networks()['networks']
-    networks = [n for n in networks if n['label'] == name]
+    # NOTE(zhufl) compute networks_client uses 'label' as network name field,
+    # while neutron networks_client uses 'name' as network name field.
+    try:
+        networks = [n for n in networks if n['label'] == name]
+    except KeyError:
+        networks = [n for n in networks if n['name'] == name]
 
     # Check that a network exists, else raise an InvalidConfigurationException
     if len(networks) == 1:
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index 83db513..fcdeb17 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -344,11 +344,11 @@
         net_creds = cred_provider.TestResources(credential)
         net_clients = clients.ServiceClients(credentials=credential,
                                              identity_uri=self.identity_uri)
-        compute_network_client = net_clients.compute.NetworksClient()
+        networks_client = net_clients.network.NetworksClient()
         net_name = self.hash_dict['networks'].get(hash, None)
         try:
             network = fixed_network.get_network_from_name(
-                net_name, compute_network_client)
+                net_name, networks_client)
         except lib_exc.InvalidTestResource:
             network = {}
         net_creds.set_resources(network=network)
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index cd4092b..8ac1d38 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -10,6 +10,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import functools
+import re
 import sys
 
 import netaddr
@@ -25,13 +27,14 @@
 
 def debug_ssh(function):
     """Decorator to generate extra debug info in case off SSH failure"""
+    @functools.wraps(function)
     def wrapper(self, *args, **kwargs):
         try:
             return function(self, *args, **kwargs)
         except Exception as e:
             caller = test_utils.find_test_caller() or "not found"
             if not isinstance(e, tempest.lib.exceptions.SSHTimeout):
-                message = ('Initializing SSH connection to %(ip)s failed. '
+                message = ('Executing command on %(ip)s failed. '
                            'Error: %(error)s' % {'ip': self.ip_address,
                                                  'error': e})
                 message = '(%s) %s' % (caller, message)
@@ -124,3 +127,27 @@
             cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
         cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
         return self.exec_command(cmd)
+
+    def mount_config_drive(self):
+        """Mount the config drive inside a virtual machine
+
+        This method will not unmount the config drive, so unmount_config_drive
+        must be used for cleanup.
+        """
+        cmd_blkid = 'blkid | grep -i config-2'
+        result = self.exec_command(cmd_blkid)
+        dev_name = re.match('([^:]+)', result).group()
+
+        try:
+            self.exec_command('sudo mount %s /mnt' % dev_name)
+        except tempest.lib.exceptions.SSHExecCommandFailed:
+            # So the command failed, let's try to know why and print some
+            # useful information.
+            lsblk = self.exec_command('sudo lsblk --fs --ascii')
+            LOG.error("Mounting %s on /mnt failed. Right after the "
+                      "failure 'lsblk' in the guest reported:\n%s",
+                      dev_name, lsblk)
+            raise
+
+    def unmount_config_drive(self):
+        self.exec_command('sudo umount /mnt')
diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py
index c2e93ee..2a9f3a9 100644
--- a/tempest/lib/common/utils/test_utils.py
+++ b/tempest/lib/common/utils/test_utils.py
@@ -102,13 +102,13 @@
     now = time.time()
     begin_time = now
     timeout = now + duration
+    func_name = getattr(func, '__name__', getattr(func.__class__, '__name__'))
     while now < timeout:
         if func(*args, **kwargs):
             LOG.debug("Call %s returns true in %f seconds",
-                      getattr(func, '__name__'), time.time() - begin_time)
+                      func_name, time.time() - begin_time)
             return True
         time.sleep(sleep_for)
         now = time.time()
-    LOG.debug("Call %s returns false in %f seconds",
-              getattr(func, '__name__'), duration)
+    LOG.debug("Call %s returns false in %f seconds", func_name, duration)
     return False
diff --git a/tempest/lib/services/compute/images_client.py b/tempest/lib/services/compute/images_client.py
index 86bea9e..0f4eb42 100644
--- a/tempest/lib/services/compute/images_client.py
+++ b/tempest/lib/services/compute/images_client.py
@@ -17,6 +17,7 @@
 from six.moves.urllib import parse as urllib
 
 from tempest.lib.api_schema.response.compute.v2_1 import images as schema
+from tempest.lib.api_schema.response.compute.v2_45 import images as schemav245
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services.compute import base_compute_client
@@ -24,6 +25,10 @@
 
 class ImagesClient(base_compute_client.BaseComputeClient):
 
+    schema_versions_info = [
+        {'min': None, 'max': '2.44', 'schema': schema},
+        {'min': '2.45', 'max': None, 'schema': schemav245}]
+
     def create_image(self, server_id, **kwargs):
         """Create an image of the original server.
 
@@ -36,7 +41,10 @@
         post_body = json.dumps(post_body)
         resp, body = self.post('servers/%s/action' % server_id,
                                post_body)
-        self.validate_response(schema.create_image, resp, body)
+        _schema = self.get_schema(self.schema_versions_info)
+        if body:
+            body = json.loads(body)
+        self.validate_response(_schema.create_image, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def list_images(self, detail=False, **params):
diff --git a/tempest/lib/services/compute/quota_classes_client.py b/tempest/lib/services/compute/quota_classes_client.py
index 0fe9868..64e06f4 100644
--- a/tempest/lib/services/compute/quota_classes_client.py
+++ b/tempest/lib/services/compute/quota_classes_client.py
@@ -35,8 +35,9 @@
     def update_quota_class_set(self, quota_class_id, **kwargs):
         """Update the quota class's limits for one or more resources.
 
-        # NOTE: Current api-site doesn't contain this API description.
-        # LP: https://bugs.launchpad.net/nova/+bug/1602400
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#create-or-update-quotas-for-quota-class
         """
         post_body = json.dumps({'quota_class_set': kwargs})
 
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
index daf4bc0..12df895 100644
--- a/tempest/lib/services/compute/quotas_client.py
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -28,8 +28,8 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref-compute-v2.1.html/#show-a-quota
-        http://developer.openstack.org/api-ref-compute-v2.1.html/#show-the-detail-of-quota
+        https://developer.openstack.org/api-ref/compute/#show-a-quota
+        https://developer.openstack.org/api-ref/compute/#show-the-detail-of-quota
         """
 
         params = {}
@@ -49,7 +49,10 @@
         return rest_client.ResponseBody(resp, body)
 
     def show_default_quota_set(self, tenant_id):
-        """List the default quota set for a tenant."""
+        """List the default quota set for a tenant.
+
+        https://developer.openstack.org/api-ref/compute/#list-default-quotas-for-tenant
+        """
 
         url = 'os-quota-sets/%s/defaults' % tenant_id
         resp, body = self.get(url)
@@ -79,7 +82,10 @@
         return rest_client.ResponseBody(resp, body)
 
     def delete_quota_set(self, tenant_id):
-        """Delete the tenant's quota set."""
+        """Delete the tenant's quota set.
+
+        https://developer.openstack.org/api-ref/compute/#revert-quotas-to-defaults
+        """
         resp, body = self.delete('os-quota-sets/%s' % tenant_id)
         self.validate_response(schema.delete_quota, resp, body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 598d5a6..09bccab 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -126,7 +126,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref-compute-v2.1.html#showServer
+        https://developer.openstack.org/api-ref/compute/#show-server-details
         """
         resp, body = self.get("servers/%s" % server_id)
         body = json.loads(body)
@@ -321,7 +321,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#create-or-replace-metadata-items
+        https://developer.openstack.org/api-ref/compute/#replace-metadata-items
         """
         if no_metadata_field:
             post_body = ""
@@ -338,7 +338,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#update-metadata-items
+        https://developer.openstack.org/api-ref/compute/#create-or-update-metadata-items
         """
         post_body = json.dumps({'metadata': meta})
         resp, body = self.post('servers/%s/metadata' % server_id,
@@ -609,9 +609,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        TODO (markus_z) The api-ref for that isn't yet available, update this
-        here when the docs in Nova are updated. The old API is at
-        http://developer.openstack.org/api-ref/compute/#get-serial-console-os-getserialconsole-action
+        https://developer.openstack.org/api-ref/compute/#create-remote-console
         """
         param = {
             'remote_console': {
@@ -722,7 +720,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#get-vnc-console-os-getvncconsole-action
+        https://developer.openstack.org/api-ref/compute/#get-vnc-console-os-getvncconsole-action-deprecated
         """
         return self.action(server_id, "os-getVNCConsole",
                            schema.get_vnc_console, **kwargs)
@@ -732,7 +730,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#add-associate-fixed-ip-addfixedip-action
+        https://developer.openstack.org/api-ref/compute/#add-associate-fixed-ip-addfixedip-action-deprecated
         """
         return self.action(server_id, 'addFixedIp', **kwargs)
 
@@ -741,7 +739,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/compute/#remove-disassociate-fixed-ip-removefixedip-action
+        https://developer.openstack.org/api-ref/compute/#remove-disassociate-fixed-ip-removefixedip-action-deprecated
         """
         return self.action(server_id, 'removeFixedIp', **kwargs)
 
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index a539d08..f302455 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -33,6 +33,8 @@
 from tempest.lib.services.identity.v3.oauth_token_client import \
     OAUTHTokenClient
 from tempest.lib.services.identity.v3.policies_client import PoliciesClient
+from tempest.lib.services.identity.v3.project_tags_client import \
+    ProjectTagsClient
 from tempest.lib.services.identity.v3.projects_client import ProjectsClient
 from tempest.lib.services.identity.v3.regions_client import RegionsClient
 from tempest.lib.services.identity.v3.role_assignments_client import \
@@ -49,6 +51,6 @@
            'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient',
            'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient',
            'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient',
-           'RegionsClient', 'RoleAssignmentsClient', 'RolesClient',
-           'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient',
-           'VersionsClient']
+           'ProjectTagsClient', 'RegionsClient', 'RoleAssignmentsClient',
+           'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient',
+           'UsersClient', 'VersionsClient']
diff --git a/tempest/lib/services/identity/v3/policies_client.py b/tempest/lib/services/identity/v3/policies_client.py
index d4560e2..ca8dbbd 100644
--- a/tempest/lib/services/identity/v3/policies_client.py
+++ b/tempest/lib/services/identity/v3/policies_client.py
@@ -73,3 +73,115 @@
         resp, body = self.delete(url)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def update_policy_association_for_endpoint(self, policy_id, endpoint_id):
+        """Create policy association with endpoint.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-endpoint
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\
+              .format(policy_id, endpoint_id)
+        resp, body = self.put(url, '{}')
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_policy_association_for_endpoint(self, policy_id, endpoint_id):
+        """Get policy association of endpoint.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-endpoint-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\
+              .format(policy_id, endpoint_id)
+        resp, body = self.get(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_policy_association_for_endpoint(self, policy_id, endpoint_id):
+        """Delete policy association with endpoint.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-endpoint-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\
+              .format(policy_id, endpoint_id)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_policy_association_for_service(self, policy_id, service_id):
+        """Create policy association with service.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-service-type-endpoint
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\
+              .format(policy_id, service_id)
+        resp, body = self.put(url, '{}')
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_policy_association_for_service(self, policy_id, service_id):
+        """Get policy association of service.
+
+        API Reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-service-type-endpoint-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\
+              .format(policy_id, service_id)
+        resp, body = self.get(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_policy_association_for_service(self, policy_id, service_id):
+        """Delete policy association with service.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-service-type-endpoint-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\
+              .format(policy_id, service_id)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_policy_association_for_region_and_service(
+            self, policy_id, service_id, region_id):
+        """Create policy association with service and region.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-service-type-endpoint-in-a-region
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\
+              .format(policy_id, service_id, region_id)
+        resp, body = self.put(url, '{}')
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_policy_association_for_region_and_service(
+        self, policy_id, service_id, region_id):
+        """Get policy association of service and region.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-service-type-endpoint-in-a-region-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\
+              .format(policy_id, service_id, region_id)
+        resp, body = self.get(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_policy_association_for_region_and_service(
+        self, policy_id, service_id, region_id):
+        """Delete policy association with service and region.
+
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-service-type-endpoint-in-a-region-association
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\
+              .format(policy_id, service_id, region_id)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/project_tags_client.py b/tempest/lib/services/identity/v3/project_tags_client.py
new file mode 100644
index 0000000..dd1a2a5
--- /dev/null
+++ b/tempest/lib/services/identity/v3/project_tags_client.py
@@ -0,0 +1,80 @@
+# Copyright 2018 AT&T Corporation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ProjectTagsClient(rest_client.RestClient):
+    api_version = "v3"
+
+    def update_project_tag(self, project_id, tag):
+        """Updates the specified tag and adds it to the project's list of tags.
+
+        """
+        url = 'projects/%s/tags/%s' % (project_id, tag)
+        resp, body = self.put(url, '{}')
+        # NOTE(felipemonteiro): This API endpoint returns 201 AND an empty
+        # response body, which is consistent with the spec:
+        # https://specs.openstack.org/openstack/api-wg/guidelines/tags.html#addressing-individual-tags
+        self.expected_success(201, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_project_tags(self, project_id):
+        """List tags for a project."""
+        url = "projects/%s/tags" % project_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_all_project_tags(self, project_id, tags, **kwargs):
+        """Updates all the tags for a project.
+
+        Any existing tags not specified will be deleted.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/identity/v3/#modify-tag-list-for-a-project
+        """
+        body = {'tags': tags}
+        if kwargs:
+            body.update(kwargs)
+        put_body = json.dumps(body)
+        resp, body = self.put('projects/%s/tags' % project_id, put_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def check_project_tag_existence(self, project_id, tag):
+        """Check if a project contains a tag."""
+        url = 'projects/%s/tags/%s' % (project_id, tag)
+        resp, body = self.get(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_project_tag(self, project_id, tag):
+        """Delete a project tag."""
+        url = 'projects/%s/tags/%s' % (project_id, tag)
+        resp, body = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_all_project_tags(self, project_id):
+        """Delete all tags from a project."""
+        resp, body = self.delete('projects/%s/tags' % project_id)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index bcdae44..ed6df47 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -161,7 +161,7 @@
         """
         url = 'images/%s/file' % image_id
         resp, body = self.get(url)
-        self.expected_success(200, resp.status)
+        self.expected_success([200, 204, 206], resp.status)
         return rest_client.ResponseBodyData(resp, body)
 
     def add_image_tag(self, image_id, tag):
diff --git a/tempest/lib/services/network/metering_label_rules_client.py b/tempest/lib/services/network/metering_label_rules_client.py
index 36cf8e3..9542e8f 100644
--- a/tempest/lib/services/network/metering_label_rules_client.py
+++ b/tempest/lib/services/network/metering_label_rules_client.py
@@ -16,6 +16,12 @@
 class MeteringLabelRulesClient(base.BaseNetworkClient):
 
     def create_metering_label_rule(self, **kwargs):
+        """Create metering label rule.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#create-metering-label-rule
+        """
         uri = '/metering/metering-label-rules'
         post_data = {'metering_label_rule': kwargs}
         return self.create_resource(uri, post_data)
@@ -29,5 +35,11 @@
         return self.delete_resource(uri)
 
     def list_metering_label_rules(self, **filters):
+        """List metering label rules.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#list-metering-label-rules
+        """
         uri = '/metering/metering-label-rules'
         return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
index 752b253..e9666de 100644
--- a/tempest/lib/services/network/quotas_client.py
+++ b/tempest/lib/services/network/quotas_client.py
@@ -18,6 +18,12 @@
 class QuotasClient(base.BaseNetworkClient):
 
     def update_quotas(self, tenant_id, **kwargs):
+        """Update quota for a project.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#update-quota-for-a-project
+        """
         put_body = {'quota': kwargs}
         uri = '/quotas/%s' % tenant_id
         return self.update_resource(uri, put_body)
@@ -35,3 +41,13 @@
     def list_quotas(self, **filters):
         uri = '/quotas'
         return self.list_resources(uri, **filters)
+
+    def show_default_quotas(self, tenant_id):
+        """List default quotas for a project."""
+        uri = '/quotas/%s/default' % tenant_id
+        return self.show_resource(uri)
+
+    def show_quota_details(self, tenant_id):
+        """Show quota details for a project."""
+        uri = '/quotas/%s/details.json' % tenant_id
+        return self.show_resource(uri)
diff --git a/tempest/lib/services/network/service_providers_client.py b/tempest/lib/services/network/service_providers_client.py
index 0ee9bc3..01313a0 100644
--- a/tempest/lib/services/network/service_providers_client.py
+++ b/tempest/lib/services/network/service_providers_client.py
@@ -16,6 +16,11 @@
 class ServiceProvidersClient(base.BaseNetworkClient):
 
     def list_service_providers(self, **filters):
-        """Lists service providers."""
+        """Lists service providers.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#list-service-providers
+        """
         uri = '/service-providers'
         return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/object_storage/account_client.py b/tempest/lib/services/object_storage/account_client.py
index 67f01a6..6b097c1 100644
--- a/tempest/lib/services/object_storage/account_client.py
+++ b/tempest/lib/services/object_storage/account_client.py
@@ -34,7 +34,7 @@
         Account Metadata can be created, updated or deleted based on
         metadata header or value. For detailed info, please refer to the
         official API reference:
-        http://developer.openstack.org/api-ref/object-storage/?expanded=create-update-or-delete-account-metadata-detail
+        https://developer.openstack.org/api-ref/object-store/#create-update-or-delete-account-metadata
         """
         headers = {}
         if create_update_metadata:
diff --git a/tempest/lib/services/object_storage/container_client.py b/tempest/lib/services/object_storage/container_client.py
index 2da8e24..430e0d4 100644
--- a/tempest/lib/services/object_storage/container_client.py
+++ b/tempest/lib/services/object_storage/container_client.py
@@ -97,7 +97,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        https://developer.openstack.org/api-ref/object-storage/?expanded=show-container-details-and-list-objects-detail
+        https://developer.openstack.org/api-ref/object-store/#show-container-details-and-list-objects
         """
 
         url = str(container_name)
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index 20f3356..b99d1fe 100644
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -47,6 +47,14 @@
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
 
+    def show_encryption_specs_item(self, volume_type_id, key):
+        """Get the encryption specs item for the specified volume type."""
+        url = "/types/%s/encryption/%s" % (volume_type_id, key)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
     def create_encryption_type(self, volume_type_id, **kwargs):
         """Create encryption type.
 
diff --git a/tempest/lib/services/volume/v2/quota_classes_client.py b/tempest/lib/services/volume/v2/quota_classes_client.py
index d40d2d9..733b1ac 100644
--- a/tempest/lib/services/volume/v2/quota_classes_client.py
+++ b/tempest/lib/services/volume/v2/quota_classes_client.py
@@ -26,8 +26,9 @@
     def show_quota_class_set(self, quota_class_id):
         """List quotas for a quota class.
 
-        TODO: Current api-site doesn't contain this API description.
-        LP: https://bugs.launchpad.net/nova/+bug/1602400
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/index.html#show-quota-classes
         """
         url = 'os-quota-class-sets/%s' % quota_class_id
         resp, body = self.get(url)
@@ -38,8 +39,9 @@
     def update_quota_class_set(self, quota_class_id, **kwargs):
         """Update quotas for a quota class.
 
-        TODO: Current api-site doesn't contain this API description.
-        LP: https://bugs.launchpad.net/nova/+bug/1602400
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/index.html#update-quota-classes
         """
         url = 'os-quota-class-sets/%s' % quota_class_id
         put_body = json.dumps({'quota_class_set': kwargs})
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index 6181472..ecbcba1 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -88,3 +88,54 @@
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
+
+    def create_or_update_group_type_specs(self, group_type_id, group_specs):
+        """Creates new group specs or updates existing group specs.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#create-or-update-group-specs-for-a-group-type
+        """
+        url = "group_types/%s/group_specs" % group_type_id
+        post_body = json.dumps({'group_specs': group_specs})
+        resp, body = self.post(url, post_body)
+        body = json.loads(body)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_group_type_specs(self, group_type_id):
+        """Lists all group specs for a given group type."""
+        url = 'group_types/%s/group_specs' % group_type_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_group_type_specs_item(self, group_type_id, spec_id):
+        """Shows specified item of group specs for a given group type."""
+        url = "group_types/%s/group_specs/%s" % (group_type_id, spec_id)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_group_type_specs_item(self, group_type_id, spec_id, spec):
+        """Updates specified item of group specs for a given group type.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#update-one-specific-group-spec-for-a-group-type
+        """
+        url = "group_types/%s/group_specs/%s" % (group_type_id, spec_id)
+        put_body = json.dumps(spec)
+        resp, body = self.put(url, put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_group_type_specs_item(self, group_type_id, spec_id):
+        """Deletes specified item of group specs for a given group type."""
+        resp, body = self.delete("group_types/%s/group_specs/%s" % (
+            group_type_id, spec_id))
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 06b4b59..cf53b67 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -139,13 +139,20 @@
             name = data_utils.rand_name(self.__class__.__name__ + "-server")
 
         vnic_type = CONF.network.port_vnic_type
+        profile = CONF.network.port_profile
 
-        # If vnic_type is configured create port for
+        # If vnic_type or profile are configured create port for
         # every network
-        if vnic_type:
+        if vnic_type or profile:
             ports = []
+            create_port_body = {}
 
-            create_port_body = {'binding:vnic_type': vnic_type}
+            if vnic_type:
+                create_port_body['binding:vnic_type'] = vnic_type
+
+            if profile:
+                create_port_body['binding:profile'] = profile
+
             if kwargs:
                 # Convert security group names to security group ids
                 # to pass to create_port
@@ -252,6 +259,8 @@
         self.addCleanup(self.snapshots_client.delete_snapshot, snapshot['id'])
         waiters.wait_for_volume_resource_status(self.snapshots_client,
                                                 snapshot['id'], 'available')
+        snapshot = self.snapshots_client.show_snapshot(
+            snapshot['id'])['snapshot']
         return snapshot
 
     def create_volume_type(self, client=None, name=None, backend_name=None):
@@ -653,8 +662,8 @@
                 addresses = (server['addresses'][network['name']]
                              if network else [])
             for address in addresses:
-                if (address['version'] == CONF.validation.ip_version_for_ssh
-                        and address['OS-EXT-IPS:type'] == 'fixed'):
+                if (address['version'] == CONF.validation.ip_version_for_ssh and  # noqa
+                        address['OS-EXT-IPS:type'] == 'fixed'):
                     return address['addr']
             raise exceptions.ServerUnreachable(server_id=server['id'])
         else:
@@ -783,8 +792,8 @@
         port_map = [(p["id"], fxip["ip_address"])
                     for p in ports
                     for fxip in p["fixed_ips"]
-                    if netutils.is_valid_ipv4(fxip["ip_address"])
-                    and p['status'] in p_status]
+                    if (netutils.is_valid_ipv4(fxip["ip_address"]) and
+                        p['status'] in p_status)]
         inactive = [p for p in ports if p['status'] != 'ACTIVE']
         if inactive:
             LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
@@ -1174,7 +1183,7 @@
         LOG.debug("Creating an encryption type for volume type: %s", type_id)
         client.create_encryption_type(
             type_id, provider=provider, key_size=key_size, cipher=cipher,
-            control_location=control_location)['encryption']
+            control_location=control_location)
 
     def create_encrypted_volume(self, encryption_provider, volume_type,
                                 key_size=256, cipher='aes-xts-plain64',
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 9ff6227..b515639 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -37,7 +37,7 @@
         super(TestAggregatesBasicOps, cls).setup_clients()
         # Use admin client by default
         cls.aggregates_client = cls.os_admin.aggregates_client
-        cls.hosts_client = cls.os_admin.hosts_client
+        cls.services_client = cls.os_admin.services_client
 
     def _create_aggregate(self, **kwargs):
         aggregate = (self.aggregates_client.create_aggregate(**kwargs)
@@ -51,10 +51,10 @@
         return aggregate
 
     def _get_host_name(self):
-        hosts = self.hosts_client.list_hosts()['hosts']
-        self.assertNotEmpty(hosts)
-        computes = [x for x in hosts if x['service'] == 'compute']
-        return computes[0]['host_name']
+        svc_list = self.services_client.list_services(
+            binary='nova-compute')['services']
+        self.assertNotEmpty(svc_list)
+        return svc_list[0]['host']
 
     def _add_host(self, aggregate_id, host):
         aggregate = (self.aggregates_client.add_host(aggregate_id, host=host)
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 29f1743..2b35e45 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -13,8 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
 from tempest.common import custom_matchers
 from tempest.common import utils
 from tempest.common import waiters
@@ -101,10 +99,6 @@
                     return address
 
     @decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8')
-    @testtools.skipUnless(CONF.network.public_network_id,
-                          'The public_network_id option must be specified.')
-    @testtools.skipUnless(CONF.network_feature_enabled.floating_ips,
-                          'Floating ips are not available')
     @utils.services('compute', 'volume', 'image', 'network')
     def test_minimum_basic_scenario(self):
         image = self.glance_image_create()
@@ -126,22 +120,29 @@
         self.addCleanup(self.nova_volume_detach, server, volume)
         self.cinder_show(volume)
 
-        floating_ip = self.create_floating_ip(server)
-        # fetch the server again to make sure the addresses were refreshed
-        # after associating the floating IP
+        floating_ip = None
         server = self.servers_client.show_server(server['id'])['server']
-        address = self._get_floating_ip_in_server_addresses(
-            floating_ip, server)
-        self.assertIsNotNone(
-            address,
-            "Failed to find floating IP '%s' in server addresses: %s" %
-            (floating_ip['ip'], server['addresses']))
+        if (CONF.network_feature_enabled.floating_ips and
+            CONF.network.floating_network_name):
+            floating_ip = self.create_floating_ip(server)
+            # fetch the server again to make sure the addresses were refreshed
+            # after associating the floating IP
+            server = self.servers_client.show_server(server['id'])['server']
+            address = self._get_floating_ip_in_server_addresses(
+                floating_ip, server)
+            self.assertIsNotNone(
+                address,
+                "Failed to find floating IP '%s' in server addresses: %s" %
+                (floating_ip['ip'], server['addresses']))
+            ssh_ip = floating_ip['ip']
+        else:
+            ssh_ip = self.get_server_ip(server)
 
         self.create_and_add_security_group_to_server(server)
 
         # check that we can SSH to the server before reboot
         self.linux_client = self.get_remote_client(
-            floating_ip['ip'], private_key=keypair['private_key'],
+            ssh_ip, private_key=keypair['private_key'],
             server=server)
 
         self.nova_reboot(server)
@@ -149,25 +150,27 @@
         # check that we can SSH to the server after reboot
         # (both connections are part of the scenario)
         self.linux_client = self.get_remote_client(
-            floating_ip['ip'], private_key=keypair['private_key'],
+            ssh_ip, private_key=keypair['private_key'],
             server=server)
 
         self.check_disks()
 
-        # delete the floating IP, this should refresh the server addresses
-        self.compute_floating_ips_client.delete_floating_ip(floating_ip['id'])
+        if floating_ip:
+            # delete the floating IP, this should refresh the server addresses
+            self.compute_floating_ips_client.delete_floating_ip(
+                floating_ip['id'])
 
-        def is_floating_ip_detached_from_server():
-            server_info = self.servers_client.show_server(
-                server['id'])['server']
-            address = self._get_floating_ip_in_server_addresses(
-                floating_ip, server_info)
-            return (not address)
+            def is_floating_ip_detached_from_server():
+                server_info = self.servers_client.show_server(
+                    server['id'])['server']
+                address = self._get_floating_ip_in_server_addresses(
+                    floating_ip, server_info)
+                return (not address)
 
-        if not test_utils.call_until_true(
-            is_floating_ip_detached_from_server,
-            CONF.compute.build_timeout,
-            CONF.compute.build_interval):
-            msg = ("Floating IP '%s' should not be in server addresses: %s" %
-                   (floating_ip['ip'], server['addresses']))
-            raise exceptions.TimeoutException(msg)
+            if not test_utils.call_until_true(
+                is_floating_ip_detached_from_server,
+                CONF.compute.build_timeout,
+                CONF.compute.build_interval):
+                msg = ("Floating IP '%s' should not be in server addresses: %s"
+                       % (floating_ip['ip'], server['addresses']))
+                raise exceptions.TimeoutException(msg)
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 7c404ad..87ce951 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -43,8 +43,8 @@
     @classmethod
     def skip_checks(cls):
         super(TestNetworkAdvancedServerOps, cls).skip_checks()
-        if not (CONF.network.project_networks_reachable
-                or CONF.network.public_network_id):
+        if not (CONF.network.project_networks_reachable or
+                CONF.network.public_network_id):
             msg = ('Either project_networks_reachable must be "true", or '
                    'public_network_id must be defined.')
             raise cls.skipException(msg)
@@ -195,6 +195,8 @@
         waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'VERIFY_RESIZE')
         self.servers_client.confirm_resize_server(server['id'])
+        server = self.servers_client.show_server(server['id'])['server']
+        self.assertEqual(resize_flavor, server['flavor']['id'])
         self._wait_server_status_and_check_network_connectivity(
             server, keypair, floating_ip)
 
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index ff8837f..bcd4ddb 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -81,8 +81,8 @@
     @classmethod
     def skip_checks(cls):
         super(TestNetworkBasicOps, cls).skip_checks()
-        if not (CONF.network.project_networks_reachable
-                or CONF.network.public_network_id):
+        if not (CONF.network.project_networks_reachable or
+                CONF.network.public_network_id):
             msg = ('Either project_networks_reachable must be "true", or '
                    'public_network_id must be defined.')
             raise cls.skipException(msg)
@@ -304,16 +304,19 @@
 
         - ping internal gateway and DHCP port, implying in-tenant connectivity
         pinging both, because L3 and DHCP agents might be on different nodes
+        - ping internal compute port, implying connectivity to other VMs on
+        this network
         """
         floating_ip, server = self.floating_ip_tuple
         # get internal ports' ips:
-        # get all network ports in the new network
+        # get all network and compute ports in the new network
         internal_ips = (
             p['fixed_ips'][0]['ip_address'] for p in
             self.os_admin.ports_client.list_ports(
                 tenant_id=server['tenant_id'],
                 network_id=network['id'])['ports']
-            if p['device_owner'].startswith('network')
+            if p['device_owner'].startswith('network') or
+            p['device_owner'].startswith('compute')
         )
 
         self._check_server_connectivity(floating_ip,
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 9f4e62b..e4e39c3 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -38,11 +38,11 @@
     @classmethod
     def skip_checks(cls):
         super(TestGettingAddress, cls).skip_checks()
-        if not (CONF.network_feature_enabled.ipv6
-                and CONF.network_feature_enabled.ipv6_subnet_attributes):
+        if not (CONF.network_feature_enabled.ipv6 and
+                CONF.network_feature_enabled.ipv6_subnet_attributes):
             raise cls.skipException('IPv6 or its attributes not supported')
-        if not (CONF.network.project_networks_reachable
-                or CONF.network.public_network_id):
+        if not (CONF.network.project_networks_reachable or
+                CONF.network.public_network_id):
             msg = ('Either project_networks_reachable must be "true", or '
                    'public_network_id must be defined.')
             raise cls.skipException(msg)
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index d5c378e..1671216 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -14,7 +14,6 @@
 #    under the License.
 
 import json
-import re
 
 from tempest.common import utils
 from tempest.common import waiters
@@ -43,12 +42,6 @@
      * Terminate the instance
     """
 
-    @classmethod
-    def skip_checks(cls):
-        super(TestServerBasicOps, cls).skip_checks()
-        if not CONF.network_feature_enabled.floating_ips:
-            raise cls.skipException("Floating ips are not available")
-
     def setUp(self):
         super(TestServerBasicOps, self).setUp()
         self.run_ssh = CONF.validation.run_validation
@@ -56,11 +49,17 @@
 
     def verify_ssh(self, keypair):
         if self.run_ssh:
-            # Obtain a floating IP
-            self.fip = self.create_floating_ip(self.instance)['ip']
+            # Obtain a floating IP if floating_ips is enabled
+            if (CONF.network_feature_enabled.floating_ips and
+                CONF.network.floating_network_name):
+                self.ip = self.create_floating_ip(self.instance)['ip']
+            else:
+                server = self.servers_client.show_server(
+                    self.instance['id'])['server']
+                self.ip = self.get_server_ip(server)
             # Check ssh
             self.ssh_client = self.get_remote_client(
-                ip_address=self.fip,
+                ip_address=self.ip,
                 username=self.ssh_user,
                 private_key=keypair['private_key'],
                 server=self.instance)
@@ -75,8 +74,8 @@
                 result = self.ssh_client.exec_command(cmd)
                 if result:
                     msg = ('Failed while verifying metadata on server. Result '
-                           'of command "%s" is NOT "%s".' % (cmd, self.fip))
-                    self.assertEqual(self.fip, result, msg)
+                           'of command "%s" is NOT "%s".' % (cmd, self.ip))
+                    self.assertEqual(self.ip, result, msg)
                     return 'Verification is successful!'
 
             if not test_utils.call_until_true(exec_cmd_and_verify_output,
@@ -94,22 +93,13 @@
             result = self.servers_client.show_password(self.instance['id'])
             self.assertEqual(data, result['password'])
 
-    def _mount_config_drive(self):
-        cmd_blkid = 'blkid | grep -i config-2'
-        result = self.ssh_client.exec_command(cmd_blkid)
-        dev_name = re.match('([^:]+)', result).group()
-        self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
-
-    def _unmount_config_drive(self):
-        self.ssh_client.exec_command('sudo umount /mnt')
-
     def verify_metadata_on_config_drive(self):
         if self.run_ssh and CONF.compute_feature_enabled.config_drive:
             # Verify metadata on config_drive
-            self._mount_config_drive()
+            self.ssh_client.mount_config_drive()
             cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
             result = self.ssh_client.exec_command(cmd_md)
-            self._unmount_config_drive()
+            self.ssh_client.unmount_config_drive()
             result = json.loads(result)
             self.assertIn('meta', result)
             msg = ('Failed while verifying metadata on config_drive on server.'
@@ -119,10 +109,10 @@
     def verify_networkdata_on_config_drive(self):
         if self.run_ssh and CONF.compute_feature_enabled.config_drive:
             # Verify network data on config_drive
-            self._mount_config_drive()
+            self.ssh_client.mount_config_drive()
             cmd_md = 'sudo cat /mnt/openstack/latest/network_data.json'
             result = self.ssh_client.exec_command(cmd_md)
-            self._unmount_config_drive()
+            self.ssh_client.unmount_config_drive()
             result = json.loads(result)
             self.assertIn('services', result)
             self.assertIn('links', result)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index beb039c..1fc57e7 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -30,12 +30,6 @@
     # breathing room to get through deletes in the time allotted.
     TIMEOUT_SCALING_FACTOR = 2
 
-    @classmethod
-    def skip_checks(cls):
-        super(TestVolumeBootPattern, cls).skip_checks()
-        if not CONF.volume_feature_enabled.snapshot:
-            raise cls.skipException("Cinder volume snapshots are disabled")
-
     def _create_volume_from_image(self):
         img_uuid = CONF.compute.image_ref
         vol_name = data_utils.rand_name(
@@ -76,6 +70,8 @@
     @decorators.idempotent_id('557cd2c2-4eb8-4dce-98be-f86765ff311b')
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
+    @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+                          'Cinder volume snapshots are disabled')
     @utils.services('compute', 'volume', 'image')
     def test_volume_boot_pattern(self):
 
@@ -156,6 +152,8 @@
 
     @decorators.idempotent_id('05795fb2-b2a7-4c9f-8fac-ff25aedb1489')
     @decorators.attr(type='slow')
+    @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+                          'Cinder volume snapshots are disabled')
     @utils.services('compute', 'image', 'volume')
     def test_create_server_from_volume_snapshot(self):
         # Create a volume from an image
@@ -192,37 +190,60 @@
                          created_volume_info['attachments'][0]['volume_id'])
 
     @decorators.idempotent_id('36c34c67-7b54-4b59-b188-02a2f458a63b')
+    @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+                          'Cinder volume snapshots are disabled')
     @utils.services('compute', 'volume', 'image')
-    def test_create_ebs_image_and_check_boot(self):
-        # create an instance from volume
+    def test_image_defined_boot_from_volume(self):
+        # create an instance from image-backed volume
         volume_origin = self._create_volume_from_image()
         instance = self._boot_instance_from_resource(
             source_id=volume_origin['id'],
             source_type='volume',
             delete_on_termination=True)
-        # create EBS image
+        # Create a snapshot image from the volume-backed server.
+        # The compute service will have the block service create a snapshot of
+        # the root volume and store its metadata in the image.
         image = self.create_server_snapshot(instance)
 
-        # delete instance
+        # Delete the first server which will also delete the first image-backed
+        # volume.
         self._delete_server(instance)
 
-        # boot instance from EBS image
+        # Create a server from the image snapshot which has an
+        # "image-defined block device mapping (BDM)" in it, i.e. the metadata
+        # about the volume snapshot. The compute service will use this to
+        # create a volume from the volume snapshot and use that as the root
+        # disk for the server.
         instance = self.create_server(image_id=image['id'])
 
-        # Verify the server was created from the image
-        created_volume = instance['os-extended-volumes:volumes_attached']
-        self.assertNotEmpty(created_volume, "No volume attachment found.")
-        created_volume_info = self.volumes_client.show_volume(
-            created_volume[0]['id'])['volume']
+        # Verify the server was created from the image-defined BDM.
+        volume_attachments = instance['os-extended-volumes:volumes_attached']
+        self.assertEqual(1, len(volume_attachments),
+                         "No volume attachment found.")
+        created_volume = self.volumes_client.show_volume(
+            volume_attachments[0]['id'])['volume']
+        # Assert that the volume service also shows the server attachment.
+        self.assertEqual(1, len(created_volume['attachments']),
+                         "No server attachment found for volume: %s" %
+                         created_volume)
         self.assertEqual(instance['id'],
-                         created_volume_info['attachments'][0]['server_id'])
-        self.assertEqual(created_volume[0]['id'],
-                         created_volume_info['attachments'][0]['volume_id'])
+                         created_volume['attachments'][0]['server_id'])
+        self.assertEqual(volume_attachments[0]['id'],
+                         created_volume['attachments'][0]['volume_id'])
         self.assertEqual(
             volume_origin['volume_image_metadata']['image_id'],
-            created_volume_info['volume_image_metadata']['image_id'])
+            created_volume['volume_image_metadata']['image_id'])
 
-        # delete instance
+        # Delete the second server which should also delete the second volume
+        # created from the volume snapshot.
+        # TODO(mriedem): Currently, the compute service fails to delete the
+        # volume it created because the volume still has the snapshot
+        # associated with it, and the cleanups for the volume snapshot which
+        # were added in create_server_snapshot above, don't run until after
+        # this is called. So we need to delete the volume snapshot and wait for
+        # it to be gone here first before deleting the server, and then we can
+        # also assert that the underlying volume is deleted when the server is
+        # deleted.
         self._delete_server(instance)
 
     @decorators.idempotent_id('cb78919a-e553-4bab-b73b-10cf4d2eb125')
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index cd10bbd..ff7996a 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -38,6 +38,11 @@
     credentials = ['primary', 'admin']
 
     @classmethod
+    def setup_clients(cls):
+        super(TestVolumeMigrateRetypeAttached, cls).setup_clients()
+        cls.admin_volumes_client = cls.os_admin.volumes_v2_client
+
+    @classmethod
     def skip_checks(cls):
         super(TestVolumeMigrateRetypeAttached, cls).skip_checks()
         if not CONF.volume_feature_enabled.multi_backend:
@@ -76,8 +81,10 @@
         return source_body['name'], dest_body['name']
 
     def _volume_retype_with_migration(self, volume_id, new_volume_type):
+        # NOTE: The 'on-demand' migration requires admin operation, so
+        # admin_volumes_client() should be used here.
         migration_policy = 'on-demand'
-        self.volumes_client.retype_volume(
+        self.admin_volumes_client.retype_volume(
             volume_id, new_type=new_volume_type,
             migration_policy=migration_policy)
         waiters.wait_for_volume_retype(self.volumes_client,
diff --git a/tempest/test.py b/tempest/test.py
index 9da85d5..f2babbb 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -68,9 +68,9 @@
 def validate_tearDownClass():
     if at_exit_set:
         LOG.error(
-            "tearDownClass does not call the super's "
-            "tearDownClass in these classes: \n"
-            + str(at_exit_set))
+            "tearDownClass does not call the super's tearDownClass in "
+            "these classes:\n"
+            "  %s", at_exit_set)
 
 
 atexit.register(validate_tearDownClass)
@@ -582,8 +582,8 @@
         super(BaseTestCase, self).setUp()
         if not self.__setupclass_called:
             raise RuntimeError("setUpClass does not calls the super's"
-                               "setUpClass in the "
-                               + self.__class__.__name__)
+                               "setUpClass in the " +
+                               self.__class__.__name__)
         at_exit_set.add(self.__class__)
         test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
         try:
@@ -602,7 +602,7 @@
             stderr = self.useFixture(fixtures.StringStream('stderr')).stream
             self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
         if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
-            os.environ.get('OS_LOG_CAPTURE') != '0'):
+                os.environ.get('OS_LOG_CAPTURE') != '0'):
             self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
                                                    format=self.log_format,
                                                    level=None))
@@ -836,7 +836,7 @@
             manager = cls.get_client_manager()
 
         # Make sure cred_provider exists and get a network client
-        networks_client = manager.compute_networks_client
+        networks_client = manager.networks_client
         cred_provider = cls._get_credentials_provider()
         # In case of nova network, isolated tenants are not able to list the
         # network configured in fixed_network_name, even if they can use it
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index 8bf4c5b..fd9af08 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -153,17 +153,14 @@
 
     def test_generate_resources_no_admin(self):
         cfg.CONF.set_default('swift', False, group='service_available')
-        cfg.CONF.set_default('heat', False, group='service_available')
         cfg.CONF.set_default('operator_role', 'fake_operator',
                              group='object-storage')
         cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
                              group='object-storage')
-        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
-                             group='orchestration')
         resources = account_generator.generate_resources(
             self.cred_provider, admin=False)
         resource_types = [k for k, _ in resources]
-        # No admin, no heat, no swift, expect two credentials only
+        # No admin, no swift, expect two credentials only
         self.assertEqual(2, len(resources))
         # Ensure create_user was invoked twice (two distinct users)
         self.assertEqual(2, self.user_create_fixture.mock.call_count)
@@ -180,17 +177,14 @@
 
     def test_generate_resources_admin(self):
         cfg.CONF.set_default('swift', False, group='service_available')
-        cfg.CONF.set_default('heat', False, group='service_available')
         cfg.CONF.set_default('operator_role', 'fake_operator',
                              group='object-storage')
         cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
                              group='object-storage')
-        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
-                             group='orchestration')
         resources = account_generator.generate_resources(
             self.cred_provider, admin=True)
         resource_types = [k for k, _ in resources]
-        # Admin, no heat, no swift, expect three credentials only
+        # Admin, no swift, expect three credentials only
         self.assertEqual(3, len(resources))
         # Ensure create_user was invoked 3 times (3 distinct users)
         self.assertEqual(3, self.user_create_fixture.mock.call_count)
@@ -205,28 +199,24 @@
             self.assertIsNotNone(resource[1].router)
             self.assertIsNotNone(resource[1].subnet)
 
-    def test_generate_resources_swift_heat_admin(self):
+    def test_generate_resources_swift_admin(self):
         cfg.CONF.set_default('swift', True, group='service_available')
-        cfg.CONF.set_default('heat', True, group='service_available')
         cfg.CONF.set_default('operator_role', 'fake_operator',
                              group='object-storage')
         cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
                              group='object-storage')
-        cfg.CONF.set_default('stack_owner_role', 'fake_owner',
-                             group='orchestration')
         resources = account_generator.generate_resources(
             self.cred_provider, admin=True)
         resource_types = [k for k, _ in resources]
         # all options on, expect six credentials
         self.assertEqual(6, len(resources))
         # Ensure create_user was invoked 6 times (6 distinct users)
-        self.assertEqual(6, self.user_create_fixture.mock.call_count)
+        self.assertEqual(5, self.user_create_fixture.mock.call_count)
         self.assertIn('primary', resource_types)
         self.assertIn('alt', resource_types)
         self.assertIn('admin', resource_types)
         self.assertIn(['fake_operator'], resource_types)
         self.assertIn(['fake_reseller'], resource_types)
-        self.assertIn(['fake_owner', 'fake_operator'], resource_types)
         for resource in resources:
             self.assertIsNotNone(resource[1].network)
             self.assertIsNotNone(resource[1].router)
@@ -258,7 +248,6 @@
             self.opts)
         self.mock_resource_creation()
         cfg.CONF.set_default('swift', True, group='service_available')
-        cfg.CONF.set_default('heat', True, group='service_available')
         self.resources = account_generator.generate_resources(
             self.cred_provider, admin=True)
 
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 0485e14..6cc356e 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -21,6 +21,7 @@
 
 import fixtures
 import mock
+import six
 
 from tempest.cmd import run
 from tempest.tests import base
@@ -35,24 +36,13 @@
         super(TestTempestRun, self).setUp()
         self.run_cmd = run.TempestRun(None, None)
 
-    def test_build_options(self):
-        args = mock.Mock(spec=argparse.Namespace)
-        setattr(args, "subunit", True)
-        setattr(args, "parallel", False)
-        setattr(args, "concurrency", 10)
-        setattr(args, "load_list", '')
-        options = self.run_cmd._build_options(args)
-        self.assertEqual(['--subunit',
-                          '--concurrency=10'],
-                         options)
-
     def test__build_regex_default(self):
         args = mock.Mock(spec=argparse.Namespace)
         setattr(args, 'smoke', False)
         setattr(args, 'regex', '')
         setattr(args, 'whitelist_file', None)
         setattr(args, 'blacklist_file', None)
-        self.assertEqual('', self.run_cmd._build_regex(args))
+        self.assertIsNone(None, self.run_cmd._build_regex(args))
 
     def test__build_regex_smoke(self):
         args = mock.Mock(spec=argparse.Namespace)
@@ -60,7 +50,7 @@
         setattr(args, 'regex', '')
         setattr(args, 'whitelist_file', None)
         setattr(args, 'blacklist_file', None)
-        self.assertEqual('smoke', self.run_cmd._build_regex(args))
+        self.assertEqual(['smoke'], self.run_cmd._build_regex(args))
 
     def test__build_regex_regex(self):
         args = mock.Mock(spec=argparse.Namespace)
@@ -68,37 +58,9 @@
         setattr(args, "regex", 'i_am_a_fun_little_regex')
         setattr(args, 'whitelist_file', None)
         setattr(args, 'blacklist_file', None)
-        self.assertEqual('i_am_a_fun_little_regex',
+        self.assertEqual(['i_am_a_fun_little_regex'],
                          self.run_cmd._build_regex(args))
 
-    def test__build_whitelist_file(self):
-        args = mock.Mock(spec=argparse.Namespace)
-        setattr(args, 'smoke', False)
-        setattr(args, 'regex', None)
-        self.tests = tempfile.NamedTemporaryFile(
-            prefix='whitelist', delete=False)
-        self.tests.write(b"volume \n compute")
-        self.tests.close()
-        setattr(args, 'whitelist_file', self.tests.name)
-        setattr(args, 'blacklist_file', None)
-        self.assertEqual("volume|compute",
-                         self.run_cmd._build_regex(args))
-        os.unlink(self.tests.name)
-
-    def test__build_blacklist_file(self):
-        args = mock.Mock(spec=argparse.Namespace)
-        setattr(args, 'smoke', False)
-        setattr(args, 'regex', None)
-        self.tests = tempfile.NamedTemporaryFile(
-            prefix='blacklist', delete=False)
-        self.tests.write(b"volume \n compute")
-        self.tests.close()
-        setattr(args, 'whitelist_file', None)
-        setattr(args, 'blacklist_file', self.tests.name)
-        self.assertEqual("^((?!compute|volume).)*$",
-                         self.run_cmd._build_regex(args))
-        os.unlink(self.tests.name)
-
 
 class TestRunReturnCode(base.TestCase):
     def setUp(self):
@@ -109,13 +71,13 @@
         self.test_dir = os.path.join(self.directory, 'tests')
         os.mkdir(self.test_dir)
         # Setup Test files
-        self.testr_conf_file = os.path.join(self.directory, '.testr.conf')
+        self.stestr_conf_file = os.path.join(self.directory, '.stestr.conf')
         self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg')
         self.passing_file = os.path.join(self.test_dir, 'test_passing.py')
         self.failing_file = os.path.join(self.test_dir, 'test_failing.py')
         self.init_file = os.path.join(self.test_dir, '__init__.py')
         self.setup_py = os.path.join(self.directory, 'setup.py')
-        shutil.copy('tempest/tests/files/testr-conf', self.testr_conf_file)
+        shutil.copy('tempest/tests/files/testr-conf', self.stestr_conf_file)
         shutil.copy('tempest/tests/files/passing-tests', self.passing_file)
         shutil.copy('tempest/tests/files/failing-tests', self.failing_file)
         shutil.copy('setup.py', self.setup_py)
@@ -132,29 +94,57 @@
         msg = ("Running %s got an unexpected returncode\n"
                "Stdout: %s\nStderr: %s" % (' '.join(cmd), out, err))
         self.assertEqual(p.returncode, expected, msg)
+        return out, err
 
     def test_tempest_run_passes(self):
-        # Git init is required for the pbr testr command. pbr requires a git
-        # version or an sdist to work. so make the test directory a git repo
-        # too.
-        subprocess.call(['git', 'init'], stderr=DEVNULL)
         self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0)
 
-    def test_tempest_run_passes_with_testrepository(self):
-        # Git init is required for the pbr testr command. pbr requires a git
-        # version or an sdist to work. so make the test directory a git repo
-        # too.
-        subprocess.call(['git', 'init'], stderr=DEVNULL)
-        subprocess.call(['testr', 'init'])
+    def test_tempest_run_passes_with_stestr_repository(self):
+        subprocess.call(['stestr', 'init'])
         self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0)
 
     def test_tempest_run_fails(self):
-        # Git init is required for the pbr testr command. pbr requires a git
-        # version or an sdist to work. so make the test directory a git repo
-        # too.
-        subprocess.call(['git', 'init'], stderr=DEVNULL)
         self.assertRunExit(['tempest', 'run'], 1)
 
+    def test_run_list(self):
+        subprocess.call(['stestr', 'init'])
+        out, err = self.assertRunExit(['tempest', 'run', '-l'], 0)
+        tests = out.split()
+        tests = sorted([six.text_type(x.rstrip()) for x in tests if x])
+        result = [
+            six.text_type('tests.test_failing.FakeTestClass.test_pass'),
+            six.text_type('tests.test_failing.FakeTestClass.test_pass_list'),
+            six.text_type('tests.test_passing.FakeTestClass.test_pass'),
+            six.text_type('tests.test_passing.FakeTestClass.test_pass_list'),
+        ]
+        # NOTE(mtreinish): on python 3 the subprocess prints b'' around
+        # stdout.
+        if six.PY3:
+            result = ["b\'" + x + "\'" for x in result]
+        self.assertEqual(result, tests)
+
+    def test_tempest_run_with_whitelist(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        whitelist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(whitelist_file.close)
+        whitelist_file.write('passing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path], 0)
+
+    def test_tempest_run_with_whitelist_with_regex(self):
+        fd, path = tempfile.mkstemp()
+        self.addCleanup(os.remove, path)
+        whitelist_file = os.fdopen(fd, 'wb', 0)
+        self.addCleanup(whitelist_file.close)
+        whitelist_file.write('passing'.encode('utf-8'))
+        self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path,
+                            '--regex', 'fail'], 1)
+
+    def test_tempest_run_passes_with_config_file(self):
+        self.assertRunExit(['tempest', 'run',
+                            '--config-file', self.stestr_conf_file,
+                            '--regex', 'passing'], 0)
+
 
 class TestTakeAction(base.TestCase):
     def test_workspace_not_registered(self):
@@ -183,3 +173,27 @@
         self.assertRaises(Exception_, tempest_run.take_action, parsed_args)
         exit_msg = m_exit.call_args[0][0]
         self.assertIn(workspace, exit_msg)
+
+    def test_config_file_specified(self):
+        # Setup test dirs
+        self.directory = tempfile.mkdtemp(prefix='tempest-unit')
+        self.addCleanup(shutil.rmtree, self.directory)
+        self.test_dir = os.path.join(self.directory, 'tests')
+        os.mkdir(self.test_dir)
+        # Change directory, run wrapper and check result
+        self.addCleanup(os.chdir, os.path.abspath(os.curdir))
+        os.chdir(self.directory)
+
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.config_file = []
+
+        parsed_args.workspace = None
+        parsed_args.state = None
+        parsed_args.list_tests = False
+        parsed_args.config_file = '.stestr.conf'
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
diff --git a/tempest/tests/cmd/test_subunit_describe_calls.py b/tempest/tests/cmd/test_subunit_describe_calls.py
index 5f3d770..cb34ba6 100644
--- a/tempest/tests/cmd/test_subunit_describe_calls.py
+++ b/tempest/tests/cmd/test_subunit_describe_calls.py
@@ -33,15 +33,42 @@
         p.communicate()
         self.assertEqual(0, p.returncode)
 
+    def test_verbose(self):
+        subunit_file = os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            'sample_streams/calls.subunit')
+        p = subprocess.Popen([
+            'subunit-describe-calls', '-s', subunit_file,
+            '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        stdout = p.communicate()
+        self.assertEqual(0, p.returncode)
+        self.assertIn(b'- request headers:', stdout[0])
+        self.assertIn(b'- request body:', stdout[0])
+        self.assertIn(b'- response headers:', stdout[0])
+        self.assertIn(b'- response body:', stdout[0])
+
     def test_return_code_no_output(self):
         subunit_file = os.path.join(
             os.path.dirname(os.path.abspath(__file__)),
             'sample_streams/calls.subunit')
         p = subprocess.Popen([
             'subunit-describe-calls', '-s', subunit_file],
-            stdin=subprocess.PIPE)
-        p.communicate()
+            stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        stdout = p.communicate()
         self.assertEqual(0, p.returncode)
+        self.assertIn(b'foo', stdout[0])
+        self.assertIn(b'- 200 POST request for Nova to v2.1/<id>/',
+                      stdout[0])
+        self.assertIn(b'- 200 DELETE request for Nova to v2.1/<id>/',
+                      stdout[0])
+        self.assertIn(b'- 200 GET request for Nova to v2.1/<id>/',
+                      stdout[0])
+        self.assertIn(b'- 404 DELETE request for Nova to v2.1/<id>/',
+                      stdout[0])
+        self.assertNotIn(b'- request headers:', stdout[0])
+        self.assertNotIn(b'- request body:', stdout[0])
+        self.assertNotIn(b'- response headers:', stdout[0])
+        self.assertNotIn(b'- response body:', stdout[0])
 
     def test_parse(self):
         subunit_file = os.path.join(
diff --git a/tempest/tests/cmd/test_tempest_init.py b/tempest/tests/cmd/test_tempest_init.py
index 79510be..5f39ac9 100644
--- a/tempest/tests/cmd/test_tempest_init.py
+++ b/tempest/tests/cmd/test_tempest_init.py
@@ -27,16 +27,16 @@
         conf_dir = self.useFixture(fixtures.TempDir())
 
         init_cmd = init.TempestInit(None, None)
-        init_cmd.generate_testr_conf(conf_dir.path)
+        init_cmd.generate_stestr_conf(conf_dir.path)
 
         # Generate expected file contents
         top_level_path = os.path.dirname(os.path.dirname(init.__file__))
         discover_path = os.path.join(top_level_path, 'test_discover')
-        testr_conf_file = init.TESTR_CONF % (top_level_path, discover_path)
+        stestr_conf_file = init.STESTR_CONF % (discover_path, top_level_path)
 
-        conf_path = conf_dir.join('.testr.conf')
+        conf_path = conf_dir.join('.stestr.conf')
         with open(conf_path, 'r') as conf_file:
-            self.assertEqual(conf_file.read(), testr_conf_file)
+            self.assertEqual(conf_file.read(), stestr_conf_file)
 
     def test_generate_sample_config(self):
         local_dir = self.useFixture(fixtures.TempDir())
@@ -125,18 +125,18 @@
         lock_path = os.path.join(fake_local_dir.path, 'tempest_lock')
         etc_dir = os.path.join(fake_local_dir.path, 'etc')
         log_dir = os.path.join(fake_local_dir.path, 'logs')
-        testr_dir = os.path.join(fake_local_dir.path, '.testrepository')
+        stestr_dir = os.path.join(fake_local_dir.path, '.stestr')
         self.assertTrue(os.path.isdir(lock_path))
         self.assertTrue(os.path.isdir(etc_dir))
         self.assertTrue(os.path.isdir(log_dir))
-        self.assertTrue(os.path.isdir(testr_dir))
+        self.assertTrue(os.path.isdir(stestr_dir))
         # Assert file creation
         fake_file_moved = os.path.join(etc_dir, 'conf_file.conf')
         local_conf_file = os.path.join(etc_dir, 'tempest.conf')
-        local_testr_conf = os.path.join(fake_local_dir.path, '.testr.conf')
+        local_stestr_conf = os.path.join(fake_local_dir.path, '.stestr.conf')
         self.assertTrue(os.path.isfile(fake_file_moved))
         self.assertTrue(os.path.isfile(local_conf_file))
-        self.assertTrue(os.path.isfile(local_testr_conf))
+        self.assertTrue(os.path.isfile(local_stestr_conf))
 
     def test_take_action_fails(self):
         class ParsedArgs(object):
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index ee63684..4a2fff4 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -39,11 +39,12 @@
         self.conf.set_default('uri_v3', 'http://fake_uri_v3.com/auth',
                               group='identity')
         self.conf.set_default('neutron', True, group='service_available')
-        self.conf.set_default('heat', True, group='service_available')
-        if not os.path.exists(str(os.environ.get('OS_TEST_LOCK_PATH'))):
-            os.mkdir(str(os.environ.get('OS_TEST_LOCK_PATH')))
+        lock_path = str(os.environ.get('OS_TEST_LOCK_PATH',
+                                       os.environ.get('TMPDIR', '/tmp')))
+        if not os.path.exists(lock_path):
+            os.mkdir(lock_path)
         lockutils.set_defaults(
-            lock_path=str(os.environ.get('OS_TEST_LOCK_PATH')),
+            lock_path=lock_path,
         )
         self.conf.set_default('auth_version', 'v2', group='identity')
         for config_option in ['username', 'password', 'project_name']:
diff --git a/tempest/tests/files/setup.cfg b/tempest/tests/files/setup.cfg
index f6f9f73..bd68708 100644
--- a/tempest/tests/files/setup.cfg
+++ b/tempest/tests/files/setup.cfg
@@ -4,7 +4,7 @@
 summary = Fake Project for testing wrapper scripts
 author = OpenStack
 author-email = openstack-dev@lists.openstack.org
-home-page = http://www.openstack.org/
+home-page = https://docs.openstack.org/tempest/latest/
 classifier =
     Intended Audience :: Information Technology
     Intended Audience :: System Administrators
diff --git a/tempest/tests/files/testr-conf b/tempest/tests/files/testr-conf
index d5ad083..63b3c44 100644
--- a/tempest/tests/files/testr-conf
+++ b/tempest/tests/files/testr-conf
@@ -1,5 +1,3 @@
 [DEFAULT]
-test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
-test_id_option=--load-list $IDFILE
-test_list_option=--list
+test_path=./tests
 group_regex=([^\.]*\.)*
diff --git a/tempest/tests/lib/cli/test_execute.py b/tempest/tests/lib/cli/test_execute.py
index c276386..c069af5 100644
--- a/tempest/tests/lib/cli/test_execute.py
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -125,3 +125,27 @@
                          mock_execute.call_args[0][2])
         self.assertNotIn('--os-project-domain-name',
                          mock_execute.call_args[0][2])
+
+    @mock.patch.object(cli_base, 'execute')
+    def test_execute_with_default_api_version(self, mock_execute):
+        cli = cli_base.CLIClient()
+        cli.openstack('action')
+        self.assertEqual(mock_execute.call_count, 1)
+        self.assertNotIn('--os-identity-api-version ',
+                         mock_execute.call_args[0][2])
+
+    @mock.patch.object(cli_base, 'execute')
+    def test_execute_with_empty_api_version(self, mock_execute):
+        cli = cli_base.CLIClient(identity_api_version='')
+        cli.openstack('action')
+        self.assertEqual(mock_execute.call_count, 1)
+        self.assertNotIn('--os-identity-api-version ',
+                         mock_execute.call_args[0][2])
+
+    @mock.patch.object(cli_base, 'execute')
+    def test_execute_with_explicit_api_version(self, mock_execute):
+        cli = cli_base.CLIClient(identity_api_version='0.0')
+        cli.openstack('action')
+        self.assertEqual(mock_execute.call_count, 1)
+        self.assertIn('--os-identity-api-version 0.0 ',
+                      mock_execute.call_args[0][2])
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index 9b10159..25df2a7 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -339,7 +339,7 @@
             return_value=test_accounts))
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
             **self.fixed_params)
-        with mock.patch('tempest.lib.services.compute.networks_client.'
+        with mock.patch('tempest.lib.services.network.networks_client.'
                         'NetworksClient.list_networks',
                         return_value={'networks': [{'name': 'network-2',
                                                     'id': 'fake-id',
diff --git a/tempest/tests/lib/services/identity/v3/test_policies_client.py b/tempest/tests/lib/services/identity/v3/test_policies_client.py
index 66c3d65..0237475 100644
--- a/tempest/tests/lib/services/identity/v3/test_policies_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_policies_client.py
@@ -81,6 +81,10 @@
                 }
             ]
     }
+    FAKE_ENDPOINT_ID = "234789"
+    FAKE_SERVICE_ID = "556782"
+    FAKE_POLICY_ID = "717273"
+    FAKE_REGION_ID = "73"
 
     def setUp(self):
         super(TestPoliciesClient, self).setUp()
@@ -150,3 +154,87 @@
             {},
             policy_id="717273",
             status=204)
+
+    def test_update_policy_association_for_endpoint(self):
+        self.check_service_client_function(
+            self.client.update_policy_association_for_endpoint,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            status=204)
+
+    def test_show_policy_association_for_endpoint(self):
+        self.check_service_client_function(
+            self.client.show_policy_association_for_endpoint,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            status=204)
+
+    def test_delete_policy_association_for_endpoint(self):
+        self.check_service_client_function(
+            self.client.delete_policy_association_for_endpoint,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            status=204)
+
+    def test_update_policy_association_for_service(self):
+        self.check_service_client_function(
+            self.client.update_policy_association_for_service,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            status=204)
+
+    def test_show_policy_association_for_service(self):
+        self.check_service_client_function(
+            self.client.show_policy_association_for_service,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            status=204)
+
+    def test_delete_policy_association_for_service(self):
+        self.check_service_client_function(
+            self.client.delete_policy_association_for_service,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            status=204)
+
+    def test_update_policy_association_for_region_and_service(self):
+        self.check_service_client_function(
+            self.client.update_policy_association_for_region_and_service,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            region_id=self.FAKE_REGION_ID,
+            status=204)
+
+    def test_show_policy_association_for_region_and_service(self):
+        self.check_service_client_function(
+            self.client.show_policy_association_for_region_and_service,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            region_id=self.FAKE_REGION_ID,
+            status=204)
+
+    def test_delete_policy_association_for_region_and_service(self):
+        self.check_service_client_function(
+            self.client.delete_policy_association_for_region_and_service,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            policy_id=self.FAKE_POLICY_ID,
+            service_id=self.FAKE_SERVICE_ID,
+            region_id=self.FAKE_REGION_ID,
+            status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_project_tags_client.py b/tempest/tests/lib/services/identity/v3/test_project_tags_client.py
new file mode 100644
index 0000000..2d65a29
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_project_tags_client.py
@@ -0,0 +1,104 @@
+# Copyright 2018 AT&T Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.lib.services.identity.v3 import project_tags_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestProjectTagsClient(base.BaseServiceTest):
+
+    FAKE_PROJECT_ID = "0c4e939acacf4376bdcd1129f1a054ad"
+
+    FAKE_PROJECT_TAG = "foo"
+
+    FAKE_PROJECT_TAGS = ["foo", "bar"]
+
+    def setUp(self):
+        super(TestProjectTagsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = project_tags_client.ProjectTagsClient(fake_auth,
+                                                            'identity',
+                                                            'regionOne')
+
+    def _test_update_project_tag(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_project_tag,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            project_id=self.FAKE_PROJECT_ID,
+            tag=self.FAKE_PROJECT_TAG,
+            status=201)
+
+    def _test_list_project_tags(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_project_tags,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {"tags": self.FAKE_PROJECT_TAGS},
+            bytes_body,
+            project_id=self.FAKE_PROJECT_ID)
+
+    def _test_update_all_project_tags(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_all_project_tags,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {"tags": self.FAKE_PROJECT_TAGS},
+            bytes_body,
+            project_id=self.FAKE_PROJECT_ID,
+            tags=self.FAKE_PROJECT_TAGS)
+
+    def test_update_project_tag_with_str_body(self):
+        self._test_update_project_tag()
+
+    def test_update_project_tag_with_bytes_body(self):
+        self._test_update_project_tag(bytes_body=True)
+
+    def test_list_project_tags_with_str_body(self):
+        self._test_list_project_tags()
+
+    def test_list_project_tags_with_bytes_body(self):
+        self._test_list_project_tags(bytes_body=True)
+
+    def test_update_all_project_tags_with_str_body(self):
+        self._test_update_all_project_tags()
+
+    def test_update_all_project_tags_with_bytes_body(self):
+        self._test_update_all_project_tags(bytes_body=True)
+
+    def test_check_project_project_tag_existence(self):
+        self.check_service_client_function(
+            self.client.check_project_tag_existence,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            project_id=self.FAKE_PROJECT_ID,
+            tag=self.FAKE_PROJECT_TAG,
+            status=204)
+
+    def test_delete_project_tag(self):
+        self.check_service_client_function(
+            self.client.delete_project_tag,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            project_id=self.FAKE_PROJECT_ID,
+            tag=self.FAKE_PROJECT_TAG,
+            status=204)
+
+    def test_delete_all_project_tags(self):
+        self.check_service_client_function(
+            self.client.delete_all_project_tags,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            project_id=self.FAKE_PROJECT_ID,
+            status=204)
diff --git a/tempest/tests/lib/services/network/test_quotas_client.py b/tempest/tests/lib/services/network/test_quotas_client.py
index e76bc9c..aa6c1a1 100644
--- a/tempest/tests/lib/services/network/test_quotas_client.py
+++ b/tempest/tests/lib/services/network/test_quotas_client.py
@@ -38,8 +38,62 @@
         ]
     }
 
+    FAKE_PROJECT_QUOTAS = {
+        "quota": {
+            "floatingip": 50,
+            "network": 10,
+            "port": 50,
+            "rbac_policy": -1,
+            "router": 10,
+            "security_group": 10,
+            "security_group_rule": 100,
+            "subnet": 10,
+            "subnetpool": -1
+        }
+    }
+
     FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978"
 
+    FAKE_QUOTA_DETAILS = {
+        "quota": {
+            "rbac_policy": {
+                "used": 4,
+                "limit": 10,
+                "reserved": 0
+            },
+            "subnetpool": {
+                "used": 2,
+                "limit": -1,
+                "reserved": 0
+            },
+            "security_group_rule": {
+                "used": 10,
+                "limit": 100,
+                "reserved": 1
+            },
+            "security_group": {
+                "used": 3,
+                "limit": 10,
+                "reserved": 0
+            },
+            "subnet": {
+                "used": 3,
+                "limit": 100,
+                "reserved": 0
+            },
+            "port": {
+                "used": 21,
+                "limit": 500,
+                "reserved": 3
+            },
+            "network": {
+                "used": 9,
+                "limit": 100,
+                "reserved": 2
+            }
+        }
+    }
+
     def setUp(self):
         super(TestQuotasClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -58,7 +112,16 @@
         self.check_service_client_function(
             self.quotas_client.show_quotas,
             "tempest.lib.common.rest_client.RestClient.get",
-            {"quota": self.FAKE_QUOTAS["quotas"][0]},
+            self.FAKE_PROJECT_QUOTAS,
+            bytes_body,
+            200,
+            tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+    def _test_show_default_quotas(self, bytes_body=False):
+        self.check_service_client_function(
+            self.quotas_client.show_default_quotas,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_PROJECT_QUOTAS,
             bytes_body,
             200,
             tenant_id=self.FAKE_QUOTA_TENANT_ID)
@@ -67,7 +130,16 @@
         self.check_service_client_function(
             self.quotas_client.update_quotas,
             "tempest.lib.common.rest_client.RestClient.put",
-            {"quota": self.FAKE_QUOTAS["quotas"][0]},
+            self.FAKE_PROJECT_QUOTAS,
+            bytes_body,
+            200,
+            tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+    def _test_show_quota_details(self, bytes_body=False):
+        self.check_service_client_function(
+            self.quotas_client.show_quota_details,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_QUOTA_DETAILS,
             bytes_body,
             200,
             tenant_id=self.FAKE_QUOTA_TENANT_ID)
@@ -92,8 +164,20 @@
     def test_show_quotas_with_bytes_body(self):
         self._test_show_quotas(bytes_body=True)
 
+    def test_show_default_quotas_with_str_body(self):
+        self._test_show_default_quotas()
+
+    def test_show_default_quotas_with_bytes_body(self):
+        self._test_show_default_quotas(bytes_body=True)
+
     def test_update_quotas_with_str_body(self):
         self._test_update_quotas()
 
     def test_update_quotas_with_bytes_body(self):
         self._test_update_quotas(bytes_body=True)
+
+    def test_show_quota_details_with_str_body(self):
+        self._test_show_quota_details()
+
+    def test_show_quota_details_with_bytes_body(self):
+        self._test_show_quota_details(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py b/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
index d029091..8de9fb4 100644
--- a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
@@ -43,6 +43,10 @@
         }
     }
 
+    FAKE_ENCRYPTION_SPECS_ITEM = {
+        "cipher": "aes-xts-plain64"
+    }
+
     def setUp(self):
         super(TestEncryptionTypesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -65,6 +69,13 @@
             self.FAKE_INFO_ENCRYPTION_TYPE,
             bytes_body, volume_type_id="cbc36478b0bd8e67e89")
 
+    def _test_show_encryption_specs_item(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_encryption_specs_item,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ENCRYPTION_SPECS_ITEM,
+            bytes_body, volume_type_id="cbc36478b0bd8e67e89", key="cipher")
+
     def test_create_encryption_type_with_str_body(self):
         self._test_create_encryption()
 
@@ -77,6 +88,12 @@
     def test_show_encryption_type_with_bytes_body(self):
         self._test_show_encryption_type(bytes_body=True)
 
+    def test_show_encryption_specs_item_with_str_body(self):
+        self._test_show_encryption_specs_item()
+
+    def test_show_encryption_specs_item_with_bytes_body(self):
+        self._test_show_encryption_specs_item(bytes_body=True)
+
     def test_delete_encryption_type(self):
         self.check_service_client_function(
             self.client.delete_encryption_type,
diff --git a/tempest/tests/lib/services/volume/v3/test_group_types_client.py b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
index e86594e..c60cc36 100644
--- a/tempest/tests/lib/services/volume/v3/test_group_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
@@ -69,6 +69,28 @@
         ]
     }
 
+    FAKE_CREATE_GROUP_TYPE_SPECS = {
+        "group_specs": {
+            "key1": "value1",
+            "key2": "value2"
+        }
+    }
+
+    FAKE_LIST_GROUP_TYPE_SPECS = {
+        "group_specs": {
+            "key1": "value1",
+            "key2": "value2"
+        }
+    }
+
+    FAKE_SHOW_GROUP_TYPE_SPECS_ITEM = {
+        "key1": "value1"
+    }
+
+    FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM = {
+        "key2": "value2-updated"
+    }
+
     def setUp(self):
         super(TestGroupTypesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -111,6 +133,45 @@
             group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
             name='updated-group-type-name')
 
+    def _test_create_or_update_group_type_specs(self, bytes_body=False):
+        group_specs = self.FAKE_CREATE_GROUP_TYPE_SPECS['group_specs']
+        self.check_service_client_function(
+            self.client.create_or_update_group_type_specs,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_GROUP_TYPE_SPECS,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+            group_specs=group_specs,
+            status=202)
+
+    def _test_list_group_type_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_group_type_specs,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_GROUP_TYPE_SPECS,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
+
+    def _test_show_group_type_specs_item(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_group_type_specs_item,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_GROUP_TYPE_SPECS_ITEM,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+            spec_id="key1")
+
+    def _test_update_group_type_specs_item(self, bytes_body=False):
+        spec = self.FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM
+        self.check_service_client_function(
+            self.client.update_group_type_specs_item,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM,
+            bytes_body,
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+            spec_id="key2",
+            spec=spec)
+
     def test_create_group_type_with_str_body(self):
         self._test_create_group_type()
 
@@ -142,3 +203,36 @@
 
     def test_update_group_types_with_bytes_body(self):
         self._test_update_group_types(bytes_body=True)
+
+    def test_create_or_update_group_type_specs_with_str_body(self):
+        self._test_create_or_update_group_type_specs()
+
+    def test_create_or_update_group_type_specs_with_bytes_body(self):
+        self._test_create_or_update_group_type_specs(bytes_body=True)
+
+    def test_list_group_type_specs_with_str_body(self):
+        self._test_list_group_type_specs()
+
+    def test_list_group_type_specs_with_bytes_body(self):
+        self._test_list_group_type_specs(bytes_body=True)
+
+    def test_show_group_type_specs_item_with_str_body(self):
+        self._test_show_group_type_specs_item()
+
+    def test_show_group_type_specs_item_with_bytes_body(self):
+        self._test_show_group_type_specs_item(bytes_body=True)
+
+    def test_update_group_type_specs_item_with_str_body(self):
+        self._test_update_group_type_specs_item()
+
+    def test_update_group_type_specs_item_with_bytes_body(self):
+        self._test_update_group_type_specs_item(bytes_body=True)
+
+    def test_delete_group_type_specs_item(self):
+        self.check_service_client_function(
+            self.client.delete_group_type_specs_item,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            group_type_id='0e58433f-d108-4bf3-a22c-34e6b71ef86b',
+            spec_id='key1',
+            status=202)
diff --git a/tempest/tests/services/object_storage/test_object_client.py b/tempest/tests/services/object_storage/test_object_client.py
deleted file mode 100644
index 86535f9..0000000
--- a/tempest/tests/services/object_storage/test_object_client.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# Copyright 2016 IBM Corp.
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-
-import mock
-
-from tempest.lib import exceptions
-from tempest.services.object_storage import object_client
-from tempest.tests import base
-from tempest.tests.lib import fake_auth_provider
-
-
-class TestObjectClient(base.TestCase):
-
-    def setUp(self):
-        super(TestObjectClient, self).setUp()
-        self.fake_auth = fake_auth_provider.FakeAuthProvider()
-        self.url = self.fake_auth.base_url(None)
-        self.object_client = object_client.ObjectClient(self.fake_auth,
-                                                        'swift', 'region1')
-
-    @mock.patch.object(object_client, '_create_connection')
-    def test_create_object_continue_no_data(self, mock_poc):
-        self._validate_create_object_continue(None, mock_poc)
-
-    @mock.patch.object(object_client, '_create_connection')
-    def test_create_object_continue_with_data(self, mock_poc):
-        self._validate_create_object_continue('hello', mock_poc)
-
-    @mock.patch.object(object_client, '_create_connection')
-    def test_create_continue_with_no_continue_received(self, mock_poc):
-        self._validate_create_object_continue('hello', mock_poc,
-                                              initial_status=201)
-
-    def _validate_create_object_continue(self, req_data,
-                                         mock_poc, initial_status=100):
-
-        expected_hdrs = {
-            'X-Auth-Token': self.fake_auth.get_token(),
-            'content-length': 0 if req_data is None else len(req_data),
-            'Expect': '100-continue'}
-
-        # Setup the Mocks prior to invoking the object creation
-        mock_resp_cls = mock.Mock()
-        mock_resp_cls._read_status.return_value = ("1", initial_status, "OK")
-
-        mock_poc.return_value.response_class.return_value = mock_resp_cls
-
-        # This is the final expected return value
-        mock_poc.return_value.getresponse.return_value.status = 201
-        mock_poc.return_value.getresponse.return_value.reason = 'OK'
-
-        # Call method to PUT object using expect:100-continue
-        cnt = "container1"
-        obj = "object1"
-        path = "/%s/%s" % (cnt, obj)
-
-        # If the expected initial status is not 100, then an exception
-        # should be thrown and the connection closed
-        if initial_status is 100:
-            status, reason = \
-                self.object_client.create_object_continue(cnt, obj, req_data)
-        else:
-            self.assertRaises(exceptions.UnexpectedResponseCode,
-                              self.object_client.create_object_continue, cnt,
-                              obj, req_data)
-            mock_poc.return_value.close.assert_called_once_with()
-
-        # Verify that putrequest is called 1 time with the appropriate values
-        mock_poc.return_value.putrequest.assert_called_once_with('PUT', path)
-
-        # Verify that headers were written, including "Expect:100-continue"
-        calls = []
-
-        for header, value in expected_hdrs.items():
-            calls.append(mock.call(header, value))
-
-        mock_poc.return_value.putheader.assert_has_calls(calls, False)
-        mock_poc.return_value.endheaders.assert_called_once_with()
-
-        # The following steps are only taken if the initial status is 100
-        if initial_status is 100:
-            # Verify that the method returned what it was supposed to
-            self.assertEqual(status, 201)
-
-            # Verify that _safe_read was called once to remove the CRLF
-            # after the 100 response
-            mock_rc = mock_poc.return_value.response_class.return_value
-            mock_rc._safe_read.assert_called_once_with(2)
-
-            # Verify the actual data was written via send
-            mock_poc.return_value.send.assert_called_once_with(req_data)
-
-            # Verify that the getresponse method was called to receive
-            # the final
-            mock_poc.return_value.getresponse.assert_called_once_with()
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
index 011bc9b..2b5a947 100644
--- a/tempest/tests/test_base_test.py
+++ b/tempest/tests/test_base_test.py
@@ -41,7 +41,7 @@
     def test_get_tenant_network(self, mock_gtn, mock_gprov, mock_gcm):
         net_client = mock.Mock()
         mock_prov = mock.Mock()
-        mock_gcm.return_value.compute_networks_client = net_client
+        mock_gcm.return_value.networks_client = net_client
         mock_gprov.return_value = mock_prov
 
         test.BaseTestCase.get_tenant_network()
@@ -85,7 +85,7 @@
                                                mock_gcm):
         net_client = mock.Mock()
         mock_prov = mock.Mock()
-        mock_gcm.return_value.compute_networks_client = net_client
+        mock_gcm.return_value.networks_client = net_client
         mock_gprov.return_value = mock_prov
 
         test.BaseTestCase.get_tenant_network(credentials_type='alt')
@@ -102,7 +102,7 @@
                                                 mock_gcm):
         net_client = mock.Mock()
         mock_prov = mock.Mock()
-        mock_gcm.return_value.compute_networks_client = net_client
+        mock_gcm.return_value.networks_client = net_client
         mock_gprov.return_value = mock_prov
         creds = ['foo_type', 'role1']
 
diff --git a/test-requirements.txt b/test-requirements.txt
index 37644d0..e33f207 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,11 +2,7 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
-# needed for doc build
-sphinx>=1.6.2 # BSD
-openstackdocstheme>=1.17.0 # Apache-2.0
-reno>=2.5.0 # Apache-2.0
 mock>=2.0.0 # BSD
 coverage!=4.4,>=4.0 # Apache-2.0
-oslotest>=1.10.0 # Apache-2.0
+oslotest>=3.2.0 # Apache-2.0
 flake8-import-order==0.11 # LGPLv3
diff --git a/tools/check_logs.py b/tools/check_logs.py
index fc21f75..b80ccc0 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -62,7 +62,7 @@
     for (name, filename) in file_specs:
         whitelist = whitelists.get(name, [])
         with open(filename) as content:
-            if scan_content(name, content, regexp, whitelist):
+            if scan_content(content, regexp, whitelist):
                 logs_with_errors.append(name)
     for (name, url) in url_specs:
         whitelist = whitelists.get(name, [])
@@ -71,12 +71,12 @@
         page = urlreq.urlopen(req)
         buf = six.StringIO(page.read())
         f = gzip.GzipFile(fileobj=buf)
-        if scan_content(name, f.read().splitlines(), regexp, whitelist):
+        if scan_content(f.read().splitlines(), regexp, whitelist):
             logs_with_errors.append(name)
     return logs_with_errors
 
 
-def scan_content(name, content, regexp, whitelist):
+def scan_content(content, regexp, whitelist):
     had_errors = False
     for line in content:
         if not line.startswith("Stderr:") and regexp.match(line):
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
deleted file mode 100755
index 1f2b88b..0000000
--- a/tools/find_stack_traces.py
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2013 IBM Corp.
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-import gzip
-import pprint
-import re
-import sys
-
-import six
-import six.moves.urllib.request as urlreq
-
-
-pp = pprint.PrettyPrinter()
-
-NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d"
-
-NOVA_REGEX = r"(?P<timestamp>%s) (?P<pid>\d+ )?(?P<level>(ERROR|TRACE)) " \
-    "(?P<module>[\w\.]+) (?P<msg>.*)" % (NOVA_TIMESTAMP)
-
-
-class StackTrace(object):
-    timestamp = None
-    pid = None
-    level = ""
-    module = ""
-    msg = ""
-
-    def __init__(self, timestamp=None, pid=None, level="", module="",
-                 msg=""):
-        self.timestamp = timestamp
-        self.pid = pid
-        self.level = level
-        self.module = module
-        self.msg = msg
-
-    def append(self, msg):
-        self.msg = self.msg + msg
-
-    def is_same(self, data):
-        return (data['timestamp'] == self.timestamp and
-                data['level'] == self.level)
-
-    def not_none(self):
-        return self.timestamp is not None
-
-    def __str__(self):
-        buff = "<%s %s %s>\n" % (self.timestamp, self.level, self.module)
-        for line in self.msg.splitlines():
-            buff = buff + line + "\n"
-        return buff
-
-
-def hunt_for_stacktrace(url):
-    """Return TRACE or ERROR lines out of logs."""
-    req = urlreq.Request(url)
-    req.add_header('Accept-Encoding', 'gzip')
-    page = urlreq.urlopen(req)
-    buf = six.StringIO(page.read())
-    f = gzip.GzipFile(fileobj=buf)
-    content = f.read()
-
-    traces = []
-    trace = StackTrace()
-    for line in content.splitlines():
-        m = re.match(NOVA_REGEX, line)
-        if m:
-            data = m.groupdict()
-            if trace.not_none() and trace.is_same(data):
-                trace.append(data['msg'] + "\n")
-            else:
-                trace = StackTrace(
-                    timestamp=data.get('timestamp'),
-                    pid=data.get('pid'),
-                    level=data.get('level'),
-                    module=data.get('module'),
-                    msg=data.get('msg'))
-
-        else:
-            if trace.not_none():
-                traces.append(trace)
-                trace = StackTrace()
-
-    # once more at the end to pick up any stragglers
-    if trace.not_none():
-        traces.append(trace)
-
-    return traces
-
-
-def log_url(url, log):
-    return "%s/%s" % (url, log)
-
-
-def collect_logs(url):
-    page = urlreq.urlopen(url)
-    content = page.read()
-    logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
-    return logs
-
-
-def usage():
-    print("""
-Usage: find_stack_traces.py <logurl>
-
-Hunts for stack traces in a devstack run. Must provide it a base log url
-from a tempest devstack run. Should start with http and end with /logs/.
-
-Returns a report listing stack traces out of the various files where
-they are found.
-""")
-    sys.exit(0)
-
-
-def print_stats(items, fname, verbose=False):
-    errors = len([x for x in items if x.level == "ERROR"])
-    traces = len([x for x in items if x.level == "TRACE"])
-    print("%d ERRORS found in %s" % (errors, fname))
-    print("%d TRACES found in %s" % (traces, fname))
-
-    if verbose:
-        for item in items:
-            print(item)
-        print("\n\n")
-
-
-def main():
-    if len(sys.argv) == 2:
-        url = sys.argv[1]
-        loglist = collect_logs(url)
-
-        # probably wrong base url
-        if not loglist:
-            usage()
-
-        for log in loglist:
-            logurl = log_url(url, log)
-            traces = hunt_for_stacktrace(logurl)
-
-            if traces:
-                print_stats(traces, log, verbose=True)
-
-    else:
-        usage()
-
-if __name__ == '__main__':
-    main()
diff --git a/tox.ini b/tox.ini
index e7ea1e2..da0233a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -16,6 +16,9 @@
 setenv =
     VIRTUAL_ENV={envdir}
     OS_LOG_CAPTURE=1
+    OS_STDOUT_CAPTURE=1
+    OS_STDERR_CAPTURE=1
+    OS_TEST_TIMEOUT=160
     PYTHONWARNINGS=default::DeprecationWarning
 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
@@ -80,6 +83,16 @@
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
     tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' {posargs}
 
+[testenv:full-parallel]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select all tempest scenario and including the non slow api tests
+commands =
+    find . -type f -name "*.pyc" -delete
+    tempest run --regex '(^tempest\.scenario.*)|(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
+
 [testenv:full-serial]
 envdir = .tox/tempest
 sitepackages = {[tempestenv]sitepackages}
@@ -123,6 +136,10 @@
     tempest run --serial --regex '\[.*\bsmoke\b.*\]' {posargs}
 
 [testenv:venv]
+deps =
+  -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+  -r{toxinidir}/requirements.txt
+  -r{toxinidir}/doc/requirements.txt
 commands = {posargs}
 
 [testenv:venv-tempest]
@@ -133,9 +150,14 @@
 commands = {posargs}
 
 [testenv:docs]
+deps =
+  -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+  -r{toxinidir}/requirements.txt
+  -r{toxinidir}/doc/requirements.txt
 commands =
-    rm -rf doc/build
-    python setup.py build_sphinx {posargs}
+  rm -rf doc/build
+  sphinx-build -W -b html doc/source doc/build/html
+whitelist_externals = rm
 
 [testenv:pep8]
 commands =
@@ -161,9 +183,15 @@
 import-order-style = pep8
 
 [testenv:releasenotes]
+deps =
+  -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+  -r{toxinidir}/requirements.txt
+  -r{toxinidir}/doc/requirements.txt
 commands =
-    rm -rf releasenotes/build
-    sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+  rm -rf releasenotes/build
+  sphinx-build -a -E -W -d releasenotes/build/doctrees \
+         -b html releasenotes/source releasenotes/build/html
+whitelist_externals = rm
 
 [testenv:pip-check-reqs]
 # Do not install test-requirements as that will pollute the virtualenv for