Merge "compute: Add [compute-feature-enabled]ide_bus flag"
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index c43e420..c790c5f 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -153,6 +153,11 @@
 
 Pre-Provisioned Credentials are also known as accounts.yaml or accounts file.
 
+Keystone Scopes & Roles Support in Tempest
+""""""""""""""""""""""""""""""""""""""""""
+For details on scope and roles support in Tempest,
+please refer to :doc:`this document <keystone_scopes_and_roles_support>`
+
 Compute
 -------
 
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 66e68ea..2f29cf2 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -105,6 +105,14 @@
 
    tempest_and_plugins_compatible_version_policy
 
+Keystone Scopes & Roles Support in Tempest
+------------------------------------------
+
+.. toctree::
+   :maxdepth: 2
+
+   keystone_scopes_and_roles_support
+
 Stable Branch Support Policy
 ----------------------------
 
diff --git a/doc/source/keystone_scopes_and_roles_support.rst b/doc/source/keystone_scopes_and_roles_support.rst
new file mode 100644
index 0000000..f446f8c
--- /dev/null
+++ b/doc/source/keystone_scopes_and_roles_support.rst
@@ -0,0 +1,286 @@
+Keystone Scopes & Roles Support in Tempest
+==========================================
+
+OpenStack Keystone supports different scopes in token, refer to the
+`Keystone doc <https://docs.openstack.org/keystone/latest/admin/tokens-overview.html#authorization-scopes>`_.
+Along with the scopes, keystone supports default roles, one of which
+is a reader role, for details refer to
+`this keystone document <https://docs.openstack.org/keystone/latest/admin/service-api-protection.html>`_.
+
+Tempest supports those scopes and roles credentials that can be used
+to test APIs under different scope and roles.
+
+Dynamic Credentials
+-------------------
+
+Dynamic credential supports all the below set of personas and allows
+you to generate credentials tailored to a specific persona that you
+can use in your test.
+
+Domain scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+  #. Domain Admin: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['domain_admin']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_d_admin_client = (
+                     cls.os_domain_admin.availability_zone_client)
+
+  #. Domain Member: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['domain_member']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_d_member_client = (
+                     cls.os_domain_member.availability_zone_client)
+
+  #. Domain Reader: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['domain_reader']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_d_reader_client = (
+                     cls.os_domain_reader.availability_zone_client)
+
+  #. Domain other roles: This is supported and can be requested and used from
+     the test as below:
+
+     You need to use the ``domain`` as the prefix in credentials type, and
+     based on that, Tempest will create test users under 'domain' scope.
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = [['domain_my_role1', 'my_own_role1', 'admin']
+                            ['domain_my_role2', 'my_own_role2']]
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_d_role1_client = (
+                     cls.os_domain_my_role1.availability_zone_client)
+                 cls.az_d_role2_client = (
+                     cls.os_domain_my_role2.availability_zone_client)
+
+System scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^
+
+  #. System Admin: This is supported and can be requested and used from the
+     test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['system_admin']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_s_admin_client = (
+                     cls.os_system_admin.availability_zone_client)
+
+  #. System Member: This is supported and can be requested and used from the
+     test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['system_member']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_s_member_client = (
+                     cls.os_system_member.availability_zone_client)
+
+  #. System Reader: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['system_reader']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_s_reader_client = (
+                     cls.os_system_reader.availability_zone_client)
+
+  #. System other roles: This is supported and can be requested and used from
+     the test as below:
+
+     You need to use the ``system`` as the prefix in credentials type, and
+     based on that, Tempest will create test users under 'project' scope.
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = [['system_my_role1', 'my_own_role1', 'admin']
+                            ['system_my_role2', 'my_own_role2']]
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_s_role1_client = (
+                     cls.os_system_my_role1.availability_zone_client)
+                 cls.az_s_role2_client = (
+                     cls.os_system_my_role2.availability_zone_client)
+
+Project scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+  #. Project Admin: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_admin']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_admin_client = (
+                     cls.os_project_admin.availability_zone_client)
+
+  #. Project Member: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_member']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_member_client = (
+                     cls.os_project_member.availability_zone_client)
+
+  #. Project Reader: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_reader']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_reader_client = (
+                     cls.os_project_reader.availability_zone_client)
+
+  #. Project alternate Admin: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_alt_admin']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_alt_admin_client = (
+                     cls.os_project_alt_admin.availability_zone_client)
+
+  #. Project alternate Member: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_alt_member']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_alt_member_client = (
+                     cls.os_project_alt_member.availability_zone_client)
+
+  #. Project alternate Reader: This is supported and can be requested and used from
+     the test as below:
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = ['project_alt_reader']
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_p_alt_reader_client = (
+                     cls.os_project_alt_reader.availability_zone_client)
+
+  #. Project other roles: This is supported and can be requested and used from
+     the test as below:
+
+     You need to use the ``project`` as the prefix in credentials type, and
+     based on that, Tempest will create test users under 'project' scope.
+
+     .. code-block:: python
+
+         class TestDummy(base.DummyBaseTest):
+
+             credentials = [['project_my_role1', 'my_own_role1', 'admin']
+                            ['project_my_role2', 'my_own_role2']]
+
+             @classmethod
+             def setup_clients(cls):
+                 super(TestDummy, cls).setup_clients()
+                 cls.az_role1_client = (
+                     cls.os_project_my_role1.availability_zone_client)
+                 cls.az_role2_client = (
+                     cls.os_project_my_role2.availability_zone_client)
+
+Pre-Provisioned Credentials
+---------------------------
+
+Pre-Provisioned credentials support the below set of personas and can be
+used in the test as shown above in the ``Dynamic Credentials`` Section.
+
+* Domain Admin
+* Domain Member
+* Domain Reader
+* System Admin
+* System Member
+* System Reader
+* Project Admin
+* Project Member
+* Project Reader
diff --git a/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml b/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml
new file mode 100644
index 0000000..fde6193
--- /dev/null
+++ b/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Adds a method to images_client to get tasks relevant to a given image. Also adds
+    has_version() method to image versions_client to probe for availability of a given
+    API version.
diff --git a/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml b/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml
new file mode 100644
index 0000000..ac20340
--- /dev/null
+++ b/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    The old deprecated ``data-ultis`` from ``tempest.common.utils`` has been
+    removed. If you are still using this, use the stable version
+    of ``data-utils`` from new location ``tempest.lib.common.utils``.
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index 52713be..5f13883 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -6,3 +6,6 @@
 stable_constraints_file: "{{ devstack_base_dir }}/requirements/upper-constraints.txt"
 target_branch: "{{ zuul.branch }}"
 tempest_tox_environment: {}
