deb-heat/heat/tests/test_heat_autoscaling_group.py
tengqm 91300e4069 Split scaling policy into separate files
A further step to split the AWS and the OS version of auto-scaling group
related classes.  This patch splits the scaling policy and launch
configuration into separate files.

This patch is not breaking any existing test cases. But more test cases
will be needed for the OS version scaling policy.

Implements: partial-blueprint reorg-asg-code
Change-Id: I0f888496d92b0efb5d63b567864488ff8a45e4ac
2014-08-27 20:27:19 +08:00

471 lines
18 KiB
Python

# 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 copy
import datetime
import itertools
from oslo.config import cfg
from heat.common import exception
from heat.common import short_id
from heat.common import template_format
from heat.engine import resource
from heat.engine import rsrc_defn
from heat.engine import scheduler
from heat.engine import stack_resource
from heat.openstack.common import timeutils
from heat.tests.common import HeatTestCase
from heat.tests import generic_resource
from heat.tests import utils
class AutoScalingGroupTest(HeatTestCase):
as_template = '''
heat_template_version: 2013-05-23
description: AutoScaling Test
resources:
my-group:
properties:
max_size: 5
min_size: 1
resource:
type: ResourceWithPropsAndAttrs
properties:
Foo: hello
type: OS::Heat::AutoScalingGroup
'''
def setUp(self):
super(AutoScalingGroupTest, self).setUp()
resource._register_class('ResourceWithPropsAndAttrs',
generic_resource.ResourceWithPropsAndAttrs)
cfg.CONF.set_default('heat_waitcondition_server_url',
'http://server.test:8000/v1/waitcondition')
self.stub_keystoneclient()
self.parsed = template_format.parse(self.as_template)
def create_stack(self, t):
stack = utils.parse_stack(t)
stack.create()
self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
return stack
def test_scaling_delete_empty(self):
properties = self.parsed['resources']['my-group']['properties']
properties['min_size'] = 0
properties['max_size'] = 0
rsrc = self.create_stack(self.parsed)['my-group']
self.assertEqual(0, len(rsrc.get_instances()))
rsrc.delete()
def test_scaling_adjust_down_empty(self):
properties = self.parsed['resources']['my-group']['properties']
properties['min_size'] = 1
properties['max_size'] = 1
rsrc = self.create_stack(self.parsed)['my-group']
resources = rsrc.get_instances()
self.assertEqual(1, len(resources))
# Reduce the min size to 0, should complete without adjusting
props = copy.copy(rsrc.properties.data)
props['min_size'] = 0
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
rsrc.type(),
props)
scheduler.TaskRunner(rsrc.update, update_snippet)()
self.assertEqual(resources, rsrc.get_instances())
# trigger adjustment to reduce to 0, there should be no more instances
rsrc.adjust(-1)
self.assertEqual(0, len(rsrc.get_instances()))
def test_scaling_group_suspend(self):
rsrc = self.create_stack(self.parsed)['my-group']
self.assertEqual(1, len(rsrc.get_instances()))
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
scheduler.TaskRunner(rsrc.suspend)()
self.assertEqual((rsrc.SUSPEND, rsrc.COMPLETE), rsrc.state)
def test_scaling_group_resume(self):
rsrc = self.create_stack(self.parsed)['my-group']
self.assertEqual(1, len(rsrc.get_instances()))
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
rsrc.state_set(rsrc.SUSPEND, rsrc.COMPLETE)
for i in rsrc.nested().values():
i.state_set(rsrc.SUSPEND, rsrc.COMPLETE)
scheduler.TaskRunner(rsrc.resume)()
self.assertEqual((rsrc.RESUME, rsrc.COMPLETE), rsrc.state)
def test_scaling_group_create_error(self):
mock_create = self.patchobject(generic_resource.ResourceWithProps,
'handle_create')
mock_create.side_effect = Exception("Creation failed!")
rsrc = utils.parse_stack(self.parsed)['my-group']
self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.create))
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.assertEqual(0, len(rsrc.get_instances()))
def test_scaling_group_update_ok_maxsize(self):
properties = self.parsed['resources']['my-group']['properties']
properties['min_size'] = 1
properties['max_size'] = 3
rsrc = self.create_stack(self.parsed)['my-group']
resources = rsrc.get_instances()
self.assertEqual(1, len(resources))
# Reduce the max size to 2, should complete without adjusting
props = copy.copy(rsrc.properties.data)
props['max_size'] = 2
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
rsrc.type(),
props)
scheduler.TaskRunner(rsrc.update, update_snippet)()
self.assertEqual(resources, rsrc.get_instances())
self.assertEqual(2, rsrc.properties['max_size'])
def test_scaling_group_update_ok_minsize(self):
properties = self.parsed['resources']['my-group']['properties']
properties['min_size'] = 1
properties['max_size'] = 3
rsrc = self.create_stack(self.parsed)['my-group']
self.assertEqual(1, len(rsrc.get_instances()))
props = copy.copy(rsrc.properties.data)
props['min_size'] = 2
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
rsrc.type(),
props)
scheduler.TaskRunner(rsrc.update, update_snippet)()
self.assertEqual(2, len(rsrc.get_instances()))
self.assertEqual(2, rsrc.properties['min_size'])
def test_scaling_group_update_ok_desired(self):
properties = self.parsed['resources']['my-group']['properties']
properties['min_size'] = 1
properties['max_size'] = 3
rsrc = self.create_stack(self.parsed)['my-group']
self.assertEqual(1, len(rsrc.get_instances()))
props = copy.copy(rsrc.properties.data)
props['desired_capacity'] = 2
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
rsrc.type(),
props)
scheduler.TaskRunner(rsrc.update, update_snippet)()
self.assertEqual(2, len(rsrc.get_instances()))
self.assertEqual(2, rsrc.properties['desired_capacity'])
def test_scaling_group_update_ok_desired_remove(self):
properties = self.parsed['resources']['my-group']['properties']
properties['desired_capacity'] = 2
rsrc = self.create_stack(self.parsed)['my-group']
resources = rsrc.get_instances()
self.assertEqual(2, len(resources))
props = copy.copy(rsrc.properties.data)
props.pop('desired_capacity')
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
rsrc.type(),
props)
scheduler.TaskRunner(rsrc.update, update_snippet)()
self.assertEqual(resources, rsrc.get_instances())
self.assertIsNone(rsrc.properties['desired_capacity'])
def test_scaling_group_scale_up_failure(self):
stack = self.create_stack(self.parsed)
mock_create = self.patchobject(generic_resource.ResourceWithProps,
'handle_create')
rsrc = stack['my-group']
self.assertEqual(1, len(rsrc.get_instances()))
mock_create.side_effect = exception.Error('Bang')
self.assertRaises(exception.Error, rsrc.adjust, 1)
self.assertEqual(1, len(rsrc.get_instances()))
def test_scaling_group_truncate_adjustment(self):
# Create initial group, 2 instances
properties = self.parsed['resources']['my-group']['properties']
properties['desired_capacity'] = 2
rsrc = self.create_stack(self.parsed)['my-group']
self.assertEqual(2, len(rsrc.get_instances()))
rsrc.adjust(4)
self.assertEqual(5, len(rsrc.get_instances()))
rsrc.adjust(-5)
self.assertEqual(1, len(rsrc.get_instances()))
rsrc.adjust(0)
self.assertEqual(1, len(rsrc.get_instances()))
def _do_test_scaling_group_percent(self, decrease, lowest,
increase, create, highest):
# Create initial group, 2 instances
properties = self.parsed['resources']['my-group']['properties']
properties['desired_capacity'] = 2
rsrc = self.create_stack(self.parsed)['my-group']
self.assertEqual(2, len(rsrc.get_instances()))
# reduce by decrease %
rsrc.adjust(decrease, 'percentage_change_in_capacity')
self.assertEqual(lowest, len(rsrc.get_instances()))
# raise by increase %
rsrc.adjust(increase, 'percentage_change_in_capacity')
self.assertEqual(highest, len(rsrc.get_instances()))
def test_scaling_group_percent(self):
self._do_test_scaling_group_percent(-50, 1, 200, 2, 3)
def test_scaling_group_percent_round_up(self):
self._do_test_scaling_group_percent(-33, 1, 33, 1, 2)
def test_scaling_group_percent_round_down(self):
self._do_test_scaling_group_percent(-66, 1, 225, 2, 3)
def test_min_min_size(self):
self.parsed['resources']['my-group']['properties']['min_size'] = -1
stack = utils.parse_stack(self.parsed)
self.assertRaises(exception.StackValidationFailed,
stack['my-group'].validate)
def test_min_max_size(self):
self.parsed['resources']['my-group']['properties']['max_size'] = -1
stack = utils.parse_stack(self.parsed)
self.assertRaises(exception.StackValidationFailed,
stack['my-group'].validate)
def test_output_attribute_list(self):
rsrc = self.create_stack(self.parsed)['my-group']
member_names = rsrc.nested().resources.keys()
self.assertEqual(member_names, rsrc.FnGetAtt('outputs_list', 'Bar'))
def test_output_attribute_dict(self):
rsrc = self.create_stack(self.parsed)['my-group']
member_names = rsrc.nested().resources.keys()
self.assertEqual(dict((n, n) for n in member_names),
rsrc.FnGetAtt('outputs', 'Bar'))
class HeatScalingGroupWithCFNScalingPolicyTest(HeatTestCase):
as_template = '''
heat_template_version: 2013-05-23
description: AutoScaling Test
resources:
my-group:
properties:
max_size: 5
min_size: 1
resource:
type: ResourceWithProps
properties:
Foo: hello
type: OS::Heat::AutoScalingGroup
scale-up:
type: AWS::AutoScaling::ScalingPolicy
properties:
AutoScalingGroupName: {get_resource: my-group}
ScalingAdjustment: 1
AdjustmentType: ChangeInCapacity
Cooldown: 60
'''
def setUp(self):
super(HeatScalingGroupWithCFNScalingPolicyTest, self).setUp()
resource._register_class('ResourceWithProps',
generic_resource.ResourceWithProps)
cfg.CONF.set_default('heat_waitcondition_server_url',
'http://server.test:8000/v1/waitcondition')
self.stub_keystoneclient()
self.parsed = template_format.parse(self.as_template)
def create_stack(self, t):
stack = utils.parse_stack(t)
stack.create()
self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
return stack
def test_scale_up(self):
stack = self.create_stack(self.parsed)
scale_up = stack['scale-up']
group = stack['my-group']
self.assertEqual(1, len(group.get_instances()))
scale_up.signal()
self.assertEqual(2, len(group.get_instances()))
def test_no_instance_list(self):
"""
The InstanceList attribute is not inherited from
AutoScalingResourceGroup's superclasses.
"""
stack = self.create_stack(self.parsed)
group = stack['my-group']
self.assertRaises(exception.InvalidTemplateAttribute,
group.FnGetAtt, 'InstanceList')
class ScalingPolicyTest(HeatTestCase):
# TODO(Qiming): Add more tests to the scaling policy
as_template = '''
heat_template_version: 2013-05-23
resources:
my-policy:
type: OS::Heat::ScalingPolicy
properties:
auto_scaling_group_id: {get_resource: my-group}
adjustment_type: change_in_capacity
scaling_adjustment: 1
my-group:
type: OS::Heat::AutoScalingGroup
properties:
max_size: 5
min_size: 1
resource:
type: ResourceWithProps
properties:
Foo: hello
'''
def setUp(self):
super(ScalingPolicyTest, self).setUp()
resource._register_class('ResourceWithProps',
generic_resource.ResourceWithProps)
self.stub_keystoneclient()
self.parsed = template_format.parse(self.as_template)
def test_alarm_attribute(self):
stack = utils.parse_stack(self.parsed)
stack.create()
policy = stack['my-policy']
self.assertIn("my-policy", policy.FnGetAtt('alarm_url'))
def test_signal(self):
stack = utils.parse_stack(self.parsed)
stack.create()
self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
policy = stack['my-policy']
group = stack['my-group']
self.assertEqual("1234", policy.FnGetRefId())
self.assertEqual(1, len(group.get_instance_names()))
policy.signal()
self.assertEqual(2, len(group.get_instance_names()))
def test_signal_with_cooldown(self):
self.parsed['resources']['my-policy']['properties']['cooldown'] = 60
stack = utils.parse_stack(self.parsed)
stack.create()
policy = stack['my-policy']
group = stack['my-group']
self.assertEqual(1, len(group.get_instance_names()))
policy.signal()
self.assertEqual(2, len(group.get_instance_names()))
policy.signal()
# The second signal shouldn't have changed it because of cooldown
self.assertEqual(2, len(group.get_instance_names()))
past = timeutils.strtime(timeutils.utcnow() -
datetime.timedelta(seconds=65))
policy.metadata_set({past: 'ChangeInCapacity : 1'})
policy.signal()
self.assertEqual(3, len(group.get_instance_names()))
class RollingUpdatesTest(HeatTestCase):
as_template = '''
heat_template_version: 2013-05-23
description: AutoScaling Test
resources:
my-group:
properties:
max_size: 5
min_size: 4
rolling_updates:
min_in_service: 2
max_batch_size: 2
pause_time: 0
resource:
type: ResourceWithProps
properties:
Foo: hello
type: OS::Heat::AutoScalingGroup
'''
def setUp(self):
super(RollingUpdatesTest, self).setUp()
utils.setup_dummy_db()
resource._register_class('ResourceWithProps',
generic_resource.ResourceWithProps)
cfg.CONF.set_default('heat_waitcondition_server_url',
'http://server.test:8000/v1/waitcondition')
self.stub_keystoneclient()
self.parsed = template_format.parse(self.as_template)
generate_id = self.patchobject(short_id, 'generate_id')
generate_id.side_effect = ('id-%d' % (i,)
for i in itertools.count()).next
def create_stack(self, t):
stack = utils.parse_stack(t)
stack.create()
self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
return stack
def test_rolling_update(self):
stack = self.create_stack(self.parsed)
rsrc = stack['my-group']
created_order = sorted(
rsrc.nested().resources,
key=lambda name: rsrc.nested().resources[name].created_time)
batches = []
def update_with_template(tmpl, env):
# keep track of the new updates to resources _in creation order_.
definitions = tmpl.resource_definitions(stack)
templates = [definitions[name] for name in created_order]
batches.append(templates)
self.patchobject(
stack_resource.StackResource, 'update_with_template',
side_effect=update_with_template, wraps=rsrc.update_with_template)
props = copy.deepcopy(rsrc.properties.data)
props['resource']['properties']['Foo'] = 'Hi'
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
rsrc.type(),
props)
scheduler.TaskRunner(rsrc.update, update_snippet)()
props_schema = generic_resource.ResourceWithProps.properties_schema
def get_foos(defns):
return [d.properties(props_schema)['Foo'] for d in defns]
# first batch has 2 new resources
self.assertEqual(['Hi', 'Hi', 'hello', 'hello'],
get_foos(batches[0]))
# second batch has all new resources.
self.assertEqual(['Hi', 'Hi', 'Hi', 'Hi'],
get_foos(batches[1]))