9817ed77ef
Allow tagging of stacks with simple string tags. blueprint stack-tags Change-Id: I65e1e8e87515595edae332c2ff7e0e82ded409ce
1538 lines
69 KiB
Python
1538 lines
69 KiB
Python
#
|
|
# 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 copy
|
|
|
|
import mock
|
|
|
|
from heat.common import template_format
|
|
from heat.engine import environment
|
|
from heat.engine import resource
|
|
from heat.engine import scheduler
|
|
from heat.engine import stack
|
|
from heat.engine import template
|
|
from heat.tests import common
|
|
from heat.tests import generic_resource as generic_rsrc
|
|
from heat.tests import utils
|
|
|
|
empty_template = template_format.parse('''{
|
|
"HeatTemplateFormatVersion" : "2012-12-12",
|
|
}''')
|
|
|
|
|
|
class StackUpdateTest(common.HeatTestCase):
|
|
def setUp(self):
|
|
super(StackUpdateTest, self).setUp()
|
|
|
|
self.tmpl = template.Template(copy.deepcopy(empty_template))
|
|
self.ctx = utils.dummy_context()
|
|
|
|
resource._register_class('GenericResourceType',
|
|
generic_rsrc.GenericResource)
|
|
resource._register_class('ResourceWithPropsType',
|
|
generic_rsrc.ResourceWithProps)
|
|
resource._register_class('ResWithComplexPropsAndAttrs',
|
|
generic_rsrc.ResWithComplexPropsAndAttrs)
|
|
|
|
def test_update_add(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'GenericResourceType'},
|
|
'BResource': {'Type': 'GenericResourceType'}}}
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertIn('BResource', self.stack)
|
|
|
|
def test_update_remove(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'GenericResourceType'},
|
|
'BResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertNotIn('BResource', self.stack)
|
|
|
|
def test_update_different_type(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('GenericResourceType',
|
|
self.stack['AResource'].type())
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('ResourceWithPropsType',
|
|
self.stack['AResource'].type())
|
|
|
|
def test_update_description(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Description': 'ATemplate',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Description': 'BTemplate',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('BTemplate',
|
|
self.stack.t[self.stack.t.DESCRIPTION])
|
|
|
|
def test_update_timeout(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Description': 'ATemplate',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl), timeout_mins=60)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Description': 'ATemplate',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2), timeout_mins=30)
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual(30, self.stack.timeout_mins)
|
|
|
|
def test_update_disable_rollback(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Description': 'ATemplate',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=False)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Description': 'ATemplate',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=True)
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual(True, self.stack.disable_rollback)
|
|
|
|
def test_update_tags(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Description': 'ATemplate',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
tags=['tag1', 'tag2'])
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual(['tag1', 'tag2'], self.stack.tags)
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl),
|
|
tags=['tag3', 'tag4'])
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual(['tag3', 'tag4'], self.stack.tags)
|
|
|
|
def test_update_tags_remove_all(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Description': 'ATemplate',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
tags=['tag1', 'tag2'])
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual(['tag1', 'tag2'], self.stack.tags)
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual(None, self.stack.tags)
|
|
|
|
def test_update_modify_ok_replace(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'xyz'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
|
|
|
|
loaded_stack = stack.Stack.load(self.ctx, self.stack.id)
|
|
stored_props = loaded_stack['AResource']._stored_properties_data
|
|
self.assertEqual({'Foo': 'xyz'}, stored_props)
|
|
|
|
def test_update_modify_ok_replace_int(self):
|
|
# create
|
|
# ========
|
|
tmpl = {'heat_template_version': '2013-05-23',
|
|
'resources': {'AResource': {
|
|
'type': 'ResWithComplexPropsAndAttrs',
|
|
'properties': {'an_int': 1}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
stack_id = self.stack.id
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
value1 = 2
|
|
prop_diff1 = {'an_int': value1}
|
|
value2 = 1
|
|
prop_diff2 = {'an_int': value2}
|
|
|
|
mock_upd = self.patchobject(generic_rsrc.ResWithComplexPropsAndAttrs,
|
|
'handle_update')
|
|
|
|
# update 1
|
|
# ==========
|
|
|
|
self.stack = stack.Stack.load(self.ctx, stack_id=stack_id)
|
|
tmpl2 = {'heat_template_version': '2013-05-23',
|
|
'resources': {'AResource': {
|
|
'type': 'ResWithComplexPropsAndAttrs',
|
|
'properties': {'an_int': value1}}}}
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
mock_upd.assert_called_once_with(mock.ANY, mock.ANY, prop_diff1)
|
|
|
|
# update 2
|
|
# ==========
|
|
# reload the previous stack
|
|
self.stack = stack.Stack.load(self.ctx, stack_id=stack_id)
|
|
tmpl3 = {'heat_template_version': '2013-05-23',
|
|
'resources': {'AResource': {
|
|
'type': 'ResWithComplexPropsAndAttrs',
|
|
'properties': {'an_int': value2}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl3))
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
mock_upd.assert_called_with(mock.ANY, mock.ANY, prop_diff2)
|
|
|
|
def test_update_modify_param_ok_replace(self):
|
|
tmpl = {
|
|
'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Parameters': {
|
|
'foo': {'Type': 'String'}
|
|
},
|
|
'Resources': {
|
|
'AResource': {
|
|
'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': {'Ref': 'foo'}}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.stack = stack.Stack(
|
|
self.ctx, 'update_test_stack',
|
|
template.Template(
|
|
tmpl, env=environment.Environment({'foo': 'abc'})))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
env2 = environment.Environment({'foo': 'xyz'})
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl, env=env2))
|
|
|
|
def check_and_raise(*args):
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
raise resource.UpdateReplace
|
|
|
|
mock_upd = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'update_template_diff',
|
|
side_effect=check_and_raise)
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
|
|
mock_upd.assert_called_once_with(
|
|
{'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'xyz'}},
|
|
{'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}})
|
|
|
|
def test_update_modify_update_failed(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=True)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
res = self.stack['AResource']
|
|
res.update_allowed_properties = ('Foo',)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'xyz'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
mock_upd = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_update',
|
|
side_effect=Exception("Foo"))
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
mock_upd.assert_called_once_with(
|
|
tmpl2['Resources']['AResource'],
|
|
{'Properties': {'Foo': 'xyz'}},
|
|
{'Foo': 'xyz'})
|
|
|
|
def test_update_modify_replace_failed_delete(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=True)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'xyz'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
# make the update fail deleting the existing resource
|
|
mock_del = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_delete',
|
|
side_effect=Exception)
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
mock_del.assert_called_once_with()
|
|
# Unset here so destroy() is not stubbed for stack.delete cleanup
|
|
self.m.UnsetStubs()
|
|
|
|
def test_update_modify_replace_failed_create(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=True)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'xyz'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
# patch in a dummy handle_create making the replace fail creating
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create',
|
|
side_effect=Exception)
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
mock_create.assert_called_once_with()
|
|
|
|
def test_update_modify_replace_failed_create_and_delete_1(self):
|
|
resource._register_class('ResourceWithResourceIDType',
|
|
generic_rsrc.ResourceWithResourceID)
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'a_res'}},
|
|
'BResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'b_res'},
|
|
'DependsOn': 'AResource'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=True)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'xyz'}},
|
|
'BResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'b_res'},
|
|
'DependsOn': 'AResource'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
# patch in a dummy handle_create making the replace fail creating
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithResourceID,
|
|
'handle_create', side_effect=Exception)
|
|
mock_id = self.patchobject(generic_rsrc.ResourceWithResourceID,
|
|
'mox_resource_id',
|
|
return_value=None)
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.stack.delete()
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
mock_create.assert_called_once_with()
|
|
|
|
# Three calls in list: first is an attempt to delete backup_stack
|
|
# where create(xyz) has failed, so no resource_id passed; the 2nd
|
|
# and the 3rd calls are invoked by resource BResource deletion
|
|
# followed by AResource deletion.
|
|
mock_id.assert_has_calls(
|
|
[mock.call(None), mock.call('b_res'), mock.call('a_res')])
|
|
|
|
def test_update_modify_replace_failed_create_and_delete_2(self):
|
|
resource._register_class('ResourceWithResourceIDType',
|
|
generic_rsrc.ResourceWithResourceID)
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'a_res'}},
|
|
'BResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'b_res'},
|
|
'DependsOn': 'AResource'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=True)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'c_res'}},
|
|
'BResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'xyz'},
|
|
'DependsOn': 'AResource'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
# patch in a dummy handle_create making the replace fail creating
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithResourceID,
|
|
'handle_create',
|
|
side_effect=[None, Exception])
|
|
mock_id = self.patchobject(generic_rsrc.ResourceWithResourceID,
|
|
'mox_resource_id', return_value=None)
|
|
|
|
self.stack.update(updated_stack)
|
|
# set resource_id for AResource because handle_create() is overwritten
|
|
# by the mock.
|
|
self.stack.resources['AResource'].resource_id_set('c_res')
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.stack.delete()
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.assertEqual(2, mock_create.call_count)
|
|
# Four calls in chain: 1st is an attempt to delete backup_stack where
|
|
# the create(xyz) failed with no resource_id, the other three are
|
|
# derived from resource dependencies.
|
|
mock_id.assert_has_calls(
|
|
[mock.call(None), mock.call('c_res'), mock.call('b_res'),
|
|
mock.call('a_res')])
|
|
|
|
def test_update_modify_replace_create_in_progress_and_delete_1(self):
|
|
resource._register_class('ResourceWithResourceIDType',
|
|
generic_rsrc.ResourceWithResourceID)
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'a_res'}},
|
|
'BResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'b_res'},
|
|
'DependsOn': 'AResource'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=True)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'xyz'}},
|
|
'BResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'b_res'},
|
|
'DependsOn': 'AResource'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
# patch in a dummy handle_create making the replace fail creating
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithResourceID,
|
|
'handle_create', side_effect=Exception)
|
|
mock_id = self.patchobject(generic_rsrc.ResourceWithResourceID,
|
|
'mox_resource_id', return_value=None)
|
|
|
|
self.stack.update(updated_stack)
|
|
# Override stack status and resources status for emulating
|
|
# IN_PROGRESS situation
|
|
self.stack.state_set(
|
|
stack.Stack.UPDATE, stack.Stack.IN_PROGRESS, None)
|
|
self.stack.resources['AResource'].state_set(
|
|
resource.Resource.CREATE, resource.Resource.IN_PROGRESS, None)
|
|
self.stack.delete()
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
mock_create.assert_called_once_with()
|
|
# Three calls in chain: 1st is an attempt to delete backup_stack where
|
|
# the create(xyz) failed with no resource_id, the other two ordered by
|
|
# resource dependencies.
|
|
mock_id.assert_has_calls(
|
|
[mock.call(None), mock.call('b_res'), mock.call('a_res')])
|
|
|
|
def test_update_modify_replace_create_in_progress_and_delete_2(self):
|
|
resource._register_class('ResourceWithResourceIDType',
|
|
generic_rsrc.ResourceWithResourceID)
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'a_res'}},
|
|
'BResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'b_res'},
|
|
'DependsOn': 'AResource'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=True)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'c_res'}},
|
|
'BResource': {'Type':
|
|
'ResourceWithResourceIDType',
|
|
'Properties': {'ID': 'xyz'},
|
|
'DependsOn': 'AResource'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
# patch in a dummy handle_create making the replace fail creating
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithResourceID,
|
|
'handle_create',
|
|
side_effect=[None, Exception])
|
|
mock_id = self.patchobject(generic_rsrc.ResourceWithResourceID,
|
|
'mox_resource_id', return_value=None)
|
|
|
|
self.stack.update(updated_stack)
|
|
# set resource_id for AResource because handle_create() is mocked
|
|
self.stack.resources['AResource'].resource_id_set('c_res')
|
|
# Override stack status and resources status for emulating
|
|
# IN_PROGRESS situation
|
|
self.stack.state_set(
|
|
stack.Stack.UPDATE, stack.Stack.IN_PROGRESS, None)
|
|
self.stack.resources['BResource'].state_set(
|
|
resource.Resource.CREATE, resource.Resource.IN_PROGRESS, None)
|
|
self.stack.delete()
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.assertEqual(2, mock_create.call_count)
|
|
# Four calls in chain: 1st is an attempt to delete backup_stack where
|
|
# the create(xyz) failed with no resource_id, the other three are
|
|
# derived from resource dependencies.
|
|
mock_id.assert_has_calls(
|
|
[mock.call(None), mock.call('c_res'), mock.call('b_res'),
|
|
mock.call('a_res')])
|
|
|
|
def test_update_add_signal(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'GenericResourceType'},
|
|
'BResource': {'Type': 'GenericResourceType'}}}
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
updater = scheduler.TaskRunner(self.stack.update_task, updated_stack)
|
|
updater.start()
|
|
while 'BResource' not in self.stack:
|
|
self.assertFalse(updater.step())
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.IN_PROGRESS),
|
|
self.stack.state)
|
|
|
|
# Reload the stack from the DB and prove that it contains the new
|
|
# resource already
|
|
re_stack = stack.Stack.load(utils.dummy_context(), self.stack.id)
|
|
self.assertIn('BResource', re_stack)
|
|
|
|
updater.run_to_completion()
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertIn('BResource', self.stack)
|
|
|
|
def test_update_add_failed_create(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'GenericResourceType'},
|
|
'BResource': {'Type': 'GenericResourceType'}}}
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
# patch in a dummy handle_create making BResource fail creating
|
|
mock_create = self.patchobject(generic_rsrc.GenericResource,
|
|
'handle_create', side_effect=Exception)
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.assertIn('BResource', self.stack)
|
|
|
|
# Reload the stack from the DB and prove that it contains the failed
|
|
# resource (to ensure it will be deleted on stack delete)
|
|
re_stack = stack.Stack.load(utils.dummy_context(), self.stack.id)
|
|
self.assertIn('BResource', re_stack)
|
|
mock_create.assert_called_once_with()
|
|
|
|
def test_update_add_failed_create_rollback_failed(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'GenericResourceType'},
|
|
'BResource': {'Type': 'GenericResourceType'}}}
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=False)
|
|
|
|
# patch handle_create/delete making BResource fail creation/deletion
|
|
mock_create = self.patchobject(generic_rsrc.GenericResource,
|
|
'handle_create', side_effect=Exception)
|
|
mock_delete = self.patchobject(generic_rsrc.GenericResource,
|
|
'handle_delete', side_effect=Exception)
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.assertIn('BResource', self.stack)
|
|
|
|
# Reload the stack from the DB and prove that it contains the failed
|
|
# resource (to ensure it will be deleted on stack delete)
|
|
re_stack = stack.Stack.load(utils.dummy_context(), self.stack.id)
|
|
self.assertIn('BResource', re_stack)
|
|
|
|
mock_create.assert_called_once_with()
|
|
mock_delete.assert_called_once_with()
|
|
|
|
def test_update_rollback(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=False)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'xyz'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=False)
|
|
|
|
# patch in a dummy handle_create making the replace fail when creating
|
|
# the replacement rsrc
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create', side_effect=Exception)
|
|
|
|
with mock.patch.object(self.stack, 'state_set',
|
|
side_effect=self.stack.state_set) as mock_state:
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual(2, mock_state.call_count)
|
|
self.assertEqual(('UPDATE', 'IN_PROGRESS'),
|
|
mock_state.call_args_list[0][0][:2])
|
|
self.assertEqual(('ROLLBACK', 'IN_PROGRESS'),
|
|
mock_state.call_args_list[1][0][:2])
|
|
|
|
mock_create.assert_called_once_with()
|
|
|
|
def test_update_rollback_on_cancel_event(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=False)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'xyz'}},
|
|
}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=False)
|
|
evt_mock = mock.MagicMock()
|
|
evt_mock.ready.return_value = True
|
|
evt_mock.wait.return_value = 'cancel'
|
|
|
|
self.stack.update(updated_stack, event=evt_mock)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
evt_mock.ready.assert_called_once_with()
|
|
evt_mock.wait.assert_called_once_with()
|
|
|
|
def test_update_rollback_fail(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Parameters': {'AParam': {'Type': 'String'}},
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
|
|
env1 = environment.Environment({'parameters': {'AParam': 'abc'}})
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl, env=env1),
|
|
disable_rollback=False)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Parameters': {'BParam': {'Type': 'String'}},
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'xyz'}}}}
|
|
|
|
env2 = environment.Environment({'parameters': {'BParam': 'smelly'}})
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2, env=env2),
|
|
disable_rollback=False)
|
|
|
|
# patch in a dummy handle_create making the replace fail when creating
|
|
# the replacement rsrc, and again on the second call (rollback)
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create', side_effect=Exception)
|
|
mock_delete = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_delete', side_effect=Exception)
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
|
|
mock_create.assert_called_once_with()
|
|
mock_delete.assert_called_once_with()
|
|
|
|
def test_update_rollback_add(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=False)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'GenericResourceType'},
|
|
'BResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=False)
|
|
|
|
# patch in a dummy handle_create making the replace fail when creating
|
|
# the replacement rsrc, and succeed on the second call (rollback)
|
|
mock_create = self.patchobject(generic_rsrc.GenericResource,
|
|
'handle_create', side_effect=Exception)
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertNotIn('BResource', self.stack)
|
|
|
|
mock_create.assert_called_once_with()
|
|
|
|
def test_update_rollback_remove(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'GenericResourceType'},
|
|
'BResource': {'Type': 'ResourceWithPropsType'}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=False)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=False)
|
|
|
|
# patch in a dummy delete making the destroy fail
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create')
|
|
mock_delete = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_delete',
|
|
side_effect=[Exception, None])
|
|
|
|
self.stack.update(updated_stack)
|
|
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertIn('BResource', self.stack)
|
|
|
|
mock_create.assert_called_once_with()
|
|
self.assertEqual(2, mock_delete.call_count)
|
|
|
|
# Unset here so delete() is not stubbed for stack.delete cleanup
|
|
self.m.UnsetStubs()
|
|
|
|
def test_update_rollback_replace(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'foo'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=False)
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'bar'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=False)
|
|
|
|
# patch in a dummy delete making the destroy fail
|
|
mock_delete = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_delete',
|
|
side_effect=[Exception, None, None])
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual(3, mock_delete.call_count)
|
|
|
|
# Unset here so delete() is not stubbed for stack.delete cleanup
|
|
self.m.UnsetStubs()
|
|
|
|
def test_update_replace_by_reference(self):
|
|
'''
|
|
assertion:
|
|
changes in dynamic attributes, due to other resources been updated
|
|
are not ignored and can cause dependent resources to be updated.
|
|
'''
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'smelly'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource',
|
|
self.stack['BResource'].properties['Foo'])
|
|
|
|
self.ref_id_called = False
|
|
|
|
def get_ref_id(*args):
|
|
ref_id = 'inst-007' if self.ref_id_called else 'AResource'
|
|
if self.ref_id_called is False:
|
|
self.ref_id_called = True
|
|
return ref_id
|
|
|
|
mock_id = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'FnGetRefId',
|
|
side_effect=get_ref_id)
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('inst-007', self.stack['BResource'].properties['Foo'])
|
|
|
|
# Note: mock_id is called 14 times!!!
|
|
mock_id.assert_called_with()
|
|
|
|
def test_update_with_new_resources_with_reference(self):
|
|
'''
|
|
assertion:
|
|
check, that during update with new resources which one has
|
|
reference on second, reference will be correct resolved.
|
|
'''
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'CResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}}}}
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'CResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}},
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'smelly'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['CResource'].properties['Foo'])
|
|
self.assertEqual(1, len(self.stack.resources))
|
|
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create', return_value=None)
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource',
|
|
self.stack['BResource'].properties['Foo'])
|
|
|
|
self.assertEqual(3, len(self.stack.resources))
|
|
|
|
mock_create.assert_called_with()
|
|
|
|
def test_update_by_reference_and_rollback_1(self):
|
|
'''
|
|
assertion:
|
|
check that rollback still works with dynamic metadata
|
|
this test fails the first instance
|
|
'''
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'abc'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'smelly'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=False)
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource',
|
|
self.stack['BResource'].properties['Foo'])
|
|
|
|
mock_id = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'FnGetRefId', return_value='AResource')
|
|
|
|
# mock to make the replace fail when creating the replacement resource
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create', side_effect=Exception)
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=False)
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
|
|
mock_id.assert_called_with()
|
|
mock_create.assert_called_once_with()
|
|
|
|
def test_update_by_reference_and_rollback_2(self):
|
|
'''
|
|
assertion:
|
|
check that rollback still works with dynamic metadata
|
|
this test fails the second instance
|
|
'''
|
|
|
|
class ResourceTypeA(generic_rsrc.ResourceWithProps):
|
|
count = 0
|
|
|
|
def handle_create(self):
|
|
ResourceTypeA.count += 1
|
|
self.resource_id_set('%s%d' % (self.name, self.count))
|
|
|
|
resource._register_class('ResourceTypeA', ResourceTypeA)
|
|
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceTypeA',
|
|
'Properties': {'Foo': 'abc'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceTypeA',
|
|
'Properties': {'Foo': 'smelly'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=False)
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource1',
|
|
self.stack['BResource'].properties['Foo'])
|
|
|
|
# mock to make the replace fail when creating the second
|
|
# replacement resource
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create', side_effect=Exception)
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=False)
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource1',
|
|
self.stack['BResource'].properties['Foo'])
|
|
mock_create.assert_called_once_with()
|
|
|
|
def test_update_failure_recovery(self):
|
|
'''
|
|
assertion:
|
|
check that rollback still works with dynamic metadata
|
|
this test fails the second instance
|
|
'''
|
|
|
|
class ResourceTypeA(generic_rsrc.ResourceWithProps):
|
|
count = 0
|
|
|
|
def handle_create(self):
|
|
ResourceTypeA.count += 1
|
|
self.resource_id_set('%s%d' % (self.name, self.count))
|
|
|
|
def handle_delete(self):
|
|
return super(ResourceTypeA, self).handle_delete()
|
|
|
|
resource._register_class('ResourceTypeA', ResourceTypeA)
|
|
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceTypeA',
|
|
'Properties': {'Foo': 'abc'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceTypeA',
|
|
'Properties': {'Foo': 'smelly'}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {
|
|
'Foo': {'Ref': 'AResource'}}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl),
|
|
disable_rollback=True)
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource1',
|
|
self.stack['BResource'].properties['Foo'])
|
|
|
|
# mock to make the replace fail when creating the second
|
|
# replacement resource
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create',
|
|
side_effect=[Exception, None])
|
|
mock_delete = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_delete')
|
|
mock_delete_A = self.patchobject(ResourceTypeA, 'handle_delete')
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=True)
|
|
self.stack.update(updated_stack)
|
|
|
|
mock_create.assert_called_once_with()
|
|
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
|
|
|
self.stack = stack.Stack.load(self.ctx, self.stack.id)
|
|
updated_stack2 = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2),
|
|
disable_rollback=True)
|
|
|
|
self.stack.update(updated_stack2)
|
|
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource2',
|
|
self.stack['BResource'].properties['Foo'])
|
|
|
|
self.assertEqual(2, mock_create.call_count)
|
|
self.assertEqual(2, mock_delete.call_count)
|
|
mock_delete_A.assert_called_once_with()
|
|
|
|
def test_update_failure_recovery_new_param(self):
|
|
'''
|
|
assertion:
|
|
check that rollback still works with dynamic metadata
|
|
this test fails the second instance
|
|
'''
|
|
|
|
class ResourceTypeA(generic_rsrc.ResourceWithProps):
|
|
count = 0
|
|
|
|
def handle_create(self):
|
|
ResourceTypeA.count += 1
|
|
self.resource_id_set('%s%d' % (self.name, self.count))
|
|
|
|
def handle_delete(self):
|
|
return super(ResourceTypeA, self).handle_delete()
|
|
|
|
resource._register_class('ResourceTypeA', ResourceTypeA)
|
|
|
|
tmpl = {
|
|
'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Parameters': {
|
|
'abc-param': {'Type': 'String'}
|
|
},
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceTypeA',
|
|
'Properties': {'Foo': {'Ref': 'abc-param'}}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': {'Ref': 'AResource'}}}
|
|
}
|
|
}
|
|
env1 = environment.Environment({'abc-param': 'abc'})
|
|
tmpl2 = {
|
|
'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Parameters': {
|
|
'smelly-param': {'Type': 'String'}
|
|
},
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceTypeA',
|
|
'Properties': {'Foo': {'Ref': 'smelly-param'}}},
|
|
'BResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': {'Ref': 'AResource'}}}
|
|
}
|
|
}
|
|
env2 = environment.Environment({'smelly-param': 'smelly'})
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl, env=env1),
|
|
disable_rollback=True)
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource1',
|
|
self.stack['BResource'].properties['Foo'])
|
|
|
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_create',
|
|
side_effect=[Exception, None])
|
|
mock_delete = self.patchobject(generic_rsrc.ResourceWithProps,
|
|
'handle_delete')
|
|
mock_delete_A = self.patchobject(ResourceTypeA, 'handle_delete')
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2, env=env2),
|
|
disable_rollback=True)
|
|
self.stack.update(updated_stack)
|
|
|
|
# creation was a failure
|
|
mock_create.assert_called_once_with()
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
|
|
|
self.stack = stack.Stack.load(self.ctx, self.stack.id)
|
|
updated_stack2 = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2, env=env2),
|
|
disable_rollback=True)
|
|
|
|
self.stack.update(updated_stack2)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.stack = stack.Stack.load(self.ctx, self.stack.id)
|
|
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
|
self.assertEqual('AResource2',
|
|
self.stack['BResource'].properties['Foo'])
|
|
|
|
self.assertEqual(2, mock_delete.call_count)
|
|
mock_delete_A.assert_called_once_with()
|
|
self.assertEqual(2, mock_create.call_count)
|
|
|
|
def test_update_replace_parameters(self):
|
|
'''
|
|
assertion:
|
|
changes in static environment parameters
|
|
are not ignored and can cause dependent resources to be updated.
|
|
'''
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Parameters': {'AParam': {'Type': 'String'}},
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': {'Ref': 'AParam'}}}}}
|
|
|
|
env1 = environment.Environment({'parameters': {'AParam': 'abc'}})
|
|
env2 = environment.Environment({'parameters': {'AParam': 'smelly'}})
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl, env=env1))
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl, env=env2))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
|
|
|
def test_update_deletion_policy(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': 'Bar'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
resource_id = self.stack['AResource'].id
|
|
|
|
new_tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithPropsType',
|
|
'DeletionPolicy': 'Retain',
|
|
'Properties': {'Foo': 'Bar'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(new_tmpl))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.assertEqual(resource_id, self.stack['AResource'].id)
|
|
|
|
def test_update_deletion_policy_no_handle_update(self):
|
|
|
|
class ResourceWithNoUpdate(resource.Resource):
|
|
properties_schema = {'Foo': {'Type': 'String'}}
|
|
|
|
resource._register_class('ResourceWithNoUpdate',
|
|
ResourceWithNoUpdate)
|
|
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithNoUpdate',
|
|
'Properties': {'Foo': 'Bar'}}}}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
resource_id = self.stack['AResource'].id
|
|
|
|
new_tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {
|
|
'AResource': {'Type': 'ResourceWithNoUpdate',
|
|
'DeletionPolicy': 'Retain',
|
|
'Properties': {'Foo': 'Bar'}}}}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(new_tmpl))
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.assertEqual(resource_id, self.stack['AResource'].id)
|
|
|
|
def test_update_template_format_version(self):
|
|
tmpl = {
|
|
'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Parameters': {
|
|
'AParam': {'Type': 'String', 'Default': 'abc'}},
|
|
'Resources': {
|
|
'AResource': {
|
|
'Type': 'ResourceWithPropsType',
|
|
'Properties': {'Foo': {'Ref': 'AParam'}}
|
|
},
|
|
}
|
|
}
|
|
|
|
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
|
template.Template(tmpl))
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
|
|
|
tmpl2 = {
|
|
'heat_template_version': '2013-05-23',
|
|
'parameters': {
|
|
'AParam': {'type': 'string', 'default': 'foo'}},
|
|
'resources': {
|
|
'AResource': {
|
|
'type': 'ResourceWithPropsType',
|
|
'properties': {'Foo': {'get_param': 'AParam'}}
|
|
}
|
|
}
|
|
}
|
|
|
|
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
|
template.Template(tmpl2))
|
|
|
|
self.stack.update(updated_stack)
|
|
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual('foo', self.stack['AResource'].properties['Foo'])
|