Merge "Add response schema validation for volumes"
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 33e503f..ecddfba 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -108,5 +108,4 @@
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume_id, 'available')
         vol_info = self.volumes_client.show_volume(volume_id)['volume']
-        self.assertIn('attachments', vol_info)
         self.assertEmpty(vol_info['attachments'])
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 9edffc6..e42ea40 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -84,7 +84,6 @@
                         self.volume['id'], 'available')
         self.addCleanup(self.volumes_client.detach_volume, self.volume['id'])
         volume = self.volumes_client.show_volume(self.volume['id'])['volume']
-        self.assertIn('attachments', volume)
         attachment = volume['attachments'][0]
 
         self.assertEqual('/dev/%s' %
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index ade2deb..91728ab 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -37,11 +37,9 @@
         kwargs['name'] = v_name
         kwargs['metadata'] = metadata
         volume = self.volumes_client.create_volume(**kwargs)['volume']
-        self.assertIn('id', volume)
         self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume['id'], 'available')
-        self.assertIn('name', volume)
         self.assertEqual(volume['name'], v_name,
                          "The created volume name is not equal "
                          "to the requested name")
@@ -101,7 +99,6 @@
                   'availability_zone': volume['availability_zone'],
                   'size': CONF.volume.volume_size}
         new_volume = self.volumes_client.create_volume(**params)['volume']
-        self.assertIn('id', new_volume)
         self.addCleanup(self.delete_volume, self.volumes_client,
                         new_volume['id'])
         waiters.wait_for_volume_resource_status(self.volumes_client,
@@ -153,7 +150,5 @@
     @decorators.idempotent_id('c4f2431e-4920-4736-9e00-4040386b6feb')
     def test_show_volume_summary(self):
         """Test showing volume summary"""
-        volume_summary = \
-            self.volumes_client.show_volume_summary()['volume-summary']
-        for key in ['total_size', 'total_count']:
-            self.assertIn(key, volume_summary)
+        # check response schema
+        self.volumes_client.show_volume_summary()
diff --git a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
index 28ed816..8aed37d 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
@@ -120,3 +120,10 @@
     # 7: SUSPENDED
     'enum': [0, 1, 3, 4, 6, 7]
 }
