From: Steven Hardy Date: Mon, 4 Feb 2013 18:03:52 +0000 (+0000) Subject: heat engine : AutoScalingGroup UpdateStack support X-Git-Tag: 2014.1~938 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=89ad44e0ce85ed26d446234a155acb8d62af6a34;p=openstack-build%2Fheat-build.git heat engine : AutoScalingGroup UpdateStack support Adds improved UpdateStack support for AutoScalingGroup, now the following properties can be updated without replacement: 'MaxSize', 'MinSize', 'Cooldown', 'DesiredCapacity' Change-Id: Ic47b4a2456dc19cd91eec7b0381d5d82fcd3f332 Signed-off-by: Steven Hardy --- diff --git a/heat/engine/resources/autoscaling.py b/heat/engine/resources/autoscaling.py index 1de527ce..6b09c0d7 100644 --- a/heat/engine/resources/autoscaling.py +++ b/heat/engine/resources/autoscaling.py @@ -19,6 +19,7 @@ from heat.engine import resource from heat.openstack.common import log as logging from heat.openstack.common import timeutils +from heat.engine.properties import Properties logger = logging.getLogger(__name__) @@ -191,6 +192,12 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin): 'Schema': tags_schema}} } + # template keys and properties supported for handle_update, + # note trailing comma is required for a single item to get a tuple + update_allowed_keys = ('Properties',) + update_allowed_properties = ('MaxSize', 'MinSize', + 'Cooldown', 'DesiredCapacity',) + def __init__(self, name, json_snippet, stack): super(AutoScalingGroup, self).__init__(name, json_snippet, stack) # resource_id is a list of resources @@ -206,7 +213,51 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin): raise_on_error=True) def handle_update(self, json_snippet): - return self.UPDATE_REPLACE + try: + tmpl_diff = self.update_template_diff(json_snippet) + except NotImplementedError: + logger.error("Could not update %s, invalid key" % self.name) + return self.UPDATE_REPLACE + + try: + prop_diff = self.update_template_diff_properties(json_snippet) + except NotImplementedError: + logger.error("Could not update %s, invalid Property" % self.name) + return self.UPDATE_REPLACE + + # If Properties has changed, update self.properties, so we + # get the new values during any subsequent adjustment + if prop_diff: + self.properties = Properties(self.properties_schema, + json_snippet.get('Properties', {}), + self.stack.resolve_runtime_data, + self.name) + + # Get the current capacity, we may need to adjust if + # MinSize or MaxSize has changed + inst_list = [] + if self.resource_id is not None: + inst_list = sorted(self.resource_id.split(',')) + + capacity = len(inst_list) + + # Figure out if an adjustment is required + new_capacity = None + if 'MinSize' in prop_diff: + if capacity < int(self.properties['MinSize']): + new_capacity = int(self.properties['MinSize']) + if 'MaxSize' in prop_diff: + if capacity > int(self.properties['MinSize']): + new_capacity = int(self.properties['MinSize']) + if 'DesiredCapacity' in prop_diff: + if self.properties['DesiredCapacity']: + new_capacity = int(self.properties['DesiredCapacity']) + + if new_capacity is not None: + self.adjust(new_capacity, adjustment_type='ExactCapacity', + raise_on_error=True) + + return self.UPDATE_COMPLETE def adjust(self, adjustment, adjustment_type='ChangeInCapacity', raise_on_error=False): diff --git a/heat/tests/test_autoscaling.py b/heat/tests/test_autoscaling.py index 5d93542d..026c82c6 100644 --- a/heat/tests/test_autoscaling.py +++ b/heat/tests/test_autoscaling.py @@ -15,6 +15,7 @@ import os import datetime +import copy import unittest import mox @@ -126,6 +127,144 @@ class AutoScalingTest(unittest.TestCase): resource.delete() self.m.VerifyAll() + def test_scaling_group_update_ok_maxsize(self): + t = self.load_template() + properties = t['Resources']['WebServerGroup']['Properties'] + properties['MinSize'] = '1' + properties['MaxSize'] = '3' + stack = self.parse_stack(t) + + self._stub_lb_reload(['WebServerGroup-0']) + now = timeutils.utcnow() + self._stub_meta_expected(now, 'ExactCapacity : 1') + self._stub_create(1) + self.m.ReplayAll() + resource = self.create_scaling_group(t, stack, 'WebServerGroup') + self.assertEqual('WebServerGroup-0', resource.resource_id) + + # Reduce the max size to 2, should complete without adjusting + update_snippet = copy.deepcopy(resource.parsed_template()) + update_snippet['Properties']['MaxSize'] = '2' + self.assertEqual(asc.AutoScalingGroup.UPDATE_COMPLETE, + resource.handle_update(update_snippet)) + self.assertEqual('WebServerGroup-0', resource.resource_id) + + resource.delete() + self.m.VerifyAll() + + def test_scaling_group_update_ok_minsize(self): + t = self.load_template() + properties = t['Resources']['WebServerGroup']['Properties'] + properties['MinSize'] = '1' + properties['MaxSize'] = '3' + stack = self.parse_stack(t) + + self._stub_lb_reload(['WebServerGroup-0']) + now = timeutils.utcnow() + self._stub_meta_expected(now, 'ExactCapacity : 1') + self._stub_create(1) + self.m.ReplayAll() + resource = self.create_scaling_group(t, stack, 'WebServerGroup') + self.assertEqual('WebServerGroup-0', resource.resource_id) + + # Increase min size to 2, should trigger an ExactCapacity adjust + self._stub_lb_reload(['WebServerGroup-0', 'WebServerGroup-1']) + self._stub_meta_expected(now, 'ExactCapacity : 2') + self._stub_create(1) + self.m.ReplayAll() + + update_snippet = copy.deepcopy(resource.parsed_template()) + update_snippet['Properties']['MinSize'] = '2' + self.assertEqual(asc.AutoScalingGroup.UPDATE_COMPLETE, + resource.handle_update(update_snippet)) + self.assertEqual('WebServerGroup-0,WebServerGroup-1', + resource.resource_id) + + resource.delete() + self.m.VerifyAll() + + def test_scaling_group_update_ok_desired(self): + t = self.load_template() + properties = t['Resources']['WebServerGroup']['Properties'] + properties['MinSize'] = '1' + properties['MaxSize'] = '3' + stack = self.parse_stack(t) + + self._stub_lb_reload(['WebServerGroup-0']) + now = timeutils.utcnow() + self._stub_meta_expected(now, 'ExactCapacity : 1') + self._stub_create(1) + self.m.ReplayAll() + resource = self.create_scaling_group(t, stack, 'WebServerGroup') + self.assertEqual('WebServerGroup-0', resource.resource_id) + + # Increase min size to 2 via DesiredCapacity, should adjust + self._stub_lb_reload(['WebServerGroup-0', 'WebServerGroup-1']) + self._stub_meta_expected(now, 'ExactCapacity : 2') + self._stub_create(1) + self.m.ReplayAll() + + update_snippet = copy.deepcopy(resource.parsed_template()) + update_snippet['Properties']['DesiredCapacity'] = '2' + self.assertEqual(asc.AutoScalingGroup.UPDATE_COMPLETE, + resource.handle_update(update_snippet)) + self.assertEqual('WebServerGroup-0,WebServerGroup-1', + resource.resource_id) + + resource.delete() + self.m.VerifyAll() + + def test_scaling_group_update_ok_desired_remove(self): + t = self.load_template() + properties = t['Resources']['WebServerGroup']['Properties'] + properties['DesiredCapacity'] = '2' + stack = self.parse_stack(t) + + self._stub_lb_reload(['WebServerGroup-0', 'WebServerGroup-1']) + now = timeutils.utcnow() + self._stub_meta_expected(now, 'ExactCapacity : 2') + self._stub_create(2) + self.m.ReplayAll() + resource = self.create_scaling_group(t, stack, 'WebServerGroup') + self.assertEqual('WebServerGroup-0,WebServerGroup-1', + resource.resource_id) + + # Remove DesiredCapacity from the updated template, which should + # have no effect, it's an optional parameter + update_snippet = copy.deepcopy(resource.parsed_template()) + del(update_snippet['Properties']['DesiredCapacity']) + self.assertEqual(asc.AutoScalingGroup.UPDATE_COMPLETE, + resource.handle_update(update_snippet)) + self.assertEqual('WebServerGroup-0,WebServerGroup-1', + resource.resource_id) + + resource.delete() + self.m.VerifyAll() + + def test_scaling_group_update_ok_cooldown(self): + t = self.load_template() + properties = t['Resources']['WebServerGroup']['Properties'] + properties['Cooldown'] = '60' + stack = self.parse_stack(t) + + self._stub_lb_reload(['WebServerGroup-0']) + now = timeutils.utcnow() + self._stub_meta_expected(now, 'ExactCapacity : 1') + self._stub_create(1) + self.m.ReplayAll() + resource = self.create_scaling_group(t, stack, 'WebServerGroup') + + self.assertEqual('WebServerGroup', resource.FnGetRefId()) + self.assertEqual('WebServerGroup-0', resource.resource_id) + update_snippet = copy.deepcopy(resource.parsed_template()) + old_cd = update_snippet['Properties']['Cooldown'] + update_snippet['Properties']['Cooldown'] = '61' + self.assertEqual(asc.AutoScalingGroup.UPDATE_COMPLETE, + resource.handle_update(update_snippet)) + + resource.delete() + self.m.VerifyAll() + def test_scaling_group_adjust(self): t = self.load_template() stack = self.parse_stack(t)