Merge "Fix directory tree appearance in document"
diff --git a/.zuul.yaml b/.zuul.yaml
new file mode 100644
index 0000000..ec6c59a
--- /dev/null
+++ b/.zuul.yaml
@@ -0,0 +1,101 @@
+- job:
+    name: devstack-tempest
+    parent: devstack
+    description: Base Tempest job.
+    required-projects:
+      - openstack/tempest
+    timeout: 7200
+    roles:
+      - zuul: openstack-dev/devstack
+    vars:
+      devstack_services:
+        tempest: True
+    run: playbooks/devstack-tempest.yaml
+    post-run: playbooks/post-tempest.yaml
+
+- job:
+    name: tempest-tox-plugin-sanity-check
+    parent: tox
+    description: |
+      Run tempest plugin sanity check script using tox.
+    nodeset: ubuntu-xenial
+    vars:
+      tox_envlist: plugin-sanity-check
+    voting: false
+    timeout: 5000
+    required-projects:
+      - openstack/almanach
+      - openstack/aodh
+      - openstack/barbican-tempest-plugin
+      - openstack/ceilometer
+      - openstack/cinder
+      - openstack/congress
+      - openstack/designate-tempest-plugin
+      - openstack/ec2-api
+      - openstack/freezer
+      - openstack/freezer-api
+      - openstack/freezer-tempest-plugin
+      - openstack/gce-api
+      - openstack/glare
+      - openstack/heat
+      - openstack/intel-nfv-ci-tests
+      - openstack/ironic
+      - openstack/ironic-inspector
+      - openstack/keystone-tempest-plugin
+      - openstack/kingbird
+      - openstack/kuryr-tempest-plugin
+      - openstack/magnum
+      - openstack/magnum-tempest-plugin
+      - openstack/manila
+      - openstack/manila-tempest-plugin
+      - openstack/mistral
+      - openstack/mogan
+      - openstack/monasca-api
+      - openstack/monasca-log-api
+      - openstack/murano
+      - openstack/networking-bgpvpn
+      - openstack/networking-cisco
+      - openstack/networking-fortinet
+      - openstack/networking-generic-switch
+      - openstack/networking-l2gw
+      - openstack/networking-midonet
+      - openstack/networking-plumgrid
+      - openstack/networking-sfc
+      - openstack/neutron
+      - openstack/neutron-dynamic-routing
+      - openstack/neutron-fwaas
+      - openstack/neutron-lbaas
+      - openstack/neutron-tempest-plugin
+      - openstack/neutron-vpnaas
+      - openstack/nova-lxd
+      - openstack/novajoin-tempest-plugin
+      - openstack/octavia
+      - openstack/oswin-tempest-plugin
+      - openstack/panko
+      - openstack/patrole
+      - openstack/qinling
+      - openstack/requirements
+      - openstack/sahara-tests
+      - openstack/senlin
+      - openstack/senlin-tempest-plugin
+      - openstack/tap-as-a-service
+      - openstack/tempest-horizon
+      - openstack/trio2o
+      - openstack/trove
+      - openstack/valet
+      - openstack/vitrage
+      - openstack/vmware-nsx-tempest-plugin
+      - openstack/watcher-tempest-plugin
+      - openstack/zaqar-tempest-plugin
+      - openstack/zun-tempest-plugin
+
+- project:
+    name: openstack/tempest
+    check:
+      jobs:
+        - devstack-tempest:
+            files:
+              - ^playbooks/
+              - ^roles/
+              - ^.zuul.yaml$
+        - tempest-tox-plugin-sanity-check
diff --git a/HACKING.rst b/HACKING.rst
index 8407734..a3e9c26 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -9,14 +9,14 @@
 ------------------------------
 
 - [T102] Cannot import OpenStack python clients in tempest/api &
-         tempest/scenario tests
+  tempest/scenario tests
 - [T104] Scenario tests require a services decorator
 - [T105] Tests cannot use setUpClass/tearDownClass
 - [T106] vim configuration should not be kept in source files.
 - [T107] Check that a service tag isn't in the module path
 - [T108] Check no hyphen at the end of rand_name() argument
 - [T109] Cannot use testtools.skip decorator; instead use
-         decorators.skip_because from tempest.lib
+  decorators.skip_because from tempest.lib
 - [T110] Check that service client names of GET should be consistent
 - [T111] Check that service client names of DELETE should be consistent
 - [T112] Check that tempest.lib should not import local tempest code
@@ -84,7 +84,7 @@
 It is recommended to use testtools `matcher`_ for the more tricky assertions.
 You can implement your own specific `matcher`_ as well.
 
-.. _matcher: http://testtools.readthedocs.org/en/latest/for-test-authors.html#matchers
+.. _matcher: https://testtools.readthedocs.org/en/latest/for-test-authors.html#matchers
 
 If the test case fails you can see the related logs and the information
 carried by the exception (exception class, backtrack and exception info).
@@ -121,38 +121,38 @@
 Test fixtures and resources
 ---------------------------
 Test level resources should be cleaned-up after the test execution. Clean-up
-is best scheduled using `addCleanup` which ensures that the resource cleanup
+is best scheduled using ``addCleanup`` which ensures that the resource cleanup
 code is always invoked, and in reverse order with respect to the creation
 order.
 
-Test class level resources should be defined in the `resource_setup` method of
-the test class, except for any credential obtained from the credentials
-provider, which should be set-up in the `setup_credentials` method.
-Cleanup is best scheduled using `addClassResourceCleanup` which ensures that
+Test class level resources should be defined in the ``resource_setup`` method
+of the test class, except for any credential obtained from the credentials
+provider, which should be set-up in the ``setup_credentials`` method.
+Cleanup is best scheduled using ``addClassResourceCleanup`` which ensures that
 the cleanup code is always invoked, and in reverse order with respect to the
 creation order.
 
 In both cases - test level and class level cleanups - a wait loop should be
 scheduled before the actual delete of resources with an asynchronous delete.
 
-The test base class `BaseTestCase` defines Tempest framework for class level
-fixtures. `setUpClass` and `tearDownClass` are defined here and cannot be
+The test base class ``BaseTestCase`` defines Tempest framework for class level
+fixtures. ``setUpClass`` and ``tearDownClass`` are defined here and cannot be
 overwritten by subclasses (enforced via hacking rule T105).
 
 Set-up is split in a series of steps (setup stages), which can be overwritten
 by test classes. Set-up stages are:
 
-- `skip_checks`
-- `setup_credentials`
-- `setup_clients`
-- `resource_setup`
+- ``skip_checks``
+- ``setup_credentials``
+- ``setup_clients``
+- ``resource_setup``
 
 Tear-down is also split in a series of steps (teardown stages), which are
 stacked for execution only if the corresponding setup stage had been
 reached during the setup phase. Tear-down stages are:
 
-- `clear_credentials` (defined in the base test class)
-- `resource_cleanup`
+- ``clear_credentials`` (defined in the base test class)
+- ``resource_cleanup``
 
 Skipping Tests
 --------------
@@ -178,7 +178,7 @@
 All negative tests should be based on `API-WG guideline`_ . Such negative
 tests can block any changes from accurate failure code to invalid one.
 
-.. _API-WG guideline: http://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications
+.. _API-WG guideline: https://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications
 
 If facing some gray area which is not clarified on the above guideline, propose
 a new guideline to the API-WG. With a proposal to the API-WG we will be able to
diff --git a/README.rst b/README.rst
index c67362a..242f4d5 100644
--- a/README.rst
+++ b/README.rst
@@ -2,7 +2,7 @@
 Team and repository tags
 ========================
 
-.. image:: http://governance.openstack.org/badges/tempest.svg
+.. image:: https://governance.openstack.org/badges/tempest.svg
     :target: https://governance.openstack.org/tc/reference/tags/index.html
 
 .. Change things from this point on
@@ -61,7 +61,7 @@
 #. You first need to install Tempest. This is done with pip after you check out
    the Tempest repo::
 
-    $ git clone http://git.openstack.org/openstack/tempest
+    $ git clone https://git.openstack.org/openstack/tempest
     $ pip install tempest/
 
    This can be done within a venv, but the assumption for this guide is that
@@ -133,7 +133,7 @@
 
 Release Versioning
 ------------------
-`Tempest Release Notes <http://docs.openstack.org/releasenotes/tempest>`_
+`Tempest Release Notes <https://docs.openstack.org/releasenotes/tempest>`_
 shows what changes have been released on each version.
 
 Tempest's released versions are broken into 2 sets of information. Depending on
@@ -193,6 +193,25 @@
 Alternatively, there are the py27 and py35 tox jobs which will run the unit
 tests with the corresponding version of python.
 
+One common activity is to just run a single test, you can do this with tox
+simply by specifying to just run py27 or py35 tests against a single test::
+
+    $ tox -e py27 -- -n tempest.tests.test_microversions.TestMicroversionsTestsClass.test_config_version_none_23
+
+Or all tests in the test_microversions.py file::
+
+    $ tox -e py27 -- -n tempest.tests.test_microversions
+
+You may also use regular expressions to run any matching tests::
+
+    $ tox -e py27 -- test_microversions
+
+Additionally, when running a single test, or test-file, the ``-n/--no-discover``
+argument is no longer required, however it may perform faster if included.
+
+For more information on these options and details about stestr, please see the
+`stestr documentation <http://stestr.readthedocs.io/en/latest/MANUAL.html>`_.
+
 Python 2.6
 ----------
 
diff --git a/REVIEWING.rst b/REVIEWING.rst
index 7d28320..766d0c6 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -2,7 +2,7 @@
 ======================
 
 To start read the `OpenStack Common Review Checklist
-<http://docs.openstack.org/infra/manual/developers.html#peer-review>`_
+<https://docs.openstack.org/infra/manual/developers.html#peer-review>`_
 
 
 Ensuring code is executed
@@ -16,7 +16,7 @@
 If a new test is added that depends on a new config option (like a feature
 flag), the commit message must reference a change in DevStack or DevStack-Gate
 that enables the execution of this newly introduced test. This reference could
-either be a `Cross-Repository Dependency <http://docs.openstack.org/infra/
+either be a `Cross-Repository Dependency <https://docs.openstack.org/infra/
 manual/developers.html#cross-repository-dependencies>`_ or a simple link
 to a Gerrit review.
 
@@ -121,8 +121,8 @@
 
 When to approve
 ---------------
- * Every patch needs two +2s before being approved.
- * Its ok to hold off on an approval until a subject matter expert reviews it
- * If a patch has already been approved but requires a trivial rebase to merge,
-   you do not have to wait for a second +2, since the patch has already had
-   two +2s.
+* Every patch needs two +2s before being approved.
+* Its ok to hold off on an approval until a subject matter expert reviews it
+* If a patch has already been approved but requires a trivial rebase to merge,
+  you do not have to wait for a second +2, since the patch has already had
+  two +2s.
diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess
new file mode 100644
index 0000000..7745594
--- /dev/null
+++ b/doc/source/_extra/.htaccess
@@ -0,0 +1 @@
+redirectmatch 301 ^/developer/tempest/(.*) /tempest/latest/$1
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 067eb81..0a061b8 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -154,6 +154,9 @@
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
 html_static_path = ['_static']
+# Add any paths that contain "extra" files, such as .htaccess or
+# robots.txt.
+html_extra_path = ['_extra']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 8f2865a..e5f70d2 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -17,10 +17,10 @@
 Tempest allows for configuring a set of admin credentials in the ``auth``
 section, via the following parameters:
 
- #. ``admin_username``
- #. ``admin_password``
- #. ``admin_project_name``
- #. ``admin_domain_name``
+#. ``admin_username``
+#. ``admin_password``
+#. ``admin_project_name``
+#. ``admin_domain_name``
 
 Admin credentials are not mandatory to run Tempest, but when provided they
 can be used to:
@@ -47,9 +47,9 @@
 to provide it with information about how it communicates with keystone.
 This involves configuring the following options in the ``identity`` section:
 
- - ``auth_version``
- - ``uri``
- - ``uri_v3``
+- ``auth_version``
+- ``uri``
+- ``uri_v3``
 
 The ``auth_version`` option is used to tell Tempest whether it should be using
 Keystone's v2 or v3 api for communicating with Keystone. The two uri options are
@@ -74,12 +74,12 @@
 an admin user, and an alternate user. To enable and use dynamic credentials you
 only need to configure two things:
 
- #. A set of admin credentials with permissions to create users and
-    projects. This is specified in the ``auth`` section with the
-    ``admin_username``, ``admin_project_name``, ``admin_domain_name`` and
-    ``admin_password`` options
- #. To enable dynamic credentials in the ``auth`` section with the
-    ``use_dynamic_credentials`` option.
+#. A set of admin credentials with permissions to create users and
+   projects. This is specified in the ``auth`` section with the
+   ``admin_username``, ``admin_project_name``, ``admin_domain_name`` and
+   ``admin_password`` options
+#. To enable dynamic credentials in the ``auth`` section with the
+   ``use_dynamic_credentials`` option.
 
 This is also currently the default credential provider enabled by Tempest, due
 to its common use and ease of configuration.
@@ -115,21 +115,21 @@
 
 To enable and use locking test accounts you need do a few things:
 
- #. Create an accounts.yaml file which contains the set of pre-existing
-    credentials to use for testing. To make sure you don't have a credentials
-    starvation issue when running in parallel make sure you have at least two
-    times the number of worker processes you are using to execute Tempest
-    available in the file. (If running serially the worker count is 1.)
+#. Create an accounts.yaml file which contains the set of pre-existing
+   credentials to use for testing. To make sure you don't have a credentials
+   starvation issue when running in parallel make sure you have at least two
+   times the number of worker processes you are using to execute Tempest
+   available in the file. (If running serially the worker count is 1.)
 
-    You can check the accounts.yaml.sample file packaged in Tempest for the yaml
-    format.
- #. Provide Tempest with the location of your accounts.yaml file with the
-    ``test_accounts_file`` option in the ``auth`` section
+   You can check the accounts.yaml.sample file packaged in Tempest for the yaml
+   format.
+#. Provide Tempest with the location of your accounts.yaml file with the
+   ``test_accounts_file`` option in the ``auth`` section
 
