blob: 392bdddd1b9c3ac981141279b47265e423506a72 [file] [log] [blame]
Angus Salkeld2bd63a42015-01-07 11:11:29 +10001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import json
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020014
Angus Salkeld2bd63a42015-01-07 11:11:29 +100015import yaml
16
17from heat_integrationtests.common import test
18
19
Angus Salkeld2bd63a42015-01-07 11:11:29 +100020class TemplateResourceTest(test.HeatIntegrationTest):
21 """Prove that we can use the registry in a nested provider."""
Angus Salkeld95403d82015-02-12 14:06:01 +100022
23 template = '''
24heat_template_version: 2013-05-23
25resources:
26 secret1:
27 type: OS::Heat::RandomString
28outputs:
29 secret-out:
30 value: { get_attr: [secret1, value] }
31'''
32 nested_templ = '''
33heat_template_version: 2013-05-23
34resources:
35 secret2:
36 type: OS::Heat::RandomString
37outputs:
38 value:
39 value: { get_attr: [secret2, value] }
40'''
41
42 env_templ = '''
43resource_registry:
44 "OS::Heat::RandomString": nested.yaml
45'''
46
Angus Salkeld2bd63a42015-01-07 11:11:29 +100047 def setUp(self):
48 super(TemplateResourceTest, self).setUp()
49 self.client = self.orchestration_client
50
51 def test_nested_env(self):
52 main_templ = '''
53heat_template_version: 2013-05-23
54resources:
55 secret1:
56 type: My::NestedSecret
57outputs:
58 secret-out:
59 value: { get_attr: [secret1, value] }
60'''
61
62 nested_templ = '''
63heat_template_version: 2013-05-23
64resources:
65 secret2:
66 type: My::Secret
67outputs:
68 value:
69 value: { get_attr: [secret2, value] }
70'''
71
72 env_templ = '''
73resource_registry:
74 "My::Secret": "OS::Heat::RandomString"
75 "My::NestedSecret": nested.yaml
76'''
77
78 stack_identifier = self.stack_create(
79 template=main_templ,
80 files={'nested.yaml': nested_templ},
81 environment=env_templ)
Angus Salkeld2e61f9f2015-04-07 09:25:50 +100082 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
83 'secret1')
84 # prove that resource.parent_resource is populated.
85 sec2 = self.client.resources.get(nested_ident, 'secret2')
86 self.assertEqual('secret1', sec2.parent_resource)
Angus Salkeld2bd63a42015-01-07 11:11:29 +100087
88 def test_no_infinite_recursion(self):
89 """Prove that we can override a python resource.
90
91 And use that resource within the template resource.
92 """
Angus Salkeld2bd63a42015-01-07 11:11:29 +100093 stack_identifier = self.stack_create(
Angus Salkeld95403d82015-02-12 14:06:01 +100094 template=self.template,
95 files={'nested.yaml': self.nested_templ},
96 environment=self.env_templ)
Angus Salkeld2bd63a42015-01-07 11:11:29 +100097 self.assert_resource_is_a_stack(stack_identifier, 'secret1')
98
Angus Salkeld95403d82015-02-12 14:06:01 +100099 def test_nested_stack_delete_then_delete_parent_stack(self):
100 """Check the robustness of stack deletion.
101
102 This tests that if you manually delete a nested
103 stack, the parent stack is still deletable.
104 """
105 name = self._stack_rand_name()
106 # do this manually so we can call _stack_delete() directly.
107 self.client.stacks.create(
108 stack_name=name,
109 template=self.template,
110 files={'nested.yaml': self.nested_templ},
111 environment=self.env_templ,
112 disable_rollback=True)
113 stack = self.client.stacks.get(name)
114 stack_identifier = '%s/%s' % (name, stack.id)
115 self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
116
117 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
118 'secret1')
119
120 self._stack_delete(nested_ident)
121 self._stack_delete(stack_identifier)
122
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000123
124class NestedAttributesTest(test.HeatIntegrationTest):
125 """Prove that we can use the template resource references."""
126
127 main_templ = '''
128heat_template_version: 2014-10-16
129resources:
130 secret2:
131 type: My::NestedSecret
132outputs:
133 old_way:
134 value: { get_attr: [secret2, nested_str]}
135 test_attr1:
136 value: { get_attr: [secret2, resource.secret1, value]}
137 test_attr2:
138 value: { get_attr: [secret2, resource.secret1.value]}
139 test_ref:
140 value: { get_resource: secret2 }
141'''
142
143 env_templ = '''
144resource_registry:
145 "My::NestedSecret": nested.yaml
146'''
147
148 def setUp(self):
149 super(NestedAttributesTest, self).setUp()
150 self.client = self.orchestration_client
151
152 def test_stack_ref(self):
153 nested_templ = '''
154heat_template_version: 2014-10-16
155resources:
156 secret1:
157 type: OS::Heat::RandomString
158'''
159 stack_identifier = self.stack_create(
160 template=self.main_templ,
161 files={'nested.yaml': nested_templ},
162 environment=self.env_templ)
163 self.assert_resource_is_a_stack(stack_identifier, 'secret2')
164 stack = self.client.stacks.get(stack_identifier)
165 test_ref = self._stack_output(stack, 'test_ref')
166 self.assertIn('arn:openstack:heat:', test_ref)
167
168 def test_transparent_ref(self):
169 """With the addition of OS::stack_id we can now use the nested resource
170 more transparently.
171 """
172 nested_templ = '''
173heat_template_version: 2014-10-16
174resources:
175 secret1:
176 type: OS::Heat::RandomString
177outputs:
178 OS::stack_id:
179 value: {get_resource: secret1}
180 nested_str:
181 value: {get_attr: [secret1, value]}
182'''
183 stack_identifier = self.stack_create(
184 template=self.main_templ,
185 files={'nested.yaml': nested_templ},
186 environment=self.env_templ)
187 self.assert_resource_is_a_stack(stack_identifier, 'secret2')
188 stack = self.client.stacks.get(stack_identifier)
189 test_ref = self._stack_output(stack, 'test_ref')
190 test_attr = self._stack_output(stack, 'old_way')
191
192 self.assertNotIn('arn:openstack:heat', test_ref)
193 self.assertEqual(test_attr, test_ref)
194
195 def test_nested_attributes(self):
196 nested_templ = '''
197heat_template_version: 2014-10-16
198resources:
199 secret1:
200 type: OS::Heat::RandomString
201outputs:
202 nested_str:
203 value: {get_attr: [secret1, value]}
204'''
205 stack_identifier = self.stack_create(
206 template=self.main_templ,
207 files={'nested.yaml': nested_templ},
208 environment=self.env_templ)
209 self.assert_resource_is_a_stack(stack_identifier, 'secret2')
210 stack = self.client.stacks.get(stack_identifier)
211 old_way = self._stack_output(stack, 'old_way')
212 test_attr1 = self._stack_output(stack, 'test_attr1')
213 test_attr2 = self._stack_output(stack, 'test_attr2')
214
215 self.assertEqual(old_way, test_attr1)
216 self.assertEqual(old_way, test_attr2)
217
218
219class TemplateResourceUpdateTest(test.HeatIntegrationTest):
220 """Prove that we can do template resource updates."""
221
222 main_template = '''
223HeatTemplateFormatVersion: '2012-12-12'
224Resources:
225 the_nested:
226 Type: the.yaml
227 Properties:
228 one: my_name
229
230Outputs:
231 identifier:
232 Value: {Ref: the_nested}
233 value:
234 Value: {'Fn::GetAtt': [the_nested, the_str]}
235'''
236
237 main_template_2 = '''
238HeatTemplateFormatVersion: '2012-12-12'
239Resources:
240 the_nested:
241 Type: the.yaml
242 Properties:
243 one: updated_name
244
245Outputs:
246 identifier:
247 Value: {Ref: the_nested}
248 value:
249 Value: {'Fn::GetAtt': [the_nested, the_str]}
250'''
251
252 initial_tmpl = '''
253HeatTemplateFormatVersion: '2012-12-12'
254Parameters:
255 one:
256 Default: foo
257 Type: String
258Resources:
259 NestedResource:
260 Type: OS::Heat::RandomString
261 Properties:
262 salt: {Ref: one}
263Outputs:
264 the_str:
265 Value: {'Fn::GetAtt': [NestedResource, value]}
266'''
267 prop_change_tmpl = '''
268HeatTemplateFormatVersion: '2012-12-12'
269Parameters:
270 one:
271 Default: yikes
272 Type: String
273 two:
274 Default: foo
275 Type: String
276Resources:
277 NestedResource:
278 Type: OS::Heat::RandomString
279 Properties:
280 salt: {Ref: one}
281Outputs:
282 the_str:
283 Value: {'Fn::GetAtt': [NestedResource, value]}
284'''
285 attr_change_tmpl = '''
286HeatTemplateFormatVersion: '2012-12-12'
287Parameters:
288 one:
289 Default: foo
290 Type: String
291Resources:
292 NestedResource:
293 Type: OS::Heat::RandomString
294 Properties:
295 salt: {Ref: one}
296Outputs:
297 the_str:
298 Value: {'Fn::GetAtt': [NestedResource, value]}
299 something_else:
300 Value: just_a_string
301'''
302 content_change_tmpl = '''
303HeatTemplateFormatVersion: '2012-12-12'
304Parameters:
305 one:
306 Default: foo
307 Type: String
308Resources:
309 NestedResource:
310 Type: OS::Heat::RandomString
311 Properties:
312 salt: yum
313Outputs:
314 the_str:
315 Value: {'Fn::GetAtt': [NestedResource, value]}
316'''
317
318 EXPECTED = (UPDATE, NOCHANGE) = ('update', 'nochange')
319 scenarios = [
320 ('no_changes', dict(template=main_template,
321 provider=initial_tmpl,
322 expect=NOCHANGE)),
323 ('main_tmpl_change', dict(template=main_template_2,
324 provider=initial_tmpl,
325 expect=UPDATE)),
326 ('provider_change', dict(template=main_template,
327 provider=content_change_tmpl,
328 expect=UPDATE)),
329 ('provider_props_change', dict(template=main_template,
330 provider=prop_change_tmpl,
331 expect=NOCHANGE)),
332 ('provider_attr_change', dict(template=main_template,
333 provider=attr_change_tmpl,
334 expect=NOCHANGE)),
335 ]
336
337 def setUp(self):
338 super(TemplateResourceUpdateTest, self).setUp()
339 self.client = self.orchestration_client
340
341 def test_template_resource_update_template_schema(self):
342 stack_identifier = self.stack_create(
343 template=self.main_template,
344 files={'the.yaml': self.initial_tmpl})
345 stack = self.client.stacks.get(stack_identifier)
346 initial_id = self._stack_output(stack, 'identifier')
347 initial_val = self._stack_output(stack, 'value')
348
349 self.update_stack(stack_identifier,
350 self.template,
351 files={'the.yaml': self.provider})
352 stack = self.client.stacks.get(stack_identifier)
353 self.assertEqual(initial_id,
354 self._stack_output(stack, 'identifier'))
355 if self.expect == self.NOCHANGE:
356 self.assertEqual(initial_val,
357 self._stack_output(stack, 'value'))
358 else:
359 self.assertNotEqual(initial_val,
360 self._stack_output(stack, 'value'))
361
362
Angus Salkeld5a3e1dd2015-02-03 15:31:47 +1000363class TemplateResourceUpdateFailedTest(test.HeatIntegrationTest):
364 """Prove that we can do updates on a nested stack to fix a stack."""
365 main_template = '''
366HeatTemplateFormatVersion: '2012-12-12'
367Resources:
368 keypair:
369 Type: OS::Nova::KeyPair
370 Properties:
371 name: replace-this
372 save_private_key: false
373 server:
374 Type: server_fail.yaml
375 DependsOn: keypair
376'''
377 nested_templ = '''
378HeatTemplateFormatVersion: '2012-12-12'
379Resources:
380 RealRandom:
381 Type: OS::Heat::RandomString
382'''
383
384 def setUp(self):
385 super(TemplateResourceUpdateFailedTest, self).setUp()
386 self.client = self.orchestration_client
Sergey Krayneva265c132015-02-13 03:51:03 -0500387 self.assign_keypair()
Angus Salkeld5a3e1dd2015-02-03 15:31:47 +1000388
389 def test_update_on_failed_create(self):
Vikas Jainac6b02f2015-02-11 11:31:46 -0800390 # create a stack with "server" dependent on "keypair", but
Angus Salkeld5a3e1dd2015-02-03 15:31:47 +1000391 # keypair fails, so "server" is not created properly.
392 # We then fix the template and it should succeed.
393 broken_templ = self.main_template.replace('replace-this',
394 self.keypair_name)
395 stack_identifier = self.stack_create(
396 template=broken_templ,
397 files={'server_fail.yaml': self.nested_templ},
398 expected_status='CREATE_FAILED')
399
400 fixed_templ = self.main_template.replace('replace-this',
401 test.rand_name())
402 self.update_stack(stack_identifier,
403 fixed_templ,
404 files={'server_fail.yaml': self.nested_templ})
405
406
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000407class TemplateResourceAdoptTest(test.HeatIntegrationTest):
408 """Prove that we can do template resource adopt/abandon."""
409
410 main_template = '''
411HeatTemplateFormatVersion: '2012-12-12'
412Resources:
413 the_nested:
414 Type: the.yaml
415 Properties:
416 one: my_name
417Outputs:
418 identifier:
419 Value: {Ref: the_nested}
420 value:
421 Value: {'Fn::GetAtt': [the_nested, the_str]}
422'''
423
424 nested_templ = '''
425HeatTemplateFormatVersion: '2012-12-12'
426Parameters:
427 one:
428 Default: foo
429 Type: String
430Resources:
431 RealRandom:
432 Type: OS::Heat::RandomString
433 Properties:
434 salt: {Ref: one}
435Outputs:
436 the_str:
437 Value: {'Fn::GetAtt': [RealRandom, value]}
438'''
439
440 def setUp(self):
441 super(TemplateResourceAdoptTest, self).setUp()
442 self.client = self.orchestration_client
443
444 def _yaml_to_json(self, yaml_templ):
445 return yaml.load(yaml_templ)
446
447 def test_abandon(self):
448 stack_name = self._stack_rand_name()
449 self.client.stacks.create(
450 stack_name=stack_name,
451 template=self.main_template,
452 files={'the.yaml': self.nested_templ},
453 disable_rollback=True,
454 )
455 stack = self.client.stacks.get(stack_name)
456 stack_identifier = '%s/%s' % (stack_name, stack.id)
457 self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
458
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530459 info = self.stack_abandon(stack_id=stack_identifier)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000460 self.assertEqual(self._yaml_to_json(self.main_template),
461 info['template'])
462 self.assertEqual(self._yaml_to_json(self.nested_templ),
463 info['resources']['the_nested']['template'])
464
465 def test_adopt(self):
466 data = {
467 'resources': {
468 'the_nested': {
469 "type": "the.yaml",
470 "resources": {
471 "RealRandom": {
472 "type": "OS::Heat::RandomString",
473 'resource_data': {'value': 'goopie'},
474 'resource_id': 'froggy'
475 }
476 }
477 }
478 },
479 "environment": {"parameters": {}},
480 "template": yaml.load(self.main_template)
481 }
482
483 stack_identifier = self.stack_adopt(
484 adopt_data=json.dumps(data),
485 files={'the.yaml': self.nested_templ})
486
487 self.assert_resource_is_a_stack(stack_identifier, 'the_nested')
488 stack = self.client.stacks.get(stack_identifier)
489 self.assertEqual('goopie', self._stack_output(stack, 'value'))
Angus Salkeld87601722015-01-05 14:57:27 +1000490
491
492class TemplateResourceCheckTest(test.HeatIntegrationTest):
493 """Prove that we can do template resource check."""
494
495 main_template = '''
496HeatTemplateFormatVersion: '2012-12-12'
497Resources:
498 the_nested:
499 Type: the.yaml
500 Properties:
501 one: my_name
502Outputs:
503 identifier:
504 Value: {Ref: the_nested}
505 value:
506 Value: {'Fn::GetAtt': [the_nested, the_str]}
507'''
508
509 nested_templ = '''
510HeatTemplateFormatVersion: '2012-12-12'
511Parameters:
512 one:
513 Default: foo
514 Type: String
515Resources:
516 RealRandom:
517 Type: OS::Heat::RandomString
518 Properties:
519 salt: {Ref: one}
520Outputs:
521 the_str:
522 Value: {'Fn::GetAtt': [RealRandom, value]}
523'''
524
525 def setUp(self):
526 super(TemplateResourceCheckTest, self).setUp()
527 self.client = self.orchestration_client
528
529 def test_check(self):
530 stack_name = self._stack_rand_name()
531 self.client.stacks.create(
532 stack_name=stack_name,
533 template=self.main_template,
534 files={'the.yaml': self.nested_templ},
535 disable_rollback=True,
536 )
537 stack = self.client.stacks.get(stack_name)
538 stack_identifier = '%s/%s' % (stack_name, stack.id)
539 self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
540
541 self.client.actions.check(stack_id=stack_identifier)
542 self._wait_for_stack_status(stack_identifier, 'CHECK_COMPLETE')