Merge "Check network connectivity of the instance before and after live migration"
diff --git a/.zuul.yaml b/.zuul.yaml
index 9569a55..ef9b0eb 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,7 +1,6 @@
 - job:
     name: devstack-tempest
     parent: devstack
-    nodeset: openstack-single-node
     description: |
       Base Tempest job.
 
@@ -38,6 +37,53 @@
     post-run: playbooks/post-tempest.yaml
 
 - job:
+    name: tempest-all
+    parent: devstack-tempest
+    description: |
+      Integration test that runs all tests.
+      Former name for this job was:
+        * legacy-periodic-tempest-dsvm-all-master
+    vars:
+      tox_envlist: all
+      tempest_test_regex: tempest
+      devstack_localrc:
+        ENABLE_FILE_INJECTION: true
+
+- job:
+    name: devstack-tempest-ipv6
+    parent: devstack-ipv6
+    description: |
+      Base Tempest IPv6 job.
+    required-projects:
+      - git.openstack.org/openstack/tempest
+    timeout: 7200
+    roles:
+      - zuul: git.openstack.org/openstack-dev/devstack
+    vars:
+      devstack_services:
+        tempest: true
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            compute:
+              min_compute_nodes: "{{ groups['compute'] | default(['controller']) | length }}"
+      test_results_stage_name: test_results
+      zuul_copy_output:
+        '{{ devstack_base_dir }}/tempest/etc/tempest.conf': logs
+        '{{ devstack_base_dir }}/tempest/etc/accounts.yaml': logs
+        '{{ devstack_base_dir }}/tempest/tempest.log': logs
+        '{{ stage_dir }}/{{ test_results_stage_name }}.subunit': logs
+        '{{ stage_dir }}/{{ test_results_stage_name }}.html': logs
+        '{{ stage_dir }}/stackviz': logs
+      extensions_to_txt:
+        conf: true
+        log: true
+        yaml: true
+        yml: true
+    run: playbooks/devstack-tempest.yaml
+    post-run: playbooks/post-tempest.yaml
+
+- job:
     name: tempest-full
     parent: devstack-tempest
     # This currently works from stable/pike on.
@@ -55,6 +101,37 @@
         ENABLE_FILE_INJECTION: true
 
 - job:
+    name: tempest-full-oslo-master
+    parent: tempest-full
+    description: |
+      Integration test using current git of oslo libs.
+      This ensures that when oslo libs get released that they
+      do not break OpenStack server projects.
+
+      Former name for this job was
+      periodic-tempest-dsvm-oslo-latest-full-master.
+    timeout: 10800
+    required-projects:
+      - git.openstack.org/openstack/oslo.cache
+      - git.openstack.org/openstack/oslo.concurrency
+      - git.openstack.org/openstack/oslo.config
+      - git.openstack.org/openstack/oslo.context
+      - git.openstack.org/openstack/oslo.db
+      - git.openstack.org/openstack/oslo.i18n
+      - git.openstack.org/openstack/oslo.log
+      - git.openstack.org/openstack/oslo.messaging
+      - git.openstack.org/openstack/oslo.middleware
+      - git.openstack.org/openstack/oslo.policy
+      - git.openstack.org/openstack/oslo.privsep
+      - git.openstack.org/openstack/oslo.reports
+      - git.openstack.org/openstack/oslo.rootwrap
+      - git.openstack.org/openstack/oslo.serialization
+      - git.openstack.org/openstack/oslo.service
+      - git.openstack.org/openstack/oslo.utils
+      - git.openstack.org/openstack/oslo.versionedobjects
+      - git.openstack.org/openstack/oslo.vmware
+
+- job:
     name: tempest-full-parallel
     parent: tempest-full
     voting: false
@@ -93,9 +170,31 @@
         c-bak: false
 
 - job:
+    name: tempest-full-py3-ipv6
+    parent: devstack-tempest-ipv6
+    # This currently works from stable/pike on.
+    # Before stable/pike, legacy version of tempest-full
+    # 'legacy-tempest-dsvm-neutron-full' run.
+    branches: ^(?!stable/ocata).*$
+    description: |
+      Base integration test with Neutron networking, IPv6 and py3.
+    vars:
+      tox_envlist: full
+      devstack_localrc:
+        USE_PYTHON3: true
+        FORCE_CONFIG_DRIVE: true
+      devstack_services:
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        # without Swift, c-bak cannot run (in the Gate at least)
+        c-bak: false
+
+- job:
     name: tempest-multinode-full
     parent: devstack-tempest
-    nodeset: openstack-two-node
+    nodeset: openstack-two-node-bionic
     # Until the devstack changes are backported, only run this on master
     branches:
       - master
@@ -133,14 +232,23 @@
         nodes:
           - controller
 
+- nodeset:
+    name: openstack-opensuse150-node
+    nodes:
+      - name: controller
+        label: opensuse-150
+    groups:
+      - name: tempest
+        nodes:
+          - controller
+
 - job:
-    name: tempest-full-py36
+    name: tempest-full-py3-opensuse150
     parent: tempest-full-py3
-    nodeset: openstack-bionic-node
-    branches:
-      - master
+    nodeset: openstack-opensuse150-node
     description: |
-      Base integration test with Neutron networking and py36.
+      Base integration test with Neutron networking and py36 running
+      on openSUSE Leap 15.0
     voting: false
 
 - job:
@@ -151,7 +259,7 @@
     description: |
       This multinode integration job will run all the tests tagged as slow.
       It enables the lvm multibackend setup to cover few scenario tests.
-      This job will run only slow tests(API or Scenario) serially.
+      This job will run only slow tests (API or Scenario) serially.
 
       Former names for this job were:
         * legacy-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend
@@ -166,26 +274,31 @@
 - job:
     name: tempest-full-rocky
     parent: tempest-full
+    nodeset: openstack-single-node-xenial
     override-checkout: stable/rocky
 
 - job:
     name: tempest-full-rocky-py3
     parent: tempest-full-py3
+    nodeset: openstack-single-node-xenial
     override-checkout: stable/rocky
 
 - job:
     name: tempest-full-queens
     parent: tempest-full
+    nodeset: openstack-single-node-xenial
     override-checkout: stable/queens
 
 - job:
     name: tempest-full-queens-py3
     parent: tempest-full-py3
+    nodeset: openstack-single-node-xenial
     override-checkout: stable/queens
 
 - job:
     name: tempest-full-pike
     parent: tempest-full
+    nodeset: openstack-single-node-xenial
     override-checkout: stable/pike
 
 - job:
@@ -206,22 +319,26 @@
       - ^tempest/hacking/.*$
       - ^tempest/tests/.*$
     required-projects:
+      - git.openstack.org/openstack/airship-tempest-plugin
       - git.openstack.org/openstack/almanach
       - git.openstack.org/openstack/aodh
       - git.openstack.org/openstack/barbican-tempest-plugin
+      - git.openstack.org/openstack/blazar-tempest-plugin
       - git.openstack.org/openstack/ceilometer
-      - git.openstack.org/openstack/cinder
-      - git.openstack.org/openstack/congress
+      - git.openstack.org/openstack/cinder-tempest-plugin
+      - git.openstack.org/openstack/cloudkitty-tempest-plugin
+      - git.openstack.org/openstack/congress-tempest-plugin
       - git.openstack.org/openstack/designate-tempest-plugin
-      - git.openstack.org/openstack/ec2-api
+      - git.openstack.org/openstack/ec2api-tempest-plugin
       - git.openstack.org/openstack/freezer
       - git.openstack.org/openstack/freezer-api
       - git.openstack.org/openstack/freezer-tempest-plugin
+      - git.openstack.org/openstack/gabbi-tempest
       - git.openstack.org/openstack/gce-api
       - git.openstack.org/openstack/glare
-      - git.openstack.org/openstack/heat
+      - git.openstack.org/openstack/heat-tempest-plugin
       - git.openstack.org/openstack/intel-nfv-ci-tests
-      - git.openstack.org/openstack/ironic
+      - git.openstack.org/openstack/ironic-tempest-plugin
       - git.openstack.org/openstack/ironic-inspector
       - git.openstack.org/openstack/keystone-tempest-plugin
       - git.openstack.org/openstack/kingbird
@@ -230,19 +347,21 @@
       - git.openstack.org/openstack/magnum-tempest-plugin
       - git.openstack.org/openstack/manila
       - git.openstack.org/openstack/manila-tempest-plugin
-      - git.openstack.org/openstack/mistral
+      - git.openstack.org/openstack/mistral-tempest-plugin
       - git.openstack.org/openstack/mogan
       - git.openstack.org/openstack/monasca-api
       - git.openstack.org/openstack/monasca-log-api
-      - git.openstack.org/openstack/murano
+      - git.openstack.org/openstack/monasca-tempest-plugin
+      - git.openstack.org/openstack/murano-tempest-plugin
+      - git.openstack.org/openstack/networking-ansible
       - git.openstack.org/openstack/networking-bgpvpn
       - git.openstack.org/openstack/networking-cisco
       - git.openstack.org/openstack/networking-fortinet
       - git.openstack.org/openstack/networking-generic-switch
-      - git.openstack.org/openstack/networking-l2gw
+      - git.openstack.org/openstack/networking-l2gw-tempest-plugin
       - git.openstack.org/openstack/networking-midonet
-      - git.openstack.org/openstack/networking-plumgrid
       - git.openstack.org/openstack/networking-sfc
+      - git.openstack.org/openstack/networking-spp
       - git.openstack.org/openstack/neutron
       - git.openstack.org/openstack/neutron-dynamic-routing
       - git.openstack.org/openstack/neutron-fwaas
@@ -251,21 +370,25 @@
       - git.openstack.org/openstack/neutron-vpnaas
       - git.openstack.org/openstack/nova-lxd
       - git.openstack.org/openstack/novajoin-tempest-plugin
+      - git.openstack.org/openstack/octavia
       - git.openstack.org/openstack/octavia-tempest-plugin
       - git.openstack.org/openstack/oswin-tempest-plugin
       - git.openstack.org/openstack/panko
       - git.openstack.org/openstack/patrole
+      - git.openstack.org/openstack/python-watcherclient
       - git.openstack.org/openstack/qinling
       - git.openstack.org/openstack/requirements
       - git.openstack.org/openstack/sahara-tests
       - git.openstack.org/openstack/senlin
       - git.openstack.org/openstack/senlin-tempest-plugin
       - git.openstack.org/openstack/tap-as-a-service
+      - git.openstack.org/openstack/telemetry-tempest-plugin
       - git.openstack.org/openstack/tempest-horizon
-      - git.openstack.org/openstack/trio2o
-      - git.openstack.org/openstack/trove
+      - git.openstack.org/openstack/tobiko
+      - git.openstack.org/openstack/tripleo-common-tempest-plugin
+      - git.openstack.org/openstack/trove-tempest-plugin
       - git.openstack.org/openstack/valet
-      - git.openstack.org/openstack/vitrage
+      - git.openstack.org/openstack/vitrage-tempest-plugin
       - git.openstack.org/openstack/vmware-nsx-tempest-plugin
       - git.openstack.org/openstack/watcher-tempest-plugin
       - git.openstack.org/openstack/zaqar-tempest-plugin
@@ -284,11 +407,49 @@
       devstack_localrc:
         TEMPEST_VOLUME_TYPE: volumev2
 
+- job:
+    name: tempest-full-test-account-py3
+    parent: tempest-full-py3
+    description: |
+      This job runs the full set of tempest tests using pre-provisioned
+      credentials instead of dynamic credentials and py3.
+      Former names for this job were:
+        - legacy-tempest-dsvm-full-test-accounts
+        - legacy-tempest-dsvm-neutron-full-test-accounts
+        - legacy-tempest-dsvm-identity-v3-test-accounts
+    vars:
+      devstack_localrc:
+        TEMPEST_USE_TEST_ACCOUNTS: True
+
+- job:
+    name: tempest-full-test-account-no-admin-py3
+    parent: tempest-full-test-account-py3
+    description: |
+      This job runs the full set of tempest tests using pre-provisioned
+      credentials and py3 without having an admin account.
+      Former name for this job was:
+        - legacy-tempest-dsvm-neutron-full-non-admin
+
+    vars:
+      devstack_localrc:
+        TEMPEST_HAS_ADMIN: False
+
+- job:
+    name: tempest-pg-full
+    parent: tempest-full
+    description: |
+      Base integration test with Neutron networking and py27 and PostgreSQL.
+      Former name for this job was legacy-tempest-dsvm-neutron-pg-full.
+    vars:
+      devstack_localrc:
+        ENABLE_FILE_INJECTION: true
+        DATABASE_TYPE: postgresql
+
 - project:
     templates:
       - check-requirements
       - integrated-gate
-      - integrated-gate-py35
+      - integrated-gate-py3
       - openstack-cover-jobs
       - openstack-python-jobs
       - openstack-python35-jobs
@@ -302,6 +463,12 @@
               - ^playbooks/
               - ^roles/
               - ^.zuul.yaml$
+        - devstack-tempest-ipv6:
+            voting: false
+            files:
+              - ^playbooks/
+              - ^roles/
+              - ^.zuul.yaml$
         - nova-multiattach:
             # Define list of irrelevant files to use everywhere else
             irrelevant-files: &tempest-irrelevant-files
@@ -313,9 +480,13 @@
               - ^setup.cfg$
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
+              - ^tools/.*$
         - tempest-full-parallel:
             irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-py36:
+        - tempest-full-py3:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3-ipv6:
+            voting: false
             irrelevant-files: *tempest-irrelevant-files
         - tempest-full-rocky:
             irrelevant-files: *tempest-irrelevant-files
@@ -329,11 +500,19 @@
             irrelevant-files: *tempest-irrelevant-files
         - tempest-multinode-full:
             irrelevant-files: *tempest-irrelevant-files
-        - tempest-tox-plugin-sanity-check
+        - tempest-tox-plugin-sanity-check:
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
+              # tools/ is not here since this relies on a script in tools/.
         - tempest-slow:
             irrelevant-files: *tempest-irrelevant-files
-        - nova-cells-v1:
-            irrelevant-files: *tempest-irrelevant-files
         - nova-live-migration:
             voting: false
             irrelevant-files: *tempest-irrelevant-files
@@ -341,6 +520,8 @@
             irrelevant-files: *tempest-irrelevant-files
         - neutron-grenade:
             irrelevant-files: *tempest-irrelevant-files
+        - grenade-py3:
+            irrelevant-files: *tempest-irrelevant-files
         - devstack-plugin-ceph-tempest:
             voting: false
             irrelevant-files: *tempest-irrelevant-files
@@ -362,6 +543,15 @@
             irrelevant-files: *tempest-irrelevant-files
         - tempest-full:
             irrelevant-files: *tempest-irrelevant-files
+        - interop-tempest-consistency:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-test-account-py3:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-test-account-no-admin-py3:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
     gate:
       jobs:
         - nova-multiattach:
@@ -370,41 +560,33 @@
             irrelevant-files: *tempest-irrelevant-files
         - neutron-grenade-multinode:
             irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-neutron-full:
+        - tempest-full:
             irrelevant-files: *tempest-irrelevant-files
         - neutron-grenade:
             irrelevant-files: *tempest-irrelevant-files
+        - grenade-py3:
+            irrelevant-files: *tempest-irrelevant-files
     experimental:
       jobs:
         - tempest-cinder-v2-api:
             irrelevant-files: *tempest-irrelevant-files
-        - legacy-periodic-tempest-dsvm-all-master:
-            irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-multinode-full:
+        - tempest-all:
             irrelevant-files: *tempest-irrelevant-files
         - legacy-tempest-dsvm-neutron-dvr-multinode-full:
             irrelevant-files: *tempest-irrelevant-files
         - neutron-tempest-dvr-ha-multinode-full:
             irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-full-test-accounts:
-            irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-neutron-full-test-accounts:
-            irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-identity-v3-test-accounts:
-            irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-neutron-full-non-admin:
+        - nova-cells-v1:
             irrelevant-files: *tempest-irrelevant-files
         - legacy-tempest-dsvm-nova-v20-api:
             irrelevant-files: *tempest-irrelevant-files
         - legacy-tempest-dsvm-lvm-multibackend:
             irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-cinder-v1:
-            irrelevant-files: *tempest-irrelevant-files
         - devstack-plugin-ceph-tempest-py3:
             irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-neutron-pg-full:
+        - tempest-pg-full:
             irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-neutron-full-opensuse-423:
+        - tempest-full-py3-opensuse150:
             irrelevant-files: *tempest-irrelevant-files
     periodic-stable:
       jobs:
@@ -416,7 +598,5 @@
         - legacy-periodic-tempest-dsvm-neutron-full-ocata
     periodic:
       jobs:
