227
def test_select_from_list(self):
228
data = {"Fn::Select": ["1", ["foo", "bar"]]}
229
self.assertEqual(parser.Template.resolve_select(data), "bar")
231
def test_select_from_list_not_int(self):
232
data = {"Fn::Select": ["one", ["foo", "bar"]]}
233
self.assertRaises(TypeError, parser.Template.resolve_select,
236
def test_select_from_list_out_of_bound(self):
237
data = {"Fn::Select": ["3", ["foo", "bar"]]}
238
self.assertRaises(IndexError, parser.Template.resolve_select,
241
def test_select_from_dict(self):
242
data = {"Fn::Select": ["red", {"red": "robin", "re": "foo"}]}
243
self.assertEqual(parser.Template.resolve_select(data), "robin")
245
def test_select_from_none(self):
246
data = {"Fn::Select": ["red", None]}
247
self.assertEqual(parser.Template.resolve_select(data), "")
249
def test_select_from_dict_not_str(self):
250
data = {"Fn::Select": ["1", {"red": "robin", "re": "foo"}]}
251
self.assertRaises(TypeError, parser.Template.resolve_select,
254
def test_select_from_dict_not_existing(self):
255
data = {"Fn::Select": ["green", {"red": "robin", "re": "foo"}]}
256
self.assertRaises(KeyError, parser.Template.resolve_select,
259
def test_select_from_serialized_json_map(self):
260
js = json.dumps({"red": "robin", "re": "foo"})
261
data = {"Fn::Select": ["re", js]}
262
self.assertEqual(parser.Template.resolve_select(data), "foo")
264
def test_select_from_serialized_json_list(self):
265
js = json.dumps(["foo", "fee", "fum"])
266
data = {"Fn::Select": ["0", js]}
267
self.assertEqual(parser.Template.resolve_select(data), "foo")
269
def test_select_from_serialized_json_wrong(self):
270
js = "this is really not serialized json"
271
data = {"Fn::Select": ["not", js]}
272
self.assertRaises(ValueError, parser.Template.resolve_select,
275
def test_select_wrong_num_args(self):
276
join0 = {"Fn::Select": []}
277
self.assertRaises(ValueError, parser.Template.resolve_select,
279
join1 = {"Fn::Select": ["4"]}
280
self.assertRaises(ValueError, parser.Template.resolve_select,
282
join3 = {"Fn::Select": ["foo", {"foo": "bar"}, ""]}
283
self.assertRaises(ValueError, parser.Template.resolve_select,
213
286
def test_join_reduce(self):
214
287
join = {"Fn::Join": [" ", ["foo", "bar", "baz", {'Ref': 'baz'},
215
288
"bink", "bonk"]]}
291
382
self.assertRaises(TypeError, parser.Template.resolve_base64,
295
@attr(tag=['unit', 'parser', 'stack'])
297
class StackTest(unittest.TestCase):
385
def test_get_azs(self):
386
snippet = {"Fn::GetAZs": ""}
388
parser.Template.resolve_availability_zones(snippet, None),
391
def test_get_azs_with_stack(self):
392
snippet = {"Fn::GetAZs": ""}
393
stack = parser.Stack(None, 'test_stack', parser.Template({}))
394
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
395
fc = fakes.FakeClient()
396
clients.OpenStackClients.nova().MultipleTimes().AndReturn(fc)
399
parser.Template.resolve_availability_zones(snippet, stack),
402
def test_replace(self):
403
snippet = {"Fn::Replace": [
404
{'$var1': 'foo', '%var2%': 'bar'},
408
parser.Template.resolve_replace(snippet),
411
def test_replace_list_mapping(self):
412
snippet = {"Fn::Replace": [
413
['var1', 'foo', 'var2', 'bar'],
416
self.assertRaises(TypeError, parser.Template.resolve_replace,
419
def test_replace_dict(self):
420
snippet = {"Fn::Replace": {}}
421
self.assertRaises(TypeError, parser.Template.resolve_replace,
424
def test_replace_missing_template(self):
425
snippet = {"Fn::Replace": [['var1', 'foo', 'var2', 'bar']]}
426
self.assertRaises(ValueError, parser.Template.resolve_replace,
429
def test_replace_none_template(self):
430
snippet = {"Fn::Replace": [['var1', 'foo', 'var2', 'bar'], None]}
431
self.assertRaises(TypeError, parser.Template.resolve_replace,
434
def test_replace_list_string(self):
435
snippet = {"Fn::Replace": [
436
{'var1': 'foo', 'var2': 'bar'},
439
self.assertRaises(TypeError, parser.Template.resolve_replace,
442
def test_replace_none_values(self):
443
snippet = {"Fn::Replace": [
444
{'$var1': None, '${var2}': None},
445
'"$var1" is "${var2}"'
448
parser.Template.resolve_replace(snippet),
451
def test_replace_missing_key(self):
452
snippet = {"Fn::Replace": [
453
{'$var1': 'foo', 'var2': 'bar'},
454
'"$var1" is "${var3}"'
457
parser.Template.resolve_replace(snippet),
458
'"foo" is "${var3}"')
460
def test_resource_facade(self):
461
metadata_snippet = {'Fn::ResourceFacade': 'Metadata'}
462
deletion_policy_snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
463
update_policy_snippet = {'Fn::ResourceFacade': 'UpdatePolicy'}
467
parent_resource = DummyClass()
468
parent_resource.metadata = '{"foo": "bar"}'
469
parent_resource.t = {'DeletionPolicy': 'Retain',
470
'UpdatePolicy': '{"foo": "bar"}'}
471
stack = parser.Stack(None, 'test_stack',
473
parent_resource=parent_resource)
475
parser.Template.resolve_resource_facade(metadata_snippet, stack),
478
parser.Template.resolve_resource_facade(deletion_policy_snippet,
481
parser.Template.resolve_resource_facade(update_policy_snippet,
482
stack), '{"foo": "bar"}')
484
def test_resource_facade_invalid_arg(self):
485
snippet = {'Fn::ResourceFacade': 'wibble'}
486
stack = parser.Stack(None, 'test_stack', parser.Template({}))
487
self.assertRaises(ValueError,
488
parser.Template.resolve_resource_facade,
492
def test_resource_facade_missing_key(self):
493
snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
497
parent_resource = DummyClass()
498
parent_resource.metadata = '{"foo": "bar"}'
499
parent_resource.t = {}
500
stack = parser.Stack(None, 'test_stack',
502
parent_resource=parent_resource)
503
self.assertRaises(KeyError,
504
parser.Template.resolve_resource_facade,
509
class StackTest(HeatTestCase):
511
super(StackTest, self).setUp()
299
513
self.username = 'parser_stack_test_user'
303
516
self.ctx = context.get_admin_context()
304
self.m.StubOutWithMock(self.ctx, 'username')
305
self.ctx.username = self.username
517
self.m.StubOutWithMock(self.ctx, 'user')
518
self.ctx.user = self.username
306
519
self.ctx.tenant_id = 'test_tenant'
308
generic_rsrc.GenericResource.properties_schema = {}
309
521
resource._register_class('GenericResourceType',
310
522
generic_rsrc.GenericResource)
523
resource._register_class('ResourceWithPropsType',
524
generic_rsrc.ResourceWithProps)
312
526
self.m.ReplayAll()
317
528
def test_state_defaults(self):
318
529
stack = parser.Stack(None, 'test_stack', parser.Template({}))
319
self.assertEqual(stack.state, None)
320
self.assertEqual(stack.state_description, '')
530
self.assertEqual(stack.state, (None, None))
531
self.assertEqual(stack.status_reason, '')
322
533
def test_state(self):
323
534
stack = parser.Stack(None, 'test_stack', parser.Template({}),
325
self.assertEqual(stack.state, 'foo')
326
stack.state_set('bar', '')
327
self.assertEqual(stack.state, 'bar')
329
def test_state_description(self):
330
stack = parser.Stack(None, 'test_stack', parser.Template({}),
331
state_description='quux')
332
self.assertEqual(stack.state_description, 'quux')
333
stack.state_set('blarg', 'wibble')
334
self.assertEqual(stack.state_description, 'wibble')
535
action=parser.Stack.CREATE,
536
status=parser.Stack.IN_PROGRESS)
537
self.assertEqual(stack.state,
538
(parser.Stack.CREATE, parser.Stack.IN_PROGRESS))
539
stack.state_set(parser.Stack.CREATE, parser.Stack.COMPLETE, 'test')
540
self.assertEqual(stack.state,
541
(parser.Stack.CREATE, parser.Stack.COMPLETE))
542
stack.state_set(parser.Stack.DELETE, parser.Stack.COMPLETE, 'test')
543
self.assertEqual(stack.state,
544
(parser.Stack.DELETE, parser.Stack.COMPLETE))
546
def test_state_bad(self):
547
stack = parser.Stack(None, 'test_stack', parser.Template({}),
548
action=parser.Stack.CREATE,
549
status=parser.Stack.IN_PROGRESS)
550
self.assertEqual(stack.state,
551
(parser.Stack.CREATE, parser.Stack.IN_PROGRESS))
552
self.assertRaises(ValueError, stack.state_set,
553
'baad', parser.Stack.COMPLETE, 'test')
554
self.assertRaises(ValueError, stack.state_set,
555
parser.Stack.CREATE, 'oops', 'test')
557
def test_status_reason(self):
558
stack = parser.Stack(None, 'test_stack', parser.Template({}),
559
status_reason='quux')
560
self.assertEqual(stack.status_reason, 'quux')
561
stack.state_set(parser.Stack.CREATE, parser.Stack.IN_PROGRESS,
563
self.assertEqual(stack.status_reason, 'wibble')
336
565
def test_load_nonexistant_id(self):
337
566
self.assertRaises(exception.NotFound, parser.Stack.load,
570
def test_load_parent_resource(self):
571
self.stack = parser.Stack(self.ctx, 'load_parent_resource',
574
stack = db_api.stack_get(self.ctx, self.stack.id)
576
t = template.Template.load(self.ctx, stack.raw_template_id)
577
self.m.StubOutWithMock(template.Template, 'load')
578
template.Template.load(self.ctx, stack.raw_template_id).AndReturn(t)
580
env = environment.Environment(stack.parameters)
581
self.m.StubOutWithMock(environment, 'Environment')
582
environment.Environment(stack.parameters).AndReturn(env)
584
self.m.StubOutWithMock(parser.Stack, '__init__')
585
parser.Stack.__init__(self.ctx, stack.name, t, env, stack.id,
586
stack.action, stack.status, stack.status_reason,
587
stack.timeout, True, stack.disable_rollback,
591
parser.Stack.load(self.ctx, stack_id=self.stack.id,
592
parent_resource='parent')
340
596
# Note tests creating a stack should be decorated with @stack_delete_after
341
597
# to ensure the self.stack is properly cleaned up
342
598
@stack_delete_after
414
670
db_s = db_api.stack_get(self.ctx, stack_id)
415
671
self.assertEqual(db_s, None)
416
self.assertEqual(self.stack.state, self.stack.DELETE_COMPLETE)
672
self.assertEqual(self.stack.state,
673
(parser.Stack.DELETE, parser.Stack.COMPLETE))
676
def test_suspend_resume(self):
678
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
679
self.stack = parser.Stack(self.ctx, 'suspend_test',
680
parser.Template(tmpl))
681
stack_id = self.stack.store()
683
self.assertEqual(self.stack.state,
684
(self.stack.CREATE, self.stack.COMPLETE))
688
self.assertEqual(self.stack.state,
689
(self.stack.SUSPEND, self.stack.COMPLETE))
693
self.assertEqual(self.stack.state,
694
(self.stack.RESUME, self.stack.COMPLETE))
699
def test_suspend_fail(self):
700
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
701
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_suspend')
702
exc = exception.ResourceFailure(Exception('foo'))
703
generic_rsrc.GenericResource.handle_suspend().AndRaise(exc)
706
self.stack = parser.Stack(self.ctx, 'suspend_test_fail',
707
parser.Template(tmpl))
709
stack_id = self.stack.store()
711
self.assertEqual(self.stack.state,
712
(self.stack.CREATE, self.stack.COMPLETE))
716
self.assertEqual(self.stack.state,
717
(self.stack.SUSPEND, self.stack.FAILED))
718
self.assertEqual(self.stack.status_reason,
719
'Resource suspend failed: Exception: foo')
723
def test_resume_fail(self):
724
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
725
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_resume')
726
exc = exception.ResourceFailure(Exception('foo'))
727
generic_rsrc.GenericResource.handle_resume().AndRaise(exc)
730
self.stack = parser.Stack(self.ctx, 'resume_test_fail',
731
parser.Template(tmpl))
733
stack_id = self.stack.store()
735
self.assertEqual(self.stack.state,
736
(self.stack.CREATE, self.stack.COMPLETE))
740
self.assertEqual(self.stack.state,
741
(self.stack.SUSPEND, self.stack.COMPLETE))
745
self.assertEqual(self.stack.state,
746
(self.stack.RESUME, self.stack.FAILED))
747
self.assertEqual(self.stack.status_reason,
748
'Resource resume failed: Exception: foo')
752
def test_suspend_timeout(self):
753
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
754
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_suspend')
755
exc = scheduler.Timeout('foo', 0)
756
generic_rsrc.GenericResource.handle_suspend().AndRaise(exc)
759
self.stack = parser.Stack(self.ctx, 'suspend_test_fail_timeout',
760
parser.Template(tmpl))
762
stack_id = self.stack.store()
764
self.assertEqual(self.stack.state,
765
(self.stack.CREATE, self.stack.COMPLETE))
769
self.assertEqual(self.stack.state,
770
(self.stack.SUSPEND, self.stack.FAILED))
771
self.assertEqual(self.stack.status_reason, 'Suspend timed out')
775
def test_resume_timeout(self):
776
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
777
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_resume')
778
exc = scheduler.Timeout('foo', 0)
779
generic_rsrc.GenericResource.handle_resume().AndRaise(exc)
782
self.stack = parser.Stack(self.ctx, 'resume_test_fail_timeout',
783
parser.Template(tmpl))
785
stack_id = self.stack.store()
787
self.assertEqual(self.stack.state,
788
(self.stack.CREATE, self.stack.COMPLETE))
792
self.assertEqual(self.stack.state,
793
(self.stack.SUSPEND, self.stack.COMPLETE))
797
self.assertEqual(self.stack.state,
798
(self.stack.RESUME, self.stack.FAILED))
800
self.assertEqual(self.stack.status_reason, 'Resume timed out')
418
803
@stack_delete_after
419
804
def test_delete_rollback(self):
530
928
updated_stack = parser.Stack(self.ctx, 'updated_stack',
531
929
template.Template(tmpl2))
532
930
self.stack.update(updated_stack)
533
self.assertEqual(self.stack.state, parser.Stack.UPDATE_COMPLETE)
931
self.assertEqual(self.stack.state,
932
(parser.Stack.UPDATE, parser.Stack.COMPLETE))
534
933
self.assertEqual(self.stack.t[template.DESCRIPTION], 'BTemplate')
536
935
@stack_delete_after
537
936
def test_update_modify_ok_replace(self):
538
# patch in a dummy property schema for GenericResource
539
dummy_schema = {'Foo': {'Type': 'String'}}
540
generic_rsrc.GenericResource.properties_schema = dummy_schema
542
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType',
937
tmpl = {'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
543
938
'Properties': {'Foo': 'abc'}}}}
545
940
self.stack = parser.Stack(self.ctx, 'update_test_stack',
546
941
template.Template(tmpl))
547
942
self.stack.store()
548
943
self.stack.create()
549
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
944
self.assertEqual(self.stack.state,
945
(parser.Stack.CREATE, parser.Stack.COMPLETE))
551
tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType',
947
tmpl2 = {'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
552
948
'Properties': {'Foo': 'xyz'}}}}
554
950
updated_stack = parser.Stack(self.ctx, 'updated_stack',
555
951
template.Template(tmpl2))
556
# patch in a dummy handle_update
557
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_update')
558
generic_rsrc.GenericResource.handle_update(
559
tmpl2['Resources']['AResource']).AndReturn(
560
resource.Resource.UPDATE_REPLACE)
953
# Calls to GenericResource.handle_update will raise
954
# resource.UpdateReplace because we've not specified the modified
955
# key/property in update_allowed_keys/update_allowed_properties
561
956
self.m.ReplayAll()
563
958
self.stack.update(updated_stack)
564
self.assertEqual(self.stack.state, parser.Stack.UPDATE_COMPLETE)
959
self.assertEqual(self.stack.state,
960
(parser.Stack.UPDATE, parser.Stack.COMPLETE))
565
961
self.assertEqual(self.stack['AResource'].properties['Foo'], 'xyz')
566
962
self.m.VerifyAll()
568
964
@stack_delete_after
569
965
def test_update_modify_update_failed(self):
570
# patch in a dummy property schema for GenericResource
571
dummy_schema = {'Foo': {'Type': 'String'}}
572
generic_rsrc.GenericResource.properties_schema = dummy_schema
574
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType',
966
tmpl = {'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
575
967
'Properties': {'Foo': 'abc'}}}}
577
969
self.stack = parser.Stack(self.ctx, 'update_test_stack',
612
1008
disable_rollback=True)
613
1009
self.stack.store()
614
1010
self.stack.create()
615
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
1011
self.assertEqual(self.stack.state,
1012
(parser.Stack.CREATE, parser.Stack.COMPLETE))
617
tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType',
1014
tmpl2 = {'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
618
1015
'Properties': {'Foo': 'xyz'}}}}
620
1017
updated_stack = parser.Stack(self.ctx, 'updated_stack',
621
1018
template.Template(tmpl2))
623
# patch in a dummy handle_update
624
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_update')
625
generic_rsrc.GenericResource.handle_update(
626
tmpl2['Resources']['AResource']).AndReturn(
627
resource.Resource.UPDATE_REPLACE)
1020
# Calls to GenericResource.handle_update will raise
1021
# resource.UpdateReplace because we've not specified the modified
1022
# key/property in update_allowed_keys/update_allowed_properties
629
1024
# make the update fail deleting the existing resource
630
1025
self.m.StubOutWithMock(resource.Resource, 'destroy')
631
resource.Resource.destroy().AndReturn("Error")
1026
exc = exception.ResourceFailure(Exception())
1027
resource.Resource.destroy().AndRaise(exc)
632
1028
self.m.ReplayAll()
634
1030
self.stack.update(updated_stack)
635
self.assertEqual(self.stack.state, parser.Stack.UPDATE_FAILED)
1031
self.assertEqual(self.stack.state,
1032
(parser.Stack.UPDATE, parser.Stack.FAILED))
636
1033
self.m.VerifyAll()
637
1034
# Unset here so destroy() is not stubbed for stack.delete cleanup
638
1035
self.m.UnsetStubs()
640
1037
@stack_delete_after
641
1038
def test_update_modify_replace_failed_create(self):
642
# patch in a dummy property schema for GenericResource
643
dummy_schema = {'Foo': {'Type': 'String'}}
644
generic_rsrc.GenericResource.properties_schema = dummy_schema
646
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType',
1039
tmpl = {'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
647
1040
'Properties': {'Foo': 'abc'}}}}
649
1042
self.stack = parser.Stack(self.ctx, 'update_test_stack',
719
1110
disable_rollback=False)
720
1111
self.stack.store()
721
1112
self.stack.create()
722
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
1113
self.assertEqual(self.stack.state,
1114
(parser.Stack.CREATE, parser.Stack.COMPLETE))
724
tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType',
1116
tmpl2 = {'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
725
1117
'Properties': {'Foo': 'xyz'}}}}
727
1119
updated_stack = parser.Stack(self.ctx, 'updated_stack',
728
1120
template.Template(tmpl2))
730
# There will be two calls to handle_update, one for the new template
731
# then another (with the initial template) for rollback
732
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_update')
733
generic_rsrc.GenericResource.handle_update(
734
tmpl2['Resources']['AResource']).AndReturn(
735
resource.Resource.UPDATE_REPLACE)
736
generic_rsrc.GenericResource.handle_update(
737
tmpl['Resources']['AResource']).AndReturn(
738
resource.Resource.UPDATE_REPLACE)
1122
# Calls to GenericResource.handle_update will raise
1123
# resource.UpdateReplace because we've not specified the modified
1124
# key/property in update_allowed_keys/update_allowed_properties
740
1126
# patch in a dummy handle_create making the replace fail when creating
741
# the replacement resource, but succeed the second call (rollback)
1127
# the replacement rsrc, but succeed the second call (rollback)
742
1128
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_create')
743
1129
generic_rsrc.GenericResource.handle_create().AndRaise(Exception)
744
1130
generic_rsrc.GenericResource.handle_create().AndReturn(None)
745
1131
self.m.ReplayAll()
747
1133
self.stack.update(updated_stack)
748
self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE)
1134
self.assertEqual(self.stack.state,
1135
(parser.Stack.ROLLBACK, parser.Stack.COMPLETE))
749
1136
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
750
1137
self.m.VerifyAll()
752
1139
@stack_delete_after
753
1140
def test_update_rollback_fail(self):
754
# patch in a dummy property schema for GenericResource
755
dummy_schema = {'Foo': {'Type': 'String'}}
756
generic_rsrc.GenericResource.properties_schema = dummy_schema
758
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType',
1141
tmpl = {'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
759
1142
'Properties': {'Foo': 'abc'}}}}
761
1144
self.stack = parser.Stack(self.ctx, 'update_test_stack',
763
1146
disable_rollback=False)
764
1147
self.stack.store()
765
1148
self.stack.create()
766
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
1149
self.assertEqual(self.stack.state,
1150
(parser.Stack.CREATE, parser.Stack.COMPLETE))
768
tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType',
1152
tmpl2 = {'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
769
1153
'Properties': {'Foo': 'xyz'}}}}
771
1155
updated_stack = parser.Stack(self.ctx, 'updated_stack',
772
1156
template.Template(tmpl2))
774
# There will be two calls to handle_update, one for the new template
775
# then another (with the initial template) for rollback
776
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_update')
777
generic_rsrc.GenericResource.handle_update(
778
tmpl2['Resources']['AResource']).AndReturn(
779
resource.Resource.UPDATE_REPLACE)
780
generic_rsrc.GenericResource.handle_update(
781
tmpl['Resources']['AResource']).AndReturn(
782
resource.Resource.UPDATE_REPLACE)
1158
# Calls to GenericResource.handle_update will raise
1159
# resource.UpdateReplace because we've not specified the modified
1160
# key/property in update_allowed_keys/update_allowed_properties
784
1162
# patch in a dummy handle_create making the replace fail when creating
785
# the replacement resource, and again on the second call (rollback)
1163
# the replacement rsrc, and again on the second call (rollback)
786
1164
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_create')
787
1165
generic_rsrc.GenericResource.handle_create().AndRaise(Exception)
788
1166
generic_rsrc.GenericResource.handle_create().AndRaise(Exception)
789
1167
self.m.ReplayAll()
791
1169
self.stack.update(updated_stack)
792
self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_FAILED)
1170
self.assertEqual(self.stack.state,
1171
(parser.Stack.ROLLBACK, parser.Stack.FAILED))
793
1172
self.m.VerifyAll()
795
1174
@stack_delete_after
858
1242
changes in dynamic attributes, due to other resources been updated
859
1243
are not ignored and can cause dependant resources to be updated.
861
# patch in a dummy property schema for GenericResource
862
dummy_schema = {'Foo': {'Type': 'String'}}
863
generic_rsrc.GenericResource.properties_schema = dummy_schema
864
1245
tmpl = {'Resources': {
865
'AResource': {'Type': 'GenericResourceType',
1246
'AResource': {'Type': 'ResourceWithPropsType',
866
1247
'Properties': {'Foo': 'abc'}},
867
'BResource': {'Type': 'GenericResourceType',
1248
'BResource': {'Type': 'ResourceWithPropsType',
869
1250
'Foo': {'Ref': 'AResource'}}}}}
870
1251
tmpl2 = {'Resources': {
871
'AResource': {'Type': 'GenericResourceType',
1252
'AResource': {'Type': 'ResourceWithPropsType',
872
1253
'Properties': {'Foo': 'smelly'}},
873
'BResource': {'Type': 'GenericResourceType',
1254
'BResource': {'Type': 'ResourceWithPropsType',
875
1256
'Foo': {'Ref': 'AResource'}}}}}
877
1258
self.stack = parser.Stack(self.ctx, 'update_test_stack',
878
1259
template.Template(tmpl))
879
1263
self.stack.store()
880
1264
self.stack.create()
881
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
1266
self.assertEqual(self.stack.state,
1267
(parser.Stack.CREATE, parser.Stack.COMPLETE))
882
1268
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
883
1269
self.assertEqual(self.stack['BResource'].properties['Foo'],
886
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_update')
887
generic_rsrc.GenericResource.handle_update(
888
tmpl2['Resources']['AResource']).AndReturn(
889
resource.Resource.UPDATE_REPLACE)
891
br2_snip = {'Type': 'GenericResourceType',
892
'Properties': {'Foo': 'inst-007'}}
893
generic_rsrc.GenericResource.handle_update(
895
resource.Resource.UPDATE_REPLACE)
1272
# Calls to GenericResource.handle_update will raise
1273
# resource.UpdateReplace because we've not specified the modified
1274
# key/property in update_allowed_keys/update_allowed_properties
897
1276
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'FnGetRefId')
898
1277
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
916
1296
check that rollback still works with dynamic metadata
917
1297
this test fails the first instance
919
# patch in a dummy property schema for GenericResource
920
dummy_schema = {'Foo': {'Type': 'String'}}
921
generic_rsrc.GenericResource.properties_schema = dummy_schema
922
1299
tmpl = {'Resources': {
923
'AResource': {'Type': 'GenericResourceType',
1300
'AResource': {'Type': 'ResourceWithPropsType',
924
1301
'Properties': {'Foo': 'abc'}},
925
'BResource': {'Type': 'GenericResourceType',
1302
'BResource': {'Type': 'ResourceWithPropsType',
927
1304
'Foo': {'Ref': 'AResource'}}}}}
928
1305
tmpl2 = {'Resources': {
929
'AResource': {'Type': 'GenericResourceType',
1306
'AResource': {'Type': 'ResourceWithPropsType',
930
1307
'Properties': {'Foo': 'smelly'}},
931
'BResource': {'Type': 'GenericResourceType',
1308
'BResource': {'Type': 'ResourceWithPropsType',
933
1310
'Foo': {'Ref': 'AResource'}}}}}
935
1312
self.stack = parser.Stack(self.ctx, 'update_test_stack',
936
1313
template.Template(tmpl),
937
1314
disable_rollback=False)
938
1318
self.stack.store()
939
1319
self.stack.create()
940
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
1322
self.assertEqual(self.stack.state,
1323
(parser.Stack.CREATE, parser.Stack.COMPLETE))
941
1324
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
942
1325
self.assertEqual(self.stack['BResource'].properties['Foo'],
945
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_update')
946
1328
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'FnGetRefId')
947
1329
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_create')
949
# mocks for first (failed update)
950
generic_rsrc.GenericResource.handle_update(
951
tmpl2['Resources']['AResource']).AndReturn(
952
resource.Resource.UPDATE_REPLACE)
1331
# Calls to GenericResource.handle_update will raise
1332
# resource.UpdateReplace because we've not specified the modified
1333
# key/property in update_allowed_keys/update_allowed_properties
953
1335
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
956
1338
# mock to make the replace fail when creating the replacement resource
957
1339
generic_rsrc.GenericResource.handle_create().AndRaise(Exception)
959
# mocks for second rollback update
960
generic_rsrc.GenericResource.handle_update(
961
tmpl['Resources']['AResource']).AndReturn(
962
resource.Resource.UPDATE_REPLACE)
964
1341
generic_rsrc.GenericResource.handle_create().AndReturn(None)
965
1342
generic_rsrc.GenericResource.FnGetRefId().MultipleTimes().AndReturn(
983
1361
check that rollback still works with dynamic metadata
984
1362
this test fails the second instance
986
# patch in a dummy property schema for GenericResource
987
dummy_schema = {'Foo': {'Type': 'String'}}
988
generic_rsrc.GenericResource.properties_schema = dummy_schema
1365
class ResourceTypeA(generic_rsrc.ResourceWithProps):
1368
def handle_create(self):
1369
ResourceTypeA.count += 1
1370
self.resource_id_set('%s%d' % (self.name, self.count))
1372
resource._register_class('ResourceTypeA', ResourceTypeA)
989
1374
tmpl = {'Resources': {
990
'AResource': {'Type': 'GenericResourceType',
1375
'AResource': {'Type': 'ResourceTypeA',
991
1376
'Properties': {'Foo': 'abc'}},
992
'BResource': {'Type': 'GenericResourceType',
1377
'BResource': {'Type': 'ResourceWithPropsType',
994
1379
'Foo': {'Ref': 'AResource'}}}}}
995
1380
tmpl2 = {'Resources': {
996
'AResource': {'Type': 'GenericResourceType',
1381
'AResource': {'Type': 'ResourceTypeA',
997
1382
'Properties': {'Foo': 'smelly'}},
998
'BResource': {'Type': 'GenericResourceType',
1383
'BResource': {'Type': 'ResourceWithPropsType',
1000
1385
'Foo': {'Ref': 'AResource'}}}}}
1002
1387
self.stack = parser.Stack(self.ctx, 'update_test_stack',
1003
1388
template.Template(tmpl),
1004
1389
disable_rollback=False)
1005
1393
self.stack.store()
1006
1394
self.stack.create()
1007
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
1397
self.assertEqual(self.stack.state,
1398
(parser.Stack.CREATE, parser.Stack.COMPLETE))
1008
1399
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
1009
1400
self.assertEqual(self.stack['BResource'].properties['Foo'],
1012
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_update')
1013
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'FnGetRefId')
1014
1403
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_create')
1016
# mocks for first and second (failed update)
1017
generic_rsrc.GenericResource.handle_update(
1018
tmpl2['Resources']['AResource']).AndReturn(
1019
resource.Resource.UPDATE_REPLACE)
1020
br2_snip = {'Type': 'GenericResourceType',
1021
'Properties': {'Foo': 'inst-007'}}
1022
generic_rsrc.GenericResource.handle_update(
1023
br2_snip).AndReturn(
1024
resource.Resource.UPDATE_REPLACE)
1026
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1028
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1030
# self.state_set(self.UPDATE_IN_PROGRESS)
1031
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1033
# self.state_set(self.DELETE_IN_PROGRESS)
1034
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1036
# self.state_set(self.DELETE_COMPLETE)
1037
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1039
# self.properties.validate()
1040
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1042
# self.state_set(self.CREATE_IN_PROGRESS)
1043
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1405
# Calls to GenericResource.handle_update will raise
1406
# resource.UpdateReplace because we've not specified the modified
1407
# key/property in update_allowed_keys/update_allowed_properties
1046
1409
# mock to make the replace fail when creating the second
1047
1410
# replacement resource
1048
generic_rsrc.GenericResource.handle_create().AndReturn(None)
1049
1411
generic_rsrc.GenericResource.handle_create().AndRaise(Exception)
1051
# mocks for second rollback update
1052
generic_rsrc.GenericResource.handle_update(
1053
tmpl['Resources']['AResource']).AndReturn(
1054
resource.Resource.UPDATE_REPLACE)
1055
br2_snip = {'Type': 'GenericResourceType',
1056
'Properties': {'Foo': 'AResource'}}
1057
generic_rsrc.GenericResource.handle_update(
1058
br2_snip).AndReturn(
1059
resource.Resource.UPDATE_REPLACE)
1061
# self.state_set(self.DELETE_IN_PROGRESS)
1062
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1064
# self.state_set(self.DELETE_IN_PROGRESS)
1065
generic_rsrc.GenericResource.FnGetRefId().AndReturn(
1068
generic_rsrc.GenericResource.handle_create().AndReturn(None)
1069
generic_rsrc.GenericResource.handle_create().AndReturn(None)
1071
# reverting to AResource
1072
generic_rsrc.GenericResource.FnGetRefId().MultipleTimes().AndReturn(
1413
# Calls to GenericResource.handle_update will raise
1414
# resource.UpdateReplace because we've not specified the modified
1415
# key/property in update_allowed_keys/update_allowed_properties
1417
generic_rsrc.GenericResource.handle_create().AndReturn(None)
1075
1419
self.m.ReplayAll()
1078
1422
template.Template(tmpl2),
1079
1423
disable_rollback=False)
1080
1424
self.stack.update(updated_stack)
1081
self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE)
1082
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
1425
self.assertEqual(self.stack.state,
1426
(parser.Stack.ROLLBACK, parser.Stack.COMPLETE))
1427
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
1428
self.assertEqual(self.stack['BResource'].properties['Foo'],
1434
def test_update_replace_parameters(self):
1437
changes in static environment parameters
1438
are not ignored and can cause dependant resources to be updated.
1440
tmpl = {'Parameters': {'AParam': {'Type': 'String'}},
1442
'AResource': {'Type': 'ResourceWithPropsType',
1443
'Properties': {'Foo': {'Ref': 'AParam'}}}}}
1445
env1 = {'parameters': {'AParam': 'abc'}}
1446
env2 = {'parameters': {'AParam': 'smelly'}}
1447
self.stack = parser.Stack(self.ctx, 'update_test_stack',
1448
template.Template(tmpl),
1449
environment.Environment(env1))
1453
self.assertEqual(self.stack.state,
1454
(parser.Stack.CREATE, parser.Stack.COMPLETE))
1455
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
1457
updated_stack = parser.Stack(self.ctx, 'updated_stack',
1458
template.Template(tmpl),
1459
environment.Environment(env2))
1460
self.stack.update(updated_stack)
1461
self.assertEqual(self.stack.state,
1462
(parser.Stack.UPDATE, parser.Stack.COMPLETE))
1463
self.assertEqual(self.stack['AResource'].properties['Foo'], 'smelly')
1465
def test_stack_create_timeout(self):
1466
self.m.StubOutWithMock(scheduler.DependencyTaskGroup, '__call__')
1467
self.m.StubOutWithMock(scheduler, 'wallclock')
1469
stack = parser.Stack(None, 's', parser.Template({}))
1475
start_time = time.time()
1476
scheduler.wallclock().AndReturn(start_time)
1477
scheduler.wallclock().AndReturn(start_time + 1)
1478
scheduler.DependencyTaskGroup.__call__().AndReturn(dummy_task())
1479
scheduler.wallclock().AndReturn(start_time + stack.timeout_secs() + 1)
1485
self.assertEqual(stack.state,
1486
(parser.Stack.CREATE, parser.Stack.FAILED))
1487
self.assertEqual(stack.status_reason, 'Create timed out')
1084
1489
self.m.VerifyAll()
1124
1529
parser.Template({}))
1125
1530
self.assertRaises(ValueError, parser.Stack, None, '#test',
1126
1531
parser.Template({}))
1534
def test_resource_state_get_att(self):
1536
'Resources': {'AResource': {'Type': 'GenericResourceType'}},
1537
'Outputs': {'TestOutput': {'Value': {
1538
'Fn::GetAtt': ['AResource', 'Foo']}}
1542
self.stack = parser.Stack(self.ctx, 'resource_state_get_att',
1543
template.Template(tmpl))
1546
self.assertEqual(self.stack.state,
1547
(parser.Stack.CREATE, parser.Stack.COMPLETE))
1548
self.assertTrue('AResource' in self.stack)
1549
rsrc = self.stack['AResource']
1550
rsrc.resource_id_set('aaaa')
1551
self.assertEqual('AResource', rsrc.FnGetAtt('Foo'))
1553
for action, status in (
1554
(rsrc.CREATE, rsrc.IN_PROGRESS),
1555
(rsrc.CREATE, rsrc.COMPLETE),
1556
(rsrc.UPDATE, rsrc.IN_PROGRESS),
1557
(rsrc.UPDATE, rsrc.COMPLETE)):
1558
rsrc.state_set(action, status)
1559
self.assertEqual('AResource', self.stack.output('TestOutput'))
1560
for action, status in (
1561
(rsrc.CREATE, rsrc.FAILED),
1562
(rsrc.DELETE, rsrc.IN_PROGRESS),
1563
(rsrc.DELETE, rsrc.FAILED),
1564
(rsrc.DELETE, rsrc.COMPLETE),
1565
(rsrc.UPDATE, rsrc.FAILED)):
1566
rsrc.state_set(action, status)
1567
self.assertEqual(None, self.stack.output('TestOutput'))
1570
def test_resource_required_by(self):
1571
tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType'},
1572
'BResource': {'Type': 'GenericResourceType',
1573
'DependsOn': 'AResource'},
1574
'CResource': {'Type': 'GenericResourceType',
1575
'DependsOn': 'BResource'},
1576
'DResource': {'Type': 'GenericResourceType',
1577
'DependsOn': 'BResource'}}}
1579
self.stack = parser.Stack(self.ctx, 'depends_test_stack',
1580
template.Template(tmpl))
1583
self.assertEqual(self.stack.state,
1584
(parser.Stack.CREATE, parser.Stack.COMPLETE))
1586
self.assertEqual(['BResource'],
1587
self.stack['AResource'].required_by())
1588
self.assertEqual([],
1589
self.stack['CResource'].required_by())
1590
required_by = self.stack['BResource'].required_by()
1591
self.assertEqual(2, len(required_by))
1592
for r in ['CResource', 'DResource']:
1593
self.assertIn(r, required_by)