From b24b301e7a9a2d851195fc9350d94efec7c387d5 Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Thu, 27 Mar 2014 16:50:18 +0100 Subject: [PATCH] Pass and use the environment in validate_template Use the environment passed in validate_template to be able to validate template using custom resources. Change-Id: I85e23f26f5abbafe50073c3ac023933316af05ac Closes-Bug: #1298450 --- heat/api/openstack/v1/stacks.py | 3 +- heat/engine/service.py | 7 +++- heat/rpc/client.py | 6 ++- heat/tests/test_api_cfn_v1.py | 2 +- heat/tests/test_api_openstack_v1.py | 8 +++- heat/tests/test_rpc_client.py | 3 +- heat/tests/test_validate.py | 61 ++++++++++++++++++----------- 7 files changed, 59 insertions(+), 31 deletions(-) diff --git a/heat/api/openstack/v1/stacks.py b/heat/api/openstack/v1/stacks.py index 56d11e7d84..4a4aa1d573 100644 --- a/heat/api/openstack/v1/stacks.py +++ b/heat/api/openstack/v1/stacks.py @@ -346,7 +346,8 @@ class StackController(object): data = InstantiationData(body) result = self.rpc_client.validate_template(req.context, - data.template()) + data.template(), + data.environment()) if 'Error' in result: raise exc.HTTPBadRequest(result['Error']) diff --git a/heat/engine/service.py b/heat/engine/service.py index be597d9390..a3aa5bf1d2 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -581,13 +581,14 @@ class EngineService(service.Service): return dict(current_stack.identifier()) @request_context - def validate_template(self, cnxt, template): + def validate_template(self, cnxt, template, params=None): """ The validate_template method uses the stack parser to check the validity of a template. :param cnxt: RPC context. :param template: Template of stack you want to create. + :param params: Stack Input Params """ logger.info(_('validate_template')) if template is None: @@ -606,6 +607,8 @@ class EngineService(service.Service): if not tmpl_resources: return {'Error': 'At least one Resources member must be defined.'} + env = environment.Environment(params) + for res in tmpl_resources.values(): try: if not res.get('Type'): @@ -620,7 +623,7 @@ class EngineService(service.Service): 'Resources must contain Resource. ' 'Found a [%s] instead' % type_res} - ResourceClass = resource.get_class(res['Type']) + ResourceClass = env.get_class(res['Type']) if ResourceClass == resources.template_resource.TemplateResource: # we can't validate a TemplateResource unless we instantiate # it as we need to download the template and convert the diff --git a/heat/rpc/client.py b/heat/rpc/client.py index a724b098d4..13a095203b 100644 --- a/heat/rpc/client.py +++ b/heat/rpc/client.py @@ -153,16 +153,18 @@ class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy): files=files, args=args)) - def validate_template(self, ctxt, template): + def validate_template(self, ctxt, template, params=None): """ The validate_template method uses the stack parser to check the validity of a template. :param ctxt: RPC context. :param template: Template of stack you want to create. + :param params: Stack Input Params/Environment """ return self.call(ctxt, self.make_msg('validate_template', - template=template)) + template=template, + params=params)) def authenticated_to_backend(self, ctxt): """ diff --git a/heat/tests/test_api_cfn_v1.py b/heat/tests/test_api_cfn_v1.py index 1e8209ffc1..ad17482404 100644 --- a/heat/tests/test_api_cfn_v1.py +++ b/heat/tests/test_api_cfn_v1.py @@ -1146,7 +1146,7 @@ class CfnStackControllerTest(HeatTestCase): rpc.call(dummy_req.context, self.topic, {'namespace': None, 'method': 'validate_template', - 'args': {'template': json_template}, + 'args': {'template': json_template, 'params': None}, 'version': self.api_version}, None).AndReturn(response) self.m.ReplayAll() diff --git a/heat/tests/test_api_openstack_v1.py b/heat/tests/test_api_openstack_v1.py index c57deaf5cf..763beafb67 100644 --- a/heat/tests/test_api_openstack_v1.py +++ b/heat/tests/test_api_openstack_v1.py @@ -1442,7 +1442,9 @@ class StackControllerTest(ControllerTest, HeatTestCase): rpc.call(req.context, self.topic, {'namespace': None, 'method': 'validate_template', - 'args': {'template': template}, + 'args': {'template': template, + 'params': {'parameters': {}, + 'resource_registry': {}}}, 'version': self.api_version}, None).AndReturn(engine_response) self.m.ReplayAll() @@ -1464,7 +1466,9 @@ class StackControllerTest(ControllerTest, HeatTestCase): rpc.call(req.context, self.topic, {'namespace': None, 'method': 'validate_template', - 'args': {'template': template}, + 'args': {'template': template, + 'params': {'parameters': {}, + 'resource_registry': {}}}, 'version': self.api_version}, None).AndReturn({'Error': 'fubar'}) self.m.ReplayAll() diff --git a/heat/tests/test_rpc_client.py b/heat/tests/test_rpc_client.py index 12effa6d1b..67dc1c1ebd 100644 --- a/heat/tests/test_rpc_client.py +++ b/heat/tests/test_rpc_client.py @@ -142,7 +142,8 @@ class EngineRpcAPITestCase(testtools.TestCase): def test_validate_template(self): self._test_engine_api('validate_template', 'call', - template={u'Foo': u'bar'}) + template={u'Foo': u'bar'}, + params={u'Egg': u'spam'}) def test_list_resource_types(self): self._test_engine_api('list_resource_types', 'call', diff --git a/heat/tests/test_validate.py b/heat/tests/test_validate.py index 85e47b0053..1da5eaa25c 100644 --- a/heat/tests/test_validate.py +++ b/heat/tests/test_validate.py @@ -806,7 +806,24 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) + self.assertEqual('test.', res['Description']) + + def test_validate_with_environment(self): + test_template = test_template_ref % 'WikiDatabase' + test_template = test_template.replace('AWS::EC2::Instance', + 'My::Instance') + t = template_format.parse(test_template) + + self.m.StubOutWithMock(instances.Instance, 'nova') + instances.Instance.nova().AndReturn(self.fc) + self.m.StubOutWithMock(service.EngineListener, 'start') + service.EngineListener.start().AndReturn(None) + self.m.ReplayAll() + + engine = service.EngineService('a', 't') + params = {'resource_registry': {'My::Instance': 'AWS::EC2::Instance'}} + res = dict(engine.validate_template(None, t, params)) self.assertEqual('test.', res['Description']) def test_validate_hot_valid(self): @@ -825,7 +842,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertEqual('test.', res['Description']) def test_validate_ref_invalid(self): @@ -838,7 +855,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertNotEqual(res['Description'], 'Successfully validated') def test_validate_findinmap_valid(self): @@ -851,7 +868,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertEqual('test.', res['Description']) def test_validate_findinmap_invalid(self): @@ -864,7 +881,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertNotEqual(res['Description'], 'Successfully validated') def test_validate_parameters(self): @@ -877,7 +894,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) # Note: the assertion below does not expect a CFN dict of the parameter # but a dict of the parameters.Schema object. # For API CFN backward compatibility, formating to CFN is done in the @@ -899,7 +916,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) parameters = res['Parameters'] expected = {'KeyName': { @@ -919,7 +936,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) parameters = res['Parameters'] expected = {'KeyName': { @@ -939,7 +956,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) parameters = res['Parameters'] expected = {'KeyName': { @@ -959,7 +976,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertEqual({'Error': 'Unknown Property UnknownProperty'}, res) def test_invalid_resources(self): @@ -971,7 +988,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertEqual({'Error': 'Resources must contain Resource. ' 'Found a [string] instead'}, res) @@ -1035,7 +1052,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertEqual( {'Error': 'Property SourceDestCheck not implemented yet'}, res) @@ -1049,7 +1066,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertEqual({'Error': 'Invalid DeletionPolicy Destroy'}, res) def test_snapshot_deletion_policy(self): @@ -1061,7 +1078,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertEqual( {'Error': 'Snapshot DeletionPolicy not supported'}, res) @@ -1076,7 +1093,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, t)) + res = dict(engine.validate_template(None, t, {})) self.assertEqual({'Description': u'test.', 'Parameters': {}}, res) def test_validate_template_without_resources(self): @@ -1089,7 +1106,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, hot_tpl)) + res = dict(engine.validate_template(None, hot_tpl, {})) self.assertEqual({'Error': 'At least one Resources member ' 'must be defined.'}, res) @@ -1114,7 +1131,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, hot_tpl)) + res = dict(engine.validate_template(None, hot_tpl, {})) self.assertEqual({'Error': 'u\'"Type" is not a valid keyword ' 'inside a resource definition\''}, res) @@ -1139,7 +1156,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, hot_tpl)) + res = dict(engine.validate_template(None, hot_tpl, {})) self.assertEqual({'Error': 'u\'"Properties" is not a valid keyword ' 'inside a resource definition\''}, res) @@ -1164,7 +1181,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, hot_tpl)) + res = dict(engine.validate_template(None, hot_tpl, {})) self.assertEqual({'Error': 'u\'"Metadata" is not a valid keyword ' 'inside a resource definition\''}, res) @@ -1189,7 +1206,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, hot_tpl)) + res = dict(engine.validate_template(None, hot_tpl, {})) self.assertEqual({'Error': 'u\'"DependsOn" is not a valid keyword ' 'inside a resource definition\''}, res) @@ -1214,7 +1231,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, hot_tpl)) + res = dict(engine.validate_template(None, hot_tpl, {})) self.assertEqual({'Error': 'u\'"DeletionPolicy" is not a valid ' 'keyword inside a resource definition\''}, res) @@ -1240,7 +1257,7 @@ class validateTest(HeatTestCase): self.m.ReplayAll() engine = service.EngineService('a', 't') - res = dict(engine.validate_template(None, hot_tpl)) + res = dict(engine.validate_template(None, hot_tpl, {})) self.assertEqual({'Error': 'u\'"UpdatePolicy" is not a valid ' 'keyword inside a resource definition\''}, res)