]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Updated LaunchConfig for AutoScaling UpdatePolicy
authorWinson Chan <winson.c.chan@intel.com>
Tue, 13 Aug 2013 07:37:48 +0000 (00:37 -0700)
committerWinson Chan <winson.c.chan@intel.com>
Thu, 15 Aug 2013 15:58:42 +0000 (08:58 -0700)
This is the first part of a series to implement support
for AutoScaling UpdatePolicy.

Updated LaunchConfiguration resource type to return
self.physical_resource_name() in FnGetRefId(). For both
InstanceGroup and AutoScalingGroup, the property
LaunchConfigurationName is added to the list of update allowed
properties. With this change, any property change to the
LaunchConfiguration resource will be result in a different
LaunchConfigurationName on reference resolution and thus will
trigger InstanceGroup and AutoScalingGroup to handle the update.

Change-Id: I94da8f7083b64873c511b953c6bc63dba2b51034
blueprint: as-update-policy

heat/engine/resources/autoscaling.py
heat/tests/test_autoscaling.py
heat/tests/test_instance_group.py
heat/tests/test_instance_group_update_policy.py [new file with mode: 0644]
heat/tests/test_server_tags.py

index d541de7ac204874e7637b9f63c611113397b0acc..79ad56ae3579a8fdecb4ff884a8b0258b2cb2c61 100644 (file)
@@ -13,6 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import copy
 from heat.common import exception
 from heat.engine import resource
 from heat.engine import signal_responder
@@ -73,7 +74,7 @@ class InstanceGroup(stack_resource.StackResource):
                             'Schema': tags_schema}}
     }
     update_allowed_keys = ('Properties',)
