blob: d4e85b9a2a1073dde8bce9ee04eb2cd8505836f4 [file] [log] [blame]
Angus Salkeldebf15d72014-12-10 17:03:15 +10001# 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 InstanceGroupTest(test.HeatIntegrationTest):
22
23 template = '''
24{
25 "AWSTemplateFormatVersion" : "2010-09-09",
26 "Description" : "Template to create multiple instances.",
27 "Parameters" : {"size": {"Type": "String", "Default": "1"},
28 "AZ": {"Type": "String", "Default": "nova"},
29 "image": {"Type": "String"},
Anastasia Kuznetsova33258742015-01-14 16:13:42 +040030 "flavor": {"Type": "String"}},
Angus Salkeldebf15d72014-12-10 17:03:15 +100031 "Resources": {
32 "JobServerGroup": {
33 "Type": "OS::Heat::InstanceGroup",
34 "Properties": {
35 "LaunchConfigurationName" : {"Ref": "JobServerConfig"},
36 "Size" : {"Ref": "size"},
37 "AvailabilityZones" : [{"Ref": "AZ"}]
38 }
39 },
40
41 "JobServerConfig" : {
42 "Type" : "AWS::AutoScaling::LaunchConfiguration",
43 "Metadata": {"foo": "bar"},
44 "Properties": {
45 "ImageId" : {"Ref": "image"},
46 "InstanceType" : {"Ref": "flavor"},
Angus Salkeldebf15d72014-12-10 17:03:15 +100047 "SecurityGroups" : [ "sg-1" ],
Sergey Kraynev9612adc2014-12-19 08:17:08 -050048 "UserData" : "jsconfig data"
Angus Salkeldebf15d72014-12-10 17:03:15 +100049 }
50 }
51 },
52 "Outputs": {
53 "InstanceList": {"Value": {
54 "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}}
55 }
56}
57'''
58
59 instance_template = '''
60heat_template_version: 2013-05-23
61parameters:
62 ImageId: {type: string}
63 InstanceType: {type: string}
Angus Salkeldebf15d72014-12-10 17:03:15 +100064 SecurityGroups: {type: comma_delimited_list}
65 UserData: {type: string}
66 Tags: {type: comma_delimited_list}
67
68resources:
69 random1:
70 type: OS::Heat::RandomString
71
72outputs:
73 PublicIp:
74 value: {get_attr: [random1, value]}
75'''
76
Angus Salkeldd67cf702014-12-18 10:40:47 +100077 # This is designed to fail.
78 bad_instance_template = '''
79heat_template_version: 2013-05-23
80parameters:
81 ImageId: {type: string}
82 InstanceType: {type: string}
Angus Salkeldd67cf702014-12-18 10:40:47 +100083 SecurityGroups: {type: comma_delimited_list}
84 UserData: {type: string}
85 Tags: {type: comma_delimited_list}
86
87resources:
88 random1:
89 type: OS::Heat::RandomString
90 depends_on: waiter
91 ready_poster:
92 type: AWS::CloudFormation::WaitConditionHandle
93 waiter:
94 type: AWS::CloudFormation::WaitCondition
95 properties:
96 Handle: {Ref: ready_poster}
97 Timeout: 1
98outputs:
99 PublicIp:
100 value: {get_attr: [random1, value]}
101'''
102
Angus Salkeldebf15d72014-12-10 17:03:15 +1000103 def setUp(self):
104 super(InstanceGroupTest, self).setUp()
105 self.client = self.orchestration_client
106 if not self.conf.image_ref:
107 raise self.skipException("No image configured to test")
Angus Salkeldebf15d72014-12-10 17:03:15 +1000108 if not self.conf.instance_type:
109 raise self.skipException("No flavor configured to test")
110
Angus Salkeldbfc7e932014-12-15 11:15:45 +1000111 def assert_instance_count(self, stack, expected_count):
112 inst_list = self._stack_output(stack, 'InstanceList')
113 self.assertEqual(expected_count, len(inst_list.split(',')))
114
Angus Salkeldd67cf702014-12-18 10:40:47 +1000115 def _get_nested_identifier(self, stack_identifier):
116 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
117 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
118 self.assertEqual(1, len(nested_link))
119 nested_href = nested_link[0]['href']
120 nested_id = nested_href.split('/')[-1]
121 nested_identifier = '/'.join(nested_href.split('/')[-2:])
122 physical_resource_id = rsrc.physical_resource_id
123 self.assertEqual(physical_resource_id, nested_id)
124 return nested_identifier
125
126 def _assert_instance_state(self, nested_identifier,
127 num_complete, num_failed):
128 for res in self.client.resources.list(nested_identifier):
129 if 'COMPLETE' in res.resource_status:
130 num_complete = num_complete - 1
131 elif 'FAILED' in res.resource_status:
132 num_failed = num_failed - 1
133 self.assertEqual(0, num_failed)
134 self.assertEqual(0, num_complete)
135
Angus Salkeldebf15d72014-12-10 17:03:15 +1000136 def test_basic_create_works(self):
137 """Make sure the working case is good.
138 Note this combines test_override_aws_ec2_instance into this test as
139 well, which is:
140 If AWS::EC2::Instance is overridden, InstanceGroup will automatically
141 use that overridden resource type.
142 """
143
Angus Salkeldebf15d72014-12-10 17:03:15 +1000144 files = {'provider.yaml': self.instance_template}
145 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
146 'parameters': {'size': 4,
147 'image': self.conf.image_ref,
Angus Salkeldebf15d72014-12-10 17:03:15 +1000148 'flavor': self.conf.instance_type}}
Angus Salkeldbfc7e932014-12-15 11:15:45 +1000149 stack_identifier = self.stack_create(template=self.template,
150 files=files, environment=env)
Angus Salkeldebf15d72014-12-10 17:03:15 +1000151 initial_resources = {
152 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
153 'JobServerGroup': 'OS::Heat::InstanceGroup'}
154 self.assertEqual(initial_resources,
155 self.list_resources(stack_identifier))
156
Angus Salkeldbfc7e932014-12-15 11:15:45 +1000157 stack = self.client.stacks.get(stack_identifier)
158 self.assert_instance_count(stack, 4)
159
160 def test_size_updates_work(self):
161 files = {'provider.yaml': self.instance_template}
162 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
163 'parameters': {'size': 2,
164 'image': self.conf.image_ref,
Angus Salkeldbfc7e932014-12-15 11:15:45 +1000165 'flavor': self.conf.instance_type}}
166
167 stack_identifier = self.stack_create(template=self.template,
168 files=files,
169 environment=env)
170 stack = self.client.stacks.get(stack_identifier)
171 self.assert_instance_count(stack, 2)
172
173 # Increase min size to 5
174 env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
175 'parameters': {'size': 5,
176 'image': self.conf.image_ref,
Angus Salkeldbfc7e932014-12-15 11:15:45 +1000177 'flavor': self.conf.instance_type}}
178 self.update_stack(stack_identifier, self.template,
179 environment=env2, files=files)
180 self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
181 stack = self.client.stacks.get(stack_identifier)
182 self.assert_instance_count(stack, 5)
Angus Salkeldcd21b1b2014-12-15 11:27:04 +1000183
184 def test_update_group_replace(self):
185 """Make sure that during a group update the non updatable
186 properties cause a replacement.
187 """
188 files = {'provider.yaml': self.instance_template}
189 env = {'resource_registry':
190 {'AWS::EC2::Instance': 'provider.yaml'},
191 'parameters': {'size': 1,
192 'image': self.conf.image_ref,
Angus Salkeldcd21b1b2014-12-15 11:27:04 +1000193 'flavor': self.conf.instance_type}}
194
195 stack_identifier = self.stack_create(template=self.template,
196 files=files,
197 environment=env)
198 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
199 orig_asg_id = rsrc.physical_resource_id
200
201 env2 = {'resource_registry':
202 {'AWS::EC2::Instance': 'provider.yaml'},
203 'parameters': {'size': '2',
204 'AZ': 'wibble',
205 'image': self.conf.image_ref,
Angus Salkeldcd21b1b2014-12-15 11:27:04 +1000206 'flavor': self.conf.instance_type}}
207 self.update_stack(stack_identifier, self.template,
208 environment=env2, files=files)
209
210 # replacement will cause the resource physical_resource_id to change.
211 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
212 self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id)
Angus Salkeldd67cf702014-12-18 10:40:47 +1000213
214 def test_create_instance_error_causes_group_error(self):
215 """If a resource in an instance group fails to be created, the instance
216 group itself will fail and the broken inner resource will remain.
217 """
218 stack_name = self._stack_rand_name()
219 files = {'provider.yaml': self.bad_instance_template}
220 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
221 'parameters': {'size': 2,
222 'image': self.conf.image_ref,
Angus Salkeldd67cf702014-12-18 10:40:47 +1000223 'flavor': self.conf.instance_type}}
224
225 self.client.stacks.create(
226 stack_name=stack_name,
227 template=self.template,
228 files=files,
229 disable_rollback=True,
230 parameters={},
231 environment=env
232 )
233 self.addCleanup(self.client.stacks.delete, stack_name)
234 stack = self.client.stacks.get(stack_name)
235 stack_identifier = '%s/%s' % (stack_name, stack.id)
236 self._wait_for_stack_status(stack_identifier, 'CREATE_FAILED')
237 initial_resources = {
238 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
239 'JobServerGroup': 'OS::Heat::InstanceGroup'}
240 self.assertEqual(initial_resources,
241 self.list_resources(stack_identifier))
242
243 nested_ident = self._get_nested_identifier(stack_identifier)
244 self._assert_instance_state(nested_ident, 0, 2)
245
246 def test_update_instance_error_causes_group_error(self):
247 """If a resource in an instance group fails to be created during an
248 update, the instance group itself will fail and the broken inner
249 resource will remain.
250 """
251 files = {'provider.yaml': self.instance_template}
252 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
253 'parameters': {'size': 2,
254 'image': self.conf.image_ref,
Angus Salkeldd67cf702014-12-18 10:40:47 +1000255 'flavor': self.conf.instance_type}}
256
257 stack_identifier = self.stack_create(template=self.template,
258 files=files,
259 environment=env)
260 initial_resources = {
261 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
262 'JobServerGroup': 'OS::Heat::InstanceGroup'}
263 self.assertEqual(initial_resources,
264 self.list_resources(stack_identifier))
265
266 stack = self.client.stacks.get(stack_identifier)
267 self.assert_instance_count(stack, 2)
268 nested_ident = self._get_nested_identifier(stack_identifier)
269 self._assert_instance_state(nested_ident, 2, 0)
270
271 env['parameters']['size'] = 3
272 files2 = {'provider.yaml': self.bad_instance_template}
273 self.client.stacks.update(
274 stack_id=stack_identifier,
275 template=self.template,
276 files=files2,
277 disable_rollback=True,
278 parameters={},
279 environment=env
280 )
281 self._wait_for_stack_status(stack_identifier, 'UPDATE_FAILED')
282
283 # assert that there are 3 bad instances
284 nested_ident = self._get_nested_identifier(stack_identifier)
285 self._assert_instance_state(nested_ident, 0, 3)