From bf9df0e689542bd29a353fc1e42ea620e8175dbd Mon Sep 17 00:00:00 2001 From: Steve McLellan Date: Tue, 10 Jun 2014 11:11:13 -0500 Subject: [PATCH] Add heat_template_version to network fragments Without heat_template_version heat assumes CFN format templates. Post Icehouse, HOT has added a lot of functionality not in CFN, and template version appears to be mandatory in recent builds of heat - without it, heat raises a 400 error: "The template version is invalid: Template version was not provided". This patch changes engine/heat_stack to add heat_template_version if it's not already present in a template at push time, and to reject heat_template_version if it's in a dictionary passed to updateTemplate that does not match the expected version. Change-Id: I5f30100182d282d65710097e0859918705b1a9c0 Closes-Bug: #1328593 --- .../Classes/SecurityGroupManager.yaml | 1 - .../io.murano/Classes/resources/Instance.yaml | 2 - murano/engine/system/heat_stack.py | 15 +++ murano/tests/test_heat_stack.py | 96 +++++++++++++++++++ 4 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 murano/tests/test_heat_stack.py diff --git a/meta/io.murano/Classes/SecurityGroupManager.yaml b/meta/io.murano/Classes/SecurityGroupManager.yaml index 9297e682..7bbe82b2 100644 --- a/meta/io.murano/Classes/SecurityGroupManager.yaml +++ b/meta/io.murano/Classes/SecurityGroupManager.yaml @@ -36,7 +36,6 @@ Methods: - $stack: $.environment.stack - $template: - heat_template_version: '2013-05-23' resources: $groupName: type: 'OS::Neutron::SecurityGroup' diff --git a/meta/io.murano/Classes/resources/Instance.yaml b/meta/io.murano/Classes/resources/Instance.yaml index 346c231d..489c31b6 100644 --- a/meta/io.murano/Classes/resources/Instance.yaml +++ b/meta/io.murano/Classes/resources/Instance.yaml @@ -72,7 +72,6 @@ Methods: - $userData: $.prepareUserData() - $template: - heat_template_version: '2013-05-23' resources: $.name: type: 'OS::Nova::Server' @@ -142,7 +141,6 @@ Methods: - $netRef: $net.getNetworkReference() - $subnetRef: $net.getSubnetReference() - $template: - heat_template_version: '2013-05-23' resources: $portname: type: 'OS::Neutron::Port' diff --git a/murano/engine/system/heat_stack.py b/murano/engine/system/heat_stack.py index 7d435dc0..c55bc671 100644 --- a/murano/engine/system/heat_stack.py +++ b/murano/engine/system/heat_stack.py @@ -26,6 +26,12 @@ import murano.openstack.common.log as logging LOG = logging.getLogger(__name__) +HEAT_TEMPLATE_VERSION = '2013-05-23' + + +class HeatStackError(Exception): + pass + @murano_class.classname('io.murano.system.HeatStack') class HeatStack(murano_object.MuranoObject): @@ -107,6 +113,12 @@ class HeatStack(murano_object.MuranoObject): self._applied = False def updateTemplate(self, template): + template_version = template.get('heat_template_version', + HEAT_TEMPLATE_VERSION) + if template_version != HEAT_TEMPLATE_VERSION: + err_msg = ("Currently only heat_template_version %s " + "is supported." % HEAT_TEMPLATE_VERSION) + raise HeatStackError(err_msg) self.current() self._template = helpers.merge_dicts(self._template, template) self._applied = False @@ -169,6 +181,9 @@ class HeatStack(murano_object.MuranoObject): if self._applied or self._template is None: return + if 'heat_template_version' not in self._template: + self._template['heat_template_version'] = HEAT_TEMPLATE_VERSION + LOG.info('Pushing: {0}'.format(self._template)) current_status = self._get_status() diff --git a/murano/tests/test_heat_stack.py b/murano/tests/test_heat_stack.py new file mode 100644 index 00000000..96541ba0 --- /dev/null +++ b/murano/tests/test_heat_stack.py @@ -0,0 +1,96 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock +from murano.tests import base + +from murano.dsl import murano_object +from murano.engine.system import heat_stack + + +MOD_NAME = 'murano.engine.system.heat_stack' + + +class TestHeatStack(base.MuranoTestCase): + def setUp(self): + super(TestHeatStack, self).setUp() + self.mock_murano_obj = mock.Mock(spec=murano_object.MuranoObject) + self.mock_murano_obj.name = 'TestObj' + self.mock_murano_obj.parents = [] + + @mock.patch('heatclient.client.Client') + def test_push_adds_version(self, mock_heat_client): + """Assert that if heat_template_version is omitted, it's added""" + # Note that the 'with x as y, a as b:' syntax was introduced in + # python 2.7, and contextlib.nested was deprecated in py2.7 + with mock.patch(MOD_NAME + '.HeatStack._get_status') as status_get: + with mock.patch(MOD_NAME + '.HeatStack._wait_state') as wait_st: + + status_get.return_value = 'NOT_FOUND' + wait_st.return_value = {} + + hs = heat_stack.HeatStack(self.mock_murano_obj, + None, None, None) + hs._heat_client = mock_heat_client + hs._name = 'test-stack' + hs._template = {'resources': {'test': 1}} + hs._parameters = {} + hs._applied = False + hs.push() + + expected_template = { + 'heat_template_version': '2013-05-23', + 'resources': {'test': 1} + } + mock_heat_client.stacks.create.assert_called_with( + stack_name='test-stack', + disable_rollback=False, + parameters={}, + template=expected_template + ) + self.assertTrue(hs._applied) + + def test_update_wrong_template_version(self): + """Template version other than expected should cause error""" + + hs = heat_stack.HeatStack(self.mock_murano_obj, + None, None, None) + hs._name = 'test-stack' + hs._template = {'resources': {'test': 1}} + hs.type.properties = {} + + erroring_template = { + 'heat_template_version': 'something else' + } + + with mock.patch(MOD_NAME + '.HeatStack.current') as current: + current.return_value = {} + + e = self.assertRaises(heat_stack.HeatStackError, + hs.updateTemplate, + erroring_template) + err_msg = "Currently only heat_template_version 2013-05-23 "\ + "is supported." + self.assertEqual(err_msg, str(e)) + + # Check it's ok without a version + hs.updateTemplate({}) + expected = {'resources': {'test': 1}} + self.assertEqual(expected, hs._template) + + # .. or with a good version + hs.updateTemplate({'heat_template_version': '2013-05-23'}) + expected['heat_template_version'] = '2013-05-23' + self.assertEqual(expected, hs._template)