-    *NOTE: Be sure to use a full path for the file; otherwise Tempest will
-    likely not find it.*
+   *NOTE: Be sure to use a full path for the file; otherwise Tempest will
+   likely not find it.*
 
- #. Set ``use_dynamic_credentials = False`` in the ``auth`` group
+#. Set ``use_dynamic_credentials = False`` in the ``auth`` group
 
 It is worth pointing out that each set of credentials in the accounts.yaml
 should have a unique project. This is required to provide proper isolation
@@ -162,8 +162,8 @@
 can use to boot the servers with. There are two options in the Tempest config
 for doing this:
 
- #. ``flavor_ref``
- #. ``flavor_ref_alt``
+#. ``flavor_ref``
+#. ``flavor_ref_alt``
 
 Both of these options are in the ``compute`` section of the config file and take
 in the flavor id (not the name) from Nova. The ``flavor_ref`` option is what
@@ -181,8 +181,8 @@
 Just like with flavors, Tempest needs to know which images to use for booting
 servers. There are two options in the compute section just like with flavors:
 
- #. ``image_ref``
- #. ``image_ref_alt``
+#. ``image_ref``
+#. ``image_ref_alt``
 
 Both options are expecting an image id (not name) from Nova. The ``image_ref``
 option is what will be used for booting the majority of servers in Tempest.
@@ -192,13 +192,13 @@
 
 There are also options in the ``scenario`` section for images:
 
- #. ``img_file``
- #. ``img_dir``
- #. ``aki_img_file``
- #. ``ari_img_file``
- #. ``ami_img_file``
- #. ``img_container_format``
- #. ``img_disk_format``
+#. ``img_file``
+#. ``img_dir``
+#. ``aki_img_file``
+#. ``ari_img_file``
+#. ``ami_img_file``
+#. ``img_container_format``
+#. ``img_disk_format``
 
 However, unlike the other image options, these are used for a very small subset
 of scenario tests which are uploading an image. These options are used to tell
@@ -261,7 +261,7 @@
 
 To set a fixed network name simply:
 
- #. Set the ``fixed_network_name`` option in the ``compute`` group
+#. Set the ``fixed_network_name`` option in the ``compute`` group
 
 In the case that the configured fixed network name can not be found by a user
 network list call, it will be treated like one was not provided except that a
@@ -329,9 +329,9 @@
 
 To enable remote access to servers, there are 3 options at a minimum that are used:
 
- #. ``run_validation``
- #. ``connect_method``
- #. ``auth_method``
+#. ``run_validation``
+#. ``connect_method``
+#. ``auth_method``
 
 The ``run_validation`` is used to enable or disable ssh connectivity for
 all tests (with the exception of scenario tests which do not have a flag for
@@ -370,9 +370,9 @@
 service catalog. There are three options for each service section to accomplish
 this:
 
- #. ``catalog_type``
- #. ``endpoint_type``
- #. ``region``
+#. ``catalog_type``
+#. ``endpoint_type``
+#. ``region``
 
 Setting ``catalog_type`` and ``endpoint_type`` should normally give Tempest
 enough information to determine which endpoint it should pull from the service
diff --git a/doc/source/library.rst b/doc/source/library.rst
index 074d642..14415ae 100644
--- a/doc/source/library.rst
+++ b/doc/source/library.rst
@@ -44,9 +44,9 @@
 existing interfaces we have to be careful to make sure we don't break any
 external consumers. Some common red flags are:
 
- * a change to an existing API requires a change outside the library directory
-   where the interface is being consumed
- * a unit test has to be significantly changed to make the proposed change pass
+* a change to an existing API requires a change outside the library directory
+  where the interface is being consumed
+* a unit test has to be significantly changed to make the proposed change pass
 
 Testing
 '''''''
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index acf5593..7189312 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -33,46 +33,46 @@
 Tempest will cover only integration testing of applicable microversions with
 below exceptions:
 
-  #. Test covers a feature which is important for interoperability. This covers tests requirement
-     from Defcore.
-  #. Test needed to fill Schema gaps.
-     Tempest validates API responses with defined JSON schema. API responses can be different on
-     each microversion and the JSON schemas need to be defined separately for the microversion.
-     While implementing new integration tests for a specific microversion, there
-     may be a gap in the JSON schemas (caused by previous microversions) implemented
-     in Tempest.
-     Filling that gap while implementing the new integration test cases is not efficient due to
-     many reasons:
+#. Test covers a feature which is important for interoperability. This covers tests requirement
+   from Defcore.
+#. Test needed to fill Schema gaps.
+   Tempest validates API responses with defined JSON schema. API responses can be different on
+   each microversion and the JSON schemas need to be defined separately for the microversion.
+   While implementing new integration tests for a specific microversion, there
+   may be a gap in the JSON schemas (caused by previous microversions) implemented
+   in Tempest.
+   Filling that gap while implementing the new integration test cases is not efficient due to
+   many reasons:
 
-     * Hard to review
-     * Sync between multiple integration tests patches which try to fill the same schema gap at same
-       time
-     * Might delay the microversion change on project side where project team wants Tempest
-       tests to verify the results.
+   * Hard to review
+   * Sync between multiple integration tests patches which try to fill the same schema gap at same
+     time
+   * Might delay the microversion change on project side where project team wants Tempest
+     tests to verify the results.
 
-     Tempest will allow to fill the schema gaps at the end of each cycle, or more
-     often if required.
-     Schema gap can be filled with testing those with a minimal set of tests. Those
-     tests might not be integration tests and might be already covered on project
-     side also.
-     This exception is needed because:
+   Tempest will allow to fill the schema gaps at the end of each cycle, or more
+   often if required.
+   Schema gap can be filled with testing those with a minimal set of tests. Those
+   tests might not be integration tests and might be already covered on project
+   side also.
+   This exception is needed because:
 
-     * Allow to create microversion response schema in Tempest at the same time that projects are
-       implementing their API microversions. This will make implementation easier for adding
-       required tests before a new microversion change can be merged in the corresponding project
-       and hence accelerate the development of microversions.
-     * New schema must be verified by at least one test case which exercises such schema.
+   * Allow to create microversion response schema in Tempest at the same time that projects are
+     implementing their API microversions. This will make implementation easier for adding
+     required tests before a new microversion change can be merged in the corresponding project
+     and hence accelerate the development of microversions.
+   * New schema must be verified by at least one test case which exercises such schema.
 
-     For example:
-        If any projects implemented 4 API microversion say- v2.3, v2.4, v2.5, v2.6
-        Assume microversion v2.3, v2.4, v2.6 change the API Response which means Tempest
-        needs to add JSON schema for v2.3, v2.4, v2.6.
-        In that case if only 1 or 2 tests can verify all new schemas then we do not need
-        separate tests for each new schemas. In worst case, we have to add 3 separate tests.
-  #. Test covers service behavior at large scale with involvement of more deep layer like hypervisor
-     etc not just API/DB layer. This type of tests will be added case by case basis and
-     with project team consultation about why it cannot be covered on project side and worth to test
-     in Tempest.
+   For example:
+      If any projects implemented 4 API microversion say- v2.3, v2.4, v2.5, v2.6
+      Assume microversion v2.3, v2.4, v2.6 change the API Response which means Tempest
+      needs to add JSON schema for v2.3, v2.4, v2.6.
+      In that case if only 1 or 2 tests can verify all new schemas then we do not need
+      separate tests for each new schemas. In worst case, we have to add 3 separate tests.
+#. Test covers service behavior at large scale with involvement of more deep layer like hypervisor
+   etc not just API/DB layer. This type of tests will be added case by case basis and
+   with project team consultation about why it cannot be covered on project side and worth to test
+   in Tempest.
 
 Project Scope For Microversion Testing
 """"""""""""""""""""""""""""""""""""""
@@ -294,72 +294,72 @@
 
 * Compute
 
- * `2.1`_
+  * `2.1`_
 
- .. _2.1:  https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id1
+  .. _2.1:  https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id1
 
- * `2.2`_
+  * `2.2`_
 
- .. _2.2: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2
+  .. _2.2: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2
 
- * `2.10`_
+  * `2.10`_
 
- .. _2.10: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9
+  .. _2.10: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9
 
- * `2.20`_
+  * `2.20`_
 
- .. _2.20: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18
+  .. _2.20: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18
 
- * `2.25`_
+  * `2.25`_
 
- .. _2.25: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka
+  .. _2.25: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka
 
- * `2.32`_
+  * `2.32`_
 
- .. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
+  .. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
 
- * `2.37`_
+  * `2.37`_
 
- .. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34
+  .. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34
 
- * `2.42`_
+  * `2.42`_
 
- .. _2.42: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-ocata
+  .. _2.42: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-ocata
 
- * `2.47`_
+  * `2.47`_
 
- .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id42
+  .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id42
 
- * `2.48`_
+  * `2.48`_
 
- .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
+  .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
 
 * Volume
 
- * `3.3`_
+  * `3.3`_
 
- .. _3.3:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id3
+  .. _3.3:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id3
 
- * `3.9`_
+  * `3.9`_
 
- .. _3.9:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id9
+  .. _3.9:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id9
 
- * `3.11`_
+  * `3.11`_
 
- .. _3.11:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id11
+  .. _3.11:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id11
 
- * `3.12`_
+  * `3.12`_
 
- .. _3.12:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id12
+  .. _3.12:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id12
 
- * `3.14`_
+  * `3.14`_
 
- .. _3.14:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id14
+  .. _3.14:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id14
 
- * `3.19`_
+  * `3.19`_
 
- .. _3.19:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id18
+  .. _3.19:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id18
 
- * `3.20`_
+  * `3.20`_
 
- .. _3.20:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id19
+  .. _3.20:  https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id19
diff --git a/doc/source/test_removal.rst b/doc/source/test_removal.rst
index 07c3046..ddae6e2 100644
--- a/doc/source/test_removal.rst
+++ b/doc/source/test_removal.rst
@@ -29,19 +29,19 @@
 
 In the proposal etherpad we'll be looking for answers to 3 questions
 
- #. The tests proposed for removal must have equiv. coverage in a different
-    project's test suite (whether this is another gating test project, or an in
-    tree functional test suite). For API tests preferably the other project will
-    have a similar source of friction in place to prevent breaking api changes
-    so that we don't regress and let breaking api changes slip through the
-    gate.
- #. The test proposed for removal has a failure rate <  0.50% in the gate over
-    the past release (the value and interval will likely be adjusted in the
-    future)
+#. The tests proposed for removal must have equiv. coverage in a different
+   project's test suite (whether this is another gating test project, or an in
+   tree functional test suite). For API tests preferably the other project will
+   have a similar source of friction in place to prevent breaking api changes
+   so that we don't regress and let breaking api changes slip through the
+   gate.
+#. The test proposed for removal has a failure rate <  0.50% in the gate over
+   the past release (the value and interval will likely be adjusted in the
+   future)
 
-    .. _`prong #3`:
- #. There must not be an external user/consumer of tempest
-    that depends on the test proposed for removal
+   .. _`prong #3`:
+#. There must not be an external user/consumer of tempest
+   that depends on the test proposed for removal
 
 The answers to 1 and 2 are easy to verify. For 1 just provide a link to the new
 test location. If you are linking to the tempest removal patch please also put
@@ -62,29 +62,34 @@
 The Old Way using subunit2sql directly
 """"""""""""""""""""""""""""""""""""""
 
-SELECT * from tests where test_id like "%test_id%";
-(where $test_id is the full test_id, but truncated to the class because of
-setUpClass or tearDownClass failures)
+``SELECT * from tests where test_id like "%test_id%";``
+(where ``$test_id`` is the full test_id, but truncated to the class because of
+``setUpClass`` or ``tearDownClass`` failures)
 
 You can access the infra mysql subunit2sql db w/ read-only permissions with:
 
- * hostname: logstash.openstack.org
- * username: query
- * password: query
- * db_name: subunit2sql
+* hostname: logstash.openstack.org
+* username: query
+* password: query
+* db_name: subunit2sql
 
 For example if you were trying to remove the test with the id:
-tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON.test_get_flavor_details_for_deleted_flavor
+``tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON.test_get_flavor_details_for_deleted_flavor``
 you would run the following:
 
- #. run: "mysql -u query -p -h logstash.openstack.org subunit2sql" to connect
-    to the subunit2sql db
- #. run the query: MySQL [subunit2sql]> select * from tests where test_id like
-    "tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON%";
-    which will return a table of all the tests in the class (but it will also
-    catch failures in setUpClass and tearDownClass)
- #. paste the output table with numbers and the mysql command you ran to
-    generate it into the etherpad.
+#. run the command: ``mysql -u query -p -h logstash.openstack.org subunit2sql``
+   to connect to the subunit2sql db
+#. run the query:
+
+   .. code-block:: console
+
+       MySQL [subunit2sql]> select * from tests where test_id like \
+       "tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON%";
+
+   which will return a table of all the tests in the class (but it will also
+   catch failures in ``setUpClass`` and ``tearDownClass``)
+#. paste the output table with numbers and the mysql command you ran to
+   generate it into the etherpad.
 
 Eventually a cli interface will be created to make that a bit more friendly.
 Also a dashboard is in the works so we don't need to manually run the command.
@@ -131,23 +136,23 @@
 For the most part all tempest test removals have to go through this procedure
 there are a couple of exceptions though:
 
