From adb162024daa1549c2cd145d3a853104e2ec9d29 Mon Sep 17 00:00:00 2001 From: zhufl Date: Thu, 11 Oct 2018 16:52:49 +0800 Subject: [PATCH] Add response schema validation for volume snapshots This is to add response schema validation for volume snapshots. Change-Id: I7de5569ed58fb6a3bf8115f89163cd951c19d951 partially-implements: blueprint volume-response-schema-validation --- .../api_schema/response/volume/snapshots.py | 198 ++++++++++++++++++ .../services/volume/v3/snapshots_client.py | 35 ++-- tempest/tests/cmd/test_cleanup_services.py | 7 +- 3 files changed, 223 insertions(+), 17 deletions(-) create mode 100644 tempest/lib/api_schema/response/volume/snapshots.py diff --git a/tempest/lib/api_schema/response/volume/snapshots.py b/tempest/lib/api_schema/response/volume/snapshots.py new file mode 100644 index 0000000000..e9aeb6405d --- /dev/null +++ b/tempest/lib/api_schema/response/volume/snapshots.py @@ -0,0 +1,198 @@ +# 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 + +metadata = { + 'type': 'object', + 'patternProperties': { + '^.+$': {'type': 'string'} + } +} + +common_snapshot_schema = { + 'type': 'object', + 'properties': { + 'status': {'type': 'string'}, + 'description': {'type': ['string', 'null']}, + 'created_at': parameter_types.date_time, + 'name': {'type': 'string'}, + 'volume_id': {'type': 'string', 'format': 'uuid'}, + 'metadata': metadata, + 'id': {'type': 'string', 'format': 'uuid'}, + 'size': {'type': 'integer'}, + 'updated_at': parameter_types.date_time_or_null, + # TODO(zhufl): user_id is added in 3.41, we should move it + # to the 3.41 schema file when microversion is supported + # in volume interfaces + # 'user_id': {'type': 'string', 'format': 'uuid'} + }, + 'additionalProperties': False, + 'required': ['status', 'description', 'created_at', 'metadata', + 'name', 'volume_id', 'id', 'size', 'updated_at'] +} + +common_snapshot_detail_schema = copy.deepcopy(common_snapshot_schema) +common_snapshot_detail_schema['properties'].update( + {'os-extended-snapshot-attributes:progress': {'type': 'string'}, + 'os-extended-snapshot-attributes:project_id': { + 'type': 'string', 'format': 'uuid'}}) +common_snapshot_detail_schema['required'].extend( + ['os-extended-snapshot-attributes:progress', + 'os-extended-snapshot-attributes:project_id']) + +list_snapshots_no_detail = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'snapshots': { + 'type': 'array', + 'items': common_snapshot_schema + }, + 'snapshots_links': parameter_types.links, + # 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': ['snapshots'], + } +} + +list_snapshots_with_detail = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'snapshots': { + 'type': 'array', + 'items': common_snapshot_detail_schema + }, + 'snapshots_links': parameter_types.links, + # 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': ['snapshots'], + } +} + +show_snapshot = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'snapshot': common_snapshot_detail_schema + }, + 'additionalProperties': False, + 'required': ['snapshot'], + } +} + +create_snapshot = { + 'status_code': [202], + 'response_body': { + 'type': 'object', + 'properties': { + 'snapshot': common_snapshot_schema + }, + 'additionalProperties': False, + 'required': ['snapshot'], + } +} + +update_snapshot = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'snapshot': common_snapshot_schema + }, + 'additionalProperties': False, + 'required': ['snapshot'], + } +} + +delete_snapshot = {'status_code': [202]} +reset_snapshot_status = {'status_code': [202]} +update_snapshot_status = {'status_code': [202]} + +create_snapshot_metadata = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'metadata': metadata + }, + 'additionalProperties': False, + 'required': ['metadata'], + } +} + +show_snapshot_metadata = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'metadata': metadata + }, + 'additionalProperties': False, + 'required': ['metadata'], + } +} + +update_snapshot_metadata = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'metadata': metadata + }, + 'additionalProperties': False, + 'required': ['metadata'], + } +} + +show_snapshot_metadata_item = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'meta': metadata + }, + 'additionalProperties': False, + 'required': ['meta'], + } +} + +update_snapshot_metadata_item = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'meta': metadata + }, + 'additionalProperties': False, + 'required': ['meta'], + } +} + +delete_snapshot_metadata_item = {'status_code': [200]} +force_delete_snapshot = {'status_code': [202]} +unmanage_snapshot = {'status_code': [202]} diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py index 264381db2e..8ca2044d20 100644 --- a/tempest/lib/services/volume/v3/snapshots_client.py +++ b/tempest/lib/services/volume/v3/snapshots_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 snapshots as schema from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc @@ -32,14 +33,16 @@ class SnapshotsClient(rest_client.RestClient): https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-snapshots-and-details """ url = 'snapshots' + list_schema = schema.list_snapshots_no_detail if detail: url += '/detail' + list_schema = schema.list_snapshots_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(list_schema, resp, body) return rest_client.ResponseBody(resp, body) def show_snapshot(self, snapshot_id): @@ -52,7 +55,7 @@ class SnapshotsClient(rest_client.RestClient): url = "snapshots/%s" % snapshot_id resp, body = self.get(url) body = json.loads(body) - self.expected_success(200, resp.status) + self.validate_response(schema.show_snapshot, resp, body) return rest_client.ResponseBody(resp, body) def create_snapshot(self, **kwargs): @@ -65,7 +68,7 @@ class SnapshotsClient(rest_client.RestClient): post_body = json.dumps({'snapshot': kwargs}) resp, body = self.post('snapshots', post_body) body = json.loads(body) - self.expected_success(202, resp.status) + self.validate_response(schema.create_snapshot, resp, body) return rest_client.ResponseBody(resp, body) def update_snapshot(self, snapshot_id, **kwargs): @@ -78,7 +81,7 @@ class SnapshotsClient(rest_client.RestClient): put_body = json.dumps({'snapshot': kwargs}) resp, body = self.put('snapshots/%s' % snapshot_id, put_body) body = json.loads(body) - self.expected_success(200, resp.status) + self.validate_response(schema.update_snapshot, resp, body) return rest_client.ResponseBody(resp, body) def delete_snapshot(self, snapshot_id): @@ -89,7 +92,7 @@ class SnapshotsClient(rest_client.RestClient): https://docs.openstack.org/api-ref/block-storage/v3/index.html#delete-a-snapshot """ resp, body = self.delete("snapshots/%s" % snapshot_id) - self.expected_success(202, resp.status) + self.validate_response(schema.delete_snapshot, resp, body) return rest_client.ResponseBody(resp, body) def is_resource_deleted(self, id): @@ -108,7 +111,7 @@ class SnapshotsClient(rest_client.RestClient): """Reset the specified snapshot's status.""" post_body = json.dumps({'os-reset_status': {"status": status}}) resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body) - self.expected_success(202, resp.status) + self.validate_response(schema.reset_snapshot_status, resp, body) return rest_client.ResponseBody(resp, body) def update_snapshot_status(self, snapshot_id, **kwargs): @@ -121,7 +124,7 @@ class SnapshotsClient(rest_client.RestClient): post_body = json.dumps({'os-update_snapshot_status': kwargs}) url = 'snapshots/%s/action' % snapshot_id resp, body = self.post(url, post_body) - self.expected_success(202, resp.status) + self.validate_response(schema.update_snapshot_status, resp, body) return rest_client.ResponseBody(resp, body) def create_snapshot_metadata(self, snapshot_id, metadata): @@ -135,7 +138,7 @@ class SnapshotsClient(rest_client.RestClient): url = "snapshots/%s/metadata" % snapshot_id resp, body = self.post(url, put_body) body = json.loads(body) - self.expected_success(200, resp.status) + self.validate_response(schema.create_snapshot_metadata, resp, body) return rest_client.ResponseBody(resp, body) def show_snapshot_metadata(self, snapshot_id): @@ -148,7 +151,7 @@ class SnapshotsClient(rest_client.RestClient): url = "snapshots/%s/metadata" % snapshot_id resp, body = self.get(url) body = json.loads(body) - self.expected_success(200, resp.status) + self.validate_response(schema.show_snapshot_metadata, resp, body) return rest_client.ResponseBody(resp, body) def update_snapshot_metadata(self, snapshot_id, **kwargs): @@ -162,7 +165,7 @@ class SnapshotsClient(rest_client.RestClient): url = "snapshots/%s/metadata" % snapshot_id resp, body = self.put(url, put_body) body = json.loads(body) - self.expected_success(200, resp.status) + self.validate_response(schema.update_snapshot_metadata, resp, body) return rest_client.ResponseBody(resp, body) def show_snapshot_metadata_item(self, snapshot_id, id): @@ -170,7 +173,7 @@ class SnapshotsClient(rest_client.RestClient): url = "snapshots/%s/metadata/%s" % (snapshot_id, id) resp, body = self.get(url) body = json.loads(body) - self.expected_success(200, resp.status) + self.validate_response(schema.show_snapshot_metadata_item, resp, body) return rest_client.ResponseBody(resp, body) def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs): @@ -184,21 +187,23 @@ class SnapshotsClient(rest_client.RestClient): url = "snapshots/%s/metadata/%s" % (snapshot_id, id) resp, body = self.put(url, put_body) body = json.loads(body) - self.expected_success(200, resp.status) + self.validate_response( + schema.update_snapshot_metadata_item, resp, body) return rest_client.ResponseBody(resp, body) def delete_snapshot_metadata_item(self, snapshot_id, id): """Delete metadata item for the snapshot.""" url = "snapshots/%s/metadata/%s" % (snapshot_id, id) resp, body = self.delete(url) - self.expected_success(200, resp.status) + self.validate_response( + schema.delete_snapshot_metadata_item, resp, body) return rest_client.ResponseBody(resp, body) def force_delete_snapshot(self, snapshot_id): """Force Delete Snapshot.""" post_body = json.dumps({'os-force_delete': {}}) resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body) - self.expected_success(202, resp.status) + self.validate_response(schema.force_delete_snapshot, resp, body) return rest_client.ResponseBody(resp, body) def unmanage_snapshot(self, snapshot_id): @@ -206,5 +211,5 @@ class SnapshotsClient(rest_client.RestClient): post_body = json.dumps({'os-unmanage': {}}) url = 'snapshots/%s/action' % (snapshot_id) resp, body = self.post(url, post_body) - self.expected_success(202, resp.status) + self.validate_response(schema.unmanage_snapshot, 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..7bf7315365 100644 --- a/tempest/tests/cmd/test_cleanup_services.py +++ b/tempest/tests/cmd/test_cleanup_services.py @@ -274,19 +274,22 @@ class TestSnapshotService(BaseCmdServiceTests): "name": "test" }, "name": "test-volume-snapshot", - "user_id": "40c2102f4a554b848d96b14f3eec39ed", "volume_id": "173f7b48-c4c1-4e70-9acc-086b39073506", "created_at": "2015-11-29T02:25:51.000000", "size": 1, "updated_at": "2015-11-20T05:36:40.000000", - "os-extended-snapshot-attributes:progress": "100%", "id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c", "description": "volume snapshot" }, { "status": "available", "name": "saved-snapshot", + "metadata": {}, "id": "1ad4c789-7e8w-4dwg-afc5", + "size": 1, + "volume_id": "af7c41be-1ff6-4233-a690-7ed61c34347f", + "created_at": "2015-11-20T05:39:40.000000", + "updated_at": "2015-11-20T05:39:40.000000", "description": "snapshot in saved state" } ]