Enable conversion for composite attribute items

Bug 1207881

Enable 'dict validators' to convert composite attributes' items
using a 'convert_to' specification in a way similar to first-level
API attributes.
This is needed in order to ensure boolean sub-attributes are
properly handled.

Change-Id: I7f466befaa88112cf7e9b77d854ac292b2af638f
This commit is contained in:
Salvatore Orlando
2013-08-02 11:47:09 -07:00
parent 256f9743a3
commit b8b28bd679
3 changed files with 46 additions and 18 deletions

View File

@@ -320,6 +320,29 @@ def _validate_uuid_list(data, valid_values=None):
return msg return msg
def _validate_dict_item(key, key_validator, data):
# Find conversion function, if any, and apply it
conv_func = key_validator.get('convert_to')
if conv_func:
data[key] = conv_func(data.get(key))
# Find validator function
# TODO(salv-orlando): Structure of dict attributes should be improved
# to avoid iterating over items
val_func = val_params = None
for (k, v) in key_validator.iteritems():
if k.startswith('type:'):
# ask forgiveness, not permission
try:
val_func = validators[k]
except KeyError:
return _("Validator '%s' does not exist.") % k
val_params = v
break
# Process validation
if val_func:
return val_func(data.get(key), val_params)
def _validate_dict(data, key_specs=None): def _validate_dict(data, key_specs=None):
if not isinstance(data, dict): if not isinstance(data, dict):
msg = _("'%s' is not a dictionary") % data msg = _("'%s' is not a dictionary") % data
@@ -339,25 +362,14 @@ def _validate_dict(data, key_specs=None):
LOG.debug(msg) LOG.debug(msg)
return msg return msg
# Perform validation of all values according to the specifications. # Perform validation and conversion of all values
# according to the specifications.
for key, key_validator in [(k, v) for k, v in key_specs.iteritems() for key, key_validator in [(k, v) for k, v in key_specs.iteritems()
if k in data]: if k in data]:
msg = _validate_dict_item(key, key_validator, data)
for val_name in [n for n in key_validator.iterkeys() if msg:
if n.startswith('type:')]: LOG.debug(msg)
# Check whether specified validator exists. return msg
if val_name not in validators:
msg = _("Validator '%s' does not exist.") % val_name
LOG.debug(msg)
return msg
val_func = validators[val_name]
val_params = key_validator[val_name]
msg = val_func(data.get(key), val_params)
if msg:
LOG.debug(msg)
return msg
def _validate_dict_or_none(data, key_specs=None): def _validate_dict_or_none(data, key_specs=None):

View File

@@ -19,6 +19,7 @@
# #
from neutron.api import extensions from neutron.api import extensions
from neutron.api.v2 import attributes as attrs
from neutron.common import exceptions as qexception from neutron.common import exceptions as qexception
from neutron.extensions import l3 from neutron.extensions import l3
@@ -37,7 +38,8 @@ EXTENDED_ATTRIBUTES_2_0 = {
'validate': 'validate':
{'type:dict_or_nodata': {'type:dict_or_nodata':
{'network_id': {'type:uuid': None, 'required': True}, {'network_id': {'type:uuid': None, 'required': True},
'enable_snat': {'type:boolean': None, 'required': False}} 'enable_snat': {'type:boolean': None, 'required': False,
'convert_to': attrs.convert_to_boolean}}
}}}} }}}}

View File

@@ -537,6 +537,20 @@ class TestAttributes(base.BaseTestCase):
msg = attributes._validate_dict(dictionary, constraints) msg = attributes._validate_dict(dictionary, constraints)
self.assertIsNotNone(msg) self.assertIsNotNone(msg)
def test_validate_dict_convert_boolean(self):
dictionary, constraints = self._construct_dict_and_constraints()
constraints['key_bool'] = {
'type:boolean': None,
'required': False,
'convert_to': attributes.convert_to_boolean}
dictionary['key_bool'] = 'true'
msg = attributes._validate_dict(dictionary, constraints)
self.assertIsNone(msg)
# Explicitly comparing with literal 'True' as assertTrue
# succeeds also for 'true'
self.assertIs(True, dictionary['key_bool'])
def test_subdictionary(self): def test_subdictionary(self):
dictionary, constraints = self._construct_dict_and_constraints() dictionary, constraints = self._construct_dict_and_constraints()