diff --git a/heat/engine/resource.py b/heat/engine/resource.py index 147f835e01..e2d3ddda05 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -1441,6 +1441,7 @@ class Resource(status.ResourceStatus): self.state_set(self.UPDATE, self.FAILED, six.text_type(failure)) raise failure + self.replaced_by = None runner = scheduler.TaskRunner(self.update, new_res_def, new_template_id=template_id, diff --git a/heat/tests/convergence/framework/fake_resource.py b/heat/tests/convergence/framework/fake_resource.py index e0e82b6bdb..073897561b 100644 --- a/heat/tests/convergence/framework/fake_resource.py +++ b/heat/tests/convergence/framework/fake_resource.py @@ -24,9 +24,9 @@ LOG = logging.getLogger(__name__) class TestResource(resource.Resource): PROPERTIES = ( - A, C, CA, rA, rB + A, B, C, CA, rA, rB ) = ( - 'a', 'c', 'ca', '!a', '!b' + 'a', 'b', 'c', 'ca', '!a', '!b' ) ATTRIBUTES = ( @@ -42,6 +42,12 @@ class TestResource(resource.Resource): default='a', update_allowed=True ), + B: properties.Schema( + properties.Schema.STRING, + _('Fake property b.'), + default='b', + update_allowed=True + ), C: properties.Schema( properties.Schema.STRING, _('Fake property c.'), diff --git a/heat/tests/convergence/scenarios/update_user_replace_rollback_update.py b/heat/tests/convergence/scenarios/update_user_replace_rollback_update.py new file mode 100644 index 0000000000..1f365ea6dc --- /dev/null +++ b/heat/tests/convergence/scenarios/update_user_replace_rollback_update.py @@ -0,0 +1,54 @@ +# +# 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. + +example_template = Template({ + 'A': RsrcDef({'a': 'initial'}, []), + 'B': RsrcDef({}, []), + 'C': RsrcDef({'!a': GetAtt('A', 'a'), 'b': 'val1'}, []), + 'D': RsrcDef({'c': GetRes('C')}, []), + 'E': RsrcDef({'ca': GetAtt('C', '!a')}, []), +}) +engine.create_stack('foo', example_template) +engine.noop(5) +engine.call(verify, example_template) + +example_template_updated = Template({ + 'A': RsrcDef({'a': 'updated'}, []), + 'B': RsrcDef({}, []), + 'C': RsrcDef({'!a': GetAtt('A', 'a'), 'b': 'val1'}, []), + 'D': RsrcDef({'c': GetRes('C')}, []), + 'E': RsrcDef({'ca': GetAtt('C', '!a')}, []), +}) +engine.update_stack('foo', example_template_updated) +engine.noop(3) + +engine.rollback_stack('foo') +engine.noop(12) +engine.call(verify, example_template) + +example_template_final = Template({ + 'A': RsrcDef({'a': 'initial'}, []), + 'B': RsrcDef({}, []), + 'C': RsrcDef({'!a': GetAtt('A', 'a'), 'b': 'val2'}, []), + 'D': RsrcDef({'c': GetRes('C')}, []), + 'E': RsrcDef({'ca': GetAtt('C', '!a')}, []), +}) + +engine.update_stack('foo', example_template_final) +engine.noop(3) +engine.call(verify, example_template_final) +engine.noop(4) + +engine.delete_stack('foo') +engine.noop(6) +engine.call(verify, Template({}))