-        - legacy-periodic-tempest-dsvm-full-test-accounts-master
-        - legacy-periodic-tempest-dsvm-neutron-full-test-accounts-master
-        - legacy-periodic-tempest-dsvm-neutron-full-non-admin-master
-        - legacy-periodic-tempest-dsvm-all-master
+        - tempest-all
+        - tempest-full-oslo-master
diff --git a/HACKING.rst b/HACKING.rst
index e767b25..eb6551a 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -6,7 +6,7 @@
 - Step 2: Read on
 
 Tempest Specific Commandments
-------------------------------
+-----------------------------
 
 - [T102] Cannot import OpenStack python clients in tempest/api &
   tempest/scenario tests
@@ -35,6 +35,30 @@
 - Clean up test data at the completion of each test
 - Use configuration files for values that will vary by environment
 
+Supported OpenStack Components
+------------------------------
+
+Tempest's :ref:`library` and :ref:`plugin interface <tempest_plugin>` can be
+leveraged to support integration testing for virtually any OpenStack component.
+
+However, Tempest only offers **in-tree** integration testing coverage for the
+following components:
+
+* Cinder
+* Glance
+* Keystone
+* Neutron
+* Nova
+* Swift
+
+Historically, Tempest offered in-tree testing for other components as well, but
+since the introduction of the `External Plugin Interface`_, Tempest's in-tree
+testing scope has been limited to the projects above. Integration tests for
+projects not included above should go into one of the
+`relevant plugin projects`_.
+
+.. _External Plugin Interface: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/tempest-external-plugin-interface.html
+.. _relevant plugin projects: https://docs.openstack.org/tempest/latest/plugin-registry.html#detected-plugins
 
 Exception Handling
 ------------------
@@ -431,7 +455,7 @@
 by modifying Tempest's `lib installation script`_ for previous branches
 (because DevStack is branched).
 
-.. _lib installation script: http://git.openstack.org/cgit/openstack-dev/devstack/tree/lib/tempest
+.. _lib installation script: https://git.openstack.org/cgit/openstack-dev/devstack/tree/lib/tempest
 
 2. Bug fix on core project needing Tempest changes
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/README.rst b/README.rst
index 307ceb3..73930f1 100644
--- a/README.rst
+++ b/README.rst
@@ -165,7 +165,7 @@
 interface and when Z is incremented it's a bug fix release for the library.
 Also note that both Y and Z are reset to 0 at each increment of X.
 
-.. _semver: http://semver.org/
+.. _semver: https://semver.org/
 
 Configuration
 -------------
@@ -218,7 +218,7 @@
 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>`_.
+`stestr documentation <https://stestr.readthedocs.io/en/latest/MANUAL.html>`_.
 
 Python 3.x
 ----------
diff --git a/REVIEWING.rst b/REVIEWING.rst
index bf63ed2..31fedce 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -159,8 +159,32 @@
 
 When to approve
 ---------------
-* Every patch needs two +2s before being approved.
-* It's ok to hold off on an approval until a subject matter expert reviews it
-* If a patch has already been approved but requires a trivial rebase to merge,
-  you do not have to wait for a second +2, since the patch has already had
-  two +2s.
+* It's OK to hold off on an approval until a subject matter expert reviews it.
+* Every patch needs two +2's before being approved.
+* However, a single Tempest core reviewer can approve patches without waiting
+  for another +2 in the following cases:
+
+  * If a patch has already been approved but requires a trivial rebase to
+    merge, then there is no need to wait for a second +2, since the patch has
+    already had two +2's.
+  * If any trivial patch set fixes one of the items below:
+
+    * Documentation or code comment typo
+    * Documentation ref link
+    * Example: `example`_
+
+    .. note::
+
+      Any other small documentation, CI job, or code change does not fall under
+      this category.
+
+  * If the patch **unblocks** a failing project gate, provided that:
+
+    * the project's PTL +1's the change
+    * the patch does not affect any other project's testing gates
+    * the patch does not cause any negative side effects
+
+  Note that such a policy should be used judiciously, as we should strive to
+  have two +2's on each patch set, prior to approval.
+
+.. _example: https://review.openstack.org/#/c/611032/
diff --git a/doc/source/data/tempest-plugins-registry.header b/doc/source/data/tempest-plugins-registry.header
index 0de12b7..831d8a6 100644
--- a/doc/source/data/tempest-plugins-registry.header
+++ b/doc/source/data/tempest-plugins-registry.header
@@ -3,9 +3,9 @@
   job.  You should edit the files data/tempest-plugins-registry.footer
   and data/tempest-plugins-registry.header instead of this one.
 
-==========================
- Tempest Plugin Registry
-==========================
+=======================
+Tempest Plugin Registry
+=======================
 
 Since we've created the external plugin mechanism, it's gotten used by
 a lot of projects. The following is a list of plugins that currently
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 9958792..dc0e94c 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -96,7 +96,7 @@
 that users don't have to worry about inadvertently installing a Tempest plugin
 when they install another package.
 
-.. _Branchless Tempest Spec: http://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
+.. _Branchless Tempest Spec: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
 
 The sole advantage to integrating a plugin into an existing python project is
 that it enables you to land code changes at the same time you land test changes
diff --git a/playbooks/devstack-tempest.yaml b/playbooks/devstack-tempest.yaml
index 01155a8..b51e701 100644
--- a/playbooks/devstack-tempest.yaml
+++ b/playbooks/devstack-tempest.yaml
@@ -7,6 +7,11 @@
 
 # We run tests only on one node, regardless how many nodes are in the system
 - hosts: tempest
+  environment:
+    # This enviroment variable is used by the optional tempest-gabbi
+    # job provided by the gabbi-tempest plugin. It can be safely ignored
+    # if that plugin is not being used.
+    GABBI_TEMPEST_PATH: "{{ gabbi_tempest_path }}"
   roles:
     - setup-tempest-run-dir
     - setup-tempest-data-dir
diff --git a/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml b/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml
new file mode 100644
index 0000000..931d689
--- /dev/null
+++ b/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    Add a new config setting ``immutable_user_source`` in the
+    ``[identity-feature-enabled]`` group that defaults to false.
+    This setting, combined with the usage of the ``@testtools.skipIf()``
+    decorator, will allow tests that require user creation, deletion,
+    or modification to skip instead of failing in environments that
+    are LDAP-backed. In such environments, the user source is read-only,
+    so this feature flag is needed to allow such tests to gracefully skip
+    without having to blacklist them.
diff --git a/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml b/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml
new file mode 100644
index 0000000..f245dcb
--- /dev/null
+++ b/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml
@@ -0,0 +1,16 @@
+---
+features:
+  - |
+    A new parameter ``follow_redirects`` has been added to the class
+    ``RestClient``, which is passed through to ``ClosingHttp`` or
+    ``ClosingProxyHttp`` respectively. The default value is ``True``
+    which corresponds to the previous behaviour of following up to five
+    redirections before returning a response. Setting
+    ``follow_redirects = False`` allows to disable this behaviour, so
+    that any redirect that is received is directly returned to the caller.
+    This allows tests to verify that an API is responding with a redirect.
+fixes:
+  - |
+    [`bug 1616892 <https://bugs.launchpad.net/tempest/+bug/1616892>`_]
+    Tempest now allows tests to verify that an API responds with a
+    redirect.
diff --git a/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml b/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml
new file mode 100644
index 0000000..21068ec
--- /dev/null
+++ b/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Adds the new method to AgentsClient that implements agent deletion
+    according to the API [0].
+    [0] https://developer.openstack.org/api-ref/network/v2/index.html#delete-agent
+
diff --git a/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml b/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml
new file mode 100644
index 0000000..a2e23fd
--- /dev/null
+++ b/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+  - |
+    Fixed bug #1791007. ``tempest workspace register`` and ``tempest workspace rename`` CLI will
+    error if None or empty string is passed in --name arguments. Earlier both CLI used to accept
+    the None or empty string as name which was confusing.
+
+
diff --git a/releasenotes/notes/bug-1799883-6ea95fc64f70c9ef.yaml b/releasenotes/notes/bug-1799883-6ea95fc64f70c9ef.yaml
new file mode 100644
index 0000000..630908f
--- /dev/null
+++ b/releasenotes/notes/bug-1799883-6ea95fc64f70c9ef.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    Fixed bug #1799883. ``tempest workspace register`` and ``tempest workspace move`` CLI
+    will now validate the value of the ``--path`` CLI argument and raise an exception if
+    it is None or empty string. Earlier both CLI actions were accepting None or empty path
+    which was confusing.
diff --git a/releasenotes/notes/deprecate-scheduler-available-filters-cbca2017ba3cf2aa.yaml b/releasenotes/notes/deprecate-scheduler-available-filters-cbca2017ba3cf2aa.yaml
new file mode 100644
index 0000000..d0c3a7d
--- /dev/null
+++ b/releasenotes/notes/deprecate-scheduler-available-filters-cbca2017ba3cf2aa.yaml
@@ -0,0 +1,13 @@
+---
+deprecations:
+  - |
+    The ``scheduler_available_filters`` option is being deprecated in favor of
+    ``scheduler_enabled_filters``. The new name is more indicative of what the
+    option means. ``scheduler_enabled_filters``'s default value is set to the
+    default value of Nova's ``enabled_filters``.
+    ``scheduler_available_filters``'s default was `all`. There was confusion
+    around this value. Sometimes it was understood to mean the default Nova
+    filters are enabled, other times it was understood to mean all filters are
+    enabled. While `all` is still allowed for ``scheduler_enabled_filters`` for
+    backwards compatibility, it is strongly recommended to provide an explicit
+    list of filters that matches what's configured in nova.conf.
diff --git a/releasenotes/notes/remove-deprecated-find-test-caller-f4525cd043bfd1b6.yaml b/releasenotes/notes/remove-deprecated-find-test-caller-f4525cd043bfd1b6.yaml
new file mode 100644
index 0000000..f22736f
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-find-test-caller-f4525cd043bfd1b6.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+  - |
+    ``tempest.lib.common.utils.misc.find_test_caller`` was deprecated during
+    Kilo release cycle in favor of
+    ``tempest.lib.common.utils.test_utils.find_test_caller``. The deprecated
+    version of ``find_test_caller`` is removed.
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 71b8e4f..e1787b6 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -56,3 +56,14 @@
                (?x)    # Ignore comments and whitespaces
                # Line with only a comment.
                (tempest.api.identity).*$
+
+.. zuul:rolevar:: tox_extra_args
+   :default: ''
+
+   String of extra command line options to pass to tox.
+
+   Here is an example of running tox with --sitepackages option:
+
+       ::
+           vars:
+             tox_extra_args: --sitepackages
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index c89eb93..06918b5 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -2,3 +2,4 @@
 tempest_test_regex: ''
 tox_envlist: smoke
 tempest_black_regex: ''
+tox_extra_args: ''
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 54ddc71..16086aa 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -35,7 +35,7 @@
       when: blacklist_stat.stat.exists
 
 - name: Run Tempest
-  command: tox -e {{tox_envlist}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} \
+  command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} \
             --concurrency={{tempest_concurrency|default(default_concurrency)}} \
             --black-regex={{tempest_black_regex|quote}}
   args:
diff --git a/setup.cfg b/setup.cfg
index 96ee7ea..fcbe956 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,7 +4,7 @@
 description-file =
     README.rst
 author = OpenStack
-author-email = openstack-dev@lists.openstack.org
+author-email = openstack-discuss@lists.openstack.org
 home-page = https://docs.openstack.org/tempest/latest/
 classifier =
     Intended Audience :: Information Technology
diff --git a/tempest/README.rst b/tempest/README.rst
index a5f4a92..b345032 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -9,7 +9,7 @@
 OpenStack clouds.
 
 As such Tempest tests come in many flavors, each with their own rules
-and guidelines. Below is the overview of the Tempest respository structure
+and guidelines. Below is the overview of the Tempest repository structure
 to make this clear.
 
 .. code-block:: console
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
index f0178aa..dfa801b 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -73,8 +73,17 @@
         # search filter
         fetched_list = (self.client.list_security_groups(all_tenants='true')
                         ['security_groups'])
-        # Now check if all created Security Groups are present in fetched list
-        for sec_group in fetched_list:
-            self.assertEqual(sec_group['tenant_id'], client_tenant_id,
-                             "Failed to get all security groups for "
-                             "non admin user.")
+        sec_group_id_list = [sg['id'] for sg in fetched_list]
+        # Now check that 'all_tenants='true' filter for non-admin user only
+        # provide the requested non-admin user's created security groups,
+        # not all security groups which include security groups created by
+        # other users.
+        for sec_group in security_group_list:
+            if sec_group['tenant_id'] == client_tenant_id:
+                self.assertIn(sec_group['id'], sec_group_id_list,
+                              "Failed to get all security groups for "
+                              "non admin user.")
+            else:
+                self.assertNotIn(sec_group['id'], sec_group_id_list,
+                                 "Non admin user shouldn't get other user's "
+                                 "security groups.")
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index a853182..6b58939 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -70,6 +70,7 @@
     """The test suite for swapping of volume with admin user.
 
     The following is the scenario outline:
+
     1. Create a volume "volume1" with non-admin.
     2. Create a volume "volume2" with non-admin.
     3. Boot an instance "instance1" with non-admin.
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 4e2b59b..09dd409 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -477,7 +477,7 @@
         """Create a volume and wait for it to become 'available'.
 
         :param image_ref: Specify an image id to create a bootable volume.
