Merge "Validate network downtime during live migration"
diff --git a/doc/source/supported_version.rst b/doc/source/supported_version.rst
index 4ca7f0d..f630578 100644
--- a/doc/source/supported_version.rst
+++ b/doc/source/supported_version.rst
@@ -9,9 +9,10 @@
 
 Tempest master supports the below OpenStack Releases:
 
+* Yoga
+* Xena
+* Wallaby
 * Victoria
-* Ussuri
-* Train
 
 For older OpenStack Release:
 
@@ -32,6 +33,5 @@
 
 Tempest master supports the below python versions:
 
-* Python 3.6
-* Python 3.7
 * Python 3.8
+* Python 3.9
diff --git a/releasenotes/notes/drop-py-3-6-and-3-7-a34f2294f5341539.yaml b/releasenotes/notes/drop-py-3-6-and-3-7-a34f2294f5341539.yaml
new file mode 100644
index 0000000..ec4e2f2
--- /dev/null
+++ b/releasenotes/notes/drop-py-3-6-and-3-7-a34f2294f5341539.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    Python 3.6 and 3.7 support has been dropped. Last release of Tempest
+    to support python 3.6 and 3.7 is Temepst 30.0.0. The minimum version
+    of Python now supported by Tempest is Python 3.8.
diff --git a/releasenotes/notes/end-of-support-of-victoria-9c33f2b089b14cb5.yaml b/releasenotes/notes/end-of-support-of-victoria-9c33f2b089b14cb5.yaml
new file mode 100644
index 0000000..c644e3a
--- /dev/null
+++ b/releasenotes/notes/end-of-support-of-victoria-9c33f2b089b14cb5.yaml
@@ -0,0 +1,12 @@
+---
+prelude: |
+    This is an intermediate release during the Zed development cycle to
+    mark the end of support for EM Victoria release in Tempest.
+    After this release, Tempest will support below OpenStack Releases:
+
+    * Yoga
+    * Xena
+    * Wallaby
+
+    Current development of Tempest is for OpenStack Zed development
+    cycle.
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 1919393..d9f855a 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -81,7 +81,7 @@
 .. zuul:rolevar:: stable_constraints_file
    :default: ''
 
-   Upper constraints file to be used for stable branch till stable/train.
+   Upper constraints file to be used for stable branch till stable/victoria.
 
 .. zuul:rolevar:: tempest_tox_environment
    :default: ''
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 397de1e..f302fa5 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -25,11 +25,11 @@
     target_branch: "{{ zuul.override_checkout }}"
   when: zuul.override_checkout is defined
 
-- name: Use stable branch upper-constraints till stable/train
+- name: Use stable branch upper-constraints till stable/victoria
   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': stable_constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': stable_constraints_file}) }}"
-  when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein", "stable/train"]
+  when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein", "stable/train", "stable/ussuri", "stable/victoria"]
 
 - name: Use Configured upper-constraints for non-master Tempest
   set_fact:
@@ -78,16 +78,16 @@
         exclude_list_option: "--exclude-list={{ tempest_test_exclude_list|quote }}"
       when: exclude_list_stat.stat.exists
 
-- name: stable/train workaround to fallback exclude-list to blacklist
-  # NOTE(gmann): stable/train use Tempest 26.1.0 and with stestr 2.5.1
-  # (beacause of upper constraints of stestr 2.5.1 in stable/train) which
-  # does not have new args exclude-list so let's fallback to old arg
-  # if new arg is passed.
+- name: Tempest 26.1.0 workaround to fallback exclude-list to blacklist
+  # NOTE(gmann): stable/train|ussuri|victoria use Tempest 26.1.0 and with
+  # stestr 2.5.1/3.0.1 (beacause of upper constraints of stestr 2.5.1/3.0.1
+  # in stable/train|ussuri|victoria) which does not have new args exclude-list
+  # so let's fallback to old arg if new arg is passed.
   set_fact:
     exclude_list_option: "--blacklist-file={{ tempest_test_exclude_list|quote }}"
   when:
     - tempest_test_exclude_list is defined
-    - target_branch == "stable/train"
+    - target_branch in ["stable/train", "stable/ussuri", "stable/victoria"]
 
 # TODO(kopecmartin) remove this after all consumers of the role have switched
 # to tempest_exclude_regex option, until then it's kept here for the backward
