352 lines
13 KiB
Python
352 lines
13 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import mock
|
|
from oslo_utils import timeutils
|
|
import six
|
|
|
|
from senlin.common import consts
|
|
from senlin.common.i18n import _
|
|
from senlin.db.sqlalchemy import api as db_api
|
|
from senlin.policies import base as policy_base
|
|
from senlin.policies import scaling_policy as sp
|
|
from senlin.tests.unit.common import base
|
|
from senlin.tests.unit.common import utils
|
|
|
|
|
|
class TestScalingPolicy(base.SenlinTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestScalingPolicy, self).setUp()
|
|
self.context = utils.dummy_context()
|
|
self.spec = {
|
|
'type': 'senlin.policy.scaling',
|
|
'version': '1.0',
|
|
'properties': {
|
|
'event': 'CLUSTER_SCALE_IN',
|
|
'adjustment': {
|
|
'type': 'CHANGE_IN_CAPACITY',
|
|
'number': 1,
|
|
'min_step': 1,
|
|
'best_effort': False,
|
|
'cooldown': 3,
|
|
}
|
|
}
|
|
}
|
|
self.profile = self._create_profile('PROFILE1')
|
|
self.cluster = self._create_cluster('CLUSTER1',
|
|
self.profile['id'])
|
|
self.nodes = self._create_nodes(self.cluster['id'],
|
|
self.profile['id'], 3)
|
|
|
|
def _create_profile(self, profile_id):
|
|
values = {
|
|
'id': profile_id,
|
|
'type': 'os.heat.stack',
|
|
'name': 'test-profile',
|
|
'created_at': timeutils.utcnow(),
|
|
'user': self.context.user,
|
|
'project': self.context.project,
|
|
}
|
|
return db_api.profile_create(self.context, values)
|
|
|
|
def _create_cluster(self, cluster_id, profile_id):
|
|
values = {
|
|
'id': cluster_id,
|
|
'profile_id': profile_id,
|
|
'name': 'test-cluster',
|
|
'user': self.context.user,
|
|
'project': self.context.project,
|
|
'next_index': 1,
|
|
'min_size': 1,
|
|
'max_size': 5,
|
|
'desired_capacity': 3,
|
|
}
|
|
|
|
return db_api.cluster_create(self.context, values)
|
|
|
|
def _create_nodes(self, cluster_id, profile_id, count):
|
|
nodes = []
|
|
for i in range(count):
|
|
values = {
|
|
'id': 'FAKE_NODE_%s_%s' % (profile_id, (i + 1)),
|
|
'name': 'test_node_%s' % (i + 1),
|
|
'physical_id': 'FAKE_PHY_ID_%s' % (i + 1),
|
|
'cluster_id': cluster_id,
|
|
'profile_id': profile_id,
|
|
'project': self.context.project,
|
|
'index': i + 1,
|
|
'role': None,
|
|
'created_at': timeutils.utcnow(),
|
|
'updated_at': None,
|
|
'status': 'ACTIVE',
|
|
'status_reason': 'create complete',
|
|
'metadata': {'foo': '123'},
|
|
'data': {'key1': 'value1'},
|
|
}
|
|
db_node = db_api.node_create(self.context, values)
|
|
nodes.append(six.text_type(db_node.id))
|
|
return nodes
|
|
|
|
def test_policy_init(self):
|
|
policy = sp.ScalingPolicy('p1', self.spec)
|
|
self.assertFalse(policy.singleton)
|
|
|
|
self.assertIsNone(policy.id)
|
|
self.assertEqual('p1', policy.name)
|
|
self.assertEqual('senlin.policy.scaling-1.0', policy.type)
|
|
self.assertEqual('CLUSTER_SCALE_IN', policy.event)
|
|
adjustment = self.spec['properties']['adjustment']
|
|
self.assertEqual(adjustment['type'], policy.adjustment_type)
|
|
self.assertEqual(adjustment['number'], policy.adjustment_number)
|
|
self.assertEqual(adjustment['min_step'], policy.adjustment_min_step)
|
|
self.assertEqual(adjustment['best_effort'], policy.best_effort)
|
|
self.assertEqual(adjustment['cooldown'], policy.cooldown)
|
|
|
|
def test_policy_init_default_value(self):
|
|
self.spec['properties']['adjustment'] = {}
|
|
policy = sp.ScalingPolicy('p1', self.spec)
|
|
|
|
self.assertIsNone(policy.id)
|
|
self.assertEqual('senlin.policy.scaling-1.0', policy.type)
|
|
self.assertEqual('p1', policy.name)
|
|
self.assertEqual(consts.CHANGE_IN_CAPACITY, policy.adjustment_type)
|
|
self.assertEqual(1, policy.adjustment_number)
|
|
self.assertEqual(1, policy.adjustment_min_step)
|
|
self.assertFalse(policy.best_effort)
|
|
self.assertEqual(0, policy.cooldown)
|
|
|
|
def test_calculate_adjustment_count(self):
|
|
adjustment = self.spec['properties']['adjustment']
|
|
# adjustment_type as EXACT_CAPACITY and event as cluster_scale_in
|
|
current_size = 3
|
|
adjustment['type'] = consts.EXACT_CAPACITY
|
|
adjustment['number'] = 1
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
policy.event = consts.CLUSTER_SCALE_IN
|
|
count = policy._calculate_adjustment_count(current_size)
|
|
self.assertEqual(2, count)
|
|
|
|
# adjustment_type as EXACT_CAPACITY and event as cluster_scale_out
|
|
current_size = 3
|
|
adjustment['type'] = consts.EXACT_CAPACITY
|
|
adjustment['number'] = 1
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
policy.event = consts.CLUSTER_SCALE_OUT
|
|
count = policy._calculate_adjustment_count(current_size)
|
|
self.assertEqual(-2, count)
|
|
|
|
# adjustment_type is CHANGE_IN_CAPACITY
|
|
adjustment['type'] = consts.CHANGE_IN_CAPACITY
|
|
adjustment['number'] = 1
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
count = policy._calculate_adjustment_count(current_size)
|
|
self.assertEqual(1, count)
|
|
|
|
# adjustment_type is CHANGE_IN_PERCENTAGE
|
|
current_size = 10
|
|
adjustment['type'] = consts.CHANGE_IN_PERCENTAGE
|
|
adjustment['number'] = 50
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
count = policy._calculate_adjustment_count(current_size)
|
|
self.assertEqual(5, count)
|
|
|
|
# adjustment_type is CHANGE_IN_PERCENTAGE and min_step is 2
|
|
adjustment['type'] = consts.CHANGE_IN_PERCENTAGE
|
|
adjustment['number'] = 1
|
|
adjustment['min_step'] = 2
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
count = policy._calculate_adjustment_count(current_size)
|
|
self.assertEqual(2, count)
|
|
|
|
def test_pre_op_pass_without_input(self):
|
|
action = mock.Mock()
|
|
action.context = self.context
|
|
action.action = consts.CLUSTER_SCALE_IN
|
|
action.inputs = {}
|
|
adjustment = self.spec['properties']['adjustment']
|
|
adjustment['type'] = consts.EXACT_CAPACITY
|
|
adjustment['number'] = 1
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
|
|
policy.pre_op(self.cluster['id'], action)
|
|
pd = {
|
|
'deletion': {
|
|
'count': 2,
|
|
},
|
|
'reason': 'Scaling request validated.',
|
|
'status': policy_base.CHECK_OK,
|
|
}
|
|
action.data.update.assert_called_with(pd)
|
|
action.store.assert_called_with(self.context)
|
|
|
|
def test_pre_op_pass_with_input(self):
|
|
action = mock.Mock()
|
|
action.context = self.context
|
|
action.action = consts.CLUSTER_SCALE_IN
|
|
action.inputs = {'count': 1}
|
|
adjustment = self.spec['properties']['adjustment']
|
|
adjustment['type'] = consts.CHANGE_IN_CAPACITY
|
|
adjustment['number'] = 2
|
|
policy = sp.ScalingPolicy('p1', self.spec)
|
|
|
|
policy.pre_op(self.cluster['id'], action)
|
|
pd = {
|
|
'deletion': {
|
|
'count': 1,
|
|
},
|
|
'reason': 'Scaling request validated.',
|
|
'status': policy_base.CHECK_OK,
|
|
}
|
|
action.data.update.assert_called_with(pd)
|
|
action.store.assert_called_with(self.context)
|
|
|
|
# count value is string rather than integer
|
|
action.inputs = {'count': '1'}
|
|
policy.pre_op(self.cluster['id'], action)
|
|
pd = {
|
|
'deletion': {
|
|
'count': 1,
|
|
},
|
|
'reason': 'Scaling request validated.',
|
|
'status': policy_base.CHECK_OK,
|
|
}
|
|
action.data.update.assert_called_with(pd)
|
|
|
|
def test_pre_op_fail_negative_count(self):
|
|
action = mock.Mock()
|
|
action.context = self.context
|
|
action.action = consts.CLUSTER_SCALE_IN
|
|
action.inputs = {}
|
|
adjustment = self.spec['properties']['adjustment']
|
|
adjustment['type'] = consts.EXACT_CAPACITY
|
|
adjustment['number'] = 5
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
|
|
policy.pre_op(self.cluster['id'], action)
|
|
reason = _('Count (-2) invalid for action CLUSTER_SCALE_IN.')
|
|
|
|
pd = {
|
|
'reason': reason,
|
|
'status': policy_base.CHECK_ERROR,
|
|
}
|
|
action.data.update.assert_called_with(pd)
|
|
action.store.assert_called_with(self.context)
|
|
|
|
def test_pre_op_fail_below_min_size(self):
|
|
action = mock.Mock()
|
|
action.action = consts.CLUSTER_SCALE_IN
|
|
action.context = self.context
|
|
action.inputs = {}
|
|
adjustment = self.spec['properties']['adjustment']
|
|
adjustment['type'] = consts.CHANGE_IN_CAPACITY
|
|
adjustment['number'] = 3
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
|
|
policy.pre_op(self.cluster['id'], action)
|
|
reason = _('Attempted scaling below minimum size.')
|
|
|
|
pd = {
|
|
'reason': reason,
|
|
'status': policy_base.CHECK_ERROR,
|
|
}
|
|
action.data.update.assert_called_with(pd)
|
|
action.store.assert_called_with(self.context)
|
|
|
|
def test_pre_op_pass_best_effort(self):
|
|
action = mock.Mock()
|
|
action.context = self.context
|
|
action.action = consts.CLUSTER_SCALE_IN
|
|
action.inputs = {}
|
|
adjustment = self.spec['properties']['adjustment']
|
|
adjustment['best_effort'] = True
|
|
adjustment['type'] = consts.CHANGE_IN_CAPACITY
|
|
adjustment['number'] = 3
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
|
|
policy.pre_op(self.cluster['id'], action)
|
|
reason = _('Do best effort scaling.')
|
|
|
|
pd = {
|
|
'deletion': {
|
|
'count': 2,
|
|
},
|
|
'reason': reason,
|
|
'status': policy_base.CHECK_OK,
|
|
}
|
|
action.data.update.assert_called_with(pd)
|
|
action.store.assert_called_with(self.context)
|
|
|
|
def test_pre_op_with_bad_nodes(self):
|
|
node_id = self.nodes[0]
|
|
db_api.node_update(self.context, node_id,
|
|
{'status': 'ERROR'})
|
|
|
|
action = mock.Mock()
|
|
action.context = self.context
|
|
action.action = consts.CLUSTER_SCALE_IN
|
|
action.inputs = {}
|
|
adjustment = self.spec['properties']['adjustment']
|
|
adjustment['type'] = consts.EXACT_CAPACITY
|
|
adjustment['number'] = 1
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
|
|
policy.pre_op(self.cluster['id'], action)
|
|
pd = {
|
|
'deletion': {
|
|
'count': 1,
|
|
},
|
|
'reason': 'Scaling request validated.',
|
|
'status': policy_base.CHECK_OK,
|
|
}
|
|
action.data.update.assert_called_with(pd)
|
|
action.store.assert_called_with(self.context)
|
|
|
|
node_id = self.nodes[1]
|
|
db_api.node_update(self.context, node_id,
|
|
{'status': 'ERROR'})
|
|
node_id = self.nodes[2]
|
|
db_api.node_update(self.context, node_id,
|
|
{'status': 'ERROR'})
|
|
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
|
|
policy.pre_op(self.cluster['id'], action)
|
|
reason = _('Count (-1) invalid for action CLUSTER_SCALE_IN.')
|
|
|
|
pd = {
|
|
'reason': reason,
|
|
'status': policy_base.CHECK_ERROR,
|
|
}
|
|
action.data.update.assert_called_with(pd)
|
|
action.store.assert_called_with(self.context)
|
|
|
|
def test_need_check_in_event(self):
|
|
action = mock.Mock()
|
|
action.context = self.context
|
|
action.action = consts.CLUSTER_SCALE_IN
|
|
action.data = {}
|
|
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
res = policy.need_check('BEFORE', action)
|
|
self.assertTrue(res)
|
|
|
|
def test_need_check_not_in_event(self):
|
|
action = mock.Mock()
|
|
action.context = self.context
|
|
action.action = consts.CLUSTER_SCALE_OUT
|
|
action.data = {}
|
|
|
|
policy = sp.ScalingPolicy('test-policy', self.spec)
|
|
res = policy.need_check('BEFORE', action)
|
|
self.assertFalse(res)
|