blob: 53352451b740ecc41ccaff0173dad4d5e19694cf [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):
270 """Test update when the resource definition has changed."""
271 # Test the scenario when the ResourceGroup update happens without
272 # any changed properties, this can happen if the definition of
273 # a contained provider resource changes (files map changes), then
274 # the group and underlying nested stack should end up updated.
275
276 random_templ1 = '''
277heat_template_version: 2013-05-23
278parameters:
279 length:
280 type: string
281 default: not-used
282 salt:
283 type: string
284 default: not-used
285resources:
286 random1:
287 type: OS::Heat::RandomString
288 properties:
289 salt: initial
290outputs:
291 value:
292 value: {get_attr: [random1, value]}
293'''
294 files1 = {'my_random.yaml': random_templ1}
295
296 random_templ2 = random_templ1.replace('salt: initial',
297 'salt: more')
298 files2 = {'my_random.yaml': random_templ2}
299
300 env = {'resource_registry':
301 {'My::RandomString': 'my_random.yaml'}}
302
Angus Salkelda89a0282015-07-24 15:47:38 +1000303 template_one = self.template.replace("count: 0", "count: 2")
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000304 stack_identifier = self.stack_create(template=template_one,
305 environment=env,
306 files=files1)
307 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
308 self.list_resources(stack_identifier))
309
310 initial_nested_ident = self._group_nested_identifier(stack_identifier)
Angus Salkelda89a0282015-07-24 15:47:38 +1000311 self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'},
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000312 self.list_resources(initial_nested_ident))
313 # get the output
314 stack0 = self.client.stacks.get(stack_identifier)
315 initial_rand = self._stack_output(stack0, 'random1')
316
317 # change the environment so we use a different TemplateResource.
318 # note "files2".
319 self.update_stack(stack_identifier, template_one,
320 environment=env, files=files2)
321 updated_nested_ident = self._group_nested_identifier(stack_identifier)
322 self.assertEqual(initial_nested_ident, updated_nested_ident)
323
324 # compare the output, we expect a change.
325 stack1 = self.client.stacks.get(stack_identifier)
326 updated_rand = self._stack_output(stack1, 'random1')
327 self.assertNotEqual(initial_rand, updated_rand)
328
Angus Salkeld011acc72015-01-16 20:26:34 +1000329
Rabi Mishra477efc92015-07-31 13:01:45 +0530330class ResourceGroupTestNullParams(functional_base.FunctionalTestsBase):
Angus Salkeld59b8f412015-02-25 21:01:12 +1000331 template = '''
332heat_template_version: 2013-05-23
333parameters:
334 param:
335 type: empty
336resources:
337 random_group:
338 type: OS::Heat::ResourceGroup
339 properties:
340 count: 1
341 resource_def:
342 type: My::RandomString
343 properties:
344 param: {get_param: param}
345outputs:
346 val:
347 value: {get_attr: [random_group, val]}
348'''
349
350 nested_template_file = '''
351heat_template_version: 2013-05-23
352parameters:
353 param:
354 type: empty
355outputs:
356 val:
357 value: {get_param: param}
358'''
359
360 scenarios = [
361 ('string_empty', dict(
362 param='',
363 p_type='string',
364 )),
365 ('boolean_false', dict(
366 param=False,
367 p_type='boolean',
368 )),
369 ('number_zero', dict(
370 param=0,
371 p_type='number',
372 )),
373 ('comma_delimited_list', dict(
374 param=[],
375 p_type='comma_delimited_list',
376 )),
377 ('json_empty', dict(
378 param={},
379 p_type='json',
380 )),
381 ]
382
383 def setUp(self):
384 super(ResourceGroupTestNullParams, self).setUp()
Angus Salkeld59b8f412015-02-25 21:01:12 +1000385
386 def test_create_pass_zero_parameter(self):
387 templ = self.template.replace('type: empty',
388 'type: %s' % self.p_type)
389 n_t_f = self.nested_template_file.replace('type: empty',
390 'type: %s' % self.p_type)
391 files = {'provider.yaml': n_t_f}
392 env = {'resource_registry':
393 {'My::RandomString': 'provider.yaml'}}
394 stack_identifier = self.stack_create(
395 template=templ,
396 files=files,
397 environment=env,
398 parameters={'param': self.param}
399 )
400 stack = self.client.stacks.get(stack_identifier)
401 self.assertEqual(self.param, self._stack_output(stack, 'val')[0])
402
403
Rabi Mishra477efc92015-07-31 13:01:45 +0530404class ResourceGroupAdoptTest(functional_base.FunctionalTestsBase):
Angus Salkeld011acc72015-01-16 20:26:34 +1000405 """Prove that we can do resource group adopt."""
406
407 main_template = '''
408heat_template_version: "2013-05-23"
409resources:
410 group1:
411 type: OS::Heat::ResourceGroup
412 properties:
413 count: 2
414 resource_def:
415 type: OS::Heat::RandomString
416outputs:
417 test0:
418 value: {get_attr: [group1, resource.0.value]}
419 test1:
420 value: {get_attr: [group1, resource.1.value]}
421'''
422
423 def setUp(self):
424 super(ResourceGroupAdoptTest, self).setUp()
Angus Salkeld011acc72015-01-16 20:26:34 +1000425
426 def _yaml_to_json(self, yaml_templ):
427 return yaml.load(yaml_templ)
428
429 def test_adopt(self):
430 data = {
431 "resources": {
432 "group1": {
433 "status": "COMPLETE",
434 "name": "group1",
435 "resource_data": {},
436 "metadata": {},
437 "resource_id": "test-group1-id",
438 "action": "CREATE",
439 "type": "OS::Heat::ResourceGroup",
440 "resources": {
441 "0": {
442 "status": "COMPLETE",
443 "name": "0",
444 "resource_data": {"value": "goopie"},
445 "resource_id": "ID-0",
446 "action": "CREATE",
447 "type": "OS::Heat::RandomString",
448 "metadata": {}
449 },
450 "1": {
451 "status": "COMPLETE",
452 "name": "1",
453 "resource_data": {"value": "different"},
454 "resource_id": "ID-1",
455 "action": "CREATE",
456 "type": "OS::Heat::RandomString",
457 "metadata": {}
458 }
459 }
460 }
461 },
462 "environment": {"parameters": {}},
463 "template": yaml.load(self.main_template)
464 }
465 stack_identifier = self.stack_adopt(
466 adopt_data=json.dumps(data))
467
468 self.assert_resource_is_a_stack(stack_identifier, 'group1')
469 stack = self.client.stacks.get(stack_identifier)
470 self.assertEqual('goopie', self._stack_output(stack, 'test0'))
471 self.assertEqual('different', self._stack_output(stack, 'test1'))
Steve Baker75ee9d12015-07-22 10:56:55 +1200472
473
Rabi Mishra477efc92015-07-31 13:01:45 +0530474class ResourceGroupErrorResourceTest(functional_base.FunctionalTestsBase):
Steve Baker75ee9d12015-07-22 10:56:55 +1200475 template = '''
476heat_template_version: "2013-05-23"
477resources:
478 group1:
479 type: OS::Heat::ResourceGroup
480 properties:
481 count: 2
482 resource_def:
483 type: fail.yaml
484'''
485 nested_templ = '''
486heat_template_version: "2013-05-23"
487resources:
488 oops:
489 type: OS::Heat::TestResource
490 properties:
491 fail: true
492 wait_secs: 2
493'''
494
495 def setUp(self):
496 super(ResourceGroupErrorResourceTest, self).setUp()
Steve Baker75ee9d12015-07-22 10:56:55 +1200497
498 def test_fail(self):
499 stack_identifier = self.stack_create(
500 template=self.template,
501 files={'fail.yaml': self.nested_templ},
502 expected_status='CREATE_FAILED',
503 enable_cleanup=False)
504 stack = self.client.stacks.get(stack_identifier)
505
506 self.assertEqual('CREATE_FAILED', stack.stack_status)
507 self.client.stacks.delete(stack_identifier)
508 self._wait_for_stack_status(
509 stack_identifier, 'DELETE_COMPLETE',
510 success_on_not_found=True)