blob: 369fa000cfbd9801db6032bf096336bc9b0c7545 [file] [log] [blame]
Angus Salkeld28339012015-01-20 19:15:37 +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 copy
14import json
Angus Salkeld28339012015-01-20 19:15:37 +100015
huangtianhuaf71ae072015-07-09 09:15:10 +080016from heatclient import exc
Steve Baker24641292015-03-13 10:47:50 +130017from oslo_log import log as logging
huangtianhuaf71ae072015-07-09 09:15:10 +080018import six
Angus Salkeld28339012015-01-20 19:15:37 +100019from testtools import matchers
20
21from heat_integrationtests.common import test
Rabi Mishra477efc92015-07-31 13:01:45 +053022from heat_integrationtests.functional import functional_base
Angus Salkeld28339012015-01-20 19:15:37 +100023
24
25LOG = logging.getLogger(__name__)
26
27
Rabi Mishra477efc92015-07-31 13:01:45 +053028class AutoscalingGroupTest(functional_base.FunctionalTestsBase):
Angus Salkeld28339012015-01-20 19:15:37 +100029
30 template = '''
31{
32 "AWSTemplateFormatVersion" : "2010-09-09",
33 "Description" : "Template to create multiple instances.",
34 "Parameters" : {"size": {"Type": "String", "Default": "1"},
35 "AZ": {"Type": "String", "Default": "nova"},
36 "image": {"Type": "String"},
rabic333f762016-09-29 08:34:03 +053037 "flavor": {"Type": "String"},
38 "user_data": {"Type": "String", "Default": "jsconfig data"}},
Angus Salkeld28339012015-01-20 19:15:37 +100039 "Resources": {
40 "JobServerGroup": {
41 "Type" : "AWS::AutoScaling::AutoScalingGroup",
42 "Properties" : {
43 "AvailabilityZones" : [{"Ref": "AZ"}],
44 "LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
45 "MinSize" : {"Ref": "size"},
46 "MaxSize" : "20"
47 }
48 },
49
50 "JobServerConfig" : {
51 "Type" : "AWS::AutoScaling::LaunchConfiguration",
52 "Metadata": {"foo": "bar"},
53 "Properties": {
54 "ImageId" : {"Ref": "image"},
55 "InstanceType" : {"Ref": "flavor"},
56 "SecurityGroups" : [ "sg-1" ],
rabic333f762016-09-29 08:34:03 +053057 "UserData" : {"Ref": "user_data"}
Angus Salkeld28339012015-01-20 19:15:37 +100058 }
59 }
60 },
61 "Outputs": {
62 "InstanceList": {"Value": {
63 "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}},
64 "JobServerConfigRef": {"Value": {
65 "Ref": "JobServerConfig"}}
66 }
67}
68'''
69
70 instance_template = '''
71heat_template_version: 2013-05-23
72parameters:
73 ImageId: {type: string}
74 InstanceType: {type: string}
75 SecurityGroups: {type: comma_delimited_list}
76 UserData: {type: string}
Angus Salkeld8d1050c2015-02-24 12:23:06 +100077 Tags: {type: comma_delimited_list, default: "x,y"}
Angus Salkeld28339012015-01-20 19:15:37 +100078
79resources:
80 random1:
81 type: OS::Heat::RandomString
82 properties:
rabic333f762016-09-29 08:34:03 +053083 salt: {get_param: UserData}
Angus Salkeld28339012015-01-20 19:15:37 +100084outputs:
Angus Salkeldc85229b2015-02-09 10:58:04 +100085 PublicIp: {value: {get_attr: [random1, value]}}
86 AvailabilityZone: {value: 'not-used11'}
87 PrivateDnsName: {value: 'not-used12'}
88 PublicDnsName: {value: 'not-used13'}
89 PrivateIp: {value: 'not-used14'}
Angus Salkeld28339012015-01-20 19:15:37 +100090'''
91
92 # This is designed to fail.
93 bad_instance_template = '''
94heat_template_version: 2013-05-23
95parameters:
96 ImageId: {type: string}
97 InstanceType: {type: string}
98 SecurityGroups: {type: comma_delimited_list}
99 UserData: {type: string}
Angus Salkeld8d1050c2015-02-24 12:23:06 +1000100 Tags: {type: comma_delimited_list, default: "x,y"}
Angus Salkeld28339012015-01-20 19:15:37 +1000101
102resources:
103 random1:
104 type: OS::Heat::RandomString
105 depends_on: waiter
106 ready_poster:
107 type: AWS::CloudFormation::WaitConditionHandle
108 waiter:
109 type: AWS::CloudFormation::WaitCondition
110 properties:
Angus Salkeld12e13d42015-08-11 12:17:58 +1000111 Handle: {get_resource: ready_poster}
Angus Salkeld28339012015-01-20 19:15:37 +1000112 Timeout: 1
113outputs:
114 PublicIp:
115 value: {get_attr: [random1, value]}
116'''
117
118 def setUp(self):
119 super(AutoscalingGroupTest, self).setUp()
Angus Salkeld28339012015-01-20 19:15:37 +1000120 if not self.conf.minimal_image_ref:
121 raise self.skipException("No minimal image configured to test")
122 if not self.conf.instance_type:
123 raise self.skipException("No flavor configured to test")
124
125 def assert_instance_count(self, stack, expected_count):
126 inst_list = self._stack_output(stack, 'InstanceList')
127 self.assertEqual(expected_count, len(inst_list.split(',')))
128
129 def _assert_instance_state(self, nested_identifier,
130 num_complete, num_failed):
131 for res in self.client.resources.list(nested_identifier):
132 if 'COMPLETE' in res.resource_status:
133 num_complete = num_complete - 1
134 elif 'FAILED' in res.resource_status:
135 num_failed = num_failed - 1
136 self.assertEqual(0, num_failed)
137 self.assertEqual(0, num_complete)
138
139
140class AutoscalingGroupBasicTest(AutoscalingGroupTest):
141
142 def test_basic_create_works(self):
143 """Make sure the working case is good.
144
145 Note this combines test_override_aws_ec2_instance into this test as
146 well, which is:
147 If AWS::EC2::Instance is overridden, AutoScalingGroup will
148 automatically use that overridden resource type.
149 """
150
151 files = {'provider.yaml': self.instance_template}
152 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
153 'parameters': {'size': 4,
rabic333f762016-09-29 08:34:03 +0530154 'image': self.conf.minimal_image_ref,
Angus Salkeld28339012015-01-20 19:15:37 +1000155 'flavor': self.conf.instance_type}}
156 stack_identifier = self.stack_create(template=self.template,
157 files=files, environment=env)
158 initial_resources = {
159 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
160 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
161 self.assertEqual(initial_resources,
162 self.list_resources(stack_identifier))
163
164 stack = self.client.stacks.get(stack_identifier)
165 self.assert_instance_count(stack, 4)
166
167 def test_size_updates_work(self):
168 files = {'provider.yaml': self.instance_template}
169 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
170 'parameters': {'size': 2,
rabic333f762016-09-29 08:34:03 +0530171 'image': self.conf.minimal_image_ref,
Angus Salkeld28339012015-01-20 19:15:37 +1000172 'flavor': self.conf.instance_type}}
173
174 stack_identifier = self.stack_create(template=self.template,
175 files=files,
176 environment=env)
177 stack = self.client.stacks.get(stack_identifier)
178 self.assert_instance_count(stack, 2)
179
180 # Increase min size to 5
181 env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
182 'parameters': {'size': 5,
rabic333f762016-09-29 08:34:03 +0530183 'image': self.conf.minimal_image_ref,
Angus Salkeld28339012015-01-20 19:15:37 +1000184 'flavor': self.conf.instance_type}}
185 self.update_stack(stack_identifier, self.template,
186 environment=env2, files=files)
Angus Salkeld28339012015-01-20 19:15:37 +1000187 stack = self.client.stacks.get(stack_identifier)
188 self.assert_instance_count(stack, 5)
189
190 def test_update_group_replace(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300191 """Test case for ensuring non-updatable props case a replacement.
192
193 Make sure that during a group update the non-updatable
Angus Salkeld28339012015-01-20 19:15:37 +1000194 properties cause a replacement.
195 """
196 files = {'provider.yaml': self.instance_template}
197 env = {'resource_registry':
198 {'AWS::EC2::Instance': 'provider.yaml'},
199 'parameters': {'size': '1',
rabic333f762016-09-29 08:34:03 +0530200 'image': self.conf.minimal_image_ref,
Angus Salkeld28339012015-01-20 19:15:37 +1000201 'flavor': self.conf.instance_type}}
202
203 stack_identifier = self.stack_create(template=self.template,
204 files=files,
205 environment=env)
206 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
207 orig_asg_id = rsrc.physical_resource_id
208
209 env2 = {'resource_registry':
210 {'AWS::EC2::Instance': 'provider.yaml'},
211 'parameters': {'size': '1',
212 'AZ': 'wibble',
rabic333f762016-09-29 08:34:03 +0530213 'image': self.conf.minimal_image_ref,
214 'flavor': self.conf.instance_type,
215 'user_data': 'new data'}}
Angus Salkeld28339012015-01-20 19:15:37 +1000216 self.update_stack(stack_identifier, self.template,
217 environment=env2, files=files)
218
219 # replacement will cause the resource physical_resource_id to change.
220 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
221 self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id)
222
223 def test_create_instance_error_causes_group_error(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300224 """Test create failing a resource in the instance group.
225
226 If a resource in an instance group fails to be created, the instance
Angus Salkeld28339012015-01-20 19:15:37 +1000227 group itself will fail and the broken inner resource will remain.
228 """
229 stack_name = self._stack_rand_name()
230 files = {'provider.yaml': self.bad_instance_template}
231 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
232 'parameters': {'size': 2,
rabic333f762016-09-29 08:34:03 +0530233 'image': self.conf.minimal_image_ref,
Angus Salkeld28339012015-01-20 19:15:37 +1000234 'flavor': self.conf.instance_type}}
235
236 self.client.stacks.create(
237 stack_name=stack_name,
238 template=self.template,
239 files=files,
240 disable_rollback=True,
241 parameters={},
242 environment=env
243 )
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200244 self.addCleanup(self._stack_delete, stack_name)
Angus Salkeld28339012015-01-20 19:15:37 +1000245 stack = self.client.stacks.get(stack_name)
246 stack_identifier = '%s/%s' % (stack_name, stack.id)
247 self._wait_for_stack_status(stack_identifier, 'CREATE_FAILED')
248 initial_resources = {
249 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
250 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
251 self.assertEqual(initial_resources,
252 self.list_resources(stack_identifier))
253
254 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
255 'JobServerGroup')
256 self._assert_instance_state(nested_ident, 0, 2)
257
258 def test_update_instance_error_causes_group_error(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300259 """Test update failing a resource in the instance group.
260
261 If a resource in an instance group fails to be created during an
Angus Salkeld28339012015-01-20 19:15:37 +1000262 update, the instance group itself will fail and the broken inner
263 resource will remain.
264 """
265 files = {'provider.yaml': self.instance_template}
266 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
267 'parameters': {'size': 2,
rabic333f762016-09-29 08:34:03 +0530268 'image': self.conf.minimal_image_ref,
Angus Salkeld28339012015-01-20 19:15:37 +1000269 'flavor': self.conf.instance_type}}
270
271 stack_identifier = self.stack_create(template=self.template,
272 files=files,
273 environment=env)
274 initial_resources = {
275 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
276 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
277 self.assertEqual(initial_resources,
278 self.list_resources(stack_identifier))
279
280 stack = self.client.stacks.get(stack_identifier)
281 self.assert_instance_count(stack, 2)
282 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
283 'JobServerGroup')
284 self._assert_instance_state(nested_ident, 2, 0)
Angus Salkeldd4b6bc02015-02-04 16:48:45 +1000285 initial_list = [res.resource_name
286 for res in self.client.resources.list(nested_ident)]
Angus Salkeld28339012015-01-20 19:15:37 +1000287
288 env['parameters']['size'] = 3
289 files2 = {'provider.yaml': self.bad_instance_template}
290 self.client.stacks.update(
291 stack_id=stack_identifier,
292 template=self.template,
293 files=files2,
294 disable_rollback=True,
295 parameters={},
296 environment=env
297 )
298 self._wait_for_stack_status(stack_identifier, 'UPDATE_FAILED')
299
300 # assert that there are 3 bad instances
301 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
302 'JobServerGroup')
Angus Salkeldd4b6bc02015-02-04 16:48:45 +1000303
304 # 2 resources should be in update failed, and one create failed.
305 for res in self.client.resources.list(nested_ident):
306 if res.resource_name in initial_list:
307 self._wait_for_resource_status(nested_ident,
308 res.resource_name,
309 'UPDATE_FAILED')
310 else:
311 self._wait_for_resource_status(nested_ident,
312 res.resource_name,
313 'CREATE_FAILED')
Angus Salkeld28339012015-01-20 19:15:37 +1000314
Angus Salkeldf1b10dd2015-02-04 10:57:38 +1000315 def test_group_suspend_resume(self):
316
317 files = {'provider.yaml': self.instance_template}
318 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
319 'parameters': {'size': 4,
rabic333f762016-09-29 08:34:03 +0530320 'image': self.conf.minimal_image_ref,
Angus Salkeldf1b10dd2015-02-04 10:57:38 +1000321 'flavor': self.conf.instance_type}}
322 stack_identifier = self.stack_create(template=self.template,
323 files=files, environment=env)
324
325 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
326 'JobServerGroup')
327
Angus Salkelda7500d12015-04-10 15:44:07 +1000328 self.stack_suspend(stack_identifier)
329 self._wait_for_all_resource_status(nested_ident, 'SUSPEND_COMPLETE')
Angus Salkeldf1b10dd2015-02-04 10:57:38 +1000330
Angus Salkelda7500d12015-04-10 15:44:07 +1000331 self.stack_resume(stack_identifier)
332 self._wait_for_all_resource_status(nested_ident, 'RESUME_COMPLETE')
Angus Salkeldf1b10dd2015-02-04 10:57:38 +1000333
Angus Salkeld28339012015-01-20 19:15:37 +1000334
335class AutoscalingGroupUpdatePolicyTest(AutoscalingGroupTest):
336
337 def ig_tmpl_with_updt_policy(self):
338 templ = json.loads(copy.deepcopy(self.template))
339 up = {"AutoScalingRollingUpdate": {
340 "MinInstancesInService": "1",
341 "MaxBatchSize": "2",
342 "PauseTime": "PT1S"}}
343 templ['Resources']['JobServerGroup']['UpdatePolicy'] = up
344 return templ
345
346 def update_instance_group(self, updt_template,
347 num_updates_expected_on_updt,
348 num_creates_expected_on_updt,
Rabi Mishra06b11272015-10-14 10:19:22 +0530349 num_deletes_expected_on_updt):
Angus Salkeld28339012015-01-20 19:15:37 +1000350
351 # setup stack from the initial template
352 files = {'provider.yaml': self.instance_template}
353 size = 10
354 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
355 'parameters': {'size': size,
rabic333f762016-09-29 08:34:03 +0530356 'image': self.conf.minimal_image_ref,
Angus Salkeld28339012015-01-20 19:15:37 +1000357 'flavor': self.conf.instance_type}}
358 stack_name = self._stack_rand_name()
359 stack_identifier = self.stack_create(
360 stack_name=stack_name,
361 template=self.ig_tmpl_with_updt_policy(),
362 files=files,
363 environment=env)
364 stack = self.client.stacks.get(stack_identifier)
365 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
366 'JobServerGroup')
367
368 # test that physical resource name of launch configuration is used
369 conf_name = self._stack_output(stack, 'JobServerConfigRef')
370 conf_name_pattern = '%s-JobServerConfig-[a-zA-Z0-9]+$' % stack_name
371 self.assertThat(conf_name,
372 matchers.MatchesRegex(conf_name_pattern))
373
374 # test the number of instances created
375 self.assert_instance_count(stack, size)
376 # saves info from initial list of instances for comparison later
377 init_instances = self.client.resources.list(nested_ident)
378 init_names = [inst.resource_name for inst in init_instances]
379
380 # test stack update
381 self.update_stack(stack_identifier, updt_template,
382 environment=env, files=files)
Angus Salkeld28339012015-01-20 19:15:37 +1000383 updt_stack = self.client.stacks.get(stack_identifier)
384
385 # test that the launch configuration is replaced
386 updt_conf_name = self._stack_output(updt_stack, 'JobServerConfigRef')
387 self.assertThat(updt_conf_name,
388 matchers.MatchesRegex(conf_name_pattern))
389 self.assertNotEqual(conf_name, updt_conf_name)
390
391 # test that the group size are the same
392 updt_instances = self.client.resources.list(nested_ident)
393 updt_names = [inst.resource_name for inst in updt_instances]
394 self.assertEqual(len(init_names), len(updt_names))
395 for res in updt_instances:
396 self.assertEqual('UPDATE_COMPLETE', res.resource_status)
397
398 # test that the appropriate number of instance names are the same
399 matched_names = set(updt_names) & set(init_names)
400 self.assertEqual(num_updates_expected_on_updt, len(matched_names))
401
402 # test that the appropriate number of new instances are created
403 self.assertEqual(num_creates_expected_on_updt,
404 len(set(updt_names) - set(init_names)))
405
406 # test that the appropriate number of instances are deleted
407 self.assertEqual(num_deletes_expected_on_updt,
408 len(set(init_names) - set(updt_names)))
409
410 # test that the older instances are the ones being deleted
411 if num_deletes_expected_on_updt > 0:
412 deletes_expected = init_names[:num_deletes_expected_on_updt]
413 self.assertNotIn(deletes_expected, updt_names)
414
415 def test_instance_group_update_replace(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300416 """Test simple update replace.
417
418 Test update replace with no conflict in batch size and minimum
419 instances in service.
Angus Salkeld28339012015-01-20 19:15:37 +1000420 """
421 updt_template = self.ig_tmpl_with_updt_policy()
422 grp = updt_template['Resources']['JobServerGroup']
423 policy = grp['UpdatePolicy']['AutoScalingRollingUpdate']
424 policy['MinInstancesInService'] = '1'
425 policy['MaxBatchSize'] = '3'
426 config = updt_template['Resources']['JobServerConfig']
rabic333f762016-09-29 08:34:03 +0530427 config['Properties']['UserData'] = 'new data'
Angus Salkeld28339012015-01-20 19:15:37 +1000428
429 self.update_instance_group(updt_template,
430 num_updates_expected_on_updt=10,
431 num_creates_expected_on_updt=0,
Rabi Mishra06b11272015-10-14 10:19:22 +0530432 num_deletes_expected_on_updt=0)
Angus Salkeld28339012015-01-20 19:15:37 +1000433
434 def test_instance_group_update_replace_with_adjusted_capacity(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300435 """Test update replace with capacity adjustment.
436
437 Test update replace with capacity adjustment due to conflict in batch
438 size and minimum instances in service.
Angus Salkeld28339012015-01-20 19:15:37 +1000439 """
440 updt_template = self.ig_tmpl_with_updt_policy()
441 grp = updt_template['Resources']['JobServerGroup']
442 policy = grp['UpdatePolicy']['AutoScalingRollingUpdate']
443 policy['MinInstancesInService'] = '8'
444 policy['MaxBatchSize'] = '4'
445 config = updt_template['Resources']['JobServerConfig']
rabic333f762016-09-29 08:34:03 +0530446 config['Properties']['UserData'] = 'new data'
Angus Salkeld28339012015-01-20 19:15:37 +1000447
448 self.update_instance_group(updt_template,
449 num_updates_expected_on_updt=8,
450 num_creates_expected_on_updt=2,
Rabi Mishra06b11272015-10-14 10:19:22 +0530451 num_deletes_expected_on_updt=2)
Angus Salkeld28339012015-01-20 19:15:37 +1000452
453 def test_instance_group_update_replace_huge_batch_size(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300454 """Test update replace with a huge batch size."""
Angus Salkeld28339012015-01-20 19:15:37 +1000455 updt_template = self.ig_tmpl_with_updt_policy()
456 group = updt_template['Resources']['JobServerGroup']
457 policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
458 policy['MinInstancesInService'] = '0'
459 policy['MaxBatchSize'] = '20'
460 config = updt_template['Resources']['JobServerConfig']
rabic333f762016-09-29 08:34:03 +0530461 config['Properties']['UserData'] = 'new data'
Angus Salkeld28339012015-01-20 19:15:37 +1000462
463 self.update_instance_group(updt_template,
464 num_updates_expected_on_updt=10,
465 num_creates_expected_on_updt=0,
Rabi Mishra06b11272015-10-14 10:19:22 +0530466 num_deletes_expected_on_updt=0)
Angus Salkeld28339012015-01-20 19:15:37 +1000467
468 def test_instance_group_update_replace_huge_min_in_service(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300469 """Update replace with huge number of minimum instances in service."""
Angus Salkeld28339012015-01-20 19:15:37 +1000470 updt_template = self.ig_tmpl_with_updt_policy()
471 group = updt_template['Resources']['JobServerGroup']
472 policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
473 policy['MinInstancesInService'] = '20'
474 policy['MaxBatchSize'] = '1'
475 policy['PauseTime'] = 'PT0S'
476 config = updt_template['Resources']['JobServerConfig']
rabic333f762016-09-29 08:34:03 +0530477 config['Properties']['UserData'] = 'new data'
Angus Salkeld28339012015-01-20 19:15:37 +1000478
479 self.update_instance_group(updt_template,
480 num_updates_expected_on_updt=9,
481 num_creates_expected_on_updt=1,
Rabi Mishra06b11272015-10-14 10:19:22 +0530482 num_deletes_expected_on_updt=1)
Angus Salkeld28339012015-01-20 19:15:37 +1000483
484 def test_instance_group_update_no_replace(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300485 """Test simple update only and no replace.
486
Angus Salkeld28339012015-01-20 19:15:37 +1000487 Test simple update only and no replace (i.e. updated instance flavor
488 in Launch Configuration) with no conflict in batch size and
489 minimum instances in service.
490 """
491 updt_template = self.ig_tmpl_with_updt_policy()
492 group = updt_template['Resources']['JobServerGroup']
493 policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
494 policy['MinInstancesInService'] = '1'
495 policy['MaxBatchSize'] = '3'
496 policy['PauseTime'] = 'PT0S'
497 config = updt_template['Resources']['JobServerConfig']
Amit Ugolfa535cf2016-11-03 13:15:29 +0200498 config['Properties']['InstanceType'] = self.conf.minimal_instance_type
Angus Salkeld28339012015-01-20 19:15:37 +1000499
500 self.update_instance_group(updt_template,
501 num_updates_expected_on_updt=10,
502 num_creates_expected_on_updt=0,
Rabi Mishra06b11272015-10-14 10:19:22 +0530503 num_deletes_expected_on_updt=0)
Angus Salkeld28339012015-01-20 19:15:37 +1000504
505 def test_instance_group_update_no_replace_with_adjusted_capacity(self):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300506 """Test update only and no replace with capacity adjustment.
507
Angus Salkeld28339012015-01-20 19:15:37 +1000508 Test update only and no replace (i.e. updated instance flavor in
509 Launch Configuration) with capacity adjustment due to conflict in
510 batch size and minimum instances in service.
511 """
512 updt_template = self.ig_tmpl_with_updt_policy()
513 group = updt_template['Resources']['JobServerGroup']
514 policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
515 policy['MinInstancesInService'] = '8'
516 policy['MaxBatchSize'] = '4'
517 policy['PauseTime'] = 'PT0S'
518 config = updt_template['Resources']['JobServerConfig']
Amit Ugolfa535cf2016-11-03 13:15:29 +0200519 config['Properties']['InstanceType'] = self.conf.minimal_instance_type
Angus Salkeld28339012015-01-20 19:15:37 +1000520
521 self.update_instance_group(updt_template,
522 num_updates_expected_on_updt=8,
523 num_creates_expected_on_updt=2,
Rabi Mishra06b11272015-10-14 10:19:22 +0530524 num_deletes_expected_on_updt=2)
Angus Salkeldc85229b2015-02-09 10:58:04 +1000525
526
527class AutoScalingSignalTest(AutoscalingGroupTest):
528
529 template = '''
530{
531 "AWSTemplateFormatVersion" : "2010-09-09",
532 "Description" : "Template to create multiple instances.",
533 "Parameters" : {"size": {"Type": "String", "Default": "1"},
534 "AZ": {"Type": "String", "Default": "nova"},
535 "image": {"Type": "String"},
536 "flavor": {"Type": "String"}},
537 "Resources": {
538 "custom_lb": {
539 "Type": "AWS::EC2::Instance",
540 "Properties": {
541 "ImageId": {"Ref": "image"},
542 "InstanceType": {"Ref": "flavor"},
543 "UserData": "foo",
544 "SecurityGroups": [ "sg-1" ],
545 "Tags": []
546 },
547 "Metadata": {
548 "IPs": {"Fn::GetAtt": ["JobServerGroup", "InstanceList"]}
549 }
550 },
551 "JobServerGroup": {
552 "Type" : "AWS::AutoScaling::AutoScalingGroup",
553 "Properties" : {
554 "AvailabilityZones" : [{"Ref": "AZ"}],
555 "LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
556 "DesiredCapacity" : {"Ref": "size"},
557 "MinSize" : "0",
558 "MaxSize" : "20"
559 }
560 },
561 "JobServerConfig" : {
562 "Type" : "AWS::AutoScaling::LaunchConfiguration",
563 "Metadata": {"foo": "bar"},
564 "Properties": {
565 "ImageId" : {"Ref": "image"},
566 "InstanceType" : {"Ref": "flavor"},
567 "SecurityGroups" : [ "sg-1" ],
568 "UserData" : "jsconfig data"
569 }
570 },
571 "ScaleUpPolicy" : {
572 "Type" : "AWS::AutoScaling::ScalingPolicy",
573 "Properties" : {
574 "AdjustmentType" : "ChangeInCapacity",
575 "AutoScalingGroupName" : { "Ref" : "JobServerGroup" },
576 "Cooldown" : "0",
577 "ScalingAdjustment": "1"
578 }
579 },
580 "ScaleDownPolicy" : {
581 "Type" : "AWS::AutoScaling::ScalingPolicy",
582 "Properties" : {
583 "AdjustmentType" : "ChangeInCapacity",
584 "AutoScalingGroupName" : { "Ref" : "JobServerGroup" },
585 "Cooldown" : "0",
586 "ScalingAdjustment" : "-2"
587 }
588 }
589 },
590 "Outputs": {
591 "InstanceList": {"Value": {
592 "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}}
593 }
594}
595'''
596
597 lb_template = '''
598heat_template_version: 2013-05-23
599parameters:
600 ImageId: {type: string}
601 InstanceType: {type: string}
602 SecurityGroups: {type: comma_delimited_list}
603 UserData: {type: string}
Angus Salkeld8d1050c2015-02-24 12:23:06 +1000604 Tags: {type: comma_delimited_list, default: "x,y"}
Angus Salkeldc85229b2015-02-09 10:58:04 +1000605
606resources:
607outputs:
608 PublicIp: {value: "not-used"}
609 AvailabilityZone: {value: 'not-used1'}
610 PrivateDnsName: {value: 'not-used2'}
611 PublicDnsName: {value: 'not-used3'}
612 PrivateIp: {value: 'not-used4'}
613
614'''
615
616 def setUp(self):
617 super(AutoScalingSignalTest, self).setUp()
618 self.build_timeout = self.conf.build_timeout
619 self.build_interval = self.conf.build_interval
620 self.files = {'provider.yaml': self.instance_template,
621 'lb.yaml': self.lb_template}
622 self.env = {'resource_registry':
623 {'resources':
624 {'custom_lb': {'AWS::EC2::Instance': 'lb.yaml'}},
625 'AWS::EC2::Instance': 'provider.yaml'},
626 'parameters': {'size': 2,
rabic333f762016-09-29 08:34:03 +0530627 'image': self.conf.minimal_image_ref,
Angus Salkeldc85229b2015-02-09 10:58:04 +1000628 'flavor': self.conf.instance_type}}
629
630 def check_instance_count(self, stack_identifier, expected):
631 md = self.client.resources.metadata(stack_identifier, 'custom_lb')
632 actual_md = len(md['IPs'].split(','))
633 if actual_md != expected:
LiuNankec7e36ed2015-12-29 12:54:49 +0800634 LOG.warning('check_instance_count exp:%d, meta:%s' % (expected,
635 md['IPs']))
Angus Salkeldc85229b2015-02-09 10:58:04 +1000636 return False
637
638 stack = self.client.stacks.get(stack_identifier)
639 inst_list = self._stack_output(stack, 'InstanceList')
640 actual = len(inst_list.split(','))
641 if actual != expected:
LiuNankec7e36ed2015-12-29 12:54:49 +0800642 LOG.warning('check_instance_count exp:%d, act:%s' % (expected,
643 inst_list))
Angus Salkeldc85229b2015-02-09 10:58:04 +1000644 return actual == expected
645
646 def test_scaling_meta_update(self):
647 """Use heatclient to signal the up and down policy.
648
649 Then confirm that the metadata in the custom_lb is updated each
650 time.
651 """
652 stack_identifier = self.stack_create(template=self.template,
653 files=self.files,
654 environment=self.env)
655
656 self.assertTrue(test.call_until_true(
657 self.build_timeout, self.build_interval,
658 self.check_instance_count, stack_identifier, 2))
659
660 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
661 'JobServerGroup')
662 # Scale up one, Trigger alarm
663 self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
664 self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
665 self.assertTrue(test.call_until_true(
666 self.build_timeout, self.build_interval,
667 self.check_instance_count, stack_identifier, 3))
668
669 # Scale down two, Trigger alarm
670 self.client.resources.signal(stack_identifier, 'ScaleDownPolicy')
671 self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
672 self.assertTrue(test.call_until_true(
673 self.build_timeout, self.build_interval,
674 self.check_instance_count, stack_identifier, 1))
675
676 def test_signal_with_policy_update(self):
677 """Prove that an updated policy is used in the next signal."""
678
679 stack_identifier = self.stack_create(template=self.template,
680 files=self.files,
681 environment=self.env)
682
683 self.assertTrue(test.call_until_true(
684 self.build_timeout, self.build_interval,
685 self.check_instance_count, stack_identifier, 2))
686
687 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
688 'JobServerGroup')
689 # Scale up one, Trigger alarm
690 self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
691 self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
692 self.assertTrue(test.call_until_true(
693 self.build_timeout, self.build_interval,
694 self.check_instance_count, stack_identifier, 3))
695
696 # increase the adjustment to "+2" and remove the DesiredCapacity
697 # so we don't go from 3 to 2.
698 new_template = self.template.replace(
699 '"ScalingAdjustment": "1"',
700 '"ScalingAdjustment": "2"').replace(
701 '"DesiredCapacity" : {"Ref": "size"},', '')
702
703 self.update_stack(stack_identifier, template=new_template,
704 environment=self.env, files=self.files)
705
706 # Scale up two, Trigger alarm
707 self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
708 self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
709 self.assertTrue(test.call_until_true(
710 self.build_timeout, self.build_interval,
711 self.check_instance_count, stack_identifier, 5))
Angus Salkeld2eb7d602015-04-01 11:22:40 +1000712
713 def test_signal_during_suspend(self):
714 """Prove that a signal will fail when the stack is in suspend."""
715
716 stack_identifier = self.stack_create(template=self.template,
717 files=self.files,
718 environment=self.env)
719
720 self.assertTrue(test.call_until_true(
721 self.build_timeout, self.build_interval,
722 self.check_instance_count, stack_identifier, 2))
723
724 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
725 'JobServerGroup')
726
727 # suspend the top level stack.
728 self.client.actions.suspend(stack_id=stack_identifier)
Rabi Mishra647406e2016-06-24 16:15:36 +0530729
730 # Wait for stack to reach SUSPEND_COMPLETE
731 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
Angus Salkeld2eb7d602015-04-01 11:22:40 +1000732
hgangwx398e6bd2015-12-26 18:06:39 +0800733 # Send a signal and an exception will raise
huangtianhuaf71ae072015-07-09 09:15:10 +0800734 ex = self.assertRaises(exc.BadRequest,
735 self.client.resources.signal,
736 stack_identifier, 'ScaleUpPolicy')
737
738 error_msg = 'Signal resource during SUSPEND is not supported'
739 self.assertIn(error_msg, six.text_type(ex))
Angus Salkeldf1324492015-04-02 09:14:39 +1000740 ev = self.wait_for_event_with_reason(
741 stack_identifier,
742 reason='Cannot signal resource during SUSPEND',
743 rsrc_name='ScaleUpPolicy')
744 self.assertEqual('SUSPEND_COMPLETE', ev[0].resource_status)
745
Angus Salkeld2eb7d602015-04-01 11:22:40 +1000746 # still SUSPEND_COMPLETE (not gone to UPDATE_COMPLETE)
747 self._wait_for_stack_status(nested_ident, 'SUSPEND_COMPLETE')
Pavlo Shchelokovskyy28ac2c02015-04-06 10:22:35 +0000748 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
Angus Salkeld2eb7d602015-04-01 11:22:40 +1000749 # still 2 instances.
750 self.assertTrue(test.call_until_true(
751 self.build_timeout, self.build_interval,
752 self.check_instance_count, stack_identifier, 2))