-        :**kwargs: other parameters to create volume.
+        :param kwargs: other parameters to create volume.
         :returns: The available volume.
         """
         if 'size' not in kwargs:
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 1e952a1..05c2a28 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -20,10 +20,11 @@
 from tempest.api.compute import base
 from tempest.common import compute
 from tempest.common import utils
-from tempest.common.utils.linux import remote_client
 from tempest.common.utils import net_utils
 from tempest.common import waiters
 from tempest import config
+from tempest.lib.common.utils.linux import remote_client
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
@@ -168,7 +169,9 @@
         iface = self.interfaces_client.create_interface(
             server['id'], net_id=network_id,
             fixed_ips=fixed_ips)['interfaceAttachment']
-        self.addCleanup(self.ports_client.delete_port, iface['port_id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ports_client.delete_port,
+                        iface['port_id'])
         self._check_interface(iface, server_id=server['id'],
                               fixed_ip=ip_list[0])
         return iface
@@ -260,16 +263,6 @@
         interface_count = len(ifs)
         self.assertGreater(interface_count, 0)
 
-        try:
-            iface = self._test_create_interface(server)
-        except lib_exc.BadRequest as e:
-            msg = ('Multiple possible networks found, use a Network ID to be '
-                   'more specific.')
-            if not CONF.compute.fixed_network_name and six.text_type(e) == msg:
-                raise
-        else:
-            ifs.append(iface)
-
         iface = self._test_create_interface_by_fixed_ips(server, ifs)
         ifs.append(iface)
 
@@ -340,20 +333,61 @@
     def test_add_remove_fixed_ip(self):
         # Add and Remove the fixed IP to server.
         server, ifs = self._create_server_get_interfaces()
-        interface_count = len(ifs)
-        self.assertGreater(interface_count, 0)
+        original_interface_count = len(ifs)  # This is the number of ports.
+        self.assertGreater(original_interface_count, 0)
+        # Get the starting list of IPs on the server.
+        addresses = self.os_primary.servers_client.list_addresses(
+            server['id'])['addresses']
+        # There should be one entry for the single network mapped to a list of
+        # addresses, which at this point should have at least one entry.
+        # Note that we could start with two addresses depending on how tempest
+        # is configured for using floating IPs.
+        self.assertEqual(1, len(addresses), addresses)  # number of networks
+        # Keep track of the original addresses so we can know which IP is new.
+        original_ips = [addr['addr'] for addr in list(addresses.values())[0]]
+        original_ip_count = len(original_ips)
+        self.assertGreater(original_ip_count, 0, addresses)  # at least 1
         network_id = ifs[0]['net_id']
+        # Add another fixed IP to the server. This should result in another
+        # fixed IP on the same network (and same port since we only have one
+        # port).
         self.servers_client.add_fixed_ip(server['id'], networkId=network_id)
-        # Remove the fixed IP from server.
+        # Wait for the ips count to increase by one.
+
+        def _wait_for_ip_increase():
+            _addresses = self.os_primary.servers_client.list_addresses(
+                server['id'])['addresses']
+            return len(list(_addresses.values())[0]) == original_ip_count + 1
+
+        if not test_utils.call_until_true(
+                _wait_for_ip_increase, CONF.compute.build_timeout,
+                CONF.compute.build_interval):
+            raise lib_exc.TimeoutException(
+                'Timed out while waiting for IP count to increase.')
+
+        # Remove the fixed IP that we just added.
         server_detail = self.os_primary.servers_client.show_server(
             server['id'])['server']
         # Get the Fixed IP from server.
         fixed_ip = None
         for ip_set in server_detail['addresses']:
             for ip in server_detail['addresses'][ip_set]:
-                if ip['OS-EXT-IPS:type'] == 'fixed':
+                if (ip['OS-EXT-IPS:type'] == 'fixed' and
+                        ip['addr'] not in original_ips):
                     fixed_ip = ip['addr']
                     break
             if fixed_ip is not None:
                 break
         self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
+        # Wait for the interface count to decrease by one.
+
+        def _wait_for_ip_decrease():
+            _addresses = self.os_primary.servers_client.list_addresses(
+                server['id'])['addresses']
+            return len(list(_addresses.values())[0]) == original_ip_count
+
+        if not test_utils.call_until_true(
+                _wait_for_ip_decrease, CONF.compute.build_timeout,
+                CONF.compute.build_interval):
+            raise lib_exc.TimeoutException(
+                'Timed out while waiting for IP count to decrease.')
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 0093752..0263b81 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -107,11 +107,10 @@
     @utils.services('volume')
     def test_delete_server_while_in_attached_volume(self):
         # Delete a server while a volume is attached to it
-        device = '/dev/%s' % CONF.compute.volume_device_name
         server = self.create_test_server(wait_until='ACTIVE')
 
         volume = self.create_volume()
-        self.attach_volume(server, volume, device=device)
+        self.attach_volume(server, volume)
 
         self.client.delete_server(server['id'])
         waiters.wait_for_server_termination(self.client, server['id'])
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 18a78f0..f0915de 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -80,7 +80,7 @@
     @decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
     def test_list_servers_status_non_existing(self):
         # When invalid status is specified, up to microversion 2.37,
-        # an empty list is returnd, and starting from microversion 2.38,
+        # an empty list is returned, and starting from microversion 2.38,
         # a 400 error is returned in that case.
         if self.is_requested_microversion_compatible('2.37'):
             body = self.client.list_servers(status='non_existing_status')
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index b0ef3bc..6629794 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -24,11 +24,11 @@
 CONF = config.CONF
 
 
-class ServerRescueTestJSON(base.BaseV2ComputeTest):
+class ServerRescueTestBase(base.BaseV2ComputeTest):
 
     @classmethod
     def skip_checks(cls):
-        super(ServerRescueTestJSON, cls).skip_checks()
+        super(ServerRescueTestBase, cls).skip_checks()
         if not CONF.compute_feature_enabled.rescue:
             msg = "Server rescue not available."
             raise cls.skipException(msg)
@@ -36,11 +36,11 @@
     @classmethod
     def setup_credentials(cls):
         cls.set_network_resources(network=True, subnet=True, router=True)
-        super(ServerRescueTestJSON, cls).setup_credentials()
+        super(ServerRescueTestBase, cls).setup_credentials()
 
     @classmethod
     def resource_setup(cls):
-        super(ServerRescueTestJSON, cls).resource_setup()
+        super(ServerRescueTestBase, cls).resource_setup()
 
         password = data_utils.rand_password()
         server = cls.create_test_server(adminPass=password,
@@ -50,6 +50,9 @@
                                        'RESCUE')
         cls.rescued_server_id = server['id']
 
+
+class ServerRescueTestJSON(ServerRescueTestBase):
+
     @decorators.idempotent_id('fd032140-714c-42e4-a8fd-adcd8df06be6')
     def test_rescue_unrescue_instance(self):
         password = data_utils.rand_password()
@@ -62,6 +65,15 @@
         waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'ACTIVE')
 
+
+class ServerRescueTestJSONUnderV235(ServerRescueTestBase):
+
+    max_microversion = '2.35'
+
+    # TODO(zhufl): After 2.35 we should switch to neutron client to create
+    # floating ip, but that will need admin credential, so the testcases will
+    # have to be added in somewhere in admin directory.
+
     @decorators.idempotent_id('4842e0cf-e87d-4d9d-b61f-f4791da3cacc')
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 1260c6b..caceb64 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -43,7 +43,6 @@
     @classmethod
     def resource_setup(cls):
         super(ServerRescueNegativeTestJSON, cls).resource_setup()
-        cls.device = CONF.compute.volume_device_name
         cls.password = data_utils.rand_password()
         rescue_password = data_utils.rand_password()
         # Server for negative tests
@@ -125,8 +124,7 @@
         self.assertRaises(lib_exc.Conflict,
                           self.servers_client.attach_volume,
                           self.server_id,
-                          volumeId=volume['id'],
-                          device='/dev/%s' % self.device)
+                          volumeId=volume['id'])
 
     @decorators.idempotent_id('f56e465b-fe10-48bf-b75d-646cda3a8bc9')
     @utils.services('volume')
@@ -136,7 +134,7 @@
 
         # Attach the volume to the server
         server = self.servers_client.show_server(self.server_id)['server']
-        self.attach_volume(server, volume, device='/dev/%s' % self.device)
+        self.attach_volume(server, volume)
 
         # Rescue the server
         self.servers_client.rescue_server(self.server_id,
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 811b521..f7b5b4b 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -84,6 +84,11 @@
             linux_client.validate_authentication()
 
         volume = self.create_volume()
+
+        # NOTE: As of the 12.0.0 Liberty release, the Nova libvirt driver
+        # no longer honors a user-supplied device name, in that case
+        # CONF.compute.volume_device_name must be set the equal value as
+        # the libvirt auto-assigned one
         attachment = self.attach_volume(server, volume,
                                         device=('/dev/%s' % self.device))
 
@@ -121,8 +126,7 @@
         # List volume attachment of the server
         server, _ = self._create_server()
         volume_1st = self.create_volume()
-        attachment_1st = self.attach_volume(server, volume_1st,
-                                            device=('/dev/%s' % self.device))
+        attachment_1st = self.attach_volume(server, volume_1st)
         body = self.servers_client.list_volume_attachments(
             server['id'])['volumeAttachments']
         self.assertEqual(1, len(body))
@@ -234,8 +238,7 @@
         volume = self.create_volume()
         num_vol = self._count_volumes(server, validation_resources)
         self._shelve_server(server, validation_resources)
-        attachment = self.attach_volume(server, volume,
-                                        device=('/dev/%s' % self.device))
+        attachment = self.attach_volume(server, volume)
 
         # Unshelve the instance and check that attached volume exists
         self._unshelve_server_and_check_volumes(
@@ -264,7 +267,7 @@
         self._shelve_server(server, validation_resources)
 
         # Attach and then detach the volume
-        self.attach_volume(server, volume, device=('/dev/%s' % self.device))
+        self.attach_volume(server, volume)
         self.servers_client.detach_volume(server['id'], volume['id'])
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume['id'], 'available')
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index 8618148..6d08f90 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -35,9 +35,7 @@
     def test_delete_attached_volume(self):
         server = self.create_test_server(wait_until='ACTIVE')
         volume = self.create_volume()
-
-        path = "/dev/%s" % CONF.compute.volume_device_name
-        self.attach_volume(server, volume, device=path)
+        self.attach_volume(server, volume)
 
         self.assertRaises(lib_exc.BadRequest,
                           self.delete_volume, volume['id'])
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index e2ed5ef..03543ac 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -89,14 +89,10 @@
             service = self.services_client.create_service(
                 name=name, type=s_type,
                 description=description)['OS-KSADM:service']
+            self.addCleanup(self.services_client.delete_service, service['id'])
             services.append(service)
         service_ids = [svc['id'] for svc in services]
 
-        def delete_services():
-            for service_id in service_ids:
-                self.services_client.delete_service(service_id)
-
-        self.addCleanup(delete_services)
         # List and Verify Services
         body = self.services_client.list_services()['OS-KSADM:services']
         found = [serv for serv in body if serv['id'] in service_ids]
diff --git a/tempest/api/identity/admin/v2/test_tenants.py b/tempest/api/identity/admin/v2/test_tenants.py
index cda721c..f68754e 100644
--- a/tempest/api/identity/admin/v2/test_tenants.py
+++ b/tempest/api/identity/admin/v2/test_tenants.py
@@ -50,7 +50,7 @@
                          'been sent in response for create')
         body = self.tenants_client.show_tenant(tenant_id)['tenant']
         desc2 = body['description']
-        self.assertEqual(desc2, tenant_desc, 'Description does not appear'
+        self.assertEqual(desc2, tenant_desc, 'Description does not appear '
                          'to be set')
         self.tenants_client.delete_tenant(tenant_id)
 
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index 302a0e5..a79cbc3 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -9,6 +9,8 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+import testtools
+
 from tempest.api.identity import base
 from tempest import clients
 from tempest import config
@@ -32,6 +34,10 @@
         self.domains_client.update_domain(domain_id, enabled=False)
         self.domains_client.delete_domain(domain_id)
 
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an '
+                      'immutable user source and solely '
+                      'provides read-only access to users.')
     @decorators.idempotent_id('d6110661-6a71-49a7-a453-b5e26640ff6d')
     def test_default_project_id(self):
         # create a domain
diff --git a/tempest/api/identity/admin/v3/test_endpoint_groups.py b/tempest/api/identity/admin/v3/test_endpoint_groups.py
index eef93c2..625568d 100644
--- a/tempest/api/identity/admin/v3/test_endpoint_groups.py
+++ b/tempest/api/identity/admin/v3/test_endpoint_groups.py
@@ -20,6 +20,10 @@
 
 
 class EndPointGroupsTest(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def setup_clients(cls):
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 148b368..50f3186 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -14,9 +14,12 @@
 #    under the License.
 
 from tempest.api.identity import base
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
+CONF = config.CONF
+
 
 class BaseListProjectsTestJSON(base.BaseIdentityV3AdminTest):
 
@@ -60,34 +63,12 @@
                                     cls.p3['id'])
         cls.project_ids.append(cls.p3['id'])
 
-    @decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
-    def test_list_projects(self):
-        # List projects
-        list_projects = self.projects_client.list_projects()['projects']
-
-        for p in self.project_ids:
-            show_project = self.projects_client.show_project(p)['project']
-            self.assertIn(show_project, list_projects)
-
-    @decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10')
-    def test_list_projects_with_domains(self):
-        # List projects with domain
-        self._list_projects_with_params(
-            [self.p1], [self.p2, self.p3], {'domain_id': self.domain['id']},
-            'domain_id')
-
     @decorators.idempotent_id('0fe7a334-675a-4509-b00e-1c4b95d5dae8')
     def test_list_projects_with_enabled(self):
         # List the projects with enabled
         self._list_projects_with_params(
             [self.p1], [self.p2, self.p3], {'enabled': False}, 'enabled')
 
-    @decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26')
-    def test_list_projects_with_name(self):
-        # List projects with name
-        self._list_projects_with_params(
-            [self.p1], [self.p2, self.p3], {'name': self.p1_name}, 'name')
-
     @decorators.idempotent_id('6edc66f5-2941-4a17-9526-4073311c1fac')
     def test_list_projects_with_parent(self):
         # List projects with parent
@@ -97,3 +78,51 @@
         self.assertNotEmpty(fetched_projects)
         for project in fetched_projects:
             self.assertEqual(self.p3['parent_id'], project['parent_id'])
+
+
+class ListProjectsStaticTestJSON(BaseListProjectsTestJSON):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
+
+    @classmethod
+    def resource_setup(cls):
+        super(ListProjectsStaticTestJSON, cls).resource_setup()
+        cls.domain_id = CONF.identity.default_domain_id
+        cls.project_ids = list()
+        cls.p1_name = cls.os_primary.credentials.project_name
+        cls.p1 = cls.projects_client.show_project(
+            cls.os_primary.credentials.project_id)['project']
+        cls.project_ids.append(cls.p1['id'])
+        p2_name = data_utils.rand_name('project')
+        cls.p2 = cls.projects_client.create_project(
+            p2_name, domain_id=cls.domain_id)['project']
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.p2['id'])
+        cls.project_ids.append(cls.p2['id'])
+
+    @decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
+    def test_list_projects(self):
+        # List projects
+        list_projects = self.projects_client.list_projects()['projects']
+
+        for p in self.project_ids:
+            show_project = self.projects_client.show_project(p)['project']
+            self.assertIn(show_project, list_projects)
+
+    @decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26')
+    def test_list_projects_with_name(self):
+        # List projects with name
+        self._list_projects_with_params(
+            [self.p1], [self.p2], {'name': self.p1_name}, 'name')
+
+    @decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10')
+    def test_list_projects_with_domains(self):
+        # List projects with domain
+        key = 'domain_id'
+        params = {key: self.domain_id}
+        # Verify both projects are in the self.domain_id which is the default
+        # domain
+        self._list_projects_with_params(
+            [self.p1, self.p2], [], params, key)
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 6ddf42e..f75edaa 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -31,7 +31,7 @@
                          'been sent in response for create')
         body = self.projects_client.show_project(project_id)['project']
         desc2 = body['description']
-        self.assertEqual(desc2, project_desc, 'Description does not appear'
+        self.assertEqual(desc2, project_desc, 'Description does not appear '
                          'to be set')
 
     @decorators.idempotent_id('5f50fe07-8166-430b-a882-3b2ee0abe26f')
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index 33a9c8c..12f1d4a 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -22,6 +22,22 @@
 class ProjectsNegativeTestJSON(base.BaseIdentityV3AdminTest):
 
     @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97')
+    def test_project_delete_by_unauthorized_user(self):
+        # Non-admin user should not be able to delete a project
+        project = self.setup_test_project()
+        self.assertRaises(
+            lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
+            project['id'])
+
+
+class ProjectsNegativeStaticTestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
+
+    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('24c49279-45dd-4155-887a-cb738c2385aa')
     def test_list_projects_by_unauthorized_user(self):
         # Non-admin user should not be able to list projects
@@ -63,15 +79,6 @@
                           self.projects_client.create_project, project_name)
 
     @decorators.attr(type=['negative'])
-    @decorators.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97')
-    def test_project_delete_by_unauthorized_user(self):
-        # Non-admin user should not be able to delete a project
-        project = self.setup_test_project()
-        self.assertRaises(
-            lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
-            project['id'])
-
-    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('7965b581-60c1-43b7-8169-95d4ab7fc6fb')
     def test_delete_non_existent_project(self):
         # Attempt to delete a non existent project should fail
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 2530072..83b3c30 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -39,7 +39,6 @@
         # Use alt_username as the trustee
         self.trust_id = None
         self.create_trustor_and_roles()
-        self.addCleanup(self.cleanup_user_and_roles)
 
     def tearDown(self):
         if self.trust_id:
@@ -55,6 +54,7 @@
             trustor_project_name,
             domain_id=CONF.identity.default_domain_id)['project']
         self.trustor_project_id = project['id']
+        self.addCleanup(self.projects_client.delete_project, project['id'])
         self.assertIsNotNone(self.trustor_project_id)
 
         # Create a trustor User
@@ -69,6 +69,7 @@
             email=u_email,
             project_id=self.trustor_project_id,
             domain_id=CONF.identity.default_domain_id)['user']
+        self.addCleanup(self.users_client.delete_user, user['id'])
         self.trustor_user_id = user['id']
 
         # And two roles, one we'll delegate and one we won't
@@ -76,10 +77,12 @@
         self.not_delegated_role = data_utils.rand_name('NotDelegatedRole')
 
         role = self.roles_client.create_role(name=self.delegated_role)['role']
+        self.addCleanup(self.roles_client.delete_role, role['id'])
         self.delegated_role_id = role['id']
 
         role = self.roles_client.create_role(
             name=self.not_delegated_role)['role']
+        self.addCleanup(self.roles_client.delete_role, role['id'])
         self.not_delegated_role_id = role['id']
 
         # Assign roles to trustor
@@ -109,16 +112,6 @@
         os = clients.Manager(credentials=creds)
         self.trustor_client = os.trusts_client
 
-    def cleanup_user_and_roles(self):
-        if self.trustor_user_id:
-            self.users_client.delete_user(self.trustor_user_id)
-        if self.trustor_project_id:
-            self.projects_client.delete_project(self.trustor_project_id)
-        if self.delegated_role_id:
-            self.roles_client.delete_role(self.delegated_role_id)
-        if self.not_delegated_role_id:
-            self.roles_client.delete_role(self.not_delegated_role_id)
-
     def create_trust(self, impersonate=True, expires=None):
 
         trust_create = self.trustor_client.create_trust(
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 3813568..8955a93 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -28,6 +28,14 @@
 
 class UsersV3TestJSON(base.BaseIdentityV3AdminTest):
 
+    @classmethod
+    def skip_checks(cls):
+        super(UsersV3TestJSON, cls).skip_checks()
+        if CONF.identity_feature_enabled.immutable_user_source:
+            raise cls.skipException('Skipped because environment has an '
+                                    'immutable user source and solely '
+                                    'provides read-only access to users.')
+
     @decorators.idempotent_id('b537d090-afb9-4519-b95d-270b0708e87e')
     def test_user_update(self):
         # Test case to check if updating of user attributes is successful.
diff --git a/tempest/api/identity/v2/test_ec2_credentials.py b/tempest/api/identity/v2/test_ec2_credentials.py
index 237e728..9981ef8 100644
--- a/tempest/api/identity/v2/test_ec2_credentials.py
+++ b/tempest/api/identity/v2/test_ec2_credentials.py
@@ -57,18 +57,19 @@
             self.creds.user_id,
             tenant_id=self.creds.tenant_id)["credential"]
         created_creds.append(creds1['access'])
+        self.addCleanup(
+            self.non_admin_users_client.delete_user_ec2_credential,
+            self.creds.user_id, creds1['access'])
+
         # create second ec2 credentials
         creds2 = self.non_admin_users_client.create_user_ec2_credential(
             self.creds.user_id,
             tenant_id=self.creds.tenant_id)["credential"]
         created_creds.append(creds2['access'])
-        # add credentials to be cleaned up
-        self.addCleanup(
-            self.non_admin_users_client.delete_user_ec2_credential,
-            self.creds.user_id, creds1['access'])
         self.addCleanup(
             self.non_admin_users_client.delete_user_ec2_credential,
             self.creds.user_id, creds2['access'])
+
         # get the list of user ec2 credentials
         resp = self.non_admin_users_client.list_user_ec2_credentials(
             self.creds.user_id)["credentials"]
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 6d6baca..13b5161 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -133,6 +133,13 @@
                           'Security compliance not available.')
     @decorators.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658')
     def test_user_account_lockout(self):
+        if (CONF.identity.user_lockout_failure_attempts <= 0 or
+                CONF.identity.user_lockout_duration <= 0):
+            raise self.skipException(
+                "Both CONF.identity.user_lockout_failure_attempts and "
+                "CONF.identity.user_lockout_duration should be greater than "
+                "zero to test this feature")
+
         password = self.creds.password
 
         # First, we login using the correct credentials
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index aa57daf..c938cee 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -185,7 +185,7 @@
                      for disk_fmt in disk_fmts]
 
         for (container_fmt, disk_fmt) in all_pairs[:6]:
-            LOG.debug("Creating an image"
+            LOG.debug("Creating an image "
                       "(Container format: %s, Disk format: %s).",
                       container_fmt, disk_fmt)
             cls._create_standard_image(container_fmt, disk_fmt)
diff --git a/tempest/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
index 5068fc4..eaf477c 100644
--- a/tempest/api/network/admin/test_agent_management.py
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -15,7 +15,9 @@
 from tempest.api.network import base
 from tempest.common import tempest_fixtures as fixtures
 from tempest.common import utils
+from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
 
 
 class AgentManagementTestJSON(base.BaseAdminNetworkTest):
@@ -46,11 +48,6 @@
             agent.pop('configurations', None)
         self.assertIn(self.agent, agents)
 
-    @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
-    def test_list_agents_non_admin(self):
-        body = self.agents_client.list_agents()
-        self.assertEmpty(body["agents"])
-
     @decorators.idempotent_id('869bc8e8-0fda-4a30-9b71-f8a7cf58ca9f')
     def test_show_agent(self):
         body = self.admin_agents_client.show_agent(self.agent['id'])
@@ -86,3 +83,11 @@
         origin_agent = {'description': description}
         self.admin_agents_client.update_agent(agent_id=self.agent['id'],
                                               agent=origin_agent)
+
+    @decorators.idempotent_id('b33af888-b6ac-4e68-a0ca-0444c2696cf9')
+    @decorators.attr(type=['negative'])
+    def test_delete_agent_negative(self):
+        non_existent_id = data_utils.rand_uuid()
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.admin_agents_client.delete_agent, non_existent_id)
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index e79f8c3..a075b51 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -14,7 +14,9 @@
 #    under the License.
 
 from tempest.api.network import base
+from tempest.common import identity
 from tempest.common import utils
+from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
@@ -30,7 +32,6 @@
 
         quota_driver = neutron.db.quota_db.DbQuotaDriver
     """
