From 3cb4772db4b11bc111845a27cb23c3671e59d328 Mon Sep 17 00:00:00 2001 From: zhufl Date: Fri, 9 Nov 2018 14:31:49 +0800 Subject: [PATCH] Add response schema validation for volumes This is to add response schema validation for volumes. NOTE: upload_volume schema is under discussion, so will be merged in a seperate patch. https://bugs.launchpad.net/cinder/+bug/1880566 Change-Id: I0613c36e9cc512a1aba1f7952ffbc7447b05198b partially-implements: blueprint volume-response-schema-validation --- .../api/volume/admin/test_volumes_actions.py | 1 - tempest/api/volume/test_volumes_actions.py | 1 - tempest/api/volume/test_volumes_get.py | 9 +- .../response/compute/v2_1/parameter_types.py | 7 + .../lib/api_schema/response/volume/volumes.py | 368 ++++++++++++++++++ .../lib/services/volume/v3/volumes_client.py | 61 +-- tempest/tests/cmd/test_cleanup_services.py | 3 +- .../services/volume/v3/test_volumes_client.py | 4 - 8 files changed, 413 insertions(+), 41 deletions(-) create mode 100644 tempest/lib/api_schema/response/volume/volumes.py diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py index 5bac3d877e..dc324a731e 100644 --- a/tempest/api/volume/admin/test_volumes_actions.py +++ b/tempest/api/volume/admin/test_volumes_actions.py @@ -102,5 +102,4 @@ class VolumesActionsTest(base.BaseVolumeAdminTest): 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 9edffc697b..e42ea409df 100644 --- a/tempest/api/volume/test_volumes_actions.py +++ b/tempest/api/volume/test_volumes_actions.py @@ -84,7 +84,6 @@ class VolumesActionsTest(base.BaseVolumeTest): 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 ade2deb19b..91728ab415 100644 --- a/tempest/api/volume/test_volumes_get.py +++ b/tempest/api/volume/test_volumes_get.py @@ -37,11 +37,9 @@ class VolumesGetTest(base.BaseVolumeTest): 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 @@ class VolumesGetTest(base.BaseVolumeTest): '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 @@ class VolumesSummaryTest(base.BaseVolumeTest): @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 28ed81673a..8aed37d829 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 @@ power_state = { # 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 0000000000..ffcf488b59 --- /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 4fb6d2ee56..ebe669afb7 100644 --- a/tempest/lib/services/volume/v3/volumes_client.py +++ b/tempest/lib/services/volume/v3/volumes_client.py @@ -17,6 +17,7 @@ from oslo_serialization import jsonutils as json 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): """ 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): """ 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): 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 @@ class VolumesClient(base_client.BaseClient): """ 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 8366290612..2660514a61 100644 --- a/tempest/tests/cmd/test_cleanup_services.py +++ b/tempest/tests/cmd/test_cleanup_services.py @@ -508,7 +508,8 @@ class TestVolumeService(BaseCmdServiceTests): }, { "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 56c1a359c6..6bd75d9fce 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 @@ class TestVolumesClient(base.BaseServiceTest): "volume-summary": { "total_size": 4, "total_count": 4, - "metadata": { - "key1": ["value1", "value2"], - "key2": ["value2"] - } } }