blob: 4fd70af614b5c5b1166ba1524cb095eef5fc420e [file] [log] [blame]
Steven Hardy6f0bda82014-12-12 17:49:10 +00001# 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
Angus Salkeldb61f8f12015-01-19 23:00:45 +100013import copy
Angus Salkeld011acc72015-01-16 20:26:34 +100014import json
15
Steven Hardy6f0bda82014-12-12 17:49:10 +000016from heatclient import exc
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020017import six
Angus Salkeld011acc72015-01-16 20:26:34 +100018import yaml
Steven Hardy6f0bda82014-12-12 17:49:10 +000019
Rabi Mishra477efc92015-07-31 13:01:45 +053020from heat_integrationtests.functional import functional_base
Steven Hardy6f0bda82014-12-12 17:49:10 +000021
22
Rabi Mishra477efc92015-07-31 13:01:45 +053023class ResourceGroupTest(functional_base.FunctionalTestsBase):
Angus Salkeld665d86c2015-01-19 22:15:48 +100024 template = '''
Unmesh Gurjar0a25a732014-12-23 17:28:33 +053025heat_template_version: 2013-05-23
26resources:
27 random_group:
28 type: OS::Heat::ResourceGroup
29 properties:
Angus Salkeld665d86c2015-01-19 22:15:48 +100030 count: 0
Unmesh Gurjar0a25a732014-12-23 17:28:33 +053031 resource_def:
Angus Salkeld665d86c2015-01-19 22:15:48 +100032 type: My::RandomString
Unmesh Gurjar0a25a732014-12-23 17:28:33 +053033 properties:
34 length: 30
Angus Salkeldb61f8f12015-01-19 23:00:45 +100035 salt: initial
Unmesh Gurjar0a25a732014-12-23 17:28:33 +053036outputs:
37 random1:
38 value: {get_attr: [random_group, resource.0.value]}
39 random2:
40 value: {get_attr: [random_group, resource.1.value]}
41 all_values:
42 value: {get_attr: [random_group, value]}
43'''
44
Steven Hardy6f0bda82014-12-12 17:49:10 +000045 def setUp(self):
46 super(ResourceGroupTest, self).setUp()
Steven Hardy6f0bda82014-12-12 17:49:10 +000047
48 def _group_nested_identifier(self, stack_identifier,
49 group_name='random_group'):
50 # Get the nested stack identifier from the group
51 rsrc = self.client.resources.get(stack_identifier, group_name)
52 physical_resource_id = rsrc.physical_resource_id
53
54 nested_stack = self.client.stacks.get(physical_resource_id)
55 nested_identifier = '%s/%s' % (nested_stack.stack_name,
56 nested_stack.id)
57 parent_id = stack_identifier.split("/")[-1]
58 self.assertEqual(parent_id, nested_stack.parent)
59 return nested_identifier
60
61 def test_resource_group_zero_novalidate(self):
62 # Nested resources should be validated only when size > 0
63 # This allows features to be disabled via size=0 without
64 # triggering validation of nested resource custom contraints
65 # e.g images etc in the nested schema.
66 nested_template_fail = '''
67heat_template_version: 2013-05-23
Angus Salkeld665d86c2015-01-19 22:15:48 +100068parameters:
69 length:
70 type: string
71 default: 50
Angus Salkeldb61f8f12015-01-19 23:00:45 +100072 salt:
73 type: string
74 default: initial
Steven Hardy6f0bda82014-12-12 17:49:10 +000075resources:
76 random:
77 type: OS::Heat::RandomString
78 properties:
Angus Salkeld665d86c2015-01-19 22:15:48 +100079 length: BAD
Steven Hardy6f0bda82014-12-12 17:49:10 +000080'''
81
82 files = {'provider.yaml': nested_template_fail}
83 env = {'resource_registry':
84 {'My::RandomString': 'provider.yaml'}}
85 stack_identifier = self.stack_create(
Angus Salkeld665d86c2015-01-19 22:15:48 +100086 template=self.template,
Steven Hardy6f0bda82014-12-12 17:49:10 +000087 files=files,
88 environment=env
89 )
90
91 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
92 self.list_resources(stack_identifier))
93
94 # Check we created an empty nested stack
95 nested_identifier = self._group_nested_identifier(stack_identifier)
96 self.assertEqual({}, self.list_resources(nested_identifier))
97
98 # Prove validation works for non-zero create/update
Angus Salkeld665d86c2015-01-19 22:15:48 +100099 template_two_nested = self.template.replace("count: 0", "count: 2")
Peter Razumovskyd7500792014-10-08 19:19:13 +0400100 expected_err = "Value 'BAD' is not an integer"
Steven Hardy6f0bda82014-12-12 17:49:10 +0000101 ex = self.assertRaises(exc.HTTPBadRequest, self.update_stack,
102 stack_identifier, template_two_nested,
103 environment=env, files=files)
104 self.assertIn(expected_err, six.text_type(ex))
105
106 ex = self.assertRaises(exc.HTTPBadRequest, self.stack_create,
107 template=template_two_nested,
108 environment=env, files=files)
109 self.assertIn(expected_err, six.text_type(ex))
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530110
Steven Hardy0145b8d2015-04-23 14:14:26 +1000111 def _list_group_resources(self, stack_identifier):
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530112 nested_identifier = self._group_nested_identifier(stack_identifier)
Steven Hardy0145b8d2015-04-23 14:14:26 +1000113 return self.list_resources(nested_identifier)
114
115 def _validate_resources(self, stack_identifier, expected_count):
116 resources = self._list_group_resources(stack_identifier)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530117 self.assertEqual(expected_count, len(resources))
118 expected_resources = dict(
Angus Salkeld665d86c2015-01-19 22:15:48 +1000119 (str(idx), 'My::RandomString')
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530120 for idx in range(expected_count))
121
122 self.assertEqual(expected_resources, resources)
123
124 def test_create(self):
125 def validate_output(stack, output_key, length):
126 output_value = self._stack_output(stack, output_key)
127 self.assertEqual(length, len(output_value))
128 return output_value
129 # verify that the resources in resource group are identically
130 # configured, resource names and outputs are appropriate.
Angus Salkeld665d86c2015-01-19 22:15:48 +1000131 env = {'resource_registry':
132 {'My::RandomString': 'OS::Heat::RandomString'}}
133 create_template = self.template.replace("count: 0", "count: 2")
134 stack_identifier = self.stack_create(template=create_template,
135 environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530136 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
137 self.list_resources(stack_identifier))
138
139 # validate count, type and name of resources in a resource group.
140 self._validate_resources(stack_identifier, 2)
141
142 # validate outputs
143 stack = self.client.stacks.get(stack_identifier)
144 outputs = []
145 outputs.append(validate_output(stack, 'random1', 30))
146 outputs.append(validate_output(stack, 'random2', 30))
147 self.assertEqual(outputs, self._stack_output(stack, 'all_values'))
148
149 def test_update_increase_decrease_count(self):
150 # create stack with resource group count 2
Angus Salkeld665d86c2015-01-19 22:15:48 +1000151 env = {'resource_registry':
152 {'My::RandomString': 'OS::Heat::RandomString'}}
153 create_template = self.template.replace("count: 0", "count: 2")
154 stack_identifier = self.stack_create(template=create_template,
155 environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530156 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
157 self.list_resources(stack_identifier))
158 # verify that the resource group has 2 resources
159 self._validate_resources(stack_identifier, 2)
160
161 # increase the resource group count to 5
Angus Salkeld665d86c2015-01-19 22:15:48 +1000162 update_template = self.template.replace("count: 0", "count: 5")
163 self.update_stack(stack_identifier, update_template, environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530164 # verify that the resource group has 5 resources
165 self._validate_resources(stack_identifier, 5)
166
167 # decrease the resource group count to 3
Angus Salkeld665d86c2015-01-19 22:15:48 +1000168 update_template = self.template.replace("count: 0", "count: 3")
169 self.update_stack(stack_identifier, update_template, environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530170 # verify that the resource group has 3 resources
171 self._validate_resources(stack_identifier, 3)
Angus Salkeld011acc72015-01-16 20:26:34 +1000172
Steven Hardy0145b8d2015-04-23 14:14:26 +1000173 def test_update_removal_policies(self):
174 rp_template = '''
175heat_template_version: 2014-10-16
176resources:
177 random_group:
178 type: OS::Heat::ResourceGroup
179 properties:
180 count: 5
181 removal_policies: []
182 resource_def:
183 type: OS::Heat::RandomString
184'''
185
186 # create stack with resource group, initial count 5
187 stack_identifier = self.stack_create(template=rp_template)
188 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
189 self.list_resources(stack_identifier))
190 group_resources = self._list_group_resources(stack_identifier)
191 expected_resources = {u'0': u'OS::Heat::RandomString',
192 u'1': u'OS::Heat::RandomString',
193 u'2': u'OS::Heat::RandomString',
194 u'3': u'OS::Heat::RandomString',
195 u'4': u'OS::Heat::RandomString'}
196 self.assertEqual(expected_resources, group_resources)
197
198 # Remove three, specifying the middle resources to be removed
199 update_template = rp_template.replace(
200 'removal_policies: []',
201 'removal_policies: [{resource_list: [\'1\', \'2\', \'3\']}]')
202 self.update_stack(stack_identifier, update_template)
203 group_resources = self._list_group_resources(stack_identifier)
204 expected_resources = {u'0': u'OS::Heat::RandomString',
205 u'4': u'OS::Heat::RandomString',
206 u'5': u'OS::Heat::RandomString',
207 u'6': u'OS::Heat::RandomString',
208 u'7': u'OS::Heat::RandomString'}
209 self.assertEqual(expected_resources, group_resources)
210
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000211 def test_props_update(self):
212 """Test update of resource_def properties behaves as expected."""
213
214 env = {'resource_registry':
215 {'My::RandomString': 'OS::Heat::RandomString'}}
216 template_one = self.template.replace("count: 0", "count: 1")
217 stack_identifier = self.stack_create(template=template_one,
218 environment=env)
219 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
220 self.list_resources(stack_identifier))
221
222 initial_nested_ident = self._group_nested_identifier(stack_identifier)
223 self.assertEqual({'0': 'My::RandomString'},
224 self.list_resources(initial_nested_ident))
225 # get the resource id
226 res = self.client.resources.get(initial_nested_ident, '0')
227 initial_res_id = res.physical_resource_id
228
229 # change the salt (this should replace the RandomString but
230 # not the nested stack or resource group.
231 template_salt = template_one.replace("salt: initial", "salt: more")
232 self.update_stack(stack_identifier, template_salt, environment=env)
233 updated_nested_ident = self._group_nested_identifier(stack_identifier)
234 self.assertEqual(initial_nested_ident, updated_nested_ident)
235
236 # compare the resource id, we expect a change.
237 res = self.client.resources.get(updated_nested_ident, '0')
238 updated_res_id = res.physical_resource_id
239 self.assertNotEqual(initial_res_id, updated_res_id)
240
241 def test_update_nochange(self):
242 """Test update with no properties change."""
243
244 env = {'resource_registry':
245 {'My::RandomString': 'OS::Heat::RandomString'}}
Angus Salkelda89a0282015-07-24 15:47:38 +1000246 template_one = self.template.replace("count: 0", "count: 2")
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000247 stack_identifier = self.stack_create(template=template_one,
248 environment=env)
249 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
250 self.list_resources(stack_identifier))
251
252 initial_nested_ident = self._group_nested_identifier(stack_identifier)
Angus Salkelda89a0282015-07-24 15:47:38 +1000253 self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'},
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000254 self.list_resources(initial_nested_ident))
255 # get the output
256 stack0 = self.client.stacks.get(stack_identifier)
257 initial_rand = self._stack_output(stack0, 'random1')
258
259 template_copy = copy.deepcopy(template_one)
260 self.update_stack(stack_identifier, template_copy, environment=env)
261 updated_nested_ident = self._group_nested_identifier(stack_identifier)
262 self.assertEqual(initial_nested_ident, updated_nested_ident)
263
264 # compare the random number, we expect no change.
265 stack1 = self.client.stacks.get(stack_identifier)
266 updated_rand = self._stack_output(stack1, 'random1')
267 self.assertEqual(initial_rand, updated_rand)
268
269 def test_update_nochange_resource_needs_update(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300270 """Test update when the resource definition has changed.
271
272 Test the scenario when the ResourceGroup update happens without
273 any changed properties, this can happen if the definition of
274 a contained provider resource changes (files map changes), then
275 the group and underlying nested stack should end up updated.
276 """
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000277
278 random_templ1 = '''
279heat_template_version: 2013-05-23
280parameters:
281 length:
282 type: string
283 default: not-used
284 salt:
285 type: string
286 default: not-used
287resources:
288 random1:
289 type: OS::Heat::RandomString
290 properties:
291 salt: initial
292outputs:
293 value:
294 value: {get_attr: [random1, value]}
295'''
296 files1 = {'my_random.yaml': random_templ1}
297
298 random_templ2 = random_templ1.replace('salt: initial',
299 'salt: more')
300 files2 = {'my_random.yaml': random_templ2}
301
302 env = {'resource_registry':
303 {'My::RandomString': 'my_random.yaml'}}
304
Angus Salkelda89a0282015-07-24 15:47:38 +1000305 template_one = self.template.replace("count: 0", "count: 2")
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000306 stack_identifier = self.stack_create(template=template_one,
307 environment=env,
308 files=files1)
309 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
310 self.list_resources(stack_identifier))
311
312 initial_nested_ident = self._group_nested_identifier(stack_identifier)
Angus Salkelda89a0282015-07-24 15:47:38 +1000313 self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'},
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000314 self.list_resources(initial_nested_ident))
315 # get the output
316 stack0 = self.client.stacks.get(stack_identifier)
317 initial_rand = self._stack_output(stack0, 'random1')
318
319 # change the environment so we use a different TemplateResource.
320 # note "files2".
321 self.update_stack(stack_identifier, template_one,
322 environment=env, files=files2)
323 updated_nested_ident = self._group_nested_identifier(stack_identifier)
324 self.assertEqual(initial_nested_ident, updated_nested_ident)
325
326 # compare the output, we expect a change.
327 stack1 = self.client.stacks.get(stack_identifier)
328 updated_rand = self._stack_output(stack1, 'random1')
329 self.assertNotEqual(initial_rand, updated_rand)
330
Angus Salkeld011acc72015-01-16 20:26:34 +1000331
Rabi Mishra477efc92015-07-31 13:01:45 +0530332class ResourceGroupTestNullParams(functional_base.FunctionalTestsBase):
Angus Salkeld59b8f412015-02-25 21:01:12 +1000333 template = '''
334heat_template_version: 2013-05-23
335parameters:
336 param:
337 type: empty
338resources:
339 random_group:
340 type: OS::Heat::ResourceGroup
341 properties:
342 count: 1
343 resource_def:
344 type: My::RandomString
345 properties:
346 param: {get_param: param}
347outputs:
348 val:
349 value: {get_attr: [random_group, val]}
350'''
351
352 nested_template_file = '''
353heat_template_version: 2013-05-23
354parameters:
355 param:
356 type: empty
357outputs:
358 val:
359 value: {get_param: param}
360'''
361
362 scenarios = [
363 ('string_empty', dict(
364 param='',
365 p_type='string',
366 )),
367 ('boolean_false', dict(
368 param=False,
369 p_type='boolean',
370 )),
371 ('number_zero', dict(
372 param=0,
373 p_type='number',
374 )),
375 ('comma_delimited_list', dict(
376 param=[],
377 p_type='comma_delimited_list',
378 )),
379 ('json_empty', dict(
380 param={},
381 p_type='json',
382 )),
383 ]
384
385 def setUp(self):
386 super(ResourceGroupTestNullParams, self).setUp()
Angus Salkeld59b8f412015-02-25 21:01:12 +1000387
388 def test_create_pass_zero_parameter(self):
389 templ = self.template.replace('type: empty',
390 'type: %s' % self.p_type)
391 n_t_f = self.nested_template_file.replace('type: empty',
392 'type: %s' % self.p_type)
393 files = {'provider.yaml': n_t_f}
394 env = {'resource_registry':
395 {'My::RandomString': 'provider.yaml'}}
396 stack_identifier = self.stack_create(
397 template=templ,
398 files=files,
399 environment=env,
400 parameters={'param': self.param}
401 )
402 stack = self.client.stacks.get(stack_identifier)
403 self.assertEqual(self.param, self._stack_output(stack, 'val')[0])
404
405
Rabi Mishra477efc92015-07-31 13:01:45 +0530406class ResourceGroupAdoptTest(functional_base.FunctionalTestsBase):
Angus Salkeld011acc72015-01-16 20:26:34 +1000407 """Prove that we can do resource group adopt."""
408
409 main_template = '''
410heat_template_version: "2013-05-23"
411resources:
412 group1:
413 type: OS::Heat::ResourceGroup
414 properties:
415 count: 2
416 resource_def:
417 type: OS::Heat::RandomString
418outputs:
419 test0:
420 value: {get_attr: [group1, resource.0.value]}
421 test1:
422 value: {get_attr: [group1, resource.1.value]}
423'''
424
425 def setUp(self):
426 super(ResourceGroupAdoptTest, self).setUp()
Angus Salkeld011acc72015-01-16 20:26:34 +1000427
428 def _yaml_to_json(self, yaml_templ):
429 return yaml.load(yaml_templ)
430
431 def test_adopt(self):
432 data = {
433 "resources": {
434 "group1": {
435 "status": "COMPLETE",
436 "name": "group1",
437 "resource_data": {},
438 "metadata": {},
439 "resource_id": "test-group1-id",
440 "action": "CREATE",
441 "type": "OS::Heat::ResourceGroup",
442 "resources": {
443 "0": {
444 "status": "COMPLETE",
445 "name": "0",
446 "resource_data": {"value": "goopie"},
447 "resource_id": "ID-0",
448 "action": "CREATE",
449 "type": "OS::Heat::RandomString",
450 "metadata": {}
451 },
452 "1": {
453 "status": "COMPLETE",
454 "name": "1",
455 "resource_data": {"value": "different"},
456 "resource_id": "ID-1",
457 "action": "CREATE",
458 "type": "OS::Heat::RandomString",
459 "metadata": {}
460 }
461 }
462 }
463 },
464 "environment": {"parameters": {}},
465 "template": yaml.load(self.main_template)
466 }
467 stack_identifier = self.stack_adopt(
468 adopt_data=json.dumps(data))
469
470 self.assert_resource_is_a_stack(stack_identifier, 'group1')
471 stack = self.client.stacks.get(stack_identifier)
472 self.assertEqual('goopie', self._stack_output(stack, 'test0'))
473 self.assertEqual('different', self._stack_output(stack, 'test1'))
Steve Baker75ee9d12015-07-22 10:56:55 +1200474
475
Rabi Mishra477efc92015-07-31 13:01:45 +0530476class ResourceGroupErrorResourceTest(functional_base.FunctionalTestsBase):
Steve Baker75ee9d12015-07-22 10:56:55 +1200477 template = '''
478heat_template_version: "2013-05-23"
479resources:
480 group1:
481 type: OS::Heat::ResourceGroup
482 properties:
483 count: 2
484 resource_def:
485 type: fail.yaml
486'''
487 nested_templ = '''
488heat_template_version: "2013-05-23"
489resources:
490 oops:
491 type: OS::Heat::TestResource
492 properties:
493 fail: true
494 wait_secs: 2
495'''
496
497 def setUp(self):
498 super(ResourceGroupErrorResourceTest, self).setUp()
Steve Baker75ee9d12015-07-22 10:56:55 +1200499
500 def test_fail(self):
501 stack_identifier = self.stack_create(
502 template=self.template,
503 files={'fail.yaml': self.nested_templ},
504 expected_status='CREATE_FAILED',
505 enable_cleanup=False)
506 stack = self.client.stacks.get(stack_identifier)
507
508 self.assertEqual('CREATE_FAILED', stack.stack_status)
509 self.client.stacks.delete(stack_identifier)
510 self._wait_for_stack_status(
511 stack_identifier, 'DELETE_COMPLETE',
512 success_on_not_found=True)