-    force_tenant_isolation = True
 
     @classmethod
     def skip_checks(cls):
@@ -39,27 +40,36 @@
             msg = "quotas extension not enabled."
             raise cls.skipException(msg)
 
+    def setUp(self):
+        super(QuotasNegativeTest, self).setUp()
+        name = data_utils.rand_name('test_project_')
+        description = data_utils.rand_name('desc_')
+        self.project = identity.identity_utils(self.os_admin).create_project(
+            name=name, description=description)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+                        self.project['id'])
+
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('644f4e1b-1bf9-4af0-9fd8-eb56ac0f51cf')
     def test_network_quota_exceeding(self):
         # Set the network quota to two
-        self.admin_quotas_client.update_quotas(self.networks_client.tenant_id,
-                                               network=2)
-        self.addCleanup(self.admin_quotas_client.reset_quotas,
-                        self.networks_client.tenant_id)
+        self.admin_quotas_client.update_quotas(self.project['id'], network=2)
 
         # Create two networks
-        n1 = self.networks_client.create_network()
-        self.addCleanup(self.networks_client.delete_network,
+        n1 = self.admin_networks_client.create_network(
+            tenant_id=self.project['id'])
+        self.addCleanup(self.admin_networks_client.delete_network,
                         n1['network']['id'])
-        n2 = self.networks_client.create_network()
-        self.addCleanup(self.networks_client.delete_network,
+        n2 = self.admin_networks_client.create_network(
+            tenant_id=self.project['id'])
+        self.addCleanup(self.admin_networks_client.delete_network,
                         n2['network']['id'])
 
         # Try to create a third network while the quota is two
         with self.assertRaisesRegex(
                 lib_exc.Conflict,
                 r"Quota exceeded for resources: \['network'\].*"):
-            n3 = self.networks_client.create_network()
-            self.addCleanup(self.networks_client.delete_network,
+            n3 = self.admin_networks_client.create_network(
+                tenant_id=self.project['id'])
+            self.addCleanup(self.admin_networks_client.delete_network,
                             n3['network']['id'])
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
index a7355f3..6ce86fb 100644
--- a/tempest/api/network/admin/test_routers.py
+++ b/tempest/api/network/admin/test_routers.py
@@ -111,7 +111,8 @@
     def _verify_gateway_port(self, router_id):
         list_body = self.admin_ports_client.list_ports(
             network_id=CONF.network.public_network_id,
-            device_id=router_id)
+            device_id=router_id,
+            device_owner="network:router_gateway")
         self.assertEqual(len(list_body['ports']), 1)
         gw_port = list_body['ports'][0]
         fixed_ips = gw_port['fixed_ips']
diff --git a/tempest/api/network/test_agent_management_negative.py b/tempest/api/network/test_agent_management_negative.py
new file mode 100644
index 0000000..d1c02ce
--- /dev/null
+++ b/tempest/api/network/test_agent_management_negative.py
@@ -0,0 +1,28 @@
+# Copyright 2018 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.network import base
+from tempest.lib import decorators
+
+
+class AgentManagementNegativeTest(base.BaseNetworkTest):
+
+    @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
+    @decorators.attr(type=['negative'])
+    def test_list_agents_non_admin(self):
+        """Validate that non-admin user cannot list agents."""
+        # Listing agents requires admin_only permissions.
+        body = self.agents_client.list_agents()
+        self.assertEmpty(body["agents"])
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index 0730d58..3ab2909 100644
--- a/tempest/api/network/test_dhcp_ipv6.py
+++ b/tempest/api/network/test_dhcp_ipv6.py
@@ -135,7 +135,7 @@
             real_ip, eui_ip = self._get_ips_from_subnet(**kwargs)
             self._clean_network()
             self.assertEqual(eui_ip, real_ip,
-                             ('Real port IP %s shall be equal to EUI-64 %s'
+                             ('Real port IP %s shall be equal to EUI-64 %s '
                               'when ipv6_ra_mode=%s,ipv6_address_mode=%s') % (
                                  real_ip, eui_ip,
                                  ra_mode if ra_mode else "Off",
@@ -167,7 +167,7 @@
         self._clean_network()
         self.assertNotEqual(eui_ip, real_ip,
                             ('Real port IP %s equal to EUI-64 %s when '
-                             'ipv6_ra_mode=Off and ipv6_address_mode=Off,'
+                             'ipv6_ra_mode=Off and ipv6_address_mode=Off, '
                              'but shall be taken from fixed IPs') % (
                                 real_ip, eui_ip))
 
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 8b03631..be3cf65 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -27,22 +27,6 @@
 
 class RoutersTest(base.BaseNetworkTest):
 
-    def _cleanup_router(self, router):
-        self.delete_router(router)
-
-    def _create_router(self, name=None, admin_state_up=False,
-                       external_network_id=None, enable_snat=None):
-        # associate a cleanup with created routers to avoid quota limits
-        router = self.create_router(name, admin_state_up,
-                                    external_network_id, enable_snat)
-        self.addCleanup(self._cleanup_router, router)
-        return router
-
-    def _create_subnet(self, network, gateway='', cidr=None):
-        subnet = self.create_subnet(network, gateway, cidr)
-        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
-        return subnet
-
     def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
         interface = self.routers_client.add_router_interface(
             router_id, subnet_id=subnet_id)
@@ -70,10 +54,11 @@
     def test_create_show_list_update_delete_router(self):
         # Create a router
         router_name = data_utils.rand_name(self.__class__.__name__ + '-router')
-        router = self._create_router(
-            name=router_name,
+        router = self.create_router(
+            router_name,
             admin_state_up=False,
             external_network_id=CONF.network.public_network_id)
+        self.addCleanup(self.delete_router, router)
         self.assertEqual(router['name'], router_name)
         self.assertEqual(router['admin_state_up'], False)
         self.assertEqual(
@@ -106,8 +91,10 @@
             name=network_name)['network']
         self.addCleanup(self.networks_client.delete_network,
                         network['id'])
-        subnet = self._create_subnet(network)
-        router = self._create_router()
+        subnet = self.create_subnet(network)
+        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         # Add router interface with subnet id
         interface = self.routers_client.add_router_interface(
             router['id'], subnet_id=subnet['id'])
@@ -129,8 +116,10 @@
             name=network_name)['network']
         self.addCleanup(self.networks_client.delete_network,
                         network['id'])
-        self._create_subnet(network)
-        router = self._create_router()
+        subnet = self.create_subnet(network)
+        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         port_body = self.ports_client.create_port(
             network_id=network['id'])
         # add router interface to port created above
@@ -188,7 +177,8 @@
         test_routes = []
         routes_num = 4
         # Create a router
-        router = self._create_router(admin_state_up=True)
+        router = self.create_router(admin_state_up=True)
+        self.addCleanup(self.delete_router, router)
         self.addCleanup(
             self._delete_extra_routes,
             router['id'])
@@ -201,6 +191,7 @@
             self.addCleanup(self.networks_client.delete_network,
                             network['id'])
             subnet = self.create_subnet(network, cidr=next_cidr)
+            self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
             next_cidr = next_cidr.next()
 
             # Add router interface with subnet id
@@ -247,7 +238,8 @@
 
     @decorators.idempotent_id('a8902683-c788-4246-95c7-ad9c6d63a4d9')
     def test_update_router_admin_state(self):
-        router = self._create_router()
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         self.assertFalse(router['admin_state_up'])
         # Update router admin state
         update_body = self.routers_client.update_router(router['id'],
@@ -264,14 +256,18 @@
             name=network_name)['network']
         self.addCleanup(self.networks_client.delete_network,
                         network01['id'])
+        network_name = data_utils.rand_name(self.__class__.__name__)
         network02 = self.networks_client.create_network(
-            name=data_utils.rand_name(self.__class__.__name__))['network']
+            name=network_name)['network']
         self.addCleanup(self.networks_client.delete_network,
                         network02['id'])
-        subnet01 = self._create_subnet(network01)
+        subnet01 = self.create_subnet(network01)
+        self.addCleanup(self.subnets_client.delete_subnet, subnet01['id'])
         sub02_cidr = self.cidr.next()
-        subnet02 = self._create_subnet(network02, cidr=sub02_cidr)
-        router = self._create_router()
+        subnet02 = self.create_subnet(network02, cidr=sub02_cidr)
+        self.addCleanup(self.subnets_client.delete_subnet, subnet02['id'])
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         interface01 = self._add_router_interface_with_subnet_id(router['id'],
                                                                 subnet01['id'])
         self._verify_router_interface(router['id'], subnet01['id'],
@@ -288,8 +284,10 @@
             name=network_name)['network']
         self.addCleanup(self.networks_client.delete_network,
                         network['id'])
-        subnet = self._create_subnet(network)
-        router = self._create_router()
+        subnet = self.create_subnet(network)
+        self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+        router = self.create_router()
+        self.addCleanup(self.delete_router, router)
         fixed_ip = [{'subnet_id': subnet['id']}]
         interface = self._add_router_interface_with_subnet_id(router['id'],
                                                               subnet['id'])
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index 765bc6d..e9ca0b1 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -38,9 +38,9 @@
     def test_read_object_with_rights(self):
         # attempt to read object using authorized user
         # update X-Container-Read metadata ACL
-        tenant_name = self.os_roles_operator_alt.credentials.tenant_name
-        username = self.os_roles_operator_alt.credentials.username
-        cont_headers = {'X-Container-Read': tenant_name + ':' + username}
+        tenant_id = self.os_roles_operator_alt.credentials.tenant_id
+        user_id = self.os_roles_operator_alt.credentials.user_id
+        cont_headers = {'X-Container-Read': tenant_id + ':' + user_id}
         container_client = self.os_roles_operator.container_client
         resp_meta, _ = (
             container_client.create_update_or_delete_container_metadata(
@@ -66,9 +66,9 @@
     def test_write_object_with_rights(self):
         # attempt to write object using authorized user
         # update X-Container-Write metadata ACL
-        tenant_name = self.os_roles_operator_alt.credentials.tenant_name
-        username = self.os_roles_operator_alt.credentials.username
-        cont_headers = {'X-Container-Write': tenant_name + ':' + username}
+        tenant_id = self.os_roles_operator_alt.credentials.tenant_id
+        user_id = self.os_roles_operator_alt.credentials.user_id
+        cont_headers = {'X-Container-Write': tenant_id + ':' + user_id}
         container_client = self.os_roles_operator.container_client
         resp_meta, _ = (
             container_client.create_update_or_delete_container_metadata(
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 982c4a1..fcd9a7c 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -31,9 +31,10 @@
 
         Quotas are set by adding meta values to the container,
         and are validated when set:
-          - X-Container-Meta-Quota-Bytes:
+
+        - X-Container-Meta-Quota-Bytes:
                      Maximum size of the container, in bytes.
-          - X-Container-Meta-Quota-Count:
+        - X-Container-Meta-Quota-Count:
                      Maximum object count of the container.
         """
         super(ContainerQuotasTest, self).setUp()
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index 915aeec..5c7ab15 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -45,13 +45,6 @@
         cls.addClassResourceCleanup(cls.admin_quotas_client.update_quota_set,
                                     cls.demo_tenant_id, **cleanup_quota_set)
 
-        cls.shared_quota_set = {'gigabytes': 2 * CONF.volume.volume_size,
-                                'volumes': 1}
-
-        cls.admin_quotas_client.update_quota_set(
-            cls.demo_tenant_id,
-            **cls.shared_quota_set)
-
         # NOTE(gfidente): no need to delete in tearDown as
         # they are created using utility wrapper methods.
         cls.volume = cls.create_volume()
@@ -59,6 +52,8 @@
     @decorators.attr(type='negative')
     @decorators.idempotent_id('bf544854-d62a-47f2-a681-90f7a47d86b6')
     def test_quota_volumes(self):
+        self.admin_quotas_client.update_quota_set(self.demo_tenant_id,
+                                                  volumes=1, gigabytes=-1)
         self.assertRaises(lib_exc.OverLimit,
                           self.volumes_client.create_volume,
                           size=CONF.volume.volume_size)
@@ -66,17 +61,18 @@
     @decorators.attr(type='negative')
     @decorators.idempotent_id('2dc27eee-8659-4298-b900-169d71a91374')
     def test_quota_volume_gigabytes(self):
-        # NOTE(gfidente): quota set needs to be changed for this test
-        # or we may be limited by the volumes or snaps quota number, not by
-        # actual gigs usage; next line ensures shared set is restored.
-        self.addCleanup(self.admin_quotas_client.update_quota_set,
-                        self.demo_tenant_id,
-                        **self.shared_quota_set)
-        new_quota_set = {'gigabytes': CONF.volume.volume_size,
-                         'volumes': 2, 'snapshots': 1}
         self.admin_quotas_client.update_quota_set(
-            self.demo_tenant_id,
-            **new_quota_set)
+            self.demo_tenant_id, gigabytes=CONF.volume.volume_size, volumes=-1)
         self.assertRaises(lib_exc.OverLimit,
                           self.volumes_client.create_volume,
-                          size=CONF.volume.volume_size)
+                          size=CONF.volume.volume_size * 2)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('d321dc21-d8c6-401f-95fe-49f4845f1a6d')
+    def test_volume_extend_gigabytes_quota_deviation(self):
+        self.admin_quotas_client.update_quota_set(
+            self.demo_tenant_id, gigabytes=CONF.volume.volume_size)
+        self.assertRaises(lib_exc.OverLimit,
+                          self.volumes_client.extend_volume,
+                          self.volume['id'],
+                          new_size=CONF.volume.volume_size * 2)
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 1077524..9e24176 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -193,8 +193,13 @@
                   'is_public': is_public}
         updated_vol_type = self.admin_volume_types_client.update_volume_type(
             volume_type['id'], **kwargs)['volume_type']
-
-        # Verify volume type details were updated
         self.assertEqual(name, updated_vol_type['name'])
         self.assertEqual(description, updated_vol_type['description'])
         self.assertEqual(is_public, updated_vol_type['is_public'])
+
+        # Verify volume type details were updated
+        fetched_volume_type = self.admin_volume_types_client.show_volume_type(
+            volume_type['id'])['volume_type']
+        self.assertEqual(name, fetched_volume_type['name'])
+        self.assertEqual(description, fetched_volume_type['description'])
+        self.assertEqual(is_public, fetched_volume_type['is_public'])
diff --git a/tempest/clients.py b/tempest/clients.py
index 204ce08..e5d5be1 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -82,10 +82,8 @@
             self.schemas_client = self.image_v2.SchemasClient()
             self.namespace_properties_client = \
                 self.image_v2.NamespacePropertiesClient()
-            self.namespace_tags_client = \
-                self.image_v2.NamespaceTagsClient()
-            self.image_versions_client = \
-                self.image_v2.VersionsClient()
+            self.namespace_tags_client = self.image_v2.NamespaceTagsClient()
+            self.image_versions_client = self.image_v2.VersionsClient()
 
     def _set_compute_clients(self):
         self.agents_client = self.compute.AgentsClient()
@@ -284,8 +282,7 @@
                 self.volume_v3.QuotaClassesClient()
             self.volume_scheduler_stats_v2_client = \
                 self.volume_v3.SchedulerStatsClient()
-            self.volume_transfers_v2_client = \
-                self.volume_v3.TransfersClient()
+            self.volume_transfers_v2_client = self.volume_v3.TransfersClient()
             self.volume_v2_availability_zone_client = \
                 self.volume_v3.AvailabilityZoneClient()
             self.volume_v2_limits_client = self.volume_v3.LimitsClient()
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index 29abd49..2f54f9a 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -279,7 +279,7 @@
                                                        self.admin_id,
                                                        self.admin_role_id)
             except Exception as ex:
-                LOG.exception("Failed removing role from project which still"
+                LOG.exception("Failed removing role from project which still "
                               "exists, exception: %s", ex)
 
     def _project_exists(self, project_id):
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 84c6d9a..3e84b82 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -243,8 +243,8 @@
         parser.add_argument('--load-list', '--load_list',
                             help='Path to a non-regex whitelist file, '
                                  'this file contains a separate test '
-                                 'on each newline. This command'
-                                 'supports files created by the tempest'
+                                 'on each newline. This command '
+                                 'supports files created by the tempest '
                                  'run ``--list-tests`` command')
         # list only args
         parser.add_argument('--list-tests', '-l', action='store_true',
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 6c2fee8..d25d3ca 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -365,11 +365,11 @@
         catalog_type = getattr(cfg, 'catalog_type', None)
         if not catalog_type:
             continue
-        else:
-            if cfgname == 'identity':
-                # Keystone is a required service for tempest
-                continue
-            if catalog_type not in services:
+        if cfgname == 'identity':
+            # Keystone is a required service for tempest
+            continue
+        if catalog_type not in services:
+            try:
                 if getattr(CONF.service_available, codename_match[cfgname]):
                     print('Endpoint type %s not found either disable service '
                           '%s or fix the catalog_type in the config file' % (
@@ -377,7 +377,13 @@
                     if update:
                         change_option(codename_match[cfgname],
                                       'service_available', False)
-            else:
+            except KeyError:
+                print('%s is a third party plugin, cannot be verified '
+                      'automatically, but it is suggested that it is set to '
+                      'False because %s service is not available ' % (
+                          cfgname, catalog_type))
+        else:
+            try:
                 if not getattr(CONF.service_available,
                                codename_match[cfgname]):
                     print('Endpoint type %s is available, service %s should be'
@@ -391,6 +397,11 @@
                         avail_services.append(codename_match[cfgname])
                 else:
                     avail_services.append(codename_match[cfgname])
+            except KeyError:
+                print('%s is a third party plugin, cannot be verified '
+                      'automatically, but it is suggested that it is set to '
+                      'True because %s service is available ' % (
+                          cfgname, catalog_type))
     return avail_services
 
 
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index 929a584..d0c4b28 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -86,6 +86,7 @@
     def rename_workspace(self, old_name, new_name):
         self._populate()
         self._name_exists(old_name)
+        self._invalid_name_check(new_name)
         self._workspace_name_exists(new_name)
         self.workspaces[new_name] = self.workspaces.pop(old_name)
         self._write_file()
@@ -93,7 +94,7 @@
     @lockutils.synchronized('workspaces', external=True)
     def move_workspace(self, name, path):
         self._populate()
-        path = os.path.abspath(os.path.expanduser(path))
+        path = os.path.abspath(os.path.expanduser(path)) if path else path
         self._name_exists(name)
         self._validate_path(path)
         self.workspaces[name] = path
@@ -114,6 +115,7 @@
 
     @lockutils.synchronized('workspaces', external=True)
     def remove_workspace_directory(self, workspace_path):
+        self._validate_path(workspace_path)
         shutil.rmtree(workspace_path)
 
     @lockutils.synchronized('workspaces', external=True)
@@ -128,7 +130,17 @@
                 name))
             sys.exit(1)
 
+    def _invalid_name_check(self, name):
+        if not name:
+            print("None or empty name is specified."
+                  " Please specify correct name for workspace.")
+            sys.exit(1)
+
     def _validate_path(self, path):
+        if not path:
+            print("None or empty path is specified for workspace."
+                  " Please specify correct workspace path.")
+            sys.exit(1)
         if not os.path.exists(path):
             print("Path does not exist.")
             sys.exit(1)
@@ -137,10 +149,11 @@
     def register_new_workspace(self, name, path, init=False):
         """Adds the new workspace and writes out the new workspace config"""
         self._populate()
-        path = os.path.abspath(os.path.expanduser(path))
+        path = os.path.abspath(os.path.expanduser(path)) if path else path
         # This only happens when register is called from outside of init
         if not init:
             self._validate_path(path)
+        self._invalid_name_check(name)
         self._workspace_name_exists(name)
         self.workspaces[name] = path
         self._write_file()
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index f2730b3..375113d 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -44,15 +44,14 @@
 def is_scheduler_filter_enabled(filter_name):
     """Check the list of enabled compute scheduler filters from config.
 
-    This function checks whether the given compute scheduler filter is
-    available and configured in the config file. If the
-    scheduler_available_filters option is set to 'all' (Default value. which
-    means default filters are configured in nova) in tempest.conf then, this
-    function returns True with assumption that requested filter 'filter_name'
-    is one of available filter in nova ("nova.scheduler.filters.all_filters").
+    This function checks whether the given compute scheduler filter is enabled
+    in the nova config file. If the scheduler_enabled_filters option is set to
+    'all' in tempest.conf then, this function returns True with assumption that
+    requested filter 'filter_name' is one of the enabled filters in nova
+    ("nova.scheduler.filters.all_filters").
     """
 
-    filters = CONF.compute_feature_enabled.scheduler_available_filters
+    filters = CONF.compute_feature_enabled.scheduler_enabled_filters
     if not filters:
         return False
     if 'all' in filters:
@@ -79,23 +78,22 @@
     :param wait_until: Server status to wait for the server to reach after
         its creation.
     :param volume_backed: Whether the server is volume backed or not.
-                          If this is true, a volume will be created and
-                          create server will be requested with
-                          'block_device_mapping_v2' populated with below
-                          values:
-                          --------------------------------------------
-                          bd_map_v2 = [{
-                              'uuid': volume['volume']['id'],
-                              'source_type': 'volume',
-                              'destination_type': 'volume',
-                              'boot_index': 0,
-                              'delete_on_termination': True}]
-                          kwargs['block_device_mapping_v2'] = bd_map_v2
-                          ---------------------------------------------
-                          If server needs to be booted from volume with other
-                          combination of bdm inputs than mentioned above, then
-                          pass the bdm inputs explicitly as kwargs and image_id
-                          as empty string ('').
+        If this is true, a volume will be created and create server will be
+        requested with 'block_device_mapping_v2' populated with below values:
+
+        .. code-block:: python
+
+            bd_map_v2 = [{
+                'uuid': volume['volume']['id'],
+                'source_type': 'volume',
+                'destination_type': 'volume',
+                'boot_index': 0,
+                'delete_on_termination': True}]
+            kwargs['block_device_mapping_v2'] = bd_map_v2
+
+        If server needs to be booted from volume with other combination of bdm
+        inputs than mentioned above, then pass the bdm inputs explicitly as
+        kwargs and image_id as empty string ('').
     :param name: Name of the server to be provisioned. If not defined a random
         string ending with '-instance' will be generated.
     :param flavor: Flavor of the server to be provisioned. If not defined,
diff --git a/tempest/common/identity.py b/tempest/common/identity.py
index eaf651b..525110b 100644
--- a/tempest/common/identity.py
+++ b/tempest/common/identity.py
@@ -64,7 +64,8 @@
     should not be used for testing identity features.
 
     :param clients: a client manager.
-    :return
+    :return: v2 or v3 of CredsClient
+    :rtype: V2CredsClient or V3CredsClient
     """
     if CONF.identity.auth_version == 'v2':
         client = clients.identity_client
diff --git a/tempest/config.py b/tempest/config.py
index dbce504..716c000 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -101,7 +101,7 @@
                deprecated_group='identity'),
     cfg.StrOpt('admin_domain_name',
                default='Default',
-               help="Admin domain name for authentication (Keystone V3)."
+               help="Admin domain name for authentication (Keystone V3). "
                     "The same domain applies to user and project",
                deprecated_group='identity'),
 ]
@@ -170,15 +170,35 @@
     cfg.IntOpt('user_lockout_failure_attempts',
                default=2,
                help="The number of unsuccessful login attempts the user is "
-                    "allowed before having the account locked."),
+                    "allowed before having the account locked. This only "
+                    "takes effect when identity-feature-enabled."
+                    "security_compliance is set to 'True'. For more details, "
+                    "refer to keystone config options keystone.conf:"
+                    "security_compliance.lockout_failure_attempts. "
+                    "This feature is disabled by default in keystone."),
     cfg.IntOpt('user_lockout_duration',
                default=5,
                help="The number of seconds a user account will remain "
-                    "locked."),
+                    "locked. This only takes "
+                    "effect when identity-feature-enabled.security_compliance "
+                    "is set to 'True'. For more details, refer to "
+                    "keystone config options "
+                    "keystone.conf:security_compliance.lockout_duration. "
+                    "Setting this option will have no effect unless you also "
+                    "set identity.user_lockout_failure_attempts."),
     cfg.IntOpt('user_unique_last_password_count',
                default=2,
                help="The number of passwords for a user that must be unique "
-                    "before an old password can be reused."),
+                    "before an old password can be reused. This only takes "
+                    "effect when identity-feature-enabled.security_compliance "
+                    "is set to 'True'. "
+                    "This config option corresponds to keystone.conf: "
+                    "security_compliance.unique_last_password_count, whose "
+                    "default value is 0 meaning disabling this feature. "
+                    "NOTE: This config option value must be same as "
+                    "keystone.conf: security_compliance.unique_last_password_"
+                    "count otherwise test might fail"
+               ),
 ]
 
 service_clients_group = cfg.OptGroup(name='service-clients',
@@ -242,7 +262,13 @@
     cfg.BoolOpt('application_credentials',
                 default=False,
                 help='Does the environment have application credentials '
-                     'enabled?')
+                     'enabled?'),
+    cfg.BoolOpt('immutable_user_source',
+                default=False,
+                help='Set to True if the environment has a read-only '
+                     'user source. This will skip all tests that attempt to '
+                     'create, delete, or modify users. This should not be set '
+                     'to True if using dynamic credentials')
 ]
 
 compute_group = cfg.OptGroup(name='compute',
@@ -463,20 +489,30 @@
     cfg.BoolOpt('config_drive',
                 default=True,
                 help='Enable special configuration drive with metadata.'),
-    cfg.ListOpt('scheduler_available_filters',
-                default=['all'],
-                help="A list of enabled filters that nova will accept as hints"
-                     " to the scheduler when creating a server. A special "
-                     "entry 'all' indicates all filters that are included "
-                     "with nova are enabled. Empty list indicates all filters "
-                     "are disabled. The full list of available filters is in "
-                     "nova.conf: filter_scheduler.enabled_filters. If the "
+    cfg.ListOpt('scheduler_enabled_filters',
+                default=["RetryFilter", "AvailabilityZoneFilter",
+                         "ComputeFilter", "ComputeCapabilitiesFilter",
+                         "ImagePropertiesFilter",
+                         "ServerGroupAntiAffinityFilter",
+                         "ServerGroupAffinityFilter"],
+                help="A list of enabled filters that Nova will accept as "
+                     "hints to the scheduler when creating a server. If the "
                      "default value is overridden in nova.conf by the test "
                      "environment (which means that a different set of "
                      "filters is enabled than what is included in Nova by "
-                     "default) then, this option must be configured to "
+                     "default), then this option must be configured to "
                      "contain the same filters that Nova uses in the test "
-                     "environment."),
+                     "environment. A special entry 'all' indicates all "
+                     "filters that are included with Nova are enabled. If "
+                     "using 'all', be sure to enable all filters in "
+                     "nova.conf, as tests can fail in unpredictable ways if "
+                     "Nova's and Tempest's enabled filters don't match. "
+                     "Empty list indicates all filters are disabled. The "
+                     "full list of enabled filters is in nova.conf: "
+                     "filter_scheduler.enabled_filters.",
+                deprecated_opts=[cfg.DeprecatedOpt(
+                    'scheduler_available_filters',
+                    group='compute-feature-enabled')]),
     cfg.BoolOpt('swap_volume',
                 default=False,
                 help='Does the test environment support in-place swapping of '
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 2dd9d00..8e6d3d5 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -324,7 +324,7 @@
                 pass
         if expiry is None:
             raise ValueError(
-                "time data '{data}' does not match any of the"
+                "time data '{data}' does not match any of the "
                 "expected formats: {formats}".format(
                     data=expiry_string, formats=self.EXPIRY_DATE_FORMATS))
         return expiry
diff --git a/tempest/lib/common/fixed_network.py b/tempest/lib/common/fixed_network.py
index 875a79d..926c3a4 100644
--- a/tempest/lib/common/fixed_network.py
+++ b/tempest/lib/common/fixed_network.py
@@ -24,7 +24,7 @@
     """Get a full network dict from just a network name
 
     :param str name: the name of the network to use
-    :param NetworksClient compute_networks_client: The network client
+    :param network.NetworksClient compute_networks_client: The network client
         object to use for making the network lists api request
     :return: The full dictionary for the network in question
     :rtype: dict
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
index 738c37f..8c1a802 100644
--- a/tempest/lib/common/http.py
+++ b/tempest/lib/common/http.py
@@ -19,7 +19,8 @@
 
 class ClosingProxyHttp(urllib3.ProxyManager):
     def __init__(self, proxy_url, disable_ssl_certificate_validation=False,
-                 ca_certs=None, timeout=None):
+                 ca_certs=None, timeout=None, follow_redirects=True):
+        self.follow_redirects = follow_redirects
         kwargs = {}
 
         if disable_ssl_certificate_validation:
@@ -50,9 +51,14 @@
         new_headers = dict(original_headers, connection='close')
         new_kwargs = dict(kwargs, headers=new_headers)
 
-        # Follow up to 5 redirections. Don't raise an exception if
-        # it's exceeded but return the HTTP 3XX response instead.
-        retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+        if self.follow_redirects:
+            # Follow up to 5 redirections. Don't raise an exception if
+            # it's exceeded but return the HTTP 3XX response instead.
+            retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+        else:
+            # Do not follow redirections. Don't raise an exception if
+            # a redirect is found, but return the HTTP 3XX response instead.
+            retry = urllib3.util.Retry(redirect=False)
         r = super(ClosingProxyHttp, self).request(method, url, retries=retry,
                                                   *args, **new_kwargs)
         return Response(r), r.data
@@ -60,7 +66,8 @@
 
 class ClosingHttp(urllib3.poolmanager.PoolManager):
     def __init__(self, disable_ssl_certificate_validation=False,
-                 ca_certs=None, timeout=None):
+                 ca_certs=None, timeout=None, follow_redirects=True):
+        self.follow_redirects = follow_redirects
         kwargs = {}
 
         if disable_ssl_certificate_validation:
@@ -93,9 +100,14 @@
         new_headers = dict(original_headers, connection='close')
         new_kwargs = dict(kwargs, headers=new_headers)
 
-        # Follow up to 5 redirections. Don't raise an exception if
-        # it's exceeded but return the HTTP 3XX response instead.
-        retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+        if self.follow_redirects:
+            # Follow up to 5 redirections. Don't raise an exception if
+            # it's exceeded but return the HTTP 3XX response instead.
+            retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+        else:
+            # Do not follow redirections. Don't raise an exception if
+            # a redirect is found, but return the HTTP 3XX response instead.
+            retry = urllib3.util.Retry(redirect=False)
         r = super(ClosingHttp, self).request(method, url, retries=retry,
                                              *args, **new_kwargs)
         return Response(r), r.data
diff --git a/tempest/lib/common/jsonschema_validator.py b/tempest/lib/common/jsonschema_validator.py
index 9a35b76..bbf5e89 100644
--- a/tempest/lib/common/jsonschema_validator.py
+++ b/tempest/lib/common/jsonschema_validator.py
@@ -12,9 +12,8 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
-import base64
-
 import jsonschema
+from oslo_serialization import base64
 from oslo_utils import timeutils
 import six
 
@@ -46,9 +45,7 @@
     try:
         if isinstance(instance, six.text_type):
             instance = instance.encode('utf-8')
-        base64.decodestring(instance)
-    except base64.binascii.Error:
-        return False
+        base64.decode_as_bytes(instance)
     except TypeError:
         # The name must be string type. If instance isn't string type, the
         # TypeError will be raised at here.
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index e2fd722..ec46caf 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -70,6 +70,7 @@
     :param str http_timeout: Timeout in seconds to wait for the http request to
                              return
     :param str proxy_url: http proxy url to use.
+    :param bool follow_redirects: Set to false to stop following redirects.
     """
 
     # The version of the API this client implements
@@ -82,7 +83,7 @@
                  build_interval=1, build_timeout=60,
                  disable_ssl_certificate_validation=False, ca_certs=None,
                  trace_requests='', name=None, http_timeout=None,
-                 proxy_url=None):
+                 proxy_url=None, follow_redirects=True):
         self.auth_provider = auth_provider
         self.service = service
         self.region = region
@@ -107,11 +108,11 @@
             self.http_obj = http.ClosingProxyHttp(
                 proxy_url,
                 disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
-                timeout=http_timeout)
+                timeout=http_timeout, follow_redirects=follow_redirects)
         else:
             self.http_obj = http.ClosingHttp(
                 disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
-                timeout=http_timeout)
+                timeout=http_timeout, follow_redirects=follow_redirects)
 
     def get_headers(self, accept_type=None, send_type=None):
         """Return the default headers which will be used with outgoing requests
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index c5df590..3483c51 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -50,8 +50,7 @@
              (e.g. 'prefixfoo-namebar-154876201')
     :rtype: string
     """
