Merge "Migrate tempest jobs to focal"
diff --git a/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml b/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml
new file mode 100644
index 0000000..2b407ae
--- /dev/null
+++ b/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Added missing client methods for keystone's OS-ENDPOINT-POLICY and
+    OS-EP-FILTER APIs.
diff --git a/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml b/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml
new file mode 100644
index 0000000..bb91213
--- /dev/null
+++ b/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml
@@ -0,0 +1,16 @@
+prelude: |
+    This release is to tag the Tempest for OpenStack Victoria release.
+    This release marks the start of Victoria release support in Tempest.
+    After this release, Tempest will support below OpenStack Releases:
+
+    * Victoria
+    * Ussuri
+    * Train
+    * Stein
+
+    Current development of Tempest is for OpenStack Wallaby development
+    cycle. Every Tempest commit is also tested against master during
+    the Wallaby cycle. However, this does not necessarily mean that using
+    Tempest as of this tag will work against a Victoria (or future release)
+    cloud.
+    To be on safe side, use this tag to test the OpenStack Victoria release.
diff --git a/requirements.txt b/requirements.txt
index d8738f0..abc0bce 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,7 @@
 cliff!=2.9.0,>=2.8.0 # Apache-2.0
 jsonschema>=3.2.0 # MIT
 testtools>=2.2.0 # MIT
-paramiko>=2.0.0 # LGPLv2.1+
+paramiko>=2.7.0 # LGPLv2.1+
 netaddr>=0.7.18 # BSD
 oslo.concurrency>=3.26.0 # Apache-2.0
 oslo.config>=5.2.0 # Apache-2.0
diff --git a/tempest/api/volume/admin/test_encrypted_volumes_extend.py b/tempest/api/volume/admin/test_encrypted_volumes_extend.py
new file mode 100644
index 0000000..7339179
--- /dev/null
+++ b/tempest/api/volume/admin/test_encrypted_volumes_extend.py
@@ -0,0 +1,35 @@
+#    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 testtools
+
+from tempest.api.volume import base
+from tempest.api.volume import test_volumes_extend as extend
+from tempest.common import utils
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class EncryptedVolumesExtendAttachedTest(extend.BaseVolumesExtendAttachedTest,
+                                         base.BaseVolumeAdminTest):
+    """Tests extending the size of an attached encrypted volume."""
+
+    @decorators.idempotent_id('e93243ec-7c37-4b5b-a099-ebf052c13216')
+    @testtools.skipUnless(
+        CONF.volume_feature_enabled.extend_attached_encrypted_volume,
+        "Attached encrypted volume extend is disabled.")
+    @utils.services('compute')
+    def test_extend_attached_encrypted_volume_luksv1(self):
+        volume = self.create_encrypted_volume(encryption_provider="luks")
+        self._test_extend_attached_volume(volume)
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 7af5927..c538e60 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -302,6 +302,27 @@
         cls.addClassResourceCleanup(cls.clear_volume_type, volume_type['id'])
         return volume_type
 
+    def create_encryption_type(self, type_id=None, provider=None,
+                               key_size=None, cipher=None,
+                               control_location=None):
+        if not type_id:
+            volume_type = self.create_volume_type()
+            type_id = volume_type['id']
+        self.admin_encryption_types_client.create_encryption_type(
+            type_id, provider=provider, key_size=key_size, cipher=cipher,
+            control_location=control_location)
+
+    def create_encrypted_volume(self, encryption_provider, key_size=256,
+                                cipher='aes-xts-plain64',
+                                control_location='front-end'):
+        volume_type = self.create_volume_type()
+        self.create_encryption_type(type_id=volume_type['id'],
+                                    provider=encryption_provider,
+                                    key_size=key_size,
+                                    cipher=cipher,
+                                    control_location=control_location)
+        return self.create_volume(volume_type=volume_type['name'])
+
     def create_group_type(self, name=None, **kwargs):
         """Create a test group-type"""
         name = name or data_utils.rand_name(
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 041823d..7441f1d 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -61,7 +61,7 @@
         self.assertEqual(extend_size, resized_volume['size'])
 
 
-class VolumesExtendAttachedTest(base.BaseVolumeTest):
+class BaseVolumesExtendAttachedTest(base.BaseVolumeTest):
     """Tests extending the size of an attached volume."""
     create_default_network = True
 
@@ -100,14 +100,9 @@
                     event['finish_time']):
                 return event
 