- #. The class of testing has been decided to be outside the scope of tempest.
- #. A revert for a patch which added a broken test, or testing which didn't
-    actually run in the gate (basically any revert for something which
-    shouldn't have been added)
- #. Tests that would become out of scope as a consequence of an API change,
-    as described in `API Compatibility`_.
-    Such tests cannot live in Tempest because of the branchless nature of
-    Tempest. Such test must still honor `prong #3`_.
+#. The class of testing has been decided to be outside the scope of tempest.
+#. A revert for a patch which added a broken test, or testing which didn't
+   actually run in the gate (basically any revert for something which
+   shouldn't have been added)
+#. Tests that would become out of scope as a consequence of an API change,
+   as described in `API Compatibility`_.
+   Such tests cannot live in Tempest because of the branchless nature of
+   Tempest. Such test must still honor `prong #3`_.
 
 For the first exception type the only types of testing in tree which have been
 declared out of scope at this point are:
 
- * The CLI tests (which should be completely removed at this point)
- * Neutron Adv. Services testing (which should be completely removed at this
-   point)
- * XML API Tests (which should be completely removed at this point)
- * EC2 API/boto tests (which should be completely removed at this point)
+* The CLI tests (which should be completely removed at this point)
+* Neutron Adv. Services testing (which should be completely removed at this
+  point)
+* XML API Tests (which should be completely removed at this point)
+* EC2 API/boto tests (which should be completely removed at this point)
 
 For tests that fit into this category the only criteria for removal is that
 there is equivalent testing elsewhere.
@@ -159,12 +164,12 @@
 are defined as in scope for direct testing in tempest. As of today that list
 is:
 
- * Keystone
- * Nova
- * Glance
- * Cinder
- * Neutron
- * Swift
+* Keystone
+* Nova
+* Glance
+* Cinder
+* Neutron
+* Swift
 
 anything that lives in tempest which doesn't test one of these projects can be
 removed assuming there is equivalent testing elsewhere. Preferably using the
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
index 5a2876e..49af95a 100644
--- a/doc/source/write_tests.rst
+++ b/doc/source/write_tests.rst
@@ -36,12 +36,12 @@
 In standard unittest the lifecycle of a TestCase can be described in the
 following phases:
 
- #. setUpClass
- #. setUp
- #. Test Execution
- #. tearDown
- #. doCleanups
- #. tearDownClass
+#. setUpClass
+#. setUp
+#. Test Execution
+#. tearDown
+#. doCleanups
+#. tearDownClass
 
 setUpClass
 ----------
@@ -54,10 +54,10 @@
 To accomplish this you do **not** define a setUpClass function, instead there
 are a number of predefined phases to setUpClass that are used. The phases are:
 
- * skip_checks
- * setup_credentials
- * setup_clients
- * resource_setup
+* skip_checks
+* setup_credentials
+* setup_clients
+* resource_setup
 
 which is executed in that order. Cleanup of resources provisioned during
 the resource_setup must be scheduled right after provisioning using
@@ -65,7 +65,7 @@
 are executed in reverse order during tearDownClass, before the cleanup of
 test credentials takes place. An example of a TestCase which defines all
 of these would be::
-  
+
   from tempest.common import waiters
   from tempest import config
   from tempest.lib.common.utils import test_utils
diff --git a/playbooks/devstack-tempest.yaml b/playbooks/devstack-tempest.yaml
new file mode 100644
index 0000000..a684984
--- /dev/null
+++ b/playbooks/devstack-tempest.yaml
@@ -0,0 +1,14 @@
+# Changes that run through devstack-tempest are likely to have an impact on
+# the devstack part of the job, so we keep devstack in the main play to
+# avoid zuul retrying on legitimate failures.
+- hosts: all
+  roles:
+    - run-devstack
+
+# We run tests only on one node, regardless how many nodes are in the system
+- hosts: tempest
+  roles:
+    - setup-tempest-run-dir
+    - setup-tempest-data-dir
+    - acl-devstack-files
+    - run-tempest
diff --git a/playbooks/post-tempest.yaml b/playbooks/post-tempest.yaml
new file mode 100644
index 0000000..820e4f6
--- /dev/null
+++ b/playbooks/post-tempest.yaml
@@ -0,0 +1,24 @@
+- 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: 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/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml b/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
new file mode 100644
index 0000000..e23abe3
--- /dev/null
+++ b/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
@@ -0,0 +1,5 @@
+---
+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.
diff --git a/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml b/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml
new file mode 100644
index 0000000..621731d
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+  - |
+    Remove the deprecated decorator ``skip_unless_attr`` in lib/decorators.py.
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index ae3dca1..57ec7e1 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -65,16 +65,12 @@
 project = u'tempest Release Notes'
 copyright = u'2016, tempest Developers'
 
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-from tempest.version import version_info as tempest_version
+# Release do not need a version number in the title, they
+# cover multiple versions.
 # The full version, including alpha/beta/rc tags.
-release = tempest_version.version_string_with_vcs()
+release = ''
 # The short X.Y version.
-version = tempest_version.canonical_version_string()
+version = ''
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/requirements.txt b/requirements.txt
index 4b8de27..2300214 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,19 +4,19 @@
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
 cliff!=2.9.0,>=2.8.0 # Apache-2.0
 jsonschema<3.0.0,>=2.6.0 # MIT
-testtools>=1.4.0 # MIT
+testtools>=2.2.0 # MIT
 paramiko>=2.0.0 # LGPLv2.1+
 netaddr>=0.7.18 # BSD
 testrepository>=0.0.18 # Apache-2.0/BSD
 oslo.concurrency>=3.20.0 # Apache-2.0
-oslo.config>=4.6.0 # Apache-2.0
+oslo.config>=5.1.0 # Apache-2.0
 oslo.log>=3.30.0 # Apache-2.0
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
 oslo.utils>=3.31.0 # Apache-2.0
-six>=1.9.0 # MIT
+six>=1.10.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
 PyYAML>=3.10 # MIT
-python-subunit>=0.0.18 # Apache-2.0/BSD
+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
diff --git a/roles/acl-devstack-files/README.rst b/roles/acl-devstack-files/README.rst
new file mode 100644
index 0000000..76e7e58
--- /dev/null
+++ b/roles/acl-devstack-files/README.rst
@@ -0,0 +1,10 @@
+Grant global read access to devstack `files` folder.
+
+This is handy to grant the `tempest` user access to VM images for testing.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_data_dir
+   :default: /opt/stack/data
+
+   The devstack data directory.
diff --git a/roles/acl-devstack-files/defaults/main.yaml b/roles/acl-devstack-files/defaults/main.yaml
new file mode 100644
index 0000000..14265f0
--- /dev/null
+++ b/roles/acl-devstack-files/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_data_dir: /opt/stack/data
diff --git a/roles/acl-devstack-files/tasks/main.yaml b/roles/acl-devstack-files/tasks/main.yaml
new file mode 100644
index 0000000..b3eeec7
--- /dev/null
+++ b/roles/acl-devstack-files/tasks/main.yaml
@@ -0,0 +1,6 @@
+- name: Grant global read access to devstack files
+  file:
+    path: "{{devstack_data_dir}}/files"
+    mode: "o+rx"
+    recurse: yes
+  become: yes
diff --git a/roles/process-stackviz/README.rst b/roles/process-stackviz/README.rst
new file mode 100644
index 0000000..b05326d
--- /dev/null
+++ b/roles/process-stackviz/README.rst
@@ -0,0 +1,22 @@
+Generate stackviz report.
+
+Generate stackviz report using subunit and dstat data, using
+the stackviz archive embedded in test images.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: stage_dir
+   :default: /opt/stack/logs
+
+   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
+
+   The name of the subunit file to be used as input.
diff --git a/roles/process-stackviz/defaults/main.yaml b/roles/process-stackviz/defaults/main.yaml
new file mode 100644
index 0000000..b1eb8d9
--- /dev/null
+++ b/roles/process-stackviz/defaults/main.yaml
@@ -0,0 +1,3 @@
+devstack_base_dir: /opt/stack
+stage_dir: /opt/stack/
+test_results_stage_name: test_results
diff --git a/roles/process-stackviz/tasks/main.yaml b/roles/process-stackviz/tasks/main.yaml
new file mode 100644
index 0000000..09de606
--- /dev/null
+++ b/roles/process-stackviz/tasks/main.yaml
@@ -0,0 +1,63 @@
+- name: Check if stackviz archive exists
+  stat:
+    path: "/opt/cache/files/stackviz-latest.tar.gz"
+  register: stackviz_archive
+
+- debug:
+    msg: "Stackviz archive could not be found in /opt/cache/files/stackviz-latest.tar.gz"
+  when: not stackviz_archive.stat.exists
+
+- name: Check if subunit data exists
+  stat:
+    path: "{{ stage_dir }}/{{ test_results_stage_name }}.subunit"
+  register: subunit_input
+
+- debug:
+    msg: "Subunit file could not be found at {{ stage_dir }}/{{ test_results_stage_name }}.subunit"
+  when: not subunit_input.stat.exists
+
+- name: Install stackviz
+  pip:
+    name: "file://{{ stackviz_archive.stat.path }}"
+    virtualenv: /tmp/stackviz
+    extra_args: -U
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- name: Deploy stackviz static html+js
+  command: cp -pR /tmp/stackviz/share/stackviz-html {{ stage_dir }}/stackviz
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- name: Check if dstat data exists
+  stat:
+    path: "{{ devstack_base_dir }}/logs/dstat-csv.log"
+  register: dstat_input
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- name: Run stackviz with dstat
+  shell: |
+    cat {{ subunit_input.stat.path }} | \
+      /tmp/stackviz/bin/stackviz-export \
+        --dstat "{{ devstack_base_dir }}/logs/dstat-csv.log" \
+        --env --stdin \
+        {{ stage_dir }}/stackviz/data
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+    - dstat_input.stat.exists
+
+- name: Run stackviz without dstat
+  shell: |
+    cat {{ subunit_input.stat.path }} | \
+      /tmp/stackviz/bin/stackviz-export \
+        --env --stdin \
+        {{ stage_dir }}/stackviz/data
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+    - not dstat_input.stat.exists
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
new file mode 100644
index 0000000..001586e
--- /dev/null
+++ b/roles/run-tempest/README.rst
@@ -0,0 +1,25 @@
+Run Tempest
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: tempest_concurrency
+   :default: 0
+
+   The number of parallel test processes.
+
+.. zuul:rolevar:: tempest_test_regex
+   :default: ''
+
+   A regular expression used to select the tests.
+   It works only when used with some specific tox environments
+   ('all', 'all-plugin'.)
+
+.. zuul:rolevar:: tox_venvlist
+   :default: smoke
+
+   The Tempest tox environment to run.
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
new file mode 100644
index 0000000..3e57511
--- /dev/null
+++ b/roles/run-tempest/defaults/main.yaml
@@ -0,0 +1,3 @@
+devstack_base_dir: /opt/stack
+tempest_test_regex: ''
+tox_venvlist: smoke
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
new file mode 100644
index 0000000..297cd72
--- /dev/null
+++ b/roles/run-tempest/tasks/main.yaml
@@ -0,0 +1,28 @@
+# NOTE(andreaf) The number of vcpus is not available on all systems.
+# See https://github.com/ansible/ansible/issues/30688
+# When not available, we fall back to ansible_processor_cores
+- name: Get hw.logicalcpu from sysctl
+  shell: sysctl hw.logicalcpu | cut -d' ' -f2
+  register: sysctl_hw_logicalcpu
+  when: ansible_processor_vcpus is not defined
+
+- name: Number of cores
+  set_fact:
+    num_cores: "{{ansible_processor_vcpus|default(sysctl_hw_logicalcpu.stdout)}}"
+
+- name: Set concurrency for cores == 3 or less
+  set_fact:
+    default_concurrency: "{{ num_cores }}"
+  when: num_cores|int <= 3
+
+- name: Limit max concurrency when more than 3 vcpus are available
+  set_fact:
+    default_concurrency: "{{ num_cores|int // 2 }}"
+  when: num_cores|int > 3
+
+- name: Run Tempest
+  command: tox -e {{tox_venvlist}} -- {{tempest_test_regex|quote}} --concurrency={{tempest_concurrency|default(default_concurrency)}}
+  args:
+    chdir: "{{devstack_base_dir}}/tempest"
+  become: true
+  become_user: tempest
diff --git a/roles/setup-tempest-data-dir/README.rst b/roles/setup-tempest-data-dir/README.rst
new file mode 100644
index 0000000..db0b083
--- /dev/null
+++ b/roles/setup-tempest-data-dir/README.rst
@@ -0,0 +1,12 @@
+Setup the `tempest` user as owner of Tempest's data folder.
+
+Tempest's devstack plugin creates the data folder, but it has no knowledge
+of the `tempest` user, so we need a role to fix ownership on the data folder.
+
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_data_dir
+   :default: /opt/stack/data
+
+   The devstack data directory.
diff --git a/roles/setup-tempest-data-dir/defaults/main.yaml b/roles/setup-tempest-data-dir/defaults/main.yaml
new file mode 100644
index 0000000..14265f0
--- /dev/null
+++ b/roles/setup-tempest-data-dir/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_data_dir: /opt/stack/data
diff --git a/roles/setup-tempest-data-dir/tasks/main.yaml b/roles/setup-tempest-data-dir/tasks/main.yaml
new file mode 100644
index 0000000..9dd6309
--- /dev/null
+++ b/roles/setup-tempest-data-dir/tasks/main.yaml
@@ -0,0 +1,7 @@
+- name: Set tempest as owner of Tempest data folder
+  file:
+    path: "{{devstack_data_dir}}/tempest"
+    owner: tempest
+    group: stack
+    recurse: yes
+  become: yes
diff --git a/roles/setup-tempest-run-dir/README.rst b/roles/setup-tempest-run-dir/README.rst
new file mode 100644
index 0000000..c8e2339
--- /dev/null
+++ b/roles/setup-tempest-run-dir/README.rst
@@ -0,0 +1,14 @@
+Setup Tempest run folder.
+
+To support isolation between multiple runs, separate run folders are required.
+Set `tempest` as owner of Tempest's current run folder.
+There is an implicit assumption here of a one to one relationship between
+devstack versions and Tempest runs.
+
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
diff --git a/roles/setup-tempest-run-dir/defaults/main.yaml b/roles/setup-tempest-run-dir/defaults/main.yaml
new file mode 100644
index 0000000..fea05c8
--- /dev/null
+++ b/roles/setup-tempest-run-dir/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_base_dir: /opt/stack
diff --git a/roles/setup-tempest-run-dir/tasks/main.yaml b/roles/setup-tempest-run-dir/tasks/main.yaml
new file mode 100644
index 0000000..a012d72
--- /dev/null
+++ b/roles/setup-tempest-run-dir/tasks/main.yaml
@@ -0,0 +1,7 @@
+- name: Set tempest as owner of Tempest run folder
+  file:
+    path: "{{devstack_base_dir}}/tempest"
+    owner: tempest
+    group: stack
+    recurse: yes
+  become: yes
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 705814c..9ee8858 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -434,7 +434,7 @@
             # the compute API will return a 400 response.
             if volume['status'] == 'in-use':
                 self.servers_client.detach_volume(server['id'], volume['id'])
-        except exceptions.NotFound:
+        except lib_exc.NotFound:
             # Ignore 404s on detach in case the server is deleted or the volume
             # is already detached.
             pass
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 0248c65..0e8f681 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -78,8 +78,11 @@
 
         return port
 
-    def _check_interface(self, iface, port_id=None, network_id=None,
-                         fixed_ip=None, mac_addr=None):
+    def _check_interface(self, iface, server_id=None, port_id=None,
+                         network_id=None, fixed_ip=None, mac_addr=None):
+        if server_id:
+            iface = waiters.wait_for_interface_status(
+                self.interfaces_client, server_id, iface['port_id'], 'ACTIVE')
         if port_id:
             self.assertEqual(iface['port_id'], port_id)
         if network_id:
@@ -109,9 +112,8 @@
         network_id = ifs[0]['net_id']
         iface = self.interfaces_client.create_interface(
             server['id'], net_id=network_id)['interfaceAttachment']
-        iface = waiters.wait_for_interface_status(
-            self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
-        self._check_interface(iface, network_id=network_id)
+        self._check_interface(iface, server_id=server['id'],
+                              network_id=network_id)
         return iface
 
     def _test_create_interface_by_port_id(self, server, ifs):
@@ -121,9 +123,8 @@
         self.addCleanup(self.ports_client.delete_port, port_id)
         iface = self.interfaces_client.create_interface(
             server['id'], port_id=port_id)['interfaceAttachment']
-        iface = waiters.wait_for_interface_status(
-            self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
-        self._check_interface(iface, port_id=port_id)
+        self._check_interface(iface, server_id=server['id'], port_id=port_id,
+                              network_id=network_id)
         return iface
 
     def _test_create_interface_by_fixed_ips(self, server, ifs):
@@ -140,9 +141,8 @@
             server['id'], net_id=network_id,
             fixed_ips=fixed_ips)['interfaceAttachment']
         self.addCleanup(self.ports_client.delete_port, iface['port_id'])
-        iface = waiters.wait_for_interface_status(
-            self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
-        self._check_interface(iface, fixed_ip=ip_list[0])
+        self._check_interface(iface, server_id=server['id'],
+                              fixed_ip=ip_list[0])
         return iface
 
     def _test_show_interface(self, server, ifs):
@@ -271,7 +271,8 @@
             # attach the port to the server
             iface = self.interfaces_client.create_interface(
                 server['id'], port_id=port_id)['interfaceAttachment']
-            self._check_interface(iface, port_id=port_id)
+            self._check_interface(iface, server_id=server['id'],
+                                  port_id=port_id)
 
             # detach the port from the server; this is a cast in the compute
             # API so we have to poll the port until the device_id is unset.
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index bce7524..e2be249 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -326,10 +326,22 @@
         server = self.create_test_server(
             volume_backed=True, wait_until='ACTIVE')
         self._test_resize_server_confirm(server['id'])
-        # Now do something interactive with the guest like get its console
-        # output; we don't actually care about the output, just that it doesn't
-        # raise an error.
-        self.client.get_console_output(server['id'])
+        if CONF.compute_feature_enabled.console_output:
+            # Now do something interactive with the guest like get its console
+            # output; we don't actually care about the output,
+            # just that it doesn't raise an error.
+            self.client.get_console_output(server['id'])
+        if CONF.validation.run_validation:
+            validation_resources = self.get_class_validation_resources(
+                self.os_primary)
+            linux_client = remote_client.RemoteClient(
+                self.get_server_ip(server, validation_resources),
+                self.ssh_user,
+                password=None,
+                pkey=validation_resources['keypair']['private_key'],
+                server=server,
+                servers_client=self.client)
+            linux_client.validate_authentication()
 
     @decorators.idempotent_id('138b131d-66df-48c9-a171-64f45eb92962')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
diff --git a/tempest/api/identity/admin/v2/test_roles.py b/tempest/api/identity/admin/v2/test_roles.py
index 124bb5f..9736a76 100644
--- a/tempest/api/identity/admin/v2/test_roles.py
+++ b/tempest/api/identity/admin/v2/test_roles.py
@@ -28,14 +28,11 @@
         for _ in range(5):
             role_name = data_utils.rand_name(name='role')
             role = cls.roles_client.create_role(name=role_name)['role']
+            cls.addClassResourceCleanup(
+                test_utils.call_and_ignore_notfound_exc,
+                cls.roles_client.delete_role, role['id'])
             cls.roles.append(role)
 
-    @classmethod
-    def resource_cleanup(cls):
-        super(RolesTestJSON, cls).resource_cleanup()
-        for role in cls.roles:
-            cls.roles_client.delete_role(role['id'])
-
     def _get_role_params(self):
         user = self.setup_test_user()
         tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant']
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index bf04ede..ca6b03e 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -34,19 +34,6 @@
             domain = cls.create_domain(enabled=i < 2)
             cls.setup_domains.append(domain)
 
-    @classmethod
-    def resource_cleanup(cls):
-        for domain in cls.setup_domains:
-            cls._delete_domain(domain['id'])
-        super(DomainsTestJSON, cls).resource_cleanup()
-
-    @classmethod
-    def _delete_domain(cls, domain_id):
-        # It is necessary to disable the domain before deleting,
-        # or else it would result in unauthorized error
-        cls.domains_client.update_domain(domain_id, enabled=False)
-        cls.domains_client.delete_domain(domain_id)
-
     @decorators.idempotent_id('8cf516ef-2114-48f1-907b-d32726c734d4')
     def test_list_domains(self):
         # Test to list domains
@@ -92,7 +79,7 @@
         domain = self.domains_client.create_domain(
             name=d_name, description=d_desc)['domain']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self._delete_domain, domain['id'])
+                        self.delete_domain, domain['id'])
         self.assertIn('description', domain)
         self.assertIn('name', domain)
         self.assertIn('enabled', domain)
@@ -145,7 +132,7 @@
         # Create domain only with name
         d_name = data_utils.rand_name('domain')
         domain = self.domains_client.create_domain(name=d_name)['domain']
-        self.addCleanup(self._delete_domain, domain['id'])
+        self.addCleanup(self.delete_domain, domain['id'])
         expected_data = {'name': d_name, 'enabled': True}
         self.assertEqual('', domain['description'])
         self.assertDictContainsSubset(expected_data, domain)
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 17db3ea..507810b 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -28,13 +28,6 @@
         super(GroupsV3TestJSON, cls).resource_setup()
         cls.domain = cls.create_domain()
 
-    @classmethod
-    def resource_cleanup(cls):
-        # Cleanup the domains created in the setup
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
-        super(GroupsV3TestJSON, cls).resource_cleanup()
-
     @decorators.idempotent_id('2e80343b-6c81-4ac3-88c7-452f3e9d5129')
     def test_group_create_update_get(self):
         name = data_utils.rand_name('Group')
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index 8b687cd..c0c79b9 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -49,8 +49,6 @@
         cls.groups_client.delete_group(cls.group['id'])
         cls.users_client.delete_user(cls.user['id'])
         cls.projects_client.delete_project(cls.project['id'])
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
         super(InheritsV3TestJSON, cls).resource_cleanup()
 
     def _list_assertions(self, body, fetched_role_ids, role_id):
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 7e70c14..82664e8 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -27,35 +27,27 @@
         # Create a domain
         cls.domain = cls.create_domain()
         # Create project with domain
-        cls.projects = list()
         cls.p1_name = data_utils.rand_name('project')
         cls.p1 = cls.projects_client.create_project(
             cls.p1_name, enabled=False,
             domain_id=cls.domain['id'])['project']
-        cls.projects.append(cls.p1)
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.p1['id'])
         cls.project_ids.append(cls.p1['id'])
         # Create default project
         p2_name = data_utils.rand_name('project')
         cls.p2 = cls.projects_client.create_project(p2_name)['project']
-        cls.projects.append(cls.p2)
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.p2['id'])
         cls.project_ids.append(cls.p2['id'])
         # Create a new project (p3) using p2 as parent project
         p3_name = data_utils.rand_name('project')
         cls.p3 = cls.projects_client.create_project(
             p3_name, parent_id=cls.p2['id'])['project']