+# NOTE(gmann): external_bridge_mtu shows as undefined for run-tempest role
+# defining default value here to avoid that error.
+external_bridge_mtu: 0
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 5b00de3..37026e4 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -31,6 +31,17 @@
     tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': stable_constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': stable_constraints_file}) }}"
   when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]
 
+- name: Use Configured upper-constraints for non-master Tempest
+  set_fact:
+    # TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
+    tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS']}) | combine({'TOX_CONSTRAINTS_FILE': devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS']}) }}"
+  when:
+    - devstack_localrc is defined
+    - "'TEMPEST_BRANCH' in devstack_localrc"
+    - "'TEMPEST_VENV_UPPER_CONSTRAINTS' in devstack_localrc"
+    - devstack_localrc['TEMPEST_BRANCH'] != 'master'
+    - devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS'] != 'default'
+
 - name: Set OS_TEST_TIMEOUT if requested
   set_fact:
     tempest_tox_environment: "{{ tempest_tox_environment | combine({'OS_TEST_TIMEOUT': tempest_test_timeout}) }}"
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 59067d1..efa23bb 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -68,16 +68,12 @@
         self.assertEqual('queued', image['status'])
         return image
 
-    @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2')
-    def test_image_glance_direct_import(self):
-        """Test 'glance-direct' import functionalities
-
-        Create image, stage image data, import image and verify
-        that import succeeded.
-        """
-        if 'glance-direct' not in self.available_import_methods:
+    def _require_import_method(self, method):
+        if method not in self.available_import_methods:
             raise self.skipException('Server does not support '
-                                     'glance-direct import method')
+                                     '%s import method' % method)
+
+    def _stage_and_check(self):
         image = self._create_image()
         # Stage image data
         file_content = data_utils.random_bytes()
@@ -87,9 +83,38 @@
         body = self.client.show_image(image['id'])
         self.assertEqual(image['id'], body['id'])
         self.assertEqual('uploading', body['status'])
