Merge "Make connectivity test less backend dependent"
diff --git a/.zuul.yaml b/.zuul.yaml
index c5f7ecc..9af79a6 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -51,6 +51,41 @@
         ENABLE_FILE_INJECTION: true
 
 - job:
+    name: devstack-tempest-ipv6
+    parent: devstack-ipv6
+    nodeset: openstack-single-node
+    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.
@@ -68,6 +103,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
@@ -106,6 +172,28 @@
         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
@@ -238,22 +326,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
@@ -262,19 +354,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
@@ -283,21 +377,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
@@ -372,6 +470,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
@@ -383,10 +487,16 @@
               - ^setup.cfg$
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
+              - ^tools/.*$
         - tempest-full-parallel:
             irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3:
+            irrelevant-files: *tempest-irrelevant-files
         - tempest-full-py36:
             irrelevant-files: *tempest-irrelevant-files
+        - tempest-full-py3-ipv6:
+            voting: false
+            irrelevant-files: *tempest-irrelevant-files
         - tempest-full-rocky:
             irrelevant-files: *tempest-irrelevant-files
         - tempest-full-rocky-py3:
@@ -399,7 +509,17 @@
             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:
@@ -459,8 +579,6 @@
             irrelevant-files: *tempest-irrelevant-files
         - tempest-all:
             irrelevant-files: *tempest-irrelevant-files
-        - legacy-tempest-dsvm-multinode-full:
-            irrelevant-files: *tempest-irrelevant-files
         - legacy-tempest-dsvm-neutron-dvr-multinode-full:
             irrelevant-files: *tempest-irrelevant-files
         - neutron-tempest-dvr-ha-multinode-full:
@@ -486,3 +604,4 @@
     periodic:
       jobs:
         - tempest-all
+        - tempest-full-oslo-master
diff --git a/HACKING.rst b/HACKING.rst
index e767b25..e8791f8 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -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/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/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/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/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 2a5d607..05c2a28 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -24,6 +24,7 @@
 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_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/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_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_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/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/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/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index 0730d58..399954c 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",
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/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/workspace.py b/tempest/cmd/workspace.py
index d276bde..d0c4b28 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -94,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
@@ -115,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)
@@ -136,6 +137,10 @@
             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)
@@ -144,7 +149,7 @@
     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)
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index f2730b3..e218d5a 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:
diff --git a/tempest/config.py b/tempest/config.py
index dbce504..18c9d9d 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -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',
@@ -463,20 +483,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/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/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/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/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 57a560c..438ee01 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -280,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/test.py b/tempest/test.py
index f2babbb..3e98c33 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -581,7 +581,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_workspace.py b/tempest/tests/cmd/test_workspace.py
index a256368..65481de 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -140,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))
@@ -149,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)
@@ -207,3 +230,15 @@
         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/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/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..4d3c622 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 = *
@@ -182,6 +182,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 +193,7 @@
 whitelist_externals = rm
 
 [testenv:pep8]
+basepython = python3
 commands =
     flake8 {posargs}
     check-uuid
@@ -216,6 +218,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