Move new capacity calculation to scaling library

Change-Id: Ief647b68bcd54adf3c7b2afbd32555a789e4c38c
This commit is contained in:
Rabi Mishra 2015-09-08 18:42:07 +05:30
parent b3444583b8
commit b336c29589
6 changed files with 124 additions and 102 deletions

View File

@ -11,8 +11,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import math
from oslo_log import log as logging
from oslo_utils import excutils
import six
@ -32,54 +30,11 @@ from heat.engine.resources.openstack.heat import instance_group as instgrp
from heat.engine import rsrc_defn
from heat.engine import support
from heat.scaling import cooldown
from heat.scaling import scalingutil as sc_util
LOG = logging.getLogger(__name__)
(EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY) = (
'ExactCapacity', 'ChangeInCapacity', 'PercentChangeInCapacity')
def _calculate_new_capacity(current, adjustment, adjustment_type,
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:
new_capacity = adjustment
else:
# PercentChangeInCapacity
delta = current * adjustment / 100.0
if math.fabs(delta) < 1.0:
rounded = int(math.ceil(delta) if delta > 0.0
else math.floor(delta))
else:
rounded = int(math.floor(delta) if delta > 0.0
else math.ceil(delta))
adjustment = _get_minimum_adjustment(rounded, min_adjustment_step)
new_capacity = current + adjustment
if new_capacity > maximum:
LOG.debug('truncating growth to %s' % maximum)
return maximum
if new_capacity < minimum:
LOG.debug('truncating shrinkage to %s' % minimum)
return minimum
return new_capacity
class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
support_status = support.SupportStatus(version='2014.1')
@ -273,7 +228,8 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
done = super(AutoScalingGroup, self).check_create_complete(task)
if done:
self._cooldown_timestamp(
"%s : %s" % (EXACT_CAPACITY, grouputils.get_size(self)))
"%s : %s" % (sc_util.CFN_EXACT_CAPACITY,
grouputils.get_size(self)))
return done
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
@ -296,12 +252,14 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
if self.properties[self.DESIRED_CAPACITY] is not None:
self.adjust(self.properties[self.DESIRED_CAPACITY],
adjustment_type=EXACT_CAPACITY)
adjustment_type=sc_util.CFN_EXACT_CAPACITY)
else:
current_capacity = grouputils.get_size(self)
self.adjust(current_capacity, adjustment_type=EXACT_CAPACITY)
self.adjust(current_capacity,
adjustment_type=sc_util.CFN_EXACT_CAPACITY)
def adjust(self, adjustment, adjustment_type=CHANGE_IN_CAPACITY,
def adjust(self, adjustment,
adjustment_type=sc_util.CFN_CHANGE_IN_CAPACITY,
min_adjustment_step=None, signal=False):
"""
Adjust the size of the scaling group if the cooldown permits.
@ -320,10 +278,10 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
lower = self.properties[self.MIN_SIZE]
upper = self.properties[self.MAX_SIZE]
new_capacity = _calculate_new_capacity(capacity, adjustment,
adjustment_type,
min_adjustment_step,
lower, upper)
new_capacity = sc_util.calculate_new_capacity(capacity, adjustment,
adjustment_type,
min_adjustment_step,
lower, upper)
# send a notification before, on-error and on-success.
notif = {

View File

@ -14,11 +14,13 @@
from oslo_log import log as logging
import six
from heat.common import exception
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources.openstack.heat import scaling_policy as heat_sp
from heat.scaling import scalingutil as sc_util
LOG = logging.getLogger(__name__)
@ -32,9 +34,6 @@ class AWSScalingPolicy(heat_sp.AutoScalingPolicy):
'Cooldown', 'MinAdjustmentStep',
)
EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY = (
'ExactCapacity', 'ChangeInCapacity', 'PercentChangeInCapacity')
ATTRIBUTES = (
ALARM_URL,
) = (
@ -58,9 +57,10 @@ class AWSScalingPolicy(heat_sp.AutoScalingPolicy):
_('Type of adjustment (absolute or percentage).'),
required=True,
constraints=[
constraints.AllowedValues([CHANGE_IN_CAPACITY,
EXACT_CAPACITY,
PERCENT_CHANGE_IN_CAPACITY]),
constraints.AllowedValues(
[sc_util.CFN_CHANGE_IN_CAPACITY,
sc_util.CFN_EXACT_CAPACITY,
sc_util.CFN_PERCENT_CHANGE_IN_CAPACITY]),
],
update_allowed=True
),
@ -92,8 +92,15 @@ class AWSScalingPolicy(heat_sp.AutoScalingPolicy):
),
}
def _get_adjustement_type(self):
return self.properties[self.ADJUSTMENT_TYPE]
def _validate_min_adjustment_step(self):
adjustment_type = self.properties.get(self.ADJUSTMENT_TYPE)
adjustment_step = self.properties.get(self.MIN_ADJUSTMENT_STEP)
if (adjustment_type != sc_util.CFN_PERCENT_CHANGE_IN_CAPACITY
and adjustment_step is not None):
raise exception.ResourcePropertyValueDependency(
prop1=self.MIN_ADJUSTMENT_STEP,
prop2=self.ADJUSTMENT_TYPE,
value=sc_util.CFN_PERCENT_CHANGE_IN_CAPACITY)
def FnGetRefId(self):
if self.resource_id is not None:

View File

@ -24,6 +24,7 @@ from heat.engine import resource
from heat.engine.resources import signal_responder
from heat.engine import support
from heat.scaling import cooldown
from heat.scaling import scalingutil as sc_util
LOG = logging.getLogger(__name__)
@ -44,9 +45,6 @@ class AutoScalingPolicy(signal_responder.SignalResponder,
'cooldown', 'min_adjustment_step',
)
EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY = (
'exact_capacity', 'change_in_capacity', 'percent_change_in_capacity')
ATTRIBUTES = (
ALARM_URL, SIGNAL_URL
) = (
@ -71,9 +69,10 @@ class AutoScalingPolicy(signal_responder.SignalResponder,
_('Type of adjustment (absolute or percentage).'),
required=True,
constraints=[
constraints.AllowedValues([CHANGE_IN_CAPACITY,
EXACT_CAPACITY,
PERCENT_CHANGE_IN_CAPACITY]),
constraints.AllowedValues(
[sc_util.CHANGE_IN_CAPACITY,
sc_util.EXACT_CAPACITY,
sc_util.PERCENT_CHANGE_IN_CAPACITY]),
],
update_allowed=True
),
@ -115,14 +114,17 @@ class AutoScalingPolicy(signal_responder.SignalResponder,
Add validation for min_adjustment_step
"""
super(AutoScalingPolicy, self).validate()
self._validate_min_adjustment_step()
def _validate_min_adjustment_step(self):
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
if (adjustment_type != sc_util.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)
value=sc_util.PERCENT_CHANGE_IN_CAPACITY)
def handle_create(self):
super(AutoScalingPolicy, self).handle_create()
@ -137,10 +139,6 @@ class AutoScalingPolicy(signal_responder.SignalResponder,
self.properties = json_snippet.properties(self.properties_schema,
self.context)
def _get_adjustement_type(self):
adjustment_type = self.properties[self.ADJUSTMENT_TYPE]
return ''.join([t.capitalize() for t in adjustment_type.split('_')])
def handle_signal(self, details=None):
# ceilometer sends details like this:
# {u'alarm_id': ID, u'previous': u'ok', u'current': u'alarm',
@ -182,9 +180,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,
self.properties[self.ADJUSTMENT_TYPE],
self.properties[self.MIN_ADJUSTMENT_STEP],
signal=True)

