Improve validation of openstack config requests
* In the JSON schema 'number' type restriction replaced with 'integer'. * Extend parsing boolean values in URL query Related-Bug: #1557462 Change-Id: Iaaafb61aaa030b001358cee00e9c6b66c306cd46
This commit is contained in:
parent
8c535849e2
commit
fada90bd10
@ -19,8 +19,8 @@ _base_config = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_base_properties = {
|
_base_properties = {
|
||||||
'cluster_id': {'type': 'number', },
|
'cluster_id': {'type': 'integer'},
|
||||||
'node_id': {'type': 'number'},
|
'node_id': {'type': 'integer'},
|
||||||
'node_role': {'type': 'string'},
|
'node_role': {'type': 'string'},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ OPENSTACK_CONFIG = {
|
|||||||
'title': 'OpenstackConfig',
|
'title': 'OpenstackConfig',
|
||||||
'description': 'Openstack Configuration',
|
'description': 'Openstack Configuration',
|
||||||
'properties': {
|
'properties': {
|
||||||
'id': {'type': 'number'},
|
'id': {'type': 'integer'},
|
||||||
'configuration': {'type': 'object'},
|
'configuration': {'type': 'object'},
|
||||||
},
|
},
|
||||||
'required': ['cluster_id', 'configuration'],
|
'required': ['cluster_id', 'configuration'],
|
||||||
@ -38,7 +38,7 @@ OPENSTACK_CONFIG_EXECUTE = {
|
|||||||
'title': 'OpenstackConfig execute',
|
'title': 'OpenstackConfig execute',
|
||||||
'description': 'Openstack Configuration filters for execute',
|
'description': 'Openstack Configuration filters for execute',
|
||||||
'properties': {
|
'properties': {
|
||||||
'id': {'type': 'number'},
|
'id': {'type': 'integer'},
|
||||||
'force': {'type': 'boolean'}
|
'force': {'type': 'boolean'}
|
||||||
},
|
},
|
||||||
'required': ['cluster_id'],
|
'required': ['cluster_id'],
|
||||||
@ -48,12 +48,7 @@ OPENSTACK_CONFIG_QUERY = {
|
|||||||
'title': 'OpenstackConfig query',
|
'title': 'OpenstackConfig query',
|
||||||
'description': 'URL query for Openstack Configuration filter',
|
'description': 'URL query for Openstack Configuration filter',
|
||||||
'properties': {
|
'properties': {
|
||||||
'is_active': {
|
'is_active': {'type': 'boolean'}
|
||||||
'type': 'number',
|
|
||||||
'description': "is_active is a number since GET request"
|
|
||||||
" query parameters don't carry type information"
|
|
||||||
" and are parsed as strings"},
|
|
||||||
'cluster_id': {'type': 'number'},
|
|
||||||
},
|
},
|
||||||
'required': ['cluster_id'],
|
'required': ['cluster_id'],
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,11 @@ from nailgun import consts
|
|||||||
from nailgun.db.sqlalchemy import models
|
from nailgun.db.sqlalchemy import models
|
||||||
from nailgun.errors import errors
|
from nailgun.errors import errors
|
||||||
from nailgun import objects
|
from nailgun import objects
|
||||||
|
from nailgun import utils
|
||||||
|
|
||||||
|
|
||||||
class OpenstackConfigValidator(BasicValidator):
|
class OpenstackConfigValidator(BasicValidator):
|
||||||
|
|
||||||
int_fields = frozenset(['cluster_id', 'node_id', 'is_active'])
|
|
||||||
exclusive_fields = frozenset(['node_id', 'node_role'])
|
exclusive_fields = frozenset(['node_id', 'node_role'])
|
||||||
|
|
||||||
supported_configs = frozenset([
|
supported_configs = frozenset([
|
||||||
@ -136,7 +136,7 @@ class OpenstackConfigValidator(BasicValidator):
|
|||||||
cls._check_exclusive_fields(data)
|
cls._check_exclusive_fields(data)
|
||||||
cls.validate_schema(data, schema.OPENSTACK_CONFIG_QUERY)
|
cls.validate_schema(data, schema.OPENSTACK_CONFIG_QUERY)
|
||||||
|
|
||||||
data['is_active'] = bool(data.get('is_active', True))
|
data.setdefault('is_active', True)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -156,9 +156,21 @@ class OpenstackConfigValidator(BasicValidator):
|
|||||||
Schema validation doesn't perform any type conversion, so
|
Schema validation doesn't perform any type conversion, so
|
||||||
it is required to convert them before schema validation.
|
it is required to convert them before schema validation.
|
||||||
"""
|
"""
|
||||||
for field in cls.int_fields:
|
for field in ['cluster_id', 'node_id']:
|
||||||
if field in data and data[field] is not None:
|
value = data.get(field, None)
|
||||||
data[field] = int(data[field])
|
if value is not None:
|
||||||
|
try:
|
||||||
|
data[field] = int(value)
|
||||||
|
except ValueError:
|
||||||
|
raise errors.InvalidData("Invalid '{0}' value: '{1}'"
|
||||||
|
.format(field, value))
|
||||||
|
|
||||||
|
if 'is_active' in data:
|
||||||
|
try:
|
||||||
|
data['is_active'] = utils.parse_bool(data['is_active'])
|
||||||
|
except ValueError:
|
||||||
|
raise errors.InvalidData("Invalid 'is_active' value: '{0}'"
|
||||||
|
.format(data['is_active']))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_exclusive_fields(cls, data):
|
def _check_exclusive_fields(cls, data):
|
||||||
|
@ -202,6 +202,16 @@ class TestOpenstackConfigHandlers(BaseIntegrationTest):
|
|||||||
resp.json_body['message'],
|
resp.json_body['message'],
|
||||||
r"Parameter '\w+' conflicts with '\w+(, \w+)*'")
|
r"Parameter '\w+' conflicts with '\w+(, \w+)*'")
|
||||||
|
|
||||||
|
def test_openstack_config_list_invalid_params(self):
|
||||||
|
for param in ['cluster_id', 'node_id', 'is_active']:
|
||||||
|
url = self._make_filter_url(**{param: 'invalidvalue'})
|
||||||
|
resp = self.app.get(url, headers=self.default_headers,
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(resp.status_code, 400)
|
||||||
|
self.assertEqual(
|
||||||
|
resp.json_body['message'],
|
||||||
|
"Invalid '{0}' value: 'invalidvalue'".format(param))
|
||||||
|
|
||||||
def test_openstack_config_get(self):
|
def test_openstack_config_get(self):
|
||||||
resp = self.app.get(
|
resp = self.app.get(
|
||||||
reverse('OpenstackConfigHandler',
|
reverse('OpenstackConfigHandler',
|
||||||
|
@ -26,6 +26,7 @@ from nailgun.utils import dict_update
|
|||||||
from nailgun.utils import flatten
|
from nailgun.utils import flatten
|
||||||
from nailgun.utils import get_lines
|
from nailgun.utils import get_lines
|
||||||
from nailgun.utils import grouper
|
from nailgun.utils import grouper
|
||||||
|
from nailgun.utils import parse_bool
|
||||||
from nailgun.utils import text_format_safe
|
from nailgun.utils import text_format_safe
|
||||||
from nailgun.utils import traverse
|
from nailgun.utils import traverse
|
||||||
|
|
||||||
@ -138,6 +139,18 @@ class TestUtils(base.BaseIntegrationTest):
|
|||||||
|
|
||||||
self.assertEqual(get_lines(mixed), ['abc', 'foo', 'bar'])
|
self.assertEqual(get_lines(mixed), ['abc', 'foo', 'bar'])
|
||||||
|
|
||||||
|
def test_parse_bool(self):
|
||||||
|
true_values = ['1', 't', 'T', 'TRUE', 'True', 'true']
|
||||||
|
false_values = ['0', 'f', 'F', 'FALSE', 'False', 'false']
|
||||||
|
|
||||||
|
for value in true_values:
|
||||||
|
self.assertTrue(parse_bool(value))
|
||||||
|
for value in false_values:
|
||||||
|
self.assertFalse(parse_bool(value))
|
||||||
|
|
||||||
|
self.assertRaises(ValueError, parse_bool, 'tru')
|
||||||
|
self.assertRaises(ValueError, parse_bool, 'fals')
|
||||||
|
|
||||||
|
|
||||||
class TestTraverse(base.BaseUnitTest):
|
class TestTraverse(base.BaseUnitTest):
|
||||||
|
|
||||||
|
@ -302,3 +302,25 @@ def dict_update(target, patch, level=None):
|
|||||||
target.setdefault(k, {}).update(v)
|
target.setdefault(k, {}).update(v)
|
||||||
else:
|
else:
|
||||||
target[k] = v
|
target[k] = v
|
||||||
|
|
||||||
|
|
||||||
|
def parse_bool(value):
|
||||||
|
"""
|
||||||
|
Returns boolean value from string representation.
|
||||||
|
|
||||||
|
Function returns ``True`` for ``"1", "t", "true"`` values
|
||||||
|
and ``False`` for ``"0", "f", "false"``. String values are
|
||||||
|
case insensetive.
|
||||||
|
Otherwise raises ValueError.
|
||||||
|
|
||||||
|
:param str value: string representation of boolean value
|
||||||
|
:rtype: bool
|
||||||
|
:raises ValueError: if the value cannot be converted to bool
|
||||||
|
"""
|
||||||
|
value = value.lower()
|
||||||
|
if value in ('1', 't', 'true'):
|
||||||
|
return True
|
||||||
|
elif value in ('0', 'f', 'false'):
|
||||||
|
return False
|
||||||
|
raise ValueError('Invalid value: {0}'.format(value))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user