@@ -105,19 +105,19 @@
   when:
     - tempest_black_regex is not defined
     - tempest_exclude_regex is defined
-    - target_branch != "stable/train"
+    - target_branch not in ["stable/train", "stable/ussuri", "stable/victoria"]
 
-- name: stable/train workaround to fallback exclude-regex to black-regex
-  # NOTE(gmann): stable/train use Tempest 26.1.0 and with stestr 2.5.1
-  # (beacause of upper constraints of stestr 2.5.1 in stable/train) which
-  # does not have new args exclude-regex so let's fallback to old arg
-  # if new arg is passed.
+- name: Tempest 26.1.0 workaround to fallback exclude-regex to black-regex
+  # NOTE(gmann): stable/train|ussuri|victoria use Tempest 26.1.0 and with stestr
+  # 2.5.1/3.0.1 (beacause of upper constraints of stestr 2.5.1/3.0.1 in
+  # stable/train|ussuri|victoria) which does not have new args exclude-list so
+  # let's fallback to old arg if new arg is passed.
   set_fact:
     tempest_test_exclude_regex: "--black-regex={{tempest_exclude_regex|quote}}"
   when:
     - tempest_black_regex is not defined
     - tempest_exclude_regex is defined
-    - target_branch == "stable/train"
+    - target_branch in ["stable/train", "stable/ussuri", "stable/victoria"]
 
 - name: Run Tempest
   command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} \
diff --git a/setup.cfg b/setup.cfg
index a41eccf..a531eb4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,7 +6,7 @@
 author = OpenStack
 author_email = openstack-discuss@lists.openstack.org
 home_page = https://docs.openstack.org/tempest/latest/
-python_requires = >=3.6
+python_requires = >=3.8
 classifier =
     Intended Audience :: Information Technology
     Intended Audience :: System Administrators
@@ -15,8 +15,6 @@
     Operating System :: POSIX :: Linux
     Programming Language :: Python
     Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.6
-    Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.8
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3 :: Only
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index c1236a7..7da87c7 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -26,6 +26,11 @@
     create_default_network = True
 
     @classmethod
+    def setup_credentials(cls):
+        cls.prepare_instance_network()
+        super(TestVolumeSwapBase, cls).setup_credentials()
+
+    @classmethod
     def skip_checks(cls):
         super(TestVolumeSwapBase, cls).skip_checks()
         if not CONF.compute_feature_enabled.swap_volume:
@@ -100,7 +105,16 @@
         volume1 = self.create_volume()
         volume2 = self.create_volume()
         # Boot server
-        server = self.create_test_server(wait_until='ACTIVE')
+        validation_resources = self.get_class_validation_resources(
+            self.os_primary)
+        # NOTE(gibi): We need to wait for the guest to fully boot as the test
+        # will attach a volume to the server and therefore cleanup will try to
+        # detach it. See bug 1960346 for details.
+        server = self.create_test_server(
+            validatable=True,
+            validation_resources=validation_resources,
+            wait_until='SSHABLE'
+        )
         # Attach "volume1" to server
         self.attach_volume(server, volume1)
         # Swap volume from "volume1" to "volume2"
@@ -200,9 +214,18 @@
         volume2 = self.create_volume(multiattach=True)
 
         # Create two servers and wait for them to be ACTIVE.
+        validation_resources = self.get_class_validation_resources(
+            self.os_primary)
+        # NOTE(gibi): We need to wait for the guests to fully boot as the test
+        # will attach volumes to the servers and therefore cleanup will try to
+        # detach them. See bug 1960346 for details.
         reservation_id = self.create_test_server(
-            wait_until='ACTIVE', min_count=2,
-            return_reservation_id=True)['reservation_id']
+            validatable=True,
+            validation_resources=validation_resources,
+            wait_until='SSHABLE',
+            min_count=2,
+            return_reservation_id=True,
+        )['reservation_id']
         # Get the servers using the reservation_id.
         servers = self.servers_client.list_servers(
             reservation_id=reservation_id)['servers']
