]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
heat engine : AutoScalingGroup UpdateStack support
authorSteven Hardy <shardy@redhat.com>
Mon, 4 Feb 2013 18:03:52 +0000 (18:03 +0000)
committerSteven Hardy <shardy@redhat.com>
Mon, 4 Feb 2013 18:05:57 +0000 (18:05 +0000)
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 <shardy@redhat.com>
heat/engine/resources/autoscaling.py
heat/tests/test_autoscaling.py

index 1de527ce7d232b5f5cf5113269fbcf06b399aea5..6b09c0d78137e2ff54d77c2c3e4404a7fb51d3f0 100644 (file)
@@ -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):
index 5d93542dbab43f065433a729e8654735fcc777dc..026c82c6d4a26827258bcc3a693fc74761dfd46d 100644 (file)
@@ -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)