Update volume and volume transfer schema

Fix the volume transfers client so that it supports microversions,
by deriving from the volume service's base client.

Define volume transfer schema for mv 3.55 and 3.57, and
volume schema for mv 3.65 and 3.69.

Change-Id: I4ad7fe336c5193604d95fca5a72695d82adaa9f0
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):