+        return image['id']
+
+    @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2')
+    def test_image_glance_direct_import(self):
+        """Test 'glance-direct' import functionalities
+
+        Create image, stage image data, import image and verify
+        that import succeeded.
+        """
+        self._require_import_method('glance-direct')
+
+        image_id = self._stage_and_check()
         # import image from staging to backend
-        self.client.image_import(image['id'], method='glance-direct')
-        waiters.wait_for_image_imported_to_stores(self.client, image['id'])
+        resp = self.client.image_import(image_id, method='glance-direct')
+        waiters.wait_for_image_imported_to_stores(self.client, image_id)
+
+        if not self.versions_client.has_version('2.12'):
+            # API is not new enough to support image/tasks API
+            LOG.info('Glance does not support v2.12, so I am unable to '
+                     'validate the image/tasks API.')
+            return
+
+        # Make sure we can access the task and that some of the key
+        # fields look legit.
+        tasks = self.client.show_image_tasks(image_id)
+        self.assertEqual(1, len(tasks['tasks']))
+        task = tasks['tasks'][0]
+        self.assertEqual('success', task['status'])
+        self.assertEqual(resp.response['x-openstack-request-id'],
+                         task['request_id'])
+        self.assertEqual('glance-direct',
+                         task['input']['import_req']['method']['name'])
 
     @decorators.idempotent_id('f6feb7a4-b04f-4706-a011-206129f83e62')
     def test_image_web_download_import(self):
@@ -98,9 +123,8 @@
         Create image, import image and verify that import
         succeeded.
         """
-        if 'web-download' not in self.available_import_methods:
-            raise self.skipException('Server does not support '
-                                     'web-download import method')
+        self._require_import_method('web-download')
+
         image = self._create_image()
         # Now try to get image details
         body = self.client.show_image(image['id'])
@@ -112,6 +136,47 @@
                                  image_uri=image_uri)
         waiters.wait_for_image_imported_to_stores(self.client, image['id'])
 
+    @decorators.idempotent_id('e04761a1-22af-42c2-b8bc-a34a3f12b585')
+    def test_remote_import(self):
+        """Test image import against a different worker than stage.
+
+        This creates and stages an image against the primary API worker,
+        but then calls import on a secondary worker (if available) to
+        test that distributed image import works (i.e. proxies the import
+        request to the proper worker).
+        """
+        self._require_import_method('glance-direct')
+
+        if not CONF.image.alternate_image_endpoint:
+            raise self.skipException('No image_remote service to test '
+                                     'against')
+
+        image_id = self._stage_and_check()
+        # import image from staging to backend, but on the alternate worker
+        self.os_primary.image_client_remote.image_import(
+            image_id, method='glance-direct')
+        waiters.wait_for_image_imported_to_stores(self.client, image_id)
+
+    @decorators.idempotent_id('44d60544-1524-42f7-8899-315301105dd8')
+    def test_remote_delete(self):
+        """Test image delete against a different worker than stage.
+
+        This creates and stages an image against the primary API worker,
+        but then calls delete on a secondary worker (if available) to
+        test that distributed image import works (i.e. proxies the delete
+        request to the proper worker).
+        """
+        self._require_import_method('glance-direct')
+
+        if not CONF.image.alternate_image_endpoint:
+            raise self.skipException('No image_remote service to test '
+                                     'against')
+
+        image_id = self._stage_and_check()
+        # delete image from staging to backend, but on the alternate worker
+        self.os_primary.image_client_remote.delete_image(image_id)
+        self.client.wait_for_resource_deletion(image_id)
+
 
 class MultiStoresImportImagesTest(base.BaseV2ImageTest):
     """Test importing image in multiple stores"""
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index 810c37c..a3802a9 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -14,6 +14,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import time
+
 from tempest.api.image import base
 from tempest import config
 from tempest.lib.common.utils import data_utils
@@ -156,3 +158,63 @@
                           container_format='bare',
                           disk_format='raw',
                           os_glance_foo='bar')
+
+
+class ImportImagesNegativeTest(base.BaseV2ImageTest):
+    """Here we test the import operations for image"""
+
+    @classmethod
+    def skip_checks(cls):
+        super(ImportImagesNegativeTest, cls).skip_checks()
+        if not CONF.image_feature_enabled.import_image:
+            skip_msg = (
+                "%s skipped as image import is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(ImportImagesNegativeTest, cls).resource_setup()
+        cls.available_import_methods = cls.client.info_import()[
+            'import-methods']['value']
+        if not cls.available_import_methods:
+            raise cls.skipException('Server does not support '
+                                    'any import method')
+
+        cls.available_stores = cls.get_available_stores()
+        if not len(cls.available_stores) > 0:
+            raise cls.skipException(
+                'No stores configured %s' % cls.available_stores)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('c52f6a77-f522-4411-8dbe-9d14f2b1ba6b')
+    def test_image_web_download_import_with_bad_url(self):
+        """Test 'web-download' import functionalities
+
+        Make sure that web-download with invalid URL fails properly.
+        """
+        if 'web-download' not in self.available_import_methods:
+            raise self.skipException('Server does not support '
+                                     'web-download import method')
+        image = self.client.create_image(name='test',
+                                         container_format='bare',
+                                         disk_format='raw')
+        # Now try to get image details
+        body = self.client.show_image(image['id'])
+        self.assertEqual(image['id'], body['id'])
+        self.assertEqual('queued', body['status'])
+        stores = self.get_available_stores()
+        # import image from web to backend
+        image_uri = 'http://does-not.exist/no/possible/way'
+        self.client.image_import(image['id'], method='web-download',
+                                 image_uri=image_uri,
+                                 stores=[stores[0]['id']])
+
+        start_time = int(time.time())
+        while int(time.time()) - start_time < self.client.build_timeout:
+            body = self.client.show_image(image['id'])
+            if body.get('os_glance_failed_import'):
+                # Store ended up in failed list, which is good
+                return
+            time.sleep(self.client.build_interval)
+
+        self.fail('Image never reported failed store')
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index eb31d24..64f6e80 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -15,9 +15,9 @@
 
 from tempest.api.network import base
 from tempest.common import utils
-from tempest.common.utils import data_utils
 from tempest.common.utils import net_utils
 from tempest import config
+from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index fc9b1a2..93f6fdb 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -13,12 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import hashlib
 import random
 import re
 import time
 import zlib
 
+from oslo_utils.secretutils import md5
 from tempest.api.object_storage import base
 from tempest.common import custom_matchers
 from tempest import config
@@ -151,8 +151,8 @@
         """Test creating object with Etag"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