diff --git a/tempest/api/compute/admin/test_volumes_negative.py b/tempest/api/compute/admin/test_volumes_negative.py
index 10d522b..91ab09e 100644
--- a/tempest/api/compute/admin/test_volumes_negative.py
+++ b/tempest/api/compute/admin/test_volumes_negative.py
@@ -28,21 +28,22 @@
     create_default_network = True
 
     @classmethod
+    def setup_credentials(cls):
+        cls.prepare_instance_network()
+        super(VolumesAdminNegativeTest, cls).setup_credentials()
+
+    @classmethod
     def skip_checks(cls):
         super(VolumesAdminNegativeTest, cls).skip_checks()
         if not CONF.service_available.cinder:
             skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
 
-    @classmethod
-    def resource_setup(cls):
-        super(VolumesAdminNegativeTest, cls).resource_setup()
-        cls.server = cls.create_test_server(wait_until='ACTIVE')
-
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('309b5ecd-0585-4a7e-a36f-d2b2bf55259d')
     def test_update_attached_volume_with_nonexistent_volume_in_uri(self):
         """Test swapping non existent volume should fail"""
+        self.server = self.create_test_server(wait_until="ACTIVE")
         volume = self.create_volume()
         nonexistent_volume = data_utils.rand_uuid()
         self.assertRaises(lib_exc.NotFound,
@@ -55,6 +56,17 @@
     @decorators.idempotent_id('7dcac15a-b107-46d3-a5f6-cb863f4e454a')
     def test_update_attached_volume_with_nonexistent_volume_in_body(self):
         """Test swapping volume to a non existence volume should fail"""
+        validation_resources = self.get_class_validation_resources(
+            self.os_primary)
+        # NOTE(gibi): We need to wait for the guest to fully boot as
+        # test_update_attached_volume_with_nonexistent_volume_in_body case
+        # will attach a volume to it and therefore cleanup will try to detach
+        # it. See bug 1960346 for details.
+        self.server = self.create_test_server(
+            validatable=True,
+            validation_resources=validation_resources,
+            wait_until="SSHABLE")
+
         volume = self.create_volume()
         self.attach_volume(self.server, volume)
 
@@ -76,6 +88,13 @@
     min_microversion = '2.60'
     volume_min_microversion = '3.27'
 
+    create_default_network = True
+
+    @classmethod
+    def setup_credentials(cls):
+        cls.prepare_instance_network()
+        super(UpdateMultiattachVolumeNegativeTest, cls).setup_credentials()
+
     @classmethod
     def skip_checks(cls):
         super(UpdateMultiattachVolumeNegativeTest, cls).skip_checks()
@@ -101,8 +120,21 @@
         vol2 = self.create_volume(multiattach=True)
 
         # Create two instances.
-        server1 = self.create_test_server(wait_until='ACTIVE')
-        server2 = self.create_test_server(wait_until='ACTIVE')
+        validation_resources = self.get_class_validation_resources(
+            self.os_primary)
+        # NOTE(gibi): We need to wait for the guests to fully boot as the test
+        # will attach volumes to the servers and therefore cleanup will try to
+        # detach them. See bug 1960346 for details.
+        server1 = self.create_test_server(
+            validatable=True,
+            validation_resources=validation_resources,
+            wait_until='SSHABLE'
+        )
+        server2 = self.create_test_server(
+            validatable=True,
+            validation_resources=validation_resources,
+            wait_until='SSHABLE'
+        )
 
         # Attach vol1 to both of these instances.
         vol1_attachment1 = self.attach_volume(server1, vol1)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 41e73ec..74601dc 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -456,15 +456,31 @@
         except Exception:
             LOG.exception('Failed to delete server %s', server_id)
 
-    def resize_server(self, server_id, new_flavor_id, **kwargs):
+    def resize_server(
+        self, server_id, new_flavor_id, wait_until='ACTIVE', **kwargs
+    ):
         """resize and confirm_resize an server, waits for it to be ACTIVE."""
         self.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
         waiters.wait_for_server_status(self.servers_client, server_id,
                                        'VERIFY_RESIZE')
         self.servers_client.confirm_resize_server(server_id)
+
         waiters.wait_for_server_status(
             self.servers_client, server_id, 'ACTIVE')
         server = self.servers_client.show_server(server_id)['server']
+
+        validation_resources = self.get_class_validation_resources(
+            self.os_primary)
+        if (
+            validation_resources and
+            wait_until in ("SSHABLE", "PINGABLE") and
+            CONF.validation.run_validation
+        ):
+            tenant_network = self.get_tenant_network()
+            compute.wait_for_ssh_or_ping(
+                server, self.os_primary, tenant_network,
+                True, validation_resources, wait_until, True)
+
         self.assert_flavor_equal(new_flavor_id, server['flavor'])
 
     def reboot_server(self, server_id, type):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index e4ec209..5380c67 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -378,10 +378,19 @@
                   the created volume, and dict of server ID to volumeAttachment
                   dict entries
         """
+        validation_resources = self.get_class_validation_resources(
+            self.os_primary)
+
         servers = []
         for x in range(2):
             name = 'multiattach-server-%i' % x
-            servers.append(self.create_test_server(name=name))
+            servers.append(
+                self.create_test_server(
+                    name=name,
+                    validatable=True,
+                    validation_resources=validation_resources
+                )
+            )
 
         # Now wait for the servers to be ACTIVE.
         for server in servers:
@@ -492,7 +501,10 @@
         servers, volume, _ = self._create_and_multiattach()
 
         for server in servers:
-            self.resize_server(server['id'], self.flavor_ref_alt)
+            # We need to wait until the guest OS fully boots up as we are going
+            # to detach volumes after the resize. See bug #1960346.
+            self.resize_server(
+                server['id'], self.flavor_ref_alt, wait_until='SSHABLE')
 
         for server in servers:
             self._detach_multiattach_volume(volume['id'], server['id'])
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index db28487..ce45ff6 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -419,8 +419,12 @@
 
         body = self.backups_client.restore_backup(backup_id, **kwargs)
         restore = body['restore']
-        self.addCleanup(self.volumes_client.delete_volume,
-                        restore['volume_id'])
+
+        using_pre_existing_volume = kwargs.get('volume_id', False)
+        if not using_pre_existing_volume:
+            self.addCleanup(self.volumes_client.delete_volume,
+                            restore['volume_id'])
+
         waiters.wait_for_volume_resource_status(self.backups_client,
                                                 backup_id, 'available')
         waiters.wait_for_volume_resource_status(self.volumes_client,
diff --git a/tempest/services/__init__.py b/tempest/services/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/__init__.py
+++ /dev/null
diff --git a/tempest/services/orchestration/__init__.py b/tempest/services/orchestration/__init__.py
deleted file mode 100644
index 5a1ffcc..0000000
--- a/tempest/services/orchestration/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-from tempest.services.orchestration.json.orchestration_client import \
-    OrchestrationClient
-
-__all__ = ['OrchestrationClient']
diff --git a/tempest/services/orchestration/json/__init__.py b/tempest/services/orchestration/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/orchestration/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
deleted file mode 100644
index 0d7720e..0000000
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ /dev/null
@@ -1,413 +0,0 @@
-# Copyright 2013 IBM Corp.
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-import re
-import time
-from urllib import parse as urllib
-
-from oslo_serialization import jsonutils as json
-
-from tempest import exceptions
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-
-
-class OrchestrationClient(rest_client.RestClient):
-
-    def list_stacks(self, params=None):
-        """Lists all stacks for a user."""
-
-        uri = 'stacks'
-        if params:
-            uri += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(uri)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def create_stack(self, name, disable_rollback=True, parameters=None,
-                     timeout_mins=60, template=None, template_url=None,
-                     environment=None, files=None):
-        if parameters is None:
-            parameters = {}
-        headers, body = self._prepare_update_create(
-            name,
-            disable_rollback,
-            parameters,
-            timeout_mins,
-            template,
-            template_url,
-            environment,
-            files)
-        uri = 'stacks'
-        resp, body = self.post(uri, headers=headers, body=body)
-        self.expected_success(201, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_stack(self, stack_identifier, name, disable_rollback=True,
-                     parameters=None, timeout_mins=60, template=None,
-                     template_url=None, environment=None, files=None):
-        if parameters is None:
-            parameters = {}
-        headers, body = self._prepare_update_create(
-            name,
-            disable_rollback,
-            parameters,
-            timeout_mins,
-            template,
-            template_url,
-            environment)
-
-        uri = "stacks/%s" % stack_identifier
-        resp, body = self.put(uri, headers=headers, body=body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp, body)
-
-    def _prepare_update_create(self, name, disable_rollback=True,
-                               parameters=None, timeout_mins=60,
-                               template=None, template_url=None,
-                               environment=None, files=None):
-        if parameters is None:
-            parameters = {}
-        post_body = {
-            "stack_name": name,
-            "disable_rollback": disable_rollback,
-            "parameters": parameters,
-            "timeout_mins": timeout_mins,
-            "template": "HeatTemplateFormatVersion: '2012-12-12'\n",
-            "environment": environment,
-            "files": files
-        }
-        if template:
-            post_body['template'] = template
-        if template_url:
-            post_body['template_url'] = template_url
-        body = json.dumps(post_body)
-
-        # Password must be provided on stack create so that heat
-        # can perform future operations on behalf of the user
-        headers = self.get_headers()
-        headers['X-Auth-Key'] = self.password
-        headers['X-Auth-User'] = self.user
-        return headers, body
-
-    def show_stack(self, stack_identifier):
-        """Returns the details of a single stack."""
-        url = "stacks/%s" % stack_identifier
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def suspend_stack(self, stack_identifier):
-        """Suspend a stack."""
-        url = 'stacks/%s/actions' % stack_identifier
-        body = {'suspend': None}
-        resp, body = self.post(url, json.dumps(body))
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def resume_stack(self, stack_identifier):
-        """Resume a stack."""
-        url = 'stacks/%s/actions' % stack_identifier
-        body = {'resume': None}
-        resp, body = self.post(url, json.dumps(body))
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def list_resources(self, stack_identifier):
-        """Returns the details of a single resource."""
-        url = "stacks/%s/resources" % stack_identifier
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_resource(self, stack_identifier, resource_name):
-        """Returns the details of a single resource."""
-        url = "stacks/%s/resources/%s" % (stack_identifier, resource_name)
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_stack(self, stack_identifier):
-        """Deletes the specified Stack."""
-        resp, _ = self.delete("stacks/%s" % str(stack_identifier))
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def wait_for_stack_status(self, stack_identifier, status,
-                              failure_pattern='^.*_FAILED$'):
-        """Waits for a Stack to reach a given status."""
-        start = int(time.time())
-        fail_regexp = re.compile(failure_pattern)
-
-        while True:
-            try:
-                body = self.show_stack(stack_identifier)['stack']
-            except lib_exc.NotFound:
-                if status == 'DELETE_COMPLETE':
-                    return
-            stack_name = body['stack_name']
-            stack_status = body['stack_status']
-            if stack_status == status:
-                return body
-            if fail_regexp.search(stack_status):
-                raise exceptions.StackBuildErrorException(
-                    stack_identifier=stack_identifier,
-                    stack_status=stack_status,
-                    stack_status_reason=body['stack_status_reason'])
-
-            if int(time.time()) - start >= self.build_timeout:
-                message = ('Stack %s failed to reach %s status (current: %s) '
-                           'within the required time (%s s).' %
-                           (stack_name, status, stack_status,
-                            self.build_timeout))
-                raise lib_exc.TimeoutException(message)
-            time.sleep(self.build_interval)
-
-    def show_resource_metadata(self, stack_identifier, resource_name):
-        """Returns the resource's metadata."""
-        url = ('stacks/{stack_identifier}/resources/{resource_name}'
-               '/metadata'.format(**locals()))
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_events(self, stack_identifier):
-        """Returns list of all events for a stack."""
-        url = 'stacks/{stack_identifier}/events'.format(**locals())
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_resource_events(self, stack_identifier, resource_name):
-        """Returns list of all events for a resource from stack."""
-        url = ('stacks/{stack_identifier}/resources/{resource_name}'
-               '/events'.format(**locals()))
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_event(self, stack_identifier, resource_name, event_id):
-        """Returns the details of a single stack's event."""
-        url = ('stacks/{stack_identifier}/resources/{resource_name}/events'
-               '/{event_id}'.format(**locals()))
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_template(self, stack_identifier):
-        """Returns the template for the stack."""
-        url = ('stacks/{stack_identifier}/template'.format(**locals()))
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def _validate_template(self, post_body):
-        """Returns the validation request result."""
-        post_body = json.dumps(post_body)
-        resp, body = self.post('validate', post_body)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def validate_template(self, template, parameters=None):
-        """Returns the validation result for a template with parameters."""
-        if parameters is None:
-            parameters = {}
-        post_body = {
-            'template': template,
-            'parameters': parameters,
-        }
-        return self._validate_template(post_body)
-
-    def validate_template_url(self, template_url, parameters=None):
-        """Returns the validation result for a template with parameters."""
-        if parameters is None:
-            parameters = {}
-        post_body = {
-            'template_url': template_url,
-            'parameters': parameters,
-        }
-        return self._validate_template(post_body)
-
-    def list_resource_types(self):
-        """List resource types."""
-        resp, body = self.get('resource_types')
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_resource_type(self, resource_type_name):
-        """Return the schema of a resource type."""
-        url = 'resource_types/%s' % resource_type_name
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, json.loads(body))
-
-    def show_resource_type_template(self, resource_type_name):
-        """Return the template of a resource type."""
-        url = 'resource_types/%s/template' % resource_type_name
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        return rest_client.ResponseBody(resp, json.loads(body))
-
-    def create_software_config(self, name=None, config=None, group=None,
-                               inputs=None, outputs=None, options=None):
-        headers, body = self._prep_software_config_create(
-            name, config, group, inputs, outputs, options)
-
-        url = 'software_configs'
-        resp, body = self.post(url, headers=headers, body=body)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_software_config(self, conf_id):
-        """Returns a software configuration resource."""
-        url = 'software_configs/%s' % str(conf_id)
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_software_config(self, conf_id):
-        """Deletes a specific software configuration."""
-        url = 'software_configs/%s' % str(conf_id)
-        resp, _ = self.delete(url)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def create_software_deploy(self, server_id=None, config_id=None,
-                               action=None, status=None,
-                               input_values=None, output_values=None,
-                               status_reason=None, signal_transport=None):
-        """Creates or updates a software deployment."""
-        headers, body = self._prep_software_deploy_update(
-            None, server_id, config_id, action, status, input_values,
-            output_values, status_reason, signal_transport)
-
-        url = 'software_deployments'
-        resp, body = self.post(url, headers=headers, body=body)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def update_software_deploy(self, deploy_id=None, server_id=None,
-                               config_id=None, action=None, status=None,
-                               input_values=None, output_values=None,
-                               status_reason=None, signal_transport=None):
-        """Creates or updates a software deployment."""
-        headers, body = self._prep_software_deploy_update(
-            deploy_id, server_id, config_id, action, status, input_values,
-            output_values, status_reason, signal_transport)
-
-        url = 'software_deployments/%s' % str(deploy_id)
-        resp, body = self.put(url, headers=headers, body=body)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_software_deployments(self):
-        """Returns a list of all deployments."""
-        url = 'software_deployments'
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_software_deployment(self, deploy_id):
-        """Returns a specific software deployment."""
-        url = 'software_deployments/%s' % str(deploy_id)
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def show_software_deployment_metadata(self, server_id):
-        """Return a config metadata for a specific server."""
-        url = 'software_deployments/metadata/%s' % server_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def delete_software_deploy(self, deploy_id):
-        """Deletes a specific software deployment."""
-        url = 'software_deployments/%s' % str(deploy_id)
-        resp, _ = self.delete(url)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def _prep_software_config_create(self, name=None, conf=None, group=None,
-                                     inputs=None, outputs=None, options=None):
-        """Prepares a software configuration body."""
-        post_body = {}
-        if name is not None:
-            post_body["name"] = name
-        if conf is not None:
-            post_body["config"] = conf
-        if group is not None:
-            post_body["group"] = group
-        if inputs is not None:
-            post_body["inputs"] = inputs
-        if outputs is not None:
-            post_body["outputs"] = outputs
-        if options is not None:
-            post_body["options"] = options
-        body = json.dumps(post_body)
-
-        headers = self.get_headers()
-        return headers, body
-
-    def _prep_software_deploy_update(self, deploy_id=None, server_id=None,
-                                     config_id=None, action=None, status=None,
-                                     input_values=None, output_values=None,
-                                     status_reason=None,
-                                     signal_transport=None):
-        """Prepares a deployment create or update (if an id was given)."""
-        post_body = {}
-
-        if deploy_id is not None:
-            post_body["id"] = deploy_id
-        if server_id is not None:
-            post_body["server_id"] = server_id
-        if config_id is not None:
-            post_body["config_id"] = config_id
-        if action is not None:
-            post_body["action"] = action
-        if status is not None:
-            post_body["status"] = status
-        if input_values is not None:
-            post_body["input_values"] = input_values
-        if output_values is not None:
-            post_body["output_values"] = output_values
-        if status_reason is not None:
-            post_body["status_reason"] = status_reason
-        if signal_transport is not None:
-            post_body["signal_transport"] = signal_transport
-        body = json.dumps(post_body)
-
-        headers = self.get_headers()
-        return headers, body
diff --git a/tox.ini b/tox.ini
index b07fdaf..94eb4d9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = pep8,py36,py39,bashate,pip-check-reqs
+envlist = pep8,py39,bashate,pip-check-reqs
 minversion = 3.18.0
 skipsdist = True
 ignore_basepython_conflict = True
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 6ab7eed..09802f9 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -8,8 +8,6 @@
     check:
       jobs:
         - openstack-tox-pep8
