# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import json import six import mox from oslo.config import cfg from testtools.matchers import MatchesRegex from heat.common import exception from heat.common import template_format from heat.engine.clients.os import nova from heat.engine import function from heat.engine.notification import stack as notification from heat.engine import parser from heat.engine.resources import instance from heat.engine.resources import loadbalancer as lb from heat.engine.resources import wait_condition as wc from heat.tests.common import HeatTestCase from heat.tests import utils from heat.tests.v1_1 import fakes as fakes11 asg_tmpl_without_updt_policy = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template to create autoscaling group.", "Parameters" : {}, "Resources" : { "WebServerGroup" : { "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "AvailabilityZones" : ["nova"], "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, "MinSize" : "10", "MaxSize" : "20", "LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancer" } ] } }, "ElasticLoadBalancer" : { "Type" : "AWS::ElasticLoadBalancing::LoadBalancer", "Properties" : { "AvailabilityZones" : ["nova"], "Listeners" : [ { "LoadBalancerPort" : "80", "InstancePort" : "80", "Protocol" : "HTTP" }] } }, "LaunchConfig" : { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId" : "F20-x86_64-cfntools", "InstanceType" : "m1.medium", "KeyName" : "test", "SecurityGroups" : [ "sg-1" ], "UserData" : "jsconfig data" } } } } ''' asg_tmpl_with_bad_updt_policy = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template to create autoscaling group.", "Parameters" : {}, "Resources" : { "WebServerGroup" : { "UpdatePolicy": { "foo": { } }, "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "AvailabilityZones" : ["nova"], "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, "MinSize" : "10", "MaxSize" : "20" } }, "LaunchConfig" : { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId" : "F20-x86_64-cfntools", "InstanceType" : "m1.medium", "KeyName" : "test", "SecurityGroups" : [ "sg-1" ], "UserData" : "jsconfig data" } } } } ''' asg_tmpl_with_default_updt_policy = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template to create autoscaling group.", "Parameters" : {}, "Resources" : { "WebServerGroup" : { "UpdatePolicy" : { "AutoScalingRollingUpdate" : { } }, "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "AvailabilityZones" : ["nova"], "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, "MinSize" : "10", "MaxSize" : "20", "LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancer" } ] } }, "ElasticLoadBalancer" : { "Type" : "AWS::ElasticLoadBalancing::LoadBalancer", "Properties" : { "AvailabilityZones" : ["nova"], "Listeners" : [ { "LoadBalancerPort" : "80", "InstancePort" : "80", "Protocol" : "HTTP" }] } }, "LaunchConfig" : { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId" : "F20-x86_64-cfntools", "InstanceType" : "m1.medium", "KeyName" : "test", "SecurityGroups" : [ "sg-1" ], "UserData" : "jsconfig data" } } } } ''' asg_tmpl_with_updt_policy = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template to create autoscaling group.", "Parameters" : {}, "Resources" : { "WebServerGroup" : { "UpdatePolicy" : { "AutoScalingRollingUpdate" : { "MinInstancesInService" : "1", "MaxBatchSize" : "2", "PauseTime" : "PT1S" } }, "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "AvailabilityZones" : ["nova"], "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, "MinSize" : "10", "MaxSize" : "20", "LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancer" } ] } }, "ElasticLoadBalancer" : { "Type" : "AWS::ElasticLoadBalancing::LoadBalancer", "Properties" : { "AvailabilityZones" : ["nova"], "Listeners" : [ { "LoadBalancerPort" : "80", "InstancePort" : "80", "Protocol" : "HTTP" }] } }, "LaunchConfig" : { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId" : "F20-x86_64-cfntools", "InstanceType" : "m1.medium", "KeyName" : "test", "SecurityGroups" : [ "sg-1" ], "UserData" : "jsconfig data" } } } } ''' class AutoScalingGroupTest(HeatTestCase): def setUp(self): super(AutoScalingGroupTest, self).setUp() self.fc = fakes11.FakeClient() self.stub_keystoneclient(username='test_stack.CfnLBUser') cfg.CONF.set_default('heat_waitcondition_server_url', 'http://127.0.0.1:8000/v1/waitcondition') def _stub_validate(self): self.m.StubOutWithMock(parser.Stack, 'validate') parser.Stack.validate().MultipleTimes() def _stub_lb_create(self): self.m.StubOutWithMock(wc.WaitConditionHandle, 'get_status') wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS']) def _stub_lb_reload(self, num=1, setup=True): if setup: self.m.StubOutWithMock(lb.LoadBalancer, 'handle_update') for i in range(num): lb.LoadBalancer.handle_update( mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(None) def _stub_grp_create(self, capacity=0, setup_lb=True): """ Expect creation of instances to capacity. By default, expect creation of load balancer unless specified. """ self._stub_validate() self.m.StubOutWithMock(instance.Instance, 'handle_create') self.m.StubOutWithMock(instance.Instance, 'check_create_complete') self.m.StubOutWithMock(notification, 'send') notification.send(mox.IgnoreArg()).MultipleTimes().AndReturn(None) cookie = object() self.m.StubOutWithMock(nova.NovaClientPlugin, '_create') nova.NovaClientPlugin._create().AndReturn(self.fc) # for load balancer setup if setup_lb: self._stub_lb_create() self._stub_lb_reload() instance.Instance.handle_create().AndReturn(cookie) instance.Instance.check_create_complete(cookie).AndReturn(True) # for each instance in group for i in range(capacity): instance.Instance.handle_create().AndReturn(cookie) instance.Instance.check_create_complete(cookie).AndReturn(True) def _stub_grp_replace(self, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, num_reloads_expected_on_updt=0): """ Expect replacement of the capacity by batch size """ # for load balancer setup self._stub_lb_reload(num_reloads_expected_on_updt) self.m.StubOutWithMock(notification, 'send') notification.send(mox.IgnoreArg()).MultipleTimes().AndReturn(None) # for instances in the group self.m.StubOutWithMock(instance.Instance, 'handle_create') self.m.StubOutWithMock(instance.Instance, 'check_create_complete') self.m.StubOutWithMock(instance.Instance, 'destroy') cookie = object() for i in range(num_creates_expected_on_updt): instance.Instance.handle_create().AndReturn(cookie) instance.Instance.check_create_complete(cookie).AndReturn(True) for i in range(num_deletes_expected_on_updt): instance.Instance.destroy().AndReturn(None) def _stub_grp_update(self, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, num_reloads_expected_on_updt=0): """ Expect update of the instances """ def activate_status(server): server.status = 'VERIFY_RESIZE' return_server = self.fc.servers.list()[1] return_server.id = '1234' return_server.get = activate_status.__get__(return_server) self.m.StubOutWithMock(self.fc.servers, 'get') self.m.StubOutWithMock(self.fc.client, 'post_servers_1234_action') self.fc.servers.get(mox.IgnoreArg()).\ MultipleTimes().AndReturn(return_server) self.fc.client.post_servers_1234_action( body={'resize': {'flavorRef': 3}}).\ MultipleTimes().AndReturn((202, None)) self.fc.client.post_servers_1234_action( body={'confirmResize': None}).\ MultipleTimes().AndReturn((202, None)) self._stub_grp_replace(num_creates_expected_on_updt, num_deletes_expected_on_updt, num_reloads_expected_on_updt) def get_launch_conf_name(self, stack, ig_name): return stack[ig_name].properties['LaunchConfigurationName'] def test_parse_without_update_policy(self): tmpl = template_format.parse(asg_tmpl_without_updt_policy) stack = utils.parse_stack(tmpl) self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.m.ReplayAll() stack.validate() grp = stack['WebServerGroup'] self.assertFalse(grp.update_policy['AutoScalingRollingUpdate']) self.m.VerifyAll() def test_parse_with_update_policy(self): tmpl = template_format.parse(asg_tmpl_with_updt_policy) stack = utils.parse_stack(tmpl) self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.m.ReplayAll() stack.validate() tmpl_grp = tmpl['Resources']['WebServerGroup'] tmpl_policy = tmpl_grp['UpdatePolicy']['AutoScalingRollingUpdate'] tmpl_batch_sz = int(tmpl_policy['MaxBatchSize']) grp = stack['WebServerGroup'] self.assertTrue(grp.update_policy) self.assertEqual(1, len(grp.update_policy)) self.assertIn('AutoScalingRollingUpdate', grp.update_policy) policy = grp.update_policy['AutoScalingRollingUpdate'] self.assertTrue(policy and len(policy) > 0) self.assertEqual(1, int(policy['MinInstancesInService'])) self.assertEqual(tmpl_batch_sz, int(policy['MaxBatchSize'])) self.assertEqual('PT1S', policy['PauseTime']) self.m.VerifyAll() def test_parse_with_default_update_policy(self): tmpl = template_format.parse(asg_tmpl_with_default_updt_policy) stack = utils.parse_stack(tmpl) self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.m.ReplayAll() stack.validate() grp = stack['WebServerGroup'] self.assertTrue(grp.update_policy) self.assertEqual(1, len(grp.update_policy)) self.assertIn('AutoScalingRollingUpdate', grp.update_policy) policy = grp.update_policy['AutoScalingRollingUpdate'] self.assertTrue(policy and len(policy) > 0) self.assertEqual(0, int(policy['MinInstancesInService'])) self.assertEqual(1, int(policy['MaxBatchSize'])) self.assertEqual('PT0S', policy['PauseTime']) self.m.VerifyAll() def test_parse_with_bad_update_policy(self): self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.m.ReplayAll() tmpl = template_format.parse(asg_tmpl_with_bad_updt_policy) stack = utils.parse_stack(tmpl) error = self.assertRaises( exception.StackValidationFailed, stack.validate) self.assertIn("foo", six.text_type(error)) def test_parse_with_bad_pausetime_in_update_policy(self): self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.m.ReplayAll() tmpl = template_format.parse(asg_tmpl_with_default_updt_policy) group = tmpl['Resources']['WebServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['PauseTime'] = 'P1YT1H' stack = utils.parse_stack(tmpl) error = self.assertRaises( exception.StackValidationFailed, stack.validate) self.assertIn("Only ISO 8601 duration format", six.text_type(error)) def validate_update_policy_diff(self, current, updated): # load current stack current_tmpl = template_format.parse(current) current_stack = utils.parse_stack(current_tmpl) # get the json snippet for the current InstanceGroup resource current_grp = current_stack['WebServerGroup'] current_snippets = dict((n, r.parsed_template()) for n, r in current_stack.items()) current_grp_json = current_snippets[current_grp.name] # load the updated stack updated_tmpl = template_format.parse(updated) updated_stack = utils.parse_stack(updated_tmpl) # get the updated json snippet for the InstanceGroup resource in the # context of the current stack updated_grp = updated_stack['WebServerGroup'] updated_grp_json = function.resolve(updated_grp.t) # identify the template difference tmpl_diff = updated_grp.update_template_diff( updated_grp_json, current_grp_json) updated_policy = (updated_grp.t['UpdatePolicy'] if 'UpdatePolicy' in updated_grp.t else None) expected = {u'UpdatePolicy': updated_policy} self.assertEqual(expected, tmpl_diff) def test_update_policy_added(self): self.validate_update_policy_diff(asg_tmpl_without_updt_policy, asg_tmpl_with_updt_policy) def test_update_policy_updated(self): updt_template = json.loads(asg_tmpl_with_updt_policy) grp = updt_template['Resources']['WebServerGroup'] policy = grp['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '2' policy['MaxBatchSize'] = '4' policy['PauseTime'] = 'PT1M30S' self.validate_update_policy_diff(asg_tmpl_with_updt_policy, json.dumps(updt_template)) def test_update_policy_removed(self): self.validate_update_policy_diff(asg_tmpl_with_updt_policy, asg_tmpl_without_updt_policy) def update_autoscaling_group(self, init_template, updt_template, num_updates_expected_on_updt, num_creates_expected_on_updt, num_deletes_expected_on_updt, num_reloads_expected_on_updt, update_replace, update_image_id=None): # setup stack from the initial template tmpl = template_format.parse(init_template) stack = utils.parse_stack(tmpl) self.stub_KeypairConstraint_validate() self.stub_ImageConstraint_validate() self.m.ReplayAll() stack.validate() self.m.VerifyAll() self.m.UnsetStubs() # test stack create size = int(stack['WebServerGroup'].properties['MinSize']) self._stub_grp_create(size) self.stub_ImageConstraint_validate() self.m.ReplayAll() stack.create() self.m.VerifyAll() self.assertEqual(('CREATE', 'COMPLETE'), stack.state) # test that update policy is loaded current_grp = stack['WebServerGroup'] self.assertTrue('AutoScalingRollingUpdate' in current_grp.update_policy) current_policy = current_grp.update_policy['AutoScalingRollingUpdate'] self.assertTrue(current_policy) self.assertTrue(len(current_policy) > 0) init_updt_policy = tmpl['Resources']['WebServerGroup']['UpdatePolicy'] init_roll_updt = init_updt_policy['AutoScalingRollingUpdate'] init_batch_sz = int(init_roll_updt['MaxBatchSize']) self.assertEqual(init_batch_sz, int(current_policy['MaxBatchSize'])) # test that physical resource name of launch configuration is used conf = stack['LaunchConfig'] conf_name_pattern = '%s-LaunchConfig-[a-zA-Z0-9]+$' % stack.name self.assertThat(conf.FnGetRefId(), MatchesRegex(conf_name_pattern)) # get launch conf name here to compare result after update conf_name = self.get_launch_conf_name(stack, 'WebServerGroup') # test the number of instances created nested = stack['WebServerGroup'].nested() self.assertEqual(size, len(nested.resources)) # clean up for next test self.m.UnsetStubs() # saves info from initial list of instances for comparison later init_instances = current_grp.get_instances() init_names = current_grp.get_instance_names() init_images = [(i.name, i.t['Properties']['ImageId']) for i in init_instances] init_flavors = [(i.name, i.t['Properties']['InstanceType']) for i in init_instances] # test stack update updated_tmpl = template_format.parse(updt_template) updated_stack = utils.parse_stack(updated_tmpl) new_grp_tmpl = updated_tmpl['Resources']['WebServerGroup'] new_updt_pol = new_grp_tmpl['UpdatePolicy']['AutoScalingRollingUpdate'] new_batch_sz = int(new_updt_pol['MaxBatchSize']) self.assertNotEqual(new_batch_sz, init_batch_sz) if update_replace: self._stub_grp_replace(size, size, num_reloads_expected_on_updt) else: self._stub_grp_update(num_creates_expected_on_updt, num_deletes_expected_on_updt, num_reloads_expected_on_updt) self.stub_wallclock() self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.m.ReplayAll() stack.validate() stack.update(updated_stack) self.m.VerifyAll() self.assertEqual(('UPDATE', 'COMPLETE'), stack.state) # test that the update policy is updated updated_grp = stack['WebServerGroup'] updt_instances = updated_grp.get_instances() self.assertTrue('AutoScalingRollingUpdate' in updated_grp.update_policy) updated_policy = updated_grp.update_policy['AutoScalingRollingUpdate'] self.assertTrue(updated_policy) self.assertTrue(len(updated_policy) > 0) self.assertEqual(new_batch_sz, int(updated_policy['MaxBatchSize'])) # test that the launch configuration is replaced updated_conf_name = self.get_launch_conf_name(stack, 'WebServerGroup') self.assertNotEqual(conf_name, updated_conf_name) # test that the group size are the same updt_instances = updated_grp.get_instances() updt_names = updated_grp.get_instance_names() self.assertEqual(len(init_names), len(updt_names)) # test that appropriate number of instance names are the same matched_names = set(updt_names) & set(init_names) self.assertEqual(num_updates_expected_on_updt, len(matched_names)) # test that the appropriate number of new instances are created self.assertEqual(num_creates_expected_on_updt, len(set(updt_names) - set(init_names))) # test that the appropriate number of instances are deleted self.assertEqual(num_deletes_expected_on_updt, len(set(init_names) - set(updt_names))) # test that the older instances are the ones being deleted if num_deletes_expected_on_updt > 0: deletes_expected = init_names[:num_deletes_expected_on_updt] self.assertNotIn(deletes_expected, updt_names) # test if instances are updated if update_replace: # test that the image id is changed for all instances updt_images = [(i.name, i.t['Properties']['ImageId']) for i in updt_instances] self.assertEqual(0, len(set(updt_images) & set(init_images))) else: # test that instance type is changed for all instances updt_flavors = [(i.name, i.t['Properties']['InstanceType']) for i in updt_instances] self.assertEqual(0, len(set(updt_flavors) & set(init_flavors))) def test_autoscaling_group_update_replace(self): """ Test simple update replace with no conflict in batch size and minimum instances in service. """ updt_template = json.loads(asg_tmpl_with_updt_policy) grp = updt_template['Resources']['WebServerGroup'] policy = grp['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '1' policy['MaxBatchSize'] = '3' config = updt_template['Resources']['LaunchConfig'] update_image = 'F17-x86_64-cfntools' config['Properties']['ImageId'] = update_image self.update_autoscaling_group(asg_tmpl_with_updt_policy, json.dumps(updt_template), num_updates_expected_on_updt=10, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, num_reloads_expected_on_updt=9, update_replace=True, update_image_id=update_image) def test_autoscaling_group_update_replace_with_adjusted_capacity(self): """ Test update replace with capacity adjustment due to conflict in batch size and minimum instances in service. """ updt_template = json.loads(asg_tmpl_with_updt_policy) grp = updt_template['Resources']['WebServerGroup'] policy = grp['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '8' policy['MaxBatchSize'] = '4' config = updt_template['Resources']['LaunchConfig'] update_image = 'F17-x86_64-cfntools' config['Properties']['ImageId'] = update_image self.update_autoscaling_group(asg_tmpl_with_updt_policy, json.dumps(updt_template), num_updates_expected_on_updt=8, num_creates_expected_on_updt=2, num_deletes_expected_on_updt=2, num_reloads_expected_on_updt=7, update_replace=True, update_image_id=update_image) def test_autoscaling_group_update_replace_huge_batch_size(self): """ Test update replace with a huge batch size. """ updt_template = json.loads(asg_tmpl_with_updt_policy) group = updt_template['Resources']['WebServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '0' policy['MaxBatchSize'] = '20' config = updt_template['Resources']['LaunchConfig'] update_image = 'F17-x86_64-cfntools' config['Properties']['ImageId'] = update_image self.update_autoscaling_group(asg_tmpl_with_updt_policy, json.dumps(updt_template), num_updates_expected_on_updt=10, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, num_reloads_expected_on_updt=3, update_replace=True, update_image_id=update_image) def test_autoscaling_group_update_replace_huge_min_in_service(self): """ Test update replace with a huge number of minimum instances in service. """ updt_template = json.loads(asg_tmpl_with_updt_policy) group = updt_template['Resources']['WebServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '20' policy['MaxBatchSize'] = '1' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['LaunchConfig'] update_image = 'F17-x86_64-cfntools' config['Properties']['ImageId'] = 'F17-x86_64-cfntools' self.update_autoscaling_group(asg_tmpl_with_updt_policy, json.dumps(updt_template), num_updates_expected_on_updt=9, num_creates_expected_on_updt=1, num_deletes_expected_on_updt=1, num_reloads_expected_on_updt=12, update_replace=True, update_image_id=update_image) def test_autoscaling_group_update_no_replace(self): """ Test simple update only and no replace (i.e. updated instance flavor in Launch Configuration) with no conflict in batch size and minimum instances in service. """ updt_template = json.loads(copy.deepcopy(asg_tmpl_with_updt_policy)) group = updt_template['Resources']['WebServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '1' policy['MaxBatchSize'] = '3' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['LaunchConfig'] config['Properties']['InstanceType'] = 'm1.large' self.update_autoscaling_group(asg_tmpl_with_updt_policy, json.dumps(updt_template), num_updates_expected_on_updt=10, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, num_reloads_expected_on_updt=6, update_replace=False) def test_instance_group_update_no_replace_with_adjusted_capacity(self): """ Test update only and no replace (i.e. updated instance flavor in Launch Configuration) with capacity adjustment due to conflict in batch size and minimum instances in service. """ updt_template = json.loads(copy.deepcopy(asg_tmpl_with_updt_policy)) group = updt_template['Resources']['WebServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '8' policy['MaxBatchSize'] = '4' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['LaunchConfig'] config['Properties']['InstanceType'] = 'm1.large' self.update_autoscaling_group(asg_tmpl_with_updt_policy, json.dumps(updt_template), num_updates_expected_on_updt=8, num_creates_expected_on_updt=2, num_deletes_expected_on_updt=2, num_reloads_expected_on_updt=5, update_replace=False) def test_autoscaling_group_update_policy_removed(self): # setup stack from the initial template tmpl = template_format.parse(asg_tmpl_with_updt_policy) stack = utils.parse_stack(tmpl) self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.m.ReplayAll() stack.validate() self.m.VerifyAll() self.m.UnsetStubs() # test stack create size = int(stack['WebServerGroup'].properties['MinSize']) self._stub_grp_create(size) self.stub_ImageConstraint_validate() self.m.ReplayAll() stack.create() self.m.VerifyAll() self.assertEqual(('CREATE', 'COMPLETE'), stack.state) # test that update policy is loaded current_grp = stack['WebServerGroup'] self.assertIn('AutoScalingRollingUpdate', current_grp.update_policy) current_policy = current_grp.update_policy['AutoScalingRollingUpdate'] self.assertTrue(current_policy) self.assertTrue(len(current_policy) > 0) init_updt_policy = tmpl['Resources']['WebServerGroup']['UpdatePolicy'] init_roll_updt = init_updt_policy['AutoScalingRollingUpdate'] init_batch_sz = int(init_roll_updt['MaxBatchSize']) self.assertEqual(init_batch_sz, int(current_policy['MaxBatchSize'])) # test that physical resource name of launch configuration is used conf = stack['LaunchConfig'] conf_name_pattern = '%s-LaunchConfig-[a-zA-Z0-9]+$' % stack.name self.assertThat(conf.FnGetRefId(), MatchesRegex(conf_name_pattern)) # test the number of instances created nested = stack['WebServerGroup'].nested() self.assertEqual(size, len(nested.resources)) # clean up for next test self.m.UnsetStubs() # test stack update updated_tmpl = template_format.parse(asg_tmpl_without_updt_policy) updated_stack = utils.parse_stack(updated_tmpl) self._stub_grp_replace(num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, num_reloads_expected_on_updt=1) self.m.ReplayAll() stack.update(updated_stack) self.m.VerifyAll() self.assertEqual(('UPDATE', 'COMPLETE'), stack.state) # test that update policy is removed updated_grp = stack['WebServerGroup'] self.assertFalse(updated_grp.update_policy['AutoScalingRollingUpdate']) def test_autoscaling_group_update_policy_check_timeout(self): # setup stack from the initial template tmpl = template_format.parse(asg_tmpl_with_updt_policy) stack = utils.parse_stack(tmpl) # test stack create size = int(stack['WebServerGroup'].properties['MinSize']) self._stub_grp_create(size) self.stub_ImageConstraint_validate() self.m.ReplayAll() stack.create() self.m.VerifyAll() self.assertEqual(('CREATE', 'COMPLETE'), stack.state) # test that update policy is loaded current_grp = stack['WebServerGroup'] self.assertIn('AutoScalingRollingUpdate', current_grp.update_policy) current_policy = current_grp.update_policy['AutoScalingRollingUpdate'] self.assertTrue(current_policy) self.assertTrue(len(current_policy) > 0) init_updt_policy = tmpl['Resources']['WebServerGroup']['UpdatePolicy'] init_roll_updt = init_updt_policy['AutoScalingRollingUpdate'] init_batch_sz = int(init_roll_updt['MaxBatchSize']) self.assertEqual(init_batch_sz, int(current_policy['MaxBatchSize'])) # test the number of instances created nested = stack['WebServerGroup'].nested() self.assertEqual(size, len(nested.resources)) # clean up for next test self.m.UnsetStubs() # modify the pause time and test for error new_pause_time = 'PT30M' updt_template = json.loads(copy.deepcopy(asg_tmpl_with_updt_policy)) group = updt_template['Resources']['WebServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['PauseTime'] = new_pause_time config = updt_template['Resources']['LaunchConfig'] config['Properties']['ImageId'] = 'F17-x86_64-cfntools' updated_tmpl = template_format.parse(json.dumps(updt_template)) updated_stack = utils.parse_stack(updated_tmpl) self._stub_grp_replace(num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, num_reloads_expected_on_updt=1) self.stub_KeypairConstraint_validate() self.stub_ImageConstraint_validate() self.m.ReplayAll() stack.update(updated_stack) self.m.VerifyAll() self.assertEqual(('UPDATE', 'FAILED'), stack.state) # test that the update policy is updated updated_grp = stack['WebServerGroup'] self.assertIn('AutoScalingRollingUpdate', updated_grp.update_policy) updated_policy = updated_grp.update_policy['AutoScalingRollingUpdate'] self.assertTrue(updated_policy) self.assertTrue(len(updated_policy) > 0) self.assertEqual(new_pause_time, updated_policy['PauseTime']) # test that error message match expected_error_message = ('The current UpdatePolicy will result ' 'in stack update timeout.') self.assertIn(expected_error_message, stack.status_reason)