-        md5 = hashlib.md5(data).hexdigest()
-        metadata = {'Etag': md5}
+        create_md5 = md5(data, usedforsecurity=False).hexdigest()
+        metadata = {'Etag': create_md5}
         resp, _ = self.object_client.create_object(
             self.container_name,
             object_name,
@@ -641,7 +641,7 @@
         """Test getting object with if_match"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes(10)
-        create_md5 = hashlib.md5(data).hexdigest()
+        create_md5 = md5(data, usedforsecurity=False).hexdigest()
         create_metadata = {'Etag': create_md5}
         self.object_client.create_object(self.container_name,
                                          object_name,
@@ -681,7 +681,7 @@
         """Test getting object with if_none_match"""
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes()
-        create_md5 = hashlib.md5(data).hexdigest()
+        create_md5 = md5(data, usedforsecurity=False).hexdigest()
         create_metadata = {'Etag': create_md5}
         self.object_client.create_object(self.container_name,
                                          object_name,
@@ -689,7 +689,7 @@
                                          metadata=create_metadata)
 
         list_data = data_utils.random_bytes()
-        list_md5 = hashlib.md5(list_data).hexdigest()
+        list_md5 = md5(list_data, usedforsecurity=False).hexdigest()
         list_metadata = {'If-None-Match': list_md5}
         resp, body = self.object_client.get_object(
             self.container_name,
@@ -978,8 +978,8 @@
         """
         object_name, data = self.create_object(self.container_name)
         # local copy is identical, no download
-        md5 = hashlib.md5(data).hexdigest()
-        headers = {'If-None-Match': md5}
+        object_md5 = md5(data, usedforsecurity=False).hexdigest()
+        headers = {'If-None-Match': object_md5}
         url = "%s/%s" % (self.container_name, object_name)
         resp, _ = self.object_client.get(url, headers=headers)
         self.assertEqual(resp['status'], '304')
@@ -993,8 +993,8 @@
 
         # local copy is different, download
         local_data = "something different"
-        md5 = hashlib.md5(local_data.encode()).hexdigest()
-        headers = {'If-None-Match': md5}
+        other_md5 = md5(local_data.encode(), usedforsecurity=False).hexdigest()
+        headers = {'If-None-Match': other_md5}
         resp, _ = self.object_client.get(url, headers=headers)
         self.assertHeaders(resp, 'Object', 'GET')
 
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 664bbc8..0c84357 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -12,10 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import hashlib
-
 from oslo_serialization import jsonutils as json
 
+from oslo_utils.secretutils import md5
 from tempest.api.object_storage import base
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
@@ -70,10 +69,12 @@
         path_object_2 = '/%s/%s' % (self.container_name,
                                     object_name_base_2)
         data_manifest = [{'path': path_object_1,
-                          'etag': hashlib.md5(self.content).hexdigest(),
+                          'etag': md5(self.content,
+                                      usedforsecurity=False).hexdigest(),
                           'size_bytes': data_size},
                          {'path': path_object_2,
-                          'etag': hashlib.md5(self.content).hexdigest(),
+                          'etag': md5(self.content,
+                                      usedforsecurity=False).hexdigest(),
                           'size_bytes': data_size}]
 
         return json.dumps(data_manifest)
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index 0a8b56d..659e2c4 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -64,7 +64,6 @@
 class GroupSnapshotsTest(BaseGroupSnapshotsTest):
     """Test group snapshot"""
 
-    _api_version = 3
     min_microversion = '3.14'
     max_microversion = 'latest'
 
@@ -253,7 +252,6 @@
 class GroupSnapshotsV319Test(BaseGroupSnapshotsTest):
     """Test group snapshot with volume microversion greater than 3.18"""
 
-    _api_version = 3
     min_microversion = '3.19'
     max_microversion = 'latest'
 
diff --git a/tempest/api/volume/admin/test_group_type_specs.py b/tempest/api/volume/admin/test_group_type_specs.py
index 159c6fb..5c5913e 100644
--- a/tempest/api/volume/admin/test_group_type_specs.py
+++ b/tempest/api/volume/admin/test_group_type_specs.py
@@ -21,7 +21,6 @@
 class GroupTypeSpecsTest(base.BaseVolumeAdminTest):
     """Test group type specs"""
 
-    _api_version = 3
     min_microversion = '3.11'
     max_microversion = 'latest'
 
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
index 3993020..a7a5d6f 100644
--- a/tempest/api/volume/admin/test_group_types.py
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -21,7 +21,6 @@
 class GroupTypesTest(base.BaseVolumeAdminTest):
     """Test group types"""
 
-    _api_version = 3
     min_microversion = '3.11'
     max_microversion = 'latest'
 
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
index e67b985..747a194 100644
--- a/tempest/api/volume/admin/test_groups.py
+++ b/tempest/api/volume/admin/test_groups.py
@@ -25,7 +25,6 @@
 class GroupsTest(base.BaseVolumeAdminTest):
     """Tests of volume groups with microversion greater than 3.12"""
 
-    _api_version = 3
     min_microversion = '3.13'
     max_microversion = 'latest'
 
@@ -156,7 +155,6 @@
 class GroupsV314Test(base.BaseVolumeAdminTest):
     """Tests of volume groups with microversion greater than 3.13"""
 
-    _api_version = 3
     min_microversion = '3.14'
     max_microversion = 'latest'
 
@@ -194,7 +192,6 @@
 class GroupsV320Test(base.BaseVolumeAdminTest):
     """Tests of volume groups with microversion greater than 3.19"""
 
-    _api_version = 3
     min_microversion = '3.20'
     max_microversion = 'latest'
 
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 096709c..768c129 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -24,7 +24,6 @@
 class UserMessagesTest(base.BaseVolumeAdminTest):
     """Test volume messages with microversion greater than 3.2"""
 
-    _api_version = 3
     min_microversion = '3.3'
     max_microversion = 'latest'
 
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index f6559f8..9f9fc3b 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -32,7 +32,6 @@
     # Set this to True in subclasses to create a default network. See
     # https://bugs.launchpad.net/tempest/+bug/1844568
     create_default_network = False
-    _api_version = 2
     credentials = ['primary']
 
     @classmethod
diff --git a/tempest/api/volume/test_versions.py b/tempest/api/volume/test_versions.py
index e065bdf..578be58 100644
--- a/tempest/api/volume/test_versions.py
+++ b/tempest/api/volume/test_versions.py
@@ -19,8 +19,6 @@
 class VersionsTest(base.BaseVolumeTest):
     """Test volume versions"""
 
-    _api_version = 3
-
     @decorators.idempotent_id('77838fc4-b49b-4c64-9533-166762517369')
     @decorators.attr(type='smoke')
     def test_list_versions(self):
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 2e78114..fff6a44 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -164,7 +164,6 @@
 class VolumesBackupsV39Test(base.BaseVolumeTest):
     """Test volumes backup with volume microversion greater than 3.8"""
 
-    _api_version = 3
     min_microversion = '3.9'
     max_microversion = 'latest'
 
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 7441f1d..d9790f3 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -77,7 +77,6 @@
     # details once that microversion is available in Nova.
     credentials = ['primary', 'admin']
 
-    _api_version = 3
     # NOTE(mriedem): The minimum required volume API version is 3.42 and the
     # minimum required compute API microversion is 2.51, but the compute call
     # is implicit - Cinder calls Nova at that microversion, Tempest does not.
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 91728ab..28e41bf 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -143,7 +143,6 @@
 class VolumesSummaryTest(base.BaseVolumeTest):
     """Test volume summary"""
 
-    _api_version = 3
     min_microversion = '3.12'
     max_microversion = 'latest'
 
diff --git a/tempest/clients.py b/tempest/clients.py
index 9ff02ea..6db43db 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -88,6 +88,14 @@
                 self.image_v2.NamespacePropertiesClient()
             self.namespace_tags_client = self.image_v2.NamespaceTagsClient()
             self.image_versions_client = self.image_v2.VersionsClient()
+            # NOTE(danms): If no alternate endpoint is configured,
+            # this client will work the same as the base self.images_client.
+            # If your test needs to know if these are different, check the
+            # config option to see if the alternate_image_endpoint is set.
+            self.image_client_remote = self.image_v2.ImagesClient(
+                service=CONF.image.alternate_image_endpoint,
+                endpoint_type=CONF.image.alternate_image_endpoint_type,
+                region=CONF.image.region)
 
     def _set_compute_clients(self):
         self.agents_client = self.compute.AgentsClient()
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 38881ee..88a16b7 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -13,37 +13,17 @@
 #    under the License.
 
 import functools
-from functools import partial
 
 import testtools
 
 from tempest import config
 from tempest.exceptions import InvalidServiceTag
-from tempest.lib.common.utils import data_utils as lib_data_utils
 from tempest.lib import decorators
 
 
 CONF = config.CONF
 
 
-class DataUtils(object):
-    def __getattr__(self, attr):
-
-        if attr == 'rand_name':
-            # NOTE(flwang): This is a proxy to generate a random name that
-            # includes a random number and a prefix 'tempest'
-            attr_obj = partial(lib_data_utils.rand_name,
-                               prefix='tempest')
-        else:
-            attr_obj = getattr(lib_data_utils, attr)
-
-        self.__dict__[attr] = attr_obj
-        return attr_obj
-
-
-data_utils = DataUtils()
-
-
 def get_service_list():
     service_list = {
         'compute': CONF.service_available.nova,
diff --git a/tempest/config.py b/tempest/config.py
index c2378d1..46aae23 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -661,6 +661,15 @@
                choices=['public', 'admin', 'internal',
                         'publicURL', 'adminURL', 'internalURL'],
                help="The endpoint type to use for the image service."),
+    cfg.StrOpt('alternate_image_endpoint',
+               default=None,
+               help="Alternate endpoint name for cross-worker testing"),
+    cfg.StrOpt('alternate_image_endpoint_type',
+               default='publicURL',
+               choices=['public', 'admin', 'internal',
+                        'publicURL', 'adminURL', 'internalURL'],
+               help=("The endpoint type to use for the alternate image "
+                     "service.")),
     cfg.StrOpt('http_image',
                default='http://download.cirros-cloud.net/0.3.1/'
                'cirros-0.3.1-x86_64-uec.tar.gz',
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index df0f4d6..6d948cf 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -12,11 +12,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import hashlib
 import os
 
 from oslo_concurrency import lockutils
 from oslo_log import log as logging
+from oslo_utils.secretutils import md5
 import yaml
 
 from tempest.lib import auth
@@ -134,7 +134,7 @@
                 scope = 'domain'
             elif 'system' in account:
                 scope = 'system'
-            temp_hash = hashlib.md5()
+            temp_hash = md5(usedforsecurity=False)
             account_for_hash = dict((k, v) for (k, v) in account.items()
                                     if k in cls.HASH_CRED_FIELDS)
             temp_hash.update(str(account_for_hash).encode('utf-8'))
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index fa3bb8c..abf427c 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -121,6 +121,14 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
+    def show_image_tasks(self, image_id):
+        """Show image tasks."""
+        url = 'images/%s/tasks' % image_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
     def is_resource_deleted(self, id):
         try:
             self.show_image(id)
diff --git a/tempest/lib/services/image/v2/versions_client.py b/tempest/lib/services/image/v2/versions_client.py
index 1b7f806..98b4fb6 100644
--- a/tempest/lib/services/image/v2/versions_client.py
+++ b/tempest/lib/services/image/v2/versions_client.py
@@ -30,3 +30,13 @@
         self.expected_success(300, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
+
+    def has_version(self, version):
+        """Return True if a version is supported."""
+        version = 'v%s' % version
+        supported = ['SUPPORTED', 'CURRENT']
+        versions = self.list_versions()
+        for version_struct in versions['versions']:
+            if version_struct['id'] == version:
+                return version_struct['status'] in supported
+        return False
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index e19efb7..a8a4c0f 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -18,12 +18,9 @@
 import fixtures
 from oslo_serialization import jsonutils as json
 
-from tempest import clients
 from tempest.cmd import init
 from tempest.cmd import verify_tempest_config
-from tempest.common import credentials_factory
 from tempest import config
-from tempest.lib.common import rest_client
 from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions as lib_exc
 from tempest.tests import base
@@ -514,23 +511,24 @@
         self.assertEqual([], results['swift']['extensions'])
 
     def test_get_extension_client(self):
-        creds = credentials_factory.get_credentials(
-            fill_in=False, username='fake_user', project_name='fake_project',
-            password='fake_password')
-        os = clients.Manager(creds)
-        for service in ['nova', 'neutron', 'swift', 'cinder']:
+        fake_os = mock.MagicMock()
+        services = {
+            'nova': fake_os.compute.ExtensionsClient(),
+            'neutron': fake_os.network.ExtensionsClient(),
+            'swift': fake_os.object_storage.CapabilitiesClient(),
+            'cinder': fake_os.volume_v2.ExtensionsClient(),
+        }
+        for service in services.keys():
             extensions_client = verify_tempest_config.get_extension_client(
-                os, service)
-            self.assertIsInstance(extensions_client, rest_client.RestClient)
+                fake_os, service)
+            self.assertIsInstance(extensions_client, mock.MagicMock)
+            self.assertEqual(extensions_client, services[service])
 
     def test_get_extension_client_sysexit(self):
-        creds = credentials_factory.get_credentials(
-            fill_in=False, username='fake_user', project_name='fake_project',
-            password='fake_password')
-        os = clients.Manager(creds)
+        fake_os = mock.MagicMock()
         self.assertRaises(SystemExit,
                           verify_tempest_config.get_extension_client,
-                          os, 'fakeservice')
+                          fake_os, 'fakeservice')
 
     def test_get_config_file(self):
         conf_dir = os.path.join(os.getcwd(), 'etc')
diff --git a/tempest/tests/lib/cmd/test_check_uuid.py b/tempest/tests/lib/cmd/test_check_uuid.py
index 428e047..5d63dec 100644
--- a/tempest/tests/lib/cmd/test_check_uuid.py
+++ b/tempest/tests/lib/cmd/test_check_uuid.py
@@ -13,13 +13,13 @@
 import ast
 import importlib
 import os
+import shutil
 import sys
 import tempfile
 from unittest import mock
 
-import fixtures
-
 from tempest.lib.cmd import check_uuid
+from tempest.lib import decorators
 from tempest.tests import base
 
 
@@ -40,23 +40,24 @@
         return tests_file
 
     def test_fix_argument_no(self):
-        temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
-        tests_file = self.create_tests_file(temp_dir.path)
-
+        temp_dir = tempfile.mkdtemp(prefix='check-uuid-no', dir=".")
+        self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
+        tests_file = self.create_tests_file(temp_dir)
         sys.argv = [sys.argv[0]] + ["--package",
-                                    os.path.relpath(temp_dir.path)]
+                                    os.path.relpath(temp_dir)]
 
         self.assertRaises(SystemExit, check_uuid.run)
         with open(tests_file, "r") as f:
             self.assertTrue(TestCLInterface.CODE == f.read())
 