-        cls.projects.append(cls.p3)
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.p3['id'])
         cls.project_ids.append(cls.p3['id'])
 
-    @classmethod
-    def resource_cleanup(cls):
-        # Cleanup the projects created during setup in inverse order
-        for project in reversed(cls.projects):
-            cls.projects_client.delete_project(project['id'])
-        # Cleanup the domain created during setup
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
-        super(ListProjectsTestJSON, cls).resource_cleanup()
-
     @decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
     def test_list_projects(self):
         # List projects
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index 506c729..88cd8be 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -60,9 +60,6 @@
         # Cleanup the users created during setup
         for user in cls.users:
             cls.users_client.delete_user(user['id'])
-        # Cleanup the domain created during setup
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
         super(UsersV3TestJSON, cls).resource_cleanup()
 
     @decorators.idempotent_id('08f9aabb-dcfe-41d0-8172-82b5fa0bd73d')
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index ec904e6..e7b005c 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -58,10 +58,6 @@
         cls.groups_client.delete_group(cls.group_body['id'])
         cls.users_client.delete_user(cls.user_body['id'])
         cls.projects_client.delete_project(cls.project['id'])
-        # NOTE(harika-vakadi): It is necessary to disable the domain
-        # before deleting,or else it would result in unauthorized error
-        cls.domains_client.update_domain(cls.domain['id'], enabled=False)
-        cls.domains_client.delete_domain(cls.domain['id'])
         for role in cls.roles:
             cls.roles_client.delete_role(role['id'])
         super(RolesV3TestJSON, cls).resource_cleanup()
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 30d2a36..9edccbb 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -249,13 +249,16 @@
         if 'description' not in kwargs:
             kwargs['description'] = data_utils.rand_name('desc')
         domain = cls.domains_client.create_domain(**kwargs)['domain']
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.delete_domain, domain['id'])
         return domain
 
-    def delete_domain(self, domain_id):
+    @classmethod
+    def delete_domain(cls, domain_id):
         # NOTE(mpavlase) It is necessary to disable the domain before deleting
         # otherwise it raises Forbidden exception
-        self.domains_client.update_domain(domain_id, enabled=False)
-        self.domains_client.delete_domain(domain_id)
+        cls.domains_client.update_domain(domain_id, enabled=False)
+        cls.domains_client.delete_domain(domain_id)
 
     def setup_test_user(self, password=None):
         """Set up a test user."""
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 70ba2fe..7103d56 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -46,16 +46,6 @@
         cls.created_images = []
 
     @classmethod
-    def resource_cleanup(cls):
-        for image_id in cls.created_images:
-            test_utils.call_and_ignore_notfound_exc(
-                cls.client.delete_image, image_id)
-
-        for image_id in cls.created_images:
-                cls.client.wait_for_resource_deletion(image_id)
-        super(BaseImageTest, cls).resource_cleanup()
-
-    @classmethod
     def create_image(cls, data=None, **kwargs):
         """Wrapper that returns a test image."""
 
@@ -75,6 +65,10 @@
         if 'image' in image:
             image = image['image']
         cls.created_images.append(image['id'])
+        cls.addClassResourceCleanup(cls.client.wait_for_resource_deletion,
+                                    image['id'])
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.client.delete_image, image['id'])
         return image
 
     @classmethod
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index c2a67e3..bdfda0a 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -92,7 +92,6 @@
         cls.subnets = []
         cls.ports = []
         cls.routers = []
-        cls.floating_ips = []
         cls.ethertype = "IPv" + str(cls._ip_version)
         if cls._ip_version == 4:
             cls.cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
@@ -104,11 +103,6 @@
     @classmethod
     def resource_cleanup(cls):
         if CONF.service_available.neutron:
-            # Clean up floating IPs
-            for floating_ip in cls.floating_ips:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.floating_ips_client.delete_floatingip,
-                    floating_ip['id'])
             # Clean up ports
             for port in cls.ports:
                 test_utils.call_and_ignore_notfound_exc(
@@ -222,7 +216,9 @@
         body = cls.floating_ips_client.create_floatingip(
             floating_network_id=external_network_id)
         fip = body['floatingip']
-        cls.floating_ips.append(fip)
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.floating_ips_client.delete_floatingip,
+                                    fip['id'])
         return fip
 
     @classmethod
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index de28a30..b73bdf2 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -18,6 +18,7 @@
 import testtools
 
 from tempest.api.volume import base
+from tempest.common import utils
 from tempest.common import waiters
 from tempest import config
 from tempest.lib import decorators
@@ -104,6 +105,7 @@
     @decorators.idempotent_id('301f5a30-1c6f-4ea0-be1a-91fd28d44354')
     @testtools.skipUnless(CONF.volume_feature_enabled.extend_attached_volume,
                           "Attached volume extend is disabled.")
+    @utils.services('compute')
     def test_extend_attached_volume(self):
         """This is a happy path test which does the following:
 
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 8636405..92eae02 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -15,18 +15,18 @@
 #    under the License.
 
 """
-Utility for creating **accounts.yaml** file for concurrent test runs.
+Utility for creating ``accounts.yaml`` file for concurrent test runs.
 Creates one primary user, one alt user, one swift admin, one stack owner
 and one admin (optionally) for each concurrent thread. The utility creates
-user for each tenant. The **accounts.yaml** file will be valid and contain
+user for each tenant. The ``accounts.yaml`` file will be valid and contain
 credentials for created users, so each user will be in separate tenant and
 have the username, tenant_name, password and roles.
 
-**Usage:** ``tempest account-generator [-h] [OPTIONS] accounts_file.yaml``.
+**Usage:** ``tempest account-generator [-h] [OPTIONS] accounts_file.yaml``
 
 Positional Arguments
 --------------------
-**accounts_file.yaml** (Required) Provide an output accounts yaml file. Utility
+``accounts_file.yaml`` (Required) Provide an output accounts yaml file. Utility
 creates a .yaml file in the directory where the command is ran. The appropriate
 name for the file is *accounts.yaml* and it should be placed in *tempest/etc*
 directory.
@@ -40,55 +40,62 @@
 
 You're probably familiar with these, but just to remind:
 
