# under the License.
import copy
+
from heat.common import exception
from heat.engine import resource
from heat.engine import signal_responder
from heat.openstack.common import log as logging
from heat.openstack.common import timeutils
from heat.engine.properties import Properties
+from heat.engine import properties
from heat.engine import stack_resource
logger = logging.getLogger(__name__)
'Schema': {'Type': 'Map',
'Schema': tags_schema}}
}
- update_allowed_keys = ('Properties',)
+ update_allowed_keys = ('Properties', 'UpdatePolicy',)
update_allowed_properties = ('Size', 'LaunchConfigurationName',)
attributes_schema = {
"InstanceList": ("A comma-delimited list of server ip addresses. "
"(Heat extension)")
}
+ rolling_update_schema = {
+ 'MinInstancesInService': properties.Schema(properties.NUMBER,
+ default=0),
+ 'MaxBatchSize': properties.Schema(properties.NUMBER,
+ default=1),
+ 'PauseTime': properties.Schema(properties.STRING,
+ default='PT0S')
+ }
+ update_policy_schema = {
+ 'RollingUpdate': properties.Schema(properties.MAP,
+ schema=rolling_update_schema)
+ }
+
+ def __init__(self, name, json_snippet, stack):
+ """
+ UpdatePolicy is currently only specific to InstanceGroup and
+ AutoScalingGroup. Therefore, init is overridden to parse for the
+ UpdatePolicy.
+ """
+ super(InstanceGroup, self).__init__(name, json_snippet, stack)
+ self.update_policy = Properties(self.update_policy_schema,
+ self.t.get('UpdatePolicy', {}),
+ parent_name=self.name)
+
+ def validate(self):
+ """
+ Add validation for update_policy
+ """
+ super(InstanceGroup, self).validate()
+ if self.update_policy:
+ self.update_policy.validate()
def get_instance_names(self):
"""Get a list of resource names of the instances in this InstanceGroup.
If Properties has changed, update self.properties, so we
get the new values during any subsequent adjustment.
"""
+ if tmpl_diff:
+ # parse update policy
+ if 'UpdatePolicy' in tmpl_diff:
+ self.update_policy = Properties(
+ self.update_policy_schema,
+ json_snippet.get('UpdatePolicy', {}),
+ parent_name=self.name)
+
if prop_diff:
self.properties = Properties(self.properties_schema,
json_snippet.get('Properties', {}),
'Tags': {'Type': 'List', 'Schema': {'Type': 'Map',
'Schema': tags_schema}}
}
+ rolling_update_schema = {
+ 'MinInstancesInService': properties.Schema(properties.NUMBER,
+ default=0),
+ 'MaxBatchSize': properties.Schema(properties.NUMBER,
+ default=1),
+ 'PauseTime': properties.Schema(properties.STRING,
+ default='PT0S')
+ }
+ update_policy_schema = {
+ 'AutoScalingRollingUpdate': properties.Schema(
+ properties.MAP, schema=rolling_update_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_keys = ('Properties', 'UpdatePolicy',)
update_allowed_properties = ('LaunchConfigurationName',
'MaxSize', 'MinSize',
'Cooldown', 'DesiredCapacity',)
If Properties has changed, update self.properties, so we get the new
values during any subsequent adjustment.
"""
+ if tmpl_diff:
+ # parse update policy
+ if 'UpdatePolicy' in tmpl_diff:
+ self.update_policy = Properties(
+ self.update_policy_schema,
+ json_snippet.get('UpdatePolicy', {}),
+ parent_name=self.name)
+
if prop_diff:
self.properties = Properties(self.properties_schema,
json_snippet.get('Properties', {}),
--- /dev/null
+# 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 exception
+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 import utils
+
+
+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" : "1",
+ "MaxSize" : "10"
+ }
+ },
+ "LaunchConfig" : {
+ "Type" : "AWS::AutoScaling::LaunchConfiguration",
+ "Properties": {
+ "ImageId" : "foo",
+ "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" : "1",
+ "MaxSize" : "10"
+ }
+ },
+ "LaunchConfig" : {
+ "Type" : "AWS::AutoScaling::LaunchConfiguration",
+ "Properties": {
+ "ImageId" : "foo",
+ "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" : "1",
+ "MaxSize" : "10"
+ }
+ },
+ "LaunchConfig" : {
+ "Type" : "AWS::AutoScaling::LaunchConfiguration",
+ "Properties": {
+ "ImageId" : "foo",
+ "InstanceType" : "m1.medium",
+ "KeyName" : "test",
+ "SecurityGroups" : [ "sg-1" ],
+ "UserData" : "jsconfig data"
+ }
+ }
+ }
+}
+'''
+
+asg_tmpl_with_updt_policy_1 = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Template to create autoscaling group.",
+ "Parameters" : {},
+ "Resources" : {
+ "WebServerGroup" : {
+ "UpdatePolicy" : {
+ "AutoScalingRollingUpdate" : {
+ "MinInstancesInService" : "1",
+ "MaxBatchSize" : "3",
+ "PauseTime" : "PT30S"
+ }
+ },
+ "Type" : "AWS::AutoScaling::AutoScalingGroup",
+ "Properties" : {
+ "AvailabilityZones" : ["nova"],
+ "LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
+ "MinSize" : "1",
+ "MaxSize" : "10"
+ }
+ },
+ "LaunchConfig" : {
+ "Type" : "AWS::AutoScaling::LaunchConfiguration",
+ "Properties": {
+ "ImageId" : "foo",
+ "InstanceType" : "m1.medium",
+ "KeyName" : "test",
+ "SecurityGroups" : [ "sg-1" ],
+ "UserData" : "jsconfig data"
+ }
+ }
+ }
+}
+'''
+
+asg_tmpl_with_updt_policy_2 = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Template to create autoscaling group.",
+ "Parameters" : {},
+ "Resources" : {
+ "WebServerGroup" : {
+ "UpdatePolicy" : {
+ "AutoScalingRollingUpdate" : {
+ "MinInstancesInService" : "1",
+ "MaxBatchSize" : "5",
+ "PauseTime" : "PT30S"
+ }
+ },
+ "Type" : "AWS::AutoScaling::AutoScalingGroup",
+ "Properties" : {
+ "AvailabilityZones" : ["nova"],
+ "LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
+ "MinSize" : "1",
+ "MaxSize" : "10"
+ }
+ },
+ "LaunchConfig" : {
+ "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_parse_without_update_policy(self):
+ tmpl = template_format.parse(asg_tmpl_without_updt_policy)
+ stack = utils.parse_stack(tmpl)
+ grp = stack.resources['WebServerGroup']
+ self.assertFalse(grp.update_policy['AutoScalingRollingUpdate'])
+
+ def test_parse_with_update_policy(self):
+ tmpl = template_format.parse(asg_tmpl_with_updt_policy_1)
+ stack = utils.parse_stack(tmpl)
+ grp = stack.resources['WebServerGroup']
+ self.assertTrue(grp.update_policy)
+ self.assertTrue(len(grp.update_policy) == 1)
+ self.assertTrue('AutoScalingRollingUpdate' in grp.update_policy)
+ policy = grp.update_policy['AutoScalingRollingUpdate']
+ self.assertTrue(policy and len(policy) > 0)
+ self.assertEqual(int(policy['MinInstancesInService']), 1)
+ self.assertEqual(int(policy['MaxBatchSize']), 3)
+ self.assertEqual(policy['PauseTime'], 'PT30S')
+
+ def test_parse_with_default_update_policy(self):
+ tmpl = template_format.parse(asg_tmpl_with_default_updt_policy)
+ stack = utils.parse_stack(tmpl)
+ grp = stack.resources['WebServerGroup']
+ self.assertTrue(grp.update_policy)
+ self.assertTrue(len(grp.update_policy) == 1)
+ self.assertTrue('AutoScalingRollingUpdate' in grp.update_policy)
+ policy = grp.update_policy['AutoScalingRollingUpdate']
+ self.assertTrue(policy and len(policy) > 0)
+ self.assertEqual(int(policy['MinInstancesInService']), 0)
+ self.assertEqual(int(policy['MaxBatchSize']), 1)
+ self.assertEqual(policy['PauseTime'], 'PT0S')
+
+ def test_parse_with_bad_update_policy(self):
+ tmpl = template_format.parse(asg_tmpl_with_bad_updt_policy)
+ stack = utils.parse_stack(tmpl)
+ self.assertRaises(exception.StackValidationFailed, stack.validate)
+
+ 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.resources['WebServerGroup']
+ current_snippets = dict((r.name, r.parsed_template())
+ for r in current_stack)
+ 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.resources['WebServerGroup']
+ updated_grp_json = current_stack.resolve_runtime_data(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(tmpl_diff, expected)
+
+ def test_update_policy_added(self):
+ self.validate_update_policy_diff(asg_tmpl_without_updt_policy,
+ asg_tmpl_with_updt_policy_1)
+
+ def test_update_policy_updated(self):
+ self.validate_update_policy_diff(asg_tmpl_with_updt_policy_1,
+ asg_tmpl_with_updt_policy_2)
+
+ def test_update_policy_removed(self):
+ self.validate_update_policy_diff(asg_tmpl_with_updt_policy_1,
+ asg_tmpl_without_updt_policy)
+
+ def test_autoscaling_group_update(self):
+
+ # setup stack from the initial template
+ tmpl = template_format.parse(asg_tmpl_with_updt_policy_1)
+ stack = utils.parse_stack(tmpl)
+ nested = stack.resources['WebServerGroup'].nested()
+
+ # test stack create
+ # test the number of instance creation
+ # test that physical resource name of launch configuration is used
+ size = int(stack.resources['WebServerGroup'].properties['MinSize'])
+ self._stub_create(size)
+ self.m.ReplayAll()
+ stack.create()
+ self.m.VerifyAll()
+ self.assertEqual(stack.state, ('CREATE', 'COMPLETE'))
+ conf = stack.resources['LaunchConfig']
+ conf_name_pattern = '%s-LaunchConfig-[a-zA-Z0-9]+$' % stack.name
+ regex_pattern = re.compile(conf_name_pattern)
+ self.assertTrue(regex_pattern.match(conf.FnGetRefId()))
+ nested = stack.resources['WebServerGroup'].nested()
+ self.assertTrue(len(nested.resources), size)
+
+ # test stack update
+ # test that update policy is updated
+ # test that launch configuration is replaced
+ current_grp = stack.resources['WebServerGroup']
+ self.assertTrue('AutoScalingRollingUpdate'
+ in current_grp.update_policy)
+ current_policy = current_grp.update_policy['AutoScalingRollingUpdate']
+ self.assertTrue(current_policy and len(current_policy) > 0)
+ self.assertEqual(int(current_policy['MaxBatchSize']), 3)
+ conf_name = self.get_launch_conf_name(stack, 'WebServerGroup')
+ updated_tmpl = template_format.parse(asg_tmpl_with_updt_policy_2)
+ updated_stack = utils.parse_stack(updated_tmpl)
+ stack.update(updated_stack)
+ self.assertEqual(stack.state, ('UPDATE', 'COMPLETE'))
+ updated_grp = stack.resources['WebServerGroup']
+ self.assertTrue('AutoScalingRollingUpdate'
+ in updated_grp.update_policy)
+ updated_policy = updated_grp.update_policy['AutoScalingRollingUpdate']
+ self.assertTrue(updated_policy and len(updated_policy) > 0)
+ self.assertEqual(int(updated_policy['MaxBatchSize']), 5)
+ updated_conf_name = self.get_launch_conf_name(stack, 'WebServerGroup')
+ self.assertNotEqual(conf_name, updated_conf_name)
+
+ def test_autoscaling_group_update_policy_removed(self):
+
+ # setup stack from the initial template
+ tmpl = template_format.parse(asg_tmpl_with_updt_policy_1)
+ stack = utils.parse_stack(tmpl)
+ nested = stack.resources['WebServerGroup'].nested()
+
+ # test stack create
+ # test the number of instance creation
+ # test that physical resource name of launch configuration is used
+ size = int(stack.resources['WebServerGroup'].properties['MinSize'])
+ self._stub_create(size)
+ self.m.ReplayAll()
+ stack.create()
+ self.m.VerifyAll()
+ self.assertEqual(stack.state, ('CREATE', 'COMPLETE'))
+ conf = stack.resources['LaunchConfig']
+ conf_name_pattern = '%s-LaunchConfig-[a-zA-Z0-9]+$' % stack.name
+ regex_pattern = re.compile(conf_name_pattern)
+ self.assertTrue(regex_pattern.match(conf.FnGetRefId()))
+ nested = stack.resources['WebServerGroup'].nested()
+ self.assertTrue(len(nested.resources), size)
+
+ # test stack update
+ # test that update policy is removed
+ current_grp = stack.resources['WebServerGroup']
+ self.assertTrue('AutoScalingRollingUpdate'
+ in current_grp.update_policy)
+ current_policy = current_grp.update_policy['AutoScalingRollingUpdate']
+ self.assertTrue(current_policy and len(current_policy) > 0)
+ self.assertEqual(int(current_policy['MaxBatchSize']), 3)
+ updated_tmpl = template_format.parse(asg_tmpl_without_updt_policy)
+ updated_stack = utils.parse_stack(updated_tmpl)
+ stack.update(updated_stack)
+ self.assertEqual(stack.state, ('UPDATE', 'COMPLETE'))
+ updated_grp = stack.resources['WebServerGroup']
+ self.assertFalse(updated_grp.update_policy['AutoScalingRollingUpdate'])
import re
+from heat.common import exception
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
+from heat.tests import utils
-ig_template_before = '''
+ig_tmpl_without_updt_policy = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to create multiple instances.",
}
'''
-ig_template_after = '''
+ig_tmpl_with_bad_updt_policy = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to create multiple instances.",
"Parameters" : {},
"Resources" : {
"JobServerGroup" : {
+ "UpdatePolicy" : {
+ "RollingUpdate": "foo"
+ },
+ "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_tmpl_with_default_updt_policy = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Template to create multiple instances.",
+ "Parameters" : {},
+ "Resources" : {
+ "JobServerGroup" : {
+ "UpdatePolicy" : {
+ "RollingUpdate" : {
+ }
+ },
+ "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_tmpl_with_updt_policy_1 = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Template to create multiple instances.",
+ "Parameters" : {},
+ "Resources" : {
+ "JobServerGroup" : {
+ "UpdatePolicy" : {
+ "RollingUpdate" : {
+ "MinInstancesInService" : "1",
+ "MaxBatchSize" : "3",
+ "PauseTime" : "PT30S"
+ }
+ },
+ "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_tmpl_with_updt_policy_2 = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Template to create multiple instances.",
+ "Parameters" : {},
+ "Resources" : {
+ "JobServerGroup" : {
+ "UpdatePolicy" : {
+ "RollingUpdate" : {
+ "MinInstancesInService" : "1",
+ "MaxBatchSize" : "5",
+ "PauseTime" : "PT30S"
+ }
+ },
"Type" : "OS::Heat::InstanceGroup",
"Properties" : {
"LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
def get_launch_conf_name(self, stack, ig_name):
return stack.resources[ig_name].properties['LaunchConfigurationName']
- def test_instance_group(self):
+ def test_parse_without_update_policy(self):
+ tmpl = template_format.parse(ig_tmpl_without_updt_policy)
+ stack = utils.parse_stack(tmpl)
+ grp = stack.resources['JobServerGroup']
+ self.assertFalse(grp.update_policy['RollingUpdate'])
+
+ def test_parse_with_update_policy(self):
+ tmpl = template_format.parse(ig_tmpl_with_updt_policy_1)
+ stack = utils.parse_stack(tmpl)
+ grp = stack.resources['JobServerGroup']
+ self.assertTrue(grp.update_policy)
+ self.assertTrue(len(grp.update_policy) == 1)
+ self.assertTrue('RollingUpdate' in grp.update_policy)
+ policy = grp.update_policy['RollingUpdate']
+ self.assertTrue(policy and len(policy) > 0)
+ self.assertEqual(int(policy['MinInstancesInService']), 1)
+ self.assertEqual(int(policy['MaxBatchSize']), 3)
+ self.assertEqual(policy['PauseTime'], 'PT30S')
+
+ def test_parse_with_default_update_policy(self):
+ tmpl = template_format.parse(ig_tmpl_with_default_updt_policy)
+ stack = utils.parse_stack(tmpl)
+ grp = stack.resources['JobServerGroup']
+ self.assertTrue(grp.update_policy)
+ self.assertTrue(len(grp.update_policy) == 1)
+ self.assertTrue('RollingUpdate' in grp.update_policy)
+ policy = grp.update_policy['RollingUpdate']
+ self.assertTrue(policy and len(policy) > 0)
+ self.assertEqual(int(policy['MinInstancesInService']), 0)
+ self.assertEqual(int(policy['MaxBatchSize']), 1)
+ self.assertEqual(policy['PauseTime'], 'PT0S')
+
+ def test_parse_with_bad_update_policy(self):
+ tmpl = template_format.parse(ig_tmpl_with_bad_updt_policy)
+ stack = utils.parse_stack(tmpl)
+ self.assertRaises(exception.StackValidationFailed, stack.validate)
+
+ 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.resources['JobServerGroup']
+ current_snippets = dict((r.name, r.parsed_template())
+ for r in current_stack)
+ 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.resources['JobServerGroup']
+ updated_grp_json = current_stack.resolve_runtime_data(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(tmpl_diff, expected)
+
+ def test_update_policy_added(self):
+ self.validate_update_policy_diff(ig_tmpl_without_updt_policy,
+ ig_tmpl_with_updt_policy_1)
+
+ def test_update_policy_updated(self):
+ self.validate_update_policy_diff(ig_tmpl_with_updt_policy_1,
+ ig_tmpl_with_updt_policy_2)
+
+ def test_update_policy_removed(self):
+ self.validate_update_policy_diff(ig_tmpl_with_updt_policy_1,
+ ig_tmpl_without_updt_policy)
+
+ def test_instance_group_update(self):
# setup stack from the initial template
- tmpl = template_format.parse(ig_template_before)
- stack = parse_stack(tmpl)
+ tmpl = template_format.parse(ig_tmpl_with_updt_policy_1)
+ stack = utils.parse_stack(tmpl)
+ nested = stack.resources['JobServerGroup'].nested()
# test stack create
# test the number of instance creation
self.m.ReplayAll()
stack.create()
self.m.VerifyAll()
- self.assertEqual(stack.status, stack.COMPLETE)
+ self.assertEqual(stack.state, ('CREATE', '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()))
+ nested = stack.resources['JobServerGroup'].nested()
+ self.assertTrue(len(nested.resources), size)
# test stack update
+ # test that update policy is updated
# test that launch configuration is replaced
+ current_grp = stack.resources['JobServerGroup']
+ self.assertTrue('RollingUpdate' in current_grp.update_policy)
+ current_policy = current_grp.update_policy['RollingUpdate']
+ self.assertTrue(current_policy and len(current_policy) > 0)
+ self.assertEqual(int(current_policy['MaxBatchSize']), 3)
conf_name = self.get_launch_conf_name(stack, 'JobServerGroup')
- updated_tmpl = template_format.parse(ig_template_after)
- updated_stack = parse_stack(updated_tmpl)
+ updated_tmpl = template_format.parse(ig_tmpl_with_updt_policy_2)
+ updated_stack = utils.parse_stack(updated_tmpl)
stack.update(updated_stack)
+ self.assertEqual(stack.state, ('UPDATE', 'COMPLETE'))
+ updated_grp = stack.resources['JobServerGroup']
+ self.assertTrue('RollingUpdate' in updated_grp.update_policy)
+ updated_policy = updated_grp.update_policy['RollingUpdate']
+ self.assertTrue(updated_policy and len(updated_policy) > 0)
+ self.assertEqual(int(updated_policy['MaxBatchSize']), 5)
updated_conf_name = self.get_launch_conf_name(stack, 'JobServerGroup')
self.assertNotEqual(conf_name, updated_conf_name)
+
+ def test_instance_group_update_policy_removed(self):
+
+ # setup stack from the initial template
+ tmpl = template_format.parse(ig_tmpl_with_updt_policy_1)
+ stack = utils.parse_stack(tmpl)
+ nested = stack.resources['JobServerGroup'].nested()
+
+ # 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.state, ('CREATE', '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()))
+ nested = stack.resources['JobServerGroup'].nested()
+ self.assertTrue(len(nested.resources), size)
+
+ # test stack update
+ # test that update policy is removed
+ current_grp = stack.resources['JobServerGroup']
+ self.assertTrue('RollingUpdate' in current_grp.update_policy)
+ current_policy = current_grp.update_policy['RollingUpdate']
+ self.assertTrue(current_policy and len(current_policy) > 0)
+ self.assertEqual(int(current_policy['MaxBatchSize']), 3)
+ updated_tmpl = template_format.parse(ig_tmpl_without_updt_policy)
+ updated_stack = utils.parse_stack(updated_tmpl)
+ stack.update(updated_stack)
+ self.assertEqual(stack.state, ('UPDATE', 'COMPLETE'))
+ updated_grp = stack.resources['JobServerGroup']
+ self.assertFalse(updated_grp.update_policy['RollingUpdate'])