2
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3
# not use this file except in compliance with the License. You may obtain
4
# a copy of the License at
6
# http://www.apache.org/licenses/LICENSE-2.0
8
# Unless required by applicable law or agreed to in writing, software
9
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
# License for the specific language governing permissions and limitations
20
from keystoneclient import exceptions as kc_exceptions
23
from oslo.config import cfg
26
from heat.common import context
27
from heat.common import exception
28
from heat.common import heat_keystoneclient as hkc
29
from heat.common import template_format
30
import heat.db.api as db_api
31
from heat.engine.cfn import functions as cfn_funcs
32
from heat.engine.cfn import template as cfn_t
33
from heat.engine.clients.os import keystone
34
from heat.engine.clients.os import nova
35
from heat.engine import environment
36
from heat.engine import function
37
from heat.engine.hot import template as hot_t
38
from heat.engine import parameters
39
from heat.engine import parser
40
from heat.engine import resource
41
from heat.engine import rsrc_defn
42
from heat.engine import scheduler
43
from heat.engine import template
44
from heat.tests import common
45
from heat.tests import fakes
46
from heat.tests import generic_resource as generic_rsrc
47
from heat.tests import utils
48
from heat.tests.v1_1 import fakes as fakes_v1_1
52
tmpl = template.Template(mapping_template)
53
return function.resolve(tmpl.parse(None, raw))
56
class ParserTest(common.HeatTestCase):
59
raw = ['foo', 'bar', 'baz']
61
for i in six.moves.xrange(len(raw)):
62
self.assertEqual(raw[i], parsed[i])
63
self.assertIsNot(raw, parsed)
66
raw = {'foo': 'bar', 'blarg': 'wibble'}
69
self.assertEqual(raw[k], parsed[k])
70
self.assertIsNot(raw, parsed)
72
def test_dict_list(self):
73
raw = {'foo': ['bar', 'baz'], 'blarg': 'wibble'}
75
self.assertEqual(raw['blarg'], parsed['blarg'])
76
for i in six.moves.xrange(len(raw['foo'])):
77
self.assertEqual(raw['foo'][i], parsed['foo'][i])
78
self.assertIsNot(raw, parsed)
79
self.assertIsNot(raw['foo'], parsed['foo'])
81
def test_list_dict(self):
82
raw = [{'foo': 'bar', 'blarg': 'wibble'}, 'baz', 'quux']
84
for i in six.moves.xrange(1, len(raw)):
85
self.assertEqual(raw[i], parsed[i])
87
self.assertEqual(raw[0][k], parsed[0][k])
88
self.assertIsNot(raw, parsed)
89
self.assertIsNot(raw[0], parsed[0])
92
raw = {'Fn::Join': [' ', ['foo', 'bar', 'baz']]}
93
self.assertEqual('foo bar baz', join(raw))
95
def test_join_none(self):
96
raw = {'Fn::Join': [' ', ['foo', None, 'baz']]}
97
self.assertEqual('foo baz', join(raw))
99
def test_join_list(self):
100
raw = [{'Fn::Join': [' ', ['foo', 'bar', 'baz']]}, 'blarg', 'wibble']
102
self.assertEqual('foo bar baz', parsed[0])
103
for i in six.moves.xrange(1, len(raw)):
104
self.assertEqual(raw[i], parsed[i])
105
self.assertIsNot(raw, parsed)
107
def test_join_dict_val(self):
108
raw = {'quux': {'Fn::Join': [' ', ['foo', 'bar', 'baz']]},
111
self.assertEqual('foo bar baz', parsed['quux'])
112
self.assertEqual(raw['blarg'], parsed['blarg'])
113
self.assertIsNot(raw, parsed)
116
mapping_template = template_format.parse('''{
117
"AWSTemplateFormatVersion" : "2010-09-09",
120
"TestKey" : { "TestValue" : "wibble" }
123
"ValueList" : [ "foo", "bar" ],
124
"ValueString" : "baz"
126
"MapList": [ "foo", { "bar" : "baz" } ],
127
"MapString": "foobar"
131
empty_template = template_format.parse('''{
132
"HeatTemplateFormatVersion" : "2012-12-12",
135
parameter_template = template_format.parse('''{
136
"HeatTemplateFormatVersion" : "2012-12-12",
138
"foo" : { "Type" : "String" },
139
"blarg" : { "Type" : "String", "Default": "quux" }
144
resource_template = template_format.parse('''{
145
"HeatTemplateFormatVersion" : "2012-12-12",
147
"foo" : { "Type" : "GenericResourceType" },
148
"blarg" : { "Type" : "GenericResourceType" }
153
class DummyClass(object):
156
def metadata_get(self):
159
def metadata_set(self, metadata):
160
self.metadata = metadata
163
class TemplateTest(common.HeatTestCase):
166
super(TemplateTest, self).setUp()
167
self.ctx = utils.dummy_context()
169
resource._register_class('GenericResourceType',
170
generic_rsrc.GenericResource)
173
def resolve(snippet, template, stack=None):
174
return function.resolve(template.parse(stack, snippet))
176
def test_defaults(self):
177
empty = parser.Template(empty_template)
178
self.assertNotIn('AWSTemplateFormatVersion', empty)
179
self.assertEqual('No description', empty['Description'])
180
self.assertEqual({}, empty['Mappings'])
181
self.assertEqual({}, empty['Resources'])
182
self.assertEqual({}, empty['Outputs'])
184
def test_aws_version(self):
185
tmpl = parser.Template(mapping_template)
186
self.assertEqual(('AWSTemplateFormatVersion', '2010-09-09'),
189
def test_heat_version(self):
190
tmpl = parser.Template(resource_template)
191
self.assertEqual(('HeatTemplateFormatVersion', '2012-12-12'),
194
def test_invalid_hot_version(self):
195
invalid_hot_version_tmp = template_format.parse(
197
"heat_template_version" : "2012-12-12",
199
init_ex = self.assertRaises(exception.InvalidTemplateVersion,
200
parser.Template, invalid_hot_version_tmp)
201
valid_versions = ['2014-10-16', '2013-05-23']
202
ex_error_msg = ('The template version is invalid: '
203
'"heat_template_version: 2012-12-12". '
204
'"heat_template_version" should be one of: %s'
205
% ', '.join(valid_versions))
206
self.assertEqual(ex_error_msg, six.text_type(init_ex))
208
def test_invalid_version_not_in_hot_versions(self):
209
invalid_hot_version_tmp = template_format.parse(
211
"heat_template_version" : "2012-12-12",
214
('heat_template_version', '2013-05-23'): hot_t.HOTemplate20130523,
215
('heat_template_version', '2013-06-23'): hot_t.HOTemplate20130523
218
temp_copy = copy.deepcopy(template._template_classes)
219
template._template_classes = versions
220
init_ex = self.assertRaises(exception.InvalidTemplateVersion,
221
parser.Template, invalid_hot_version_tmp)
222
ex_error_msg = ('The template version is invalid: '
223
'"heat_template_version: 2012-12-12". '
224
'"heat_template_version" should be '
225
'one of: 2013-05-23, 2013-06-23')
226
self.assertEqual(ex_error_msg, six.text_type(init_ex))
227
template._template_classes = temp_copy
229
def test_invalid_aws_version(self):
230
invalid_aws_version_tmp = template_format.parse(
232
"AWSTemplateFormatVersion" : "2012-12-12",
234
init_ex = self.assertRaises(exception.InvalidTemplateVersion,
235
parser.Template, invalid_aws_version_tmp)
236
ex_error_msg = ('The template version is invalid: '
237
'"AWSTemplateFormatVersion: 2012-12-12". '
238
'"AWSTemplateFormatVersion" should be: 2010-09-09')
239
self.assertEqual(ex_error_msg, six.text_type(init_ex))
241
def test_invalid_version_not_in_aws_versions(self):
242
invalid_aws_version_tmp = template_format.parse(
244
"AWSTemplateFormatVersion" : "2012-12-12",
247
('AWSTemplateFormatVersion', '2010-09-09'): cfn_t.CfnTemplate,
248
('AWSTemplateFormatVersion', '2011-06-23'): cfn_t.CfnTemplate
250
temp_copy = copy.deepcopy(template._template_classes)
251
template._template_classes = versions
253
init_ex = self.assertRaises(exception.InvalidTemplateVersion,
254
parser.Template, invalid_aws_version_tmp)
255
ex_error_msg = ('The template version is invalid: '
256
'"AWSTemplateFormatVersion: 2012-12-12". '
257
'"AWSTemplateFormatVersion" should be '
258
'one of: 2010-09-09, 2011-06-23')
259
self.assertEqual(ex_error_msg, six.text_type(init_ex))
260
template._template_classes = temp_copy
262
def test_invalid_heat_version(self):
263
invalid_heat_version_tmp = template_format.parse(
265
"HeatTemplateFormatVersion" : "2010-09-09",
267
init_ex = self.assertRaises(exception.InvalidTemplateVersion,
268
parser.Template, invalid_heat_version_tmp)
269
ex_error_msg = ('The template version is invalid: '
270
'"HeatTemplateFormatVersion: 2010-09-09". '
271
'"HeatTemplateFormatVersion" should be: 2012-12-12')
272
self.assertEqual(ex_error_msg, six.text_type(init_ex))
274
def test_invalid_version_not_in_heat_versions(self):
275
invalid_heat_version_tmp = template_format.parse(
277
"HeatTemplateFormatVersion" : "2010-09-09",
280
('HeatTemplateFormatVersion', '2012-12-12'): cfn_t.CfnTemplate,
281
('HeatTemplateFormatVersion', '2014-12-12'): cfn_t.CfnTemplate
283
temp_copy = copy.deepcopy(template._template_classes)
284
template._template_classes = versions
286
init_ex = self.assertRaises(exception.InvalidTemplateVersion,
287
parser.Template, invalid_heat_version_tmp)
288
ex_error_msg = ('The template version is invalid: '
289
'"HeatTemplateFormatVersion: 2010-09-09". '
290
'"HeatTemplateFormatVersion" should be '
291
'one of: 2012-12-12, 2014-12-12')
292
self.assertEqual(ex_error_msg, six.text_type(init_ex))
294
template._template_classes = temp_copy
296
def test_invalid_template(self):
306
TestKey: {TestKey1: "Value1" TestKey2: "Value2"}
309
self.assertRaises(ValueError, template_format.parse, scanner_error)
310
self.assertRaises(ValueError, template_format.parse, parser_error)
312
def test_invalid_section(self):
313
tmpl = parser.Template({'HeatTemplateFormatVersion': '2012-12-12',
315
self.assertNotIn('Foo', tmpl)
317
def test_find_in_map(self):
318
tmpl = parser.Template(mapping_template)
319
stack = parser.Stack(self.ctx, 'test', tmpl)
320
find = {'Fn::FindInMap': ["ValidMapping", "TestKey", "TestValue"]}
321
self.assertEqual("wibble", self.resolve(find, tmpl, stack))
323
def test_find_in_invalid_map(self):
324
tmpl = parser.Template(mapping_template)
325
stack = parser.Stack(self.ctx, 'test', tmpl)
326
finds = ({'Fn::FindInMap': ["InvalidMapping", "ValueList", "foo"]},
327
{'Fn::FindInMap': ["InvalidMapping", "ValueString", "baz"]},
328
{'Fn::FindInMap': ["MapList", "foo", "bar"]},
329
{'Fn::FindInMap': ["MapString", "foo", "bar"]})
332
self.assertRaises((KeyError, TypeError), self.resolve,
335
def test_bad_find_in_map(self):
336
tmpl = parser.Template(mapping_template)
337
stack = parser.Stack(self.ctx, 'test', tmpl)
338
finds = ({'Fn::FindInMap': "String"},
339
{'Fn::FindInMap': {"Dict": "String"}},
340
{'Fn::FindInMap': ["ShortList", "foo"]},
341
{'Fn::FindInMap': ["ReallyShortList"]})
344
self.assertRaises(KeyError, self.resolve, find, tmpl, stack)
346
def test_param_refs(self):
347
tmpl = parser.Template(parameter_template)
348
env = environment.Environment({'foo': 'bar', 'blarg': 'wibble'})
349
stack = parser.Stack(self.ctx, 'test', tmpl, env)
350
p_snippet = {"Ref": "foo"}
351
self.assertEqual("bar", self.resolve(p_snippet, tmpl, stack))
353
def test_param_ref_missing(self):
354
tmpl = parser.Template(parameter_template)
355
env = environment.Environment({'foo': 'bar'})
356
stack = parser.Stack(self.ctx, 'test', tmpl, env)
357
stack.env = environment.Environment({})
358
stack.parameters = parameters.Parameters(stack.identifier(), tmpl)
359
snippet = {"Ref": "foo"}
360
self.assertRaises(exception.UserParameterMissing,
362
snippet, tmpl, stack)
364
def test_resource_refs(self):
365
tmpl = parser.Template(resource_template)
366
stack = parser.Stack(self.ctx, 'test', tmpl)
368
self.m.StubOutWithMock(stack['foo'], 'FnGetRefId')
369
stack['foo'].FnGetRefId().MultipleTimes().AndReturn('bar')
372
r_snippet = {"Ref": "foo"}
373
self.assertEqual("bar", self.resolve(r_snippet, tmpl, stack))
376
def test_resource_refs_param(self):
377
tmpl = parser.Template(resource_template)
378
stack = parser.Stack(self.ctx, 'test', tmpl)
380
p_snippet = {"Ref": "baz"}
381
parsed = tmpl.parse(stack, p_snippet)
382
self.assertTrue(isinstance(parsed, cfn_funcs.ParamRef))
384
def test_select_from_list(self):
385
tmpl = parser.Template(empty_template)
386
data = {"Fn::Select": ["1", ["foo", "bar"]]}
387
self.assertEqual("bar", self.resolve(data, tmpl))
389
def test_select_from_list_integer_index(self):
390
tmpl = parser.Template(empty_template)
391
data = {"Fn::Select": [1, ["foo", "bar"]]}
392
self.assertEqual("bar", self.resolve(data, tmpl))
394
def test_select_from_list_out_of_bound(self):
395
tmpl = parser.Template(empty_template)
396
data = {"Fn::Select": ["0", ["foo", "bar"]]}
397
self.assertEqual("foo", self.resolve(data, tmpl))
398
data = {"Fn::Select": ["1", ["foo", "bar"]]}
399
self.assertEqual("bar", self.resolve(data, tmpl))
400
data = {"Fn::Select": ["2", ["foo", "bar"]]}
401
self.assertEqual("", self.resolve(data, tmpl))
403
def test_select_from_dict(self):
404
tmpl = parser.Template(empty_template)
405
data = {"Fn::Select": ["red", {"red": "robin", "re": "foo"}]}
406
self.assertEqual("robin", self.resolve(data, tmpl))
408
def test_select_from_none(self):
409
tmpl = parser.Template(empty_template)
410
data = {"Fn::Select": ["red", None]}
411
self.assertEqual("", self.resolve(data, tmpl))
413
def test_select_from_dict_not_existing(self):
414
tmpl = parser.Template(empty_template)
415
data = {"Fn::Select": ["green", {"red": "robin", "re": "foo"}]}
416
self.assertEqual("", self.resolve(data, tmpl))
418
def test_select_from_serialized_json_map(self):
419
tmpl = parser.Template(empty_template)
420
js = json.dumps({"red": "robin", "re": "foo"})
421
data = {"Fn::Select": ["re", js]}
422
self.assertEqual("foo", self.resolve(data, tmpl))
424
def test_select_from_serialized_json_list(self):
425
tmpl = parser.Template(empty_template)
426
js = json.dumps(["foo", "fee", "fum"])
427
data = {"Fn::Select": ["0", js]}
428
self.assertEqual("foo", self.resolve(data, tmpl))
430
def test_select_empty_string(self):
431
tmpl = parser.Template(empty_template)
432
data = {"Fn::Select": ["0", '']}
433
self.assertEqual("", self.resolve(data, tmpl))
434
data = {"Fn::Select": ["1", '']}
435
self.assertEqual("", self.resolve(data, tmpl))
436
data = {"Fn::Select": ["one", '']}
437
self.assertEqual("", self.resolve(data, tmpl))
440
tmpl = parser.Template(empty_template)
441
join = {"Fn::Join": [" ", ["foo", "bar"]]}
442
self.assertEqual("foo bar", self.resolve(join, tmpl))
444
def test_split_ok(self):
445
tmpl = parser.Template(empty_template)
446
data = {"Fn::Split": [";", "foo; bar; achoo"]}
447
self.assertEqual(['foo', ' bar', ' achoo'], self.resolve(data, tmpl))
449
def test_split_no_delim_in_str(self):
450
tmpl = parser.Template(empty_template)
451
data = {"Fn::Split": [";", "foo, bar, achoo"]}
452
self.assertEqual(['foo, bar, achoo'], self.resolve(data, tmpl))
454
def test_base64(self):
455
tmpl = parser.Template(empty_template)
456
snippet = {"Fn::Base64": "foobar"}
457
# For now, the Base64 function just returns the original text, and
458
# does not convert to base64 (see issue #133)
459
self.assertEqual("foobar", self.resolve(snippet, tmpl))
461
def test_get_azs(self):
462
tmpl = parser.Template(empty_template)
463
snippet = {"Fn::GetAZs": ""}
464
self.assertEqual(["nova"], self.resolve(snippet, tmpl))
466
def test_get_azs_with_stack(self):
467
tmpl = parser.Template(empty_template)
468
snippet = {"Fn::GetAZs": ""}
469
stack = parser.Stack(self.ctx, 'test_stack',
470
parser.Template(empty_template))
471
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
472
fc = fakes_v1_1.FakeClient()
473
nova.NovaClientPlugin._create().AndReturn(fc)
475
self.assertEqual(["nova1"], self.resolve(snippet, tmpl, stack))
477
def test_replace_string_values(self):
478
tmpl = parser.Template(empty_template)
479
snippet = {"Fn::Replace": [
480
{'$var1': 'foo', '%var2%': 'bar'},
483
self.assertEqual('foo is bar', self.resolve(snippet, tmpl))
485
def test_replace_number_values(self):
486
tmpl = parser.Template(empty_template)
487
snippet = {"Fn::Replace": [
488
{'$var1': 1, '%var2%': 2},
489
'$var1 is not %var2%'
491
self.assertEqual('1 is not 2', self.resolve(snippet, tmpl))
493
snippet = {"Fn::Replace": [
494
{'$var1': 1.3, '%var2%': 2.5},
495
'$var1 is not %var2%'
497
self.assertEqual('1.3 is not 2.5', self.resolve(snippet, tmpl))
499
def test_replace_none_values(self):
500
tmpl = parser.Template(empty_template)
501
snippet = {"Fn::Replace": [
502
{'$var1': None, '${var2}': None},
503
'"$var1" is "${var2}"'
505
self.assertEqual('"" is ""', self.resolve(snippet, tmpl))
507
def test_replace_missing_key(self):
508
tmpl = parser.Template(empty_template)
509
snippet = {"Fn::Replace": [
510
{'$var1': 'foo', 'var2': 'bar'},
511
'"$var1" is "${var3}"'
513
self.assertEqual('"foo" is "${var3}"', self.resolve(snippet, tmpl))
515
def test_replace_param_values(self):
516
tmpl = parser.Template(parameter_template)
517
env = environment.Environment({'foo': 'wibble'})
518
stack = parser.Stack(self.ctx, 'test_stack', tmpl, env)
519
snippet = {"Fn::Replace": [
520
{'$var1': {'Ref': 'foo'}, '%var2%': {'Ref': 'blarg'}},
523
self.assertEqual('wibble is quux', self.resolve(snippet, tmpl, stack))
525
def test_member_list2map_good(self):
526
tmpl = parser.Template(empty_template)
527
snippet = {"Fn::MemberListToMap": [
528
'Name', 'Value', ['.member.0.Name=metric',
529
'.member.0.Value=cpu',
530
'.member.1.Name=size',
531
'.member.1.Value=56']]}
532
self.assertEqual({'metric': 'cpu', 'size': '56'},
533
self.resolve(snippet, tmpl))
535
def test_member_list2map_good2(self):
536
tmpl = parser.Template(empty_template)
537
snippet = {"Fn::MemberListToMap": [
538
'Key', 'Value', ['.member.2.Key=metric',
539
'.member.2.Value=cpu',
540
'.member.5.Key=size',
541
'.member.5.Value=56']]}
542
self.assertEqual({'metric': 'cpu', 'size': '56'},
543
self.resolve(snippet, tmpl))
545
def test_resource_facade(self):
546
metadata_snippet = {'Fn::ResourceFacade': 'Metadata'}
547
deletion_policy_snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
548
update_policy_snippet = {'Fn::ResourceFacade': 'UpdatePolicy'}
550
parent_resource = DummyClass()
551
parent_resource.metadata_set({"foo": "bar"})
553
parent_resource.t = rsrc_defn.ResourceDefinition(
554
'parent', 'SomeType',
555
deletion_policy=rsrc_defn.ResourceDefinition.RETAIN,
556
update_policy={"blarg": "wibble"})
558
parent_resource.stack = parser.Stack(self.ctx, 'toplevel_stack',
559
parser.Template(empty_template))
560
stack = parser.Stack(self.ctx, 'test_stack',
561
parser.Template(empty_template),
562
parent_resource=parent_resource)
563
self.assertEqual({"foo": "bar"},
564
self.resolve(metadata_snippet, stack.t, stack))
565
self.assertEqual('Retain',
566
self.resolve(deletion_policy_snippet, stack.t, stack))
567
self.assertEqual({"blarg": "wibble"},
568
self.resolve(update_policy_snippet, stack.t, stack))
570
def test_resource_facade_function(self):
571
deletion_policy_snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
573
parent_resource = DummyClass()
574
parent_resource.metadata_set({"foo": "bar"})
575
parent_resource.stack = parser.Stack(self.ctx, 'toplevel_stack',
576
parser.Template(empty_template))
577
del_policy = cfn_funcs.Join(parent_resource.stack,
578
'Fn::Join', ['eta', ['R', 'in']])
579
parent_resource.t = rsrc_defn.ResourceDefinition(
580
'parent', 'SomeType',
581
deletion_policy=del_policy)
583
stack = parser.Stack(self.ctx, 'test_stack',
584
parser.Template(empty_template),
585
parent_resource=parent_resource)
586
self.assertEqual('Retain',
587
self.resolve(deletion_policy_snippet, stack.t, stack))
589
def test_resource_facade_invalid_arg(self):
590
snippet = {'Fn::ResourceFacade': 'wibble'}
591
stack = parser.Stack(self.ctx, 'test_stack',
592
parser.Template(empty_template))
593
error = self.assertRaises(ValueError,
597
self.assertIn(snippet.keys()[0], six.text_type(error))
599
def test_resource_facade_missing_deletion_policy(self):
600
snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
602
parent_resource = DummyClass()
603
parent_resource.metadata_set({"foo": "bar"})
604
parent_resource.t = rsrc_defn.ResourceDefinition('parent', 'SomeType')
606
parent_resource.stack = parser.Stack(self.ctx, 'toplevel_stack',
607
parser.Template(empty_template))
608
stack = parser.Stack(self.ctx, 'test_stack',
609
parser.Template(empty_template),
610
parent_resource=parent_resource)
611
self.assertEqual('Delete', self.resolve(snippet, stack.t, stack))
613
def test_prevent_parameters_access(self):
614
expected_description = "This can be accessed"
615
tmpl = parser.Template({'AWSTemplateFormatVersion': '2010-09-09',
616
'Description': expected_description,
618
{'foo': {'Type': 'String', 'Required': True}}})
619
self.assertEqual(expected_description, tmpl['Description'])
620
keyError = self.assertRaises(KeyError, tmpl.__getitem__, 'Parameters')
621
self.assertIn("can not be accessed directly", six.text_type(keyError))
623
def test_parameters_section_not_iterable(self):
624
expected_description = "This can be accessed"
625
tmpl = parser.Template({'AWSTemplateFormatVersion': '2010-09-09',
626
'Description': expected_description,
628
{'foo': {'Type': 'String', 'Required': True}}})
629
self.assertEqual(expected_description, tmpl['Description'])
630
self.assertNotIn('Parameters', tmpl.keys())
632
def test_add_resource(self):
633
cfn_tpl = template_format.parse('''
634
AWSTemplateFormatVersion: 2010-09-09
637
Type: AWS::EC2::Instance
643
DeletionPolicy: Retain
647
source = parser.Template(cfn_tpl)
648
empty = parser.Template(copy.deepcopy(empty_template))
649
stack = parser.Stack(self.ctx, 'test_stack', source)
651
for defn in source.resource_definitions(stack).values():
652
empty.add_resource(defn)
654
self.assertEqual(cfn_tpl['Resources'], empty.t['Resources'])
657
class TemplateFnErrorTest(common.HeatTestCase):
659
('select_from_list_not_int',
660
dict(expect=TypeError,
661
snippet={"Fn::Select": ["one", ["foo", "bar"]]})),
662
('select_from_dict_not_str',
663
dict(expect=TypeError,
664
snippet={"Fn::Select": ["1", {"red": "robin", "re": "foo"}]})),
665
('select_from_serialized_json_wrong',
666
dict(expect=ValueError,
667
snippet={"Fn::Select": ["not", "no json"]})),
668
('select_wrong_num_args_1',
669
dict(expect=ValueError,
670
snippet={"Fn::Select": []})),
671
('select_wrong_num_args_2',
672
dict(expect=ValueError,
673
snippet={"Fn::Select": ["4"]})),
674
('select_wrong_num_args_3',
675
dict(expect=ValueError,
676
snippet={"Fn::Select": ["foo", {"foo": "bar"}, ""]})),
677
('select_wrong_num_args_4',
678
dict(expect=TypeError,
679
snippet={'Fn::Select': [['f'], {'f': 'food'}]})),
681
dict(expect=ValueError,
682
snippet={"Fn::Split": ["foo, bar, achoo"]})),
684
dict(expect=TypeError,
685
snippet={"Fn::Split": "foo, bar, achoo"})),
687
dict(expect=TypeError,
688
snippet={"Fn::Base64": ["foobar"]})),
690
dict(expect=TypeError,
691
snippet={"Fn::Base64": {"foo": "bar"}})),
692
('replace_list_value',
693
dict(expect=TypeError,
694
snippet={"Fn::Replace": [
695
{'$var1': 'foo', '%var2%': ['bar']},
696
'$var1 is %var2%']})),
697
('replace_list_mapping',
698
dict(expect=TypeError,
699
snippet={"Fn::Replace": [
700
['var1', 'foo', 'var2', 'bar'],
701
'$var1 is ${var2}']})),
703
dict(expect=TypeError,
704
snippet={"Fn::Replace": {}})),
705
('replace_missing_template',
706
dict(expect=ValueError,
707
snippet={"Fn::Replace": [['var1', 'foo', 'var2', 'bar']]})),
708
('replace_none_template',
709
dict(expect=TypeError,
710
snippet={"Fn::Replace": [['var2', 'bar'], None]})),
711
('replace_list_string',
712
dict(expect=TypeError,
713
snippet={"Fn::Replace": [
714
{'var1': 'foo', 'var2': 'bar'},
715
['$var1 is ${var2}']]})),
717
dict(expect=TypeError,
718
snippet={"Fn::Join": [" ", "foo"]})),
720
dict(expect=TypeError,
721
snippet={"Fn::Join": [" ", {"foo": "bar"}]})),
722
('join_wrong_num_args_1',
723
dict(expect=ValueError,
724
snippet={"Fn::Join": []})),
725
('join_wrong_num_args_2',
726
dict(expect=ValueError,
727
snippet={"Fn::Join": [" "]})),
728
('join_wrong_num_args_3',
729
dict(expect=ValueError,
730
snippet={"Fn::Join": [" ", {"foo": "bar"}, ""]})),
731
('join_string_nodelim',
732
dict(expect=TypeError,
733
snippet={"Fn::Join": "o"})),
734
('join_string_nodelim_1',
735
dict(expect=TypeError,
736
snippet={"Fn::Join": "oh"})),
737
('join_string_nodelim_2',
738
dict(expect=TypeError,
739
snippet={"Fn::Join": "ohh"})),
740
('join_dict_nodelim1',
741
dict(expect=TypeError,
742
snippet={"Fn::Join": {"foo": "bar"}})),
743
('join_dict_nodelim2',
744
dict(expect=TypeError,
745
snippet={"Fn::Join": {"foo": "bar", "blarg": "wibble"}})),
746
('join_dict_nodelim3',
747
dict(expect=TypeError,
748
snippet={"Fn::Join": {"foo": "bar", "blarg": "wibble",
750
('member_list2map_no_key_or_val',
751
dict(expect=TypeError,
752
snippet={"Fn::MemberListToMap": [
753
'Key', ['.member.2.Key=metric',
754
'.member.2.Value=cpu',
755
'.member.5.Key=size',
756
'.member.5.Value=56']]})),
757
('member_list2map_no_list',
758
dict(expect=TypeError,
759
snippet={"Fn::MemberListToMap": [
760
'Key', '.member.2.Key=metric']})),
761
('member_list2map_not_string',
762
dict(expect=TypeError,
763
snippet={"Fn::MemberListToMap": [
764
'Name', ['Value'], ['.member.0.Name=metric',
765
'.member.0.Value=cpu',
766
'.member.1.Name=size',
767
'.member.1.Value=56']]})),
770
def test_bad_input(self):
771
tmpl = parser.Template(empty_template)
772
resolve = lambda s: TemplateTest.resolve(s, tmpl)
773
error = self.assertRaises(self.expect,
776
self.assertIn(self.snippet.keys()[0], six.text_type(error))
779
class ResolveDataTest(common.HeatTestCase):
782
super(ResolveDataTest, self).setUp()
783
self.username = 'parser_stack_test_user'
785
self.ctx = utils.dummy_context()
787
self.stack = parser.Stack(self.ctx, 'resolve_test_stack',
788
template.Template(empty_template),
789
environment.Environment({}))
791
def resolve(self, snippet):
792
return function.resolve(self.stack.t.parse(self.stack, snippet))
794
def test_stack_resolve_runtime_data_deprecated(self):
795
stack = parser.Stack(self.ctx, 'test_stack',
796
parser.Template(empty_template),
799
with warnings.catch_warnings(record=True) as ws:
800
warnings.filterwarnings('always')
802
# Work around http://bugs.python.org/issue4180
803
getattr(parser, '__warningregistry__', {}).clear()
805
test_data = {'foo': 'bar'}
806
resolved = stack.resolve_runtime_data(test_data)
809
self.assertTrue(issubclass(ws[0].category, DeprecationWarning))
811
self.assertEqual(test_data, resolved)
813
def test_join_split(self):
815
snippet = {'Fn::Join': [';', ['one', 'two', 'three']]}
816
self.assertEqual('one;two;three',
817
self.resolve(snippet))
820
snippet = {'Fn::Split': [';', snippet]}
821
self.assertEqual(['one', 'two', 'three'],
822
self.resolve(snippet))
824
def test_split_join_split_join(self):
825
# each snippet in this test encapsulates
826
# the snippet from the previous step, leading
827
# to increasingly nested function calls
830
snippet = {'Fn::Split': [',', 'one,two,three']}
831
self.assertEqual(['one', 'two', 'three'],
832
self.resolve(snippet))
835
snippet = {'Fn::Join': [';', snippet]}
836
self.assertEqual('one;two;three',
837
self.resolve(snippet))
839
# split then join then split
840
snippet = {'Fn::Split': [';', snippet]}
841
self.assertEqual(['one', 'two', 'three'],
842
self.resolve(snippet))
844
# split then join then split then join
845
snippet = {'Fn::Join': ['-', snippet]}
846
self.assertEqual('one-two-three',
847
self.resolve(snippet))
849
def test_join_recursive(self):
850
raw = {'Fn::Join': ['\n', [{'Fn::Join':
851
[' ', ['foo', 'bar']]}, 'baz']]}
852
self.assertEqual('foo bar\nbaz', self.resolve(raw))
854
def test_join_not_string(self):
855
snippet = {'Fn::Join': ['\n', [{'Fn::Join':
856
[' ', ['foo', 45]]}, 'baz']]}
857
error = self.assertRaises(TypeError,
860
self.assertIn('45', six.text_type(error))
862
def test_base64_replace(self):
863
raw = {'Fn::Base64': {'Fn::Replace': [
864
{'foo': 'bar'}, 'Meet at the foo']}}
865
self.assertEqual('Meet at the bar',
868
def test_replace_base64(self):
869
raw = {'Fn::Replace': [{'foo': 'bar'}, {
870
'Fn::Base64': 'Meet at the foo'}]}
871
self.assertEqual('Meet at the bar',
874
def test_nested_selects(self):
876
'a': ['one', 'two', 'three'],
877
'b': ['een', 'twee', {'d': 'D', 'e': 'E'}]
879
raw = {'Fn::Select': ['a', data]}
880
self.assertEqual(data['a'],
883
raw = {'Fn::Select': ['b', data]}
884
self.assertEqual(data['b'],
888
'Fn::Select': ['1', {
889
'Fn::Select': ['b', data]}]}
890
self.assertEqual('twee',
894
'Fn::Select': ['e', {
895
'Fn::Select': ['2', {
896
'Fn::Select': ['b', data]}]}]}
897
self.assertEqual('E',
900
def test_member_list_select(self):
901
snippet = {'Fn::Select': ['metric', {"Fn::MemberListToMap": [
902
'Name', 'Value', ['.member.0.Name=metric',
903
'.member.0.Value=cpu',
904
'.member.1.Name=size',
905
'.member.1.Value=56']]}]}
906
self.assertEqual('cpu',
907
self.resolve(snippet))
910
class StackTest(common.HeatTestCase):
912
super(StackTest, self).setUp()
914
self.username = 'parser_stack_test_user'
915
self.tmpl = parser.Template(copy.deepcopy(empty_template))
917
self.ctx = utils.dummy_context()
919
resource._register_class('GenericResourceType',
920
generic_rsrc.GenericResource)
921
resource._register_class('ResourceWithPropsType',
922
generic_rsrc.ResourceWithProps)
923
resource._register_class('ResourceWithComplexAttributesType',
924
generic_rsrc.ResourceWithComplexAttributes)
925
resource._register_class('ResWithComplexPropsAndAttrs',
926
generic_rsrc.ResWithComplexPropsAndAttrs)
928
def test_stack_reads_tenant(self):
929
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
931
self.assertEqual('bar', stack.tenant_id)
933
def test_stack_reads_tenant_from_context_if_empty(self):
934
self.ctx.tenant_id = 'foo'
935
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
937
self.assertEqual('foo', stack.tenant_id)
939
def test_stack_reads_username(self):
940
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
942
self.assertEqual('bar', stack.username)
944
def test_stack_reads_username_from_context_if_empty(self):
945
self.ctx.username = 'foo'
946
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
948
self.assertEqual('foo', stack.username)
950
def test_stack_string_repr(self):
951
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl)
952
expected = 'Stack "%s" [%s]' % (stack.name, stack.id)
953
observed = str(stack)
954
self.assertEqual(expected, observed)
956
def test_state_defaults(self):
957
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl)
958
self.assertEqual(('CREATE', 'IN_PROGRESS'), stack.state)
959
self.assertEqual('', stack.status_reason)
961
def test_timeout_secs_default(self):
962
cfg.CONF.set_override('stack_action_timeout', 1000)
963
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl)
964
self.assertIsNone(stack.timeout_mins)
965
self.assertEqual(1000, stack.timeout_secs())
967
def test_timeout_secs(self):
968
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
970
self.assertEqual(600, stack.timeout_secs())
972
def test_no_auth_token(self):
973
ctx = utils.dummy_context()
974
ctx.auth_token = None
978
stack = parser.Stack(ctx, 'test_stack', self.tmpl)
979
self.assertEqual('abcd1234',
980
stack.clients.client('keystone').auth_token)
984
def test_state(self):
985
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
986
action=parser.Stack.CREATE,
987
status=parser.Stack.IN_PROGRESS)
988
self.assertEqual((parser.Stack.CREATE, parser.Stack.IN_PROGRESS),
990
stack.state_set(parser.Stack.CREATE, parser.Stack.COMPLETE, 'test')
991
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
993
stack.state_set(parser.Stack.DELETE, parser.Stack.COMPLETE, 'test')
994
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
997
def test_state_deleted(self):
998
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
999
action=parser.Stack.CREATE,
1000
status=parser.Stack.IN_PROGRESS)
1003
# Simulate a deleted stack
1004
self.m.StubOutWithMock(db_api, 'stack_get')
1005
db_api.stack_get(stack.context, stack.id).AndReturn(None)
1009
self.assertIsNone(stack.state_set(parser.Stack.CREATE,
1010
parser.Stack.COMPLETE, 'test'))
1013
def test_state_bad(self):
1014
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
1015
action=parser.Stack.CREATE,
1016
status=parser.Stack.IN_PROGRESS)
1017
self.assertEqual((parser.Stack.CREATE, parser.Stack.IN_PROGRESS),
1019
self.assertRaises(ValueError, stack.state_set,
1020
'baad', parser.Stack.COMPLETE, 'test')
1021
self.assertRaises(ValueError, stack.state_set,
1022
parser.Stack.CREATE, 'oops', 'test')
1024
def test_status_reason(self):
1025
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
1026
status_reason='quux')
1027
self.assertEqual('quux', stack.status_reason)
1028
stack.state_set(parser.Stack.CREATE, parser.Stack.IN_PROGRESS,
1030
self.assertEqual('wibble', stack.status_reason)
1032
def test_load_nonexistant_id(self):
1033
self.assertRaises(exception.NotFound, parser.Stack.load,
1036
def test_total_resources_empty(self):
1037
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
1038
status_reason='flimflam')
1039
self.assertEqual(0, stack.total_resources())
1041
def test_total_resources_generic(self):
1042
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
1044
{'A': {'Type': 'GenericResourceType'}}}
1045
stack = parser.Stack(self.ctx, 'test_stack', parser.Template(tpl),
1046
status_reason='blarg')
1047
self.assertEqual(1, stack.total_resources())
1049
def test_total_resources_nested(self):
1050
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
1052
{'A': {'Type': 'GenericResourceType'}}}
1053
stack = parser.Stack(self.ctx, 'test_stack', parser.Template(tpl),
1054
status_reason='blarg')
1056
stack['A'].nested = mock.Mock()
1057
stack['A'].nested.return_value.total_resources.return_value = 3
1058
self.assertEqual(4, stack.total_resources())
1060
def test_iter_resources(self):
1061
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
1063
{'A': {'Type': 'GenericResourceType'},
1064
'B': {'Type': 'GenericResourceType'}}}
1065
stack = parser.Stack(self.ctx, 'test_stack', parser.Template(tpl),
1066
status_reason='blarg')
1068
def get_more(nested_depth=0):
1073
stack['A'].nested = mock.MagicMock()
1074
stack['A'].nested.return_value.iter_resources.side_effect = get_more
1076
resource_generator = stack.iter_resources()
1077
self.assertIsNot(resource_generator, list)
1079
first_level_resources = list(resource_generator)
1080
self.assertEqual(2, len(first_level_resources))
1081
all_resources = list(stack.iter_resources(1))
1082
self.assertEqual(5, len(all_resources))
1084
def test_root_stack_no_parent(self):
1085
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
1087
{'A': {'Type': 'GenericResourceType'}}}
1088
stack = parser.Stack(self.ctx, 'test_stack', parser.Template(tpl),
1089
status_reason='blarg')
1091
self.assertEqual(stack, stack.root_stack)
1093
def test_root_stack_parent_no_stack(self):
1094
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
1096
{'A': {'Type': 'GenericResourceType'}}}
1097
stack = parser.Stack(self.ctx, 'test_stack', parser.Template(tpl),
1098
status_reason='blarg')
1100
stack.parent_resource = mock.Mock()
1101
stack.parent_resource.stack = None
1102
self.assertEqual(stack, stack.root_stack)
1104
def test_root_stack_with_parent(self):
1105
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
1107
{'A': {'Type': 'GenericResourceType'}}}
1108
stack = parser.Stack(self.ctx, 'test_stack', parser.Template(tpl),
1109
status_reason='blarg')
1111
stack.parent_resource = mock.Mock()
1112
stack.parent_resource.stack.root_stack = 'test value'
1113
self.assertEqual('test value', stack.root_stack)
1115
def test_load_parent_resource(self):
1116
self.stack = parser.Stack(self.ctx, 'load_parent_resource',
1119
stack = db_api.stack_get(self.ctx, self.stack.id)
1121
t = template.Template.load(self.ctx, stack.raw_template_id)
1122
self.m.StubOutWithMock(template.Template, 'load')
1123
template.Template.load(
1124
self.ctx, stack.raw_template_id, stack.raw_template
1127
env = environment.Environment(stack.parameters)
1128
self.m.StubOutWithMock(environment, 'Environment')
1129
environment.Environment(stack.parameters).AndReturn(env)
1131
self.m.StubOutWithMock(parser.Stack, '__init__')
1132
parser.Stack.__init__(self.ctx, stack.name, t, env, stack.id,
1133
stack.action, stack.status, stack.status_reason,
1134
stack.timeout, True, stack.disable_rollback,
1135
'parent', owner_id=None,
1136
stack_user_project_id=None,
1137
created_time=mox.IgnoreArg(),
1139
user_creds_id=stack.user_creds_id,
1140
tenant_id='test_tenant_id',
1141
use_stored_context=False,
1142
username=mox.IgnoreArg())
1145
parser.Stack.load(self.ctx, stack_id=self.stack.id,
1146
parent_resource='parent')
1150
def test_identifier(self):
1151
self.stack = parser.Stack(self.ctx, 'identifier_test',
1154
identifier = self.stack.identifier()
1155
self.assertEqual(self.stack.tenant_id, identifier.tenant)
1156
self.assertEqual('identifier_test', identifier.stack_name)
1157
self.assertTrue(identifier.stack_id)
1158
self.assertFalse(identifier.path)
1160
def test_get_stack_abandon_data(self):
1161
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
1162
'Parameters': {'param1': {'Type': 'String'}},
1164
{'A': {'Type': 'GenericResourceType'},
1165
'B': {'Type': 'GenericResourceType'}}}
1166
resources = '''{"A": {"status": "COMPLETE", "name": "A",
1167
"resource_data": {}, "resource_id": null, "action": "INIT",
1168
"type": "GenericResourceType", "metadata": {}},
1169
"B": {"status": "COMPLETE", "name": "B", "resource_data": {},
1170
"resource_id": null, "action": "INIT", "type": "GenericResourceType",
1172
env = environment.Environment({'parameters': {'param1': 'test'}})
1173
self.stack = parser.Stack(self.ctx, 'stack_details_test',
1174
parser.Template(tpl),
1176
stack_user_project_id='234',
1179
info = self.stack.prepare_abandon()
1180
self.assertEqual('CREATE', info['action'])
1181
self.assertIn('id', info)
1182
self.assertEqual('stack_details_test', info['name'])
1183
self.assertEqual(json.loads(resources), info['resources'])
1184
self.assertEqual('IN_PROGRESS', info['status'])
1185
self.assertEqual(tpl, info['template'])
1186
self.assertEqual('123', info['project_id'])
1187
self.assertEqual('234', info['stack_user_project_id'])
1188
self.assertEqual(env.params, info['environment']['parameters'])
1190
def test_set_param_id(self):
1191
self.stack = parser.Stack(self.ctx, 'param_arn_test',
1193
exp_prefix = ('arn:openstack:heat::test_tenant_id'
1194
':stacks/param_arn_test/')
1195
self.assertEqual(self.stack.parameters['AWS::StackId'],
1196
exp_prefix + 'None')
1198
identifier = self.stack.identifier()
1199
self.assertEqual(exp_prefix + self.stack.id,
1200
self.stack.parameters['AWS::StackId'])
1201
self.assertEqual(self.stack.parameters['AWS::StackId'],
1205
def test_set_param_id_update(self):
1206
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1208
'AResource': {'Type': 'ResourceWithPropsType',
1209
'Metadata': {'Bar': {'Ref': 'AWS::StackId'}},
1210
'Properties': {'Foo': 'abc'}}}}
1212
self.stack = parser.Stack(self.ctx, 'update_stack_arn_test',
1213
template.Template(tmpl))
1216
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
1219
stack_arn = self.stack.parameters['AWS::StackId']
1221
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
1223
'AResource': {'Type': 'ResourceWithPropsType',
1225
{'Ref': 'AWS::StackId'}},
1226
'Properties': {'Foo': 'xyz'}}}}
1228
updated_stack = parser.Stack(self.ctx, 'updated_stack',
1229
template.Template(tmpl2))
1231
self.stack.update(updated_stack)
1232
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
1234
self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
1237
stack_arn, self.stack['AResource'].metadata_get()['Bar'])
1239
def test_load_param_id(self):
1240
self.stack = parser.Stack(self.ctx, 'param_load_arn_test',
1243
identifier = self.stack.identifier()
1244
self.assertEqual(self.stack.parameters['AWS::StackId'],
1247
newstack = parser.Stack.load(self.ctx, stack_id=self.stack.id)
1248
self.assertEqual(identifier.arn(), newstack.parameters['AWS::StackId'])
1250
def test_load_reads_tenant_id(self):
1251
self.ctx.tenant_id = 'foobar'
1252
self.stack = parser.Stack(self.ctx, 'stack_name', self.tmpl)
1254
stack_id = self.stack.id
1255
self.ctx.tenant_id = None
1256
stack = parser.Stack.load(self.ctx, stack_id=stack_id)
1257
self.assertEqual('foobar', stack.tenant_id)
1259
def test_load_reads_username_from_db(self):
1260
self.ctx.username = 'foobar'
1261
self.stack = parser.Stack(self.ctx, 'stack_name', self.tmpl)
1263
stack_id = self.stack.id
1265
self.ctx.username = None
1266
stack = parser.Stack.load(self.ctx, stack_id=stack_id)
1267
self.assertEqual('foobar', stack.username)
1269
self.ctx.username = 'not foobar'
1270
stack = parser.Stack.load(self.ctx, stack_id=stack_id)
1271
self.assertEqual('foobar', stack.username)
1273
def test_load_all(self):
1274
stack1 = parser.Stack(self.ctx, 'stack1', self.tmpl)
1276
stack2 = parser.Stack(self.ctx, 'stack2', self.tmpl)
1279
stacks = list(parser.Stack.load_all(self.ctx))
1280
self.assertEqual(2, len(stacks))
1282
# Add another, nested, stack
1283
stack3 = parser.Stack(self.ctx, 'stack3', self.tmpl,
1287
# Should still be 2 without show_nested
1288
stacks = list(parser.Stack.load_all(self.ctx))
1289
self.assertEqual(2, len(stacks))
1291
stacks = list(parser.Stack.load_all(self.ctx, show_nested=True))
1292
self.assertEqual(3, len(stacks))
1294
# A backup stack should not be returned
1295
stack1._backup_stack()
1296
stacks = list(parser.Stack.load_all(self.ctx))
1297
self.assertEqual(2, len(stacks))
1299
stacks = list(parser.Stack.load_all(self.ctx, show_nested=True))
1300
self.assertEqual(3, len(stacks))
1302
def test_created_time(self):
1303
self.stack = parser.Stack(self.ctx, 'creation_time_test',
1305
self.assertIsNone(self.stack.created_time)
1307
self.assertIsNotNone(self.stack.created_time)
1309
def test_updated_time(self):
1310
self.stack = parser.Stack(self.ctx, 'updated_time_test',
1312
self.assertIsNone(self.stack.updated_time)
1316
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1317
'Resources': {'R1': {'Type': 'GenericResourceType'}}}
1318
newstack = parser.Stack(self.ctx, 'updated_time_test',
1319
parser.Template(tmpl))
1320
self.stack.update(newstack)
1321
self.assertIsNotNone(self.stack.updated_time)
1323
def test_access_policy_update(self):
1324
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1326
'R1': {'Type': 'GenericResourceType'},
1328
'Type': 'OS::Heat::AccessPolicy',
1330
'AllowedResources': ['R1']
1333
self.stack = parser.Stack(self.ctx, 'update_stack_access_policy_test',
1334
template.Template(tmpl))
1337
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
1340
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
1342
'R1': {'Type': 'GenericResourceType'},
1343
'R2': {'Type': 'GenericResourceType'},
1345
'Type': 'OS::Heat::AccessPolicy',
1347
'AllowedResources': ['R1', 'R2'],
1350
updated_stack = parser.Stack(self.ctx, 'updated_stack',
1351
template.Template(tmpl2))
1353
self.stack.update(updated_stack)
1354
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
1357
def test_delete(self):
1358
self.stack = parser.Stack(self.ctx, 'delete_test',
1360
stack_id = self.stack.store()
1362
db_s = db_api.stack_get(self.ctx, stack_id)
1363
self.assertIsNotNone(db_s)
1367
db_s = db_api.stack_get(self.ctx, stack_id)
1368
self.assertIsNone(db_s)
1369
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1372
def test_delete_user_creds(self):
1373
self.stack = parser.Stack(self.ctx, 'delete_test',
1375
stack_id = self.stack.store()
1377
db_s = db_api.stack_get(self.ctx, stack_id)
1378
self.assertIsNotNone(db_s)
1379
self.assertIsNotNone(db_s.user_creds_id)
1380
user_creds_id = db_s.user_creds_id
1381
db_creds = db_api.user_creds_get(db_s.user_creds_id)
1382
self.assertIsNotNone(db_creds)
1386
db_s = db_api.stack_get(self.ctx, stack_id)
1387
self.assertIsNone(db_s)
1388
db_creds = db_api.user_creds_get(user_creds_id)
1389
self.assertIsNone(db_creds)
1390
del_db_s = db_api.stack_get(self.ctx, stack_id, show_deleted=True)
1391
self.assertIsNone(del_db_s.user_creds_id)
1392
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1395
def test_delete_user_creds_gone_missing(self):
1396
'''Do not block stack deletion if user_creds is missing.
1398
It may happen that user_creds were deleted when a delete operation was
1399
stopped. We should be resilient to this and still complete the delete
1402
self.stack = parser.Stack(self.ctx, 'delete_test',
1404
stack_id = self.stack.store()
1406
db_s = db_api.stack_get(self.ctx, stack_id)
1407
self.assertIsNotNone(db_s)
1408
self.assertIsNotNone(db_s.user_creds_id)
1409
user_creds_id = db_s.user_creds_id
1410
db_creds = db_api.user_creds_get(db_s.user_creds_id)
1411
self.assertIsNotNone(db_creds)
1413
db_api.user_creds_delete(self.ctx, user_creds_id)
1417
db_s = db_api.stack_get(self.ctx, stack_id)
1418
self.assertIsNone(db_s)
1419
db_creds = db_api.user_creds_get(user_creds_id)
1420
self.assertIsNone(db_creds)
1421
del_db_s = db_api.stack_get(self.ctx, stack_id, show_deleted=True)
1422
self.assertIsNone(del_db_s.user_creds_id)
1423
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1426
def test_delete_user_creds_fail(self):
1427
'''Do not stop deleting stacks even failed deleting user_creds.
1429
It may happen that user_creds were incorrectly saved (truncated) and
1430
thus cannot be correctly retrieved (and decrypted). In this case,
1431
stack delete should not be stopped.
1433
self.stack = parser.Stack(self.ctx, 'delete_test', self.tmpl)
1434
stack_id = self.stack.store()
1436
db_s = db_api.stack_get(self.ctx, stack_id)
1437
self.assertIsNotNone(db_s)
1438
self.assertIsNotNone(db_s.user_creds_id)
1439
exc = exception.Error('Cannot get user credentials')
1440
self.patchobject(db_api, 'user_creds_get').side_effect = exc
1444
db_s = db_api.stack_get(self.ctx, stack_id)
1445
self.assertIsNone(db_s)
1446
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1449
def test_delete_trust(self):
1450
cfg.CONF.set_override('deferred_auth_method', 'trusts')
1451
self.stub_keystoneclient()
1453
self.stack = parser.Stack(
1454
self.ctx, 'delete_trust', self.tmpl)
1455
stack_id = self.stack.store()
1457
db_s = db_api.stack_get(self.ctx, stack_id)
1458
self.assertIsNotNone(db_s)
1462
db_s = db_api.stack_get(self.ctx, stack_id)
1463
self.assertIsNone(db_s)
1464
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1467
def test_delete_trust_trustor(self):
1468
cfg.CONF.set_override('deferred_auth_method', 'trusts')
1470
trustor_ctx = utils.dummy_context(user_id='thetrustor')
1471
self.m.StubOutWithMock(hkc, 'KeystoneClient')
1472
hkc.KeystoneClient(trustor_ctx).AndReturn(
1473
fakes.FakeKeystoneClient(user_id='thetrustor'))
1476
self.stack = parser.Stack(
1477
trustor_ctx, 'delete_trust_nt', self.tmpl)
1478
stack_id = self.stack.store()
1480
db_s = db_api.stack_get(self.ctx, stack_id)
1481
self.assertIsNotNone(db_s)
1483
user_creds_id = db_s.user_creds_id
1484
self.assertIsNotNone(user_creds_id)
1485
user_creds = db_api.user_creds_get(user_creds_id)
1486
self.assertEqual('thetrustor', user_creds.get('trustor_user_id'))
1490
db_s = db_api.stack_get(trustor_ctx, stack_id)
1491
self.assertIsNone(db_s)
1492
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1495
def test_delete_trust_not_trustor(self):
1496
cfg.CONF.set_override('deferred_auth_method', 'trusts')
1498
# Stack gets created with trustor_ctx, deleted with other_ctx
1499
# then the trust delete should be with stored_ctx
1500
trustor_ctx = utils.dummy_context(user_id='thetrustor')
1501
other_ctx = utils.dummy_context(user_id='nottrustor')
1502
stored_ctx = utils.dummy_context(trust_id='thetrust')
1504
self.m.StubOutWithMock(hkc, 'KeystoneClient')
1505
hkc.KeystoneClient(trustor_ctx).AndReturn(
1506
fakes.FakeKeystoneClient(user_id='thetrustor'))
1507
self.m.StubOutWithMock(parser.Stack, 'stored_context')
1508
parser.Stack.stored_context().AndReturn(stored_ctx)
1509
hkc.KeystoneClient(stored_ctx).AndReturn(
1510
fakes.FakeKeystoneClient(user_id='nottrustor'))
1513
self.stack = parser.Stack(
1514
trustor_ctx, 'delete_trust_nt', self.tmpl)
1515
stack_id = self.stack.store()
1517
db_s = db_api.stack_get(self.ctx, stack_id)
1518
self.assertIsNotNone(db_s)
1520
user_creds_id = db_s.user_creds_id
1521
self.assertIsNotNone(user_creds_id)
1522
user_creds = db_api.user_creds_get(user_creds_id)
1523
self.assertEqual('thetrustor', user_creds.get('trustor_user_id'))
1525
loaded_stack = parser.Stack.load(other_ctx, self.stack.id)
1526
loaded_stack.delete()
1528
db_s = db_api.stack_get(other_ctx, stack_id)
1529
self.assertIsNone(db_s)
1530
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1533
def test_delete_trust_backup(self):
1534
cfg.CONF.set_override('deferred_auth_method', 'trusts')
1536
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
1537
def delete_trust(self, trust_id):
1538
raise Exception("Shouldn't delete")
1540
self.m.StubOutWithMock(keystone.KeystoneClientPlugin, '_create')
1541
keystone.KeystoneClientPlugin._create().AndReturn(
1542
FakeKeystoneClientFail())
1545
self.stack = parser.Stack(
1546
self.ctx, 'delete_trust', self.tmpl)
1547
stack_id = self.stack.store()
1549
db_s = db_api.stack_get(self.ctx, stack_id)
1550
self.assertIsNotNone(db_s)
1552
self.stack.delete(backup=True)
1554
db_s = db_api.stack_get(self.ctx, stack_id)
1555
self.assertIsNone(db_s)
1556
self.assertEqual(self.stack.state,
1557
(parser.Stack.DELETE, parser.Stack.COMPLETE))
1559
def test_delete_trust_nested(self):
1560
cfg.CONF.set_override('deferred_auth_method', 'trusts')
1562
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
1563
def delete_trust(self, trust_id):
1564
raise Exception("Shouldn't delete")
1566
self.stub_keystoneclient(fake_client=FakeKeystoneClientFail())
1568
self.stack = parser.Stack(
1569
self.ctx, 'delete_trust_nested', self.tmpl,
1570
owner_id='owner123')
1571
stack_id = self.stack.store()
1573
db_s = db_api.stack_get(self.ctx, stack_id)
1574
self.assertIsNotNone(db_s)
1575
user_creds_id = db_s.user_creds_id
1576
self.assertIsNotNone(user_creds_id)
1577
user_creds = db_api.user_creds_get(user_creds_id)
1578
self.assertIsNotNone(user_creds)
1582
db_s = db_api.stack_get(self.ctx, stack_id)
1583
self.assertIsNone(db_s)
1584
user_creds = db_api.user_creds_get(user_creds_id)
1585
self.assertIsNotNone(user_creds)
1586
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1589
def test_delete_trust_fail(self):
1590
cfg.CONF.set_override('deferred_auth_method', 'trusts')
1592
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
1593
def delete_trust(self, trust_id):
1594
raise kc_exceptions.Forbidden("Denied!")
1596
self.m.StubOutWithMock(keystone.KeystoneClientPlugin, '_create')
1597
keystone.KeystoneClientPlugin._create().AndReturn(
1598
FakeKeystoneClientFail())
1601
self.stack = parser.Stack(
1602
self.ctx, 'delete_trust', self.tmpl)
1603
stack_id = self.stack.store()
1605
db_s = db_api.stack_get(self.ctx, stack_id)
1606
self.assertIsNotNone(db_s)
1610
db_s = db_api.stack_get(self.ctx, stack_id)
1611
self.assertIsNotNone(db_s)
1612
self.assertEqual((parser.Stack.DELETE, parser.Stack.FAILED),
1614
self.assertIn('Error deleting trust', self.stack.status_reason)
1616
def test_delete_deletes_project(self):
1617
fkc = fakes.FakeKeystoneClient()
1618
fkc.delete_stack_domain_project = mock.Mock()
1620
self.m.StubOutWithMock(keystone.KeystoneClientPlugin, '_create')
1621
keystone.KeystoneClientPlugin._create().AndReturn(fkc)
1624
self.stack = parser.Stack(
1625
self.ctx, 'delete_trust', self.tmpl)
1626
stack_id = self.stack.store()
1628
self.stack.set_stack_user_project_id(project_id='aproject456')
1630
db_s = db_api.stack_get(self.ctx, stack_id)
1631
self.assertIsNotNone(db_s)
1635
db_s = db_api.stack_get(self.ctx, stack_id)
1636
self.assertIsNone(db_s)
1637
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1639
fkc.delete_stack_domain_project.assert_called_once_with(
1640
project_id='aproject456')
1642
def test_abandon_nodelete_project(self):
1643
fkc = fakes.FakeKeystoneClient()
1644
fkc.delete_stack_domain_project = mock.Mock()
1646
self.m.StubOutWithMock(keystone.KeystoneClientPlugin, '_create')
1647
keystone.KeystoneClientPlugin._create().AndReturn(fkc)
1650
self.stack = parser.Stack(
1651
self.ctx, 'delete_trust', self.tmpl)
1652
stack_id = self.stack.store()
1654
self.stack.set_stack_user_project_id(project_id='aproject456')
1656
db_s = db_api.stack_get(self.ctx, stack_id)
1657
self.assertIsNotNone(db_s)
1659
self.stack.delete(abandon=True)
1661
db_s = db_api.stack_get(self.ctx, stack_id)
1662
self.assertIsNone(db_s)
1663
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
1665
self.assertFalse(fkc.delete_stack_domain_project.called)
1667
def test_suspend_resume(self):
1669
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1670
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
1671
self.stack = parser.Stack(self.ctx, 'suspend_test',
1672
parser.Template(tmpl))
1675
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
1677
self.assertIsNone(self.stack.updated_time)
1679
self.stack.suspend()
1681
self.assertEqual((self.stack.SUSPEND, self.stack.COMPLETE),
1683
stack_suspend_time = self.stack.updated_time
1684
self.assertIsNotNone(stack_suspend_time)
1688
self.assertEqual((self.stack.RESUME, self.stack.COMPLETE),
1690
self.assertNotEqual(stack_suspend_time, self.stack.updated_time)
1694
def test_suspend_stack_suspended_ok(self):
1695
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1696
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
1697
self.stack = parser.Stack(self.ctx, 'suspend_test',
1698
parser.Template(tmpl))
1701
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
1704
self.stack.suspend()
1705
self.assertEqual((self.stack.SUSPEND, self.stack.COMPLETE),
1708
# unexpected to call Resource.suspend
1709
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'suspend')
1712
self.stack.suspend()
1713
self.assertEqual((self.stack.SUSPEND, self.stack.COMPLETE),
1717
def test_resume_stack_resumeed_ok(self):
1718
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1719
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
1720
self.stack = parser.Stack(self.ctx, 'suspend_test',
1721
parser.Template(tmpl))
1724
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
1727
self.stack.suspend()
1728
self.assertEqual((self.stack.SUSPEND, self.stack.COMPLETE),
1732
self.assertEqual((self.stack.RESUME, self.stack.COMPLETE),
1735
# unexpected to call Resource.resume
1736
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'resume')
1740
self.assertEqual((self.stack.RESUME, self.stack.COMPLETE),
1744
def test_suspend_fail(self):
1745
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1746
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
1747
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_suspend')
1748
exc = Exception('foo')
1749
generic_rsrc.GenericResource.handle_suspend().AndRaise(exc)
1752
self.stack = parser.Stack(self.ctx, 'suspend_test_fail',
1753
parser.Template(tmpl))
1757
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
1760
self.stack.suspend()
1762
self.assertEqual((self.stack.SUSPEND, self.stack.FAILED),
1764
self.assertEqual('Resource SUSPEND failed: Exception: foo',
1765
self.stack.status_reason)
1768
def test_resume_fail(self):
1769
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1770
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
1771
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_resume')
1772
generic_rsrc.GenericResource.handle_resume().AndRaise(Exception('foo'))
1775
self.stack = parser.Stack(self.ctx, 'resume_test_fail',
1776
parser.Template(tmpl))
1780
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
1783
self.stack.suspend()
1785
self.assertEqual((self.stack.SUSPEND, self.stack.COMPLETE),
1790
self.assertEqual((self.stack.RESUME, self.stack.FAILED),
1792
self.assertEqual('Resource RESUME failed: Exception: foo',
1793
self.stack.status_reason)
1796
def test_suspend_timeout(self):
1797
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1798
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
1799
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_suspend')
1800
exc = scheduler.Timeout('foo', 0)
1801
generic_rsrc.GenericResource.handle_suspend().AndRaise(exc)
1804
self.stack = parser.Stack(self.ctx, 'suspend_test_fail_timeout',
1805
parser.Template(tmpl))
1809
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
1812
self.stack.suspend()
1814
self.assertEqual((self.stack.SUSPEND, self.stack.FAILED),
1816
self.assertEqual('Suspend timed out', self.stack.status_reason)
1819
def test_resume_timeout(self):
1820
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
1821
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
1822
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_resume')
1823
exc = scheduler.Timeout('foo', 0)
1824
generic_rsrc.GenericResource.handle_resume().AndRaise(exc)
1827
self.stack = parser.Stack(self.ctx, 'resume_test_fail_timeout',
1828
parser.Template(tmpl))
1832
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
1835
self.stack.suspend()
1837
self.assertEqual((self.stack.SUSPEND, self.stack.COMPLETE),
1842
self.assertEqual((self.stack.RESUME, self.stack.FAILED),
1845
self.assertEqual('Resume timed out', self.stack.status_reason)
1848
def _get_stack_to_check(self, name):
1849
tpl = {"HeatTemplateFormatVersion": "2012-12-12",
1851
"A": {"Type": "GenericResourceType"},
1852
"B": {"Type": "GenericResourceType"}}}
1853
self.stack = parser.Stack(self.ctx, name, parser.Template(tpl),
1857
def _mock_check(res):
1858
res.handle_check = mock.Mock()
1860
[_mock_check(res) for res in self.stack.resources.values()]
1863
def test_check_supported(self):
1864
stack = self._get_stack_to_check('check-supported')
1867
self.assertEqual(stack.COMPLETE, stack.status)
1868
self.assertEqual(stack.CHECK, stack.action)
1869
[self.assertTrue(res.handle_check.called)
1870
for res in stack.resources.values()]
1871
self.assertNotIn('not fully supported', stack.status_reason)
1873
def test_check_not_supported(self):
1874
stack = self._get_stack_to_check('check-not-supported')
1875
del stack['B'].handle_check
1878
self.assertEqual(stack.COMPLETE, stack.status)
1879
self.assertEqual(stack.CHECK, stack.action)
1880
self.assertTrue(stack['A'].handle_check.called)
1881
self.assertIn('not fully supported', stack.status_reason)
1883
def test_check_fail(self):
1884
stack = self._get_stack_to_check('check-fail')
1885
stack['A'].handle_check.side_effect = Exception('fail-A')
1886
stack['B'].handle_check.side_effect = Exception('fail-B')
1889
self.assertEqual(stack.FAILED, stack.status)
1890
self.assertEqual(stack.CHECK, stack.action)
1891
self.assertTrue(stack['A'].handle_check.called)
1892
self.assertTrue(stack['B'].handle_check.called)
1893
self.assertIn('fail-A', stack.status_reason)
1894
self.assertIn('fail-B', stack.status_reason)
1896
def test_delete_rollback(self):
1897
self.stack = parser.Stack(self.ctx, 'delete_rollback_test',
1898
self.tmpl, disable_rollback=False)
1899
stack_id = self.stack.store()
1901
db_s = db_api.stack_get(self.ctx, stack_id)
1902
self.assertIsNotNone(db_s)
1904
self.stack.delete(action=self.stack.ROLLBACK)
1906
db_s = db_api.stack_get(self.ctx, stack_id)
1907
self.assertIsNone(db_s)
1908
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.COMPLETE),
1911
def test_delete_badaction(self):
1912
self.stack = parser.Stack(self.ctx, 'delete_badaction_test',
1914
stack_id = self.stack.store()
1916
db_s = db_api.stack_get(self.ctx, stack_id)
1917
self.assertIsNotNone(db_s)
1919
self.stack.delete(action="wibble")
1921
db_s = db_api.stack_get(self.ctx, stack_id)
1922
self.assertIsNotNone(db_s)
1923
self.assertEqual((parser.Stack.DELETE, parser.Stack.FAILED),
1926
def test_adopt_stack(self):
1929
"status": "COMPLETE",
1930
"name": "my-test-stack-name",
1933
"status": "COMPLETE",
1934
"name": "AResource",
1935
"resource_data": {},
1937
"resource_id": "test-res-id",
1939
"type": "GenericResourceType"
1945
'HeatTemplateFormatVersion': '2012-12-12',
1946
'Resources': {'AResource': {'Type': 'GenericResourceType'}},
1947
'Outputs': {'TestOutput': {'Value': {
1948
'Fn::GetAtt': ['AResource', 'Foo']}}
1952
self.stack = parser.Stack(utils.dummy_context(), 'test_stack',
1953
template.Template(tmpl),
1954
adopt_stack_data=json.loads(adopt_data))
1957
res = self.stack['AResource']
1958
self.assertEqual(u'test-res-id', res.resource_id)
1959
self.assertEqual('AResource', res.name)
1960
self.assertEqual('COMPLETE', res.status)
1961
self.assertEqual('ADOPT', res.action)
1962
self.assertEqual((self.stack.ADOPT, self.stack.COMPLETE),
1964
self.assertEqual('AResource', self.stack.output('TestOutput'))
1966
loaded_stack = parser.Stack.load(self.ctx, self.stack.id)
1967
self.assertEqual({}, loaded_stack['AResource']._stored_properties_data)
1969
def test_adopt_stack_fails(self):
1972
"status": "COMPLETE",
1973
"name": "my-test-stack-name",
1977
tmpl = template.Template({
1978
'HeatTemplateFormatVersion': '2012-12-12',
1980
'foo': {'Type': 'GenericResourceType'},
1984
self.stack = parser.Stack(utils.dummy_context(), 'test_stack',
1986
adopt_stack_data=json.loads(adopt_data))
1989
self.assertEqual((self.stack.ADOPT, self.stack.FAILED),
1991
expected = ('Resource ADOPT failed: Exception: Resource ID was not'
1993
self.assertEqual(expected, self.stack.status_reason)
1995
def test_adopt_stack_rollback(self):
1997
"name": "my-test-stack-name",
2001
tmpl = template.Template({
2002
'HeatTemplateFormatVersion': '2012-12-12',
2004
'foo': {'Type': 'GenericResourceType'},
2008
self.stack = parser.Stack(utils.dummy_context(),
2011
disable_rollback=False,
2012
adopt_stack_data=json.loads(adopt_data))
2015
self.assertEqual((self.stack.ROLLBACK, self.stack.COMPLETE),
2018
def test_resource_by_refid(self):
2019
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2020
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2022
self.stack = parser.Stack(self.ctx, 'resource_by_refid_stack',
2023
template.Template(tmpl))
2026
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2028
self.assertIn('AResource', self.stack)
2029
rsrc = self.stack['AResource']
2030
rsrc.resource_id_set('aaaa')
2031
self.assertIsNotNone(resource)
2033
for action, status in (
2034
(rsrc.INIT, rsrc.COMPLETE),
2035
(rsrc.CREATE, rsrc.IN_PROGRESS),
2036
(rsrc.CREATE, rsrc.COMPLETE),
2037
(rsrc.RESUME, rsrc.IN_PROGRESS),
2038
(rsrc.RESUME, rsrc.COMPLETE),
2039
(rsrc.UPDATE, rsrc.IN_PROGRESS),
2040
(rsrc.UPDATE, rsrc.COMPLETE)):
2041
rsrc.state_set(action, status)
2042
self.assertEqual(rsrc, self.stack.resource_by_refid('aaaa'))
2044
rsrc.state_set(rsrc.DELETE, rsrc.IN_PROGRESS)
2046
self.assertIsNone(self.stack.resource_by_refid('aaaa'))
2047
self.assertIsNone(self.stack.resource_by_refid('bbbb'))
2049
rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE)
2051
def test_update_add(self):
2052
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2053
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2055
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2056
template.Template(tmpl))
2059
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2062
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2064
'AResource': {'Type': 'GenericResourceType'},
2065
'BResource': {'Type': 'GenericResourceType'}}}
2066
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2067
template.Template(tmpl2))
2068
self.stack.update(updated_stack)
2069
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2071
self.assertIn('BResource', self.stack)
2073
def test_update_remove(self):
2074
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2076
'AResource': {'Type': 'GenericResourceType'},
2077
'BResource': {'Type': 'GenericResourceType'}}}
2079
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2080
template.Template(tmpl))
2083
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2086
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2087
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2089
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2090
template.Template(tmpl2))
2091
self.stack.update(updated_stack)
2092
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2094
self.assertNotIn('BResource', self.stack)
2096
def test_update_description(self):
2097
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2098
'Description': 'ATemplate',
2099
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2101
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2102
template.Template(tmpl))
2105
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2108
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2109
'Description': 'BTemplate',
2110
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2112
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2113
template.Template(tmpl2))
2114
self.stack.update(updated_stack)
2115
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2117
self.assertEqual('BTemplate',
2118
self.stack.t[self.stack.t.DESCRIPTION])
2120
def test_update_timeout(self):
2121
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2122
'Description': 'ATemplate',
2123
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2125
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2126
template.Template(tmpl), timeout_mins=60)
2129
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2132
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2133
'Description': 'ATemplate',
2134
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2136
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2137
template.Template(tmpl2), timeout_mins=30)
2138
self.stack.update(updated_stack)
2139
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2141
self.assertEqual(30, self.stack.timeout_mins)
2143
def test_update_disable_rollback(self):
2144
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2145
'Description': 'ATemplate',
2146
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2148
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2149
template.Template(tmpl),
2150
disable_rollback=False)
2153
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2156
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2157
'Description': 'ATemplate',
2158
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2160
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2161
template.Template(tmpl2),
2162
disable_rollback=True)
2163
self.stack.update(updated_stack)
2164
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2166
self.assertEqual(True, self.stack.disable_rollback)
2168
def test_update_modify_ok_replace(self):
2169
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2170
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2171
'Properties': {'Foo': 'abc'}}}}
2173
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2174
template.Template(tmpl))
2177
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2180
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2181
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2182
'Properties': {'Foo': 'xyz'}}}}
2184
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2185
template.Template(tmpl2))
2189
self.stack.update(updated_stack)
2190
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2192
self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
2194
loaded_stack = parser.Stack.load(self.ctx, self.stack.id)
2195
stored_props = loaded_stack['AResource']._stored_properties_data
2196
self.assertEqual({'Foo': 'xyz'}, stored_props)
2200
def test_update_modify_ok_replace_int(self):
2203
tmpl = {'heat_template_version': '2013-05-23',
2204
'resources': {'AResource': {
2205
'type': 'ResWithComplexPropsAndAttrs',
2206
'properties': {'an_int': 1}}}}
2208
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2209
template.Template(tmpl))
2211
stack_id = self.stack.id
2213
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2217
prop_diff1 = {'an_int': value1}
2219
prop_diff2 = {'an_int': value2}
2221
self.m.StubOutWithMock(generic_rsrc.ResWithComplexPropsAndAttrs,
2223
generic_rsrc.ResWithComplexPropsAndAttrs.handle_update(mox.IgnoreArg(),
2226
generic_rsrc.ResWithComplexPropsAndAttrs.handle_update(mox.IgnoreArg(),
2235
self.stack = parser.Stack.load(self.ctx, stack_id=stack_id)
2236
tmpl2 = {'heat_template_version': '2013-05-23',
2237
'resources': {'AResource': {
2238
'type': 'ResWithComplexPropsAndAttrs',
2239
'properties': {'an_int': value1}}}}
2240
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2241
template.Template(tmpl2))
2243
self.stack.update(updated_stack)
2244
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2249
# reload the previous stack
2250
self.stack = parser.Stack.load(self.ctx, stack_id=stack_id)
2251
tmpl3 = {'heat_template_version': '2013-05-23',
2252
'resources': {'AResource': {
2253
'type': 'ResWithComplexPropsAndAttrs',
2254
'properties': {'an_int': value2}}}}
2256
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2257
template.Template(tmpl3))
2259
self.stack.update(updated_stack)
2260
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2265
def test_update_modify_param_ok_replace(self):
2267
'HeatTemplateFormatVersion': '2012-12-12',
2269
'foo': {'Type': 'String'}
2273
'Type': 'ResourceWithPropsType',
2274
'Properties': {'Foo': {'Ref': 'foo'}}
2279
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps,
2280
'update_template_diff')
2282
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2283
template.Template(tmpl),
2284
environment.Environment({'foo': 'abc'}))
2287
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2290
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2291
template.Template(tmpl),
2292
environment.Environment({'foo': 'xyz'}))
2294
def check_props(*args):
2295
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
2297
generic_rsrc.ResourceWithProps.update_template_diff(
2298
{'Type': 'ResourceWithPropsType',
2299
'Properties': {'Foo': 'xyz'}},
2300
{'Type': 'ResourceWithPropsType',
2301
'Properties': {'Foo': 'abc'}}
2302
).WithSideEffects(check_props).AndRaise(resource.UpdateReplace)
2305
self.stack.update(updated_stack)
2306
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2308
self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
2311
def test_update_modify_update_failed(self):
2312
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2313
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2314
'Properties': {'Foo': 'abc'}}}}
2316
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2317
template.Template(tmpl),
2318
disable_rollback=True)
2321
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2324
res = self.stack['AResource']
2325
res.update_allowed_properties = ('Foo',)
2327
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2328
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2329
'Properties': {'Foo': 'xyz'}}}}
2331
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2332
template.Template(tmpl2))
2334
# patch in a dummy handle_update
2335
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_update')
2336
tmpl_diff = {'Properties': {'Foo': 'xyz'}}
2337
prop_diff = {'Foo': 'xyz'}
2338
generic_rsrc.ResourceWithProps.handle_update(
2339
tmpl2['Resources']['AResource'], tmpl_diff,
2340
prop_diff).AndRaise(Exception("Foo"))
2343
self.stack.update(updated_stack)
2344
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
2348
def test_update_modify_replace_failed_delete(self):
2349
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2350
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2351
'Properties': {'Foo': 'abc'}}}}
2353
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2354
template.Template(tmpl),
2355
disable_rollback=True)
2358
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2361
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2362
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2363
'Properties': {'Foo': 'xyz'}}}}
2365
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2366
template.Template(tmpl2))
2368
# make the update fail deleting the existing resource
2369
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
2370
generic_rsrc.ResourceWithProps.handle_delete().AndRaise(Exception)
2373
self.stack.update(updated_stack)
2374
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
2377
# Unset here so destroy() is not stubbed for stack.delete cleanup
2380
def test_update_modify_replace_failed_create(self):
2381
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2382
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2383
'Properties': {'Foo': 'abc'}}}}
2385
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2386
template.Template(tmpl),
2387
disable_rollback=True)
2390
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2393
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2394
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2395
'Properties': {'Foo': 'xyz'}}}}
2397
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2398
template.Template(tmpl2))
2400
# patch in a dummy handle_create making the replace fail creating
2401
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
2402
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
2405
self.stack.update(updated_stack)
2406
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
2410
def test_update_modify_replace_failed_create_and_delete_1(self):
2411
resource._register_class('ResourceWithResourceIDType',
2412
generic_rsrc.ResourceWithResourceID)
2413
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2414
'Resources': {'AResource': {'Type':
2415
'ResourceWithResourceIDType',
2416
'Properties': {'ID': 'a_res'}},
2417
'BResource': {'Type':
2418
'ResourceWithResourceIDType',
2419
'Properties': {'ID': 'b_res'},
2420
'DependsOn': 'AResource'}}}
2422
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2423
template.Template(tmpl),
2424
disable_rollback=True)
2427
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2430
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2431
'Resources': {'AResource': {'Type':
2432
'ResourceWithResourceIDType',
2433
'Properties': {'ID': 'xyz'}},
2434
'BResource': {'Type':
2435
'ResourceWithResourceIDType',
2436
'Properties': {'ID': 'b_res'},
2437
'DependsOn': 'AResource'}}}
2439
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2440
template.Template(tmpl2))
2442
# patch in a dummy handle_create making the replace fail creating
2443
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
2445
generic_rsrc.ResourceWithResourceID.handle_create().AndRaise(Exception)
2447
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
2449
# First, attempts to delete backup_stack. The create (xyz) has been
2450
# failed, so it has no resource_id.
2451
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2452
None).AndReturn(None)
2453
# There are dependency AResource and BResource, so we must delete
2454
# BResource, then delete AResource.
2455
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2456
'b_res').AndReturn(None)
2457
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2458
'a_res').AndReturn(None)
2461
self.stack.update(updated_stack)
2462
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
2465
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
2469
def test_update_modify_replace_failed_create_and_delete_2(self):
2470
resource._register_class('ResourceWithResourceIDType',
2471
generic_rsrc.ResourceWithResourceID)
2472
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2473
'Resources': {'AResource': {'Type':
2474
'ResourceWithResourceIDType',
2475
'Properties': {'ID': 'a_res'}},
2476
'BResource': {'Type':
2477
'ResourceWithResourceIDType',
2478
'Properties': {'ID': 'b_res'},
2479
'DependsOn': 'AResource'}}}
2481
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2482
template.Template(tmpl),
2483
disable_rollback=True)
2486
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2489
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2490
'Resources': {'AResource': {'Type':
2491
'ResourceWithResourceIDType',
2492
'Properties': {'ID': 'c_res'}},
2493
'BResource': {'Type':
2494
'ResourceWithResourceIDType',
2495
'Properties': {'ID': 'xyz'},
2496
'DependsOn': 'AResource'}}}
2498
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2499
template.Template(tmpl2))
2501
# patch in a dummy handle_create making the replace fail creating
2502
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
2504
generic_rsrc.ResourceWithResourceID.handle_create()
2505
generic_rsrc.ResourceWithResourceID.handle_create().AndRaise(Exception)
2507
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
2509
# First, attempts to delete backup_stack. The create (xyz) has been
2510
# failed, so it has no resource_id.
2511
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2512
None).AndReturn(None)
2513
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2514
'c_res').AndReturn(None)
2515
# There are dependency AResource and BResource, so we must delete
2516
# BResource, then delete AResource.
2517
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2518
'b_res').AndReturn(None)
2519
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2520
'a_res').AndReturn(None)
2523
self.stack.update(updated_stack)
2524
# set resource_id for AResource because handle_create() is overwritten
2526
self.stack.resources['AResource'].resource_id_set('c_res')
2527
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
2530
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
2534
def test_update_modify_replace_create_in_progress_and_delete_1(self):
2535
resource._register_class('ResourceWithResourceIDType',
2536
generic_rsrc.ResourceWithResourceID)
2537
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2538
'Resources': {'AResource': {'Type':
2539
'ResourceWithResourceIDType',
2540
'Properties': {'ID': 'a_res'}},
2541
'BResource': {'Type':
2542
'ResourceWithResourceIDType',
2543
'Properties': {'ID': 'b_res'},
2544
'DependsOn': 'AResource'}}}
2546
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2547
template.Template(tmpl),
2548
disable_rollback=True)
2551
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2554
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2555
'Resources': {'AResource': {'Type':
2556
'ResourceWithResourceIDType',
2557
'Properties': {'ID': 'xyz'}},
2558
'BResource': {'Type':
2559
'ResourceWithResourceIDType',
2560
'Properties': {'ID': 'b_res'},
2561
'DependsOn': 'AResource'}}}
2563
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2564
template.Template(tmpl2))
2566
# patch in a dummy handle_create making the replace fail creating
2567
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
2569
generic_rsrc.ResourceWithResourceID.handle_create().AndRaise(Exception)
2571
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
2573
# First, attempts to delete backup_stack. The create (xyz) has been
2574
# failed, so it has no resource_id.
2575
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2576
None).AndReturn(None)
2577
# There are dependency AResource and BResource, so we must delete
2578
# BResource, then delete AResource.
2579
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2580
'b_res').AndReturn(None)
2581
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2582
'a_res').AndReturn(None)
2585
self.stack.update(updated_stack)
2586
# Override stack status and resources status for emulating
2587
# IN_PROGRESS situation
2588
self.stack.state_set(
2589
parser.Stack.UPDATE, parser.Stack.IN_PROGRESS, None)
2590
self.stack.resources['AResource'].state_set(
2591
resource.Resource.CREATE, resource.Resource.IN_PROGRESS, None)
2593
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
2597
def test_update_modify_replace_create_in_progress_and_delete_2(self):
2598
resource._register_class('ResourceWithResourceIDType',
2599
generic_rsrc.ResourceWithResourceID)
2600
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2601
'Resources': {'AResource': {'Type':
2602
'ResourceWithResourceIDType',
2603
'Properties': {'ID': 'a_res'}},
2604
'BResource': {'Type':
2605
'ResourceWithResourceIDType',
2606
'Properties': {'ID': 'b_res'},
2607
'DependsOn': 'AResource'}}}
2609
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2610
template.Template(tmpl),
2611
disable_rollback=True)
2614
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2617
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2618
'Resources': {'AResource': {'Type':
2619
'ResourceWithResourceIDType',
2620
'Properties': {'ID': 'c_res'}},
2621
'BResource': {'Type':
2622
'ResourceWithResourceIDType',
2623
'Properties': {'ID': 'xyz'},
2624
'DependsOn': 'AResource'}}}
2626
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2627
template.Template(tmpl2))
2629
# patch in a dummy handle_create making the replace fail creating
2630
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
2632
generic_rsrc.ResourceWithResourceID.handle_create()
2633
generic_rsrc.ResourceWithResourceID.handle_create().AndRaise(Exception)
2635
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
2637
# First, attempts to delete backup_stack. The create (xyz) has been
2638
# failed, so it has no resource_id.
2639
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2640
None).AndReturn(None)
2641
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2642
'c_res').AndReturn(None)
2643
# There are dependency AResource and BResource, so we must delete
2644
# BResource, then delete AResource.
2645
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2646
'b_res').AndReturn(None)
2647
generic_rsrc.ResourceWithResourceID.mox_resource_id(
2648
'a_res').AndReturn(None)
2651
self.stack.update(updated_stack)
2652
# set resource_id for AResource because handle_create() is overwritten
2654
self.stack.resources['AResource'].resource_id_set('c_res')
2655
# Override stack status and resources status for emulating
2656
# IN_PROGRESS situation
2657
self.stack.state_set(
2658
parser.Stack.UPDATE, parser.Stack.IN_PROGRESS, None)
2659
self.stack.resources['BResource'].state_set(
2660
resource.Resource.CREATE, resource.Resource.IN_PROGRESS, None)
2662
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
2666
def test_update_add_signal(self):
2667
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2668
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2670
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2671
template.Template(tmpl))
2674
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2677
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2679
'AResource': {'Type': 'GenericResourceType'},
2680
'BResource': {'Type': 'GenericResourceType'}}}
2681
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2682
template.Template(tmpl2))
2684
updater = scheduler.TaskRunner(self.stack.update_task, updated_stack)
2686
while 'BResource' not in self.stack:
2687
self.assertFalse(updater.step())
2688
self.assertEqual((parser.Stack.UPDATE, parser.Stack.IN_PROGRESS),
2691
# Reload the stack from the DB and prove that it contains the new
2693
re_stack = parser.Stack.load(utils.dummy_context(), self.stack.id)
2694
self.assertIn('BResource', re_stack)
2696
updater.run_to_completion()
2697
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
2699
self.assertIn('BResource', self.stack)
2701
def test_update_add_failed_create(self):
2702
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2703
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2705
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2706
template.Template(tmpl))
2709
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2712
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2714
'AResource': {'Type': 'GenericResourceType'},
2715
'BResource': {'Type': 'GenericResourceType'}}}
2716
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2717
template.Template(tmpl2))
2719
# patch in a dummy handle_create making BResource fail creating
2720
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_create')
2721
generic_rsrc.GenericResource.handle_create().AndRaise(Exception)
2724
self.stack.update(updated_stack)
2725
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
2727
self.assertIn('BResource', self.stack)
2729
# Reload the stack from the DB and prove that it contains the failed
2730
# resource (to ensure it will be deleted on stack delete)
2731
re_stack = parser.Stack.load(utils.dummy_context(), self.stack.id)
2732
self.assertIn('BResource', re_stack)
2735
def test_update_add_failed_create_rollback_failed(self):
2736
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2737
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2739
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2740
template.Template(tmpl))
2743
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2746
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2748
'AResource': {'Type': 'GenericResourceType'},
2749
'BResource': {'Type': 'GenericResourceType'}}}
2750
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2751
template.Template(tmpl2),
2752
disable_rollback=False)
2754
# patch in a dummy handle_create making BResource fail creating
2755
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_create')
2756
generic_rsrc.GenericResource.handle_create().AndRaise(Exception)
2757
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_delete')
2758
generic_rsrc.GenericResource.handle_delete().AndRaise(Exception)
2761
self.stack.update(updated_stack)
2762
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.FAILED),
2764
self.assertIn('BResource', self.stack)
2766
# Reload the stack from the DB and prove that it contains the failed
2767
# resource (to ensure it will be deleted on stack delete)
2768
re_stack = parser.Stack.load(utils.dummy_context(), self.stack.id)
2769
self.assertIn('BResource', re_stack)
2772
def test_update_rollback(self):
2773
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2774
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2775
'Properties': {'Foo': 'abc'}}}}
2777
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2778
template.Template(tmpl),
2779
disable_rollback=False)
2782
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2785
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2786
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2787
'Properties': {'Foo': 'xyz'}}}}
2789
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2790
template.Template(tmpl2),
2791
disable_rollback=False)
2793
# patch in a dummy handle_create making the replace fail when creating
2794
# the replacement rsrc
2795
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
2796
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
2799
with mock.patch.object(self.stack, 'state_set',
2800
side_effect=self.stack.state_set) as mock_state:
2801
self.stack.update(updated_stack)
2802
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.COMPLETE),
2804
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
2805
self.assertEqual(2, mock_state.call_count)
2806
self.assertEqual(('UPDATE', 'IN_PROGRESS'),
2807
mock_state.call_args_list[0][0][:2])
2808
self.assertEqual(('ROLLBACK', 'IN_PROGRESS'),
2809
mock_state.call_args_list[1][0][:2])
2812
def test_update_rollback_on_cancel_event(self):
2813
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2814
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2815
'Properties': {'Foo': 'abc'}}}}
2817
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2818
template.Template(tmpl),
2819
disable_rollback=False)
2822
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2825
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2826
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2827
'Properties': {'Foo': 'xyz'}},
2830
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2831
template.Template(tmpl2),
2832
disable_rollback=False)
2833
evt_mock = mock.MagicMock()
2834
evt_mock.ready.return_value = True
2835
evt_mock.wait.return_value = 'cancel'
2839
self.stack.update(updated_stack, event=evt_mock)
2840
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.COMPLETE),
2842
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
2845
def test_update_rollback_fail(self):
2846
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2847
'Parameters': {'AParam': {'Type': 'String'}},
2848
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2849
'Properties': {'Foo': 'abc'}}}}
2851
env1 = {'parameters': {'AParam': 'abc'}}
2852
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2853
template.Template(tmpl),
2854
disable_rollback=False,
2855
env=environment.Environment(env1))
2858
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2861
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2862
'Parameters': {'BParam': {'Type': 'String'}},
2863
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2864
'Properties': {'Foo': 'xyz'}}}}
2866
env2 = {'parameters': {'BParam': 'smelly'}}
2867
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2868
template.Template(tmpl2),
2869
disable_rollback=False,
2870
env=environment.Environment(env2))
2872
# patch in a dummy handle_create making the replace fail when creating
2873
# the replacement rsrc, and again on the second call (rollback)
2874
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
2875
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
2876
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
2877
generic_rsrc.ResourceWithProps.handle_delete().AndRaise(Exception)
2880
self.stack.update(updated_stack)
2881
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.FAILED),
2885
def test_update_rollback_add(self):
2886
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2887
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2889
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2890
template.Template(tmpl),
2891
disable_rollback=False)
2894
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2897
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2899
'AResource': {'Type': 'GenericResourceType'},
2900
'BResource': {'Type': 'GenericResourceType'}}}
2902
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2903
template.Template(tmpl2),
2904
disable_rollback=False)
2906
# patch in a dummy handle_create making the replace fail when creating
2907
# the replacement rsrc, and succeed on the second call (rollback)
2908
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_create')
2909
generic_rsrc.GenericResource.handle_create().AndRaise(Exception)
2912
self.stack.update(updated_stack)
2913
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.COMPLETE),
2915
self.assertNotIn('BResource', self.stack)
2918
def test_update_rollback_remove(self):
2919
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2921
'AResource': {'Type': 'GenericResourceType'},
2922
'BResource': {'Type': 'ResourceWithPropsType'}}}
2924
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2925
template.Template(tmpl),
2926
disable_rollback=False)
2929
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2932
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2933
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
2935
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2936
template.Template(tmpl2),
2937
disable_rollback=False)
2939
# patch in a dummy delete making the destroy fail
2940
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
2941
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
2942
generic_rsrc.ResourceWithProps.handle_delete().AndRaise(Exception)
2943
# replace the failed resource on rollback
2944
generic_rsrc.ResourceWithProps.handle_create()
2945
generic_rsrc.ResourceWithProps.handle_delete()
2948
self.stack.update(updated_stack)
2950
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.COMPLETE),
2952
self.assertIn('BResource', self.stack)
2954
# Unset here so delete() is not stubbed for stack.delete cleanup
2957
def test_update_rollback_replace(self):
2958
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
2960
'AResource': {'Type': 'ResourceWithPropsType',
2961
'Properties': {'Foo': 'foo'}}}}
2963
self.stack = parser.Stack(self.ctx, 'update_test_stack',
2964
template.Template(tmpl),
2965
disable_rollback=False)
2968
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
2971
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
2972
'Resources': {'AResource': {'Type': 'ResourceWithPropsType',
2973
'Properties': {'Foo': 'bar'}}}}
2975
updated_stack = parser.Stack(self.ctx, 'updated_stack',
2976
template.Template(tmpl2),
2977
disable_rollback=False)
2979
# patch in a dummy delete making the destroy fail
2980
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
2981
generic_rsrc.ResourceWithProps.handle_delete().AndRaise(Exception)
2982
generic_rsrc.ResourceWithProps.handle_delete().AndReturn(None)
2983
generic_rsrc.ResourceWithProps.handle_delete().AndReturn(None)
2986
self.stack.update(updated_stack)
2987
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.COMPLETE),
2990
# Unset here so delete() is not stubbed for stack.delete cleanup
2993
def test_update_replace_by_reference(self):
2996
changes in dynamic attributes, due to other resources been updated
2997
are not ignored and can cause dependent resources to be updated.
2999
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3001
'AResource': {'Type': 'ResourceWithPropsType',
3002
'Properties': {'Foo': 'abc'}},
3003
'BResource': {'Type': 'ResourceWithPropsType',
3005
'Foo': {'Ref': 'AResource'}}}}}
3006
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
3008
'AResource': {'Type': 'ResourceWithPropsType',
3009
'Properties': {'Foo': 'smelly'}},
3010
'BResource': {'Type': 'ResourceWithPropsType',
3012
'Foo': {'Ref': 'AResource'}}}}}
3014
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3015
template.Template(tmpl))
3019
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3021
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3022
self.assertEqual('AResource',
3023
self.stack['BResource'].properties['Foo'])
3025
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'FnGetRefId')
3026
generic_rsrc.ResourceWithProps.FnGetRefId().AndReturn(
3028
generic_rsrc.ResourceWithProps.FnGetRefId().MultipleTimes().AndReturn(
3032
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3033
template.Template(tmpl2))
3034
self.stack.update(updated_stack)
3035
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3037
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
3038
self.assertEqual('inst-007', self.stack['BResource'].properties['Foo'])
3041
def test_update_with_new_resources_with_reference(self):
3044
check, that during update with new resources which one has
3045
reference on second, reference will be correct resolved.
3047
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3049
'CResource': {'Type': 'ResourceWithPropsType',
3050
'Properties': {'Foo': 'abc'}}}}
3051
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
3053
'CResource': {'Type': 'ResourceWithPropsType',
3054
'Properties': {'Foo': 'abc'}},
3055
'AResource': {'Type': 'ResourceWithPropsType',
3056
'Properties': {'Foo': 'smelly'}},
3057
'BResource': {'Type': 'ResourceWithPropsType',
3059
'Foo': {'Ref': 'AResource'}}}}}
3061
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3062
template.Template(tmpl))
3066
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3068
self.assertEqual('abc', self.stack['CResource'].properties['Foo'])
3069
self.assertEqual(1, len(self.stack.resources))
3071
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
3073
generic_rsrc.ResourceWithProps.handle_create().MultipleTimes(
3078
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3079
template.Template(tmpl2))
3080
self.stack.update(updated_stack)
3081
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3083
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
3084
self.assertEqual('AResource',
3085
self.stack['BResource'].properties['Foo'])
3087
self.assertEqual(3, len(self.stack.resources))
3090
def test_update_by_reference_and_rollback_1(self):
3093
check that rollback still works with dynamic metadata
3094
this test fails the first instance
3096
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3098
'AResource': {'Type': 'ResourceWithPropsType',
3099
'Properties': {'Foo': 'abc'}},
3100
'BResource': {'Type': 'ResourceWithPropsType',
3102
'Foo': {'Ref': 'AResource'}}}}}
3103
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
3105
'AResource': {'Type': 'ResourceWithPropsType',
3106
'Properties': {'Foo': 'smelly'}},
3107
'BResource': {'Type': 'ResourceWithPropsType',
3109
'Foo': {'Ref': 'AResource'}}}}}
3111
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3112
template.Template(tmpl),
3113
disable_rollback=False)
3117
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3119
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3120
self.assertEqual('AResource',
3121
self.stack['BResource'].properties['Foo'])
3123
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'FnGetRefId')
3124
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
3126
generic_rsrc.ResourceWithProps.FnGetRefId().MultipleTimes().AndReturn(
3129
# mock to make the replace fail when creating the replacement resource
3130
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
3134
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3135
template.Template(tmpl2),
3136
disable_rollback=False)
3137
self.stack.update(updated_stack)
3138
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.COMPLETE),
3140
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3144
def test_update_by_reference_and_rollback_2(self):
3147
check that rollback still works with dynamic metadata
3148
this test fails the second instance
3151
class ResourceTypeA(generic_rsrc.ResourceWithProps):
3154
def handle_create(self):
3155
ResourceTypeA.count += 1
3156
self.resource_id_set('%s%d' % (self.name, self.count))
3158
resource._register_class('ResourceTypeA', ResourceTypeA)
3160
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3162
'AResource': {'Type': 'ResourceTypeA',
3163
'Properties': {'Foo': 'abc'}},
3164
'BResource': {'Type': 'ResourceWithPropsType',
3166
'Foo': {'Ref': 'AResource'}}}}}
3167
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
3169
'AResource': {'Type': 'ResourceTypeA',
3170
'Properties': {'Foo': 'smelly'}},
3171
'BResource': {'Type': 'ResourceWithPropsType',
3173
'Foo': {'Ref': 'AResource'}}}}}
3175
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3176
template.Template(tmpl),
3177
disable_rollback=False)
3181
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3183
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3184
self.assertEqual('AResource1',
3185
self.stack['BResource'].properties['Foo'])
3187
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
3189
# mock to make the replace fail when creating the second
3190
# replacement resource
3191
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
3195
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3196
template.Template(tmpl2),
3197
disable_rollback=False)
3198
self.stack.update(updated_stack)
3199
self.assertEqual((parser.Stack.ROLLBACK, parser.Stack.COMPLETE),
3201
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3202
self.assertEqual('AResource1',
3203
self.stack['BResource'].properties['Foo'])
3207
def test_update_failure_recovery(self):
3210
check that rollback still works with dynamic metadata
3211
this test fails the second instance
3214
class ResourceTypeA(generic_rsrc.ResourceWithProps):
3217
def handle_create(self):
3218
ResourceTypeA.count += 1
3219
self.resource_id_set('%s%d' % (self.name, self.count))
3221
def handle_delete(self):
3222
return super(ResourceTypeA, self).handle_delete()
3224
resource._register_class('ResourceTypeA', ResourceTypeA)
3226
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3228
'AResource': {'Type': 'ResourceTypeA',
3229
'Properties': {'Foo': 'abc'}},
3230
'BResource': {'Type': 'ResourceWithPropsType',
3232
'Foo': {'Ref': 'AResource'}}}}}
3233
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
3235
'AResource': {'Type': 'ResourceTypeA',
3236
'Properties': {'Foo': 'smelly'}},
3237
'BResource': {'Type': 'ResourceWithPropsType',
3239
'Foo': {'Ref': 'AResource'}}}}}
3241
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3242
template.Template(tmpl),
3243
disable_rollback=True)
3248
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3250
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3251
self.assertEqual('AResource1',
3252
self.stack['BResource'].properties['Foo'])
3254
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
3255
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
3256
self.m.StubOutWithMock(ResourceTypeA, 'handle_delete')
3258
# mock to make the replace fail when creating the second
3259
# replacement resource
3260
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
3261
# delete the old resource on the second update
3262
generic_rsrc.ResourceWithProps.handle_delete()
3263
ResourceTypeA.handle_delete()
3264
generic_rsrc.ResourceWithProps.handle_create()
3265
generic_rsrc.ResourceWithProps.handle_delete()
3269
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3270
template.Template(tmpl2),
3271
disable_rollback=True)
3272
self.stack.update(updated_stack)
3273
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
3275
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
3277
self.stack = parser.Stack.load(self.ctx, self.stack.id)
3278
updated_stack2 = parser.Stack(self.ctx, 'updated_stack',
3279
template.Template(tmpl2),
3280
disable_rollback=True)
3282
self.stack.update(updated_stack2)
3283
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3285
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
3286
self.assertEqual('AResource2',
3287
self.stack['BResource'].properties['Foo'])
3291
def test_update_failure_recovery_new_param(self):
3294
check that rollback still works with dynamic metadata
3295
this test fails the second instance
3298
class ResourceTypeA(generic_rsrc.ResourceWithProps):
3301
def handle_create(self):
3302
ResourceTypeA.count += 1
3303
self.resource_id_set('%s%d' % (self.name, self.count))
3305
def handle_delete(self):
3306
return super(ResourceTypeA, self).handle_delete()
3308
resource._register_class('ResourceTypeA', ResourceTypeA)
3311
'HeatTemplateFormatVersion': '2012-12-12',
3313
'abc-param': {'Type': 'String'}
3316
'AResource': {'Type': 'ResourceTypeA',
3317
'Properties': {'Foo': {'Ref': 'abc-param'}}},
3318
'BResource': {'Type': 'ResourceWithPropsType',
3319
'Properties': {'Foo': {'Ref': 'AResource'}}}
3322
env1 = environment.Environment({'abc-param': 'abc'})
3324
'HeatTemplateFormatVersion': '2012-12-12',
3326
'smelly-param': {'Type': 'String'}
3329
'AResource': {'Type': 'ResourceTypeA',
3330
'Properties': {'Foo': {'Ref': 'smelly-param'}}},
3331
'BResource': {'Type': 'ResourceWithPropsType',
3332
'Properties': {'Foo': {'Ref': 'AResource'}}}
3335
env2 = environment.Environment({'smelly-param': 'smelly'})
3337
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3338
template.Template(tmpl), env1,
3339
disable_rollback=True)
3344
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3346
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3347
self.assertEqual('AResource1',
3348
self.stack['BResource'].properties['Foo'])
3350
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
3351
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
3352
self.m.StubOutWithMock(ResourceTypeA, 'handle_delete')
3354
# mock to make the replace fail when creating the second
3355
# replacement resource
3356
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
3357
# delete the old resource on the second update
3358
generic_rsrc.ResourceWithProps.handle_delete()
3359
ResourceTypeA.handle_delete()
3360
generic_rsrc.ResourceWithProps.handle_create()
3361
generic_rsrc.ResourceWithProps.handle_delete()
3365
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3366
template.Template(tmpl2), env2,
3367
disable_rollback=True)
3368
self.stack.update(updated_stack)
3369
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
3371
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
3373
self.stack = parser.Stack.load(self.ctx, self.stack.id)
3374
updated_stack2 = parser.Stack(self.ctx, 'updated_stack',
3375
template.Template(tmpl2), env2,
3376
disable_rollback=True)
3378
self.stack.update(updated_stack2)
3379
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3382
self.stack = parser.Stack.load(self.ctx, self.stack.id)
3383
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
3384
self.assertEqual('AResource2',
3385
self.stack['BResource'].properties['Foo'])
3389
def test_create_failure_recovery(self):
3392
check that rollback still works with dynamic metadata
3393
this test fails the second instance
3396
class ResourceTypeA(generic_rsrc.ResourceWithProps):
3399
def handle_create(self):
3400
ResourceTypeA.count += 1
3401
self.resource_id_set('%s%d' % (self.name, self.count))
3403
def handle_delete(self):
3404
return super(ResourceTypeA, self).handle_delete()
3406
resource._register_class('ResourceTypeA', ResourceTypeA)
3408
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3410
'AResource': {'Type': 'ResourceTypeA',
3411
'Properties': {'Foo': 'abc'}},
3412
'BResource': {'Type': 'ResourceWithPropsType',
3414
'Foo': {'Ref': 'AResource'}}}}}
3415
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3416
template.Template(tmpl),
3417
disable_rollback=True)
3419
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
3420
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
3421
self.m.StubOutWithMock(ResourceTypeA, 'handle_delete')
3424
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
3427
generic_rsrc.ResourceWithProps.handle_delete()
3428
generic_rsrc.ResourceWithProps.handle_create()
3435
self.assertEqual((parser.Stack.CREATE, parser.Stack.FAILED),
3437
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3439
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3440
template.Template(tmpl),
3441
disable_rollback=True)
3442
self.stack.update(updated_stack)
3443
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3445
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3446
self.assertEqual('AResource1',
3447
self.stack['BResource'].properties['Foo'])
3451
def test_update_replace_parameters(self):
3454
changes in static environment parameters
3455
are not ignored and can cause dependent resources to be updated.
3457
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3458
'Parameters': {'AParam': {'Type': 'String'}},
3460
'AResource': {'Type': 'ResourceWithPropsType',
3461
'Properties': {'Foo': {'Ref': 'AParam'}}}}}
3463
env1 = {'parameters': {'AParam': 'abc'}}
3464
env2 = {'parameters': {'AParam': 'smelly'}}
3465
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3466
template.Template(tmpl),
3467
environment.Environment(env1))
3471
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3473
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3475
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3476
template.Template(tmpl),
3477
environment.Environment(env2))
3478
self.stack.update(updated_stack)
3479
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3481
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
3483
def test_update_deletion_policy(self):
3484
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3486
'AResource': {'Type': 'ResourceWithPropsType',
3487
'Properties': {'Foo': 'Bar'}}}}
3489
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3490
template.Template(tmpl))
3494
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3496
resource_id = self.stack['AResource'].id
3498
new_tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3500
'AResource': {'Type': 'ResourceWithPropsType',
3501
'DeletionPolicy': 'Retain',
3502
'Properties': {'Foo': 'Bar'}}}}
3504
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3505
template.Template(new_tmpl))
3506
self.stack.update(updated_stack)
3507
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3510
self.assertEqual(resource_id, self.stack['AResource'].id)
3512
def test_update_deletion_policy_no_handle_update(self):
3514
class ResourceWithNoUpdate(resource.Resource):
3515
properties_schema = {'Foo': {'Type': 'String'}}
3517
resource._register_class('ResourceWithNoUpdate',
3518
ResourceWithNoUpdate)
3520
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3522
'AResource': {'Type': 'ResourceWithNoUpdate',
3523
'Properties': {'Foo': 'Bar'}}}}
3525
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3526
template.Template(tmpl))
3530
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3532
resource_id = self.stack['AResource'].id
3534
new_tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3536
'AResource': {'Type': 'ResourceWithNoUpdate',
3537
'DeletionPolicy': 'Retain',
3538
'Properties': {'Foo': 'Bar'}}}}
3540
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3541
template.Template(new_tmpl))
3542
self.stack.update(updated_stack)
3543
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3546
self.assertEqual(resource_id, self.stack['AResource'].id)
3548
def test_update_template_format_version(self):
3550
'HeatTemplateFormatVersion': '2012-12-12',
3552
'AParam': {'Type': 'String', 'Default': 'abc'}},
3555
'Type': 'ResourceWithPropsType',
3556
'Properties': {'Foo': {'Ref': 'AParam'}}
3561
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3562
template.Template(tmpl))
3565
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3567
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
3570
'heat_template_version': '2013-05-23',
3572
'AParam': {'type': 'string', 'default': 'foo'}},
3575
'type': 'ResourceWithPropsType',
3576
'properties': {'Foo': {'get_param': 'AParam'}}
3581
updated_stack = parser.Stack(self.ctx, 'updated_stack',
3582
template.Template(tmpl2))
3586
self.stack.update(updated_stack)
3587
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
3589
self.assertEqual('foo', self.stack['AResource'].properties['Foo'])
3592
def test_stack_create_timeout(self):
3593
self.m.StubOutWithMock(scheduler.DependencyTaskGroup, '__call__')
3594
self.m.StubOutWithMock(scheduler, 'wallclock')
3596
stack = parser.Stack(self.ctx, 's', self.tmpl)
3602
start_time = time.time()
3603
scheduler.wallclock().AndReturn(start_time)
3604
scheduler.wallclock().AndReturn(start_time + 1)
3605
scheduler.DependencyTaskGroup.__call__().AndReturn(dummy_task())
3606
scheduler.wallclock().AndReturn(start_time + stack.timeout_secs() + 1)
3612
self.assertEqual((parser.Stack.CREATE, parser.Stack.FAILED),
3614
self.assertEqual('Create timed out', stack.status_reason)
3618
def test_stack_delete_timeout(self):
3619
stack = parser.Stack(self.ctx, 'delete_test',
3621
stack_id = stack.store()
3623
db_s = db_api.stack_get(self.ctx, stack_id)
3624
self.assertIsNotNone(db_s)
3626
self.m.StubOutWithMock(scheduler.DependencyTaskGroup, '__call__')
3627
self.m.StubOutWithMock(scheduler, 'wallclock')
3633
start_time = time.time()
3634
scheduler.wallclock().AndReturn(start_time)
3635
scheduler.wallclock().AndReturn(start_time + 1)
3636
scheduler.DependencyTaskGroup.__call__().AndReturn(dummy_task())
3637
scheduler.wallclock().AndReturn(start_time + stack.timeout_secs() + 1)
3641
self.assertEqual((parser.Stack.DELETE, parser.Stack.FAILED),
3643
self.assertEqual('Delete timed out', stack.status_reason)
3647
def test_stack_delete_resourcefailure(self):
3648
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3649
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
3650
self.m.StubOutWithMock(generic_rsrc.GenericResource, 'handle_delete')
3651
exc = Exception('foo')
3652
generic_rsrc.GenericResource.handle_delete().AndRaise(exc)
3655
self.stack = parser.Stack(self.ctx, 'delete_test_fail',
3656
parser.Template(tmpl))
3660
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
3665
self.assertEqual((self.stack.DELETE, self.stack.FAILED),
3667
self.assertEqual('Resource DELETE failed: Exception: foo',
3668
self.stack.status_reason)
3671
def test_stack_name_valid(self):
3672
stack = parser.Stack(self.ctx, 's', self.tmpl)
3673
self.assertIsInstance(stack, parser.Stack)
3674
stack = parser.Stack(self.ctx, 'stack123', self.tmpl)
3675
self.assertIsInstance(stack, parser.Stack)
3676
stack = parser.Stack(self.ctx, 'test.stack', self.tmpl)
3677
self.assertIsInstance(stack, parser.Stack)
3678
stack = parser.Stack(self.ctx, 'test_stack', self.tmpl)
3679
self.assertIsInstance(stack, parser.Stack)
3680
stack = parser.Stack(self.ctx, 'TEST', self.tmpl)
3681
self.assertIsInstance(stack, parser.Stack)
3682
stack = parser.Stack(self.ctx, 'test-stack', self.tmpl)
3683
self.assertIsInstance(stack, parser.Stack)
3685
def test_stack_name_invalid(self):
3686
stack_names = ['_foo', '1bad', '.kcats', 'test stack', ' teststack',
3687
'^-^', '"stack"', '1234', 'cat|dog', '$(foo)',
3688
'test/stack', 'test\stack', 'test::stack', 'test;stack',
3689
'test~stack', '#test']
3690
for stack_name in stack_names:
3691
self.assertRaises(exception.StackValidationFailed, parser.Stack,
3692
self.ctx, stack_name, self.tmpl)
3694
def test_resource_state_get_att(self):
3696
'HeatTemplateFormatVersion': '2012-12-12',
3697
'Resources': {'AResource': {'Type': 'GenericResourceType'}},
3698
'Outputs': {'TestOutput': {'Value': {
3699
'Fn::GetAtt': ['AResource', 'Foo']}}
3703
self.stack = parser.Stack(self.ctx, 'resource_state_get_att',
3704
template.Template(tmpl))
3707
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3709
self.assertIn('AResource', self.stack)
3710
rsrc = self.stack['AResource']
3711
rsrc.resource_id_set('aaaa')
3712
self.assertEqual('AResource', rsrc.FnGetAtt('Foo'))
3714
for action, status in (
3715
(rsrc.CREATE, rsrc.IN_PROGRESS),
3716
(rsrc.CREATE, rsrc.COMPLETE),
3717
(rsrc.CREATE, rsrc.FAILED),
3718
(rsrc.SUSPEND, rsrc.IN_PROGRESS),
3719
(rsrc.SUSPEND, rsrc.COMPLETE),
3720
(rsrc.RESUME, rsrc.IN_PROGRESS),
3721
(rsrc.RESUME, rsrc.COMPLETE),
3722
(rsrc.UPDATE, rsrc.IN_PROGRESS),
3723
(rsrc.UPDATE, rsrc.FAILED),
3724
(rsrc.UPDATE, rsrc.COMPLETE)):
3725
rsrc.state_set(action, status)
3726
self.assertEqual('AResource', self.stack.output('TestOutput'))
3727
for action, status in (
3728
(rsrc.DELETE, rsrc.IN_PROGRESS),
3729
(rsrc.DELETE, rsrc.FAILED),
3730
(rsrc.DELETE, rsrc.COMPLETE)):
3731
rsrc.state_set(action, status)
3732
self.assertIsNone(self.stack.output('TestOutput'))
3734
def test_resource_required_by(self):
3735
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3736
'Resources': {'AResource': {'Type': 'GenericResourceType'},
3737
'BResource': {'Type': 'GenericResourceType',
3738
'DependsOn': 'AResource'},
3739
'CResource': {'Type': 'GenericResourceType',
3740
'DependsOn': 'BResource'},
3741
'DResource': {'Type': 'GenericResourceType',
3742
'DependsOn': 'BResource'}}}
3744
self.stack = parser.Stack(self.ctx, 'depends_test_stack',
3745
template.Template(tmpl))
3748
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3751
self.assertEqual(['BResource'],
3752
self.stack['AResource'].required_by())
3753
self.assertEqual([],
3754
self.stack['CResource'].required_by())
3755
required_by = self.stack['BResource'].required_by()
3756
self.assertEqual(2, len(required_by))
3757
for r in ['CResource', 'DResource']:
3758
self.assertIn(r, required_by)
3760
def test_resource_multi_required_by(self):
3761
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3762
'Resources': {'AResource': {'Type': 'GenericResourceType'},
3763
'BResource': {'Type': 'GenericResourceType'},
3764
'CResource': {'Type': 'GenericResourceType'},
3765
'DResource': {'Type': 'GenericResourceType',
3766
'DependsOn': ['AResource',
3770
self.stack = parser.Stack(self.ctx, 'depends_test_stack',
3771
template.Template(tmpl))
3774
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
3777
for r in ['AResource', 'BResource', 'CResource']:
3778
self.assertEqual(['DResource'],
3779
self.stack[r].required_by())
3781
def test_store_saves_owner(self):
3783
The owner_id attribute of Store is saved to the database when stored.
3785
self.stack = parser.Stack(
3786
self.ctx, 'owner_stack', self.tmpl)
3787
stack_ownee = parser.Stack(
3788
self.ctx, 'ownee_stack', self.tmpl,
3789
owner_id=self.stack.id)
3791
db_stack = db_api.stack_get(self.ctx, stack_ownee.id)
3792
self.assertEqual(self.stack.id, db_stack.owner_id)
3794
def test_init_user_creds_id(self):
3795
ctx_init = utils.dummy_context(user='my_user',
3797
ctx_init.request_id = self.ctx.request_id
3798
creds = db_api.user_creds_create(ctx_init)
3799
self.stack = parser.Stack(self.ctx, 'creds_init', self.tmpl,
3800
user_creds_id=creds.id)
3802
self.assertEqual(creds.id, self.stack.user_creds_id)
3803
ctx_expected = ctx_init.to_dict()
3804
ctx_expected['auth_token'] = None
3805
self.assertEqual(ctx_expected, self.stack.stored_context().to_dict())
3807
def test_store_saves_creds(self):
3809
A user_creds entry is created on first stack store
3811
self.stack = parser.Stack(
3812
self.ctx, 'creds_stack', self.tmpl)
3815
# The store should've created a user_creds row and set user_creds_id
3816
db_stack = db_api.stack_get(self.ctx, self.stack.id)
3817
user_creds_id = db_stack.user_creds_id
3818
self.assertIsNotNone(user_creds_id)
3820
# should've stored the username/password in the context
3821
user_creds = db_api.user_creds_get(user_creds_id)
3822
self.assertEqual(self.ctx.username, user_creds.get('username'))
3823
self.assertEqual(self.ctx.password, user_creds.get('password'))
3824
self.assertIsNone(user_creds.get('trust_id'))
3825
self.assertIsNone(user_creds.get('trustor_user_id'))
3827
# Check the stored_context is as expected
3828
expected_context = context.RequestContext.from_dict(self.ctx.to_dict())
3829
expected_context.auth_token = None
3830
stored_context = self.stack.stored_context().to_dict()
3831
self.assertEqual(expected_context.to_dict(), stored_context)
3833
# Store again, ID should not change
3835
self.assertEqual(user_creds_id, db_stack.user_creds_id)
3837
def test_store_saves_creds_trust(self):
3839
A user_creds entry is created on first stack store
3841
cfg.CONF.set_override('deferred_auth_method', 'trusts')
3843
self.m.StubOutWithMock(keystone.KeystoneClientPlugin, '_create')
3844
keystone.KeystoneClientPlugin._create().AndReturn(
3845
fakes.FakeKeystoneClient(user_id='auser123'))
3848
self.stack = parser.Stack(
3849
self.ctx, 'creds_stack', self.tmpl)
3852
# The store should've created a user_creds row and set user_creds_id
3853
db_stack = db_api.stack_get(self.ctx, self.stack.id)
3854
user_creds_id = db_stack.user_creds_id
3855
self.assertIsNotNone(user_creds_id)
3857
# should've stored the trust_id and trustor_user_id returned from
3858
# FakeKeystoneClient.create_trust_context, username/password should
3859
# not have been stored
3860
user_creds = db_api.user_creds_get(user_creds_id)
3861
self.assertIsNone(user_creds.get('username'))
3862
self.assertIsNone(user_creds.get('password'))
3863
self.assertEqual('atrust', user_creds.get('trust_id'))
3864
self.assertEqual('auser123', user_creds.get('trustor_user_id'))
3866
# Check the stored_context is as expected
3867
expected_context = context.RequestContext(
3868
trust_id='atrust', trustor_user_id='auser123',
3869
request_id=self.ctx.request_id, is_admin=False).to_dict()
3870
stored_context = self.stack.stored_context().to_dict()
3871
self.assertEqual(expected_context, stored_context)
3873
# Store again, ID should not change
3875
self.assertEqual(user_creds_id, db_stack.user_creds_id)
3877
def test_backup_copies_user_creds_id(self):
3878
ctx_init = utils.dummy_context(user='my_user',
3880
ctx_init.request_id = self.ctx.request_id
3881
creds = db_api.user_creds_create(ctx_init)
3882
self.stack = parser.Stack(self.ctx, 'creds_init', self.tmpl,
3883
user_creds_id=creds.id)
3885
self.assertEqual(creds.id, self.stack.user_creds_id)
3886
backup = self.stack._backup_stack()
3887
self.assertEqual(creds.id, backup.user_creds_id)
3889
def test_stored_context_err(self):
3891
Test stored_context error path.
3893
self.stack = parser.Stack(self.ctx, 'creds_stack', self.tmpl)
3894
ex = self.assertRaises(exception.Error, self.stack.stored_context)
3895
expected_err = 'Attempt to use stored_context with no user_creds'
3896
self.assertEqual(expected_err, six.text_type(ex))
3898
def test_store_gets_username_from_stack(self):
3899
self.stack = parser.Stack(self.ctx, 'username_stack',
3900
self.tmpl, username='foobar')
3901
self.ctx.username = 'not foobar'
3903
db_stack = db_api.stack_get(self.ctx, self.stack.id)
3904
self.assertEqual('foobar', db_stack.username)
3906
def test_store_backup_true(self):
3907
self.stack = parser.Stack(self.ctx, 'username_stack',
3908
self.tmpl, username='foobar')
3909
self.ctx.username = 'not foobar'
3910
self.stack.store(backup=True)
3911
db_stack = db_api.stack_get(self.ctx, self.stack.id)
3912
self.assertTrue(db_stack.backup)
3914
def test_store_backup_false(self):
3915
self.stack = parser.Stack(self.ctx, 'username_stack',
3916
self.tmpl, username='foobar')
3917
self.ctx.username = 'not foobar'
3918
self.stack.store(backup=False)
3919
db_stack = db_api.stack_get(self.ctx, self.stack.id)
3920
self.assertFalse(db_stack.backup)
3922
def test_init_stored_context_false(self):
3923
ctx_init = utils.dummy_context(user='mystored_user',
3924
password='mystored_pass')
3925
ctx_init.request_id = self.ctx.request_id
3926
creds = db_api.user_creds_create(ctx_init)
3927
self.stack = parser.Stack(self.ctx, 'creds_store1', self.tmpl,
3928
user_creds_id=creds.id,
3929
use_stored_context=False)
3930
ctx_expected = self.ctx.to_dict()
3931
self.assertEqual(ctx_expected, self.stack.context.to_dict())
3933
self.assertEqual(ctx_expected, self.stack.context.to_dict())
3935
def test_init_stored_context_true(self):
3936
ctx_init = utils.dummy_context(user='mystored_user',
3937
password='mystored_pass')
3938
ctx_init.request_id = self.ctx.request_id
3939
creds = db_api.user_creds_create(ctx_init)
3940
self.stack = parser.Stack(self.ctx, 'creds_store2', self.tmpl,
3941
user_creds_id=creds.id,
3942
use_stored_context=True)
3943
ctx_expected = ctx_init.to_dict()
3944
ctx_expected['auth_token'] = None
3945
self.assertEqual(ctx_expected, self.stack.context.to_dict())
3947
self.assertEqual(ctx_expected, self.stack.context.to_dict())
3949
def test_load_stored_context_false(self):
3950
ctx_init = utils.dummy_context(user='mystored_user',
3951
password='mystored_pass')
3952
ctx_init.request_id = self.ctx.request_id
3953
creds = db_api.user_creds_create(ctx_init)
3954
self.stack = parser.Stack(self.ctx, 'creds_store3', self.tmpl,
3955
user_creds_id=creds.id)
3958
load_stack = parser.Stack.load(self.ctx, stack_id=self.stack.id,
3959
use_stored_context=False)
3960
self.assertEqual(self.ctx.to_dict(), load_stack.context.to_dict())
3962
def test_load_stored_context_true(self):
3963
ctx_init = utils.dummy_context(user='mystored_user',
3964
password='mystored_pass')
3965
ctx_init.request_id = self.ctx.request_id
3966
creds = db_api.user_creds_create(ctx_init)
3967
self.stack = parser.Stack(self.ctx, 'creds_store4', self.tmpl,
3968
user_creds_id=creds.id)
3970
ctx_expected = ctx_init.to_dict()
3971
ctx_expected['auth_token'] = None
3973
load_stack = parser.Stack.load(self.ctx, stack_id=self.stack.id,
3974
use_stored_context=True)
3975
self.assertEqual(ctx_expected, load_stack.context.to_dict())
3977
def test_load_honors_owner(self):
3979
Loading a stack from the database will set the owner_id of the
3980
resultant stack appropriately.
3982
self.stack = parser.Stack(
3983
self.ctx, 'owner_stack', self.tmpl)
3984
stack_ownee = parser.Stack(
3985
self.ctx, 'ownee_stack', self.tmpl,
3986
owner_id=self.stack.id)
3989
saved_stack = parser.Stack.load(self.ctx, stack_id=stack_ownee.id)
3990
self.assertEqual(self.stack.id, saved_stack.owner_id)
3992
def test_requires_deferred_auth(self):
3993
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
3994
'Resources': {'AResource': {'Type': 'GenericResourceType'},
3995
'BResource': {'Type': 'GenericResourceType'},
3996
'CResource': {'Type': 'GenericResourceType'}}}
3998
self.stack = parser.Stack(self.ctx, 'update_test_stack',
3999
template.Template(tmpl),
4000
disable_rollback=False)
4002
self.assertFalse(self.stack.requires_deferred_auth())
4004
self.stack['CResource'].requires_deferred_auth = True
4005
self.assertTrue(self.stack.requires_deferred_auth())
4007
def test_stack_user_project_id_default(self):
4008
self.stack = parser.Stack(self.ctx, 'user_project_none',
4011
self.assertIsNone(self.stack.stack_user_project_id)
4012
db_stack = db_api.stack_get(self.ctx, self.stack.id)
4013
self.assertIsNone(db_stack.stack_user_project_id)
4015
def test_stack_user_project_id_constructor(self):
4016
self.stub_keystoneclient()
4019
self.stack = parser.Stack(self.ctx, 'user_project_init',
4021
stack_user_project_id='aproject1234')
4023
self.assertEqual('aproject1234', self.stack.stack_user_project_id)
4024
db_stack = db_api.stack_get(self.ctx, self.stack.id)
4025
self.assertEqual('aproject1234', db_stack.stack_user_project_id)
4028
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
4032
def test_stack_user_project_id_delete_fail(self):
4034
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
4035
def delete_stack_domain_project(self, project_id):
4036
raise kc_exceptions.Forbidden("Denied!")
4038
self.m.StubOutWithMock(keystone.KeystoneClientPlugin, '_create')
4039
keystone.KeystoneClientPlugin._create().AndReturn(
4040
FakeKeystoneClientFail())
4043
self.stack = parser.Stack(self.ctx, 'user_project_init',
4045
stack_user_project_id='aproject1234')
4047
self.assertEqual('aproject1234', self.stack.stack_user_project_id)
4048
db_stack = db_api.stack_get(self.ctx, self.stack.id)
4049
self.assertEqual('aproject1234', db_stack.stack_user_project_id)
4052
self.assertEqual((parser.Stack.DELETE, parser.Stack.FAILED),
4054
self.assertIn('Error deleting project', self.stack.status_reason)
4057
def test_stack_user_project_id_setter(self):
4058
self.stub_keystoneclient()
4061
self.stack = parser.Stack(self.ctx, 'user_project_init',
4064
self.assertIsNone(self.stack.stack_user_project_id)
4065
self.stack.set_stack_user_project_id(project_id='aproject456')
4066
self.assertEqual('aproject456', self.stack.stack_user_project_id)
4067
db_stack = db_api.stack_get(self.ctx, self.stack.id)
4068
self.assertEqual('aproject456', db_stack.stack_user_project_id)
4071
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
4075
def test_stack_user_project_id_create(self):
4076
self.stub_keystoneclient()
4079
self.stack = parser.Stack(self.ctx, 'user_project_init',
4082
self.assertIsNone(self.stack.stack_user_project_id)
4083
self.stack.create_stack_user_project_id()
4085
self.assertEqual('aprojectid', self.stack.stack_user_project_id)
4086
db_stack = db_api.stack_get(self.ctx, self.stack.id)
4087
self.assertEqual('aprojectid', db_stack.stack_user_project_id)
4090
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
4094
def test_preview_resources_returns_list_of_resource_previews(self):
4095
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
4096
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
4097
self.stack = parser.Stack(self.ctx, 'preview_stack',
4098
template.Template(tmpl))
4100
res.preview.return_value = 'foo'
4101
self.stack._resources = {'r1': res}
4103
resources = self.stack.preview_resources()
4104
self.assertEqual(['foo'], resources)
4106
def test_correct_outputs(self):
4107
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
4109
'AResource': {'Type': 'ResourceWithPropsType',
4110
'Properties': {'Foo': 'abc'}},
4111
'BResource': {'Type': 'ResourceWithPropsType',
4112
'Properties': {'Foo': 'def'}}},
4116
'Fn::GetAtt': ['AResource', 'Foo']}}}}
4118
self.stack = parser.Stack(self.ctx, 'stack_with_correct_outputs',
4119
template.Template(tmpl))
4124
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
4126
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
4127
# According _resolve_attribute method in GenericResource output
4128
# value will be equal with name AResource.
4129
self.assertEqual('AResource', self.stack.output('Resource_attr'))
4133
self.assertEqual((self.stack.DELETE, self.stack.COMPLETE),
4136
def test_incorrect_outputs(self):
4137
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
4139
'AResource': {'Type': 'ResourceWithPropsType',
4140
'Properties': {'Foo': 'abc'}}},
4144
'Fn::GetAtt': ['AResource', 'Bar']}}}}
4146
self.stack = parser.Stack(self.ctx, 'stack_with_incorrect_outputs',
4147
template.Template(tmpl))
4152
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
4155
self.assertIsNone(self.stack.output('Resource_attr'))
4156
self.assertEqual('The Referenced Attribute (AResource Bar) is '
4158
self.stack.outputs['Resource_attr']['error_msg'])
4162
self.assertEqual((self.stack.DELETE, self.stack.COMPLETE),
4165
def test_stack_load_no_param_value_validation(self):
4167
Test stack loading with disabled parameter value validation.
4169
tmpl = template_format.parse('''
4170
heat_template_version: 2013-05-23
4174
description: A flavor.
4176
- custom_constraint: nova.flavor
4179
type: GenericResourceType
4182
# Mock objects so the query for flavors in server.FlavorConstraint
4183
# works for stack creation
4184
fc = fakes.FakeClient()
4185
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
4186
nova.NovaClientPlugin._create().AndReturn(fc)
4188
fc.flavors = self.m.CreateMockAnything()
4189
flavor = collections.namedtuple("Flavor", ["id", "name"])
4191
flavor.name = "dummy"
4192
fc.flavors.list().AndReturn([flavor])
4196
self.stack = parser.Stack(self.ctx, 'stack_with_custom_constraint',
4197
template.Template(tmpl),
4198
environment.Environment({'flavor': 'dummy'}))
4200
self.stack.validate()
4203
stack_id = self.stack.id
4207
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
4210
loaded_stack = parser.Stack.load(self.ctx, stack_id=self.stack.id)
4211
self.assertEqual(stack_id, loaded_stack.parameters['OS::stack_id'])
4213
# verify that fc.flavors.list() has not been called, i.e. verify that
4214
# parameter value validation did not happen and FlavorConstraint was
4218
def test_snapshot_delete(self):
4221
class ResourceDeleteSnapshot(generic_rsrc.ResourceWithProps):
4223
def handle_delete_snapshot(self, data):
4224
snapshots.append(data)
4226
resource._register_class(
4227
'ResourceDeleteSnapshot', ResourceDeleteSnapshot)
4228
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
4229
'Resources': {'AResource': {'Type': 'ResourceDeleteSnapshot'}}}
4231
self.stack = parser.Stack(self.ctx, 'snapshot_stack',
4232
template.Template(tmpl))
4233
data = self.stack.prepare_abandon()
4234
fake_snapshot = collections.namedtuple('Snapshot', ('data',))(data)
4235
self.stack.delete_snapshot(fake_snapshot)
4236
self.assertEqual([data['resources']['AResource']], snapshots)
4238
def test_incorrect_outputs_cfn_get_attr(self):
4239
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
4241
'AResource': {'Type': 'ResourceWithPropsType',
4242
'Properties': {'Foo': 'abc'}}},
4246
'Fn::GetAtt': ['AResource', 'Bar']}}}}
4248
self.stack = parser.Stack(self.ctx, 'stack_with_correct_outputs',
4249
template.Template(tmpl))
4251
ex = self.assertRaises(exception.StackValidationFailed,
4252
self.stack.validate)
4254
self.assertEqual('Output validation error: The Referenced Attribute '
4255
'(AResource Bar) is incorrect.',
4258
def test_incorrect_outputs_cfn_incorrect_reference(self):
4259
tmpl = template_format.parse("""
4260
HeatTemplateFormatVersion: '2012-12-12'
4268
self.stack = parser.Stack(self.ctx, 'stack_with_incorrect_outputs',
4269
template.Template(tmpl))
4271
ex = self.assertRaises(exception.StackValidationFailed,
4272
self.stack.validate)
4274
self.assertIn('The specified reference "Resource" '
4275
'(in unknown) is incorrect.', six.text_type(ex))
4277
def test_incorrect_outputs_incorrect_reference(self):
4278
tmpl = template_format.parse("""
4279
heat_template_version: 2013-05-23
4282
value: { get_attr: [resource, foo] }
4284
self.stack = parser.Stack(self.ctx, 'stack_with_incorrect_outputs',
4285
template.Template(tmpl))
4287
ex = self.assertRaises(exception.StackValidationFailed,
4288
self.stack.validate)
4290
self.assertIn('The specified reference "resource" '
4291
'(in unknown) is incorrect.', six.text_type(ex))
4293
def test_incorrect_outputs_cfn_empty_output(self):
4294
tmpl = template_format.parse("""
4295
HeatTemplateFormatVersion: '2012-12-12'
4298
Type: ResourceWithPropsType
4304
self.stack = parser.Stack(self.ctx, 'stack_with_correct_outputs',
4305
template.Template(tmpl))
4307
ex = self.assertRaises(exception.StackValidationFailed,
4308
self.stack.validate)
4310
self.assertIn('Each Output must contain a Value key.',
4313
def test_incorrect_outputs_cfn_string_data(self):
4314
tmpl = template_format.parse("""
4315
HeatTemplateFormatVersion: '2012-12-12'
4318
Type: ResourceWithPropsType
4325
self.stack = parser.Stack(self.ctx, 'stack_with_correct_outputs',
4326
template.Template(tmpl))
4328
ex = self.assertRaises(exception.StackValidationFailed,
4329
self.stack.validate)
4331
self.assertIn('Outputs must contain Output. '
4332
'Found a [%s] instead' % six.text_type,
4335
def test_prop_validate_value(self):
4336
tmpl = template_format.parse("""
4337
HeatTemplateFormatVersion: '2012-12-12'
4340
Type: ResourceWithPropsType
4344
self.stack = parser.Stack(self.ctx, 'stack_with_bad_property',
4345
template.Template(tmpl))
4347
ex = self.assertRaises(exception.StackValidationFailed,
4348
self.stack.validate)
4350
self.assertIn("'notanint' is not an integer",
4353
self.stack.strict_validate = False
4354
self.assertIsNone(self.stack.validate())
4356
def test_param_validate_value(self):
4357
tmpl = template_format.parse("""
4358
HeatTemplateFormatVersion: '2012-12-12'
4364
env1 = {'parameters': {'foo': 'abc'}}
4365
self.stack = parser.Stack(self.ctx, 'stack_with_bad_param',
4366
template.Template(tmpl),
4367
env=environment.Environment(env1))
4369
ex = self.assertRaises(exception.StackValidationFailed,
4370
self.stack.validate)
4372
self.assertIn("could not convert string to float: abc",
4375
self.stack.strict_validate = False
4376
self.assertIsNone(self.stack.validate())
4378
def test_incorrect_outputs_cfn_list_data(self):
4379
tmpl = template_format.parse("""
4380
HeatTemplateFormatVersion: '2012-12-12'
4383
Type: ResourceWithPropsType
4388
- Data is not what it seems
4390
self.stack = parser.Stack(self.ctx, 'stack_with_correct_outputs',
4391
template.Template(tmpl))
4393
ex = self.assertRaises(exception.StackValidationFailed,
4394
self.stack.validate)
4396
self.assertIn('Outputs must contain Output. '
4397
'Found a [%s] instead' % type([]), six.text_type(ex))
4399
def test_incorrect_outputs_hot_get_attr(self):
4400
tmpl = {'heat_template_version': '2013-05-23',
4402
'AResource': {'type': 'ResourceWithPropsType',
4403
'properties': {'Foo': 'abc'}}},
4407
'get_attr': ['AResource', 'Bar']}}}}
4409
self.stack = parser.Stack(self.ctx, 'stack_with_correct_outputs',
4410
template.Template(tmpl))
4412
ex = self.assertRaises(exception.StackValidationFailed,
4413
self.stack.validate)
4415
self.assertEqual('Output validation error: The Referenced Attribute '
4416
'(AResource Bar) is incorrect.',
4419
def test_restore(self):
4420
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
4422
'A': {'Type': 'GenericResourceType'},
4423
'B': {'Type': 'GenericResourceType'}}}
4424
self.stack = parser.Stack(self.ctx, 'stack_details_test',
4425
parser.Template(tmpl))
4429
data = copy.deepcopy(self.stack.prepare_abandon())
4430
fake_snapshot = collections.namedtuple(
4431
'Snapshot', ('data', 'stack_id'))(data, self.stack.id)
4433
new_tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
4434
'Resources': {'A': {'Type': 'GenericResourceType'}}}
4435
updated_stack = parser.Stack(self.ctx, 'updated_stack',
4436
template.Template(new_tmpl))
4437
self.stack.update(updated_stack)
4438
self.assertEqual(1, len(self.stack.resources))
4440
self.stack.restore(fake_snapshot)
4442
self.assertEqual((parser.Stack.RESTORE, parser.Stack.COMPLETE),
4444
self.assertEqual(2, len(self.stack.resources))
4446
def test_hot_restore(self):
4448
class ResourceWithRestore(generic_rsrc.ResWithComplexPropsAndAttrs):
4450
def handle_restore(self, defn, data):
4452
(key, value) for (key, value) in
4453
six.iteritems(defn.properties(self.properties_schema))
4454
if value is not None)
4455
value = data['resource_data']['a_string']
4456
props['a_string'] = value
4457
return defn.freeze(properties=props)
4459
resource._register_class('ResourceWithRestore', ResourceWithRestore)
4460
tpl = {'heat_template_version': '2013-05-23',
4462
{'A': {'type': 'ResourceWithRestore'}}}
4463
self.stack = parser.Stack(self.ctx, 'stack_details_test',
4464
parser.Template(tpl))
4468
data = self.stack.prepare_abandon()
4469
data['resources']['A']['resource_data']['a_string'] = 'foo'
4470
fake_snapshot = collections.namedtuple(
4471
'Snapshot', ('data', 'stack_id'))(data, self.stack.id)
4473
self.stack.restore(fake_snapshot)
4475
self.assertEqual((parser.Stack.RESTORE, parser.Stack.COMPLETE),
4479
'foo', self.stack.resources['A'].properties['a_string'])