-======== ======================== ====================
-Param    CLI                      Environment Variable
-======== ======================== ====================
-Username --os-username            OS_USERNAME
-Password --os-password            OS_PASSWORD
-Project  --os-project-name        OS_PROJECT_NAME
-Tenant   --os-tenant-name (depr.) OS_TENANT_NAME
-Domain   --os-domain-name         OS_DOMAIN_NAME
-======== ======================== ====================
+======== ============================ ====================
+Param    CLI                          Environment Variable
+======== ============================ ====================
+Username ``--os-username``            OS_USERNAME
+Password ``--os-password``            OS_PASSWORD
+Project  ``--os-project-name``        OS_PROJECT_NAME
+Tenant   ``--os-tenant-name`` (depr.) OS_TENANT_NAME
+Domain   ``--os-domain-name``         OS_DOMAIN_NAME
+======== ============================ ====================
 
 Optional Arguments
 ------------------
-**-h**, **--help** (Optional) Shows help message with the description of
-utility and its arguments, and exits.
+* ``-h, --help`` (Optional) Shows help message with the description of
+  utility and its arguments, and exits.
 
-**c /etc/tempest.conf**, **--config-file /etc/tempest.conf** (Optional) Path to
-tempest config file.
+* ``-c, --config-file /etc/tempest.conf`` (Optional) Path
+  to tempest config file. If not specified, it searches for tempest.conf in
+  these locations:
 
-**--os-username <auth-user-name>** (Optional) Name used for authentication with
-the OpenStack Identity service. Defaults to env[OS_USERNAME]. Note: User should
-have permissions to create new user accounts and tenants.
+  - ./etc/
+  - /etc/tempest
+  - ~/.tempest/
+  - ~/
+  - /etc/
 
-**--os-password <auth-password>** (Optional) Password used for authentication
-with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
+* ``--os-username <auth-user-name>`` (Optional) Name used for authentication
+  with the OpenStack Identity service. Defaults to env[OS_USERNAME]. Note: User
+  should have permissions to create new user accounts and tenants.
 
-**--os-project-name <auth-project-name>** (Optional) Project to request
-authorization on. Defaults to env[OS_PROJECT_NAME].
+* ``--os-password <auth-password>`` (Optional) Password used for authentication
+  with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
 
-**--os-tenant-name <auth-tenant-name>** (Optional, deprecated) Tenant to
-request authorization on. Defaults to env[OS_TENANT_NAME].
+* ``--os-project-name <auth-project-name>`` (Optional) Project to request
+  authorization on. Defaults to env[OS_PROJECT_NAME].
 
-**--os-domain-name <auth-domain-name>** (Optional) Domain the user and project
-belong to. Defaults to env[OS_DOMAIN_NAME].
+* ``--os-tenant-name <auth-tenant-name>`` (Optional, deprecated) Tenant to
+  request authorization on. Defaults to env[OS_TENANT_NAME].
 
-**--tag TAG** (Optional) Resources tag. Each created resource (user, project)
-will have the prefix with the given TAG in its name. Using tag is recommended
-for the further using, cleaning resources.
+* ``--os-domain-name <auth-domain-name>`` (Optional) Domain the user and
+  project belong to. Defaults to env[OS_DOMAIN_NAME].
 
-**-r CONCURRENCY**, **--concurrency CONCURRENCY** (Required) Concurrency count
-(default: 1). The number of accounts required can be estimated as
-CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in
-a different tenant. This is required to provide isolation between test for
-running in parallel.
+* ``--tag TAG`` (Optional) Resources tag. Each created resource (user, project)
+  will have the prefix with the given TAG in its name. Using tag is recommended
+  for the further using, cleaning resources.
 
-**--with-admin** (Optional) Creates admin for each concurrent group
-(default: False).
+* ``-r, --concurrency CONCURRENCY`` (Optional) Concurrency count
+  (default: 1). The number of accounts required can be estimated as
+  CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in
+  a different tenant. This is required to provide isolation between test for
+  running in parallel.
 
-**-i VERSION**, **--identity-version VERSION** (Optional) Provisions accounts
-using the specified version of the identity API. (default: '3').
+* ``--with-admin`` (Optional) Creates admin for each concurrent group
+  (default: False).
+
+* ``-i, --identity-version VERSION`` (Optional) Provisions accounts
+  using the specified version of the identity API. (default: '3').
 
 To see help on specific argument, please do: ``tempest account-generator
 [OPTIONS] <accounts_file.yaml> -h``.
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index a128b3f..29abd49 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -28,45 +28,48 @@
 Example Run
 -----------
 
-**WARNING: If step 1 is skipped in the example below, the cleanup procedure
-may delete resources that existed in the cloud before the test run. This
-may cause an unwanted destruction of cloud resources, so use caution with
-this command.**
+.. warning::
 
-``$ tempest cleanup --init-saved-state``
+    If step 1 is skipped in the example below, the cleanup procedure
+    may delete resources that existed in the cloud before the test run. This
+    may cause an unwanted destruction of cloud resources, so use caution with
+    this command.
 
-``$ # Actual running of Tempest tests``
+    Examples::
 
-``$ tempest cleanup``
+     $ tempest cleanup --init-saved-state
+     $ # Actual running of Tempest tests
+     $ tempest cleanup
 
 Runtime Arguments
 -----------------
 
-**--init-saved-state**: Initializes the saved state of the OpenStack deployment
-and will output a ``saved_state.json`` file containing resources from your
-deployment that will be preserved from the cleanup command. This should be
-done prior to running Tempest tests.
+* ``--init-saved-state``: Initializes the saved state of the OpenStack
+  deployment and will output a ``saved_state.json`` file containing resources
+  from your deployment that will be preserved from the cleanup command. This
+  should be done prior to running Tempest tests.
 
-**--delete-tempest-conf-objects**: If option is present, then the command will
-delete the admin project in addition to the resources associated with them on
-clean up. If option is not present, the command will delete the resources
-associated with the Tempest and alternate Tempest users and projects but will
-not delete the projects themselves.
+* ``--delete-tempest-conf-objects``: If option is present, then the command
+  will delete the admin project in addition to the resources associated with
+  them on clean up. If option is not present, the command will delete the
+  resources associated with the Tempest and alternate Tempest users and
+  projects but will not delete the projects themselves.
 
-**--dry-run**: Creates a report (``./dry_run.json``) of the projects that will
-be cleaned up (in the ``_tenants_to_clean`` dictionary [1]_) and the global
-objects that will be removed (domains, flavors, images, roles, projects,
-and users). Once the cleanup command is executed (e.g. run without
-parameters), running it again with **--dry-run** should yield an empty report.
+* ``--dry-run``: Creates a report (``./dry_run.json``) of the projects that
+  will be cleaned up (in the ``_projects_to_clean`` dictionary [1]_) and the
+  global objects that will be removed (domains, flavors, images, roles,
+  projects, and users). Once the cleanup command is executed (e.g. run without
+  parameters), running it again with ``--dry-run`` should yield an empty
+  report.
 
-**--help**: Print the help text for the command and parameters.
+* ``--help``: Print the help text for the command and parameters.
 
-.. [1] The ``_tenants_to_clean`` dictionary in ``dry_run.json`` lists the
+.. [1] The ``_projects_to_clean`` dictionary in ``dry_run.json`` lists the
     projects that ``tempest cleanup`` will loop through to delete child
     objects, but the command will, by default, not delete the projects
-    themselves. This may differ from the ``tenants`` list as you can clean
+    themselves. This may differ from the ``projects`` list as you can clean
     the Tempest and alternate Tempest users and projects but they will not be
