From b7fa93a03cc057b8a4090fbfd9cb23f95aef804f Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Thu, 6 Feb 2014 15:59:15 +0100 Subject: [PATCH] Parse stack_adopt_data The patch parses the argument passed to stack_create, and validates that it's a dictionary, preventing engine failures in the future. Closes-Bug: #1277106 Change-Id: I88cbf933ddac5776d5ffc0151bc7a0b296048777 --- heat/common/template_format.py | 25 +++++++++++++++---------- heat/engine/api.py | 12 +++++++++--- heat/tests/test_engine_api_utils.py | 10 +++++++++- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/heat/common/template_format.py b/heat/common/template_format.py index c26fb73677..4831fbddce 100644 --- a/heat/common/template_format.py +++ b/heat/common/template_format.py @@ -51,16 +51,7 @@ yaml_loader.add_constructor(u'tag:yaml.org,2002:timestamp', _construct_yaml_str) -def parse(tmpl_str): - ''' - Takes a string and returns a dict containing the parsed structure. - This includes determination of whether the string is using the - JSON or YAML format. - ''' - if len(tmpl_str) > cfg.CONF.max_template_size: - msg = (_('Template exceeds maximum allowed size (%s bytes)') % - cfg.CONF.max_template_size) - raise exception.RequestLimitExceeded(message=msg) +def simple_parse(tmpl_str): try: tpl = json.loads(tmpl_str) except ValueError: @@ -71,6 +62,20 @@ def parse(tmpl_str): else: if tpl is None: tpl = {} + return tpl + + +def parse(tmpl_str): + ''' + Takes a string and returns a dict containing the parsed structure. + This includes determination of whether the string is using the + JSON or YAML format. + ''' + if len(tmpl_str) > cfg.CONF.max_template_size: + msg = (_('Template exceeds maximum allowed size (%s bytes)') % + cfg.CONF.max_template_size) + raise exception.RequestLimitExceeded(message=msg) + tpl = simple_parse(tmpl_str) if not isinstance(tpl, dict): raise ValueError(_('The template is not a JSON object ' 'or YAML mapping.')) diff --git a/heat/engine/api.py b/heat/engine/api.py index 6db19022dd..dfc598b191 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from heat.common import template_format from heat.rpc import api from heat.openstack.common import timeutils from heat.engine import constraints as constr @@ -49,9 +50,14 @@ def extract_args(params): dict(name=api.PARAM_DISABLE_ROLLBACK, value=disable_rollback)) - if api.PARAM_ADOPT_STACK_DATA in params: - kwargs[api.PARAM_ADOPT_STACK_DATA] = params.get( - api.PARAM_ADOPT_STACK_DATA) + adopt_data = params.get(api.PARAM_ADOPT_STACK_DATA) + if adopt_data: + adopt_data = template_format.simple_parse(adopt_data) + if not isinstance(adopt_data, dict): + raise ValueError( + _('Unexpected adopt data "%s". Adopt data must be a dict.') + % adopt_data) + kwargs[api.PARAM_ADOPT_STACK_DATA] = adopt_data return kwargs diff --git a/heat/tests/test_engine_api_utils.py b/heat/tests/test_engine_api_utils.py index 41bef3ddd0..5ceb40617c 100644 --- a/heat/tests/test_engine_api_utils.py +++ b/heat/tests/test_engine_api_utils.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import mock import uuid @@ -55,10 +56,17 @@ class EngineApiTest(HeatTestCase): self.assertNotIn('timeout_mins', args) def test_adopt_stack_data_extract_present(self): - p = {'adopt_stack_data': {'Resources': {}}} + p = {'adopt_stack_data': json.dumps({'Resources': {}})} args = api.extract_args(p) self.assertTrue(args.get('adopt_stack_data')) + def test_invalid_adopt_stack_data(self): + p = {'adopt_stack_data': json.dumps("foo")} + error = self.assertRaises(ValueError, api.extract_args, p) + self.assertEqual( + 'Unexpected adopt data "foo". Adopt data must be a dict.', + str(error)) + def test_adopt_stack_data_extract_not_present(self): args = api.extract_args({}) self.assertNotIn('adopt_stack_data', args)