From: JUN JIE NAN Date: Tue, 23 Jul 2013 00:47:38 +0000 (+0800) Subject: HOT parameter validation model translation X-Git-Tag: 2014.1~329^2 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=929c9edd5c46f2c9cb7c48bc8b2b9b0ee64df594;p=openstack-build%2Fheat-build.git HOT parameter validation model translation Translate hot parameters into heat parameter model. Change-Id: I6733be7c13e2e9fdbed27d5c21044fb5f457ed17 Implements: blueprint hot-parameters --- diff --git a/heat/engine/hot.py b/heat/engine/hot.py index 50374690..5dbfa70d 100644 --- a/heat/engine/hot.py +++ b/heat/engine/hot.py @@ -81,35 +81,64 @@ class HOTemplate(template.Template): return default + @staticmethod + def _snake_to_camel(name): + tokens = [] + if name: + tokens = name.split('_') + for i in xrange(len(tokens)): + tokens[i] = tokens[i].capitalize() + return "".join(tokens) + + def _translate_constraints(self, constraints): + param = {} + + def add_constraint(key, val, desc): + cons = param.get(key, []) + cons.append((val, desc)) + param[key] = cons + + def add_min_max(key, val, desc): + minv = val.get('min') + maxv = val.get('max') + if minv: + add_constraint('Min%s' % key, minv, desc) + if maxv: + add_constraint('Max%s' % key, maxv, desc) + + for constraint in constraints: + desc = constraint.get('description') + for key, val in constraint.iteritems(): + key = self._snake_to_camel(key) + if key == 'Description': + continue + elif key == 'Range': + add_min_max('Value', val, desc) + elif key == 'Length': + add_min_max(key, val, desc) + else: + add_constraint(key, val, desc) + + return param + def _translate_parameters(self, parameters): """Get the parameters of the template translated into CFN format.""" - HOT_TO_CFN_ATTRS = {'type': 'Type', - 'default': 'Default', - 'description': 'Description'} - - HOT_TO_CFN_TYPES = {'string': 'String'} - - cfn_params = {} - - for param_name, attrs in parameters.iteritems(): - cfn_param = {} - - for attr, attr_value in attrs.iteritems(): - cfn_attr = self._translate(attr, HOT_TO_CFN_ATTRS, attr) - - if attr == 'type': - # try to translate type; if no translation found, keep - # original value and let engine throw an error for an - # unsupported type - attr_value = self._translate(attr_value, HOT_TO_CFN_TYPES, - attr_value) - - cfn_param[cfn_attr] = attr_value - - if len(cfn_param) > 0: - cfn_params[param_name] = cfn_param - - return cfn_params + params = {} + for name, attrs in parameters.iteritems(): + param = {} + for key, val in attrs.iteritems(): + key = self._snake_to_camel(key) + if key == 'Type': + val = self._snake_to_camel(val) + elif key == 'Constraints': + param.update(self._translate_constraints(val)) + continue + elif key == 'Hidden': + key = 'NoEcho' + param[key] = val + if len(param) > 0: + params[name] = param + return params def _translate_resources(self, resources): """Get the resources of the template translated into CFN format.""" diff --git a/heat/tests/test_hot.py b/heat/tests/test_hot.py index d91ecef2..f7f759e1 100644 --- a/heat/tests/test_hot.py +++ b/heat/tests/test_hot.py @@ -86,11 +86,133 @@ class HOTemplateTest(HeatTestCase): ''') expected = {'param1': {'Description': 'foo', - 'Type': 'unsupported_type'}} + 'Type': 'UnsupportedType'}} tmpl = parser.Template(hot_tpl) self.assertEqual(tmpl[hot.PARAMETERS], expected) + def test_translate_parameters_length_range(self): + hot_tpl = template_format.parse(''' + heat_template_version: 2013-05-23 + parameters: + wait_time: + description: application wait time + type: number + default: 150 + constraints: + - range: { min: 120, max: 600} + description: min value 120 seconds, max value 600 seconds + key_name: + description: Name of an existing EC2 KeyPair + type: string + default: heat_key + constraints: + - length: {min: 1, max: 32} + description: length should be between 1 and 32 + ''') + + expected = { + 'wait_time': { + 'Description': 'application wait time', + 'Type': 'Number', + 'Default': 150, + 'MaxValue': [ + (600, 'min value 120 seconds, max value 600 seconds')], + 'MinValue': [ + (120, 'min value 120 seconds, max value 600 seconds')] + }, + 'key_name': { + 'Description': 'Name of an existing EC2 KeyPair', + 'Type': 'String', + 'Default': 'heat_key', + 'MaxLength': [(32, u'length should be between 1 and 32')], + 'MinLength': [(1, u'length should be between 1 and 32')] + }} + + tmpl = parser.Template(hot_tpl) + self.assertEqual(expected, tmpl[hot.PARAMETERS]) + + def test_translate_parameters_allowed_values(self): + hot_tpl = template_format.parse(''' + heat_template_version: 2013-05-23 + parameters: + instance_type: + description: instance type + type: string + default: m1.small + constraints: + - allowed_values: ["m1.tiny", + "m1.small", + "m1.medium", "m1.large", "m1.xlarge"] + description: must be a valid EC2 instance type. + ''') + expected = { + 'instance_type': { + 'Description': 'instance type', + 'Type': 'String', + 'Default': 'm1.small', + 'AllowedValues': [(["m1.tiny", + "m1.small", + "m1.medium", + "m1.large", + "m1.xlarge"], + 'must be a valid EC2 instance type.')]}} + + tmpl = parser.Template(hot_tpl) + self.assertEqual(expected, tmpl[hot.PARAMETERS]) + + def test_translate_parameters_allowed_patterns(self): + hot_tpl = template_format.parse(''' + heat_template_version: 2013-05-23 + parameters: + db_name: + description: The WordPress database name + type: string + default: wordpress + constraints: + - length: { min: 1, max: 64 } + description: string lenght should between 1 and 64 + - allowed_pattern: "[a-zA-Z]+" + description: Value must consist of characters only + - allowed_pattern: "[a-z]+[a-zA-Z]*" + description: Value must start with a lowercase character + ''') + expected = { + 'db_name': { + 'Description': 'The WordPress database name', + 'Type': 'String', + 'Default': 'wordpress', + 'MinLength': [(1, 'string lenght should between 1 and 64')], + 'MaxLength': [(64, 'string lenght should between 1 and 64')], + 'AllowedPattern': [ + ('[a-zA-Z]+', + 'Value must consist of characters only'), + ('[a-z]+[a-zA-Z]*', + 'Value must start with a lowercase character')]}} + tmpl = parser.Template(hot_tpl) + self.assertEqual(expected, tmpl[hot.PARAMETERS]) + + def test_translate_parameters_hidden(self): + hot_tpl = template_format.parse(''' + heat_template_version: 2013-05-23 + parameters: + user_roles: + description: User roles + type: comma_delimited_list + default: guest,newhire + hidden: TRUE + ''') + expected = { + 'user_roles': { + 'Description': 'User roles', + 'Type': 'CommaDelimitedList', + 'Default': 'guest,newhire', + 'NoEcho': True + }} + + tmpl = parser.Template(hot_tpl) + self.assertEqual(expected, tmpl[hot.PARAMETERS]) + def test_translate_resources(self): """Test translation of resources into internal engine format."""