-    randbits = str(random.randint(1, 0x7fffffff))
-    rand_name = randbits
+    rand_name = str(random.randint(1, 0x7fffffff))
     if name:
         rand_name = name + '-' + rand_name
     if prefix:
@@ -65,9 +64,9 @@
     :param int length: The length of password that you expect to set
                        (If it's smaller than 3, it's same as 3.)
     :return: a random password. The format is
-             '<random upper letter>-<random number>-<random special character>
-              -<random ascii letters or digit characters or special symbols>'
-             (e.g. 'G2*ac8&lKFFgh%2')
+        ``'<random upper letter>-<random number>-<random special character>
+        -<random ascii letters or digit characters or special symbols>'``
+        (e.g. ``G2*ac8&lKFFgh%2``)
     :rtype: string
     """
     upper = random.choice(string.ascii_uppercase)
diff --git a/tempest/lib/common/utils/misc.py b/tempest/lib/common/utils/misc.py
index f13b4c8..2b0fcd5 100644
--- a/tempest/lib/common/utils/misc.py
+++ b/tempest/lib/common/utils/misc.py
@@ -14,8 +14,6 @@
 #    under the License.
 from oslo_log import log as logging
 
-from tempest.lib.common.utils import test_utils
-
 LOG = logging.getLogger(__name__)
 
 
@@ -28,10 +26,3 @@
             instances[cls] = cls()
         return instances[cls]
     return getinstance
-
-
-def find_test_caller(*args, **kwargs):
-    LOG.warning("tempest.lib.common.utils.misc.find_test_caller is deprecated "
-                "in favor of tempest.lib.common.utils.test_utils."
-                "find_test_caller")
-    test_utils.find_test_caller(*args, **kwargs)
diff --git a/tempest/lib/services/identity/v3/inherited_roles_client.py b/tempest/lib/services/identity/v3/inherited_roles_client.py
index 691c7fd..3949437 100644
--- a/tempest/lib/services/identity/v3/inherited_roles_client.py
+++ b/tempest/lib/services/identity/v3/inherited_roles_client.py
@@ -114,8 +114,7 @@
 
     def check_user_has_flag_on_inherited_to_project(
             self, project_id, user_id, role_id):
-        """Checks whether a user has a role assignment"""
-        """with the inherited_to_projects flag on a project."""
+        """Check if user has an inherited project role on project"""
         resp, body = self.head(
             "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
             % (project_id, user_id, role_id))
@@ -142,8 +141,7 @@
 
     def check_group_has_flag_on_inherited_to_project(
             self, project_id, group_id, role_id):
-        """Checks whether a group has a role assignment"""
-        """with the inherited_to_projects flag on a project."""
+        """Check if group has an inherited project role on project"""
         resp, body = self.head(
             "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
             % (project_id, group_id, role_id))
diff --git a/tempest/lib/services/network/agents_client.py b/tempest/lib/services/network/agents_client.py
index 5068121..9fa4672 100644
--- a/tempest/lib/services/network/agents_client.py
+++ b/tempest/lib/services/network/agents_client.py
@@ -37,6 +37,16 @@
         uri = '/agents/%s' % agent_id
         return self.show_resource(uri, **fields)
 
+    def delete_agent(self, agent_id):
+        """Delete agent.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#delete-agent
+        """
+        uri = '/agents/%s' % agent_id
+        return self.delete_resource(uri)
+
     def list_agents(self, **filters):
         """List all agents.
 