+    @decorators.skip_because(bug='1918316')
     def test_fix_argument_yes(self):
-        temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
-        tests_file = self.create_tests_file(temp_dir.path)
+        temp_dir = tempfile.mkdtemp(prefix='check-uuid-yes', dir=".")
+        self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
+        tests_file = self.create_tests_file(temp_dir)
 
         sys.argv = [sys.argv[0]] + ["--fix", "--package",
-                                    os.path.relpath(temp_dir.path)]
-
+                                    os.path.relpath(temp_dir)]
         check_uuid.run()
         with open(tests_file, "r") as f:
             self.assertTrue(TestCLInterface.CODE != f.read())
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index 499b6fe..fe7fcd2 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -12,7 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import hashlib
 import os
 import shutil
 from unittest import mock
@@ -22,6 +21,7 @@
 import fixtures
 from oslo_concurrency.fixture import lockutils as lockutils_fixtures
 from oslo_config import cfg
+from oslo_utils.secretutils import md5
 
 from tempest import config
 from tempest.lib import auth
@@ -105,7 +105,7 @@
         hash_fields = (
             preprov_creds.PreProvisionedCredentialProvider.HASH_CRED_FIELDS)
         for account in accounts_list:
-            hash = hashlib.md5()
+            hash = md5(usedforsecurity=False)
             account_for_hash = dict((k, v) for (k, v) in account.items()
                                     if k in hash_fields)
             hash.update(str(account_for_hash).encode('utf-8'))
