senlin/senlin/tests/unit/policies/test_region_placement.py

427 lines
16 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.
from unittest import mock
from senlin.common import consts
from senlin.common import exception as exc
from senlin.common import scaleutils as su
from senlin.engine import cluster as cm
from senlin.policies import base as pb
from senlin.policies import region_placement as rp
from senlin.tests.unit.common import base
from senlin.tests.unit.common import utils
class TestRegionPlacementPolicy(base.SenlinTestCase):
def setUp(self):
super(TestRegionPlacementPolicy, self).setUp()
self.context = utils.dummy_context()
self.spec = {
'type': 'senlin.policy.region_placement',
'version': '1.0',
'properties': {
'regions': [
{'name': 'R1', 'weight': 100, 'cap': 50},
{'name': 'R2', 'weight': 50, 'cap': 50},
{'name': 'R3', 'weight': 30, 'cap': -1},
{'name': 'R4', 'weight': 20, 'cap': -1}
]
}
}
def test_policy_init(self):
policy = rp.RegionPlacementPolicy('test-policy', self.spec)
self.assertIsNone(policy.id)
self.assertIsNone(policy. _keystoneclient)
self.assertEqual('test-policy', policy.name)
self.assertEqual('senlin.policy.region_placement-1.0', policy.type)
expected = {
'R1': {
'weight': 100,
'cap': 50
},
'R2': {
'weight': 50,
'cap': 50,
},
'R3': {
'weight': 30,
'cap': -1,
},
'R4': {
'weight': 20,
'cap': -1,
}
}
self.assertEqual(expected, policy.regions)
@mock.patch.object(pb.Policy, 'validate')
def test_validate_okay(self, mock_base_validate):
policy = rp.RegionPlacementPolicy('test-policy', self.spec)
kc = mock.Mock()
kc.validate_regions.return_value = ['R1', 'R2', 'R3', 'R4']
policy._keystoneclient = kc
ctx = mock.Mock(user='U1', project='P1')
res = policy.validate(ctx, True)
self.assertTrue(res)
mock_base_validate.assert_called_once_with(ctx, True)
kc.validate_regions.assert_called_once_with(['R1', 'R2', 'R3', 'R4'])
@mock.patch.object(pb.Policy, 'validate')
def test_validate_no_validate_props(self, mock_base_validate):
policy = rp.RegionPlacementPolicy('test-policy', self.spec)
ctx = mock.Mock(user='U1', project='P1')
res = policy.validate(ctx, False)
self.assertTrue(res)
mock_base_validate.assert_called_once_with(ctx, False)
@mock.patch.object(pb.Policy, 'validate')
def test_validate_region_not_found(self, mock_base_validate):
policy = rp.RegionPlacementPolicy('test-policy', self.spec)
kc = mock.Mock()
kc.validate_regions.return_value = ['R2', 'R4']
policy._keystoneclient = kc
ctx = mock.Mock(user='U1', project='P1')
ex = self.assertRaises(exc.InvalidSpec,
policy.validate,
ctx, True)
mock_base_validate.assert_called_once_with(ctx, True)
kc.validate_regions.assert_called_once_with(['R1', 'R2', 'R3', 'R4'])
self.assertEqual("The specified regions '['R1', 'R3']' could not "
"be found.", str(ex))
def test_create_plan(self):
policy = rp.RegionPlacementPolicy('p1', self.spec)
regions = policy.regions
current = {'R1': 2, 'R2': 2, 'R3': 2, 'R4': 1}
result = policy._create_plan(current, regions, 5, True)
expected = {'R1': 4, 'R2': 1}
self.assertEqual(expected, result)
current = {'R1': 2, 'R2': 2, 'R3': 0, 'R4': 1}
plan = policy._create_plan(current, regions, 5, True)
answer = {'R1': 3, 'R2': 1, 'R3': 1}
self.assertEqual(answer, plan)
current = {'R1': 2, 'R2': 2, 'R3': 0, 'R4': 1}
plan = policy._create_plan(current, regions, 3, False)
answer = {'R2': 2, 'R4': 1}
self.assertEqual(answer, plan)
current = {'R1': 4, 'R2': 2, 'R3': 1, 'R4': 1}
plan = policy._create_plan(current, regions, 3, False)
answer = {'R2': 1, 'R3': 1, 'R4': 1}
self.assertEqual(answer, plan)
def test_get_count_node_create_no_region(self):
x_profile = mock.Mock(CONTEXT='context', properties={'context': {}})
x_node = mock.Mock(rt={'profile': x_profile})
action = mock.Mock(action=consts.NODE_CREATE, entity=x_node)
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(1, res)
def test_get_count_node_create_region_specified(self):
x_profile = mock.Mock(CONTEXT='context',
properties={'context': {'region_name': 'foo'}})
x_node = mock.Mock(rt={'profile': x_profile})
action = mock.Mock(action=consts.NODE_CREATE, entity=x_node)
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(0, res)
def test_get_count_resize_deletion(self):
action = mock.Mock(action=consts.CLUSTER_RESIZE,
data={'deletion': {'count': 3}})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(-3, res)
def test_get_count_resize_creation(self):
action = mock.Mock(action=consts.CLUSTER_RESIZE,
data={'creation': {'count': 3}})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(3, res)
@mock.patch.object(su, 'parse_resize_params')
def test_get_count_resize_parse_error(self, mock_parse):
x_cluster = mock.Mock()
x_cluster.nodes = [mock.Mock(), mock.Mock()]
action = mock.Mock(action=consts.CLUSTER_RESIZE, data={})
action.entity = x_cluster
mock_parse.return_value = (pb.CHECK_ERROR, 'Something wrong.')
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(0, res)
self.assertEqual(pb.CHECK_ERROR, action.data['status'])
mock_parse.assert_called_once_with(action, x_cluster, 2)
self.assertEqual('Something wrong.', action.data['reason'])
@mock.patch.object(su, 'parse_resize_params')
def test_get_count_resize_parse_creation(self, mock_parse):
def fake_parse(action, cluster, current):
action.data = {'creation': {'count': 3}}
return pb.CHECK_OK, ''
x_cluster = mock.Mock()
x_cluster.nodes = []
action = mock.Mock(action=consts.CLUSTER_RESIZE, data={})
action.entity = x_cluster
mock_parse.side_effect = fake_parse
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(3, res)
mock_parse.assert_called_once_with(action, x_cluster, 0)
@mock.patch.object(su, 'parse_resize_params')
def test_get_count_resize_parse_deletion(self, mock_parse):
def fake_parse(action, cluster, current):
action.data = {'deletion': {'count': 3}}
return pb.CHECK_OK, ''
x_cluster = mock.Mock()
x_cluster.nodes = [mock.Mock(), mock.Mock(), mock.Mock()]
action = mock.Mock(action=consts.CLUSTER_RESIZE, data={})
action.entity = x_cluster
mock_parse.side_effect = fake_parse
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(-3, res)
mock_parse.assert_called_once_with(action, x_cluster, 3)
def test_get_count_scale_in_with_data(self):
action = mock.Mock(action=consts.CLUSTER_SCALE_IN,
data={'deletion': {'count': 3}})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(-3, res)
def test_get_count_scale_in_with_no_data(self):
action = mock.Mock(action=consts.CLUSTER_SCALE_IN,
data={'deletion': {'num': 3}})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(-1, res)
def test_get_count_scale_in_with_inputs(self):
action = mock.Mock(action=consts.CLUSTER_SCALE_IN, data={},
inputs={'count': 3})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(-3, res)
def test_get_count_scale_in_with_incorrect_inputs(self):
action = mock.Mock(action=consts.CLUSTER_SCALE_IN, data={},
inputs={'num': 3})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(-1, res)
def test_get_count_scale_out_with_data(self):
action = mock.Mock(action=consts.CLUSTER_SCALE_OUT,
data={'creation': {'count': 3}})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(3, res)
def test_get_count_scale_out_with_no_data(self):
action = mock.Mock(action=consts.CLUSTER_SCALE_OUT,
data={'creation': {'num': 3}})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(1, res)
def test_get_count_scale_out_with_inputs(self):
action = mock.Mock(action=consts.CLUSTER_SCALE_OUT, data={},
inputs={'count': 3})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(3, res)
def test_get_count_scale_out_with_incorrect_inputs(self):
action = mock.Mock(action=consts.CLUSTER_SCALE_OUT, data={},
inputs={'num': 3})
policy = rp.RegionPlacementPolicy('p1', self.spec)
res = policy._get_count('FOO', action)
self.assertEqual(1, res)
@mock.patch.object(cm.Cluster, 'load')
def test_pre_op(self, mock_load):
# test pre_op method whether returns the correct action.data
policy = rp.RegionPlacementPolicy('p1', self.spec)
regions = policy.regions
kc = mock.Mock()
kc.validate_regions.return_value = regions.keys()
policy._keystoneclient = kc
plan = {'R1': 1, 'R3': 2}
self.patchobject(policy, '_create_plan', return_value=plan)
action = mock.Mock()
action.context = self.context
action.action = 'CLUSTER_SCALE_OUT'
action.inputs = {}
action.data = {
'creation': {
'count': 3,
}
}
cluster = mock.Mock()
current_dist = {'R1': 0, 'R2': 0, 'R3': 0, 'R4': 0}
cluster.get_region_distribution.return_value = current_dist
mock_load.return_value = cluster
res = policy.pre_op('FAKE_CLUSTER', action)
self.assertIsNone(res)
self.assertEqual(3, action.data['creation']['count'])
dist = action.data['creation']['regions']
self.assertEqual(2, len(dist))
self.assertEqual(1, dist['R1'])
self.assertEqual(2, dist['R3'])
mock_load.assert_called_once_with(action.context, 'FAKE_CLUSTER')
kc.validate_regions.assert_called_once_with(regions.keys())
cluster.get_region_distribution.assert_called_once_with(regions.keys())
policy._create_plan.assert_called_once_with(
current_dist, regions, 3, True)
@mock.patch.object(cm.Cluster, 'load')
def test_pre_op_count_from_inputs(self, mock_load):
# test pre_op method whether returns the correct action.data
policy = rp.RegionPlacementPolicy('p1', self.spec)
regions = policy.regions
kc = mock.Mock()
kc.validate_regions.return_value = regions.keys()
policy._keystoneclient = kc
cluster = mock.Mock()
current_dist = {'R1': 0, 'R2': 0, 'R3': 0, 'R4': 0}
cluster.get_region_distribution.return_value = current_dist
mock_load.return_value = cluster
plan = {'R1': 1, 'R3': 2}
self.patchobject(policy, '_create_plan', return_value=plan)
action = mock.Mock()
action.context = self.context
action.action = 'CLUSTER_SCALE_OUT'
action.inputs = {'count': 3}
action.data = {}
res = policy.pre_op('FAKE_CLUSTER', action)
self.assertIsNone(res)
self.assertEqual(3, action.data['creation']['count'])
dist = action.data['creation']['regions']
self.assertEqual(2, len(dist))
self.assertEqual(1, dist['R1'])
self.assertEqual(2, dist['R3'])
@mock.patch.object(cm.Cluster, 'load')
def test_pre_op_no_regions(self, mock_load):
# test pre_op method whether returns the correct action.data
policy = rp.RegionPlacementPolicy('p1', self.spec)
kc = mock.Mock()
kc.validate_regions.return_value = []
policy._keystoneclient = kc
action = mock.Mock()
action.action = 'CLUSTER_SCALE_OUT'
action.context = self.context
action.data = {'creation': {'count': 3}}
cluster = mock.Mock()
mock_load.return_value = cluster
res = policy.pre_op('FAKE_CLUSTER', action)
self.assertIsNone(res)
self.assertEqual('ERROR', action.data['status'])
self.assertEqual('No region is found usable.', action.data['reason'])
@mock.patch.object(cm.Cluster, 'load')
def test_pre_op_no_feasible_plan(self, mock_load):
# test pre_op method whether returns the correct action.data
policy = rp.RegionPlacementPolicy('p1', self.spec)
regions = policy.regions
kc = mock.Mock()
kc.validate_regions.return_value = regions.keys()
policy._keystoneclient = kc
self.patchobject(policy, '_create_plan', return_value=None)
action = mock.Mock()
action.action = 'CLUSTER_SCALE_OUT'
action.context = self.context
action.inputs = {}
action.data = {'creation': {'count': 3}}
cluster = mock.Mock()
current_dist = {'R1': 0, 'R2': 0, 'R3': 0, 'R4': 0}
cluster.get_region_distribution.return_value = current_dist
mock_load.return_value = cluster
res = policy.pre_op('FAKE_CLUSTER', action)
self.assertIsNone(res)
self.assertEqual('ERROR', action.data['status'])
self.assertEqual('There is no feasible plan to handle all nodes.',
action.data['reason'])
mock_load.assert_called_once_with(action.context, 'FAKE_CLUSTER')
kc.validate_regions.assert_called_once_with(regions.keys())
cluster.get_region_distribution.assert_called_once_with(regions.keys())
policy._create_plan.assert_called_once_with(
current_dist, regions, 3, True)