-        - openstack-tox-py36
-        - openstack-tox-py37
         - openstack-tox-py38
         - openstack-tox-py39
         - tempest-full-parallel:
@@ -41,8 +39,6 @@
             irrelevant-files: *tempest-irrelevant-files
         - tempest-full-wallaby-py3:
             irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-victoria-py3:
-            irrelevant-files: *tempest-irrelevant-files
         - tempest-slow-wallaby:
             irrelevant-files: *tempest-irrelevant-files
         - tempest-multinode-full-py3:
@@ -104,6 +100,9 @@
             voting: false
             irrelevant-files: *tempest-irrelevant-files
         - devstack-plugin-ceph-tempest-py3:
+            # TODO(kopecmartin): make it voting once the below bug is fixed
+            # https://bugs.launchpad.net/devstack-plugin-ceph/+bug/1975648
+            voting: false
             irrelevant-files: *tempest-irrelevant-files
         - neutron-ovs-grenade-multinode:
             irrelevant-files: *tempest-irrelevant-files
@@ -123,12 +122,13 @@
         - openstack-tox-bashate:
             irrelevant-files: *tempest-irrelevant-files-2
         - tempest-full-centos-9-stream:
+            # TODO(gmann): make it voting once below fix is merged
+            # https://review.opendev.org/c/openstack/tempest/+/842140
+            voting: false
             irrelevant-files: *tempest-irrelevant-files
     gate:
       jobs:
         - openstack-tox-pep8