+
+uuid_or_null = {
+    'anyOf': [
+        {'type': 'string', 'format': 'uuid'},
+        {'type': 'null'}
+    ]
+}
diff --git a/tempest/lib/api_schema/response/volume/volumes.py b/tempest/lib/api_schema/response/volume/volumes.py
new file mode 100644
index 0000000..ffcf488
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/volumes.py
@@ -0,0 +1,368 @@
+# Copyright 2018 ZTE 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+attachments = {
+    'type': 'array',
+    'items': {
+        'type': 'object',
+        'properties': {
+            'server_id': {'type': 'string', 'format': 'uuid'},
+            'attachment_id': {'type': 'string', 'format': 'uuid'},
+            'attached_at': parameter_types.date_time_or_null,
+            'host_name': {'type': ['string', 'null']},
+            'volume_id': {'type': 'string', 'format': 'uuid'},
+            'device': {'type': ['string', 'null']},
+            'id': {'type': 'string', 'format': 'uuid'}
+        },
+        'additionalProperties': False,
+        'required': ['server_id', 'attachment_id', 'host_name',
+                     'volume_id', 'device', 'id']
+    }
+}
+
+common_show_volume = {
+    'type': 'object',
+    'properties': {
+        'migration_status': {'type': ['string', 'null']},
+        'attachments': attachments,
+        'links': parameter_types.links,
+        'availability_zone': {'type': ['string', 'null']},
+        'os-vol-host-attr:host': {
+            'type': ['string', 'null'], 'pattern': '.+@.+#.+'},
+        'encrypted': {'type': 'boolean'},
+        'updated_at': parameter_types.date_time_or_null,
+        'replication_status': {'type': ['string', 'null']},
+        'snapshot_id': parameter_types.uuid_or_null,
+        'id': {'type': 'string', 'format': 'uuid'},
+        'size': {'type': 'integer'},
+        'user_id': {'type': 'string', 'format': 'uuid'},
+        'os-vol-tenant-attr:tenant_id': {'type': 'string',
+                                         'format': 'uuid'},
+        'os-vol-mig-status-attr:migstat': {'type': ['string', 'null']},
+        'metadata': {'type': 'object'},
+        'status': {'type': 'string'},
+        'volume_image_metadata': {'type': ['object', 'null']},
+        'description': {'type': ['string', 'null']},
+        'multiattach': {'type': 'boolean'},
+        'source_volid': parameter_types.uuid_or_null,
+        'consistencygroup_id': parameter_types.uuid_or_null,
+        'os-vol-mig-status-attr:name_id': parameter_types.uuid_or_null,
+        'name': {'type': ['string', 'null']},
+        'bootable': {'type': 'string'},
+        'created_at': parameter_types.date_time,
+        'volume_type': {'type': ['string', 'null']},
+        # TODO(zhufl): group_id is added in 3.13, we should move it to the
+        # 3.13 schema file when microversion is supported in volume interfaces
+        'group_id': parameter_types.uuid_or_null,
+        # TODO(zhufl): provider_id is added in 3.21, we should move it to the
+        # 3.21 schema file when microversion is supported in volume interfaces
+        'provider_id': parameter_types.uuid_or_null,
+        # TODO(zhufl): service_uuid and shared_targets are added in 3.48,
+        # we should move them to the 3.48 schema file when microversion
+        # is supported in volume interfaces.
+        'service_uuid': parameter_types.uuid_or_null,
+        'shared_targets': {'type': 'boolean'}
+    },
+    'additionalProperties': False,
+    'required': ['attachments', 'links', 'encrypted',
+                 'updated_at', 'replication_status', 'id',
+                 'size', 'user_id', 'availability_zone',
+                 'metadata', 'status', 'description',
+                 'multiattach', 'consistencygroup_id',
+                 'name', 'bootable', 'created_at',
+                 'volume_type', 'snapshot_id', 'source_volid']
+}
+
+list_volumes_no_detail = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'volumes': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'links': parameter_types.links,
+                        'id': {'type': 'string', 'format': 'uuid'},
+                        'name': {'type': ['string', 'null']},
+                        # TODO(zhufl): count is added in 3.45, we should move
+                        # it to the 3.45 schema file when microversion is
+                        # supported in volume interfaces
+                        # 'count': {'type': 'integer'}
+                    },
+                    'additionalProperties': False,
+                    'required': ['links', 'id', 'name']
+                }
+            },
+            'volumes_links': parameter_types.links
+        },
+        'additionalProperties': False,
+        'required': ['volumes']
+    }
+}
+
+show_volume = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'volume': common_show_volume
+        },
+        'additionalProperties': False,
+        'required': ['volume']
+    }
+}
+
+list_volumes_detail = copy.deepcopy(common_show_volume)
+# TODO(zhufl): count is added in 3.45, we should move it to the 3.45 schema
+# file when microversion is supported in volume interfaces
+# list_volumes_detail['properties'].update({'count': {'type': 'integer'}})
+list_volumes_with_detail = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'volumes': {
+                'type': 'array',
+                'items': list_volumes_detail
+            },
+            'volumes_links': parameter_types.links
+        },
+        'additionalProperties': False,
+        'required': ['volumes']
+    }
+}
+
+create_volume = {
+    'status_code': [202],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'volume': {
+                'type': 'object',
+                'properties': {
+                    'migration_status': {'type': ['string', 'null']},
+                    'attachments': attachments,
+                    'links': parameter_types.links,
+                    'availability_zone': {'type': ['string', 'null']},
+                    'encrypted': {'type': 'boolean'},
+                    'updated_at': parameter_types.date_time_or_null,
+                    'replication_status': {'type': ['string', 'null']},
+                    'snapshot_id': parameter_types.uuid_or_null,
+                    'id': {'type': 'string', 'format': 'uuid'},
+                    'size': {'type': 'integer'},
+                    'user_id': {'type': 'string', 'format': 'uuid'},
+                    'metadata': {'type': 'object'},
+                    'status': {'type': 'string'},
+                    'description': {'type': ['string', 'null']},
+                    'multiattach': {'type': 'boolean'},
+                    'source_volid': parameter_types.uuid_or_null,
+                    'consistencygroup_id': parameter_types.uuid_or_null,
+                    'name': {'type': ['string', 'null']},
+                    'bootable': {'type': 'string'},
+                    'created_at': parameter_types.date_time,
+                    'volume_type': {'type': ['string', 'null']},
+                    # TODO(zhufl): group_id is added in 3.13, we should move
+                    # it to the 3.13 schema file when microversion is
+                    # supported in volume interfaces.
+                    'group_id': parameter_types.uuid_or_null,
+                    # TODO(zhufl): provider_id is added in 3.21, we should
+                    # move it to the 3.21 schema file when microversion is
+                    # supported in volume interfaces
+                    'provider_id': parameter_types.uuid_or_null,
+                    # TODO(zhufl): service_uuid and shared_targets are added
+                    # in 3.48, we should move them to the 3.48 schema file
+                    # when microversion is supported in volume interfaces.
+                    'service_uuid': parameter_types.uuid_or_null,
+                    'shared_targets': {'type': 'boolean'}
+                },
+                'additionalProperties': False,
+                'required': ['attachments', 'links', 'encrypted',
+                             'updated_at', 'replication_status', 'id',
+                             'size', 'user_id', 'availability_zone',
+                             'metadata', 'status', 'description',
+                             'multiattach', 'consistencygroup_id',
+                             'name', 'bootable', 'created_at',
+                             'volume_type', 'snapshot_id', 'source_volid']
+            }
+        },
+        'additionalProperties': False,
+        'required': ['volume']
+    }
+}
+
+update_volume = copy.deepcopy(create_volume)
+update_volume.update({'status_code': [200]})
+
+delete_volume = {'status_code': [202]}
+
+show_volume_summary = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'volume-summary': {
+                'type': 'object',
+                'properties': {
+                    'total_size': {'type': 'integer'},
+                    'total_count': {'type': 'integer'},
+                    # TODO(zhufl): metadata is added in 3.36, we should move
+                    # it to the 3.36 schema file when microversion is
+                    # supported in volume interfaces
+                    'metadata': {'type': 'object'},
+                },
+                'additionalProperties': False,
+                'required': ['total_size', 'total_count']
+            }
+        },
+        'additionalProperties': False,
+        'required': ['volume-summary']
+    }
+}
+
+# TODO(zhufl): This is under discussion, so will be merged in a seperate patch.
+# https://bugs.launchpad.net/cinder/+bug/1880566
+# upload_volume = {
+#     'status_code': [202],
+#     'response_body': {
+#         'type': 'object',
+#         'properties': {
+#             'os-volume_upload_image': {
+#                 'type': 'object',
+#                 'properties': {
+#                     'status': {'type': 'string'},
+#                     'image_name': {'type': 'string'},
+#                     'disk_format': {'type': 'string'},
+#                     'container_format': {'type': 'string'},
+#                     'is_public': {'type': 'boolean'},
+#                     'visibility': {'type': 'string'},
+#                     'protected': {'type': 'boolean'},
+#                     'updated_at': parameter_types.date_time_or_null,
+#                     'image_id': {'type': 'string', 'format': 'uuid'},
+#                     'display_description': {'type': ['string', 'null']},
+#                     'id': {'type': 'string', 'format': 'uuid'},
+#                     'size': {'type': 'integer'},
+#                     'volume_type': {
+#                         'type': ['object', 'null'],
+#                         'properties': {
+#                             'created_at': parameter_types.date_time,
+#                             'deleted': {'type': 'boolean'},
+#                             'deleted_at': parameter_types.date_time_or_null,
+#                             'description': {'type': ['string', 'null']},
+#                             'extra_specs': {
+#                                 'type': 'object',
+#                                 'patternProperties': {
+#                                     '^.+$': {'type': 'string'}
+#                                 }
+#                             },
+#                             'id': {'type': 'string', 'format': 'uuid'},
+#                             'is_public': {'type': 'boolean'},
+#                             'name': {'type': ['string', 'null']},
+#                             'qos_specs_id': parameter_types.uuid_or_null,
+#                             'updated_at': parameter_types.date_time_or_null
+#                         },
+#                     }
+#                 },
+#                 'additionalProperties': False,
+#                 'required': ['status', 'image_name', 'updated_at',
+#                              'image_id',
+#                              'display_description', 'id', 'size',
+#                              'volume_type', 'disk_format',
+#                              'container_format']
+#             }
+#         },
+#         'additionalProperties': False,
+#         'required': ['os-volume_upload_image']
+#     }
+# }
+
+attach_volume = {'status_code': [202]}
+set_bootable_volume = {'status_code': [200]}
+detach_volume = {'status_code': [202]}
+reserve_volume = {'status_code': [202]}
+unreserve_volume = {'status_code': [202]}
+extend_volume = {'status_code': [202]}
+reset_volume_status = {'status_code': [202]}
+update_volume_readonly = {'status_code': [202]}
+force_delete_volume = {'status_code': [202]}
+retype_volume = {'status_code': [202]}
+force_detach_volume = {'status_code': [202]}
+
+create_volume_metadata = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'metadata': {'type': 'object'},
+        },
+        'additionalProperties': False,
+        'required': ['metadata']
+    }
+}
+
+show_volume_metadata = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'metadata': {'type': 'object'},
+        },
+        'additionalProperties': False,
+        'required': ['metadata']
+    }
+}
+update_volume_metadata = copy.deepcopy(show_volume_metadata)
+
+show_volume_metadata_item = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'meta': {'type': 'object'},
+        },
+        'additionalProperties': False,
+        'required': ['meta']
+    }
+}
+update_volume_metadata_item = copy.deepcopy(show_volume_metadata_item)
+delete_volume_metadata_item = {'status_code': [200]}
+
+update_volume_image_metadata = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {'metadata': {'type': 'object'}},
+        'additionalProperties': False,
+        'required': ['metadata']
+    }
+}
+delete_volume_image_metadata = {'status_code': [200]}
+show_volume_image_metadata = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'metadata': {'type': 'object'},
+        },
+        'additionalProperties': False,
+        'required': ['metadata']
+    }
+}
+
+unmanage_volume = {'status_code': [202]}
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 9c6729b..b8535d8 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -17,6 +17,7 @@
 import six
 from six.moves.urllib import parse as urllib
 
