blob: dcba97d312f31392644f903c3aa342aa675c70ff [file] [log] [blame]
Steve Bakerf547ec32014-10-20 14:34:41 +13001# 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
13import logging
14
15from heat_integrationtests.common import test
16
17
18LOG = logging.getLogger(__name__)
19
20
21class UpdateStackTest(test.HeatIntegrationTest):
22
23 template = '''
24heat_template_version: 2013-05-23
25resources:
26 random1:
27 type: OS::Heat::RandomString
28'''
29 update_template = '''
30heat_template_version: 2013-05-23
31resources:
32 random1:
33 type: OS::Heat::RandomString
34 random2:
35 type: OS::Heat::RandomString
36'''
37
Steven Hardybe1ce2d2014-11-18 12:54:50 +000038 provider_template = '''
39heat_template_version: 2013-05-23
40resources:
41 random1:
42 type: My::RandomString
43'''
44
Steven Hardy417097e2014-11-18 16:30:35 +000045 provider_group_template = '''
46heat_template_version: 2013-05-23
47resources:
48 random_group:
49 type: OS::Heat::ResourceGroup
50 properties:
51 count: 2
52 resource_def:
53 type: My::RandomString
54'''
55
Steve Bakerf547ec32014-10-20 14:34:41 +130056 def setUp(self):
57 super(UpdateStackTest, self).setUp()
58 self.client = self.orchestration_client
59
Steve Bakerf547ec32014-10-20 14:34:41 +130060 def test_stack_update_nochange(self):
61 stack_name = self._stack_rand_name()
62 self.client.stacks.create(
63 stack_name=stack_name,
64 template=self.template,
65 files={},
66 disable_rollback=True,
67 parameters={},
68 environment={}
69 )
70 self.addCleanup(self.client.stacks.delete, stack_name)
71
72 stack = self.client.stacks.get(stack_name)
73 stack_identifier = '%s/%s' % (stack_name, stack.id)
74
75 self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
76 expected_resources = {'random1': 'OS::Heat::RandomString'}
77 self.assertEqual(expected_resources,
78 self.list_resources(stack_identifier))
79
80 # Update with no changes, resources should be unchanged
81 self.update_stack(stack_identifier, self.template)
82 self.assertEqual(expected_resources,
83 self.list_resources(stack_identifier))
84
85 def test_stack_update_add_remove(self):
86 stack_name = self._stack_rand_name()
87
88 self.client.stacks.create(
89 stack_name=stack_name,
90 template=self.template,
91 files={},
92 disable_rollback=True,
93 parameters={},
94 environment={}
95 )
96 self.addCleanup(self.client.stacks.delete, stack_name)
97
98 stack = self.client.stacks.get(stack_name)
99 stack_identifier = '%s/%s' % (stack_name, stack.id)
100
101 self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
102 initial_resources = {'random1': 'OS::Heat::RandomString'}
103 self.assertEqual(initial_resources,
104 self.list_resources(stack_identifier))
105
106 # Add one resource via a stack update
107 self.update_stack(stack_identifier, self.update_template)
108 stack = self.client.stacks.get(stack_identifier)
109 updated_resources = {'random1': 'OS::Heat::RandomString',
110 'random2': 'OS::Heat::RandomString'}
111 self.assertEqual(updated_resources,
112 self.list_resources(stack_identifier))
113
114 # Then remove it by updating with the original template
115 self.update_stack(stack_identifier, self.template)
116 self.assertEqual(initial_resources,
117 self.list_resources(stack_identifier))
Steven Hardybe1ce2d2014-11-18 12:54:50 +0000118
119 def test_stack_update_provider(self):
120 stack_name = self._stack_rand_name()
121 files = {'provider.yaml': self.template}
122 env = {'resource_registry':
123 {'My::RandomString': 'provider.yaml'}}
124
125 self.client.stacks.create(
126 stack_name=stack_name,
127 template=self.provider_template,
128 files=files,
129 disable_rollback=True,
130 parameters={},
131 environment=env
132 )
133 self.addCleanup(self.client.stacks.delete, stack_name)
134
135 stack = self.client.stacks.get(stack_name)
136 stack_identifier = '%s/%s' % (stack_name, stack.id)
137
138 self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
139 initial_resources = {'random1': 'My::RandomString'}
140 self.assertEqual(initial_resources,
141 self.list_resources(stack_identifier))
142
143 # Prove the resource is backed by a nested stack, save the ID
144 rsrc = self.client.resources.get(stack_identifier, 'random1')
145 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
146 nested_href = nested_link[0]['href']
147 nested_id = nested_href.split('/')[-1]
148 nested_identifier = '/'.join(nested_href.split('/')[-2:])
149 physical_resource_id = rsrc.physical_resource_id
150 self.assertEqual(physical_resource_id, nested_id)
151
152 # Then check the expected resources are in the nested stack
153 nested_resources = {'random1': 'OS::Heat::RandomString'}
154 self.assertEqual(nested_resources,
155 self.list_resources(nested_identifier))
156
157 # Add one resource via a stack update by changing the nested stack
158 files['provider.yaml'] = self.update_template
159 self.update_stack(stack_identifier, self.provider_template,
160 environment=env, files=files)
161 stack = self.client.stacks.get(stack_identifier)
162
163 # Parent resources should be unchanged and the nested stack
164 # should have been updated in-place without replacement
165 self.assertEqual(initial_resources,
166 self.list_resources(stack_identifier))
167 rsrc = self.client.resources.get(stack_identifier, 'random1')
168 self.assertEqual(rsrc.physical_resource_id, nested_id)
169
170 # Then check the expected resources are in the nested stack
171 nested_resources = {'random1': 'OS::Heat::RandomString',
172 'random2': 'OS::Heat::RandomString'}
173 self.assertEqual(nested_resources,
174 self.list_resources(nested_identifier))
Steven Hardy417097e2014-11-18 16:30:35 +0000175
176 def test_stack_update_provider_group(self):
177 '''Test two-level nested update.'''
178 # Create a ResourceGroup (which creates a nested stack),
179 # containing provider resources (which create a nested
180 # stack), thus excercising an update which traverses
181 # two levels of nesting.
182 stack_name = self._stack_rand_name()
183 files = {'provider.yaml': self.template}
184 env = {'resource_registry':
185 {'My::RandomString': 'provider.yaml'}}
186
187 self.client.stacks.create(
188 stack_name=stack_name,
189 template=self.provider_group_template,
190 files=files,
191 disable_rollback=True,
192 parameters={},
193 environment=env
194 )
195 self.addCleanup(self.client.stacks.delete, stack_name)
196
197 stack = self.client.stacks.get(stack_name)
198 stack_identifier = '%s/%s' % (stack_name, stack.id)
199
200 self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
201 initial_resources = {'random_group': 'OS::Heat::ResourceGroup'}
202 self.assertEqual(initial_resources,
203 self.list_resources(stack_identifier))
204
205 # Prove the resource is backed by a nested stack, save the ID
206 rsrc = self.client.resources.get(stack_identifier, 'random_group')
207 physical_resource_id = rsrc.physical_resource_id
208
209 nested_stack = self.client.stacks.get(physical_resource_id)
210 nested_identifier = '%s/%s' % (nested_stack.stack_name,
211 nested_stack.id)
212 self.assertEqual(stack.id, nested_stack.parent)
213
214 # Then check the expected resources are in the nested stack
215 nested_resources = {'0': 'My::RandomString',
216 '1': 'My::RandomString'}
217 self.assertEqual(nested_resources,
218 self.list_resources(nested_identifier))
219
220 for n_rsrc in nested_resources:
221 rsrc = self.client.resources.get(nested_identifier, n_rsrc)
222 provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
223 provider_identifier = '%s/%s' % (provider_stack.stack_name,
224 provider_stack.id)
225 provider_resources = {u'random1': u'OS::Heat::RandomString'}
226 self.assertEqual(provider_resources,
227 self.list_resources(provider_identifier))
228
229 # Add one resource via a stack update by changing the nested stack
230 files['provider.yaml'] = self.update_template
231 self.update_stack(stack_identifier, self.provider_group_template,
232 environment=env, files=files)
233 stack = self.client.stacks.get(stack_identifier)
234
235 # Parent resources should be unchanged and the nested stack
236 # should have been updated in-place without replacement
237 self.assertEqual(initial_resources,
238 self.list_resources(stack_identifier))
239
240 # Resource group stack should also be unchanged (but updated)
241 nested_stack = self.client.stacks.get(nested_identifier)
242 self.assertEqual('UPDATE_COMPLETE', nested_stack.stack_status)
243 self.assertEqual(nested_resources,
244 self.list_resources(nested_identifier))
245
246 for n_rsrc in nested_resources:
247 rsrc = self.client.resources.get(nested_identifier, n_rsrc)
248 provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
249 provider_identifier = '%s/%s' % (provider_stack.stack_name,
250 provider_stack.id)
251 provider_resources = {'random1': 'OS::Heat::RandomString',
252 'random2': 'OS::Heat::RandomString'}
253 self.assertEqual(provider_resources,
254 self.list_resources(provider_identifier))