blob: 7da20a1d2970a6e243389558585e7d845f6f531e [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")
Steven Hardy6f0bda82014-12-12 17:49:10 +0000101 expected_err = "length Value 'BAD' is not an integer"
102 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
112 def _validate_resources(self, stack_identifier, expected_count):
113 nested_identifier = self._group_nested_identifier(stack_identifier)
114 resources = self.list_resources(nested_identifier)
115 self.assertEqual(expected_count, len(resources))
116 expected_resources = dict(
Angus Salkeld665d86c2015-01-19 22:15:48 +1000117 (str(idx), 'My::RandomString')
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530118 for idx in range(expected_count))
119
120 self.assertEqual(expected_resources, resources)
121
122 def test_create(self):
123 def validate_output(stack, output_key, length):
124 output_value = self._stack_output(stack, output_key)
125 self.assertEqual(length, len(output_value))
126 return output_value
127 # verify that the resources in resource group are identically
128 # configured, resource names and outputs are appropriate.
Angus Salkeld665d86c2015-01-19 22:15:48 +1000129 env = {'resource_registry':
130 {'My::RandomString': 'OS::Heat::RandomString'}}
131 create_template = self.template.replace("count: 0", "count: 2")
132 stack_identifier = self.stack_create(template=create_template,
133 environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530134 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
135 self.list_resources(stack_identifier))
136
137 # validate count, type and name of resources in a resource group.
138 self._validate_resources(stack_identifier, 2)
139
140 # validate outputs
141 stack = self.client.stacks.get(stack_identifier)
142 outputs = []
143 outputs.append(validate_output(stack, 'random1', 30))
144 outputs.append(validate_output(stack, 'random2', 30))
145 self.assertEqual(outputs, self._stack_output(stack, 'all_values'))
146
147 def test_update_increase_decrease_count(self):
148 # create stack with resource group count 2
Angus Salkeld665d86c2015-01-19 22:15:48 +1000149 env = {'resource_registry':
150 {'My::RandomString': 'OS::Heat::RandomString'}}
151 create_template = self.template.replace("count: 0", "count: 2")
152 stack_identifier = self.stack_create(template=create_template,
153 environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530154 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
155 self.list_resources(stack_identifier))
156 # verify that the resource group has 2 resources
157 self._validate_resources(stack_identifier, 2)
158
159 # increase the resource group count to 5
Angus Salkeld665d86c2015-01-19 22:15:48 +1000160 update_template = self.template.replace("count: 0", "count: 5")
161 self.update_stack(stack_identifier, update_template, environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530162 # verify that the resource group has 5 resources
163 self._validate_resources(stack_identifier, 5)
164
165 # decrease the resource group count to 3
Angus Salkeld665d86c2015-01-19 22:15:48 +1000166 update_template = self.template.replace("count: 0", "count: 3")
167 self.update_stack(stack_identifier, update_template, environment=env)
Unmesh Gurjar0a25a732014-12-23 17:28:33 +0530168 # verify that the resource group has 3 resources
169 self._validate_resources(stack_identifier, 3)
Angus Salkeld011acc72015-01-16 20:26:34 +1000170
Angus Salkeldb61f8f12015-01-19 23:00:45 +1000171 def test_props_update(self):
172 """Test update of resource_def properties behaves as expected."""
173
174 env = {'resource_registry':
175 {'My::RandomString': 'OS::Heat::RandomString'}}
176 template_one = self.template.replace("count: 0", "count: 1")
177 stack_identifier = self.stack_create(template=template_one,
178 environment=env)
179 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
180 self.list_resources(stack_identifier))
181
182 initial_nested_ident = self._group_nested_identifier(stack_identifier)
183 self.assertEqual({'0': 'My::RandomString'},
184 self.list_resources(initial_nested_ident))
185 # get the resource id
186 res = self.client.resources.get(initial_nested_ident, '0')
187 initial_res_id = res.physical_resource_id
188
189 # change the salt (this should replace the RandomString but
190 # not the nested stack or resource group.
191 template_salt = template_one.replace("salt: initial", "salt: more")
192 self.update_stack(stack_identifier, template_salt, environment=env)
193 updated_nested_ident = self._group_nested_identifier(stack_identifier)
194 self.assertEqual(initial_nested_ident, updated_nested_ident)
195
196 # compare the resource id, we expect a change.
197 res = self.client.resources.get(updated_nested_ident, '0')
198 updated_res_id = res.physical_resource_id
199 self.assertNotEqual(initial_res_id, updated_res_id)
200
201 def test_update_nochange(self):
202 """Test update with no properties change."""
203
204 env = {'resource_registry':
205 {'My::RandomString': 'OS::Heat::RandomString'}}
206 template_one = self.template.replace("count: 0", "count: 1")
207 stack_identifier = self.stack_create(template=template_one,
208 environment=env)
209 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
210 self.list_resources(stack_identifier))
211
212 initial_nested_ident = self._group_nested_identifier(stack_identifier)
213 self.assertEqual({'0': 'My::RandomString'},
214 self.list_resources(initial_nested_ident))
215 # get the output
216 stack0 = self.client.stacks.get(stack_identifier)
217 initial_rand = self._stack_output(stack0, 'random1')
218
219 template_copy = copy.deepcopy(template_one)
220 self.update_stack(stack_identifier, template_copy, environment=env)
221 updated_nested_ident = self._group_nested_identifier(stack_identifier)
222 self.assertEqual(initial_nested_ident, updated_nested_ident)
223
224 # compare the random number, we expect no change.
225 stack1 = self.client.stacks.get(stack_identifier)
226 updated_rand = self._stack_output(stack1, 'random1')
227 self.assertEqual(initial_rand, updated_rand)
228
229 def test_update_nochange_resource_needs_update(self):
230 """Test update when the resource definition has changed."""
231 # Test the scenario when the ResourceGroup update happens without
232 # any changed properties, this can happen if the definition of
233 # a contained provider resource changes (files map changes), then
234 # the group and underlying nested stack should end up updated.
235
236 random_templ1 = '''
237heat_template_version: 2013-05-23
238parameters:
239 length:
240 type: string
241 default: not-used
242 salt:
243 type: string
244 default: not-used
245resources:
246 random1:
247 type: OS::Heat::RandomString
248 properties:
249 salt: initial
250outputs:
251 value:
252 value: {get_attr: [random1, value]}
253'''
254 files1 = {'my_random.yaml': random_templ1}
255
256 random_templ2 = random_templ1.replace('salt: initial',
257 'salt: more')
258 files2 = {'my_random.yaml': random_templ2}
259
260 env = {'resource_registry':
261 {'My::RandomString': 'my_random.yaml'}}
262
263 template_one = self.template.replace("count: 0", "count: 1")
264 stack_identifier = self.stack_create(template=template_one,
265 environment=env,
266 files=files1)
267 self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
268 self.list_resources(stack_identifier))
269
270 initial_nested_ident = self._group_nested_identifier(stack_identifier)
271 self.assertEqual({'0': 'My::RandomString'},
272 self.list_resources(initial_nested_ident))
273 # get the output
274 stack0 = self.client.stacks.get(stack_identifier)
275 initial_rand = self._stack_output(stack0, 'random1')
276
277 # change the environment so we use a different TemplateResource.
278 # note "files2".
279 self.update_stack(stack_identifier, template_one,
280 environment=env, files=files2)
281 updated_nested_ident = self._group_nested_identifier(stack_identifier)
282 self.assertEqual(initial_nested_ident, updated_nested_ident)
283
284 # compare the output, we expect a change.
285 stack1 = self.client.stacks.get(stack_identifier)
286 updated_rand = self._stack_output(stack1, 'random1')
287 self.assertNotEqual(initial_rand, updated_rand)
288
Angus Salkeld011acc72015-01-16 20:26:34 +1000289
Angus Salkeld59b8f412015-02-25 21:01:12 +1000290class ResourceGroupTestNullParams(test.HeatIntegrationTest):
291 template = '''
292heat_template_version: 2013-05-23
293parameters:
294 param:
295 type: empty
296resources:
297 random_group:
298 type: OS::Heat::ResourceGroup
299 properties:
300 count: 1
301 resource_def:
302 type: My::RandomString
303 properties:
304 param: {get_param: param}
305outputs:
306 val:
307 value: {get_attr: [random_group, val]}
308'''
309
310 nested_template_file = '''
311heat_template_version: 2013-05-23
312parameters:
313 param:
314 type: empty
315outputs:
316 val:
317 value: {get_param: param}
318'''
319
320 scenarios = [
321 ('string_empty', dict(
322 param='',
323 p_type='string',
324 )),
325 ('boolean_false', dict(
326 param=False,
327 p_type='boolean',
328 )),
329 ('number_zero', dict(
330 param=0,
331 p_type='number',
332 )),
333 ('comma_delimited_list', dict(
334 param=[],
335 p_type='comma_delimited_list',
336 )),
337 ('json_empty', dict(
338 param={},
339 p_type='json',
340 )),
341 ]
342
343 def setUp(self):
344 super(ResourceGroupTestNullParams, self).setUp()
345 self.client = self.orchestration_client
346
347 def test_create_pass_zero_parameter(self):
348 templ = self.template.replace('type: empty',
349 'type: %s' % self.p_type)
350 n_t_f = self.nested_template_file.replace('type: empty',
351 'type: %s' % self.p_type)
352 files = {'provider.yaml': n_t_f}
353 env = {'resource_registry':
354 {'My::RandomString': 'provider.yaml'}}
355 stack_identifier = self.stack_create(
356 template=templ,
357 files=files,
358 environment=env,
359 parameters={'param': self.param}
360 )
361 stack = self.client.stacks.get(stack_identifier)
362 self.assertEqual(self.param, self._stack_output(stack, 'val')[0])
363
364
Angus Salkeld011acc72015-01-16 20:26:34 +1000365class ResourceGroupAdoptTest(test.HeatIntegrationTest):
366 """Prove that we can do resource group adopt."""
367
368 main_template = '''
369heat_template_version: "2013-05-23"
370resources:
371 group1:
372 type: OS::Heat::ResourceGroup
373 properties:
374 count: 2
375 resource_def:
376 type: OS::Heat::RandomString
377outputs:
378 test0:
379 value: {get_attr: [group1, resource.0.value]}
380 test1:
381 value: {get_attr: [group1, resource.1.value]}
382'''
383
384 def setUp(self):
385 super(ResourceGroupAdoptTest, self).setUp()
386 self.client = self.orchestration_client
387
388 def _yaml_to_json(self, yaml_templ):
389 return yaml.load(yaml_templ)
390
391 def test_adopt(self):
392 data = {
393 "resources": {
394 "group1": {
395 "status": "COMPLETE",
396 "name": "group1",
397 "resource_data": {},
398 "metadata": {},
399 "resource_id": "test-group1-id",
400 "action": "CREATE",
401 "type": "OS::Heat::ResourceGroup",
402 "resources": {
403 "0": {
404 "status": "COMPLETE",
405 "name": "0",
406 "resource_data": {"value": "goopie"},
407 "resource_id": "ID-0",
408 "action": "CREATE",
409 "type": "OS::Heat::RandomString",
410 "metadata": {}
411 },
412 "1": {
413 "status": "COMPLETE",
414 "name": "1",
415 "resource_data": {"value": "different"},
416 "resource_id": "ID-1",
417 "action": "CREATE",
418 "type": "OS::Heat::RandomString",
419 "metadata": {}
420 }
421 }
422 }
423 },
424 "environment": {"parameters": {}},
425 "template": yaml.load(self.main_template)
426 }
427 stack_identifier = self.stack_adopt(
428 adopt_data=json.dumps(data))
429
430 self.assert_resource_is_a_stack(stack_identifier, 'group1')
431 stack = self.client.stacks.get(stack_identifier)
432 self.assertEqual('goopie', self._stack_output(stack, 'test0'))
433 self.assertEqual('different', self._stack_output(stack, 'test1'))