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