Separate API schemas for v2.0 compatible API
We are facing v2 compatibility problems for v2.0 compatible API which
became the default API on /v2 endpoint in Liberty. For fixing these
problems, we need to change some API schemas but we cannot change them
directly because these schemas are used for v2.1 API also and we need
to bump a new microversion for changing them as microversions contract.
To fix these problem without v2.1 API schema changes, this patch
separates the API schemas of v2.0 compatible API from v2.1 API ones.
If we need to separate v2.0 schemas from v2.1, we can specify the
separated schema with '2.0' to the decorator @validation.schema like:
@validation.schema(schema_v20, '2.0', '2.0')
@validation.schema(schema, '2.1')
def update(self, req, id, body):
...
Change-Id: If35306b6a9dfc0da355b9edbf4451bf72516da24
This commit is contained in:
committed by
He Jie Xu
parent
1db33ca6c2
commit
6ae6845c63
@@ -190,7 +190,7 @@ class ServiceController(wsgi.Controller):
|
||||
return {'services': _services}
|
||||
|
||||
@extensions.expected_errors((400, 404))
|
||||
@validation.schema(services.service_update, '2.1', '2.10')
|
||||
@validation.schema(services.service_update, '2.0', '2.10')
|
||||
@validation.schema(services.service_update_v211, '2.11')
|
||||
def update(self, req, id, body):
|
||||
"""Perform service update"""
|
||||
|
||||
@@ -49,7 +49,19 @@ def schema(request_body_schema, min_version=None, max_version=None):
|
||||
else:
|
||||
ver = args[1].api_version_request
|
||||
legacy_v2 = args[1].is_legacy_v2()
|
||||
if ver.matches(min_ver, max_ver):
|
||||
|
||||
if legacy_v2:
|
||||
# NOTE: For v2.0 compatible API, here should work like
|
||||
# client | schema min_version | schema
|
||||
# -----------+--------------------+--------
|
||||
# legacy_v2 | None | work
|
||||
# legacy_v2 | 2.0 | work
|
||||
# legacy_v2 | 2.1+ | don't
|
||||
if min_version is None or min_version == '2.0':
|
||||
schema_validator = validators._SchemaValidator(
|
||||
request_body_schema, legacy_v2)
|
||||
schema_validator.validate(kwargs['body'])
|
||||
elif ver.matches(min_ver, max_ver):
|
||||
# Only validate against the schema if it lies within
|
||||
# the version range specified. Note that if both min
|
||||
# and max are not specified the validator will always
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import re
|
||||
|
||||
from nova.api.openstack import api_version_request as api_version
|
||||
@@ -24,16 +25,19 @@ from nova import test
|
||||
class FakeRequest(object):
|
||||
api_version_request = api_version.APIVersionRequest("2.1")
|
||||
environ = {}
|
||||
legacy_v2 = False
|
||||
|
||||
def is_legacy_v2(self):
|
||||
return False
|
||||
return self.legacy_v2
|
||||
|
||||
|
||||
class APIValidationTestCase(test.NoDBTestCase):
|
||||
|
||||
def check_validation_error(self, method, body, expected_detail):
|
||||
def check_validation_error(self, method, body, expected_detail, req=None):
|
||||
if not req:
|
||||
req = FakeRequest()
|
||||
try:
|
||||
method(body=body, req=FakeRequest(),)
|
||||
method(body=body, req=req,)
|
||||
except exception.ValidationError as ex:
|
||||
self.assertEqual(400, ex.kwargs['code'])
|
||||
if not re.match(expected_detail, ex.kwargs['detail']):
|
||||
@@ -45,6 +49,48 @@ class APIValidationTestCase(test.NoDBTestCase):
|
||||
self.fail('Any exception does not happen.')
|
||||
|
||||
|
||||
class MicroversionsSchemaTestCase(APIValidationTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MicroversionsSchemaTestCase, self).setUp()
|
||||
schema_v21_int = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'integer',
|
||||
}
|
||||
}
|
||||
}
|
||||
schema_v20_str = copy.deepcopy(schema_v21_int)
|
||||
schema_v20_str['properties']['foo'] = {'type': 'string'}
|
||||
|
||||
@validation.schema(schema_v20_str, '2.0', '2.0')
|
||||
@validation.schema(schema_v21_int, '2.1')
|
||||
def post(req, body):
|
||||
return 'Validation succeeded.'
|
||||
|
||||
self.post = post
|
||||
|
||||
def test_validate_v2compatible_request(self):
|
||||
req = FakeRequest()
|
||||
req.legacy_v2 = True
|
||||
self.assertEqual(self.post(body={'foo': 'bar'}, req=req),
|
||||
'Validation succeeded.')
|
||||
detail = ("Invalid input for field/attribute foo. Value: 1. "
|
||||
"1 is not of type 'string'")
|
||||
self.check_validation_error(self.post, body={'foo': 1},
|
||||
expected_detail=detail, req=req)
|
||||
|
||||
def test_validate_v21_request(self):
|
||||
req = FakeRequest()
|
||||
self.assertEqual(self.post(body={'foo': 1}, req=req),
|
||||
'Validation succeeded.')
|
||||
detail = ("Invalid input for field/attribute foo. Value: bar. "
|
||||
"'bar' is not of type 'integer'")
|
||||
self.check_validation_error(self.post, body={'foo': 'bar'},
|
||||
expected_detail=detail, req=req)
|
||||
|
||||
|
||||
class RequiredDisableTestCase(APIValidationTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user