diff --git a/tempest/lib/services/volume/v1/types_client.py b/tempest/lib/services/volume/v1/types_client.py
index 58a80b7..da9eb8b 100644
--- a/tempest/lib/services/volume/v1/types_client.py
+++ b/tempest/lib/services/volume/v1/types_client.py
@@ -149,10 +149,11 @@
                                        extra_specs):
         """Update a volume_type extra spec.
 
-        volume_type_id: Id of volume_type.
-        extra_spec_name: Name of the extra spec to be updated.
-        extra_spec: A dictionary of with key as extra_spec_name and the
-                     updated value.
+        :param volume_type_id: Id of volume_type.
+        :param extra_spec_name: Name of the extra spec to be updated.
+        :param extra_specs: A dictionary of with key as extra_spec_name and the
+                            updated value.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/block-storage/v2/#update-extra-specs-for-a-volume-type
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index f2d2d21..fb64333 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -104,7 +104,12 @@
         return rest_client.ResponseBody(resp, body)
 
     def import_backup(self, **kwargs):
-        """Import backup metadata record."""
+        """Import backup metadata record.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/index.html#import-a-backup
+        """
         post_body = json.dumps({'backup-record': kwargs})
         resp, body = self.post("backups/import_record", post_body)
         body = json.loads(body)
diff --git a/tempest/lib/services/volume/v3/types_client.py b/tempest/lib/services/volume/v3/types_client.py
index 13ecd15..1405785 100644
--- a/tempest/lib/services/volume/v3/types_client.py
+++ b/tempest/lib/services/volume/v3/types_client.py
@@ -153,6 +153,7 @@
         :param extra_spec_name: Name of the extra spec to be updated.
         :param extra_specs: A dictionary of with key as extra_spec_name and the
                             updated value.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-extra-specification-for-volume-type
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 11c5767..fec2950 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -182,7 +182,7 @@
 
         :param id: A checked resource id
         :raises lib_exc.DeleteErrorException: If the specified resource is on
-        the status the delete was failed.
+            the status the delete was failed.
         """
         try:
             volume = self.show_volume(id)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 00d45cd..dbb9acc 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -378,12 +378,12 @@
                           server=None):
         """Get a SSH client to a remote server
 
-        @param ip_address the server floating or fixed IP address to use
-                          for ssh validation
-        @param username name of the Linux account on the remote server
-        @param private_key the SSH private key to use
-        @param server: server dict, used for debugging purposes
-        @return a RemoteClient object
+        :param ip_address: the server floating or fixed IP address to use
+                           for ssh validation
+        :param username: name of the Linux account on the remote server
+        :param private_key: the SSH private key to use
+        :param server: server dict, used for debugging purposes
+        :return: a RemoteClient object
         """
 
         if username is None:
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index 8c210d5..008d1ae 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -29,11 +29,12 @@
 
     For both LUKS and cryptsetup encryption types, this test performs
     the following:
-        * Creates an image in Glance
-        * Boots an instance from the image
-        * Creates an encryption type (as admin)
-        * Creates a volume of that encryption type (as a regular user)
-        * Attaches and detaches the encrypted volume to the instance
+
+    * Creates an image in Glance
+    * Boots an instance from the image
+    * Creates an encryption type (as admin)
+    * Creates a volume of that encryption type (as a regular user)
+    * Attaches and detaches the encrypted volume to the instance
     """
 
     @classmethod
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index c1132cf..c11070c 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -374,39 +374,37 @@
     def test_network_basic_ops(self):
         """Basic network operation test
 
-        For a freshly-booted VM with an IP address ("port") on a given
-            network:
+        For a freshly-booted VM with an IP address ("port") on a given network:
 
         - the Tempest host can ping the IP address.  This implies, but
-         does not guarantee (see the ssh check that follows), that the
-         VM has been assigned the correct IP address and has
-         connectivity to the Tempest host.
+            does not guarantee (see the ssh check that follows), that the
+            VM has been assigned the correct IP address and has
+            connectivity to the Tempest host.
 
         - the Tempest host can perform key-based authentication to an
-         ssh server hosted at the IP address.  This check guarantees
-         that the IP address is associated with the target VM.
+            ssh server hosted at the IP address.  This check guarantees
+            that the IP address is associated with the target VM.
 
         - the Tempest host can ssh into the VM via the IP address and
-         successfully execute the following:
+            successfully execute the following:
 
-         - ping an external IP address, implying external connectivity.
+            - ping an external IP address, implying external connectivity.
 
-         - ping an external hostname, implying that dns is correctly
-           configured.
+            - ping an external hostname, implying that dns is correctly
+                configured.
 
-         - ping an internal IP address, implying connectivity to another
-           VM on the same network.
+            - ping an internal IP address, implying connectivity to another
+               VM on the same network.
 
         - detach the floating-ip from the VM and verify that it becomes
-        unreachable
+            unreachable
 
         - associate detached floating ip to a new VM and verify connectivity.
-        VMs are created with unique keypair so connectivity also asserts that
-        floating IP is associated with the new VM instead of the old one
+            VMs are created with unique keypair so connectivity also asserts
+            that floating IP is associated with the new VM instead of the old
+            one
 
         Verifies that floating IP status is updated correctly after each change
-
-
         """
         self._setup_network_and_servers()
         self._check_public_network_connectivity(should_connect=True)
@@ -445,30 +443,25 @@
     def test_connectivity_between_vms_on_different_networks(self):
         """Test connectivity between VMs on different networks
 
-        For a freshly-booted VM with an IP address ("port") on a given
-            network:
+        For a freshly-booted VM with an IP address ("port") on a given network:
 
         - the Tempest host can ping the IP address.
-
         - the Tempest host can ssh into the VM via the IP address and
-         successfully execute the following:
+            successfully execute the following:
 
-         - ping an external IP address, implying external connectivity.
-
-         - ping an external hostname, implying that dns is correctly
-           configured.
-
-         - ping an internal IP address, implying connectivity to another
-           VM on the same network.
+            - ping an external IP address, implying external connectivity.
+            - ping an external hostname, implying that dns is correctly
+               configured.
+            - ping an internal IP address, implying connectivity to another
+               VM on the same network.
 
         - Create another network on the same tenant with subnet, create
-        an VM on the new network.
+            an VM on the new network.
 
-         - Ping the new VM from previous VM failed since the new network
-         was not attached to router yet.
-
-         - Attach the new network to the router, Ping the new VM from
-         previous VM succeed.
+            - Ping the new VM from previous VM failed since the new network
+                was not attached to router yet.
+            - Attach the new network to the router, Ping the new VM from
+                previous VM succeed.
 
         """
         self._setup_network_and_servers()
@@ -476,9 +469,14 @@
         self._check_network_internal_connectivity(network=self.network)
         self._check_network_external_connectivity()
         self._create_new_network(create_gateway=True)
-        self._create_server(self.new_net)
-        self._check_network_internal_connectivity(network=self.new_net,
-                                                  should_connect=False)
+        new_server = self._create_server(self.new_net)
+        new_server_ips = [addr['addr'] for addr in
+                          new_server['addresses'][self.new_net['name']]]
+
+        # Assert that pinging the new VM fails since the new network is not
+        # connected to a router
+        self._check_server_connectivity(self.floating_ip_tuple.floating_ip,
+                                        new_server_ips, should_connect=False)
         router_id = self.router['id']
         self.routers_client.add_router_interface(
             router_id, subnet_id=self.new_subnet['id'])
@@ -486,8 +484,9 @@
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.routers_client.remove_router_interface,
                         router_id, subnet_id=self.new_subnet['id'])
-        self._check_network_internal_connectivity(network=self.new_net,
-                                                  should_connect=True)
+
+        self._check_server_connectivity(self.floating_ip_tuple.floating_ip,
+                                        new_server_ips, should_connect=True)
 
     @decorators.idempotent_id('c5adff73-e961-41f1-b4a9-343614f18cfa')
     @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
@@ -555,24 +554,25 @@
     def test_subnet_details(self):
         """Tests that subnet's extra configuration details are affecting VMs.
 
-         This test relies on non-shared, isolated tenant networks.
+        This test relies on non-shared, isolated tenant networks.
 
-         NOTE: Neutron subnets push data to servers via dhcp-agent, so any
-         update in subnet requires server to actively renew its DHCP lease.
+        NOTE: Neutron subnets push data to servers via dhcp-agent, so any
+        update in subnet requires server to actively renew its DHCP lease.
 
-         1. Configure subnet with dns nameserver
-         2. retrieve the VM's configured dns and verify it matches the one
-         configured for the subnet.
-         3. update subnet's dns
-         4. retrieve the VM's configured dns and verify it matches the new one
-         configured for the subnet.
+        1. Configure subnet with dns nameserver
+        2. retrieve the VM's configured dns and verify it matches the one
+           configured for the subnet.
+        3. update subnet's dns
+        4. retrieve the VM's configured dns and verify it matches the new one
+           configured for the subnet.
 
-         TODO(yfried): add host_routes
+        TODO(yfried): add host_routes
 
-         any resolution check would be testing either:
-            * l3 forwarding (tested in test_network_basic_ops)
-            * Name resolution of an external DNS nameserver - out of scope for
-            Tempest
+        any resolution check would be testing either:
+
+        * l3 forwarding (tested in test_network_basic_ops)
+        * Name resolution of an external DNS nameserver - out of scope for
+          Tempest
         """
         # this test check only updates (no actual resolution) so using
         # arbitrary ip addresses as nameservers, instead of parsing CONF
@@ -742,7 +742,7 @@
         2. Remove router from all l3-agents
         3. Verify connectivity is down
         4. Assign router to new l3-agent (or old one if no new agent is
-         available)
+           available)
         5. Verify connectivity
         """
 
