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:
parent
044a8aaf69
commit
167b9a58c7
@ -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', {}),
|
||||
|
382
heat/tests/test_autoscaling_update_policy.py
Normal file
382
heat/tests/test_autoscaling_update_policy.py
Normal 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'])
|
@ -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'])
|
||||
|
Loading…
Reference in New Issue
Block a user