diff options
| author | Jenkins <jenkins@review.openstack.org> | 2013-05-28 20:38:05 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2013-05-28 20:38:05 +0000 |
| commit | a3e6133628310b0f41fad7271b048e30c0401e62 (patch) | |
| tree | cae4c883ec92e867c99d6248f191afdfad5aa0ab /nova/tests | |
| parent | e18bb9f80bef34846913e667fc77e4d1132480ec (diff) | |
| parent | 7a5ed3e76766596e45716d0fc05ff1e6e9199213 (diff) | |
Merge "Cells: Add filtering and weight support"
Diffstat (limited to 'nova/tests')
| -rw-r--r-- | nova/tests/cells/test_cells_filters.py | 121 | ||||
| -rw-r--r-- | nova/tests/cells/test_cells_scheduler.py | 182 | ||||
| -rw-r--r-- | nova/tests/cells/test_cells_weights.py | 165 | ||||
| -rw-r--r-- | nova/tests/fake_policy.py | 2 | ||||
| -rw-r--r-- | nova/tests/test_filters.py | 33 |
5 files changed, 500 insertions, 3 deletions
diff --git a/nova/tests/cells/test_cells_filters.py b/nova/tests/cells/test_cells_filters.py new file mode 100644 index 000000000..e11e6c640 --- /dev/null +++ b/nova/tests/cells/test_cells_filters.py @@ -0,0 +1,121 @@ +# Copyright (c) 2012-2013 Rackspace Hosting +# All Rights Reserved. +# +# 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. +""" +Unit Tests for cells scheduler filters. +""" + +from nova.cells import filters +from nova import context +from nova import test +from nova.tests.cells import fakes + + +class FiltersTestCase(test.TestCase): + """Makes sure the proper filters are in the directory.""" + + def test_all_filters(self): + filter_classes = filters.all_filters() + class_names = [cls.__name__ for cls in filter_classes] + self.assertIn("TargetCellFilter", class_names) + + +class _FilterTestClass(test.TestCase): + """Base class for testing individual filter plugins.""" + filter_cls_name = None + + def setUp(self): + super(_FilterTestClass, self).setUp() + fakes.init(self) + self.msg_runner = fakes.get_message_runner('api-cell') + self.scheduler = self.msg_runner.scheduler + self.my_cell_state = self.msg_runner.state_manager.get_my_state() + self.filter_handler = filters.CellFilterHandler() + self.filter_classes = self.filter_handler.get_matching_classes( + [self.filter_cls_name]) + self.context = context.RequestContext('fake', 'fake', + is_admin=True) + + def _filter_cells(self, cells, filter_properties): + return self.filter_handler.get_filtered_objects(self.filter_classes, + cells, + filter_properties) + + +class TestTargetCellFilter(_FilterTestClass): + filter_cls_name = 'nova.cells.filters.target_cell.TargetCellFilter' + + def test_missing_scheduler_hints(self): + cells = [1, 2, 3] + # No filtering + filter_props = {'context': self.context} + self.assertEqual(cells, self._filter_cells(cells, filter_props)) + + def test_no_target_cell_hint(self): + cells = [1, 2, 3] + filter_props = {'scheduler_hints': {}, + 'context': self.context} + # No filtering + self.assertEqual(cells, self._filter_cells(cells, filter_props)) + + def test_target_cell_specified_me(self): + cells = [1, 2, 3] + target_cell = 'fake!cell!path' + current_cell = 'fake!cell!path' + filter_props = {'scheduler_hints': {'target_cell': target_cell}, + 'routing_path': current_cell, + 'scheduler': self.scheduler, + 'context': self.context} + # Only myself in the list. + self.assertEqual([self.my_cell_state], + self._filter_cells(cells, filter_props)) + + def test_target_cell_specified_me_but_not_admin(self): + ctxt = context.RequestContext('fake', 'fake') + cells = [1, 2, 3] + target_cell = 'fake!cell!path' + current_cell = 'fake!cell!path' + filter_props = {'scheduler_hints': {'target_cell': target_cell}, + 'routing_path': current_cell, + 'scheduler': self.scheduler, + 'context': ctxt} + # No filtering, because not an admin. + self.assertEqual(cells, self._filter_cells(cells, filter_props)) + + def test_target_cell_specified_not_me(self): + info = {} + + def _fake_sched_run_instance(ctxt, cell, sched_kwargs): + info['ctxt'] = ctxt + info['cell'] = cell + info['sched_kwargs'] = sched_kwargs + + self.stubs.Set(self.msg_runner, 'schedule_run_instance', + _fake_sched_run_instance) + cells = [1, 2, 3] + target_cell = 'fake!cell!path' + current_cell = 'not!the!same' + filter_props = {'scheduler_hints': {'target_cell': target_cell}, + 'routing_path': current_cell, + 'scheduler': self.scheduler, + 'context': self.context, + 'host_sched_kwargs': 'meow'} + # None is returned to bypass further scheduling. + self.assertEqual(None, + self._filter_cells(cells, filter_props)) + # The filter should have re-scheduled to the child cell itself. + expected_info = {'ctxt': self.context, + 'cell': 'fake!cell!path', + 'sched_kwargs': 'meow'} + self.assertEqual(expected_info, info) diff --git a/nova/tests/cells/test_cells_scheduler.py b/nova/tests/cells/test_cells_scheduler.py index c9e626385..c8f90619e 100644 --- a/nova/tests/cells/test_cells_scheduler.py +++ b/nova/tests/cells/test_cells_scheduler.py @@ -19,6 +19,8 @@ import time from oslo.config import cfg +from nova.cells import filters +from nova.cells import weights from nova.compute import vm_states from nova import context from nova import db @@ -29,6 +31,26 @@ from nova.tests.cells import fakes CONF = cfg.CONF CONF.import_opt('scheduler_retries', 'nova.cells.scheduler', group='cells') +CONF.import_opt('scheduler_filter_classes', 'nova.cells.scheduler', + group='cells') +CONF.import_opt('scheduler_weight_classes', 'nova.cells.scheduler', + group='cells') + + +class FakeFilterClass1(filters.BaseCellFilter): + pass + + +class FakeFilterClass2(filters.BaseCellFilter): + pass + + +class FakeWeightClass1(weights.BaseCellWeigher): + pass + + +class FakeWeightClass2(weights.BaseCellWeigher): + pass class CellsSchedulerTestCase(test.TestCase): @@ -36,6 +58,11 @@ class CellsSchedulerTestCase(test.TestCase): def setUp(self): super(CellsSchedulerTestCase, self).setUp() + self.flags(scheduler_filter_classes=[], scheduler_weight_classes=[], + group='cells') + self._init_cells_scheduler() + + def _init_cells_scheduler(self): fakes.init(self) self.msg_runner = fakes.get_message_runner('api-cell') self.scheduler = self.msg_runner.scheduler @@ -109,7 +136,8 @@ class CellsSchedulerTestCase(test.TestCase): self.stubs.Set(self.msg_runner, 'schedule_run_instance', msg_runner_schedule_run_instance) - host_sched_kwargs = {'request_spec': self.request_spec} + host_sched_kwargs = {'request_spec': self.request_spec, + 'filter_properties': {}} self.msg_runner.schedule_run_instance(self.ctxt, self.my_cell_state, host_sched_kwargs) @@ -138,6 +166,7 @@ class CellsSchedulerTestCase(test.TestCase): 'run_instance', fake_rpc_run_instance) host_sched_kwargs = {'request_spec': self.request_spec, + 'filter_properties': {}, 'other': 'stuff'} self.msg_runner.schedule_run_instance(self.ctxt, self.my_cell_state, host_sched_kwargs) @@ -149,7 +178,8 @@ class CellsSchedulerTestCase(test.TestCase): def test_run_instance_retries_when_no_cells_avail(self): self.flags(scheduler_retries=7, group='cells') - host_sched_kwargs = {'request_spec': self.request_spec} + host_sched_kwargs = {'request_spec': self.request_spec, + 'filter_properties': {}} call_info = {'num_tries': 0, 'errored_uuids': []} @@ -177,7 +207,8 @@ class CellsSchedulerTestCase(test.TestCase): def test_run_instance_on_random_exception(self): self.flags(scheduler_retries=7, group='cells') - host_sched_kwargs = {'request_spec': self.request_spec} + host_sched_kwargs = {'request_spec': self.request_spec, + 'filter_properties': {}} call_info = {'num_tries': 0, 'errored_uuids1': [], @@ -206,3 +237,148 @@ class CellsSchedulerTestCase(test.TestCase): self.assertEqual(1, call_info['num_tries']) self.assertEqual(self.instance_uuids, call_info['errored_uuids1']) self.assertEqual(self.instance_uuids, call_info['errored_uuids2']) + + def test_cells_filter_args_correct(self): + # Re-init our fakes with some filters. + our_path = 'nova.tests.cells.test_cells_scheduler' + cls_names = [our_path + '.' + 'FakeFilterClass1', + our_path + '.' + 'FakeFilterClass2'] + self.flags(scheduler_filter_classes=cls_names, group='cells') + self._init_cells_scheduler() + + # Make sure there's no child cells so that we will be + # selected. Makes stubbing easier. + self.state_manager.child_cells = {} + + call_info = {} + + def fake_create_instances_here(ctxt, request_spec): + call_info['ctxt'] = ctxt + call_info['request_spec'] = request_spec + + def fake_rpc_run_instance(ctxt, **host_sched_kwargs): + call_info['host_sched_kwargs'] = host_sched_kwargs + + def fake_get_filtered_objs(filter_classes, cells, filt_properties): + call_info['filt_classes'] = filter_classes + call_info['filt_cells'] = cells + call_info['filt_props'] = filt_properties + return cells + + self.stubs.Set(self.scheduler, '_create_instances_here', + fake_create_instances_here) + self.stubs.Set(self.scheduler.scheduler_rpcapi, + 'run_instance', fake_rpc_run_instance) + filter_handler = self.scheduler.filter_handler + self.stubs.Set(filter_handler, 'get_filtered_objects', + fake_get_filtered_objs) + + host_sched_kwargs = {'request_spec': self.request_spec, + 'filter_properties': {}, + 'other': 'stuff'} + + self.msg_runner.schedule_run_instance(self.ctxt, + self.my_cell_state, host_sched_kwargs) + # Our cell was selected. + self.assertEqual(self.ctxt, call_info['ctxt']) + self.assertEqual(self.request_spec, call_info['request_spec']) + self.assertEqual(host_sched_kwargs, call_info['host_sched_kwargs']) + # Filter args are correct + expected_filt_props = {'context': self.ctxt, + 'scheduler': self.scheduler, + 'routing_path': self.my_cell_state.name, + 'host_sched_kwargs': host_sched_kwargs, + 'request_spec': self.request_spec} + self.assertEqual(expected_filt_props, call_info['filt_props']) + self.assertEqual([FakeFilterClass1, FakeFilterClass2], + call_info['filt_classes']) + self.assertEqual([self.my_cell_state], call_info['filt_cells']) + + def test_cells_filter_returning_none(self): + # Re-init our fakes with some filters. + our_path = 'nova.tests.cells.test_cells_scheduler' + cls_names = [our_path + '.' + 'FakeFilterClass1', + our_path + '.' + 'FakeFilterClass2'] + self.flags(scheduler_filter_classes=cls_names, group='cells') + self._init_cells_scheduler() + + # Make sure there's no child cells so that we will be + # selected. Makes stubbing easier. + self.state_manager.child_cells = {} + + call_info = {'scheduled': False} + + def fake_create_instances_here(ctxt, request_spec): + # Should not be called + call_info['scheduled'] = True + + def fake_get_filtered_objs(filter_classes, cells, filt_properties): + # Should cause scheduling to be skipped. Means that the + # filter did it. + return None + + self.stubs.Set(self.scheduler, '_create_instances_here', + fake_create_instances_here) + filter_handler = self.scheduler.filter_handler + self.stubs.Set(filter_handler, 'get_filtered_objects', + fake_get_filtered_objs) + + self.msg_runner.schedule_run_instance(self.ctxt, + self.my_cell_state, {}) + self.assertFalse(call_info['scheduled']) + + def test_cells_weight_args_correct(self): + # Re-init our fakes with some filters. + our_path = 'nova.tests.cells.test_cells_scheduler' + cls_names = [our_path + '.' + 'FakeWeightClass1', + our_path + '.' + 'FakeWeightClass2'] + self.flags(scheduler_weight_classes=cls_names, group='cells') + self._init_cells_scheduler() + + # Make sure there's no child cells so that we will be + # selected. Makes stubbing easier. + self.state_manager.child_cells = {} + + call_info = {} + + def fake_create_instances_here(ctxt, request_spec): + call_info['ctxt'] = ctxt + call_info['request_spec'] = request_spec + + def fake_rpc_run_instance(ctxt, **host_sched_kwargs): + call_info['host_sched_kwargs'] = host_sched_kwargs + + def fake_get_weighed_objs(weight_classes, cells, filt_properties): + call_info['weight_classes'] = weight_classes + call_info['weight_cells'] = cells + call_info['weight_props'] = filt_properties + return [weights.WeightedCell(cells[0], 0.0)] + + self.stubs.Set(self.scheduler, '_create_instances_here', + fake_create_instances_here) + self.stubs.Set(self.scheduler.scheduler_rpcapi, + 'run_instance', fake_rpc_run_instance) + weight_handler = self.scheduler.weight_handler + self.stubs.Set(weight_handler, 'get_weighed_objects', + fake_get_weighed_objs) + + host_sched_kwargs = {'request_spec': self.request_spec, + 'filter_properties': {}, + 'other': 'stuff'} + + self.msg_runner.schedule_run_instance(self.ctxt, + self.my_cell_state, host_sched_kwargs) + # Our cell was selected. + self.assertEqual(self.ctxt, call_info['ctxt']) + self.assertEqual(self.request_spec, call_info['request_spec']) + self.assertEqual(host_sched_kwargs, call_info['host_sched_kwargs']) + # Weight args are correct + expected_filt_props = {'context': self.ctxt, + 'scheduler': self.scheduler, + 'routing_path': self.my_cell_state.name, + 'host_sched_kwargs': host_sched_kwargs, + 'request_spec': self.request_spec} + self.assertEqual(expected_filt_props, call_info['weight_props']) + self.assertEqual([FakeWeightClass1, FakeWeightClass2], + call_info['weight_classes']) + self.assertEqual([self.my_cell_state], call_info['weight_cells']) diff --git a/nova/tests/cells/test_cells_weights.py b/nova/tests/cells/test_cells_weights.py new file mode 100644 index 000000000..ca01e9939 --- /dev/null +++ b/nova/tests/cells/test_cells_weights.py @@ -0,0 +1,165 @@ +# Copyright (c) 2012 Openstack, LLC +# All Rights Reserved. +# +# 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. +""" +Unit Tests for testing the cells weight algorithms. + +Cells with higher weights should be given priority for new builds. +""" + +from nova.cells import state +from nova.cells import weights +from nova import test + + +class FakeCellState(state.CellState): + def __init__(self, cell_name): + super(FakeCellState, self).__init__(cell_name) + self.capacities['ram_free'] = {'total_mb': 0, + 'units_by_mb': {}} + self.db_info = {} + + def _update_ram_free(self, *args): + ram_free = self.capacities['ram_free'] + for ram_size, units in args: + ram_free['total_mb'] += units * ram_size + ram_free['units_by_mb'][str(ram_size)] = units + + +def _get_fake_cells(): + + cell1 = FakeCellState('cell1') + cell1._update_ram_free((512, 1), (1024, 4), (2048, 3)) + cell1.db_info['weight_offset'] = -200.0 + cell2 = FakeCellState('cell2') + cell2._update_ram_free((512, 2), (1024, 3), (2048, 4)) + cell2.db_info['weight_offset'] = -200.1 + cell3 = FakeCellState('cell3') + cell3._update_ram_free((512, 3), (1024, 2), (2048, 1)) + cell3.db_info['weight_offset'] = 400.0 + cell4 = FakeCellState('cell4') + cell4._update_ram_free((512, 4), (1024, 1), (2048, 2)) + cell4.db_info['weight_offset'] = 300.0 + + return [cell1, cell2, cell3, cell4] + + +class CellsWeightsTestCase(test.TestCase): + """Makes sure the proper weighers are in the directory.""" + + def test_all_weighers(self): + weighers = weights.all_weighers() + # Check at least a couple that we expect are there + self.assertTrue(len(weighers) >= 2) + class_names = [cls.__name__ for cls in weighers] + self.assertIn('WeightOffsetWeigher', class_names) + self.assert_('RamByInstanceTypeWeigher', class_names) + + +class _WeigherTestClass(test.TestCase): + """Base class for testing individual weigher plugins.""" + weigher_cls_name = None + + def setUp(self): + super(_WeigherTestClass, self).setUp() + self.weight_handler = weights.CellWeightHandler() + self.weight_classes = self.weight_handler.get_matching_classes( + [self.weigher_cls_name]) + + def _get_weighed_cells(self, cells, weight_properties): + return self.weight_handler.get_weighed_objects(self.weight_classes, + cells, weight_properties) + + +class RAMByInstanceTypeWeigherTestClass(_WeigherTestClass): + + weigher_cls_name = ('nova.cells.weights.ram_by_instance_type.' + 'RamByInstanceTypeWeigher') + + def test_default_spreading(self): + """Test that cells with more ram available return a higher weight.""" + cells = _get_fake_cells() + # Simulate building a new 512MB instance. + instance_type = {'memory_mb': 512} + weight_properties = {'request_spec': {'instance_type': instance_type}} + weighed_cells = self._get_weighed_cells(cells, weight_properties) + self.assertEqual(len(weighed_cells), 4) + resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] + expected_cells = [cells[3], cells[2], cells[1], cells[0]] + self.assertEqual(expected_cells, resulting_cells) + + # Simulate building a new 1024MB instance. + instance_type = {'memory_mb': 1024} + weight_properties = {'request_spec': {'instance_type': instance_type}} + weighed_cells = self._get_weighed_cells(cells, weight_properties) + self.assertEqual(len(weighed_cells), 4) + resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] + expected_cells = [cells[0], cells[1], cells[2], cells[3]] + self.assertEqual(expected_cells, resulting_cells) + + # Simulate building a new 2048MB instance. + instance_type = {'memory_mb': 2048} + weight_properties = {'request_spec': {'instance_type': instance_type}} + weighed_cells = self._get_weighed_cells(cells, weight_properties) + self.assertEqual(len(weighed_cells), 4) + resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] + expected_cells = [cells[1], cells[0], cells[3], cells[2]] + self.assertEqual(expected_cells, resulting_cells) + + def test_negative_multiplier(self): + """Test that cells with less ram available return a higher weight.""" + self.flags(ram_weight_multiplier=-1.0, group='cells') + cells = _get_fake_cells() + # Simulate building a new 512MB instance. + instance_type = {'memory_mb': 512} + weight_properties = {'request_spec': {'instance_type': instance_type}} + weighed_cells = self._get_weighed_cells(cells, weight_properties) + self.assertEqual(len(weighed_cells), 4) + resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] + expected_cells = [cells[0], cells[1], cells[2], cells[3]] + self.assertEqual(expected_cells, resulting_cells) + + # Simulate building a new 1024MB instance. + instance_type = {'memory_mb': 1024} + weight_properties = {'request_spec': {'instance_type': instance_type}} + weighed_cells = self._get_weighed_cells(cells, weight_properties) + self.assertEqual(len(weighed_cells), 4) + resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] + expected_cells = [cells[3], cells[2], cells[1], cells[0]] + self.assertEqual(expected_cells, resulting_cells) + + # Simulate building a new 2048MB instance. + instance_type = {'memory_mb': 2048} + weight_properties = {'request_spec': {'instance_type': instance_type}} + weighed_cells = self._get_weighed_cells(cells, weight_properties) + self.assertEqual(len(weighed_cells), 4) + resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] + expected_cells = [cells[2], cells[3], cells[0], cells[1]] + self.assertEqual(expected_cells, resulting_cells) + + +class WeightOffsetWeigherTestClass(_WeigherTestClass): + """Test the RAMWeigher class.""" + weigher_cls_name = 'nova.cells.weights.weight_offset.WeightOffsetWeigher' + + def test_weight_offset(self): + """Test that cells with higher weight_offsets return higher + weights. + """ + cells = _get_fake_cells() + weighed_cells = self._get_weighed_cells(cells, {}) + self.assertEqual(len(weighed_cells), 4) + expected_cells = [cells[2], cells[3], cells[0], cells[1]] + resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] + self.assertEqual(expected_cells, resulting_cells) diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index b30793ac4..b09944878 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -19,6 +19,8 @@ policy_data = """ { "admin_api": "role:admin", + "cells_scheduler_filter:TargetCellFilter": "is_admin:True", + "context_is_admin": "role:admin or role:administrator", "compute:create": "", "compute:create:attach_network": "", diff --git a/nova/tests/test_filters.py b/nova/tests/test_filters.py index 3940ce0c3..c06b50fde 100644 --- a/nova/tests/test_filters.py +++ b/nova/tests/test_filters.py @@ -123,3 +123,36 @@ class FiltersTestCase(test.TestCase): filter_objs_initial, filter_properties) self.assertEqual(filter_objs_last, result) + + def test_get_filtered_objects_none_response(self): + filter_objs_initial = ['initial', 'filter1', 'objects1'] + filter_properties = 'fake_filter_properties' + + def _fake_base_loader_init(*args, **kwargs): + pass + + self.stubs.Set(loadables.BaseLoader, '__init__', + _fake_base_loader_init) + + filt1_mock = self.mox.CreateMock(Filter1) + filt2_mock = self.mox.CreateMock(Filter2) + + self.mox.StubOutWithMock(sys.modules[__name__], 'Filter1', + use_mock_anything=True) + self.mox.StubOutWithMock(filt1_mock, 'filter_all') + # Shouldn't be called. + self.mox.StubOutWithMock(sys.modules[__name__], 'Filter2', + use_mock_anything=True) + self.mox.StubOutWithMock(filt2_mock, 'filter_all') + + Filter1().AndReturn(filt1_mock) + filt1_mock.filter_all(filter_objs_initial, + filter_properties).AndReturn(None) + self.mox.ReplayAll() + + filter_handler = filters.BaseFilterHandler(filters.BaseFilter) + filter_classes = [Filter1, Filter2] + result = filter_handler.get_filtered_objects(filter_classes, + filter_objs_initial, + filter_properties) + self.assertEqual(None, result) |