View File

@ -0,0 +1,60 @@
# 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 math
ADJUSTMENT_TYPES = (
EXACT_CAPACITY, CHANGE_IN_CAPACITY, PERCENT_CHANGE_IN_CAPACITY) = (
'exact_capacity', 'change_in_capacity', 'percent_change_in_capacity')
CFN_ADJUSTMENT_TYPES = (
CFN_EXACT_CAPACITY, CFN_CHANGE_IN_CAPACITY,
CFN_PERCENT_CHANGE_IN_CAPACITY) = ('ExactCapacity', 'ChangeInCapacity',
'PercentChangeInCapacity')
def calculate_new_capacity(current, adjustment, adjustment_type,
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 in (CHANGE_IN_CAPACITY, CFN_CHANGE_IN_CAPACITY):
new_capacity = current + adjustment
elif adjustment_type in (EXACT_CAPACITY, CFN_EXACT_CAPACITY):
new_capacity = adjustment
else:
# PercentChangeInCapacity
delta = current * adjustment / 100.0
if math.fabs(delta) < 1.0:
rounded = int(math.ceil(delta) if delta > 0.0
else math.floor(delta))
else:
rounded = int(math.floor(delta) if delta > 0.0
else math.ceil(delta))
adjustment = _get_minimum_adjustment(rounded, min_adjustment_step)
new_capacity = current + adjustment
if new_capacity > maximum:
return maximum
if new_capacity < minimum:
return minimum
return new_capacity

View File

@ -115,7 +115,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', None,
group.adjust.assert_called_once_with(1, 'change_in_capacity', None,
signal=True)
@ -137,7 +137,7 @@ class TestCooldownMixin(common.HeatTestCase):
now = timeutils.utcnow()
previous_meta = {'cooldown': {
now.isoformat(): 'ChangeInCapacity : 1'}}
now.isoformat(): 'change_in_capacity : 1'}}
self.patchobject(pol, 'metadata_get', return_value=previous_meta)
self.assertTrue(pol._cooldown_inprogress())
@ -158,7 +158,7 @@ class TestCooldownMixin(common.HeatTestCase):
awhile_ago = timeutils.utcnow() - datetime.timedelta(seconds=100)
previous_meta = {
'cooldown': {
awhile_ago.isoformat(): 'ChangeInCapacity : 1'
awhile_ago.isoformat(): 'change_in_capacity : 1'
},
'scaling_in_progress': False
}
@ -177,7 +177,7 @@ class TestCooldownMixin(common.HeatTestCase):
now = timeutils.utcnow()
previous_meta = {'cooldown': {
now.isoformat(): 'ChangeInCapacity : 1'}}
now.isoformat(): 'change_in_capacity : 1'}}
self.patchobject(pol, 'metadata_get', return_value=previous_meta)
self.assertFalse(pol._cooldown_inprogress())
@ -194,7 +194,7 @@ class TestCooldownMixin(common.HeatTestCase):
now = timeutils.utcnow()
previous_meta = {'cooldown': {
now.isoformat(): 'ChangeInCapacity : 1'}}
now.isoformat(): 'change_in_capacity : 1'}}
self.patchobject(pol, 'metadata_get', return_value=previous_meta)
self.assertFalse(pol._cooldown_inprogress())

