blob: 489ae63eaae16fd80bab6cf8b59109e4735b16e8 [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
Steve Baker24641292015-03-13 10:47:50 +130016from oslo_log import log as logging
Angus Salkeld28339012015-01-20 19:15:37 +100017from testtools import matchers
18
19from heat_integrationtests.common import test
20
21
22LOG = logging.getLogger(__name__)
23
24
25class AutoscalingGroupTest(test.HeatIntegrationTest):
26
27 template = '''
28{
29 "AWSTemplateFormatVersion" : "2010-09-09",
30 "Description" : "Template to create multiple instances.",
31 "Parameters" : {"size": {"Type": "String", "Default": "1"},
32 "AZ": {"Type": "String", "Default": "nova"},
33 "image": {"Type": "String"},
34 "flavor": {"Type": "String"}},
35 "Resources": {
36 "JobServerGroup": {
37 "Type" : "AWS::AutoScaling::AutoScalingGroup",
38 "Properties" : {
39 "AvailabilityZones" : [{"Ref": "AZ"}],
40 "LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
41 "MinSize" : {"Ref": "size"},
42 "MaxSize" : "20"
43 }
44 },
45
46 "JobServerConfig" : {
47 "Type" : "AWS::AutoScaling::LaunchConfiguration",
48 "Metadata": {"foo": "bar"},
49 "Properties": {
50 "ImageId" : {"Ref": "image"},
51 "InstanceType" : {"Ref": "flavor"},
52 "SecurityGroups" : [ "sg-1" ],
53 "UserData" : "jsconfig data"
54 }
55 }
56 },
57 "Outputs": {
58 "InstanceList": {"Value": {
59 "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}},
60 "JobServerConfigRef": {"Value": {
61 "Ref": "JobServerConfig"}}
62 }
63}
64'''
65
66 instance_template = '''
67heat_template_version: 2013-05-23
68parameters:
69 ImageId: {type: string}
70 InstanceType: {type: string}
71 SecurityGroups: {type: comma_delimited_list}
72 UserData: {type: string}
Angus Salkeld8d1050c2015-02-24 12:23:06 +100073 Tags: {type: comma_delimited_list, default: "x,y"}
Angus Salkeld28339012015-01-20 19:15:37 +100074
75resources:
76 random1:
77 type: OS::Heat::RandomString
78 properties:
79 salt: {get_param: ImageId}
80outputs:
Angus Salkeldc85229b2015-02-09 10:58:04 +100081 PublicIp: {value: {get_attr: [random1, value]}}
82 AvailabilityZone: {value: 'not-used11'}
83 PrivateDnsName: {value: 'not-used12'}
84 PublicDnsName: {value: 'not-used13'}
85 PrivateIp: {value: 'not-used14'}
Angus Salkeld28339012015-01-20 19:15:37 +100086'''
87
88 # This is designed to fail.
89 bad_instance_template = '''
90heat_template_version: 2013-05-23
91parameters:
92 ImageId: {type: string}
93 InstanceType: {type: string}
94 SecurityGroups: {type: comma_delimited_list}
95 UserData: {type: string}
Angus Salkeld8d1050c2015-02-24 12:23:06 +100096 Tags: {type: comma_delimited_list, default: "x,y"}
Angus Salkeld28339012015-01-20 19:15:37 +100097
98resources:
99 random1:
100 type: OS::Heat::RandomString
101 depends_on: waiter
102 ready_poster:
103 type: AWS::CloudFormation::WaitConditionHandle
104 waiter:
105 type: AWS::CloudFormation::WaitCondition
106 properties:
107 Handle: {Ref: ready_poster}
108 Timeout: 1
109outputs:
110 PublicIp:
111 value: {get_attr: [random1, value]}
112'''
113
114 def setUp(self):
115 super(AutoscalingGroupTest, self).setUp()
116 self.client = self.orchestration_client
117 if not self.conf.image_ref:
118 raise self.skipException("No image configured to test")
119 if not self.conf.minimal_image_ref:
120 raise self.skipException("No minimal image configured to test")
121 if not self.conf.instance_type:
122 raise self.skipException("No flavor configured to test")
123
124 def assert_instance_count(self, stack, expected_count):
125 inst_list = self._stack_output(stack, 'InstanceList')
126 self.assertEqual(expected_count, len(inst_list.split(',')))
127
128 def _assert_instance_state(self, nested_identifier,
129 num_complete, num_failed):
130 for res in self.client.resources.list(nested_identifier):
131 if 'COMPLETE' in res.resource_status:
132 num_complete = num_complete - 1
133 elif 'FAILED' in res.resource_status:
134 num_failed = num_failed - 1
135 self.assertEqual(0, num_failed)
136 self.assertEqual(0, num_complete)
137
138
139class AutoscalingGroupBasicTest(AutoscalingGroupTest):
140
141 def test_basic_create_works(self):
142 """Make sure the working case is good.
143
144 Note this combines test_override_aws_ec2_instance into this test as
145 well, which is:
146 If AWS::EC2::Instance is overridden, AutoScalingGroup will
147 automatically use that overridden resource type.
148 """
149
150 files = {'provider.yaml': self.instance_template}
151 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
152 'parameters': {'size': 4,
153 'image': self.conf.image_ref,
154 'flavor': self.conf.instance_type}}
155 stack_identifier = self.stack_create(template=self.template,
156 files=files, environment=env)
157 initial_resources = {
158 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
159 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
160 self.assertEqual(initial_resources,
161 self.list_resources(stack_identifier))
162
163 stack = self.client.stacks.get(stack_identifier)
164 self.assert_instance_count(stack, 4)
165
166 def test_size_updates_work(self):
167 files = {'provider.yaml': self.instance_template}
168 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
169 'parameters': {'size': 2,
170 'image': self.conf.image_ref,
171 'flavor': self.conf.instance_type}}
172
173 stack_identifier = self.stack_create(template=self.template,
174 files=files,
175 environment=env)
176 stack = self.client.stacks.get(stack_identifier)
177 self.assert_instance_count(stack, 2)
178
179 # Increase min size to 5
180 env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
181 'parameters': {'size': 5,
182 'image': self.conf.image_ref,
183 'flavor': self.conf.instance_type}}
184 self.update_stack(stack_identifier, self.template,
185 environment=env2, files=files)
186 self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
187 stack = self.client.stacks.get(stack_identifier)
188 self.assert_instance_count(stack, 5)
189
190 def test_update_group_replace(self):
191 """Make sure that during a group update the non updatable
192 properties cause a replacement.
193 """
194 files = {'provider.yaml': self.instance_template}
195 env = {'resource_registry':
196 {'AWS::EC2::Instance': 'provider.yaml'},
197 'parameters': {'size': '1',
198 'image': self.conf.image_ref,
199 'flavor': self.conf.instance_type}}
200
201 stack_identifier = self.stack_create(template=self.template,
202 files=files,
203 environment=env)
204 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
205 orig_asg_id = rsrc.physical_resource_id
206
207 env2 = {'resource_registry':
208 {'AWS::EC2::Instance': 'provider.yaml'},
209 'parameters': {'size': '1',
210 'AZ': 'wibble',
211 'image': self.conf.image_ref,
212 'flavor': self.conf.instance_type}}
213 self.update_stack(stack_identifier, self.template,
214 environment=env2, files=files)
215
216 # replacement will cause the resource physical_resource_id to change.
217 rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
218 self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id)
219
220 def test_create_instance_error_causes_group_error(self):
221 """If a resource in an instance group fails to be created, the instance
222 group itself will fail and the broken inner resource will remain.
223 """
224 stack_name = self._stack_rand_name()
225 files = {'provider.yaml': self.bad_instance_template}
226 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
227 'parameters': {'size': 2,
228 'image': self.conf.image_ref,
229 'flavor': self.conf.instance_type}}
230
231 self.client.stacks.create(
232 stack_name=stack_name,
233 template=self.template,
234 files=files,
235 disable_rollback=True,
236 parameters={},
237 environment=env
238 )
239 self.addCleanup(self.client.stacks.delete, stack_name)
240 stack = self.client.stacks.get(stack_name)
241 stack_identifier = '%s/%s' % (stack_name, stack.id)
242 self._wait_for_stack_status(stack_identifier, 'CREATE_FAILED')
243 initial_resources = {
244 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
245 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
246 self.assertEqual(initial_resources,
247 self.list_resources(stack_identifier))
248
249 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
250 'JobServerGroup')
251 self._assert_instance_state(nested_ident, 0, 2)
252
253 def test_update_instance_error_causes_group_error(self):
254 """If a resource in an instance group fails to be created during an
255 update, the instance group itself will fail and the broken inner
256 resource will remain.
257 """
258 files = {'provider.yaml': self.instance_template}
259 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
260 'parameters': {'size': 2,
261 'image': self.conf.image_ref,
262 'flavor': self.conf.instance_type}}
263
264 stack_identifier = self.stack_create(template=self.template,
265 files=files,
266 environment=env)
267 initial_resources = {
268 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
269 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'}
270 self.assertEqual(initial_resources,
271 self.list_resources(stack_identifier))
272
273 stack = self.client.stacks.get(stack_identifier)
274 self.assert_instance_count(stack, 2)
275 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
276 'JobServerGroup')
277 self._assert_instance_state(nested_ident, 2, 0)
Angus Salkeldd4b6bc02015-02-04 16:48:45 +1000278 initial_list = [res.resource_name
279 for res in self.client.resources.list(nested_ident)]
Angus Salkeld28339012015-01-20 19:15:37 +1000280
281 env['parameters']['size'] = 3
282 files2 = {'provider.yaml': self.bad_instance_template}
283 self.client.stacks.update(
284 stack_id=stack_identifier,
285 template=self.template,
286 files=files2,
287 disable_rollback=True,
288 parameters={},
289 environment=env
290 )
291 self._wait_for_stack_status(stack_identifier, 'UPDATE_FAILED')
292
293 # assert that there are 3 bad instances
294 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
295 'JobServerGroup')
Angus Salkeldd4b6bc02015-02-04 16:48:45 +1000296
297 # 2 resources should be in update failed, and one create failed.
298 for res in self.client.resources.list(nested_ident):
299 if res.resource_name in initial_list:
300 self._wait_for_resource_status(nested_ident,
301 res.resource_name,
302 'UPDATE_FAILED')
303 else:
304 self._wait_for_resource_status(nested_ident,
305 res.resource_name,
306 'CREATE_FAILED')
Angus Salkeld28339012015-01-20 19:15:37 +1000307
Angus Salkeldf1b10dd2015-02-04 10:57:38 +1000308 def test_group_suspend_resume(self):
309
310 files = {'provider.yaml': self.instance_template}
311 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
312 'parameters': {'size': 4,
313 'image': self.conf.image_ref,
314 'flavor': self.conf.instance_type}}
315 stack_identifier = self.stack_create(template=self.template,
316 files=files, environment=env)
317
318 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
319 'JobServerGroup')
320
Angus Salkelda7500d12015-04-10 15:44:07 +1000321 self.stack_suspend(stack_identifier)
322 self._wait_for_all_resource_status(nested_ident, 'SUSPEND_COMPLETE')
Angus Salkeldf1b10dd2015-02-04 10:57:38 +1000323
Angus Salkelda7500d12015-04-10 15:44:07 +1000324 self.stack_resume(stack_identifier)
325 self._wait_for_all_resource_status(nested_ident, 'RESUME_COMPLETE')
Angus Salkeldf1b10dd2015-02-04 10:57:38 +1000326
Angus Salkeld28339012015-01-20 19:15:37 +1000327
328class AutoscalingGroupUpdatePolicyTest(AutoscalingGroupTest):
329
330 def ig_tmpl_with_updt_policy(self):
331 templ = json.loads(copy.deepcopy(self.template))
332 up = {"AutoScalingRollingUpdate": {
333 "MinInstancesInService": "1",
334 "MaxBatchSize": "2",
335 "PauseTime": "PT1S"}}
336 templ['Resources']['JobServerGroup']['UpdatePolicy'] = up
337 return templ
338
339 def update_instance_group(self, updt_template,
340 num_updates_expected_on_updt,
341 num_creates_expected_on_updt,
342 num_deletes_expected_on_updt,
343 update_replace):
344
345 # setup stack from the initial template
346 files = {'provider.yaml': self.instance_template}
347 size = 10
348 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
349 'parameters': {'size': size,
350 'image': self.conf.image_ref,
351 'flavor': self.conf.instance_type}}
352 stack_name = self._stack_rand_name()
353 stack_identifier = self.stack_create(
354 stack_name=stack_name,
355 template=self.ig_tmpl_with_updt_policy(),
356 files=files,
357 environment=env)
358 stack = self.client.stacks.get(stack_identifier)
359 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
360 'JobServerGroup')
361
362 # test that physical resource name of launch configuration is used
363 conf_name = self._stack_output(stack, 'JobServerConfigRef')
364 conf_name_pattern = '%s-JobServerConfig-[a-zA-Z0-9]+$' % stack_name
365 self.assertThat(conf_name,
366 matchers.MatchesRegex(conf_name_pattern))
367
368 # test the number of instances created
369 self.assert_instance_count(stack, size)
370 # saves info from initial list of instances for comparison later
371 init_instances = self.client.resources.list(nested_ident)
372 init_names = [inst.resource_name for inst in init_instances]
373
374 # test stack update
375 self.update_stack(stack_identifier, updt_template,
376 environment=env, files=files)
377 self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
378 updt_stack = self.client.stacks.get(stack_identifier)
379
380 # test that the launch configuration is replaced
381 updt_conf_name = self._stack_output(updt_stack, 'JobServerConfigRef')
382 self.assertThat(updt_conf_name,
383 matchers.MatchesRegex(conf_name_pattern))
384 self.assertNotEqual(conf_name, updt_conf_name)
385
386 # test that the group size are the same
387 updt_instances = self.client.resources.list(nested_ident)
388 updt_names = [inst.resource_name for inst in updt_instances]
389 self.assertEqual(len(init_names), len(updt_names))
390 for res in updt_instances:
391 self.assertEqual('UPDATE_COMPLETE', res.resource_status)
392
393 # test that the appropriate number of instance names are the same
394 matched_names = set(updt_names) & set(init_names)
395 self.assertEqual(num_updates_expected_on_updt, len(matched_names))
396
397 # test that the appropriate number of new instances are created
398 self.assertEqual(num_creates_expected_on_updt,
399 len(set(updt_names) - set(init_names)))
400
401 # test that the appropriate number of instances are deleted
402 self.assertEqual(num_deletes_expected_on_updt,
403 len(set(init_names) - set(updt_names)))
404
405 # test that the older instances are the ones being deleted
406 if num_deletes_expected_on_updt > 0:
407 deletes_expected = init_names[:num_deletes_expected_on_updt]
408 self.assertNotIn(deletes_expected, updt_names)
409
410 def test_instance_group_update_replace(self):
411 """
412 Test simple update replace with no conflict in batch size and
413 minimum instances in service.
414 """
415 updt_template = self.ig_tmpl_with_updt_policy()
416 grp = updt_template['Resources']['JobServerGroup']
417 policy = grp['UpdatePolicy']['AutoScalingRollingUpdate']
418 policy['MinInstancesInService'] = '1'
419 policy['MaxBatchSize'] = '3'
420 config = updt_template['Resources']['JobServerConfig']
421 config['Properties']['ImageId'] = self.conf.minimal_image_ref
422
423 self.update_instance_group(updt_template,
424 num_updates_expected_on_updt=10,
425 num_creates_expected_on_updt=0,
426 num_deletes_expected_on_updt=0,
427 update_replace=True)
428
429 def test_instance_group_update_replace_with_adjusted_capacity(self):
430 """
431 Test update replace with capacity adjustment due to conflict in
432 batch size and minimum instances in service.
433 """
434 updt_template = self.ig_tmpl_with_updt_policy()
435 grp = updt_template['Resources']['JobServerGroup']
436 policy = grp['UpdatePolicy']['AutoScalingRollingUpdate']
437 policy['MinInstancesInService'] = '8'
438 policy['MaxBatchSize'] = '4'
439 config = updt_template['Resources']['JobServerConfig']
440 config['Properties']['ImageId'] = self.conf.minimal_image_ref
441
442 self.update_instance_group(updt_template,
443 num_updates_expected_on_updt=8,
444 num_creates_expected_on_updt=2,
445 num_deletes_expected_on_updt=2,
446 update_replace=True)
447
448 def test_instance_group_update_replace_huge_batch_size(self):
449 """
450 Test update replace with a huge batch size.
451 """
452 updt_template = self.ig_tmpl_with_updt_policy()
453 group = updt_template['Resources']['JobServerGroup']
454 policy = group['UpdatePolicy']['AutoScalingRollingUpdate']
455 policy['MinInstancesInService'] = '0'
456 policy['MaxBatchSize'] = '20'
457 config = updt_template['Resources']['JobServerConfig']
458 config['Properties']['ImageId'] = self.conf.minimal_image_ref
459
460 self.update_instance_group(updt_template,
461 num_updates_expected_on_updt=10,
462 num_creates_expected_on_updt=0,
463 num_deletes_expected_on_updt=0,
464 update_replace=True)
465
466 def test_instance_group_update_replace_huge_min_in_service(self):
467 """
468 Test update replace with a huge number of minimum instances in service.
469 """
470 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']
477 config['Properties']['ImageId'] = self.conf.minimal_image_ref
478
479 self.update_instance_group(updt_template,
480 num_updates_expected_on_updt=9,
481 num_creates_expected_on_updt=1,
482 num_deletes_expected_on_updt=1,
483 update_replace=True)
484
485 def test_instance_group_update_no_replace(self):
486 """
487 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']
498 config['Properties']['InstanceType'] = 'm1.tiny'
499
500 self.update_instance_group(updt_template,
501 num_updates_expected_on_updt=10,
502 num_creates_expected_on_updt=0,
503 num_deletes_expected_on_updt=0,
504 update_replace=False)
505
506 def test_instance_group_update_no_replace_with_adjusted_capacity(self):
507 """
508 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']
519 config['Properties']['InstanceType'] = 'm1.tiny'
520
521 self.update_instance_group(updt_template,
522 num_updates_expected_on_updt=8,
523 num_creates_expected_on_updt=2,
524 num_deletes_expected_on_updt=2,
525 update_replace=False)
Angus Salkeldc85229b2015-02-09 10:58:04 +1000526
527
528class AutoScalingSignalTest(AutoscalingGroupTest):
529
530 template = '''
531{
532 "AWSTemplateFormatVersion" : "2010-09-09",
533 "Description" : "Template to create multiple instances.",
534 "Parameters" : {"size": {"Type": "String", "Default": "1"},
535 "AZ": {"Type": "String", "Default": "nova"},
536 "image": {"Type": "String"},
537 "flavor": {"Type": "String"}},
538 "Resources": {
539 "custom_lb": {
540 "Type": "AWS::EC2::Instance",
541 "Properties": {
542 "ImageId": {"Ref": "image"},
543 "InstanceType": {"Ref": "flavor"},
544 "UserData": "foo",
545 "SecurityGroups": [ "sg-1" ],
546 "Tags": []
547 },
548 "Metadata": {
549 "IPs": {"Fn::GetAtt": ["JobServerGroup", "InstanceList"]}
550 }
551 },
552 "JobServerGroup": {
553 "Type" : "AWS::AutoScaling::AutoScalingGroup",
554 "Properties" : {
555 "AvailabilityZones" : [{"Ref": "AZ"}],
556 "LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
557 "DesiredCapacity" : {"Ref": "size"},
558 "MinSize" : "0",
559 "MaxSize" : "20"
560 }
561 },
562 "JobServerConfig" : {
563 "Type" : "AWS::AutoScaling::LaunchConfiguration",
564 "Metadata": {"foo": "bar"},
565 "Properties": {
566 "ImageId" : {"Ref": "image"},
567 "InstanceType" : {"Ref": "flavor"},
568 "SecurityGroups" : [ "sg-1" ],
569 "UserData" : "jsconfig data"
570 }
571 },
572 "ScaleUpPolicy" : {
573 "Type" : "AWS::AutoScaling::ScalingPolicy",
574 "Properties" : {
575 "AdjustmentType" : "ChangeInCapacity",
576 "AutoScalingGroupName" : { "Ref" : "JobServerGroup" },
577 "Cooldown" : "0",
578 "ScalingAdjustment": "1"
579 }
580 },
581 "ScaleDownPolicy" : {
582 "Type" : "AWS::AutoScaling::ScalingPolicy",
583 "Properties" : {
584 "AdjustmentType" : "ChangeInCapacity",
585 "AutoScalingGroupName" : { "Ref" : "JobServerGroup" },
586 "Cooldown" : "0",
587 "ScalingAdjustment" : "-2"
588 }
589 }
590 },
591 "Outputs": {
592 "InstanceList": {"Value": {
593 "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}}
594 }
595}
596'''
597
598 lb_template = '''
599heat_template_version: 2013-05-23
600parameters:
601 ImageId: {type: string}
602 InstanceType: {type: string}
603 SecurityGroups: {type: comma_delimited_list}
604 UserData: {type: string}
Angus Salkeld8d1050c2015-02-24 12:23:06 +1000605 Tags: {type: comma_delimited_list, default: "x,y"}
Angus Salkeldc85229b2015-02-09 10:58:04 +1000606
607resources:
608outputs:
609 PublicIp: {value: "not-used"}
610 AvailabilityZone: {value: 'not-used1'}
611 PrivateDnsName: {value: 'not-used2'}
612 PublicDnsName: {value: 'not-used3'}
613 PrivateIp: {value: 'not-used4'}
614
615'''
616
617 def setUp(self):
618 super(AutoScalingSignalTest, self).setUp()
619 self.build_timeout = self.conf.build_timeout
620 self.build_interval = self.conf.build_interval
621 self.files = {'provider.yaml': self.instance_template,
622 'lb.yaml': self.lb_template}
623 self.env = {'resource_registry':
624 {'resources':
625 {'custom_lb': {'AWS::EC2::Instance': 'lb.yaml'}},
626 'AWS::EC2::Instance': 'provider.yaml'},
627 'parameters': {'size': 2,
628 'image': self.conf.image_ref,
629 'flavor': self.conf.instance_type}}
630
631 def check_instance_count(self, stack_identifier, expected):
632 md = self.client.resources.metadata(stack_identifier, 'custom_lb')
633 actual_md = len(md['IPs'].split(','))
634 if actual_md != expected:
635 LOG.warn('check_instance_count exp:%d, meta:%s' % (expected,
636 md['IPs']))
637 return False
638
639 stack = self.client.stacks.get(stack_identifier)
640 inst_list = self._stack_output(stack, 'InstanceList')
641 actual = len(inst_list.split(','))
642 if actual != expected:
643 LOG.warn('check_instance_count exp:%d, act:%s' % (expected,
644 inst_list))
645 return actual == expected
646
647 def test_scaling_meta_update(self):
648 """Use heatclient to signal the up and down policy.
649
650 Then confirm that the metadata in the custom_lb is updated each
651 time.
652 """
653 stack_identifier = self.stack_create(template=self.template,
654 files=self.files,
655 environment=self.env)
656
657 self.assertTrue(test.call_until_true(
658 self.build_timeout, self.build_interval,
659 self.check_instance_count, stack_identifier, 2))
660
661 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
662 'JobServerGroup')
663 # Scale up one, Trigger alarm
664 self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
665 self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
666 self.assertTrue(test.call_until_true(
667 self.build_timeout, self.build_interval,
668 self.check_instance_count, stack_identifier, 3))
669
670 # Scale down two, Trigger alarm
671 self.client.resources.signal(stack_identifier, 'ScaleDownPolicy')
672 self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
673 self.assertTrue(test.call_until_true(
674 self.build_timeout, self.build_interval,
675 self.check_instance_count, stack_identifier, 1))
676
677 def test_signal_with_policy_update(self):
678 """Prove that an updated policy is used in the next signal."""
679
680 stack_identifier = self.stack_create(template=self.template,
681 files=self.files,
682 environment=self.env)
683
684 self.assertTrue(test.call_until_true(
685 self.build_timeout, self.build_interval,
686 self.check_instance_count, stack_identifier, 2))
687
688 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
689 'JobServerGroup')
690 # Scale up one, Trigger alarm
691 self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
692 self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
693 self.assertTrue(test.call_until_true(
694 self.build_timeout, self.build_interval,
695 self.check_instance_count, stack_identifier, 3))
696
697 # increase the adjustment to "+2" and remove the DesiredCapacity
698 # so we don't go from 3 to 2.
699 new_template = self.template.replace(
700 '"ScalingAdjustment": "1"',
701 '"ScalingAdjustment": "2"').replace(
702 '"DesiredCapacity" : {"Ref": "size"},', '')
703
704 self.update_stack(stack_identifier, template=new_template,
705 environment=self.env, files=self.files)
706
707 # Scale up two, Trigger alarm
708 self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
709 self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE')
710 self.assertTrue(test.call_until_true(
711 self.build_timeout, self.build_interval,
712 self.check_instance_count, stack_identifier, 5))
Angus Salkeld2eb7d602015-04-01 11:22:40 +1000713
714 def test_signal_during_suspend(self):
715 """Prove that a signal will fail when the stack is in suspend."""
716
717 stack_identifier = self.stack_create(template=self.template,
718 files=self.files,
719 environment=self.env)
720
721 self.assertTrue(test.call_until_true(
722 self.build_timeout, self.build_interval,
723 self.check_instance_count, stack_identifier, 2))
724
725 nested_ident = self.assert_resource_is_a_stack(stack_identifier,
726 'JobServerGroup')
727
728 # suspend the top level stack.
729 self.client.actions.suspend(stack_id=stack_identifier)
730 self._wait_for_resource_status(
731 stack_identifier, 'JobServerGroup', 'SUSPEND_COMPLETE')
732
733 # Send a signal and confirm nothing happened.
734 self.client.resources.signal(stack_identifier, 'ScaleUpPolicy')
Angus Salkeldf1324492015-04-02 09:14:39 +1000735 ev = self.wait_for_event_with_reason(
736 stack_identifier,
737 reason='Cannot signal resource during SUSPEND',
738 rsrc_name='ScaleUpPolicy')
739 self.assertEqual('SUSPEND_COMPLETE', ev[0].resource_status)
740
Angus Salkeld2eb7d602015-04-01 11:22:40 +1000741 # still SUSPEND_COMPLETE (not gone to UPDATE_COMPLETE)
742 self._wait_for_stack_status(nested_ident, 'SUSPEND_COMPLETE')
Pavlo Shchelokovskyy28ac2c02015-04-06 10:22:35 +0000743 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
Angus Salkeld2eb7d602015-04-01 11:22:40 +1000744 # still 2 instances.
745 self.assertTrue(test.call_until_true(
746 self.build_timeout, self.build_interval,
747 self.check_instance_count, stack_identifier, 2))