Merge "Add response schema validation for volume types"

This commit is contained in:
Zuul
2020-03-16 02:15:56 +00:00
committed by Gerrit Code Review
3 changed files with 195 additions and 16 deletions

View File

@@ -92,15 +92,12 @@ class VolumeTypesTest(base.BaseVolumeAdminTest):
'extra_specs': extra_specs,
'os-volume-type-access:is_public': True}
body = self.create_volume_type(**params)
self.assertIn('name', body)
self.assertEqual(name, body['name'],
"The created volume_type name is not equal "
"to the requested name")
self.assertEqual(description, body['description'],
"The created volume_type_description name is "
"not equal to the requested name")
self.assertIsNotNone(body['id'],
"Field volume_type id is empty or not found.")
fetched_volume_type = self.admin_volume_types_client.show_volume_type(
body['id'])['volume_type']
self.assertEqual(name, fetched_volume_type['name'],

View File

@@ -0,0 +1,176 @@
# 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.
extra_specs_info = {
'type': 'object',
'patternProperties': {
'^.+$': {'type': 'string'}
}
}
common_show_volume_type = {
'type': 'object',
'properties': {
'extra_specs': extra_specs_info,
'name': {'type': 'string'},
'is_public': {'type': 'boolean'},
'description': {'type': ['string', 'null']},
'id': {'type': 'string', 'format': 'uuid'},
'os-volume-type-access:is_public': {'type': 'boolean'},
'qos_specs_id': {'type': ['string', 'null'], 'format': 'uuid'}
},
'additionalProperties': False,
'required': ['name', 'is_public', 'description', 'id',
'os-volume-type-access:is_public']
}
show_volume_type = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'volume_type': common_show_volume_type,
},
'additionalProperties': False,
'required': ['volume_type']
}
}
delete_volume_type = {'status_code': [202]}
create_volume_type = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'volume_type': {
'type': 'object',
'properties': {
'extra_specs': extra_specs_info,
'name': {'type': 'string'},
'is_public': {'type': 'boolean'},
'description': {'type': ['string', 'null']},
'id': {'type': 'string', 'format': 'uuid'},
'os-volume-type-access:is_public': {'type': 'boolean'}
},
'additionalProperties': False,
'required': ['name', 'is_public', 'id',
'description', 'os-volume-type-access:is_public']
},
},
'additionalProperties': False,
'required': ['volume_type']
}
}
list_volume_types = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'volume_types': {
'type': 'array',
'items': common_show_volume_type
}
},
'additionalProperties': False,
'required': ['volume_types']
}
}
list_volume_types_extra_specs = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'extra_specs': extra_specs_info
},
'additionalProperties': False,
'required': ['extra_specs']
}
}
show_volume_types_extra_specs = {
'status_code': [200],
'response_body': extra_specs_info
}
create_volume_types_extra_specs = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'extra_specs': extra_specs_info
},
'additionalProperties': False,
'required': ['extra_specs']
}
}
delete_volume_types_extra_specs = {'status_code': [202]}
update_volume_types = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'volume_type': {
'type': 'object',
'properties': {
'extra_specs': extra_specs_info,
'name': {'type': 'string'},
'is_public': {'type': 'boolean'},
'description': {'type': ['string', 'null']},
'id': {'type': 'string', 'format': 'uuid'}
},
'additionalProperties': False,
'required': ['name', 'is_public', 'description', 'id']
},
},
'additionalProperties': False,
'required': ['volume_type']
}
}
update_volume_type_extra_specs = {
'status_code': [200],
'response_body': extra_specs_info
}
add_type_access = {'status_code': [202]}
remove_type_access = {'status_code': [202]}
list_type_access = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'volume_type_access': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'volume_type_id': {'type': 'string', 'format': 'uuid'},
'project_id': {'type': 'string', 'format': 'uuid'},
},
'additionalProperties': False,
'required': ['volume_type_id', 'project_id']
}
}
},
'additionalProperties': False,
'required': ['volume_type_access']
}
}

View File

@@ -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 volume_types as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
@@ -48,7 +49,7 @@ class TypesClient(rest_client.RestClient):
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(schema.list_volume_types, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_type(self, volume_type_id):
@@ -61,7 +62,7 @@ class TypesClient(rest_client.RestClient):
url = "types/%s" % volume_type_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(schema.show_volume_type, resp, body)
return rest_client.ResponseBody(resp, body)
def create_volume_type(self, **kwargs):
@@ -74,7 +75,7 @@ class TypesClient(rest_client.RestClient):
post_body = json.dumps({'volume_type': kwargs})
resp, body = self.post('types', post_body)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(schema.create_volume_type, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_volume_type(self, volume_type_id):
@@ -85,7 +86,7 @@ class TypesClient(rest_client.RestClient):
https://docs.openstack.org/api-ref/block-storage/v3/index.html#delete-a-volume-type
"""
resp, body = self.delete("types/%s" % volume_type_id)
self.expected_success(202, resp.status)
self.validate_response(schema.delete_volume_type, resp, body)
return rest_client.ResponseBody(resp, body)
def list_volume_types_extra_specs(self, volume_type_id, **params):
@@ -101,7 +102,8 @@ class TypesClient(rest_client.RestClient):
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(
schema.list_volume_types_extra_specs, resp, body)
return rest_client.ResponseBody(resp, body)
def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name):
@@ -109,7 +111,8 @@ class TypesClient(rest_client.RestClient):
url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name)
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(
schema.show_volume_types_extra_specs, resp, body)
return rest_client.ResponseBody(resp, body)
def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
@@ -122,14 +125,16 @@ class TypesClient(rest_client.RestClient):
post_body = json.dumps({'extra_specs': extra_specs})
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(
schema.create_volume_types_extra_specs, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name):
"""Deletes the specified volume type extra spec."""
resp, body = self.delete("types/%s/extra_specs/%s" % (
volume_type_id, extra_spec_name))
self.expected_success(202, resp.status)
self.validate_response(
schema.delete_volume_types_extra_specs, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_type(self, volume_type_id, **kwargs):
@@ -142,7 +147,7 @@ class TypesClient(rest_client.RestClient):
put_body = json.dumps({'volume_type': kwargs})
resp, body = self.put('types/%s' % volume_type_id, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(schema.update_volume_types, resp, body)
return rest_client.ResponseBody(resp, body)
def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name,
@@ -162,7 +167,8 @@ class TypesClient(rest_client.RestClient):
put_body = json.dumps(extra_specs)
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(
schema.update_volume_type_extra_specs, resp, body)
return rest_client.ResponseBody(resp, body)
def add_type_access(self, volume_type_id, **kwargs):
@@ -175,7 +181,7 @@ class TypesClient(rest_client.RestClient):
post_body = json.dumps({'addProjectAccess': kwargs})
url = 'types/%s/action' % volume_type_id
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
self.validate_response(schema.add_type_access, resp, body)
return rest_client.ResponseBody(resp, body)
def remove_type_access(self, volume_type_id, **kwargs):
@@ -188,7 +194,7 @@ class TypesClient(rest_client.RestClient):
post_body = json.dumps({'removeProjectAccess': kwargs})
url = 'types/%s/action' % volume_type_id
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
self.validate_response(schema.remove_type_access, resp, body)
return rest_client.ResponseBody(resp, body)
def list_type_access(self, volume_type_id):
@@ -201,5 +207,5 @@ class TypesClient(rest_client.RestClient):
url = 'types/%s/os-volume-type-access' % volume_type_id
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
self.validate_response(schema.list_type_access, resp, body)
return rest_client.ResponseBody(resp, body)