View File

@ -11,7 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from heat.engine.resources.aws.autoscaling import autoscaling_group as asc
from heat.scaling import scalingutil as sc_util
from heat.tests import common
@ -25,79 +25,79 @@ class TestCapacityChanges(common.HeatTestCase):
# s MIN_ADJUSTMENT_STEP
scenarios = [
('+n', dict(current=2, adjustment=3,
adjustment_type=asc.CHANGE_IN_CAPACITY,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.CHANGE_IN_CAPACITY,
min_adjustment_step=None,
minimum=1, maximum=5, expected=1)),
('e', dict(current=2, adjustment=4,
adjustment_type=asc.EXACT_CAPACITY,
adjustment_type=sc_util.EXACT_CAPACITY,
min_adjustment_step=None,
minimum=0, maximum=5, expected=4)),
('+eb', dict(current=2, adjustment=11,
adjustment_type=asc.EXACT_CAPACITY,
adjustment_type=sc_util.EXACT_CAPACITY,
min_adjustment_step=None,
minimum=0, maximum=5, expected=5)),
('-eb', dict(current=4, adjustment=1,
adjustment_type=asc.EXACT_CAPACITY,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.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,
adjustment_type=sc_util.PERCENT_CHANGE_IN_CAPACITY,
min_adjustment_step=2,
minimum=0, maximum=10, expected=1)),
@ -106,7 +106,7 @@ class TestCapacityChanges(common.HeatTestCase):
def test_calc(self):
self.assertEqual(self.expected,
asc._calculate_new_capacity(
sc_util.calculate_new_capacity(
self.current, self.adjustment,
self.adjustment_type,
self.min_adjustment_step,