blob: a41f84106997e5e5541d79837f95b1ce4b009274 [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
20from heat_integrationtests.common import test
21
22
Angus Salkeld665d86c2015-01-19 22:15:48 +100023class ResourceGroupTest(test.HeatIntegrationTest):
24 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()
47 self.client = self.orchestration_client
48
49 def _group_nested_identifier(self, stack_identifier,
50 group_name='random_group'):
51 # Get the nested stack identifier from the group
52 rsrc = self.client.resources.get(stack_identifier, group_name)
53 physical_resource_id = rsrc.physical_resource_id
54
55 nested_stack = self.client.stacks.get(physical_resource_id)
56 nested_identifier = '%s/%s' % (nested_stack.stack_name,
57 nested_stack.id)
58 parent_id = stack_identifier.split("/")[-1]
59 self.assertEqual(parent_id, nested_stack.parent)
60 return nested_identifier
61
62 def test_resource_group_zero_novalidate(self):
63 # Nested resources should be validated only when size > 0
64 # This allows features to be disabled via size=0 without
65 # triggering validation of nested resource custom contraints
66 # e.g images etc in the nested schema.
67 nested_template_fail = '''
68heat_template_version: 2013-05-23
Angus Salkeld665d86c2015-01-19 22:15:48 +100069parameters:
70 length:
71 type: string
72 default: 50
Angus Salkeldb61f8f12015-01-19 23:00:45 +100073 salt:
74 type: string
75 default: initial
Steven Hardy6f0bda82014-12-12 17:49:10 +000076resources:
77 random:
78 type: OS::Heat::RandomString
79 properties:
Angus Salkeld665d86c2015-01-19 22:15:48 +100080 length: BAD
Steven Hardy6f0bda82014-12-12 17:49:10 +000081'''
82
83 files = {'provider.yaml': nested_template_fail}
84 env = {'resource_registry':
85 {'My::RandomString': 'provider.yaml'}}
86 stack_identifier = self.stack_create(
Angus Salkeld665d86c2015-01-19 22:15:48 +100087 template=self.template,
Steven Hardy6f0bda82014-12-12 17:49:10 +000088 files=files,
89 environment=env
90 )
91
92 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
93 self.list_resources(stack_identifier))
94
95 # Check we created an empty nested stack
96 nested_identifier = self._group_nested_identifier(stack_identifier)
97 self.assertEqual({}, self.list_resources(nested_identifier))
98
99 # Prove validation works for non-zero create/update
Angus Salkeld665d86c2015-01-19 22:15:48 +1000100 template_two_nested = self.template.replace("count: 0", "count: 2")
Peter Razumovskyd7500792014-10-08 19:19:13 +0400101 expected_err = "Value 'BAD' is not an integer"
Steven Hardy6f0bda82014-12-12 17:49:10 +0000102 ex = self.assertRaises(exc.HTTPBadRequest, self.update_stack,
103 stack_identifier, template_two_nested,
104 environment=env, files=files)
105 self.assertIn(expected_err, six.text_type(ex))
106
107 ex = self.assertRaises(exc.HTTPBadRequest, self.stack_create,
108 template=template_two_nested,
109 environment=env, files=files)
110 self.assertIn(expected_err, six.text_type(ex))
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530111
Steven Hardy0145b8d2015-04-23 14:14:26 +1000112 def _list_group_resources(self, stack_identifier):
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530113 nested_identifier = self._group_nested_identifier(stack_identifier)
Steven Hardy0145b8d2015-04-23 14:14:26 +1000114 return self.list_resources(nested_identifier)
115
116 def _validate_resources(self, stack_identifier, expected_count):
117 resources = self._list_group_resources(stack_identifier)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530118 self.assertEqual(expected_count, len(resources))
119 expected_resources = dict(
Angus Salkeld665d86c2015-01-19 22:15:48 +1000120 (str(idx), 'My::RandomString')
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530121 for idx in range(expected_count))
122
123 self.assertEqual(expected_resources, resources)
124
125 def test_create(self):
126 def validate_output(stack, output_key, length):
127 output_value = self._stack_output(stack, output_key)
128 self.assertEqual(length, len(output_value))
129 return output_value
130 # verify that the resources in resource group are identically
131 # configured, resource names and outputs are appropriate.
Angus Salkeld665d86c2015-01-19 22:15:48 +1000132 env = {'resource_registry':
133 {'My::RandomString': 'OS::Heat::RandomString'}}
134 create_template = self.template.replace("count: 0", "count: 2")
135 stack_identifier = self.stack_create(template=create_template,
136 environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530137 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
138 self.list_resources(stack_identifier))
139
140 # validate count, type and name of resources in a resource group.
141 self._validate_resources(stack_identifier, 2)
142
143 # validate outputs
144 stack = self.client.stacks.get(stack_identifier)
145 outputs = []
146 outputs.append(validate_output(stack, 'random1', 30))
147 outputs.append(validate_output(stack, 'random2', 30))
148 self.assertEqual(outputs, self._stack_output(stack, 'all_values'))
149
150 def test_update_increase_decrease_count(self):
151 # create stack with resource group count 2
Angus Salkeld665d86c2015-01-19 22:15:48 +1000152 env = {'resource_registry':
153 {'My::RandomString': 'OS::Heat::RandomString'}}
154 create_template = self.template.replace("count: 0", "count: 2")
155 stack_identifier = self.stack_create(template=create_template,
156 environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530157 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
158 self.list_resources(stack_identifier))
159 # verify that the resource group has 2 resources
160 self._validate_resources(stack_identifier, 2)
161
162 # increase the resource group count to 5
Angus Salkeld665d86c2015-01-19 22:15:48 +1000163 update_template = self.template.replace("count: 0", "count: 5")
164 self.update_stack(stack_identifier, update_template, environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530165 # verify that the resource group has 5 resources
166 self._validate_resources(stack_identifier, 5)
167
168 # decrease the resource group count to 3
Angus Salkeld665d86c2015-01-19 22:15:48 +1000169 update_template = self.template.replace("count: 0", "count: 3")
170 self.update_stack(stack_identifier, update_template, environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530171 # verify that the resource group has 3 resources
172 self._validate_resources(stack_identifier, 3)
Angus Salkeld011acc72015-01-16 20:26:34 +1000173
Steven Hardy0145b8d2015-04-23 14:14:26 +1000174 def test_update_removal_policies(self):
175 rp_template = '''
176heat_template_version: 2014-10-16
177resources:
178 random_group:
179 type: OS::Heat::ResourceGroup
180 properties:
181 count: 5
182 removal_policies: []
183 resource_def:
184 type: OS::Heat::RandomString
185'''
186
187 # create stack with resource group, initial count 5
188 stack_identifier = self.stack_create(template=rp_template)
189 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
190 self.list_resources(stack_identifier))
191 group_resources = self._list_group_resources(stack_identifier)
192 expected_resources = {u'0': u'OS::Heat::RandomString',
193 u'1': u'OS::Heat::RandomString',
194 u'2': u'OS::Heat::RandomString',
195 u'3': u'OS::Heat::RandomString',
196 u'4': u'OS::Heat::RandomString'}
197 self.assertEqual(expected_resources, group_resources)
198
199 # Remove three, specifying the middle resources to be removed
200 update_template = rp_template.replace(
201 'removal_policies: []',
202 'removal_policies: [{resource_list: [\'1\', \'2\', \'3\']}]')
203 self.update_stack(stack_identifier, update_template)
204 group_resources = self._list_group_resources(stack_identifier)
205 expected_resources = {u'0': u'OS::Heat::RandomString',
206 u'4': u'OS::Heat::RandomString',
207 u'5': u'OS::Heat::RandomString',
208 u'6': u'OS::Heat::RandomString',
209 u'7': u'OS::Heat::RandomString'}
210 self.assertEqual(expected_resources, group_resources)
211
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000212 def test_props_update(self):
213 """Test update of resource_def properties behaves as expected."""
214
215 env = {'resource_registry':
216 {'My::RandomString': 'OS::Heat::RandomString'}}
217 template_one = self.template.replace("count: 0", "count: 1")
218 stack_identifier = self.stack_create(template=template_one,
219 environment=env)
220 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
221 self.list_resources(stack_identifier))
222
223 initial_nested_ident = self._group_nested_identifier(stack_identifier)
224 self.assertEqual({'0': 'My::RandomString'},
225 self.list_resources(initial_nested_ident))
226 # get the resource id
227 res = self.client.resources.get(initial_nested_ident, '0')
228 initial_res_id = res.physical_resource_id
229
230 # change the salt (this should replace the RandomString but
231 # not the nested stack or resource group.
232 template_salt = template_one.replace("salt: initial", "salt: more")
233 self.update_stack(stack_identifier, template_salt, environment=env)
234 updated_nested_ident = self._group_nested_identifier(stack_identifier)
235 self.assertEqual(initial_nested_ident, updated_nested_ident)
236
237 # compare the resource id, we expect a change.
238 res = self.client.resources.get(updated_nested_ident, '0')
239 updated_res_id = res.physical_resource_id
240 self.assertNotEqual(initial_res_id, updated_res_id)
241
242 def test_update_nochange(self):
243 """Test update with no properties change."""
244
245 env = {'resource_registry':
246 {'My::RandomString': 'OS::Heat::RandomString'}}
247 template_one = self.template.replace("count: 0", "count: 1")
248 stack_identifier = self.stack_create(template=template_one,
249 environment=env)
250 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
251 self.list_resources(stack_identifier))
252
253 initial_nested_ident = self._group_nested_identifier(stack_identifier)
254 self.assertEqual({'0': 'My::RandomString'},
255 self.list_resources(initial_nested_ident))
256 # get the output
257 stack0 = self.client.stacks.get(stack_identifier)
258 initial_rand = self._stack_output(stack0, 'random1')
259
260 template_copy = copy.deepcopy(template_one)
261 self.update_stack(stack_identifier, template_copy, environment=env)
262 updated_nested_ident = self._group_nested_identifier(stack_identifier)
263 self.assertEqual(initial_nested_ident, updated_nested_ident)
264
265 # compare the random number, we expect no change.
266 stack1 = self.client.stacks.get(stack_identifier)
267 updated_rand = self._stack_output(stack1, 'random1')
268 self.assertEqual(initial_rand, updated_rand)
269
270 def test_update_nochange_resource_needs_update(self):
271 """Test update when the resource definition has changed."""
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
277 random_templ1 = '''
278heat_template_version: 2013-05-23
279parameters:
280 length:
281 type: string
282 default: not-used
283 salt:
284 type: string
285 default: not-used
286resources:
287 random1:
288 type: OS::Heat::RandomString
289 properties:
290 salt: initial
291outputs:
292 value:
293 value: {get_attr: [random1, value]}
294'''
295 files1 = {'my_random.yaml': random_templ1}
296
297 random_templ2 = random_templ1.replace('salt: initial',
298 'salt: more')
299 files2 = {'my_random.yaml': random_templ2}
300
301 env = {'resource_registry':
302 {'My::RandomString': 'my_random.yaml'}}
303
304 template_one = self.template.replace("count: 0", "count: 1")
305 stack_identifier = self.stack_create(template=template_one,
306 environment=env,
307 files=files1)
308 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
309 self.list_resources(stack_identifier))
310
311 initial_nested_ident = self._group_nested_identifier(stack_identifier)
312 self.assertEqual({'0': 'My::RandomString'},
313 self.list_resources(initial_nested_ident))
314 # get the output
315 stack0 = self.client.stacks.get(stack_identifier)
316 initial_rand = self._stack_output(stack0, 'random1')
317
318 # change the environment so we use a different TemplateResource.
319 # note "files2".
320 self.update_stack(stack_identifier, template_one,
321 environment=env, files=files2)
322 updated_nested_ident = self._group_nested_identifier(stack_identifier)
323 self.assertEqual(initial_nested_ident, updated_nested_ident)
324
325 # compare the output, we expect a change.
326 stack1 = self.client.stacks.get(stack_identifier)
327 updated_rand = self._stack_output(stack1, 'random1')
328 self.assertNotEqual(initial_rand, updated_rand)
329
Angus Salkeld011acc72015-01-16 20:26:34 +1000330
Angus Salkeld59b8f412015-02-25 21:01:12 +1000331class ResourceGroupTestNullParams(test.HeatIntegrationTest):
332 template = '''
333heat_template_version: 2013-05-23
334parameters:
335 param:
336 type: empty
337resources:
338 random_group:
339 type: OS::Heat::ResourceGroup
340 properties:
341 count: 1
342 resource_def:
343 type: My::RandomString
344 properties:
345 param: {get_param: param}
346outputs:
347 val:
348 value: {get_attr: [random_group, val]}
349'''
350
351 nested_template_file = '''
352heat_template_version: 2013-05-23
353parameters:
354 param:
355 type: empty
356outputs:
357 val:
358 value: {get_param: param}
359'''
360
361 scenarios = [
362 ('string_empty', dict(
363 param='',
364 p_type='string',
365 )),
366 ('boolean_false', dict(
367 param=False,
368 p_type='boolean',
369 )),
370 ('number_zero', dict(
371 param=0,
372 p_type='number',
373 )),
374 ('comma_delimited_list', dict(
375 param=[],
376 p_type='comma_delimited_list',
377 )),
378 ('json_empty', dict(
379 param={},
380 p_type='json',
381 )),
382 ]
383
384 def setUp(self):
385 super(ResourceGroupTestNullParams, self).setUp()
386 self.client = self.orchestration_client
387
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
Angus Salkeld011acc72015-01-16 20:26:34 +1000406class ResourceGroupAdoptTest(test.HeatIntegrationTest):
407 """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()
427 self.client = self.orchestration_client
428
429 def _yaml_to_json(self, yaml_templ):
430 return yaml.load(yaml_templ)
431
432 def test_adopt(self):
433 data = {
434 "resources": {
435 "group1": {
436 "status": "COMPLETE",
437 "name": "group1",
438 "resource_data": {},
439 "metadata": {},
440 "resource_id": "test-group1-id",
441 "action": "CREATE",
442 "type": "OS::Heat::ResourceGroup",
443 "resources": {
444 "0": {
445 "status": "COMPLETE",
446 "name": "0",
447 "resource_data": {"value": "goopie"},
448 "resource_id": "ID-0",
449 "action": "CREATE",
450 "type": "OS::Heat::RandomString",
451 "metadata": {}
452 },
453 "1": {
454 "status": "COMPLETE",
455 "name": "1",
456 "resource_data": {"value": "different"},
457 "resource_id": "ID-1",
458 "action": "CREATE",
459 "type": "OS::Heat::RandomString",
460 "metadata": {}
461 }
462 }
463 }
464 },
465 "environment": {"parameters": {}},
466 "template": yaml.load(self.main_template)
467 }
468 stack_identifier = self.stack_adopt(
469 adopt_data=json.dumps(data))
470
471 self.assert_resource_is_a_stack(stack_identifier, 'group1')
472 stack = self.client.stacks.get(stack_identifier)
473 self.assertEqual('goopie', self._stack_output(stack, 'test0'))
474 self.assertEqual('different', self._stack_output(stack, 'test1'))