+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
 from tempest.lib.services.volume import base_client
@@ -55,14 +56,16 @@
         https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-volumes
         """
         url = 'volumes'
+        list_schema = schema.list_volumes_no_detail
         if detail:
+            list_schema = schema.list_volumes_with_detail
             url += '/detail'
         if params:
             url += '?%s' % self._prepare_params(params)
 
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(list_schema, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def migrate_volume(self, volume_id, **kwargs):
@@ -83,7 +86,7 @@
         url = "volumes/%s" % volume_id
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.show_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def create_volume(self, **kwargs):
@@ -96,7 +99,7 @@
         post_body = json.dumps({'volume': kwargs})
         resp, body = self.post('volumes', post_body)
         body = json.loads(body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.create_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume(self, volume_id, **kwargs):
@@ -109,7 +112,7 @@
         put_body = json.dumps({'volume': kwargs})
         resp, body = self.put('volumes/%s' % volume_id, put_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.update_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_volume(self, volume_id, **params):
@@ -123,7 +126,7 @@
         if params:
             url += '?%s' % urllib.urlencode(params)
         resp, body = self.delete(url)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.delete_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def show_volume_summary(self, **params):
@@ -138,7 +141,7 @@
             url += '?%s' % urllib.urlencode(params)
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.show_volume_summary, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def upload_volume(self, volume_id, **kwargs):
@@ -152,6 +155,10 @@
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
         body = json.loads(body)
+        # TODO(zhufl): This is under discussion, so will be merged
+        # in a seperate patch.
+        # https://bugs.launchpad.net/cinder/+bug/1880566
+        # self.validate_response(schema.upload_volume, resp, body)
         self.expected_success(202, resp.status)
         return rest_client.ResponseBody(resp, body)
 
@@ -165,7 +172,7 @@
         post_body = json.dumps({'os-attach': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.attach_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def set_bootable_volume(self, volume_id, **kwargs):
@@ -178,7 +185,7 @@
         post_body = json.dumps({'os-set_bootable': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.set_bootable_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def detach_volume(self, volume_id):
@@ -186,7 +193,7 @@
         post_body = json.dumps({'os-detach': {}})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.detach_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def reserve_volume(self, volume_id):
@@ -194,7 +201,7 @@
         post_body = json.dumps({'os-reserve': {}})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.reserve_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def unreserve_volume(self, volume_id):
@@ -202,7 +209,7 @@
         post_body = json.dumps({'os-unreserve': {}})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.unreserve_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def is_resource_deleted(self, id):
@@ -237,7 +244,7 @@
         post_body = json.dumps({'os-extend': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.extend_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def reset_volume_status(self, volume_id, **kwargs):
@@ -249,7 +256,7 @@
         """
         post_body = json.dumps({'os-reset_status': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.reset_volume_status, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume_readonly(self, volume_id, **kwargs):
@@ -262,14 +269,14 @@
         post_body = json.dumps({'os-update_readonly_flag': kwargs})
         url = 'volumes/%s/action' % (volume_id)
         resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.update_volume_readonly, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def force_delete_volume(self, volume_id):
         """Force Delete Volume."""
         post_body = json.dumps({'os-force_delete': {}})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.force_delete_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def create_volume_metadata(self, volume_id, metadata):
@@ -283,7 +290,7 @@
         url = "volumes/%s/metadata" % volume_id
         resp, body = self.post(url, put_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.create_volume_metadata, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def show_volume_metadata(self, volume_id):
@@ -291,7 +298,7 @@
         url = "volumes/%s/metadata" % volume_id
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.show_volume_metadata, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume_metadata(self, volume_id, metadata):
@@ -305,7 +312,7 @@
         url = "volumes/%s/metadata" % volume_id
         resp, body = self.put(url, put_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.update_volume_metadata, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def show_volume_metadata_item(self, volume_id, id):
@@ -313,7 +320,7 @@
         url = "volumes/%s/metadata/%s" % (volume_id, id)
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.show_volume_metadata_item, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume_metadata_item(self, volume_id, id, meta_item):
@@ -322,14 +329,14 @@
         url = "volumes/%s/metadata/%s" % (volume_id, id)
         resp, body = self.put(url, put_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.update_volume_metadata_item, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_volume_metadata_item(self, volume_id, id):
         """Delete metadata item for the volume."""
         url = "volumes/%s/metadata/%s" % (volume_id, id)
         resp, body = self.delete(url)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.delete_volume_metadata_item, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def retype_volume(self, volume_id, **kwargs):
@@ -341,7 +348,7 @@
         """
         post_body = json.dumps({'os-retype': kwargs})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.retype_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def force_detach_volume(self, volume_id, **kwargs):
@@ -354,7 +361,7 @@
         post_body = json.dumps({'os-force_detach': kwargs})
         url = 'volumes/%s/action' % volume_id
         resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.force_detach_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def update_volume_image_metadata(self, volume_id, **kwargs):
@@ -368,7 +375,7 @@
         url = "volumes/%s/action" % (volume_id)
         resp, body = self.post(url, post_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.update_volume_image_metadata, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_volume_image_metadata(self, volume_id, key_name):
@@ -376,7 +383,7 @@
         post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
         url = "volumes/%s/action" % (volume_id)
         resp, body = self.post(url, post_body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.delete_volume_image_metadata, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def show_volume_image_metadata(self, volume_id):
@@ -385,7 +392,7 @@
         url = "volumes/%s/action" % volume_id
         resp, body = self.post(url, post_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.show_volume_image_metadata, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def unmanage_volume(self, volume_id):
@@ -397,5 +404,5 @@
         """
         post_body = json.dumps({'os-unmanage': {}})
         resp, body = self.post('volumes/%s/action' % volume_id, post_body)
-        self.expected_success(202, resp.status)
+        self.validate_response(schema.unmanage_volume, resp, body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
index 7bf7315..fc44793 100644
--- a/tempest/tests/cmd/test_cleanup_services.py
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -511,7 +511,8 @@
             },
             {
                 "id": "aa77asdf-1234",
-                "name": "saved-volume"
+                "name": "saved-volume",
+                "links": [],
             }
         ]
     }
diff --git a/tempest/tests/lib/services/volume/v3/test_volumes_client.py b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
index 56c1a35..6bd75d9 100644
--- a/tempest/tests/lib/services/volume/v3/test_volumes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
@@ -26,10 +26,6 @@
         "volume-summary": {
             "total_size": 4,
             "total_count": 4,
-            "metadata": {
-                "key1": ["value1", "value2"],
-                "key2": ["value2"]
-            }
         }
     }