-        - openstack-tox-py36
-        - openstack-tox-py37
         - openstack-tox-py38
         - openstack-tox-py39
         - tempest-slow-py3:
@@ -141,10 +141,10 @@
             irrelevant-files: *tempest-irrelevant-files
         - tempest-ipv6-only:
             irrelevant-files: *tempest-irrelevant-files-3
-        - devstack-plugin-ceph-tempest-py3:
-            irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-centos-9-stream:
-            irrelevant-files: *tempest-irrelevant-files
+        #- devstack-plugin-ceph-tempest-py3:
+        #    irrelevant-files: *tempest-irrelevant-files
+        #- tempest-full-centos-9-stream:
+        #    irrelevant-files: *tempest-irrelevant-files
     experimental:
       jobs:
         - tempest-with-latest-microversion
@@ -168,7 +168,6 @@
         - tempest-full-yoga
         - tempest-full-xena
         - tempest-full-wallaby-py3
-        - tempest-full-victoria-py3
         - tempest-slow-yoga
         - tempest-slow-xena
         - tempest-slow-wallaby
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index 00b40f5..d1445c0 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -15,11 +15,6 @@
     override-checkout: stable/wallaby
 
 - job:
-    name: tempest-full-victoria-py3
-    parent: tempest-full-py3
-    override-checkout: stable/victoria
-
-- job:
     name: tempest-slow-yoga
     parent: tempest-slow-py3
     override-checkout: stable/yoga