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):