From 63646f8cfd341e2e88f18cfb09b92e986d282cc5 Mon Sep 17 00:00:00 2001 From: Rabi Mishra Date: Wed, 22 Apr 2015 15:56:43 +0530 Subject: [PATCH] Add min_adjustment_step property to ScalingPolicy Adds `min_adjustment_step` property to ScalingPolicy resource. It represents the minimum number of resources that are added or removed when AutoScaling group scales up or down. This can be used only when specifying value `percent_change_in_capacity` for `adjustment_type` property. Change-Id: Ia8976e52047ab47245566487ce1872a8890bcff2 Closes-Bug: #1447913 --- heat/common/exception.py | 5 + .../aws/autoscaling/autoscaling_group.py | 18 +++- .../aws/autoscaling/scaling_policy.py | 93 ++++--------------- .../openstack/heat/scaling_policy.py | 35 ++++++- .../autoscaling/test_heat_scaling_group.py | 60 ++++++++++++ .../autoscaling/test_heat_scaling_policy.py | 32 ++++++- heat/tests/autoscaling/test_new_capacity.py | 35 +++++++ heat/tests/autoscaling/test_scaling_group.py | 60 ++++++++++++ heat/tests/autoscaling/test_scaling_policy.py | 30 +++++- 9 files changed, 284 insertions(+), 84 deletions(-) diff --git a/heat/common/exception.py b/heat/common/exception.py index 6a374b7a84..bccffa45ee 100644 --- a/heat/common/exception.py +++ b/heat/common/exception.py @@ -381,6 +381,11 @@ class ResourcePropertyDependency(HeatException): msg_fmt = _('%(prop1)s cannot be specified without %(prop2)s.') +class ResourcePropertyValueDependency(HeatException): + msg_fmt = _('%(prop1)s property should only be specified ' + 'for %(prop2)s with value %(value)s.') + + class PropertyUnspecifiedError(HeatException): msg_fmt = _('At least one of the following properties ' 'must be specified: %(props)s') diff --git a/heat/engine/resources/aws/autoscaling/autoscaling_group.py b/heat/engine/resources/aws/autoscaling/autoscaling_group.py index 7825aa0cd8..f5a22ae2ab 100644 --- a/heat/engine/resources/aws/autoscaling/autoscaling_group.py +++ b/heat/engine/resources/aws/autoscaling/autoscaling_group.py @@ -41,12 +41,18 @@ LOG = logging.getLogger(__name__) def _calculate_new_capacity(current, adjustment, adjustment_type, - minimum, maximum): + min_adjustment_step, minimum, maximum): """ Given the current capacity, calculates the new capacity which results from applying the given adjustment of the given adjustment-type. The new capacity will be kept within the maximum and minimum bounds. """ + def _get_minimum_adjustment(adjustment, min_adjustment_step): + if min_adjustment_step and min_adjustment_step > abs(adjustment): + adjustment = (min_adjustment_step if adjustment > 0 + else -min_adjustment_step) + return adjustment + if adjustment_type == CHANGE_IN_CAPACITY: new_capacity = current + adjustment elif adjustment_type == EXACT_CAPACITY: @@ -60,7 +66,8 @@ def _calculate_new_capacity(current, adjustment, adjustment_type, else: rounded = int(math.floor(delta) if delta > 0.0 else math.ceil(delta)) - new_capacity = current + rounded + adjustment = _get_minimum_adjustment(rounded, min_adjustment_step) + new_capacity = current + adjustment if new_capacity > maximum: LOG.debug('truncating growth to %s' % maximum) @@ -293,7 +300,8 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin): current_capacity = grouputils.get_size(self) self.adjust(current_capacity, adjustment_type=EXACT_CAPACITY) - def adjust(self, adjustment, adjustment_type=CHANGE_IN_CAPACITY): + def adjust(self, adjustment, adjustment_type=CHANGE_IN_CAPACITY, + min_adjustment_step=None): """ Adjust the size of the scaling group if the cooldown permits. """ @@ -309,7 +317,9 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin): upper = self.properties[self.MAX_SIZE] new_capacity = _calculate_new_capacity(capacity, adjustment, - adjustment_type, lower, upper) + adjustment_type, + min_adjustment_step, + lower, upper) # send a notification before, on-error and on-success. notif = { diff --git a/heat/engine/resources/aws/autoscaling/scaling_policy.py b/heat/engine/resources/aws/autoscaling/scaling_policy.py index c092aa93ad..b97de7935f 100644 --- a/heat/engine/resources/aws/autoscaling/scaling_policy.py +++ b/heat/engine/resources/aws/autoscaling/scaling_policy.py @@ -14,26 +14,22 @@ from oslo_log import log as logging import six -from heat.common import exception from heat.common.i18n import _ -from heat.common.i18n import _LI from heat.engine import attributes from heat.engine import constraints from heat.engine import properties -from heat.engine.resources import signal_responder -from heat.scaling import cooldown +from heat.engine.resources.openstack.heat import scaling_policy as heat_sp LOG = logging.getLogger(__name__) -class AWSScalingPolicy(signal_responder.SignalResponder, - cooldown.CooldownMixin): +class AWSScalingPolicy(heat_sp.AutoScalingPolicy): PROPERTIES = ( AUTO_SCALING_GROUP_NAME, SCALING_ADJUSTMENT, ADJUSTMENT_TYPE, - COOLDOWN, + COOLDOWN, MIN_ADJUSTMENT_STEP, ) = ( 'AutoScalingGroupName', 'ScalingAdjustment', 'AdjustmentType', - 'Cooldown', + 'Cooldown', 'MinAdjustmentStep', ) EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY = ( @@ -73,6 +69,20 @@ class AWSScalingPolicy(signal_responder.SignalResponder, _('Cooldown period, in seconds.'), update_allowed=True ), + MIN_ADJUSTMENT_STEP: properties.Schema( + properties.Schema.INTEGER, + _('Minimum number of resources that are added or removed ' + 'when the AutoScaling group scales up or down. This can ' + 'be used only when specifying PercentChangeInCapacity ' + 'for the AdjustmentType property.'), + constraints=[ + constraints.Range( + min=0, + ), + ], + update_allowed=True + ), + } attributes_schema = { @@ -81,76 +91,9 @@ class AWSScalingPolicy(signal_responder.SignalResponder, ), } - def handle_create(self): - super(AWSScalingPolicy, self).handle_create() - self.resource_id_set(self._get_user_id()) - - def handle_update(self, json_snippet, tmpl_diff, prop_diff): - """ - If Properties has changed, update self.properties, so we get the new - values during any subsequent adjustment. - """ - if prop_diff: - self.properties = json_snippet.properties(self.properties_schema, - self.context) - def _get_adjustement_type(self): return self.properties[self.ADJUSTMENT_TYPE] - def handle_signal(self, details=None): - # ceilometer sends details like this: - # {u'alarm_id': ID, u'previous': u'ok', u'current': u'alarm', - # u'reason': u'...'}) - # in this policy we currently assume that this gets called - # only when there is an alarm. But the template writer can - # put the policy in all the alarm notifiers (nodata, and ok). - # - # our watchrule has upper case states so lower() them all. - if details is None: - alarm_state = 'alarm' - else: - alarm_state = details.get('current', - details.get('state', 'alarm')).lower() - - LOG.info(_LI('%(name)s Alarm, new state %(state)s'), - {'name': self.name, 'state': alarm_state}) - - if alarm_state != 'alarm': - return - if self._cooldown_inprogress(): - LOG.info(_LI("%(name)s NOT performing scaling action, " - "cooldown %(cooldown)s"), - {'name': self.name, - 'cooldown': self.properties[self.COOLDOWN]}) - return - - asgn_id = self.properties[self.AUTO_SCALING_GROUP_NAME] - group = self.stack.resource_by_refid(asgn_id) - if group is None: - raise exception.NotFound(_('Alarm %(alarm)s could not find ' - 'scaling group named "%(group)s"') % { - 'alarm': self.name, - 'group': asgn_id}) - - LOG.info(_LI('%(name)s Alarm, adjusting Group %(group)s with id ' - '%(asgn_id)s by %(filter)s'), - {'name': self.name, 'group': group.name, 'asgn_id': asgn_id, - 'filter': self.properties[self.SCALING_ADJUSTMENT]}) - adjustment_type = self._get_adjustement_type() - group.adjust(self.properties[self.SCALING_ADJUSTMENT], adjustment_type) - - self._cooldown_timestamp("%s : %s" % - (self.properties[self.ADJUSTMENT_TYPE], - self.properties[self.SCALING_ADJUSTMENT])) - - def _resolve_attribute(self, name): - ''' - heat extension: "AlarmUrl" returns the url to post to the policy - when there is an alarm. - ''' - if name == self.ALARM_URL and self.resource_id is not None: - return six.text_type(self._get_signed_url()) - def FnGetRefId(self): if self.resource_id is not None: return six.text_type(self._get_signed_url()) diff --git a/heat/engine/resources/openstack/heat/scaling_policy.py b/heat/engine/resources/openstack/heat/scaling_policy.py index d8ad84066d..a39882118b 100644 --- a/heat/engine/resources/openstack/heat/scaling_policy.py +++ b/heat/engine/resources/openstack/heat/scaling_policy.py @@ -37,10 +37,10 @@ class AutoScalingPolicy(signal_responder.SignalResponder, """ PROPERTIES = ( AUTO_SCALING_GROUP_NAME, SCALING_ADJUSTMENT, ADJUSTMENT_TYPE, - COOLDOWN, + COOLDOWN, MIN_ADJUSTMENT_STEP ) = ( 'auto_scaling_group_id', 'scaling_adjustment', 'adjustment_type', - 'cooldown', + 'cooldown', 'min_adjustment_step', ) EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY = ( @@ -81,6 +81,20 @@ class AutoScalingPolicy(signal_responder.SignalResponder, _('Cooldown period, in seconds.'), update_allowed=True ), + MIN_ADJUSTMENT_STEP: properties.Schema( + properties.Schema.INTEGER, + _('Minimum number of resources that are added or removed ' + 'when the AutoScaling group scales up or down. This can ' + 'be used only when specifying percent_change_in_capacity ' + 'for the adjustment_type property.'), + constraints=[ + constraints.Range( + min=0, + ), + ], + update_allowed=True + ), + } attributes_schema = { @@ -89,6 +103,20 @@ class AutoScalingPolicy(signal_responder.SignalResponder, ), } + def validate(self): + """ + Add validation for min_adjustment_step + """ + super(AutoScalingPolicy, self).validate() + adjustment_type = self.properties.get(self.ADJUSTMENT_TYPE) + adjustment_step = self.properties.get(self.MIN_ADJUSTMENT_STEP) + if (adjustment_type != self.PERCENT_CHANGE_IN_CAPACITY + and adjustment_step is not None): + raise exception.ResourcePropertyValueDependency( + prop1=self.MIN_ADJUSTMENT_STEP, + prop2=self.ADJUSTMENT_TYPE, + value=self.PERCENT_CHANGE_IN_CAPACITY) + def handle_create(self): super(AutoScalingPolicy, self).handle_create() self.resource_id_set(self._get_user_id()) @@ -146,7 +174,8 @@ class AutoScalingPolicy(signal_responder.SignalResponder, {'name': self.name, 'group': group.name, 'asgn_id': asgn_id, 'filter': self.properties[self.SCALING_ADJUSTMENT]}) adjustment_type = self._get_adjustement_type() - group.adjust(self.properties[self.SCALING_ADJUSTMENT], adjustment_type) + group.adjust(self.properties[self.SCALING_ADJUSTMENT], adjustment_type, + self.properties[self.MIN_ADJUSTMENT_STEP]) self._cooldown_timestamp("%s : %s" % (self.properties[self.ADJUSTMENT_TYPE], diff --git a/heat/tests/autoscaling/test_heat_scaling_group.py b/heat/tests/autoscaling/test_heat_scaling_group.py index 89f3a3c607..db287f9951 100644 --- a/heat/tests/autoscaling/test_heat_scaling_group.py +++ b/heat/tests/autoscaling/test_heat_scaling_group.py @@ -164,6 +164,66 @@ class TestGroupAdjust(common.HeatTestCase): resize.assert_called_once_with(3) cd_stamp.assert_called_once_with('ExactCapacity : 3') + def test_scale_up_min_adjustment(self): + self.patchobject(grouputils, 'get_size', return_value=1) + resize = self.patchobject(self.group, 'resize') + cd_stamp = self.patchobject(self.group, '_cooldown_timestamp') + notify = self.patch('heat.engine.notification.autoscaling.send') + self.patchobject(self.group, '_cooldown_inprogress', + return_value=False) + self.group.adjust(33, adjustment_type='PercentChangeInCapacity', + min_adjustment_step=2) + + expected_notifies = [ + mock.call( + capacity=1, suffix='start', + adjustment_type='PercentChangeInCapacity', + groupname=u'my-group', + message=u'Start resizing the group my-group', + adjustment=33, + stack=self.group.stack), + mock.call( + capacity=3, suffix='end', + adjustment_type='PercentChangeInCapacity', + groupname=u'my-group', + message=u'End resizing the group my-group', + adjustment=33, + stack=self.group.stack)] + + self.assertEqual(expected_notifies, notify.call_args_list) + resize.assert_called_once_with(3) + cd_stamp.assert_called_once_with('PercentChangeInCapacity : 33') + + def test_scale_down_min_adjustment(self): + self.patchobject(grouputils, 'get_size', return_value=3) + resize = self.patchobject(self.group, 'resize') + cd_stamp = self.patchobject(self.group, '_cooldown_timestamp') + notify = self.patch('heat.engine.notification.autoscaling.send') + self.patchobject(self.group, '_cooldown_inprogress', + return_value=False) + self.group.adjust(-33, adjustment_type='PercentChangeInCapacity', + min_adjustment_step=2) + + expected_notifies = [ + mock.call( + capacity=3, suffix='start', + adjustment_type='PercentChangeInCapacity', + groupname=u'my-group', + message=u'Start resizing the group my-group', + adjustment=-33, + stack=self.group.stack), + mock.call( + capacity=1, suffix='end', + adjustment_type='PercentChangeInCapacity', + groupname=u'my-group', + message=u'End resizing the group my-group', + adjustment=-33, + stack=self.group.stack)] + + self.assertEqual(expected_notifies, notify.call_args_list) + resize.assert_called_once_with(1) + cd_stamp.assert_called_once_with('PercentChangeInCapacity : -33') + def test_scaling_policy_cooldown_ok(self): self.patchobject(grouputils, 'get_members', return_value=[]) resize = self.patchobject(self.group, 'resize') diff --git a/heat/tests/autoscaling/test_heat_scaling_policy.py b/heat/tests/autoscaling/test_heat_scaling_policy.py index 8ba9db5152..9b78eb00fb 100644 --- a/heat/tests/autoscaling/test_heat_scaling_policy.py +++ b/heat/tests/autoscaling/test_heat_scaling_policy.py @@ -20,9 +20,11 @@ import six from heat.common import exception from heat.common import template_format +from heat.engine import resource from heat.engine import scheduler from heat.tests.autoscaling import inline_templates from heat.tests import common +from heat.tests import generic_resource from heat.tests import utils @@ -33,6 +35,8 @@ as_params = inline_templates.as_params class TestAutoScalingPolicy(common.HeatTestCase): def setUp(self): super(TestAutoScalingPolicy, self).setUp() + resource._register_class('ResourceWithPropsAndAttrs', + generic_resource.ResourceWithPropsAndAttrs) cfg.CONF.set_default('heat_waitcondition_server_url', 'http://server.test:8000/v1/waitcondition') @@ -43,6 +47,32 @@ class TestAutoScalingPolicy(common.HeatTestCase): self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) return rsrc + def test_validate_scaling_policy_ok(self): + t = template_format.parse(as_template) + t['resources']['my-policy']['properties'][ + 'scaling_adjustment'] = 33 + t['resources']['my-policy']['properties'][ + 'adjustment_type'] = 'percent_change_in_capacity' + t['resources']['my-policy']['properties'][ + 'min_adjustment_step'] = 2 + stack = utils.parse_stack(t) + self.assertIsNone(stack.validate()) + + def test_validate_scaling_policy_error(self): + t = template_format.parse(as_template) + t['resources']['my-policy']['properties'][ + 'scaling_adjustment'] = 1 + t['resources']['my-policy']['properties'][ + 'adjustment_type'] = 'change_in_capacity' + t['resources']['my-policy']['properties'][ + 'min_adjustment_step'] = 2 + stack = utils.parse_stack(t) + ex = self.assertRaises(exception.ResourcePropertyValueDependency, + stack.validate) + self.assertIn('min_adjustment_step property should only ' + 'be specified for adjustment_type with ' + 'value percent_change_in_capacity.', six.text_type(ex)) + def test_scaling_policy_bad_group(self): t = template_format.parse(inline_templates.as_heat_template_bad_group) stack = utils.parse_stack(t) @@ -92,7 +122,7 @@ class TestAutoScalingPolicy(common.HeatTestCase): return_value=False) as mock_cip: pol.handle_signal(details=test) mock_cip.assert_called_once_with() - group.adjust.assert_called_once_with(1, 'ChangeInCapacity') + group.adjust.assert_called_once_with(1, 'ChangeInCapacity', None) class TestCooldownMixin(common.HeatTestCase): diff --git a/heat/tests/autoscaling/test_new_capacity.py b/heat/tests/autoscaling/test_new_capacity.py index b7d1fb009c..8588e34ea1 100644 --- a/heat/tests/autoscaling/test_new_capacity.py +++ b/heat/tests/autoscaling/test_new_capacity.py @@ -22,52 +22,86 @@ class TestCapacityChanges(common.HeatTestCase): # r rounded (+up, -down) # e EXACT_CAPACITY # p PERCENT_CHANGE_IN_CAPACITY + # s MIN_ADJUSTMENT_STEP scenarios = [ ('+n', dict(current=2, adjustment=3, adjustment_type=asc.CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=10, expected=5)), ('-n', dict(current=6, adjustment=-2, adjustment_type=asc.CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=5, expected=4)), ('+nb', dict(current=2, adjustment=8, adjustment_type=asc.CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=5, expected=5)), ('-nb', dict(current=2, adjustment=-10, adjustment_type=asc.CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=1, maximum=5, expected=1)), ('e', dict(current=2, adjustment=4, adjustment_type=asc.EXACT_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=5, expected=4)), ('+eb', dict(current=2, adjustment=11, adjustment_type=asc.EXACT_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=5, expected=5)), ('-eb', dict(current=4, adjustment=1, adjustment_type=asc.EXACT_CAPACITY, + min_adjustment_step=None, minimum=3, maximum=5, expected=3)), ('+p', dict(current=4, adjustment=50, adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=1, maximum=10, expected=6)), ('-p', dict(current=4, adjustment=-25, adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=1, maximum=10, expected=3)), ('+pb', dict(current=4, adjustment=100, adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=1, maximum=6, expected=6)), ('-pb', dict(current=6, adjustment=-50, adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=4, maximum=10, expected=4)), ('-p+r', dict(current=2, adjustment=-33, adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=10, expected=1)), ('+p+r', dict(current=1, adjustment=33, adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=10, expected=2)), ('-p-r', dict(current=2, adjustment=-66, adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=10, expected=1)), ('+p-r', dict(current=1, adjustment=225, adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=None, minimum=0, maximum=10, expected=3)), + ('+ps', dict(current=1, adjustment=100, + adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=3, + minimum=0, maximum=10, expected=4)), + ('+p+rs', dict(current=1, adjustment=33, + adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=2, + minimum=0, maximum=10, expected=3)), + ('+p-rs', dict(current=1, adjustment=325, + adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=2, + minimum=0, maximum=10, expected=4)), + ('-p-rs', dict(current=3, adjustment=-25, + adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY, + min_adjustment_step=2, + minimum=0, maximum=10, expected=1)), + + ] def test_calc(self): @@ -75,4 +109,5 @@ class TestCapacityChanges(common.HeatTestCase): asc._calculate_new_capacity( self.current, self.adjustment, self.adjustment_type, + self.min_adjustment_step, self.minimum, self.maximum)) diff --git a/heat/tests/autoscaling/test_scaling_group.py b/heat/tests/autoscaling/test_scaling_group.py index 81a052f220..650fceab26 100644 --- a/heat/tests/autoscaling/test_scaling_group.py +++ b/heat/tests/autoscaling/test_scaling_group.py @@ -345,6 +345,66 @@ class TestGroupAdjust(common.HeatTestCase): resize.assert_called_once_with(3) cd_stamp.assert_called_once_with('ExactCapacity : 3') + def test_scale_up_min_adjustment(self): + self.patchobject(grouputils, 'get_size', return_value=1) + resize = self.patchobject(self.group, 'resize') + cd_stamp = self.patchobject(self.group, '_cooldown_timestamp') + notify = self.patch('heat.engine.notification.autoscaling.send') + self.patchobject(self.group, '_cooldown_inprogress', + return_value=False) + self.group.adjust(33, adjustment_type='PercentChangeInCapacity', + min_adjustment_step=2) + + expected_notifies = [ + mock.call( + capacity=1, suffix='start', + adjustment_type='PercentChangeInCapacity', + groupname=u'WebServerGroup', + message=u'Start resizing the group WebServerGroup', + adjustment=33, + stack=self.group.stack), + mock.call( + capacity=3, suffix='end', + adjustment_type='PercentChangeInCapacity', + groupname=u'WebServerGroup', + message=u'End resizing the group WebServerGroup', + adjustment=33, + stack=self.group.stack)] + + self.assertEqual(expected_notifies, notify.call_args_list) + resize.assert_called_once_with(3) + cd_stamp.assert_called_once_with('PercentChangeInCapacity : 33') + + def test_scale_down_min_adjustment(self): + self.patchobject(grouputils, 'get_size', return_value=5) + resize = self.patchobject(self.group, 'resize') + cd_stamp = self.patchobject(self.group, '_cooldown_timestamp') + notify = self.patch('heat.engine.notification.autoscaling.send') + self.patchobject(self.group, '_cooldown_inprogress', + return_value=False) + self.group.adjust(-33, adjustment_type='PercentChangeInCapacity', + min_adjustment_step=2) + + expected_notifies = [ + mock.call( + capacity=5, suffix='start', + adjustment_type='PercentChangeInCapacity', + groupname=u'WebServerGroup', + message=u'Start resizing the group WebServerGroup', + adjustment=-33, + stack=self.group.stack), + mock.call( + capacity=3, suffix='end', + adjustment_type='PercentChangeInCapacity', + groupname=u'WebServerGroup', + message=u'End resizing the group WebServerGroup', + adjustment=-33, + stack=self.group.stack)] + + self.assertEqual(expected_notifies, notify.call_args_list) + resize.assert_called_once_with(3) + cd_stamp.assert_called_once_with('PercentChangeInCapacity : -33') + def test_scaling_policy_cooldown_ok(self): self.patchobject(grouputils, 'get_members', return_value=[]) resize = self.patchobject(self.group, 'resize') diff --git a/heat/tests/autoscaling/test_scaling_policy.py b/heat/tests/autoscaling/test_scaling_policy.py index 4a529ab16c..ddc3fb4b43 100644 --- a/heat/tests/autoscaling/test_scaling_policy.py +++ b/heat/tests/autoscaling/test_scaling_policy.py @@ -43,6 +43,34 @@ class TestAutoScalingPolicy(common.HeatTestCase): self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) return rsrc + def test_validate_scaling_policy_ok(self): + t = template_format.parse(inline_templates.as_template) + t['Resources']['WebServerScaleUpPolicy']['Properties'][ + 'ScalingAdjustment'] = 33 + t['Resources']['WebServerScaleUpPolicy']['Properties'][ + 'AdjustmentType'] = 'PercentChangeInCapacity' + t['Resources']['WebServerScaleUpPolicy']['Properties'][ + 'MinAdjustmentStep'] = 2 + stack = utils.parse_stack(t, params=as_params) + self.policy = stack['WebServerScaleUpPolicy'] + self.assertIsNone(self.policy.validate()) + + def test_validate_scaling_policy_error(self): + t = template_format.parse(inline_templates.as_template) + t['Resources']['WebServerScaleUpPolicy']['Properties'][ + 'ScalingAdjustment'] = 1 + t['Resources']['WebServerScaleUpPolicy']['Properties'][ + 'AdjustmentType'] = 'ChangeInCapacity' + t['Resources']['WebServerScaleUpPolicy']['Properties'][ + 'MinAdjustmentStep'] = 2 + stack = utils.parse_stack(t, params=as_params) + self.policy = stack['WebServerScaleUpPolicy'] + ex = self.assertRaises(exception.ResourcePropertyValueDependency, + self.policy.validate) + self.assertIn('MinAdjustmentStep property should only ' + 'be specified for AdjustmentType with ' + 'value PercentChangeInCapacity.', six.text_type(ex)) + def test_scaling_policy_bad_group(self): t = template_format.parse(inline_templates.as_template_bad_group) stack = utils.parse_stack(t, params=as_params) @@ -93,7 +121,7 @@ class TestAutoScalingPolicy(common.HeatTestCase): return_value=False) as mock_cip: pol.handle_signal(details=test) mock_cip.assert_called_once_with() - group.adjust.assert_called_once_with(1, 'ChangeInCapacity') + group.adjust.assert_called_once_with(1, 'ChangeInCapacity', None) class TestCooldownMixin(common.HeatTestCase):