-    @decorators.idempotent_id('301f5a30-1c6f-4ea0-be1a-91fd28d44354')
-    @testtools.skipUnless(CONF.volume_feature_enabled.extend_attached_volume,
-                          "Attached volume extend is disabled.")
-    @utils.services('compute')
-    def test_extend_attached_volume(self):
+    def _test_extend_attached_volume(self, volume):
         """This is a happy path test which does the following:
 
-        * Create a volume at the configured volume_size.
         * Create a server instance.
         * Attach the volume to the server.
         * Wait for the volume status to be "in-use".
@@ -119,8 +114,6 @@
           if we timeout waiting for the instance action event to show up, or
           if the action on the server fails.
         """
-        # Create a test volume. Will be automatically cleaned up on teardown.
-        volume = self.create_volume()
         # Create a test server. Will be automatically cleaned up on teardown.
         server = self.create_server()
         # Attach the volume to the server and wait for the volume status to be
@@ -182,3 +175,14 @@
             "%(request_id)s." %
             {'result': event['result'],
              'request_id': action['request_id']})
+
+
+class VolumesExtendAttachedTest(BaseVolumesExtendAttachedTest):
+
+    @decorators.idempotent_id('301f5a30-1c6f-4ea0-be1a-91fd28d44354')
+    @testtools.skipUnless(CONF.volume_feature_enabled.extend_attached_volume,
+                          "Attached volume extend is disabled.")
+    @utils.services('compute')
+    def test_extend_attached_volume(self):
+        volume = self.create_volume()
+        self._test_extend_attached_volume(volume)
diff --git a/tempest/config.py b/tempest/config.py
index a632dee..36ad405 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -1012,7 +1012,15 @@
                      'which is currently attached to a server instance? This '
                      'depends on the 3.42 volume API microversion and the '
                      '2.51 compute API microversion. Also, not all volume or '
-                     'compute backends support this operation.')
+                     'compute backends support this operation.'),
+    cfg.BoolOpt('extend_attached_encrypted_volume',
+                default=False,
+                help='Does the cloud support extending the size of an '
+                     'encrypted volume  which is currently attached to a '
+                     'server instance? This depends on the 3.42 volume API '
+                     'microversion and the 2.51 compute API microversion. '
+                     'Also, not all volume or compute backends support this '
+                     'operation.')
 ]
 
 
