blob: 1811c299099f3acd1448bade2d58eb5287785687 [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
290class ResourceGroupAdoptTest(test.HeatIntegrationTest):
291 """Prove that we can do resource group adopt."""
292
293 main_template = '''
294heat_template_version: "2013-05-23"
295resources:
296 group1:
297 type: OS::Heat::ResourceGroup
298 properties:
299 count: 2
300 resource_def:
301 type: OS::Heat::RandomString
302outputs:
303 test0:
304 value: {get_attr: [group1, resource.0.value]}
305 test1:
306 value: {get_attr: [group1, resource.1.value]}
307'''
308
309 def setUp(self):
310 super(ResourceGroupAdoptTest, self).setUp()
311 self.client = self.orchestration_client
312
313 def _yaml_to_json(self, yaml_templ):
314 return yaml.load(yaml_templ)
315
316 def test_adopt(self):
317 data = {
318 "resources": {
319 "group1": {
320 "status": "COMPLETE",
321 "name": "group1",
322 "resource_data": {},
323 "metadata": {},
324 "resource_id": "test-group1-id",
325 "action": "CREATE",
326 "type": "OS::Heat::ResourceGroup",
327 "resources": {
328 "0": {
329 "status": "COMPLETE",
330 "name": "0",
331 "resource_data": {"value": "goopie"},
332 "resource_id": "ID-0",
333 "action": "CREATE",
334 "type": "OS::Heat::RandomString",
335 "metadata": {}
336 },
337 "1": {
338 "status": "COMPLETE",
339 "name": "1",
340 "resource_data": {"value": "different"},
341 "resource_id": "ID-1",
342 "action": "CREATE",
343 "type": "OS::Heat::RandomString",
344 "metadata": {}
345 }
346 }
347 }
348 },
349 "environment": {"parameters": {}},
350 "template": yaml.load(self.main_template)
351 }
352 stack_identifier = self.stack_adopt(
353 adopt_data=json.dumps(data))
354
355 self.assert_resource_is_a_stack(stack_identifier, 'group1')
356 stack = self.client.stacks.get(stack_identifier)
357 self.assertEqual('goopie', self._stack_output(stack, 'test0'))
358 self.assertEqual('different', self._stack_output(stack, 'test1'))