Merge "Add min_adjustment_step property to ScalingPolicy"
This commit is contained in:
commit
729aa08f4a
|
@ -381,6 +381,11 @@ class ResourcePropertyDependency(HeatException):
|
||||||
msg_fmt = _('%(prop1)s cannot be specified without %(prop2)s.')
|
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):
|
class PropertyUnspecifiedError(HeatException):
|
||||||
msg_fmt = _('At least one of the following properties '
|
msg_fmt = _('At least one of the following properties '
|
||||||
'must be specified: %(props)s')
|
'must be specified: %(props)s')
|
||||||
|
|
|
@ -41,12 +41,18 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _calculate_new_capacity(current, adjustment, adjustment_type,
|
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
|
Given the current capacity, calculates the new capacity which results
|
||||||
from applying the given adjustment of the given adjustment-type. The
|
from applying the given adjustment of the given adjustment-type. The
|
||||||
new capacity will be kept within the maximum and minimum bounds.
|
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:
|
if adjustment_type == CHANGE_IN_CAPACITY:
|
||||||
new_capacity = current + adjustment
|
new_capacity = current + adjustment
|
||||||
elif adjustment_type == EXACT_CAPACITY:
|
elif adjustment_type == EXACT_CAPACITY:
|
||||||
|
@ -60,7 +66,8 @@ def _calculate_new_capacity(current, adjustment, adjustment_type,
|
||||||
else:
|
else:
|
||||||
rounded = int(math.floor(delta) if delta > 0.0
|
rounded = int(math.floor(delta) if delta > 0.0
|
||||||
else math.ceil(delta))
|
else math.ceil(delta))
|
||||||
new_capacity = current + rounded
|
adjustment = _get_minimum_adjustment(rounded, min_adjustment_step)
|
||||||
|
new_capacity = current + adjustment
|
||||||
|
|
||||||
if new_capacity > maximum:
|
if new_capacity > maximum:
|
||||||
LOG.debug('truncating growth to %s' % maximum)
|
LOG.debug('truncating growth to %s' % maximum)
|
||||||
|
@ -293,7 +300,8 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
|
||||||
current_capacity = grouputils.get_size(self)
|
current_capacity = grouputils.get_size(self)
|
||||||
self.adjust(current_capacity, adjustment_type=EXACT_CAPACITY)
|
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.
|
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]
|
upper = self.properties[self.MAX_SIZE]
|
||||||
|
|
||||||
new_capacity = _calculate_new_capacity(capacity, adjustment,
|
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.
|
# send a notification before, on-error and on-success.
|
||||||
notif = {
|
notif = {
|
||||||
|
|
|
@ -14,26 +14,22 @@
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from heat.common import exception
|
|
||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
from heat.common.i18n import _LI
|
|
||||||
from heat.engine import attributes
|
from heat.engine import attributes
|
||||||
from heat.engine import constraints
|
from heat.engine import constraints
|
||||||
from heat.engine import properties
|
from heat.engine import properties
|
||||||
from heat.engine.resources import signal_responder
|
from heat.engine.resources.openstack.heat import scaling_policy as heat_sp
|
||||||
from heat.scaling import cooldown
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AWSScalingPolicy(signal_responder.SignalResponder,
|
class AWSScalingPolicy(heat_sp.AutoScalingPolicy):
|
||||||
cooldown.CooldownMixin):
|
|
||||||
PROPERTIES = (
|
PROPERTIES = (
|
||||||
AUTO_SCALING_GROUP_NAME, SCALING_ADJUSTMENT, ADJUSTMENT_TYPE,
|
AUTO_SCALING_GROUP_NAME, SCALING_ADJUSTMENT, ADJUSTMENT_TYPE,
|
||||||
COOLDOWN,
|
COOLDOWN, MIN_ADJUSTMENT_STEP,
|
||||||
) = (
|
) = (
|
||||||
'AutoScalingGroupName', 'ScalingAdjustment', 'AdjustmentType',
|
'AutoScalingGroupName', 'ScalingAdjustment', 'AdjustmentType',
|
||||||
'Cooldown',
|
'Cooldown', 'MinAdjustmentStep',
|
||||||
)
|
)
|
||||||
|
|
||||||
EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY = (
|
EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY = (
|
||||||
|
@ -73,6 +69,20 @@ class AWSScalingPolicy(signal_responder.SignalResponder,
|
||||||
_('Cooldown period, in seconds.'),
|
_('Cooldown period, in seconds.'),
|
||||||
update_allowed=True
|
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 = {
|
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):
|
def _get_adjustement_type(self):
|
||||||
return self.properties[self.ADJUSTMENT_TYPE]
|
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):
|
def FnGetRefId(self):
|
||||||
if self.resource_id is not None:
|
if self.resource_id is not None:
|
||||||
return six.text_type(self._get_signed_url())
|
return six.text_type(self._get_signed_url())
|
||||||
|
|
|
@ -37,10 +37,10 @@ class AutoScalingPolicy(signal_responder.SignalResponder,
|
||||||
"""
|
"""
|
||||||
PROPERTIES = (
|
PROPERTIES = (
|
||||||
AUTO_SCALING_GROUP_NAME, SCALING_ADJUSTMENT, ADJUSTMENT_TYPE,
|
AUTO_SCALING_GROUP_NAME, SCALING_ADJUSTMENT, ADJUSTMENT_TYPE,
|
||||||
COOLDOWN,
|
COOLDOWN, MIN_ADJUSTMENT_STEP
|
||||||
) = (
|
) = (
|
||||||
'auto_scaling_group_id', 'scaling_adjustment', 'adjustment_type',
|
'auto_scaling_group_id', 'scaling_adjustment', 'adjustment_type',
|
||||||
'cooldown',
|
'cooldown', 'min_adjustment_step',
|
||||||
)
|
)
|
||||||
|
|
||||||
EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY = (
|
EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY = (
|
||||||
|
@ -81,6 +81,20 @@ class AutoScalingPolicy(signal_responder.SignalResponder,
|
||||||
_('Cooldown period, in seconds.'),
|
_('Cooldown period, in seconds.'),
|
||||||
update_allowed=True
|
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 = {
|
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):
|
def handle_create(self):
|
||||||
super(AutoScalingPolicy, self).handle_create()
|
super(AutoScalingPolicy, self).handle_create()
|
||||||
self.resource_id_set(self._get_user_id())
|
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,
|
{'name': self.name, 'group': group.name, 'asgn_id': asgn_id,
|
||||||
'filter': self.properties[self.SCALING_ADJUSTMENT]})
|
'filter': self.properties[self.SCALING_ADJUSTMENT]})
|
||||||
adjustment_type = self._get_adjustement_type()
|
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._cooldown_timestamp("%s : %s" %
|
||||||
(self.properties[self.ADJUSTMENT_TYPE],
|
(self.properties[self.ADJUSTMENT_TYPE],
|
||||||
|
|
|
@ -164,6 +164,66 @@ class TestGroupAdjust(common.HeatTestCase):
|
||||||
resize.assert_called_once_with(3)
|
resize.assert_called_once_with(3)
|
||||||
cd_stamp.assert_called_once_with('ExactCapacity : 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):
|
def test_scaling_policy_cooldown_ok(self):
|
||||||
self.patchobject(grouputils, 'get_members', return_value=[])
|
self.patchobject(grouputils, 'get_members', return_value=[])
|
||||||
resize = self.patchobject(self.group, 'resize')
|
resize = self.patchobject(self.group, 'resize')
|
||||||
|
|
|
@ -20,9 +20,11 @@ import six
|
||||||
|
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
from heat.common import template_format
|
from heat.common import template_format
|
||||||
|
from heat.engine import resource
|
||||||
from heat.engine import scheduler
|
from heat.engine import scheduler
|
||||||
from heat.tests.autoscaling import inline_templates
|
from heat.tests.autoscaling import inline_templates
|
||||||
from heat.tests import common
|
from heat.tests import common
|
||||||
|
from heat.tests import generic_resource
|
||||||
from heat.tests import utils
|
from heat.tests import utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,6 +35,8 @@ as_params = inline_templates.as_params
|
||||||
class TestAutoScalingPolicy(common.HeatTestCase):
|
class TestAutoScalingPolicy(common.HeatTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestAutoScalingPolicy, self).setUp()
|
super(TestAutoScalingPolicy, self).setUp()
|
||||||
|
resource._register_class('ResourceWithPropsAndAttrs',
|
||||||
|
generic_resource.ResourceWithPropsAndAttrs)
|
||||||
cfg.CONF.set_default('heat_waitcondition_server_url',
|
cfg.CONF.set_default('heat_waitcondition_server_url',
|
||||||
'http://server.test:8000/v1/waitcondition')
|
'http://server.test:8000/v1/waitcondition')
|
||||||
|
|
||||||
|
@ -43,6 +47,32 @@ class TestAutoScalingPolicy(common.HeatTestCase):
|
||||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||||
return rsrc
|
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):
|
def test_scaling_policy_bad_group(self):
|
||||||
t = template_format.parse(inline_templates.as_heat_template_bad_group)
|
t = template_format.parse(inline_templates.as_heat_template_bad_group)
|
||||||
stack = utils.parse_stack(t)
|
stack = utils.parse_stack(t)
|
||||||
|
@ -92,7 +122,7 @@ class TestAutoScalingPolicy(common.HeatTestCase):
|
||||||
return_value=False) as mock_cip:
|
return_value=False) as mock_cip:
|
||||||
pol.handle_signal(details=test)
|
pol.handle_signal(details=test)
|
||||||
mock_cip.assert_called_once_with()
|
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):
|
class TestCooldownMixin(common.HeatTestCase):
|
||||||
|
|
|
@ -22,52 +22,86 @@ class TestCapacityChanges(common.HeatTestCase):
|
||||||
# r rounded (+up, -down)
|
# r rounded (+up, -down)
|
||||||
# e EXACT_CAPACITY
|
# e EXACT_CAPACITY
|
||||||
# p PERCENT_CHANGE_IN_CAPACITY
|
# p PERCENT_CHANGE_IN_CAPACITY
|
||||||
|
# s MIN_ADJUSTMENT_STEP
|
||||||
scenarios = [
|
scenarios = [
|
||||||
('+n', dict(current=2, adjustment=3,
|
('+n', dict(current=2, adjustment=3,
|
||||||
adjustment_type=asc.CHANGE_IN_CAPACITY,
|
adjustment_type=asc.CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=10, expected=5)),
|
minimum=0, maximum=10, expected=5)),
|
||||||
('-n', dict(current=6, adjustment=-2,
|
('-n', dict(current=6, adjustment=-2,
|
||||||
adjustment_type=asc.CHANGE_IN_CAPACITY,
|
adjustment_type=asc.CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=5, expected=4)),
|
minimum=0, maximum=5, expected=4)),
|
||||||
('+nb', dict(current=2, adjustment=8,
|
('+nb', dict(current=2, adjustment=8,
|
||||||
adjustment_type=asc.CHANGE_IN_CAPACITY,
|
adjustment_type=asc.CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=5, expected=5)),
|
minimum=0, maximum=5, expected=5)),
|
||||||
('-nb', dict(current=2, adjustment=-10,
|
('-nb', dict(current=2, adjustment=-10,
|
||||||
adjustment_type=asc.CHANGE_IN_CAPACITY,
|
adjustment_type=asc.CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=1, maximum=5, expected=1)),
|
minimum=1, maximum=5, expected=1)),
|
||||||
('e', dict(current=2, adjustment=4,
|
('e', dict(current=2, adjustment=4,
|
||||||
adjustment_type=asc.EXACT_CAPACITY,
|
adjustment_type=asc.EXACT_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=5, expected=4)),
|
minimum=0, maximum=5, expected=4)),
|
||||||
('+eb', dict(current=2, adjustment=11,
|
('+eb', dict(current=2, adjustment=11,
|
||||||
adjustment_type=asc.EXACT_CAPACITY,
|
adjustment_type=asc.EXACT_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=5, expected=5)),
|
minimum=0, maximum=5, expected=5)),
|
||||||
('-eb', dict(current=4, adjustment=1,
|
('-eb', dict(current=4, adjustment=1,
|
||||||
adjustment_type=asc.EXACT_CAPACITY,
|
adjustment_type=asc.EXACT_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=3, maximum=5, expected=3)),
|
minimum=3, maximum=5, expected=3)),
|
||||||
('+p', dict(current=4, adjustment=50,
|
('+p', dict(current=4, adjustment=50,
|
||||||
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=1, maximum=10, expected=6)),
|
minimum=1, maximum=10, expected=6)),
|
||||||
('-p', dict(current=4, adjustment=-25,
|
('-p', dict(current=4, adjustment=-25,
|
||||||
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=1, maximum=10, expected=3)),
|
minimum=1, maximum=10, expected=3)),
|
||||||
('+pb', dict(current=4, adjustment=100,
|
('+pb', dict(current=4, adjustment=100,
|
||||||
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=1, maximum=6, expected=6)),
|
minimum=1, maximum=6, expected=6)),
|
||||||
('-pb', dict(current=6, adjustment=-50,
|
('-pb', dict(current=6, adjustment=-50,
|
||||||
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=4, maximum=10, expected=4)),
|
minimum=4, maximum=10, expected=4)),
|
||||||
('-p+r', dict(current=2, adjustment=-33,
|
('-p+r', dict(current=2, adjustment=-33,
|
||||||
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=10, expected=1)),
|
minimum=0, maximum=10, expected=1)),
|
||||||
('+p+r', dict(current=1, adjustment=33,
|
('+p+r', dict(current=1, adjustment=33,
|
||||||
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=10, expected=2)),
|
minimum=0, maximum=10, expected=2)),
|
||||||
('-p-r', dict(current=2, adjustment=-66,
|
('-p-r', dict(current=2, adjustment=-66,
|
||||||
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=10, expected=1)),
|
minimum=0, maximum=10, expected=1)),
|
||||||
('+p-r', dict(current=1, adjustment=225,
|
('+p-r', dict(current=1, adjustment=225,
|
||||||
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
adjustment_type=asc.PERCENT_CHANGE_IN_CAPACITY,
|
||||||
|
min_adjustment_step=None,
|
||||||
minimum=0, maximum=10, expected=3)),
|
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):
|
def test_calc(self):
|
||||||
|
@ -75,4 +109,5 @@ class TestCapacityChanges(common.HeatTestCase):
|
||||||
asc._calculate_new_capacity(
|
asc._calculate_new_capacity(
|
||||||
self.current, self.adjustment,
|
self.current, self.adjustment,
|
||||||
self.adjustment_type,
|
self.adjustment_type,
|
||||||
|
self.min_adjustment_step,
|
||||||
self.minimum, self.maximum))
|
self.minimum, self.maximum))
|
||||||
|
|
|
@ -345,6 +345,66 @@ class TestGroupAdjust(common.HeatTestCase):
|
||||||
resize.assert_called_once_with(3)
|
resize.assert_called_once_with(3)
|
||||||
cd_stamp.assert_called_once_with('ExactCapacity : 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):
|
def test_scaling_policy_cooldown_ok(self):
|
||||||
self.patchobject(grouputils, 'get_members', return_value=[])
|
self.patchobject(grouputils, 'get_members', return_value=[])
|
||||||
resize = self.patchobject(self.group, 'resize')
|
resize = self.patchobject(self.group, 'resize')
|
||||||
|
|
|
@ -43,6 +43,34 @@ class TestAutoScalingPolicy(common.HeatTestCase):
|
||||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||||
return rsrc
|
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):
|
def test_scaling_policy_bad_group(self):
|
||||||
t = template_format.parse(inline_templates.as_template_bad_group)
|
t = template_format.parse(inline_templates.as_template_bad_group)
|
||||||
stack = utils.parse_stack(t, params=as_params)
|
stack = utils.parse_stack(t, params=as_params)
|
||||||
|
@ -93,7 +121,7 @@ class TestAutoScalingPolicy(common.HeatTestCase):
|
||||||
return_value=False) as mock_cip:
|
return_value=False) as mock_cip:
|
||||||
pol.handle_signal(details=test)
|
pol.handle_signal(details=test)
|
||||||
mock_cip.assert_called_once_with()
|
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):
|
class TestCooldownMixin(common.HeatTestCase):
|
||||||
|
|
Loading…
Reference in New Issue