Merge "Update volume and volume transfer schema"
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index f1dec06..62cb203 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -110,9 +110,7 @@
     """Test volume transfer for the "new" Transfers API mv 3.55"""
 
     volume_min_microversion = '3.55'
-    volume_max_microversion = 'latest'
-
-    credentials = ['primary', 'alt', 'admin']
+    volume_max_microversion = '3.56'
 
     @classmethod
     def setup_clients(cls):
@@ -131,3 +129,22 @@
         """Test create, list, delete with volume-transfers API mv 3.55"""
         super(VolumesTransfersV355Test, self). \
             test_create_list_delete_volume_transfer()
+
+
+class VolumesTransfersV357Test(VolumesTransfersV355Test):
+    """Test volume transfer for the "new" Transfers API mv 3.57"""
+
+    volume_min_microversion = '3.57'
+    volume_max_microversion = 'latest'
+
+    @decorators.idempotent_id('d746bd69-bb30-4414-9a1c-577959fac6a1')
+    def test_create_get_list_accept_volume_transfer(self):
+        """Test create, get, list, accept with volume-transfers API mv 3.57"""
+        super(VolumesTransfersV357Test, self). \
+            test_create_get_list_accept_volume_transfer()
+
+    @decorators.idempotent_id('d4b20ec2-e1bb-4068-adcf-6c20020a8e05')
+    def test_create_list_delete_volume_transfer(self):
+        """Test create, list, delete with volume-transfers API mv 3.57"""
+        super(VolumesTransfersV357Test, self). \
+            test_create_list_delete_volume_transfer()
diff --git a/tempest/lib/api_schema/response/volume/v3_55/__init__.py b/tempest/lib/api_schema/response/volume/v3_55/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_55/__init__.py
diff --git a/tempest/lib/api_schema/response/volume/v3_55/transfers.py b/tempest/lib/api_schema/response/volume/v3_55/transfers.py
new file mode 100644
index 0000000..683c62f
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_55/transfers.py
@@ -0,0 +1,46 @@
+# Copyright 2022 Red Hat, Inc.
+# 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 copy
+
+from tempest.lib.api_schema.response.volume import transfers
+
+# Volume microversion 3.55:
+# Add 'no_snapshots' attribute in 'transfer' responses.
+
+create_volume_transfer = copy.deepcopy(transfers.create_volume_transfer)
+create_volume_transfer['response_body']['properties']['transfer'][
+    'properties'].update({'no_snapshots': {'type': 'boolean'}})
+
+common_show_volume_transfer = copy.deepcopy(
+    transfers.common_show_volume_transfer)
+common_show_volume_transfer['properties'].update(
+    {'no_snapshots': {'type': 'boolean'}})
+
+show_volume_transfer = copy.deepcopy(transfers.show_volume_transfer)
+show_volume_transfer['response_body']['properties'][
+    'transfer'] = common_show_volume_transfer
+
+list_volume_transfers_no_detail = copy.deepcopy(
+    transfers.list_volume_transfers_no_detail)
+
+list_volume_transfers_with_detail = copy.deepcopy(
+    transfers.list_volume_transfers_with_detail)
+list_volume_transfers_with_detail['response_body']['properties']['transfers'][
+    'items'] = common_show_volume_transfer
+
+delete_volume_transfer = copy.deepcopy(transfers.delete_volume_transfer)
+
+accept_volume_transfer = copy.deepcopy(transfers.accept_volume_transfer)
diff --git a/tempest/lib/api_schema/response/volume/v3_57/__init__.py b/tempest/lib/api_schema/response/volume/v3_57/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_57/__init__.py
diff --git a/tempest/lib/api_schema/response/volume/v3_57/transfers.py b/tempest/lib/api_schema/response/volume/v3_57/transfers.py
new file mode 100644
index 0000000..2fcf0aa
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_57/transfers.py
@@ -0,0 +1,61 @@
+# Copyright 2022 Red Hat, Inc.
+# 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 copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.volume.v3_55 import transfers
+
+# Volume microversion 3.57:
+# Add these attributes in 'transfer' responses.
+#   'destination_project_id'
+#   'source_project_id'
+#   'accepted'
+
+create_volume_transfer = copy.deepcopy(transfers.create_volume_transfer)
+create_volume_transfer['response_body']['properties']['transfer'][
+    'properties'].update(
+        {'destination_project_id': parameter_types.uuid_or_null})
+create_volume_transfer['response_body']['properties']['transfer'][
+    'properties'].update(
+        {'source_project_id': {'type': 'string', 'format': 'uuid'}})
+create_volume_transfer['response_body']['properties']['transfer'][
+    'properties'].update(
+        {'accepted': {'type': 'boolean'}})
+
+common_show_volume_transfer = copy.deepcopy(
+    transfers.common_show_volume_transfer)
+common_show_volume_transfer['properties'].update(
+    {'destination_project_id': parameter_types.uuid_or_null})
+common_show_volume_transfer['properties'].update(
+    {'source_project_id': {'type': 'string', 'format': 'uuid'}})
+common_show_volume_transfer['properties'].update(
+    {'accepted': {'type': 'boolean'}})
+
+show_volume_transfer = copy.deepcopy(transfers.show_volume_transfer)
+show_volume_transfer['response_body']['properties'][
+    'transfer'] = common_show_volume_transfer
+
+list_volume_transfers_no_detail = copy.deepcopy(
+    transfers.list_volume_transfers_no_detail)
+
+list_volume_transfers_with_detail = copy.deepcopy(
+    transfers.list_volume_transfers_with_detail)
+list_volume_transfers_with_detail['response_body']['properties']['transfers'][
+    'items'] = common_show_volume_transfer
+
+delete_volume_transfer = copy.deepcopy(transfers.delete_volume_transfer)
+
+accept_volume_transfer = copy.deepcopy(transfers.accept_volume_transfer)
diff --git a/tempest/lib/api_schema/response/volume/v3_65/__init__.py b/tempest/lib/api_schema/response/volume/v3_65/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_65/__init__.py
diff --git a/tempest/lib/api_schema/response/volume/v3_65/volumes.py b/tempest/lib/api_schema/response/volume/v3_65/volumes.py
new file mode 100644
index 0000000..f7d9e1b
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_65/volumes.py
@@ -0,0 +1,65 @@
+# Copyright 2022 Red Hat, Inc.
+# 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 copy
+
+from tempest.lib.api_schema.response.volume.v3_64 import volumes
+
+# Volume microversion 3.65:
+# Add 'consumes_quota' attribute in volume details.
+
+common_show_volume = copy.deepcopy(volumes.common_show_volume)
+common_show_volume['properties'].update(
+    {'consumes_quota': {'type': 'boolean'}})
+
+create_volume = copy.deepcopy(volumes.create_volume)
+create_volume['response_body']['properties']['volume']['properties'].update(
+    {'consumes_quota': {'type': 'boolean'}})
+
+# copy unchanged volumes schema
+attachments = copy.deepcopy(volumes.attachments)
+list_volumes_no_detail = copy.deepcopy(volumes.list_volumes_no_detail)
+# show_volume refers to common_show_volume
+show_volume = copy.deepcopy(volumes.show_volume)
+show_volume['response_body']['properties']['volume'] = common_show_volume
+# list_volumes_detail refers to latest common_show_volume
+list_volumes_detail = copy.deepcopy(common_show_volume)
+list_volumes_with_detail = copy.deepcopy(volumes.list_volumes_with_detail)
+list_volumes_with_detail['response_body']['properties']['volumes']['items'] \
+    = list_volumes_detail
+update_volume = copy.deepcopy(volumes.update_volume)
+delete_volume = copy.deepcopy(volumes.delete_volume)
+show_volume_summary = copy.deepcopy(volumes.show_volume_summary)
+attach_volume = copy.deepcopy(volumes.attach_volume)
+set_bootable_volume = copy.deepcopy(volumes.set_bootable_volume)
+detach_volume = copy.deepcopy(volumes.detach_volume)
+reserve_volume = copy.deepcopy(volumes.reserve_volume)
+unreserve_volume = copy.deepcopy(volumes.unreserve_volume)
+extend_volume = copy.deepcopy(volumes.extend_volume)
+reset_volume_status = copy.deepcopy(volumes.reset_volume_status)
+update_volume_readonly = copy.deepcopy(volumes.update_volume_readonly)
+force_delete_volume = copy.deepcopy(volumes.force_delete_volume)
+retype_volume = copy.deepcopy(volumes.retype_volume)
+force_detach_volume = copy.deepcopy(volumes.force_detach_volume)
+create_volume_metadata = copy.deepcopy(volumes.create_volume_metadata)
+show_volume_metadata = copy.deepcopy(volumes.show_volume_metadata)
+update_volume_metadata = copy.deepcopy(volumes.update_volume_metadata)
+update_volume_metadata_item = copy.deepcopy(
+    volumes.update_volume_metadata_item)
+update_volume_image_metadata = copy.deepcopy(
+    volumes.update_volume_image_metadata)
+delete_volume_image_metadata = copy.deepcopy(
+    volumes.delete_volume_image_metadata)
+unmanage_volume = copy.deepcopy(volumes.unmanage_volume)
diff --git a/tempest/lib/api_schema/response/volume/v3_69/__init__.py b/tempest/lib/api_schema/response/volume/v3_69/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_69/__init__.py
diff --git a/tempest/lib/api_schema/response/volume/v3_69/volumes.py b/tempest/lib/api_schema/response/volume/v3_69/volumes.py
new file mode 100644
index 0000000..e83ef46
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/v3_69/volumes.py
@@ -0,0 +1,65 @@
+# Copyright 2022 Red Hat, Inc.
+# 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 copy
+
+from tempest.lib.api_schema.response.volume.v3_65 import volumes
+
+# Volume microversion 3.69:
+# The 'shared_targets' attribute is now a tristate boolean.
+
+common_show_volume = copy.deepcopy(volumes.common_show_volume)
+common_show_volume['properties'].update(
+    {'shared_targets': {'type': ['boolean', 'null']}})
+
+create_volume = copy.deepcopy(volumes.create_volume)
+create_volume['response_body']['properties']['volume']['properties'].update(
+    {'shared_targets': {'type': ['boolean', 'null']}})
+
+# copy unchanged volumes schema
+attachments = copy.deepcopy(volumes.attachments)
+list_volumes_no_detail = copy.deepcopy(volumes.list_volumes_no_detail)
+# show_volume refers to common_show_volume
+show_volume = copy.deepcopy(volumes.show_volume)
+show_volume['response_body']['properties']['volume'] = common_show_volume
+# list_volumes_detail refers to latest common_show_volume
+list_volumes_detail = copy.deepcopy(common_show_volume)
+list_volumes_with_detail = copy.deepcopy(volumes.list_volumes_with_detail)
+list_volumes_with_detail['response_body']['properties']['volumes']['items'] \
+    = list_volumes_detail
+update_volume = copy.deepcopy(volumes.update_volume)
+delete_volume = copy.deepcopy(volumes.delete_volume)
+show_volume_summary = copy.deepcopy(volumes.show_volume_summary)
+attach_volume = copy.deepcopy(volumes.attach_volume)
+set_bootable_volume = copy.deepcopy(volumes.set_bootable_volume)
+detach_volume = copy.deepcopy(volumes.detach_volume)
+reserve_volume = copy.deepcopy(volumes.reserve_volume)
+unreserve_volume = copy.deepcopy(volumes.unreserve_volume)
+extend_volume = copy.deepcopy(volumes.extend_volume)
+reset_volume_status = copy.deepcopy(volumes.reset_volume_status)
+update_volume_readonly = copy.deepcopy(volumes.update_volume_readonly)
+force_delete_volume = copy.deepcopy(volumes.force_delete_volume)
+retype_volume = copy.deepcopy(volumes.retype_volume)
+force_detach_volume = copy.deepcopy(volumes.force_detach_volume)
+create_volume_metadata = copy.deepcopy(volumes.create_volume_metadata)
+show_volume_metadata = copy.deepcopy(volumes.show_volume_metadata)
+update_volume_metadata = copy.deepcopy(volumes.update_volume_metadata)
+update_volume_metadata_item = copy.deepcopy(
+    volumes.update_volume_metadata_item)
+update_volume_image_metadata = copy.deepcopy(
+    volumes.update_volume_image_metadata)
+delete_volume_image_metadata = copy.deepcopy(
+    volumes.delete_volume_image_metadata)
+unmanage_volume = copy.deepcopy(volumes.unmanage_volume)
diff --git a/tempest/lib/services/volume/v3/transfers_client.py b/tempest/lib/services/volume/v3/transfers_client.py
index cc4e1b2..f85bf21 100644
--- a/tempest/lib/services/volume/v3/transfers_client.py
+++ b/tempest/lib/services/volume/v3/transfers_client.py
@@ -18,12 +18,23 @@
 from oslo_serialization import jsonutils as json
 
 from tempest.lib.api_schema.response.volume import transfers as schema
