From dbc66c1efdd0504dad47f03203794039287d4a9f Mon Sep 17 00:00:00 2001 From: huangtianhua Date: Fri, 17 Jan 2014 16:00:33 +0800 Subject: [PATCH] Fixes template not using the JSON or YAML format An internal unexpected exception is raised while validating a template which only has string content. We should handle the case and raise the correct error message such as "invalid format" and to tell the template is not using the JSON or YAML format. And modify incorrect version name and version date in this patch. And change "template" from global variale to class varialbe in test_api_cfn_v1.py. (missed in another patch which this patch depends on: Change Idc2ba2b9 by misoperation) Change-Id: I46ee8c48e3ddae71be061cfda0c029d86d132684 Closes-Bug: #1267379 --- heat/common/template_format.py | 7 ++-- heat/tests/test_api_cfn_v1.py | 51 +++++++++++++++-------------- heat/tests/test_api_openstack_v1.py | 4 +-- heat/tests/test_template_format.py | 37 +++++++++++++++++++-- 4 files changed, 67 insertions(+), 32 deletions(-) diff --git a/heat/common/template_format.py b/heat/common/template_format.py index 843c6486ca..b10ed61aa4 100644 --- a/heat/common/template_format.py +++ b/heat/common/template_format.py @@ -59,9 +59,9 @@ def parse(tmpl_str): if len(tmpl_str) > cfg.CONF.max_template_size: msg = _('Template exceeds maximum allowed size.') raise exception.RequestLimitExceeded(message=msg) - if tmpl_str.startswith('{'): + try: tpl = json.loads(tmpl_str) - else: + except ValueError: try: tpl = yaml.load(tmpl_str, Loader=yaml_loader) except yaml.YAMLError as yea: @@ -69,6 +69,9 @@ def parse(tmpl_str): else: if tpl is None: tpl = {} + if not isinstance(tpl, dict): + raise ValueError(_('The template is not a JSON object ' + 'or YAML mapping.')) # Looking for supported version keys in the loaded template if not ('HeatTemplateFormatVersion' in tpl or 'heat_template_version' in tpl diff --git a/heat/tests/test_api_cfn_v1.py b/heat/tests/test_api_cfn_v1.py index 1362b49e10..cc71ec590d 100644 --- a/heat/tests/test_api_cfn_v1.py +++ b/heat/tests/test_api_cfn_v1.py @@ -30,7 +30,6 @@ from heat.tests.common import HeatTestCase from heat.tests import utils policy_path = os.path.dirname(os.path.realpath(__file__)) + "/policy/" -template = {u'AWSTemplateFormatVersion': u'2010-09-09', u'Foo': u'bar'} class CfnStackControllerTest(HeatTestCase): @@ -51,6 +50,8 @@ class CfnStackControllerTest(HeatTestCase): cfg.CONF.set_default('host', 'host') self.topic = rpc_api.ENGINE_TOPIC self.api_version = '1.0' + self.template = {u'AWSTemplateFormatVersion': u'2010-09-09', + u'Foo': u'bar'} # Create WSGI controller instance class DummyConfig(): @@ -465,7 +466,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -488,7 +489,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -511,7 +512,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create_rollback(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -534,7 +535,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -557,7 +558,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create_onfailure_true(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -580,7 +581,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -603,7 +604,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create_onfailure_false_delete(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -626,7 +627,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -649,7 +650,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create_onfailure_false_rollback(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -672,7 +673,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -695,7 +696,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create_onfailure_err(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -734,7 +735,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create_err_rpcerr(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -756,7 +757,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -769,7 +770,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -782,7 +783,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -803,7 +804,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create_err_exists(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -822,7 +823,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -838,7 +839,7 @@ class CfnStackControllerTest(HeatTestCase): def test_create_err_engine(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'CreateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'TimeoutInMinutes': 30, @@ -856,7 +857,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'create_stack', 'args': {'stack_name': stack_name, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -873,7 +874,7 @@ class CfnStackControllerTest(HeatTestCase): def test_update(self): # Format a dummy request stack_name = "wordpress" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'UpdateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'Parameters.member.1.ParameterKey': 'InstanceType', @@ -897,7 +898,7 @@ class CfnStackControllerTest(HeatTestCase): {'namespace': None, 'method': 'update_stack', 'args': {'stack_identity': identity, - 'template': template, + 'template': self.template, 'params': engine_parms, 'files': {}, 'args': engine_args}, @@ -920,7 +921,7 @@ class CfnStackControllerTest(HeatTestCase): def test_update_bad_name(self): stack_name = "wibble" - json_template = json.dumps(template) + json_template = json.dumps(self.template) params = {'Action': 'UpdateStack', 'StackName': stack_name, 'TemplateBody': '%s' % json_template, 'Parameters.member.1.ParameterKey': 'InstanceType', @@ -956,7 +957,7 @@ class CfnStackControllerTest(HeatTestCase): self._stub_enforce(dummy_req, 'GetTemplate') # Stub out the RPC call to the engine with a pre-canned response - engine_resp = template + engine_resp = self.template self.m.StubOutWithMock(rpc, 'call') rpc.call(dummy_req.context, self.topic, @@ -976,7 +977,7 @@ class CfnStackControllerTest(HeatTestCase): expected = {'GetTemplateResponse': {'GetTemplateResult': - {'TemplateBody': template}}} + {'TemplateBody': self.template}}} self.assertEqual(response, expected) diff --git a/heat/tests/test_api_openstack_v1.py b/heat/tests/test_api_openstack_v1.py index b8d8e3c372..c4fd072ebf 100644 --- a/heat/tests/test_api_openstack_v1.py +++ b/heat/tests/test_api_openstack_v1.py @@ -81,7 +81,7 @@ class InstantiationDataTest(HeatTestCase): def test_format_parse_invalid_message(self): # make sure the parser error gets through to the caller. bad_temp = ''' -heat_template_version: '2012-12-12' +heat_template_version: '2013-05-23' parameters: KeyName: type: string @@ -110,7 +110,7 @@ parameters: self.assertEqual(data.template(), template) def test_template_string_json(self): - template = '{"heat_template_version": "2012-12-12",' \ + template = '{"heat_template_version": "2013-05-23",' \ '"foo": "bar", "blarg": "wibble"}' body = {'template': template} data = stacks.InstantiationData(body) diff --git a/heat/tests/test_template_format.py b/heat/tests/test_template_format.py index 619364ea56..48595dc87e 100644 --- a/heat/tests/test_template_format.py +++ b/heat/tests/test_template_format.py @@ -79,8 +79,14 @@ class JsonToYamlTest(HeatTestCase): class YamlMinimalTest(HeatTestCase): + def _parse_template(self, tmpl_str, msg_str): + parse_ex = self.assertRaises(ValueError, + template_format.parse, + tmpl_str) + self.assertIn(msg_str, str(parse_ex)) + def test_long_yaml(self): - template = {'HeatTemplateVersion': '2012-12-12'} + template = {'HeatTemplateFormatVersion': '2012-12-12'} config.cfg.CONF.set_override('max_template_size', 1024) template['Resources'] = ['a'] * (config.cfg.CONF.max_template_size / 3) limit = config.cfg.CONF.max_template_size @@ -93,13 +99,38 @@ class YamlMinimalTest(HeatTestCase): def test_parse_no_version_format(self): yaml = '' - self.assertRaises(ValueError, template_format.parse, yaml) + self._parse_template(yaml, 'Template format version not found') yaml2 = '''Parameters: {} Mappings: {} Resources: {} Outputs: {} ''' - self.assertRaises(ValueError, template_format.parse, yaml2) + self._parse_template(yaml2, 'Template format version not found') + + def test_parse_string_template(self): + tmpl_str = 'just string' + msg = 'The template is not a JSON object or YAML mapping.' + self._parse_template(tmpl_str, msg) + + def test_parse_invalid_yaml_and_json_template(self): + tmpl_str = '{test' + msg = 'line 1, column 1' + self._parse_template(tmpl_str, msg) + + def test_parse_json_document(self): + tmpl_str = '["foo" , "bar"]' + msg = 'The template is not a JSON object or YAML mapping.' + self._parse_template(tmpl_str, msg) + + def test_parse_empty_json_template(self): + tmpl_str = '{}' + msg = 'Template format version not found' + self._parse_template(tmpl_str, msg) + + def test_parse_yaml_template(self): + tmpl_str = 'heat_template_version: 2013-05-23' + expected = {'heat_template_version': '2013-05-23'} + self.assertEqual(expected, template_format.parse(tmpl_str)) class YamlParseExceptions(HeatTestCase):