-    deleted unless the **--delete-tempest-conf-objects** flag is used to
+    deleted unless the ``--delete-tempest-conf-objects`` flag is used to
     force their deletion.
 
 """
@@ -111,13 +114,13 @@
 
         self.admin_id = ""
         self.admin_role_id = ""
-        self.admin_tenant_id = ""
+        self.admin_project_id = ""
         self._init_admin_ids()
 
         self.admin_role_added = []
 
         # available services
-        self.tenant_services = cleanup_service.get_tenant_cleanup_services()
+        self.project_services = cleanup_service.get_project_cleanup_services()
         self.global_services = cleanup_service.get_global_cleanup_services()
 
         if parsed_args.init_saved_state:
@@ -133,24 +136,24 @@
         is_save_state = False
 
         if is_dry_run:
-            self.dry_run_data["_tenants_to_clean"] = {}
+            self.dry_run_data["_projects_to_clean"] = {}
 
         admin_mgr = self.admin_mgr
-        # Always cleanup tempest and alt tempest tenants unless
+        # Always cleanup tempest and alt tempest projects unless
         # they are in saved state json. Therefore is_preserve is False
         kwargs = {'data': self.dry_run_data,
                   'is_dry_run': is_dry_run,
                   'saved_state_json': self.json_data,
                   'is_preserve': False,
                   'is_save_state': is_save_state}
-        tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
-        tenants = tenant_service.list()
-        print("Process %s tenants" % len(tenants))
+        project_service = cleanup_service.ProjectService(admin_mgr, **kwargs)
+        projects = project_service.list()
+        print("Process %s projects" % len(projects))
 
-        # Loop through list of tenants and clean them up.
-        for tenant in tenants:
-            self._add_admin(tenant['id'])
-            self._clean_tenant(tenant)
+        # Loop through list of projects and clean them up.
+        for project in projects:
+            self._add_admin(project['id'])
+            self._clean_project(project)
 
         kwargs = {'data': self.dry_run_data,
                   'is_dry_run': is_dry_run,
@@ -169,49 +172,51 @@
         self._remove_admin_user_roles()
 
     def _remove_admin_user_roles(self):
-        tenant_ids = self.admin_role_added
-        LOG.debug("Removing admin user roles where needed for tenants: %s",
-                  tenant_ids)
-        for tenant_id in tenant_ids:
-            self._remove_admin_role(tenant_id)
+        project_ids = self.admin_role_added
+        LOG.debug("Removing admin user roles where needed for projects: %s",
+                  project_ids)
+        for project_id in project_ids:
+            self._remove_admin_role(project_id)
 
-    def _clean_tenant(self, tenant):
-        print("Cleaning tenant:  %s " % tenant['name'])
+    def _clean_project(self, project):
+        print("Cleaning project:  %s " % project['name'])
         is_dry_run = self.options.dry_run
         dry_run_data = self.dry_run_data
         is_preserve = not self.options.delete_tempest_conf_objects
-        tenant_id = tenant['id']
-        tenant_name = tenant['name']
-        tenant_data = None
+        project_id = project['id']
+        project_name = project['name']
+        project_data = None
         if is_dry_run:
-            tenant_data = dry_run_data["_tenants_to_clean"][tenant_id] = {}
-            tenant_data['name'] = tenant_name
+            project_data = dry_run_data["_projects_to_clean"][project_id] = {}
+            project_data['name'] = project_name
 
         kwargs = {"username": CONF.auth.admin_username,
                   "password": CONF.auth.admin_password,
-                  "tenant_name": tenant['name']}
+                  "project_name": project['name']}
         mgr = clients.Manager(credentials=credentials.get_credentials(
             **kwargs))
-        kwargs = {'data': tenant_data,
+        kwargs = {'data': project_data,
                   'is_dry_run': is_dry_run,
                   'saved_state_json': None,
                   'is_preserve': is_preserve,
                   'is_save_state': False,
-                  'tenant_id': tenant_id}
-        for service in self.tenant_services:
+                  'project_id': project_id}
+        for service in self.project_services:
             svc = service(mgr, **kwargs)
             svc.run()
 
     def _init_admin_ids(self):
-        tn_cl = self.admin_mgr.tenants_client
-        rl_cl = self.admin_mgr.roles_client
+        pr_cl = self.admin_mgr.projects_client
+        rl_cl = self.admin_mgr.roles_v3_client
+        rla_cl = self.admin_mgr.role_assignments_client
+        us_cl = self.admin_mgr.users_v3_client
 
-        tenant = identity.get_tenant_by_name(tn_cl,
-                                             CONF.auth.admin_project_name)
-        self.admin_tenant_id = tenant['id']
-
-        user = identity.get_user_by_username(tn_cl, self.admin_tenant_id,
-                                             CONF.auth.admin_username)
+        project = identity.get_project_by_name(pr_cl,
+                                               CONF.auth.admin_project_name)
+        self.admin_project_id = project['id']
+        user = identity.get_user_by_project(us_cl, rla_cl,
+                                            self.admin_project_id,
+                                            CONF.auth.admin_username)
         self.admin_id = user['id']
 
         roles = rl_cl.list_roles()['roles']
@@ -236,7 +241,7 @@
                             dest='delete_tempest_conf_objects',
                             default=False,
                             help="Force deletion of the tempest and "
-                            "alternate tempest users and tenants.")
+                            "alternate tempest users and projects.")
         parser.add_argument('--dry-run', action="store_true",
                             dest='dry_run', default=False,
                             help="Generate JSON file:" + DRY_RUN_JSON +
@@ -247,44 +252,44 @@
     def get_description(self):
         return 'Cleanup after tempest run'
 
-    def _add_admin(self, tenant_id):
-        rl_cl = self.admin_mgr.roles_client
+    def _add_admin(self, project_id):
+        rl_cl = self.admin_mgr.roles_v3_client
         needs_role = True
-        roles = rl_cl.list_user_roles_on_project(tenant_id,
+        roles = rl_cl.list_user_roles_on_project(project_id,
                                                  self.admin_id)['roles']
         for role in roles:
             if role['id'] == self.admin_role_id:
                 needs_role = False
-                LOG.debug("User already had admin privilege for this tenant")
+                LOG.debug("User already had admin privilege for this project")
         if needs_role:
-            LOG.debug("Adding admin privilege for : %s", tenant_id)
-            rl_cl.create_user_role_on_project(tenant_id, self.admin_id,
+            LOG.debug("Adding admin privilege for : %s", project_id)
+            rl_cl.create_user_role_on_project(project_id, self.admin_id,
                                               self.admin_role_id)
-            self.admin_role_added.append(tenant_id)
+            self.admin_role_added.append(project_id)
 
-    def _remove_admin_role(self, tenant_id):
-        LOG.debug("Remove admin user role for tenant: %s", tenant_id)
+    def _remove_admin_role(self, project_id):
+        LOG.debug("Remove admin user role for projectt: %s", project_id)
         # Must initialize Admin Manager for each user role
         # Otherwise authentication exception is thrown, weird
         id_cl = clients.Manager(
             credentials.get_configured_admin_credentials()).identity_client
-        if (self._tenant_exists(tenant_id)):
+        if (self._project_exists(project_id)):
             try:
-                id_cl.delete_role_from_user_on_project(tenant_id,
+                id_cl.delete_role_from_user_on_project(project_id,
                                                        self.admin_id,
                                                        self.admin_role_id)
             except Exception as ex:
-                LOG.exception("Failed removing role from tenant which still"
+                LOG.exception("Failed removing role from project which still"
                               "exists, exception: %s", ex)
 
-    def _tenant_exists(self, tenant_id):
-        tn_cl = self.admin_mgr.tenants_client
+    def _project_exists(self, project_id):
+        pr_cl = self.admin_mgr.projects_client
         try:
-            t = tn_cl.show_tenant(tenant_id)
-            LOG.debug("Tenant is: %s", str(t))
+            p = pr_cl.show_project(project_id)
+            LOG.debug("Project is: %s", str(p))
             return True
         except Exception as ex:
-            LOG.debug("Tenant no longer exists? %s", ex)
+            LOG.debug("Project no longer exists? %s", ex)
             return False
 
     def _init_state(self):
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index c75bc85..d1e80f1 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -32,7 +32,7 @@
 CONF_PRIV_NETWORK_NAME = None
 CONF_PUB_NETWORK = None
 CONF_PUB_ROUTER = None
-CONF_TENANTS = None
+CONF_PROJECTS = None
 CONF_USERS = None
 
 IS_CINDER = None
@@ -50,7 +50,7 @@
     global CONF_PRIV_NETWORK_NAME
     global CONF_PUB_NETWORK
     global CONF_PUB_ROUTER
-    global CONF_TENANTS
+    global CONF_PROJECTS
     global CONF_USERS
     global IS_CINDER
     global IS_GLANCE
@@ -69,7 +69,7 @@
     CONF_PRIV_NETWORK_NAME = CONF.compute.fixed_network_name
     CONF_PUB_NETWORK = CONF.network.public_network_id
     CONF_PUB_ROUTER = CONF.network.public_router_id
-    CONF_TENANTS = [CONF.auth.admin_project_name]
+    CONF_PROJECTS = [CONF.auth.admin_project_name]
     CONF_USERS = [CONF.auth.admin_username]
 
     if IS_NEUTRON:
@@ -82,14 +82,14 @@
     am = clients.Manager(
         credentials.get_configured_admin_credentials())
     net_cl = am.networks_client
-    tn_cl = am.tenants_client
+    pr_cl = am.projects_client
 
     networks = net_cl.list_networks()
-    tenant = identity.get_tenant_by_name(tn_cl, project_name)
-    t_id = tenant['id']
+    project = identity.get_project_by_name(pr_cl, project_name)
+    p_id = project['id']
     n_id = None
     for net in networks['networks']:
-        if (net['tenant_id'] == t_id and net['name'] == net_name):
+        if (net['project_id'] == p_id and net['name'] == net_name):
             n_id = net['id']
             break
     return n_id
@@ -141,7 +141,7 @@
 
     def __init__(self, manager, **kwargs):
         super(SnapshotService, self).__init__(kwargs)
-        self.client = manager.snapshots_client
+        self.client = manager.snapshots_client_latest
 
     def list(self):
         client = self.client
@@ -319,7 +319,7 @@
 class VolumeService(BaseService):
     def __init__(self, manager, **kwargs):
         super(VolumeService, self).__init__(kwargs)
-        self.client = manager.volumes_client
+        self.client = manager.volumes_client_latest
 
     def list(self):
         client = self.client
@@ -344,7 +344,7 @@
 class VolumeQuotaService(BaseService):
     def __init__(self, manager, **kwargs):
         super(VolumeQuotaService, self).__init__(kwargs)
-        self.client = manager.volume_quotas_client
+        self.client = manager.volume_quotas_v2_client
 
     def delete(self):
         client = self.client
@@ -786,14 +786,14 @@
 class IdentityService(BaseService):
     def __init__(self, manager, **kwargs):
         super(IdentityService, self).__init__(kwargs)
-        self.client = manager.identity_client
+        self.client = manager.identity_v3_client
 
 
 class UserService(BaseService):
 
     def __init__(self, manager, **kwargs):
         super(UserService, self).__init__(kwargs)
-        self.client = manager.users_client
+        self.client = manager.users_v3_client
 
     def list(self):
         users = self.client.list_users()['users']
@@ -872,43 +872,43 @@
             self.data['roles'][role['id']] = role['name']
 
 
-class TenantService(BaseService):
+class ProjectService(BaseService):
 
     def __init__(self, manager, **kwargs):
-        super(TenantService, self).__init__(kwargs)
-        self.client = manager.tenants_client
+        super(ProjectService, self).__init__(kwargs)
+        self.client = manager.projects_client
 
     def list(self):
-        tenants = self.client.list_tenants()['tenants']
+        projects = self.client.list_projects()['projects']
         if not self.is_save_state:
-            tenants = [tenant for tenant in tenants if (tenant['id']
-                       not in self.saved_state_json['tenants'].keys()
-                       and tenant['name'] != CONF.auth.admin_project_name)]
+            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)]
 
         if self.is_preserve:
-            tenants = [tenant for tenant in tenants if tenant['name']
-                       not in CONF_TENANTS]
+            projects = [project for project in projects if project['name']
+                        not in CONF_PROJECTS]
 
-        LOG.debug("List count, %s Tenants after reconcile", len(tenants))
-        return tenants
+        LOG.debug("List count, %s Projects after reconcile", len(projects))
+        return projects
 
     def delete(self):
-        tenants = self.list()
-        for tenant in tenants:
+        projects = self.list()
+        for project in projects:
             try:
-                self.client.delete_tenant(tenant['id'])
+                self.client.delete_project(project['id'])
             except Exception:
-                LOG.exception("Delete Tenant exception.")
+                LOG.exception("Delete project exception.")
 
     def dry_run(self):
-        tenants = self.list()
-        self.data['tenants'] = tenants
+        projects = self.list()
+        self.data['projects'] = projects
 
     def save_state(self):
-        tenants = self.list()
-        self.data['tenants'] = {}
-        for tenant in tenants:
-            self.data['tenants'][tenant['id']] = tenant['name']
+        projects = self.list()
+        self.data['projects'] = {}
+        for project in projects:
+            self.data['projects'][project['id']] = project['name']
 
 
 class DomainService(BaseService):
@@ -948,35 +948,35 @@
             self.data['domains'][domain['id']] = domain['name']
 
 
-def get_tenant_cleanup_services():
-    tenant_services = []
+def get_project_cleanup_services():
+    project_services = []
     # TODO(gmann): Tempest should provide some plugin hook for cleanup
     # script extension to plugin tests also.
     if IS_NOVA:
-        tenant_services.append(ServerService)
-        tenant_services.append(KeyPairService)
-        tenant_services.append(SecurityGroupService)
-        tenant_services.append(ServerGroupService)
+        project_services.append(ServerService)
+        project_services.append(KeyPairService)
+        project_services.append(SecurityGroupService)
+        project_services.append(ServerGroupService)
         if not IS_NEUTRON:
-            tenant_services.append(FloatingIpService)
-        tenant_services.append(NovaQuotaService)
+            project_services.append(FloatingIpService)
+        project_services.append(NovaQuotaService)
     if IS_HEAT:
-        tenant_services.append(StackService)
+        project_services.append(StackService)
     if IS_NEUTRON:
-        tenant_services.append(NetworkFloatingIpService)
+        project_services.append(NetworkFloatingIpService)
         if utils.is_extension_enabled('metering', 'network'):
-            tenant_services.append(NetworkMeteringLabelRuleService)
-            tenant_services.append(NetworkMeteringLabelService)
-        tenant_services.append(NetworkRouterService)
-        tenant_services.append(NetworkPortService)
-        tenant_services.append(NetworkSubnetService)
-        tenant_services.append(NetworkService)
-        tenant_services.append(NetworkSecGroupService)
+            project_services.append(NetworkMeteringLabelRuleService)
+            project_services.append(NetworkMeteringLabelService)
+        project_services.append(NetworkRouterService)
+        project_services.append(NetworkPortService)
+        project_services.append(NetworkSubnetService)
+        project_services.append(NetworkService)
+        project_services.append(NetworkSecGroupService)
     if IS_CINDER:
-        tenant_services.append(SnapshotService)
-        tenant_services.append(VolumeService)
-        tenant_services.append(VolumeQuotaService)
-    return tenant_services
+        project_services.append(SnapshotService)
+        project_services.append(VolumeService)
+        project_services.append(VolumeQuotaService)
+    return project_services
 
 
 def get_global_cleanup_services():
@@ -986,7 +986,7 @@
     if IS_GLANCE:
         global_services.append(ImageService)
     global_services.append(UserService)
-    global_services.append(TenantService)
+    global_services.append(ProjectService)
     global_services.append(DomainService)
     global_services.append(RoleService)
     return global_services
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index f07f197..0d847bd 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -19,11 +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 testr uses. It will run
+  any tests that match on re.match() with the regex
+* ``--smoke, -s``: Run all the tests tagged as smoke
 
-There are also the **--blacklist-file** and **--whitelist-file** options that
+There are also the ``--blacklist-file`` and ``--whitelist-file`` options that
 let you pass a filepath to tempest run with the file format being a line
 separated regex, with '#' used to signify the start of a comment on a line.
 For example::
@@ -44,21 +44,21 @@
 When combined with a whitelist file all the regexes from the file and the CLI
 regexes will be ORed.
 
-You can also use the **--list-tests** option in conjunction with selection
+You can also use the ``--list-tests`` option in conjunction with selection
 arguments to list which tests will be run.
 
-You can also use the **--load-list** option that lets you pass a filepath to
+You can also use the ``--load-list`` option that lets you pass a filepath to
 tempest run with the file format being in a non-regex format, similar to the
-tests generated by the **--list-tests** option. You can specify target tests
+tests generated by the ``--list-tests`` option. You can specify target tests
 by removing unnecessary tests from a list file which is generated from
-**--list-tests** option.
+``--list-tests`` option.
 
 Test Execution
 ==============
 There are several options to control how the tests are executed. By default
 tempest will run in parallel with a worker for each CPU present on the machine.
-If you want to adjust the number of workers use the **--concurrency** option
-and if you want to run tests serially use **--serial/-t**
+If you want to adjust the number of workers use the ``--concurrency`` option
+and if you want to run tests serially use ``--serial/-t``
 
 Running with Workspaces
 -----------------------
@@ -82,7 +82,7 @@
 ===========
 By default tempest run's output to STDOUT will be generated using the
 subunit-trace output filter. But, if you would prefer a subunit v2 stream be
-output to STDOUT use the **--subunit** flag
+output to STDOUT use the ``--subunit`` flag
 
 Combining Runs
 ==============
@@ -90,7 +90,7 @@
 There are certain situations in which you want to split a single run of tempest
 across 2 executions of tempest run. (for example to run part of the tests
 serially and others in parallel) To accomplish this but still treat the results
-as a single run you can leverage the **--combine** option which will append
+as a single run you can leverage the ``--combine`` option which will append
 the current run's results with the previous runs.
 """
 
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index f9ebe20..f0ade7e 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -21,17 +21,14 @@
 Runtime Arguments
 -----------------
 
-**--subunit, -s**: (Optional) The path to the subunit file being parsed,
-defaults to stdin
-
-**--non-subunit-name, -n**: (Optional) The file_name that the logs are being
-stored in
-
-**--output-file, -o**: (Optional) The path where the JSON output will be
-written to. This contains more information than is present in stdout.
-
-**--ports, -p**: (Optional) The path to a JSON file describing the ports being
-used by different services
+* ``--subunit, -s``: (Optional) The path to the subunit file being parsed,
+  defaults to stdin
+* ``--non-subunit-name, -n``: (Optional) The file_name that the logs are being
+  stored in
+* ``--output-file, -o``: (Optional) The path where the JSON output will be
+  written to. This contains more information than is present in stdout.
+* ``--ports, -p``: (Optional) The path to a JSON file describing the ports
+  being used by different services
 
 Usage
 -----
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index 8166b4f..929a584 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -26,28 +26,28 @@
 
 register
 --------
-Registers a new tempest workspace via a given --name and --path
+Registers a new tempest workspace via a given ``--name`` and ``--path``
 
 rename
 ------
-Renames a tempest workspace from --old-name to --new-name
+Renames a tempest workspace from ``--old-name`` to ``--new-name``
 
 move
 ----
-Changes the path of a given tempest workspace --name to --path
+Changes the path of a given tempest workspace ``--name`` to ``--path``
 
 remove
 ------
-Deletes the entry for a given tempest workspace --name
+Deletes the entry for a given tempest workspace ``--name``
 
---rmdir Deletes the given tempest workspace directory
+``--rmdir`` Deletes the given tempest workspace directory
 
 General Options
 ===============
 
- **--workspace_path**: Allows the user to specify a different location for the
-                       workspace.yaml file containing the workspace definitions
-                       instead of ~/.tempest/workspace.yaml
+* ``--workspace_path``: Allows the user to specify a different location for the
+  workspace.yaml file containing the workspace definitions instead of
+  ``~/.tempest/workspace.yaml``
 """
 
 import os
diff --git a/tempest/common/identity.py b/tempest/common/identity.py
index 6e496d3..eaf651b 100644
--- a/tempest/common/identity.py
+++ b/tempest/common/identity.py
@@ -20,6 +20,15 @@
 CONF = config.CONF
 
 
+def get_project_by_name(client, project_name):
+    projects = client.list_projects({'name': project_name})['projects']
+    for project in projects:
+        if project['name'] == project_name:
+            return project
+    raise lib_exc.NotFound('No such project(%s) in %s' % (project_name,
+                           projects))
+
+
 def get_tenant_by_name(client, tenant_name):
     tenants = client.list_tenants()['tenants']
     for tenant in tenants:
@@ -36,6 +45,18 @@
     raise lib_exc.NotFound('No such user(%s) in %s' % (username, users))
 
 
+def get_user_by_project(users_client, roles_client, project_id, username):
+    users = users_client.list_users(**{'name': username})['users']
+    users_in_project = roles_client.list_role_assignments(
+        **{'scope.project.id': project_id})['role_assignments']
+    for user in users:
+        if user['name'] == username:
+            for u in users_in_project:
+                if u['user']['id'] == user['id']:
+                    return user
+    raise lib_exc.NotFound('No such user(%s) in %s' % (username, users))
+
+
 def identity_utils(clients):
     """A client that abstracts v2 and v3 identity operations.
 
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 5a86caa..aa81864 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -78,7 +78,7 @@
         decorators.attr(type=list(args))(f)
 
         @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