diff --git a/tempest/lib/services/identity/v3/endpoint_filter_client.py b/tempest/lib/services/identity/v3/endpoint_filter_client.py
index ce84869..2d5c8c9 100644
--- a/tempest/lib/services/identity/v3/endpoint_filter_client.py
+++ b/tempest/lib/services/identity/v3/endpoint_filter_client.py
@@ -66,3 +66,57 @@
             % (project_id, endpoint_id))
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def list_endpoint_groups_for_project(self, project_id):
+        """List Endpoint Groups Associated with Project."""
+        resp, body = self.get(
+            self.ep_filter + '/projects/%s/endpoint_groups'
+            % project_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_projects_for_endpoint_group(self, endpoint_group_id):
+        """List Projects Associated with Endpoint Group."""
+        resp, body = self.get(
+            self.ep_filter + '/endpoint_groups/%s/projects'
+            % endpoint_group_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_endpoints_for_endpoint_group(self, endpoint_group_id):
+        """List Endpoints Associated with Endpoint Group."""
+        resp, body = self.get(
+            self.ep_filter + '/endpoint_groups/%s/endpoints'
+            % endpoint_group_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
+        """Create Endpoint Group to Project Association."""
+        body = None
+        resp, body = self.put(
+            self.ep_filter + '/endpoint_groups/%s/projects/%s'
+            % (endpoint_group_id, project_id), body)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_endpoint_group_for_project(self, endpoint_group_id, project_id):
+        """Get Endpoint Group to Project Association."""
+        resp, body = self.get(
+            self.ep_filter + '/endpoint_groups/%s/projects/%s'
+            % (endpoint_group_id, project_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_endpoint_group_from_project(
+        self, endpoint_group_id, project_id):
+        """Delete Endpoint Group to Project Association."""
+        resp, body = self.delete(
+            self.ep_filter + '/endpoint_groups/%s/projects/%s'
+            % (endpoint_group_id, project_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/policies_client.py b/tempest/lib/services/identity/v3/policies_client.py
index 31c0d18..41def38 100644
--- a/tempest/lib/services/identity/v3/policies_client.py
+++ b/tempest/lib/services/identity/v3/policies_client.py
@@ -185,3 +185,27 @@
         resp, body = self.delete(url)
         self.expected_success(204, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def list_endpoints_for_policy(self, policy_id):
+        """List policy and service endpoint associations.
+
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/#list-policy-and-service-endpoint-associations
+        """
+        url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints".format(policy_id)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_policy_for_endpoint(self, endpoint_id):
+        """Show the effective policy associated with an endpoint
+
+        API reference:
+        https://docs.openstack.org/api-ref/identity/v3-ext/#show-the-effective-policy-associated-with-an-endpoint
+        """
+        url = "endpoints/{0}/OS-ENDPOINT-POLICY/policy".format(endpoint_id)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
index 7faf6a0..e5f7a66 100644
--- a/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
@@ -83,6 +83,36 @@
         }
     }
 
+    FAKE_LIST_ENDPOINT_GROUPS_FOR_PROJECT = {
+        "endpoint_groups": [
+            {
+                "endpoint_group": {
+                    "description": "endpoint group description #2",
+                    "filters": {
+                        "interface": "admin"
+                    },
+                    "id": "3de68c",
+                    "name": "endpoint group name #2"
+                }
+            }
+            ],
+        "links": {
+            "self": "https://url/identity/v3/OS-EP-FILTER/endpoint_groups",
+        }
+    }
+
+    FAKE_PROJECT_INFO = {
+        "project": {
+            "domain_id": "1789d1",
+            "id": "263fd9",
+            "links": {
+                "self": "http://example.com/identity/v3/projects/263fd9"
+            },
+            "name": "project name #1",
+            "description": "project description #1"
+        }
+    }
+
     def setUp(self):
         super(TestEndPointsFilterClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -137,6 +167,52 @@
             project_id=3,
             endpoint_id=4)
 
+    def _test_list_endpoint_groups_for_project(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_endpoint_groups_for_project,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ENDPOINT_GROUPS_FOR_PROJECT,
+            bytes_body,
+            status=200,
+            project_id=3)
+
+    def _test_list_projects_for_endpoint_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_projects_for_endpoint_group,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_PROJECTS_FOR_ENDPOINTS,
+            bytes_body,
+            status=200,
+            endpoint_group_id=5)
+
+    def _test_list_endpoints_for_endpoint_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_endpoints_for_endpoint_group,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_ENDPOINTS_FOR_PROJECTS,
+            bytes_body,
+            status=200,
+            endpoint_group_id=5)
+
+    def _test_add_endpoint_group_to_project(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.add_endpoint_group_to_project,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            status=204,
+            endpoint_group_id=5,
+            project_id=6)
+
+    def _test_show_endpoint_group_for_project(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_endpoint_group_for_project,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_PROJECT_INFO,
+            bytes_body,
+            endpoint_group_id=5,
+            project_id=6)
+
     def test_add_endpoint_to_project_with_str_body(self):
         self._test_add_endpoint_to_project()
 
@@ -163,3 +239,43 @@
 
     def test_delete_endpoint_from_project(self):
         self._test_delete_endpoint_from_project()
+
+    def test_list_endpoint_groups_for_project_with_str_body(self):
+        self._test_list_endpoint_groups_for_project()
+
+    def test_list_endpoint_groups_for_project_with_bytes_body(self):
+        self._test_list_endpoint_groups_for_project(bytes_body=True)
+
+    def test_list_projects_for_endpoint_group_with_str_body(self):
+        self._test_list_projects_for_endpoint_group()
+
+    def test_list_projects_for_endpoint_group_with_bytes_body(self):
+        self._test_list_projects_for_endpoint_group(bytes_body=True)
+
+    def test_list_endpoints_for_endpoint_group_with_str_body(self):
+        self._test_list_endpoints_for_endpoint_group()
+
+    def test_list_endpoints_for_endpoint_group_with_bytes_body(self):
+        self._test_list_endpoints_for_endpoint_group(bytes_body=True)
+
+    def test_add_endpoint_group_to_project_with_str_body(self):
+        self._test_add_endpoint_group_to_project()
+
+    def test_add_endpoint_group_to_project_with_bytes_body(self):
+        self._test_add_endpoint_group_to_project(bytes_body=True)
+
+    def test_show_endpoint_group_for_project_with_str_body(self):
+        self._test_show_endpoint_group_for_project()
+
+    def test_show_endpoint_group_for_project_with_bytes_body(self):
+        self._test_show_endpoint_group_for_project(bytes_body=True)
+
+    def test_delete_endpoint_group_from_project(self):
+        self.check_service_client_function(
+            self.client.delete_endpoint_group_from_project,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            False,
+            status=204,
+            endpoint_group_id=5,
+            project_id=5)
diff --git a/tempest/tests/lib/services/identity/v3/test_policies_client.py b/tempest/tests/lib/services/identity/v3/test_policies_client.py
index 0237475..4fc800a 100644
--- a/tempest/tests/lib/services/identity/v3/test_policies_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_policies_client.py
@@ -44,6 +44,34 @@
             }
         }
 
+    FAKE_ENDPOINT_INFO = {
+        "endpoints": [
+            {
+                "id": "1",
+                "interface": "public",
+                "links": {
+                    "self": "http://example.com/identity/v3/endpoints/1"
+                },
+                "region": "north",
+                "service_id": "9242e05f0c23467bbd1cf1f7a6e5e596",
+                "url": "http://example.com/identity/"
+            },
+            {
+                "id": "1",
+                "interface": "internal",
+                "links": {
+                    "self": "http://example.com/identity/v3/endpoints/1"
+                },
+                "region": "south",
+                "service_id": "9242e05f0c23467bbd1cf1f7a6e5e596",
+                "url": "http://example.com/identity/"
+            }
+        ],
+        "links": {
+            "self": "http://exmp.com/identity/v3/OS-ENDPOINT-POLICY/policies/1"
+        }
+    }
+
     FAKE_LIST_POLICIES = {
         "links": {
             "next": None,
@@ -238,3 +266,33 @@
             service_id=self.FAKE_SERVICE_ID,
             region_id=self.FAKE_REGION_ID,
             status=204)
+
+    def _test_list_endpoints_for_policy(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_endpoints_for_policy,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ENDPOINT_INFO,
+            bytes_body,
+            policy_id=self.FAKE_POLICY_ID,
+            status=200)
+
+    def test_list_endpoints_for_policy_with_str_body(self):
+        self._test_list_endpoints_for_policy()
+
+    def test_list_endpoints_for_policy_with_bytes_body(self):
+        self._test_list_endpoints_for_policy(bytes_body=True)
+
+    def _test_list_policy_for_endpoint(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_policy_for_endpoint,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_POLICY_INFO,
+            bytes_body,
+            endpoint_id=self.FAKE_ENDPOINT_ID,
+            status=200)
+
+    def test_list_policy_for_endpoint_with_str_body(self):
+        self._test_list_policy_for_endpoint()
+
+    def test_list_policy_for_endpoint_with_bytes_body(self):
+        self._test_list_policy_for_endpoint(bytes_body=True)