Add response schema validation for volume transfer

This is to add response schema validation for volume transfer.
Besides, created_at should be removed from the response example of
"List volume transfers for a project" in api ref,
I52a81454a995920c1ba5534fea3d50f6c9986881 will fix them.

Change-Id: If31470dac38d7f08768f43fa77bd018a932f0313
partially-implements: blueprint volume-response-schema-validation
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index c85e0bc..4cdf898 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -63,8 +63,6 @@
         # Accept a volume transfer by alt_tenant
         body = self.alt_client.accept_volume_transfer(
             transfer_id, auth_key=auth_key)['transfer']
-        for key in ['id', 'name', 'links', 'volume_id']:
-            self.assertIn(key, body)
         waiters.wait_for_volume_resource_status(self.alt_volumes_client,
                                                 volume['id'], 'available')
         accepted_volume = self.alt_volumes_client.show_volume(
@@ -95,8 +93,6 @@
         # elements, and look for the created transfer.
         transfers = self.client.list_volume_transfers(detail=True)['transfers']
         self.assertNotEmpty(transfers)
-        for transfer in transfers:
-            self.assertIn('created_at', transfer)
         volume_list = [transfer['volume_id'] for transfer in transfers]
         self.assertIn(volume['id'], volume_list,
                       'Transfer not found for volume %s' % volume['id'])
diff --git a/tempest/lib/api_schema/response/volume/transfers.py b/tempest/lib/api_schema/response/volume/transfers.py
new file mode 100644
index 0000000..d1d1b68
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/transfers.py
@@ -0,0 +1,129 @@
+# Copyright 2015 NEC Corporation.  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.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+create_volume_transfer = {
+    'status_code': [202],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'transfer': {
+                'type': 'object',
+                'properties': {
+                    'auth_key': {'type': 'string'},
+                    'links': parameter_types.links,
+                    'created_at': parameter_types.date_time,
+                    'volume_id': {'type': 'string', 'format': 'uuid'},
+                    'id': {'type': 'string', 'format': 'uuid'},
+                    'name': {'type': ['string', 'null']}
+                },
+                'additionalProperties': False,
+                'required': ['auth_key', 'links', 'created_at',
+                             'volume_id', 'id', 'name']
+            }
+        },
+        'additionalProperties': False,
+        'required': ['transfer']
+    }
+}
+
+common_show_volume_transfer = {
+    'type': 'object',
+    'properties': {
+        'links': parameter_types.links,
+        'created_at': parameter_types.date_time,
+        'volume_id': {'type': 'string', 'format': 'uuid'},
+        'id': {'type': 'string', 'format': 'uuid'},
+        'name': {'type': ['string', 'null']}
+    },
+    'additionalProperties': False,
+    'required': ['links', 'created_at', 'volume_id', 'id', 'name']
+}
+
+show_volume_transfer = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'transfer': common_show_volume_transfer
+        },
+        'additionalProperties': False,
+        'required': ['transfer']
+    }
+}
+
+list_volume_transfers_no_detail = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'transfers': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'volume_id': {'type': 'string', 'format': 'uuid'},
+                        'id': {'type': 'string', 'format': 'uuid'},
+                        'links': parameter_types.links,
+                        'name': {'type': ['string', 'null']}
+                    },
+                    'additionalProperties': False,
+                    'required': ['volume_id', 'id', 'links', 'name']
+                }
+            }
+        },
+        'additionalProperties': False,
+        'required': ['transfers'],
+    }
+}
+
+list_volume_transfers_with_detail = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'transfers': {
+                'type': 'array',
+                'items': common_show_volume_transfer
+            }
+        },
+        'additionalProperties': False,
+        'required': ['transfers'],
+    }
+}
+
+delete_volume_transfer = {'status_code': [202]}
+
+accept_volume_transfer = {
+    'status_code': [202],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'transfer': {
+                'type': 'object',
+                'properties': {
+                    'links': parameter_types.links,
+                    'volume_id': {'type': 'string', 'format': 'uuid'},
+                    'id': {'type': 'string', 'format': 'uuid'},
+                    'name': {'type': ['string', 'null']}
+                },
+                'additionalProperties': False,
+                'required': ['links', 'volume_id', 'id', 'name']
+            }
+        },
+        'additionalProperties': False,
+        'required': ['transfer']
+    }
+}
diff --git a/tempest/lib/services/volume/v3/transfers_client.py b/tempest/lib/services/volume/v3/transfers_client.py
index 97c5597..f97db39 100644
--- a/tempest/lib/services/volume/v3/transfers_client.py
+++ b/tempest/lib/services/volume/v3/transfers_client.py
@@ -16,6 +16,7 @@
 from oslo_serialization import jsonutils as json
 from six.moves.urllib import parse as urllib
 
+from tempest.lib.api_schema.response.volume import transfers as schema
 from tempest.lib.common import rest_client
 
 
@@ -32,7 +33,7 @@
         post_body = json.dumps({'transfer': kwargs})
         resp, body = self.post('os-volume-transfer', post_body)
         body = json.loads(body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.create_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def show_volume_transfer(self, transfer_id):
@@ -40,7 +41,7 @@
         url = "os-volume-transfer/%s" % transfer_id
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.show_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def list_volume_transfers(self, detail=False, **params):
@@ -52,19 +53,21 @@
         https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-and-details
         """
         url = 'os-volume-transfer'
+        schema_list_transfers = schema.list_volume_transfers_no_detail
         if detail:
             url += '/detail'
+            schema_list_transfers = schema.list_volume_transfers_with_detail
         if params:
             url += '?%s' % urllib.urlencode(params)
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema_list_transfers, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_volume_transfer(self, transfer_id):
         """Delete a volume transfer."""
         resp, body = self.delete("os-volume-transfer/%s" % transfer_id)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.delete_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def accept_volume_transfer(self, transfer_id, **kwargs):
@@ -78,5 +81,5 @@
         post_body = json.dumps({'accept': kwargs})
         resp, body = self.post(url, post_body)
         body = json.loads(body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.accept_volume_transfer, resp, body)
         return rest_client.ResponseBody(resp, body)