diff --git a/tempest/tests/lib/services/image/v2/test_images_client.py b/tempest/tests/lib/services/image/v2/test_images_client.py
index 7ee61d2..5b162f8 100644
--- a/tempest/tests/lib/services/image/v2/test_images_client.py
+++ b/tempest/tests/lib/services/image/v2/test_images_client.py
@@ -105,6 +105,44 @@
         "first": "/v2/images"
     }
 
+    FAKE_SHOW_IMAGE_TASKS = {
+        "tasks": [
+            {
+                "id": "ee22890e-8948-4ea6-9668-831f973c84f5",
+                "image_id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
+                "request-id": "rrrrrrr-rrrr-rrrr-rrrr-rrrrrrrrrrrr",
+                "user": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu",
+                "type": "api_image_import",
+                "status": "processing",
+                "owner": "64f0efc9955145aeb06f297a8a6fe402",
+                "expires_at": None,
+                "created_at": "2020-12-18T05:20:38.000000",
+                "updated_at": "2020-12-18T05:25:39.000000",
+                "deleted_at": None,
+                "deleted": False,
+                "input": {
+                    "image_id": "829c729b-ebc4-4cc7-a164-6f43f1149b17",
+                    "import_req": {
+                        "method": {
+                            "name": "copy-image",
+                        },
+                        "all_stores": True,
+                        "all_stores_must_succeed": False,
+                    },
+                    "backend": [
+                        "fast",
+                        "cheap",
+                        "slow",
+                        "reliable",
+                        "common",
+                    ]
+                },
+                "result": None,
+                "message": "Copied 15 MiB",
+            }
+        ]
+    }
+
     FAKE_TAG_NAME = "fake tag"
 
     def setUp(self):
