Add UpdatePolicy attribute to Instance/AutoScalingGroup

This is the second part of a series to implement support
for AutoScaling UpdatePolicy.

Defined new update_policy attribute for InstanceGroup
and AutoScalingGroup, and modified init of InstanceGroup and
AutoScalingGroup to parse UpdatePolicy from the template.
Currently, only InstanceGroup and AutoScalingGroup manages
update using UpdatePolicy and so this is not implemented in the
Resource class. This can be revisited when UpdatePolicy is
applicable to other resource types. The resource validation
method is also overridden here to validate the UpdatePolicy.
Included tests to validate various uses cases of templates
with, with bad, without, and removal of UpdatePolicy. This
patch does not address handling of instances update with
UpdatePolicy yet. The next patch will address that.

Change-Id: I1faf1a8a3ba1faf063b6687b34b57b2a96c6bfa8
blueprint: as-update-policy
This commit is contained in:
Winson Chan 2013-08-16 11:35:04 -07:00
parent 044a8aaf69
commit 167b9a58c7
3 changed files with 690 additions and 11 deletions

View File

@ -14,6 +14,7 @@
# under the License.
import copy
from heat.common import exception
from heat.engine import resource
from heat.engine import signal_responder
@ -21,6 +22,7 @@ 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__)
@ -73,12 +75,43 @@ class InstanceGroup(stack_resource.StackResource):
'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.
@ -120,6 +153,14 @@ class InstanceGroup(stack_resource.StackResource):
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', {}),
@ -238,10 +279,22 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
'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',)
@ -267,6 +320,14 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
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', {}),

View File

@ -0,0 +1,382 @@
# 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'])

View File

@ -14,15 +14,16 @@
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.",
@ -50,13 +51,118 @@ ig_template_before = '''
}
'''
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" },
@ -107,11 +213,90 @@ class InstanceGroupTest(HeatTestCase):
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
@ -121,17 +306,68 @@ class InstanceGroupTest(HeatTestCase):
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'])