@@ -823,7 +823,8 @@
         prevents traffic to pass through the VM. Anti-spoof rules are not
         required in cases where the VM routes traffic through it.
 
-        The test steps are :
+        The test steps are:
+
         1. Create a new network.
         2. Connect (hotplug) the VM to a new network.
         3. Check the VM can ping a server on the new network ("peer")
@@ -832,7 +833,7 @@
            spoofed interface (VM cannot ping the peer).
         6. Disable port-security of the spoofed port- set the flag to false.
         7. Retest 3rd step and check that the Security Group allows pings via
-        the spoofed interface.
+           the spoofed interface.
         """
 
         spoof_mac = "00:00:00:00:00:01"
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index e4e39c3..438ee01 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -12,13 +12,18 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+
+from oslo_log import log as logging
+
 from tempest.common import utils
 from tempest import config
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions
 from tempest.scenario import manager
 
 CONF = config.CONF
+LOG = logging.getLogger(__name__)
 
 
 class TestGettingAddress(manager.NetworkScenarioTest):
@@ -154,8 +159,31 @@
                          % (network_id, ports))
         mac6 = ports[0]
         nic = ssh.get_nic_name_by_mac(mac6)
+        # NOTE(slaweq): on RHEL based OS ifcfg file for new interface is
+        # needed to make IPv6 working on it, so if
+        # /etc/sysconfig/network-scripts directory exists ifcfg-%(nic)s file
+        # should be added in it
+        if self._sysconfig_network_scripts_dir_exists(ssh):
+            try:
+                ssh.exec_command(
+                    'echo -e "DEVICE=%(nic)s\\nIPV6INIT=yes" | '
+                    'sudo tee /etc/sysconfig/network-scripts/ifcfg-%(nic)s; '
+                    'sudo /sbin/service network restart' % {'nic': nic})
+            except exceptions.SSHExecCommandFailed as e:
+                # NOTE(slaweq): Sometimes it can happen that this SSH command
+                # will fail because of some error from network manager in
+                # guest os.
+                # But even then doing ip link set up below is fine and
+                # IP address should be configured properly.
+                LOG.debug("Error during restarting %(nic)s interface on "
+                          "instance. Error message: %(error)s",
+                          {'nic': nic, 'error': e})
         ssh.exec_command("sudo ip link set %s up" % nic)
 
+    def _sysconfig_network_scripts_dir_exists(self, ssh):
+        return "False" not in ssh.exec_command(
+            'test -d /etc/sysconfig/network-scripts/ || echo "False"')
+
     def _prepare_and_test(self, address6_mode, n_subnets6=1, dualnet=False):
         net_list = self.prepare_network(address6_mode=address6_mode,
                                         n_subnets6=n_subnets6,
@@ -252,6 +280,7 @@
                                dualnet=True)
 
     @decorators.idempotent_id('9178ad42-10e4-47e9-8987-e02b170cc5cd')
+    @decorators.attr(type='slow')
     @utils.services('compute', 'network')
     def test_dualnet_multi_prefix_slaac(self):
         self._prepare_and_test(address6_mode='slaac', n_subnets6=2,
diff --git a/tempest/scenario/test_object_storage_basic_ops.py b/tempest/scenario/test_object_storage_basic_ops.py
index cbe321e..b635ca0 100644
--- a/tempest/scenario/test_object_storage_basic_ops.py
+++ b/tempest/scenario/test_object_storage_basic_ops.py
@@ -24,15 +24,15 @@
     def test_swift_basic_ops(self):
         """Test swift basic ops.
 
-         * get swift stat.
-         * create container.
-         * upload a file to the created container.
-         * list container's objects and assure that the uploaded file is
-         present.
-         * download the object and check the content
-         * delete object from container.
-         * list container's objects and assure that the deleted file is gone.
-         * delete a container.
+        * get swift stat.
+        * create container.
+        * upload a file to the created container.
+        * list container's objects and assure that the uploaded file is
+          present.
+        * download the object and check the content
+        * delete object from container.
+        * list container's objects and assure that the deleted file is gone.
+        * delete a container.
         """
         self.get_swift_stat()
         container_name = self.create_container()
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 28a2d64..2b7926a 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -63,28 +63,28 @@
                 a. a security group open to incoming ssh connection
                 b. a VM with a floating ip
             5. create a general empty security group (same as "default", but
-            without rules allowing in-tenant traffic)
+               without rules allowing in-tenant traffic)
 
     tests:
         1. _verify_network_details
         2. _verify_mac_addr: for each access point verify that
-        (subnet, fix_ip, mac address) are as defined in the port list
+           (subnet, fix_ip, mac address) are as defined in the port list
         3. _test_in_tenant_block: test that in-tenant traffic is disabled
-        without rules allowing it
+           without rules allowing it
         4. _test_in_tenant_allow: test that in-tenant traffic is enabled
-        once an appropriate rule has been created
+           once an appropriate rule has been created
         5. _test_cross_tenant_block: test that cross-tenant traffic is disabled
-        without a rule allowing it on destination tenant
+           without a rule allowing it on destination tenant
         6. _test_cross_tenant_allow:
             * test that cross-tenant traffic is enabled once an appropriate
-            rule has been created on destination tenant.
+              rule has been created on destination tenant.
             * test that reverse traffic is still blocked
             * test than reverse traffic is enabled once an appropriate rule has
-            been created on source tenant
-        7._test_port_update_new_security_group:
-           * test that traffic is blocked with default security group
-           * test that traffic is enabled after updating port with new security
-           group having appropriate rule
+              been created on source tenant
+        7. _test_port_update_new_security_group:
+            * test that traffic is blocked with default security group
+            * test that traffic is enabled after updating port with new
+              security group having appropriate rule
         8. _test_multiple_security_groups: test multiple security groups can be
            associated with the vm
 
@@ -93,11 +93,13 @@
         2. Public network is defined and reachable from the Tempest host
         3. Public router can either be:
             * defined, in which case all tenants networks can connect directly
-            to it, and cross tenant check will be done on the private IP of the
-            destination tenant
+              to it, and cross tenant check will be done on the private IP of
+              the destination tenant
+
             or
+
             * not defined (empty string), in which case each tenant will have
-            its own router connected to the public network
+              its own router connected to the public network
     """
 
     credentials = ['primary', 'alt', 'admin']
diff --git a/tempest/test.py b/tempest/test.py
index f2babbb..c3c58dc 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -259,6 +259,7 @@
         based on the result of an API call are discouraged.
 
         The following checks are implemented in `test.py` already:
+
         - check that alt credentials are available when requested by the test
         - check that admin credentials are available when requested by the test
         - check that the identity version specified by the test is marked as
@@ -310,6 +311,7 @@
         `os_[type]`:
 
         Valid values in `credentials` are:
+
         - 'primary':
             A normal user is provisioned.
             It can be used only once. Multiple entries will be ignored.
@@ -581,7 +583,7 @@
     def setUp(self):
         super(BaseTestCase, self).setUp()
         if not self.__setupclass_called:
-            raise RuntimeError("setUpClass does not calls the super's"
+            raise RuntimeError("setUpClass does not calls the super's "
                                "setUpClass in the " +
                                self.__class__.__name__)
         at_exit_set.add(self.__class__)
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index a1d3a40..b349bba 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -106,6 +106,8 @@
         cp = account_generator.get_credential_provider(self.opts)
         admin_creds = cp.default_admin_creds
         self.assertEqual(self.opts.os_tenant_name, admin_creds.tenant_name)
+        self.assertEqual(self.opts.os_username, admin_creds.username)
+        self.assertEqual(self.opts.os_password, admin_creds.password)
 
 
 class TestAccountGeneratorV3(TestAccountGeneratorV2):
@@ -222,6 +224,30 @@
             self.assertIsNotNone(resource[1].router)
             self.assertIsNotNone(resource[1].subnet)
 
+    def test_generate_resources_swift_no_admin(self):
+        cfg.CONF.set_default('swift', True, group='service_available')
+        cfg.CONF.set_default('operator_role', 'fake_operator',
+                             group='object-storage')
+        cfg.CONF.set_default('reseller_admin_role', 'fake_reseller',
+                             group='object-storage')
+        resources = account_generator.generate_resources(
+            self.cred_provider, admin=False)
+        resource_types = [k for k, _ in resources]
+        # No Admin, swift, expect four credentials only
+        self.assertEqual(4, len(resources))
+        # Ensure create_user was invoked 4 times (4 distinct users)
+        self.assertEqual(4, self.user_create_fixture.mock.call_count)
+        self.assertIn('primary', resource_types)
+        self.assertIn('alt', resource_types)
+        self.assertNotIn('admin', resource_types)
+        self.assertIn(['fake_operator'], resource_types)
+        self.assertIn(['fake_reseller'], resource_types)
+        self.assertNotIn(['fake_owner'], resource_types)
+        for resource in resources:
+            self.assertIsNotNone(resource[1].network)
+            self.assertIsNotNone(resource[1].router)
+            self.assertIsNotNone(resource[1].subnet)
+
 
 class TestGenerateResourcesV3(TestGenerateResourcesV2):
 
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 9a6be4e..e159cdc 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -24,7 +24,9 @@
 import six
 
 from tempest.cmd import run
+from tempest.cmd import workspace
 from tempest import config
+from tempest.lib.common.utils import data_utils
 from tempest.tests import base
 
 DEVNULL = open(os.devnull, 'wb')
@@ -58,6 +60,12 @@
         self.assertEqual(['i_am_a_fun_little_regex'],
                          self.run_cmd._build_regex(args))
 
+    def test__build_regex_smoke_regex(self):
+        args = mock.Mock(spec=argparse.Namespace)
+        setattr(args, "smoke", True)
+        setattr(args, 'regex', 'i_am_a_fun_little_regex')
+        self.assertEqual(['smoke'], self.run_cmd._build_regex(args))
+
 
 class TestRunReturnCode(base.TestCase):
     def setUp(self):
@@ -173,6 +181,27 @@
 
 
 class TestTakeAction(base.TestCase):
+    def setUp(self):
+        super(TestTakeAction, self).setUp()
+        self.name = data_utils.rand_name('workspace')
+        self.path = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.path, ignore_errors=True)
+        store_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, store_dir, ignore_errors=True)
+        self.store_file = os.path.join(store_dir, 'workspace.yaml')
+        self.workspace_manager = workspace.WorkspaceManager(
+            path=self.store_file)
+        self.workspace_manager.register_new_workspace(self.name, self.path)
+
+    def _setup_test_dirs(self):
+        self.directory = tempfile.mkdtemp(prefix='tempest-unit')
+        self.addCleanup(shutil.rmtree, self.directory, ignore_errors=True)
+        self.test_dir = os.path.join(self.directory, 'tests')
+        os.mkdir(self.test_dir)
+        # Change directory, run wrapper and check result
+        self.addCleanup(os.chdir, os.path.abspath(os.curdir))
+        os.chdir(self.directory)
+
     def test_workspace_not_registered(self):
         class Exception_(Exception):
             pass
@@ -201,15 +230,7 @@
         self.assertIn(workspace, exit_msg)
 
     def test_config_file_specified(self):
-        # Setup test dirs
-        self.directory = tempfile.mkdtemp(prefix='tempest-unit')
-        self.addCleanup(shutil.rmtree, self.directory)
-        self.test_dir = os.path.join(self.directory, 'tests')
-        os.mkdir(self.test_dir)
-        # Change directory, run wrapper and check result
-        self.addCleanup(os.chdir, os.path.abspath(os.curdir))
-        os.chdir(self.directory)
-
+        self._setup_test_dirs()
         tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
         parsed_args = mock.Mock()
 
@@ -222,3 +243,97 @@
             m.return_value = 0
             self.assertEqual(0, tempest_run.take_action(parsed_args))
             m.assert_called()
+
+    def test_no_config_file_no_workspace_no_state(self):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+
+        parsed_args.workspace = None
+        parsed_args.state = None
+        parsed_args.list_tests = False
+        parsed_args.config_file = ''
+
+        with mock.patch('stestr.commands.run_command'):
+            self.assertRaises(SystemExit, tempest_run.take_action, parsed_args)
+
+    def test_config_file_workspace_registered(self):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.workspace = self.name
+        parsed_args.workspace_path = self.store_file
+        parsed_args.state = None
+        parsed_args.list_tests = False
+        parsed_args.config_file = '.stestr.conf'
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+
+    @mock.patch('tempest.cmd.run.TempestRun._init_state')
+    def test_workspace_registered_no_config_no_state(self, mock_init_state):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.workspace = self.name
+        parsed_args.workspace_path = self.store_file
+        parsed_args.state = None
+        parsed_args.list_tests = False
+        parsed_args.config_file = ''
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+        mock_init_state.assert_not_called()
+
+    @mock.patch('tempest.cmd.run.TempestRun._init_state')
+    def test_no_config_file_no_workspace_state_true(self, mock_init_state):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+
+        parsed_args.workspace = None
+        parsed_args.state = True
+        parsed_args.list_tests = False
+        parsed_args.config_file = ''
+
+        with mock.patch('stestr.commands.run_command'):
+            self.assertRaises(SystemExit, tempest_run.take_action, parsed_args)
+        mock_init_state.assert_not_called()
+
+    @mock.patch('tempest.cmd.run.TempestRun._init_state')
+    def test_workspace_registered_no_config_state_true(self, mock_init_state):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.workspace = self.name
+        parsed_args.workspace_path = self.store_file
+        parsed_args.state = True
+        parsed_args.list_tests = False
+        parsed_args.config_file = ''
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+        mock_init_state.assert_called()
+
+    @mock.patch('tempest.cmd.run.TempestRun._init_state')
+    def test_no_workspace_config_file_state_true(self, mock_init_state):
+        self._setup_test_dirs()
+        tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
+        parsed_args = mock.Mock()
+        parsed_args.workspace = None
+        parsed_args.workspace_path = self.store_file
+        parsed_args.state = True
+        parsed_args.list_tests = False
+        parsed_args.config_file = '.stestr.conf'
+
+        with mock.patch('stestr.commands.run_command') as m:
+            m.return_value = 0
+            self.assertEqual(0, tempest_run.take_action(parsed_args))
+            m.assert_called()
+        mock_init_state.assert_called()
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 94fb74b..8dbba38 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -579,29 +579,33 @@
                           os, 'fakeservice')
 
     def test_get_config_file(self):
-        default_config_dir = os.path.join(os.getcwd(), 'etc/')
-        default_config_file = "tempest.conf"
-        conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', default_config_dir)
-        conf_file = os.environ.get('TEMPEST_CONFIG', default_config_file)
+        conf_dir = os.path.join(os.getcwd(), 'etc/')
+        conf_file = "tempest.conf.sample"
         local_sample_conf_file = os.path.join(conf_dir, conf_file)
 
-        init_cmd = init.TempestInit(None, None)
-        init_cmd.generate_sample_config(os.getcwd())
-        self.assertTrue(
-            os.path.isfile('%s/tempest.conf.sample' % (conf_dir)))
-        os.rename('%s/tempest.conf.sample'
-                  % (conf_dir), '%s' % (local_sample_conf_file))
-
-        def fake_environ_get(key, default):
+        def fake_environ_get(key, default=None):
+            if key == 'TEMPEST_CONFIG_DIR':
+                return conf_dir
+            elif key == 'TEMPEST_CONFIG':
+                return 'tempest.conf.sample'
             return default
 
-        with mock.patch('os.environ.get', side_effect=fake_environ_get):
-            file_pointer = verify_tempest_config._get_config_file()
-        self.assertEqual(local_sample_conf_file, file_pointer.name)
+        with mock.patch('os.environ.get', side_effect=fake_environ_get,
+                        autospec=True):
+            init_cmd = init.TempestInit(None, None)
+            init_cmd.generate_sample_config(os.path.join(conf_dir, os.pardir))
+            self.assertTrue(os.path.isfile(local_sample_conf_file),
+                            local_sample_conf_file)
 
-        with open(local_sample_conf_file, 'r+') as f:
-            local_sample_conf_contents = f.read()
-        self.assertEqual(local_sample_conf_contents, file_pointer.read())
+            file_pointer = verify_tempest_config._get_config_file()
+            self.assertEqual(local_sample_conf_file, file_pointer.name)
+
+            with open(local_sample_conf_file, 'r+') as f:
+                local_sample_conf_contents = f.read()
+            self.assertEqual(local_sample_conf_contents, file_pointer.read())
+
+            if file_pointer:
+                file_pointer.close()
 
     def test_print_and_or_update_true(self):
         with mock.patch.object(
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index 3ed8a10..65481de 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -122,6 +122,17 @@
         self.assertIsNone(self.workspace_manager.get_workspace(self.name))
         self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
 
+    def test_workspace_manager_rename_no_name_exist(self):
+        no_name = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.rename_workspace,
+                                   self.name, no_name)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty name is specified."
+                         " Please specify correct name for workspace.\n")
+
     def test_workspace_manager_move(self):
         new_path = tempfile.mkdtemp()
         self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