+        def wrapper(*func_args, **func_kwargs):
             service_list = get_service_list()
 
             for service in args:
@@ -86,7 +86,7 @@
                     msg = 'Skipped because the %s service is not available' % (
                         service)
                     raise testtools.TestCase.skipException(msg)
-            return f(self, *func_args, **func_kwargs)
+            return f(*func_args, **func_kwargs)
         return wrapper
     return decorator
 
diff --git a/tempest/config.py b/tempest/config.py
index 0743220..b14d4fd 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -227,7 +227,8 @@
                 help="A list of enabled identity extensions with a special "
                      "entry all which indicates every extension is enabled. "
                      "Empty list indicates all extensions are disabled. "
-                     "To get the list of extensions run: 'keystone discover'"),
+                     "To get the list of extensions run: "
+                     "'openstack extension list --identity'"),
     # TODO(rodrigods): This is a feature flag for bug 1590578 which is fixed
     # in Newton and Ocata. This option can be removed after Mitaka is end of
     # life.
diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py
index bd0db7c..c2e93ee 100644
--- a/tempest/lib/common/utils/test_utils.py
+++ b/tempest/lib/common/utils/test_utils.py
@@ -86,22 +86,29 @@
         pass
 
 
-def call_until_true(func, duration, sleep_for):
+def call_until_true(func, duration, sleep_for, *args, **kwargs):
     """Call the given function until it returns True (and return True)
 
     or until the specified duration (in seconds) elapses (and return False).
 
-    :param func: A zero argument callable that returns True on success.
+    :param func: A callable that returns True on success.
     :param duration: The number of seconds for which to attempt a
         successful call of the function.
     :param sleep_for: The number of seconds to sleep after an unsuccessful
                       invocation of the function.
+    :param args: args that are passed to func.
+    :param kwargs: kwargs that are passed to func.
     """
     now = time.time()
+    begin_time = now
     timeout = now + duration
     while now < timeout:
-        if func():
+        if func(*args, **kwargs):
+            LOG.debug("Call %s returns true in %f seconds",
+                      getattr(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)
     return False
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index f82f707..e99dd24 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -15,7 +15,6 @@
 import functools
 import uuid
 
-import debtcollector.removals
 from oslo_log import log as logging
 import six
 import testtools
@@ -31,7 +30,7 @@
     """
     def decorator(f):
         @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
+        def wrapper(*func_args, **func_kwargs):
             skip = False
             if "condition" in kwargs:
                 if kwargs["condition"] is True:
@@ -43,7 +42,7 @@
                     raise ValueError('bug must be a valid bug number')
                 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
                 raise testtools.TestCase.skipException(msg)
-            return f(self, *func_args, **func_kwargs)
+            return f(*func_args, **func_kwargs)
         return wrapper
     return decorator
 
@@ -56,9 +55,9 @@
     """
     def decorator(f):
         @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
+        def wrapper(*func_args, **func_kwargs):
             try:
-                return f(self, *func_args, **func_kwargs)
+                return f(*func_args, **func_kwargs)
             except Exception as exc:
                 exc_status_code = getattr(exc, 'status_code', None)
                 if status_code is None or status_code == exc_status_code:
@@ -87,25 +86,6 @@
     return decorator
 
 
-@debtcollector.removals.remove(removal_version='Queen')
-class skip_unless_attr(object):
-    """Decorator to skip tests if a specified attr does not exists or False"""
-    def __init__(self, attr, msg=None):
-        self.attr = attr
-        self.message = msg or ("Test case attribute %s not found "
-                               "or False") % attr
-
-    def __call__(self, func):
-        @functools.wraps(func)
-        def _skipper(*args, **kw):
-            """Wrapped skipper function."""
-            testobj = args[0]
-            if not getattr(testobj, self.attr, False):
-                raise testtools.TestCase.skipException(self.message)
-            func(*args, **kw)
-        return _skipper
-
-
 def attr(**kwargs):
     """A decorator which applies the testtools attr decorator
 
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 9b2e87e..13af890 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -96,7 +96,7 @@
 
 class Conflict(ClientRestClientException):
     status_code = 409
-    message = "An object with that identifier already exists"
+    message = "Conflict with state of target resource"
 
 
 class Gone(ClientRestClientException):
diff --git a/tempest/lib/services/volume/v1/encryption_types_client.py b/tempest/lib/services/volume/v1/encryption_types_client.py
index 067b4e8..0fac6bd 100644
--- a/tempest/lib/services/volume/v1/encryption_types_client.py
+++ b/tempest/lib/services/volume/v1/encryption_types_client.py
@@ -49,9 +49,9 @@
     def create_encryption_type(self, volume_type_id, **kwargs):
         """Create encryption type.
 
-        TODO: Current api-site doesn't contain this API description.
-        After fixing the api-site, we need to fix here also for putting
-        the link to api-site.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2
         """
         url = "/types/%s/encryption" % volume_type_id
         post_body = json.dumps({'encryption': kwargs})
diff --git a/tempest/lib/services/volume/v1/hosts_client.py b/tempest/lib/services/volume/v1/hosts_client.py
index 56ba12c..9b19b84 100644
--- a/tempest/lib/services/volume/v1/hosts_client.py
+++ b/tempest/lib/services/volume/v1/hosts_client.py
@@ -23,8 +23,12 @@
     """Client class to send CRUD Volume Host API V1 requests"""
 
     def list_hosts(self, **params):
-        """Lists all hosts."""
+        """Lists all hosts.
 
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts
+        """
         url = 'os-hosts'
         if params:
             url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/volume/v1/qos_client.py b/tempest/lib/services/volume/v1/qos_client.py
index e247b7b..593bddd 100644
--- a/tempest/lib/services/volume/v1/qos_client.py
+++ b/tempest/lib/services/volume/v1/qos_client.py
@@ -92,7 +92,9 @@
 
         :param keys: keys to delete from the QoS specification.
 
-        TODO(jordanP): Add a link once LP #1524877 is fixed.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#unset-keys-in-qos-specification
         """
         put_body = json.dumps({'keys': keys})
         resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
diff --git a/tempest/lib/services/volume/v1/quotas_client.py b/tempest/lib/services/volume/v1/quotas_client.py
index 678fd82..84f34f2 100644
--- a/tempest/lib/services/volume/v1/quotas_client.py
+++ b/tempest/lib/services/volume/v1/quotas_client.py
@@ -47,7 +47,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref-blockstorage-v1.html#updateQuota
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-quotas
         """
         put_body = jsonutils.dumps({'quota_set': kwargs})
         resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
diff --git a/tempest/lib/services/volume/v1/snapshots_client.py b/tempest/lib/services/volume/v1/snapshots_client.py
index 3433e68..51f7b9b 100644
--- a/tempest/lib/services/volume/v1/snapshots_client.py
+++ b/tempest/lib/services/volume/v1/snapshots_client.py
@@ -27,7 +27,8 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#list-snapshots-with-details-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-with-details
         """
         url = 'snapshots'
         if detail:
@@ -45,7 +46,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#show-snapshot-details-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-details
         """
         url = "snapshots/%s" % snapshot_id
         resp, body = self.get(url)
@@ -58,7 +59,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#create-snapshot-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot
         """
         post_body = json.dumps({'snapshot': kwargs})
         resp, body = self.post('snapshots', post_body)
@@ -71,7 +72,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#delete-snapshot-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot
         """
         resp, body = self.delete("snapshots/%s" % snapshot_id)
         self.expected_success(202, resp.status)
@@ -123,7 +124,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-snapshot-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot
         """
         put_body = json.dumps({'snapshot': kwargs})
         resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
@@ -136,7 +137,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#show-snapshot-metadata-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata
         """
         url = "snapshots/%s/metadata" % snapshot_id
         resp, body = self.get(url)
@@ -149,7 +150,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-snapshot-metadata-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata
         """
         put_body = json.dumps(kwargs)
         url = "snapshots/%s/metadata" % snapshot_id
diff --git a/tempest/lib/services/volume/v1/types_client.py b/tempest/lib/services/volume/v1/types_client.py
index 4ae9935..58a80b7 100644
--- a/tempest/lib/services/volume/v1/types_client.py
+++ b/tempest/lib/services/volume/v1/types_client.py
@@ -40,7 +40,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#list-volume-types-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-all-volume-types-for-v2
         """
         url = 'types'
         if params:
@@ -56,7 +56,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#show-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details-for-v2
         """
         url = "types/%s" % volume_type_id
         resp, body = self.get(url)
@@ -69,7 +69,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#create-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-for-v2
         """
         post_body = json.dumps({'volume_type': kwargs})
         resp, body = self.post('types', post_body)
@@ -82,7 +82,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#delete-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type
         """
         resp, body = self.delete("types/%s" % volume_type_id)
         self.expected_success(202, resp.status)
@@ -137,7 +137,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type
         """
         put_body = json.dumps({'volume_type': kwargs})
         resp, body = self.put('types/%s' % volume_type_id, put_body)
@@ -155,7 +155,7 @@
                      updated value.
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-extra-specs-for-a-volume-type-v1
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-extra-specs-for-a-volume-type
         """
         url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
         put_body = json.dumps(extra_specs)
diff --git a/tempest/lib/services/volume/v1/volumes_client.py b/tempest/lib/services/volume/v1/volumes_client.py
index 7a25697..0e6ea9f 100644
--- a/tempest/lib/services/volume/v1/volumes_client.py
+++ b/tempest/lib/services/volume/v1/volumes_client.py
@@ -38,6 +38,11 @@
         """List all the volumes created.
 
         Params can be a string (must be urlencoded) or a dictionary.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-volumes
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details
         """
         url = 'volumes'
         if detail:
@@ -63,7 +68,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#create-volume
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume
         """
         post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('volumes', post_body)
@@ -76,7 +81,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#update-volume
+        https://developer.openstack.org/api-ref/block-storage/v2/#update-volume
         """
         put_body = json.dumps({'volume': kwargs})
         resp, body = self.put('volumes/%s' % volume_id, put_body)
@@ -104,7 +109,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#attach-volume
+        https://developer.openstack.org/api-ref/block-storage/v2/#attach-volume-to-server
         """
         post_body = json.dumps({'os-attach': kwargs})
         url = 'volumes/%s/action' % (volume_id)
@@ -161,7 +166,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#extend-volume
+        https://developer.openstack.org/api-ref/block-storage/v2/#extend-volume-size
         """
         post_body = json.dumps({'os-extend': kwargs})
         url = 'volumes/%s/action' % (volume_id)
@@ -174,7 +179,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#reset-volume-status
+        https://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-statuses
         """
         post_body = json.dumps({'os-reset_status': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
@@ -186,7 +191,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#create-volume-transfer
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-transfer
         """
         post_body = json.dumps({'transfer': kwargs})
         resp, body = self.post('os-volume-transfer', post_body)
@@ -207,7 +212,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#list-volume-transfers
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers
         """
         url = 'os-volume-transfer'
         if params:
@@ -228,7 +233,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v1/#accept-volume-transfer
+        https://developer.openstack.org/api-ref/block-storage/v2/#accept-volume-transfer
         """
         url = 'os-volume-transfer/%s/accept' % transfer_id
         post_body = json.dumps({'accept': kwargs})
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index c1dcccc..efcd139 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -14,11 +14,12 @@
 
 Any scenario test should have a real-life use case. An example would be:
 
- - "As operator I want to start with a blank environment":
-    1. upload a glance image
-    2. deploy a vm from it
-    3. ssh to the guest
-    4. create a snapshot of the vm
+- "As operator I want to start with a blank environment":
+
+  1. upload a glance image
+  2. deploy a vm from it
+  3. ssh to the guest
+  4. create a snapshot of the vm
 
 
 Why are these tests in Tempest?
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 6a12b59..06b4b59 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -649,9 +649,7 @@
                 addresses = server['addresses'][
                     CONF.validation.network_for_ssh]
             else:
-                creds_provider = self._get_credentials_provider()
-                net_creds = creds_provider.get_primary_creds()
-                network = getattr(net_creds, 'network', None)
+                network = self.get_tenant_network()
                 addresses = (server['addresses'][network['name']]
                              if network else [])
             for address in addresses:
@@ -900,13 +898,17 @@
 
         result = test_utils.call_until_true(ping_remote,
                                             CONF.validation.ping_timeout, 1)
+        if result:
+            return
+
         source_host = source.ssh_client.host
         if should_succeed:
             msg = "Timed out waiting for %s to become reachable from %s" \
                 % (dest, source_host)
         else:
             msg = "%s is reachable from %s" % (dest, source_host)
-        self.assertTrue(result, msg)
+        self._log_console_output()
+        self.fail(msg)
 
     def _create_security_group(self, security_group_rules_client=None,
                                tenant_id=None,
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 6332c6d..ff8837f 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -421,6 +421,10 @@
     def test_mtu_sized_frames(self):
         """Validate that network MTU sized frames fit through."""
         self._setup_network_and_servers()
+        # first check that connectivity works in general for the instance
+        self.check_public_network_connectivity(should_connect=True)
+        # now that we checked general connectivity, test that full size frames
+        # can also pass between nodes
         self.check_public_network_connectivity(
             should_connect=True, mtu=self.network['mtu'])
 
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 210f0ba..9f4e62b 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -12,8 +12,6 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
-import functools
-
 from tempest.common import utils
 from tempest import config
 from tempest.lib.common.utils import test_utils
@@ -132,7 +130,7 @@
         ssh = self.get_remote_client(
             ip_address=fip['floating_ip_address'],
             username=username, server=srv)
-        return ssh, ips, srv["id"]
+        return ssh, ips, srv
 
     def turn_nic6_on(self, ssh, sid, network_id):
         """Turns the IPv6 vNIC on
@@ -163,8 +161,8 @@
                                         n_subnets6=n_subnets6,
                                         dualnet=dualnet)
 
-        sshv4_1, ips_from_api_1, sid1 = self.prepare_server(networks=net_list)
-        sshv4_2, ips_from_api_2, sid2 = self.prepare_server(networks=net_list)
+        sshv4_1, ips_from_api_1, srv1 = self.prepare_server(networks=net_list)
+        sshv4_2, ips_from_api_2, srv2 = self.prepare_server(networks=net_list)
 
         def guest_has_address(ssh, addr):
             return addr in ssh.exec_command("ip address")
@@ -172,8 +170,8 @@
         # Turn on 2nd NIC for Cirros when dualnet
         if dualnet:
             _, network_v6 = net_list
-            self.turn_nic6_on(sshv4_1, sid1, network_v6['id'])
-            self.turn_nic6_on(sshv4_2, sid2, network_v6['id'])
+            self.turn_nic6_on(sshv4_1, srv1['id'], network_v6['id'])
+            self.turn_nic6_on(sshv4_2, srv2['id'], network_v6['id'])
 
         # get addresses assigned to vNIC as reported by 'ip address' utility
         ips_from_ip_1 = sshv4_1.exec_command("ip address")
@@ -183,17 +181,19 @@
         for i in range(n_subnets6):
             # v6 should be configured since the image supports it
             # It can take time for ipv6 automatic address to get assigned
-            srv1_v6_addr_assigned = functools.partial(
-                guest_has_address, sshv4_1, ips_from_api_1['6'][i])
-
-            srv2_v6_addr_assigned = functools.partial(
-                guest_has_address, sshv4_2, ips_from_api_2['6'][i])
-
-            self.assertTrue(test_utils.call_until_true(srv1_v6_addr_assigned,
-                            CONF.validation.ping_timeout, 1))
-
-            self.assertTrue(test_utils.call_until_true(srv2_v6_addr_assigned,
-                            CONF.validation.ping_timeout, 1))
+            for srv, ssh, ips in (
+                    (srv1, sshv4_1, ips_from_api_1),
+                    (srv2, sshv4_2, ips_from_api_2)):
+                ip = ips['6'][i]
+                result = test_utils.call_until_true(
+                    guest_has_address,
+                    CONF.validation.ping_timeout, 1, ssh, ip)
+                if not result:
+                    self._log_console_output(servers=[srv])
+                    self.fail(
+                        'Address %s not configured for instance %s, '
+                        'ip address output is\n%s' %
+                        (ip, srv['id'], ssh.exec_command("ip address")))
 
         self.check_remote_connectivity(sshv4_1, ips_from_api_2['4'])
         self.check_remote_connectivity(sshv4_2, ips_from_api_1['4'])
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 64ea8f6..beb039c 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -208,7 +208,19 @@
 
         # boot instance from EBS image
         instance = self.create_server(image_id=image['id'])
-        # just ensure that instance booted
+
+        # Verify the server was created from the image
+        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']
+        self.assertEqual(instance['id'],
+                         created_volume_info['attachments'][0]['server_id'])
+        self.assertEqual(created_volume[0]['id'],
+                         created_volume_info['attachments'][0]['volume_id'])
+        self.assertEqual(
+            volume_origin['volume_image_metadata']['image_id'],
+            created_volume_info['volume_image_metadata']['image_id'])
 
         # delete instance
         self._delete_server(instance)
diff --git a/tempest/tests/lib/common/utils/test_test_utils.py b/tempest/tests/lib/common/utils/test_test_utils.py
index 29c5684..f638ba6 100644
--- a/tempest/tests/lib/common/utils/test_test_utils.py
+++ b/tempest/tests/lib/common/utils/test_test_utils.py
@@ -81,11 +81,13 @@
     @mock.patch('time.sleep')
     @mock.patch('time.time')
     def test_call_until_true_when_f_never_returns_true(self, m_time, m_sleep):
+        def set_value(bool_value):
+            return bool_value
         timeout = 42  # The value doesn't matter as we mock time.time()
         sleep = 60  # The value doesn't matter as we mock time.sleep()
         m_time.side_effect = utils.generate_timeout_series(timeout)
         self.assertEqual(
-            False, test_utils.call_until_true(lambda: False, timeout, sleep)
+            False, test_utils.call_until_true(set_value, timeout, sleep, False)
         )
         m_sleep.call_args_list = [mock.call(sleep)] * 2
         m_time.call_args_list = [mock.call()] * 2
@@ -93,11 +95,30 @@
     @mock.patch('time.sleep')
     @mock.patch('time.time')
     def test_call_until_true_when_f_returns_true(self, m_time, m_sleep):
+        def set_value(bool_value=False):
+            return bool_value
         timeout = 42  # The value doesn't matter as we mock time.time()
         sleep = 60  # The value doesn't matter as we mock time.sleep()
         m_time.return_value = 0
         self.assertEqual(
-            True, test_utils.call_until_true(lambda: True, timeout, sleep)
+            True, test_utils.call_until_true(set_value, timeout, sleep,
+                                             bool_value=True)
         )
         self.assertEqual(0, m_sleep.call_count)
-        self.assertEqual(1, m_time.call_count)
+        # when logging cost time we need to acquire current time.
+        self.assertEqual(2, m_time.call_count)
+
+    @mock.patch('time.sleep')
+    @mock.patch('time.time')
+    def test_call_until_true_when_f_returns_true_no_param(
+            self, m_time, m_sleep):
+        def set_value(bool_value=False):
+            return bool_value
+        timeout = 42  # The value doesn't matter as we mock time.time()
+        sleep = 60  # The value doesn't matter as we mock time.sleep()
+        m_time.side_effect = utils.generate_timeout_series(timeout)
+        self.assertEqual(
+            False, test_utils.call_until_true(set_value, timeout, sleep)
+        )
+        m_sleep.call_args_list = [mock.call(sleep)] * 2
+        m_time.call_args_list = [mock.call()] * 2
diff --git a/tempest/tests/lib/services/network/test_networks_client.py b/tempest/tests/lib/services/network/test_networks_client.py
new file mode 100644
index 0000000..078f4b0
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_networks_client.py
@@ -0,0 +1,242 @@
+# Copyright 2017 AT&T Corporation.
+# All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+
+from tempest.lib.services.network import networks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestNetworksClient(base.BaseServiceTest):
+
+    FAKE_NETWORKS = {
+        "networks": [
+            {
+                "admin_state_up": True,
+                "availability_zone_hints": [],
+                "availability_zones": [
+                    "nova"
+                ],
+                "created_at": "2016-03-08T20:19:41",
+                "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+                "mtu": 0,
+                "name": "net1",
+                "port_security_enabled": True,
+                "project_id": "4fd44f30292945e481c7b8a0c8908869",
+                "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
+                "router:external": False,
+                "shared": False,
+                "status": "ACTIVE",
+                "subnets": [
+                        "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+                ],
+                "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+                "updated_at": "2016-03-08T20:19:41",
+                "vlan_transparent": True,
+                "description": ""
+            },
+            {
+                "admin_state_up": True,
+                "availability_zone_hints": [],
+                "availability_zones": [
+                    "nova"
+                ],
+                "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+                "mtu": 0,
+                "name": "net2",
+                "port_security_enabled": True,
+                "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819",
+                "router:external": False,
+                "shared": False,
+                "status": "ACTIVE",
+                "subnets": [
+                        "08eae331-0402-425a-923c-34f7cfe39c1b"
+                ],
+                "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "updated_at": "2016-03-08T20:19:41",
+                "vlan_transparent": False,
+                "description": ""
+            }
+        ]
+    }
+
+    FAKE_NETWORK_ID = "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+
+    FAKE_NETWORK1 = {
+        "name": "net1",
+        "admin_state_up": True,
+        "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e"
+    }
+
+    FAKE_NETWORK2 = {
+        "name": "net2",
+        "admin_state_up": True,
+        "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819"
+    }
+
+    FAKE_NETWORKS_REQ = {
+        "networks": [
+            FAKE_NETWORK1,
+            FAKE_NETWORK2
+        ]
+    }
+
+    FAKE_DHCP_AGENT_NETWORK_ID = "80515c45-651f-4f9a-b82b-2ca8a7301a8d"
+
+    FAKE_DHCP_AGENTS = {
+        "agents": [
+            {
+                "binary": "neutron-dhcp-agent",
+                "description": None,
+                "admin_state_up": True,
+                "heartbeat_timestamp": "2017-06-22 18:29:50",
+                "availability_zone": "nova",
+                "alive": True,
+                "topic": "dhcp_agent",
+                "host": "osboxes",
+                "agent_type": "DHCP agent",
+                "resource_versions": {},
+                "created_at": "2017-06-19 21:39:51",
+                "started_at": "2017-06-19 21:39:51",
+                "id": "b6cfb7a1-6ac4-4980-993c-9d295d37062e",
+                "configurations": {
+                    "subnets": 2,
+                    "dhcp_lease_duration": 86400,
+                    "dhcp_driver": "neutron.agent.linux.dhcp.Dnsmasq",
+                    "networks": 1,
+                    "log_agent_heartbeats": False,
+                    "ports": 3
+                }
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestNetworksClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.networks_client = networks_client.NetworksClient(
+            fake_auth, "network", "regionOne")
+
+    def _test_list_networks(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.list_networks,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_NETWORKS,
+            bytes_body,
+            200)
+
+    def _test_create_network(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.create_network,
+            "tempest.lib.common.rest_client.RestClient.post",
+            {"network": self.FAKE_NETWORKS["networks"][0]},
+            bytes_body,
+            201,
+            **self.FAKE_NETWORK1)
+
+    def _test_create_bulk_networks(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.create_bulk_networks,
+            "tempest.lib.common.rest_client.RestClient.post",
+            self.FAKE_NETWORKS,
+            bytes_body,
+            201,
+            networks=self.FAKE_NETWORKS_REQ)
+
+    def _test_show_network(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.show_network,
+            "tempest.lib.common.rest_client.RestClient.get",
+            {"network": self.FAKE_NETWORKS["networks"][0]},
+            bytes_body,
+            200,
+            network_id=self.FAKE_NETWORK_ID)
+
+    def _test_update_network(self, bytes_body=False):
+        update_kwargs = {
+            "name": "sample_network_5_updated",
+            "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e"
+        }
+
+        resp_body = {
+            "network": copy.deepcopy(
+                self.FAKE_NETWORKS["networks"][0]
+            )
+        }
+        resp_body["network"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.networks_client.update_network,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            network_id=self.FAKE_NETWORK_ID,
+            **update_kwargs)
+
+    def _test_list_dhcp_agents_on_hosting_network(self, bytes_body=False):
+        self.check_service_client_function(
+            self.networks_client.list_dhcp_agents_on_hosting_network,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_DHCP_AGENTS,
+            bytes_body,
+            200,
+            network_id=self.FAKE_DHCP_AGENT_NETWORK_ID)
+
+    def test_delete_network(self):
+        self.check_service_client_function(
+            self.networks_client.delete_network,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            network_id=self.FAKE_NETWORK_ID)
+
+    def test_list_networks_with_str_body(self):
+        self._test_list_networks()
+
+    def test_list_networks_with_bytes_body(self):
+        self._test_list_networks(bytes_body=True)
+
+    def test_create_network_with_str_body(self):
+        self._test_create_network()
+
+    def test_create_network_with_bytes_body(self):
+        self._test_create_network(bytes_body=True)
+
+    def test_create_bulk_network_with_str_body(self):
+        self._test_create_bulk_networks()
+
+    def test_create_bulk_network_with_bytes_body(self):
+        self._test_create_bulk_networks(bytes_body=True)
+
+    def test_show_network_with_str_body(self):
+        self._test_show_network()
+
+    def test_show_network_with_bytes_body(self):
+        self._test_show_network(bytes_body=True)
+
+    def test_update_network_with_str_body(self):
+        self._test_update_network()
+
+    def test_update_network_with_bytes_body(self):
+        self._test_update_network(bytes_body=True)
+
+    def test_list_dhcp_agents_on_hosting_network_with_str_body(self):
+        self._test_list_dhcp_agents_on_hosting_network()
+
+    def test_list_dhcp_agents_on_hosting_network_with_bytes_body(self):
+        self._test_list_dhcp_agents_on_hosting_network(bytes_body=True)
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index bbebcd3..ed0eea3 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -125,35 +125,6 @@
         self.assertRaises(ValueError, self._test_helper, _id)
 
 
-class TestSkipUnlessAttrDecorator(base.TestCase):
-    def _test_skip_unless_attr(self, attr, expected_to_skip=True):
-        class TestFoo(test.BaseTestCase):
-            expected_attr = not expected_to_skip
-
-            @decorators.skip_unless_attr(attr)
-            def test_foo(self):
-                pass
-
-        t = TestFoo('test_foo')
-        if expected_to_skip:
-            self.assertRaises(testtools.TestCase.skipException,
-                              t.test_foo)
-        else:
-            try:
-                t.test_foo()
-            except Exception:
-                raise testtools.TestCase.failureException()
-
-    def test_skip_attr_does_not_exist(self):
-        self._test_skip_unless_attr('unexpected_attr')
-
-    def test_skip_attr_false(self):
-        self._test_skip_unless_attr('expected_attr')
-
-    def test_no_skip_for_attr_exist_and_true(self):
-        self._test_skip_unless_attr('expected_attr', expected_to_skip=False)
-
-
 class TestRelatedBugDecorator(base.TestCase):
     def test_relatedbug_when_no_exception(self):
         f = mock.Mock()
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 99df0d1..dd05438 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -28,12 +28,12 @@
 
 try:
     # For Python 3.0 and later
-    from urllib.error import HTTPError as HTTPError
+    from urllib.error import HTTPError
     import urllib.request as urllib
 except ImportError:
     # Fall back to Python 2's urllib2
     import urllib2 as urllib
-    from urllib2 import HTTPError as HTTPError
+    from urllib2 import HTTPError
 
 
 url = 'https://review.openstack.org/projects/'
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index 44bf840..8b4f913 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -120,3 +120,8 @@
         failed_plugin+=", $project"
     fi
 done
+
+# Check for failed status
+if [[ -n $failed_plugin ]]; then
+    exit 1
+fi