+from tempest.lib.api_schema.response.volume.v3_55 \
+    import transfers as schemav355
+from tempest.lib.api_schema.response.volume.v3_57 \
+    import transfers as schemav357
 from tempest.lib.common import rest_client
+from tempest.lib.services.volume import base_client
 
 
-class TransfersClient(rest_client.RestClient):
+class TransfersClient(base_client.BaseClient):
     """Client class to send CRUD Volume Transfer API requests"""
 
+    schema_versions_info = [
+        {'min': None, 'max': '3.54', 'schema': schema},
+        {'min': '3.55', 'max': '3.56', 'schema': schemav355},
+        {'min': '3.57', 'max': None, 'schema': schemav357}
+    ]
+
     resource_path = 'os-volume-transfer'
 
     def create_volume_transfer(self, **kwargs):
@@ -36,6 +47,7 @@
         post_body = json.dumps({'transfer': kwargs})
         resp, body = self.post(self.resource_path, post_body)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.create_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -44,6 +56,7 @@
         url = "%s/%s" % (self.resource_path, transfer_id)
         resp, body = self.get(url)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.show_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -56,6 +69,7 @@
         https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-and-details
         """
         url = self.resource_path
+        schema = self.get_schema(self.schema_versions_info)
         schema_list_transfers = schema.list_volume_transfers_no_detail
         if detail:
             url += '/detail'
@@ -70,6 +84,7 @@
     def delete_volume_transfer(self, transfer_id):
         """Delete a volume transfer."""
         resp, body = self.delete("%s/%s" % (self.resource_path, transfer_id))
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.delete_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
 
@@ -84,6 +99,7 @@
         post_body = json.dumps({'accept': kwargs})
         resp, body = self.post(url, post_body)
         body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
         self.validate_response(schema.accept_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
 
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 9934e47..ad8bd71 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -20,6 +20,8 @@
 from tempest.lib.api_schema.response.volume.v3_61 import volumes as schemav361
 from tempest.lib.api_schema.response.volume.v3_63 import volumes as schemav363
 from tempest.lib.api_schema.response.volume.v3_64 import volumes as schemav364
+from tempest.lib.api_schema.response.volume.v3_65 import volumes as schemav365
+from tempest.lib.api_schema.response.volume.v3_69 import volumes as schemav369
 from tempest.lib.api_schema.response.volume import volumes as schema
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions as lib_exc
@@ -33,7 +35,9 @@
         {'min': None, 'max': '3.60', 'schema': schema},
         {'min': '3.61', 'max': '3.62', 'schema': schemav361},
         {'min': '3.63', 'max': '3.63', 'schema': schemav363},
-        {'min': '3.64', 'max': None, 'schema': schemav364}
+        {'min': '3.64', 'max': '3.64', 'schema': schemav364},
+        {'min': '3.65', 'max': '3.68', 'schema': schemav365},
+        {'min': '3.69', 'max': None, 'schema': schemav369}
         ]
 
     def _prepare_params(self, params):