@@ -129,6 +140,17 @@
         self.assertEqual(
             self.workspace_manager.get_workspace(self.name), new_path)
 
+    def test_workspace_manager_move_no_workspace_path(self):
+        new_path = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.move_workspace,
+                                   self.name, new_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty path is specified for workspace."
+                         " Please specify correct workspace path.\n")
+
     def test_workspace_manager_remove_entry(self):
         self.workspace_manager.remove_workspace_entry(self.name)
         self.assertIsNone(self.workspace_manager.get_workspace(self.name))
@@ -138,6 +160,18 @@
         self.workspace_manager.remove_workspace_directory(path)
         self.assertIsNone(self.workspace_manager.get_workspace(self.name))
 
+    def test_workspace_manager_remove_directory_no_path(self):
+        no_path = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   remove_workspace_directory,
+                                   no_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty path is specified for workspace."
+                         " Please specify correct workspace path.\n")
+
     def test_path_expansion(self):
         name = data_utils.rand_uuid()
         path = os.path.join("~", name)
@@ -184,3 +218,27 @@
         self.assertEqual(1, len(listed))
         self.assertIn(self.name, listed)
         self.assertEqual(self.path, listed.get(self.name))
+
+    def test_register_new_workspace_no_name(self):
+        no_name = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   register_new_workspace,
+                                   no_name, self.path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty name is specified."
+                         " Please specify correct name for workspace.\n")
+
+    def test_register_new_workspace_no_path(self):
+        no_path = ""
+        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            ex = self.assertRaises(SystemExit,
+                                   self.workspace_manager.
+                                   register_new_workspace,
+                                   self.name, no_path)
+        self.assertEqual(1, ex.code)
+        self.assertEqual(mock_stdout.getvalue(),
+                         "None or empty path is specified for workspace."
+                         " Please specify correct workspace path.\n")
diff --git a/tempest/tests/files/setup.cfg b/tempest/tests/files/setup.cfg
index bd68708..a81d31e 100644
--- a/tempest/tests/files/setup.cfg
+++ b/tempest/tests/files/setup.cfg
@@ -3,7 +3,7 @@
 version = 1
 summary = Fake Project for testing wrapper scripts
 author = OpenStack
-author-email = openstack-dev@lists.openstack.org
+author-email = openstack-discuss@lists.openstack.org
 home-page = https://docs.openstack.org/tempest/latest/
 classifier =
     Intended Audience :: Information Technology
diff --git a/tempest/tests/lib/common/test_http.py b/tempest/tests/lib/common/test_http.py
index 02436e0..a19153f 100644
--- a/tempest/tests/lib/common/test_http.py
+++ b/tempest/tests/lib/common/test_http.py
@@ -167,3 +167,24 @@
                          '%s://%s:%i' % (proxy.scheme,
                                          proxy.host,
                                          proxy.port))
+
+
+class TestClosingHttpRedirects(base.TestCase):
+    def test_redirect_default(self):
+        connection = http.ClosingHttp()
+        self.assertTrue(connection.follow_redirects)
+
+    def test_redirect_off(self):
+        connection = http.ClosingHttp(follow_redirects=False)
+        self.assertFalse(connection.follow_redirects)
+
+
+class TestClosingProxyHttpRedirects(base.TestCase):
+    def test_redirect_default(self):
+        connection = http.ClosingProxyHttp(proxy_url=PROXY_URL)
+        self.assertTrue(connection.follow_redirects)
+
+    def test_redirect_off(self):
+        connection = http.ClosingProxyHttp(follow_redirects=False,
+                                           proxy_url=PROXY_URL)
+        self.assertFalse(connection.follow_redirects)
diff --git a/tempest/tests/lib/services/image/v2/test_resource_types_client.py b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
index 2e3b117..741b4eb 100644
--- a/tempest/tests/lib/services/image/v2/test_resource_types_client.py
+++ b/tempest/tests/lib/services/image/v2/test_resource_types_client.py
@@ -17,7 +17,7 @@
 from tempest.tests.lib.services import base
 
 
-class TestResouceTypesClient(base.BaseServiceTest):
+class TestResourceTypesClient(base.BaseServiceTest):
     FAKE_LIST_RESOURCETYPES = {
         "resource_types": [
             {
@@ -49,21 +49,21 @@
     }
 
     def setUp(self):
-        super(TestResouceTypesClient, self).setUp()
+        super(TestResourceTypesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
         self.client = resource_types_client.ResourceTypesClient(fake_auth,
                                                                 'image',
                                                                 'regionOne')
 
-    def _test_list_resouce_types(self, bytes_body=False):
+    def _test_list_resource_types(self, bytes_body=False):
         self.check_service_client_function(
             self.client.list_resource_types,
             'tempest.lib.common.rest_client.RestClient.get',
             self.FAKE_LIST_RESOURCETYPES,
             bytes_body)
 
-    def test_list_resouce_types_with_str_body(self):
-        self._test_list_resouce_types()
+    def test_list_resource_types_with_str_body(self):
+        self._test_list_resource_types()
 
-    def test_list_resouce_types_with_bytes_body(self):
-        self._test_list_resouce_types(bytes_body=True)
+    def test_list_resource_types_with_bytes_body(self):
+        self._test_list_resource_types(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_agents_client.py b/tempest/tests/lib/services/network/test_agents_client.py
new file mode 100644
index 0000000..aabc6ce
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_agents_client.py
@@ -0,0 +1,37 @@
+# Copyright 2018 AT&T Corporation.
+# All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.services.network import agents_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestAgentsClient(base.BaseServiceTest):
+
+    FAKE_AGENT_ID = "d32019d3-bc6e-4319-9c1d-6123f4135a88"
+
+    def setUp(self):
+        super(TestAgentsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.agents_client = agents_client.AgentsClient(
+            fake_auth, "network", "regionOne")
+
+    def test_delete_agent(self):
+        self.check_service_client_function(
+            self.agents_client.delete_agent,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            agent_id=self.FAKE_AGENT_ID)
diff --git a/tempest/tests/lib/services/volume/v3/test_types_client.py b/tempest/tests/lib/services/volume/v3/test_types_client.py
new file mode 100644
index 0000000..7021a3f
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_types_client.py
@@ -0,0 +1,281 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.services.volume.v3 import types_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTypesClient(base.BaseServiceTest):
+    FAKE_CREATE_VOLUME_TYPE = {
+        'volume_type': {
+            'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+            'name': 'vol-type-001',
+            'description': 'volume type 0001',
+            'is_public': True,
+            'os-volume-type-access:is_public': True,
+            'extra_specs': {
+                'volume_backend_name': 'rbd'
+            }
+        }
+    }
+
+    FAKE_DEFAULT_VOLUME_TYPE_INFO = {
+        'volume_type': {
+            'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+            'qos_specs_id': None,
+            'name': 'volume-type-test',
+            'description': 'default volume type',
+            'is_public': True,
+            'os-volume-type-access:is_public': True,
+            'extra_specs': {
+                'volume_backend_name': 'rbd'
+            }
+        }
+    }
+
+    FAKE_UPDATE_VOLUME_TYPE = {
+        'volume_type': {
+            'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+            'qos_specs_id': None,
+            'name': 'volume-type-test',
+            'description': 'default volume type',
+            'os-volume-type-access:is_public': True,
+            'is_public': True,
+            'extra_specs': {
+                'volume_backend_name': 'rbd'
+            }
+        }
+    }
+
+    FAKE_VOLUME_TYPES = {
+        'volume_types': [
+            {
+                'name': 'volume_type01',
+                'qos_specs_id': None,
+                'extra_specs': {
+                    'volume_backend_name': 'lvmdriver-1'
+                },
+                'os-volume-type-access:is_public': True,
+                'is_public': True,
+                'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+                'description': None
+            },
+            {
+                'name': 'volume_type02',
+                'qos_specs_id': None,
+                'extra_specs': {
+                    'volume_backend_name': 'lvmdriver-1'
+                },
+                'os-volume-type-access:is_public': True,
+                'is_public': True,
+                'id': '8eb69a46-df97-4e41-9586-9a40a7533803',
+                'description': None
+            }
+        ]
+    }
+
+    FAKE_VOLUME_TYPE_EXTRA_SPECS = {
+        'extra_specs': {
+            'capabilities': 'gpu'
+        }
+    }
+
+    FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS = {
+        'capabilities': 'gpu'
+    }
+
+    FAKE_VOLUME_TYPE_ACCESS = {
+        'volume_type_access': [{
+            'volume_type_id': '3c67e124-39ad-4ace-a507-8bb7bf510c26',
+            'project_id': 'f270b245cb11498ca4031deb7e141cfa'
+        }]
+    }
+
+    def setUp(self):
+        super(TestTypesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = types_client.TypesClient(fake_auth,
+                                               'volume',
+                                               'regionOne')
+
+    def _test_list_volume_types(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_volume_types,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VOLUME_TYPES,
+            bytes_body)
+
+    def _test_show_volume_type(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_volume_type,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_DEFAULT_VOLUME_TYPE_INFO,
+            to_utf=bytes_body,
+            volume_type_id="6685584b-1eac-4da6-b5c3-555430cf68ff")
+
+    def _test_create_volume_type(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_volume_type,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_VOLUME_TYPE,
+            to_utf=bytes_body,
+            name='volume-type-test')
+
+    def _test_delete_volume_type(self):
+        self.check_service_client_function(
+            self.client.delete_volume_type,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {}, status=202,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+    def _test_list_volume_types_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_volume_types_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+            to_utf=bytes_body,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+    def _test_show_volume_type_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_volume_type_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+            extra_specs_name='capabilities',
+            to_utf=bytes_body)
+
+    def _test_create_volume_type_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_volume_type_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+            volume_type_id="6685584b-1eac-4da6-b5c3-555430cf68ff",
+            extra_specs=self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+            to_utf=bytes_body)
+
+    def _test_delete_volume_type_extra_specs(self):
+        self.check_service_client_function(
+            self.client.delete_volume_type_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {}, status=202,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+            extra_spec_name='volume_backend_name')
+
+    def _test_update_volume_type(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_volume_type,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_UPDATE_VOLUME_TYPE,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+            to_utf=bytes_body,
+            name='update-volume-type-test',
+            description='test update volume type description')
+
+    def _test_update_volume_type_extra_specs(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_volume_type_extra_specs,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+            extra_spec_name='capabilities',
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+            extra_specs=self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+            to_utf=bytes_body)
+
+    def _test_add_type_access(self):
+        self.check_service_client_function(
+            self.client.add_type_access,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {}, status=202,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+    def _test_remove_type_access(self):
+        self.check_service_client_function(
+            self.client.remove_type_access,
+            'tempest.lib.common.rest_client.RestClient.post',
+            {}, status=202,
+            volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+    def _test_list_type_access(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_type_access,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VOLUME_TYPE_ACCESS,
+            volume_type_id='3c67e124-39ad-4ace-a507-8bb7bf510c26',
+            to_utf=bytes_body)
+
+    def test_list_volume_types_with_str_body(self):
+        self._test_list_volume_types()
+
+    def test_list_volume_types_with_bytes_body(self):
+        self._test_list_volume_types(bytes_body=True)
+
+    def test_show_volume_type_with_str_body(self):
+        self._test_show_volume_type()
+
+    def test_show_volume_type_with_bytes_body(self):
+        self._test_show_volume_type(bytes_body=True)
+
+    def test_create_volume_type_str_body(self):
+        self._test_create_volume_type()
+
+    def test_create_volume_type_with_bytes_body(self):
+        self._test_create_volume_type(bytes_body=True)
+
+    def test_list_volume_types_extra_specs_with_str_body(self):
+        self._test_list_volume_types_extra_specs()
+
+    def test_list_volume_types_extra_specs_with_bytes_body(self):
+        self._test_list_volume_types_extra_specs(bytes_body=True)
+
+    def test_show_volume_type_extra_specs_with_str_body(self):
+        self._test_show_volume_type_extra_specs()
+
+    def test_show_volume_type_extra_specs_with_bytes_body(self):
+        self._test_show_volume_type_extra_specs(bytes_body=True)
+
+    def test_create_volume_type_extra_specs_with_str_body(self):
+        self._test_create_volume_type_extra_specs()
+
+    def test_create_volume_type_extra_specs_with_bytes_body(self):
+        self._test_create_volume_type_extra_specs(bytes_body=True)
+
+    def test_delete_volume_type_extra_specs(self):
+        self._test_delete_volume_type_extra_specs()
+
+    def test_update_volume_type_with_str_body(self):
+        self._test_update_volume_type()
+
+    def test_update_volume_type_with_bytes_body(self):
+        self._test_update_volume_type(bytes_body=True)
+
+    def test_delete_volume_type(self):
+        self._test_delete_volume_type()
+
+    def test_update_volume_type_extra_specs_with_str_body(self):
+        self._test_update_volume_type_extra_specs()
+
+    def test_update_volume_type_extra_specs_with_bytes_body(self):
+        self._test_update_volume_type_extra_specs(bytes_body=True)
+
+    def test_add_type_access(self):
+        self._test_add_type_access()
+
+    def test_remove_type_access(self):
+        self._test_remove_type_access()
+
+    def test_list_type_access_with_str_body(self):
+        self._test_list_type_access()
+
+    def test_list_type_access_with_bytes_body(self):
+        self._test_list_type_access(bytes_body=True)
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 4eb78fb..3772774 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -74,7 +74,8 @@
 # Gerrit prepends 4 garbage octets to the JSON, in order to counter
 # cross-site scripting attacks.  Therefore we must discard it so the
 # json library won't choke.
-projects = sorted(filter(is_in_openstack_namespace, json.loads(r.read()[4:])))
+content = r.read().decode('utf-8')[4:]
+projects = sorted(filter(is_in_openstack_namespace, json.loads(content)))
 
 # Retrieve projects having no deb, puppet, ui or spec namespace as those
 # namespaces do not contains tempest plugins.
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index b27b23a..111c9ce 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -41,8 +41,6 @@
 set -ex
 
 (
-declare -A plugins
-
 if [[ -r doc/source/data/tempest-plugins-registry.header ]]; then
     cat doc/source/data/tempest-plugins-registry.header
 fi
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index 8b4f913..661329b 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -47,7 +47,7 @@
 # retrieve a list of projects having tempest plugins
 PROJECT_LIST="$(python tools/generate-tempest-plugins-list.py)"
 # List of projects having tempest plugin stale or unmaintained from long time
-BLACKLIST="trio2o"
+BLACKLIST="networking-plumgrid,trio2o"
 
 # Function to clone project using zuul-cloner or from git
 function clone_project() {
@@ -105,8 +105,8 @@
 
 # Function to run sanity check on each project
 function plugin_sanity_check() {
-        clone_project "$1"  &&  install_project "$1"  &&  tempest_sanity "$1" \
-        &&  uninstall_project "$1"  &&  "$TVENV" pip install .
+    clone_project "$1"  &&  install_project "$1"  &&  tempest_sanity "$1" \
+    &&  uninstall_project "$1"  &&  "$TVENV" pip install .
 }
 
 # Log status
diff --git a/tox.ini b/tox.ini
index 8208066..4068054 100644
--- a/tox.ini
+++ b/tox.ini
@@ -20,7 +20,7 @@
     OS_STDERR_CAPTURE=1
     OS_TEST_TIMEOUT=160
     PYTHONWARNINGS=default::DeprecationWarning,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:site
-passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST
+passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST GABBI_TEMPEST_PATH
 usedevelop = True
 install_command = pip install {opts} {packages}
 whitelist_externals = *
@@ -48,6 +48,10 @@
   coverage xml -o cover/coverage.xml
   coverage report
 
+[testenv:debug]
+basepython = python3
+commands = oslo_debug_helper -t tempest/tests {posargs}
+
 [testenv:all]
 envdir = .tox/tempest
 sitepackages = {[tempestenv]sitepackages}
@@ -182,6 +186,7 @@
 commands = {posargs}
 
 [testenv:docs]
+basepython = python3
 deps =
   -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
   -r{toxinidir}/requirements.txt
@@ -192,6 +197,7 @@
 whitelist_externals = rm
 
 [testenv:pep8]
+basepython = python3
 commands =
     flake8 {posargs}
     check-uuid
@@ -216,6 +222,7 @@
 import-order-style = pep8
 
 [testenv:releasenotes]
+basepython = python3
 deps =
   -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
   -r{toxinidir}/requirements.txt