-    update_allowed_properties = ('Size',)
+    update_allowed_properties = ('Size', 'LaunchConfigurationName',)
     attributes_schema = {
         "InstanceList": ("A comma-delimited list of server ip addresses. "
                          "(Heat extension)")
@@ -154,12 +155,12 @@ class InstanceGroup(stack_resource.StackResource):
         launch configuration.
         """
         conf_name = self.properties['LaunchConfigurationName']
-        instance_definition = self.stack.t['Resources'][conf_name].copy()
+        conf = self.stack.resource_by_refid(conf_name)
+        instance_definition = copy.deepcopy(conf.t)
         instance_definition['Type'] = 'AWS::EC2::Instance'
         instance_definition['Properties']['Tags'] = self._tags()
         # resolve references within the context of this stack.
-        static_parsed = self.stack.resolve_static_data(instance_definition)
-        fully_parsed = self.stack.resolve_runtime_data(static_parsed)
+        fully_parsed = self.stack.resolve_runtime_data(instance_definition)
 
         resources = {}
         for i in range(num_instances):
@@ -241,7 +242,8 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
     # 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',
+    update_allowed_properties = ('LaunchConfigurationName',
+                                 'MaxSize', 'MinSize',
                                  'Cooldown', 'DesiredCapacity',)
 
     def handle_create(self):
@@ -364,6 +366,9 @@ class LaunchConfiguration(resource.Resource):
                                           'Schema': tags_schema}},
     }
 
+    def FnGetRefId(self):
+        return unicode(self.physical_resource_name())
+
 
 class ScalingPolicy(signal_responder.SignalResponder, CooldownMixin):
     properties_schema = {
index 4759675d9aefe1bda86c2ae2ff4ef229c2eb5195..7ea0b1c71f47c90b4d9ef92cd7fbf30090a80be3 100644 (file)
@@ -106,18 +106,21 @@ class AutoScalingTest(HeatTestCase):
         self.fc = fakes.FakeKeystoneClient()
 
     def create_scaling_group(self, t, stack, resource_name):
-        rsrc = asc.AutoScalingGroup(resource_name,
-                                    t['Resources'][resource_name],
-                                    stack)
+        # create the launch configuration resource
+        conf = stack.resources['LaunchConfig']
+        self.assertEqual(None, conf.validate())
+        scheduler.TaskRunner(conf.create)()
+        self.assertEqual((conf.CREATE, conf.COMPLETE), conf.state)
+
+        # create the group resource
+        rsrc = stack.resources[resource_name]
         self.assertEqual(None, rsrc.validate())
         scheduler.TaskRunner(rsrc.create)()
         self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
         return rsrc
 
     def create_scaling_policy(self, t, stack, resource_name):
-        rsrc = asc.ScalingPolicy(resource_name,
-                                 t['Resources'][resource_name],
-                                 stack)
+        rsrc = stack.resources[resource_name]
         self.assertEqual(None, rsrc.validate())
         scheduler.TaskRunner(rsrc.create)()
         self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
@@ -231,7 +234,7 @@ class AutoScalingTest(HeatTestCase):
         self.assertEqual('WebServerGroup', rsrc.FnGetRefId())
         self.assertEqual(['WebServerGroup-0'], rsrc.get_instance_names())
         update_snippet = copy.deepcopy(rsrc.parsed_template())
-        update_snippet['Properties']['LaunchConfigurationName'] = 'foo'
+        update_snippet['Properties']['AvailabilityZones'] = ['foo']
         self.assertRaises(resource.UpdateReplace,
                           rsrc.update, update_snippet)
 
@@ -460,9 +463,13 @@ class AutoScalingTest(HeatTestCase):
         instance.Instance.handle_create().AndRaise(Exception)
 
         self.m.ReplayAll()
-        rsrc = asc.AutoScalingGroup('WebServerGroup',
-                                    t['Resources']['WebServerGroup'],
-                                    stack)
+
+        conf = stack.resources['LaunchConfig']
+        self.assertEqual(None, conf.validate())
+        scheduler.TaskRunner(conf.create)()
+        self.assertEqual((conf.CREATE, conf.COMPLETE), conf.state)
+
+        rsrc = stack.resources['WebServerGroup']
         self.assertEqual(None, rsrc.validate())
         self.assertRaises(exception.ResourceFailure,
                           scheduler.TaskRunner(rsrc.create))
index c4f554e8f3df870baa1790c4a8087870622a9d93..9c4ac7eb552b7909aa75c9b29b08e6339507928d 100644 (file)
@@ -16,7 +16,6 @@ import copy
 
 from heat.common import exception
 from heat.common import template_format
-from heat.engine.resources import autoscaling as asc
 from heat.engine.resources import instance
 from heat.engine import resource
 from heat.engine import resources
@@ -81,10 +80,10 @@ class InstanceGroupTest(HeatTestCase):
         instance_class.check_create_complete(
             cookie).MultipleTimes().AndReturn(True)
 
-    def create_instance_group(self, t, stack, resource_name):
-        rsrc = asc.InstanceGroup(resource_name,
-                                 t['Resources'][resource_name],
-                                 stack)
+    def create_resource(self, t, stack, resource_name):
+        # subsequent resources may need to reference previous created resources
+        # use the stack's resource objects instead of instantiating new ones
+        rsrc = stack.resources[resource_name]
         self.assertEqual(None, rsrc.validate())
         scheduler.TaskRunner(rsrc.create)()
         self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
@@ -101,8 +100,8 @@ class InstanceGroupTest(HeatTestCase):
         instance.Instance.FnGetAtt('PublicIp').AndReturn('1.2.3.4')
 
         self.m.ReplayAll()
-        rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
-
+        conf = self.create_resource(t, stack, 'JobServerConfig')
+        rsrc = self.create_resource(t, stack, 'JobServerGroup')
         self.assertEqual('JobServerGroup', rsrc.FnGetRefId())
         self.assertEqual('1.2.3.4', rsrc.FnGetAtt('InstanceList'))
 
@@ -133,8 +132,8 @@ class InstanceGroupTest(HeatTestCase):
         self._stub_create(1, instance_class=MyInstance)
 
         self.m.ReplayAll()
-
-        rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
+        conf = self.create_resource(t, stack, 'JobServerConfig')
+        rsrc = self.create_resource(t, stack, 'JobServerGroup')
         self.assertEqual('JobServerGroup', rsrc.FnGetRefId())
         rsrc.delete()
         self.m.VerifyAll()
@@ -144,9 +143,8 @@ class InstanceGroupTest(HeatTestCase):
         t = template_format.parse(ig_template)
         stack = utils.parse_stack(t)
 
-        rsrc = asc.InstanceGroup('JobServerGroup',
-                                 t['Resources']['JobServerGroup'],
-                                 stack)
+        conf = self.create_resource(t, stack, 'JobServerConfig')
+        rsrc = stack.resources['JobServerGroup']
 
         self.m.StubOutWithMock(instance.Instance, 'handle_create')
         not_found = exception.ImageNotFound(image_name='bla')
@@ -170,7 +168,8 @@ class InstanceGroupTest(HeatTestCase):
 
         self._stub_create(2)
         self.m.ReplayAll()
-        rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
+        conf = self.create_resource(t, stack, 'JobServerConfig')
+        rsrc = self.create_resource(t, stack, 'JobServerGroup')
 
         self.m.VerifyAll()
         self.m.UnsetStubs()
@@ -206,7 +205,8 @@ class InstanceGroupTest(HeatTestCase):
 
         self._stub_create(2)
         self.m.ReplayAll()
-        rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
+        conf = self.create_resource(t, stack, 'JobServerConfig')
+        rsrc = self.create_resource(t, stack, 'JobServerGroup')
 
         self.m.ReplayAll()
 
@@ -226,12 +226,13 @@ class InstanceGroupTest(HeatTestCase):
 
         self._stub_create(2)
         self.m.ReplayAll()
-        rsrc = self.create_instance_group(t, stack, 'JobServerGroup')
+        conf = self.create_resource(t, stack, 'JobServerConfig')
+        rsrc = self.create_resource(t, stack, 'JobServerGroup')
 
         self.m.ReplayAll()
 
         update_snippet = copy.deepcopy(rsrc.parsed_template())
-        update_snippet['Properties']['LaunchConfigurationName'] = 'wibble'
+        update_snippet['Properties']['AvailabilityZones'] = ['wibble']
         self.assertRaises(resource.UpdateReplace,
                           rsrc.update, update_snippet)
 
diff --git a/heat/tests/test_instance_group_update_policy.py b/heat/tests/test_instance_group_update_policy.py
new file mode 100644 (file)
index 0000000..2ac000b
--- /dev/null
@@ -0,0 +1,137 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+#    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 re
+
+from heat.common import template_format
+from heat.engine.resources import instance
+from heat.engine import parser
+from heat.tests.common import HeatTestCase
+from heat.tests.utils import setup_dummy_db
+from heat.tests.utils import parse_stack
+
+
+ig_template_before = '''
+{
+  "AWSTemplateFormatVersion" : "2010-09-09",
+  "Description" : "Template to create multiple instances.",
+  "Parameters" : {},
+  "Resources" : {
+    "JobServerGroup" : {
+      "Type" : "OS::Heat::InstanceGroup",
+      "Properties" : {
+        "LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
+        "Size" : "8",
+        "AvailabilityZones" : ["nova"]
+      }
+    },
+    "JobServerConfig" : {
+      "Type" : "AWS::AutoScaling::LaunchConfiguration",
+      "Properties": {
+        "ImageId"           : "foo",
+        "InstanceType"      : "m1.medium",
+        "KeyName"           : "test",
+        "SecurityGroups"    : [ "sg-1" ],
+        "UserData"          : "jsconfig data"
+      }
+    }
+  }
+}
+'''
+
+ig_template_after = '''
+{
+  "AWSTemplateFormatVersion" : "2010-09-09",
+  "Description" : "Template to create multiple instances.",
+  "Parameters" : {},
+  "Resources" : {
+    "JobServerGroup" : {
+      "Type" : "OS::Heat::InstanceGroup",
+      "Properties" : {
+        "LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
+        "Size" : "8",
+        "AvailabilityZones" : ["nova"]
+      }
+    },
+    "JobServerConfig" : {
+      "Type" : "AWS::AutoScaling::LaunchConfiguration",
+      "Properties": {
+        "ImageId"           : "foo",
+        "InstanceType"      : "m1.large",
+        "KeyName"           : "test",
+        "SecurityGroups"    : [ "sg-1" ],
+        "UserData"          : "jsconfig data"
+      }
+    }
+  }
+}
+'''
+
+
+class InstanceGroupTest(HeatTestCase):
+    def setUp(self):
+        super(InstanceGroupTest, self).setUp()
+        setup_dummy_db()
+
+    def _stub_create(self, num, instance_class=instance.Instance):
+        """
+        Expect creation of C{num} number of Instances.
+
+        :param instance_class: The resource class to expect to be created
+                               instead of instance.Instance.
+        """
+
+        self.m.StubOutWithMock(parser.Stack, 'validate')
+        parser.Stack.validate()
+
+        self.m.StubOutWithMock(instance_class, 'handle_create')
+        self.m.StubOutWithMock(instance_class, 'check_create_complete')
+        cookie = object()
+        for x in range(num):
+            instance_class.handle_create().AndReturn(cookie)
+        instance_class.check_create_complete(cookie).AndReturn(False)
+        instance_class.check_create_complete(
+            cookie).MultipleTimes().AndReturn(True)
+
+    def get_launch_conf_name(self, stack, ig_name):
+        return stack.resources[ig_name].properties['LaunchConfigurationName']
+
+    def test_instance_group(self):
+
+        # setup stack from the initial template
+        tmpl = template_format.parse(ig_template_before)
+        stack = parse_stack(tmpl)
+
+        # test stack create
+        # test the number of instance creation
+        # test that physical resource name of launch configuration is used
+        size = int(stack.resources['JobServerGroup'].properties['Size'])
+        self._stub_create(size)
+        self.m.ReplayAll()
+        stack.create()
+        self.m.VerifyAll()
+        self.assertEqual(stack.status, stack.COMPLETE)
+        conf = stack.resources['JobServerConfig']
+        conf_name_pattern = '%s-JobServerConfig-[a-zA-Z0-9]+$' % stack.name
+        regex_pattern = re.compile(conf_name_pattern)
+        self.assertTrue(regex_pattern.match(conf.FnGetRefId()))
+
+        # test stack update
+        # test that launch configuration is replaced
+        conf_name = self.get_launch_conf_name(stack, 'JobServerGroup')
+        updated_tmpl = template_format.parse(ig_template_after)
+        updated_stack = parse_stack(updated_tmpl)
+        stack.update(updated_stack)
+        updated_conf_name = self.get_launch_conf_name(stack, 'JobServerGroup')
+        self.assertNotEqual(conf_name, updated_conf_name)
index 742cc8a1df065377c7b6db9fd7f83b2680599fe5..e6b517e9ac7347ce633673e7a72d960af362efe1 100644 (file)
@@ -15,7 +15,6 @@ import mox
 
 from heat.engine import environment
 from heat.tests.v1_1 import fakes
-from heat.engine.resources import autoscaling
 from heat.engine.resources import instance as instances
 from heat.engine.resources import nova_utils
 from heat.common import template_format
@@ -77,7 +76,7 @@ group_template = '''
       "Type": "OS::Heat::InstanceGroup",
       "Properties": {
         "AvailabilityZones"      : ["nova"],
-        "LaunchConfigurationName": "Config",
+        "LaunchConfigurationName": { "Ref": "Config" },
         "Size"                   : "1"
       }
     }
@@ -145,9 +144,14 @@ class ServerTagsTest(HeatTestCase):
                              stack_id=uuidutils.generate_uuid())
 
         t['Resources']['WebServer']['Properties']['Tags'] = intags
-        group = autoscaling.InstanceGroup('WebServer',
-                                          t['Resources']['WebServer'],
-                                          stack)
+
+        # create the launch configuration
+        conf = stack.resources['Config']
+        self.assertEqual(None, conf.validate())
+        scheduler.TaskRunner(conf.create)()
+        self.assertEqual((conf.CREATE, conf.COMPLETE), conf.state)
+
+        group = stack.resources['WebServer']
 
         self.m.StubOutWithMock(instances.Instance, 'nova')
         instances.Instance.nova().MultipleTimes().AndReturn(self.fc)