@@ -230,3 +268,11 @@
 
     def test_list_images_with_bytes_body(self):
         self._test_list_images(bytes_body=True)
+
+    def test_show_image_tasks(self):
+        self.check_service_client_function(
+            self.client.show_image_tasks,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SHOW_IMAGE_TASKS,
+            True,
+            image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8")
diff --git a/tempest/tests/lib/services/image/v2/test_versions_client.py b/tempest/tests/lib/services/image/v2/test_versions_client.py
index 6234b06..98c558a 100644
--- a/tempest/tests/lib/services/image/v2/test_versions_client.py
+++ b/tempest/tests/lib/services/image/v2/test_versions_client.py
@@ -12,6 +12,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import fixtures
+
 from tempest.lib.services.image.v2 import versions_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
@@ -92,3 +94,13 @@
 
     def test_list_versions_with_bytes_body(self):
         self._test_list_versions(bytes_body=True)
+
+    def test_has_version(self):
+        mocked_r = self.create_response(self.FAKE_VERSIONS_INFO, False,
+                                        300, None)
+        self.useFixture(fixtures.MockPatch(
+            'tempest.lib.common.rest_client.RestClient.raw_request',
+            return_value=mocked_r))
+
+        self.assertTrue(self.client.has_version('2.1'))
+        self.assertFalse(self.client.has_version('9.9'))