diff --git a/nova/cells/filters/different_cell.py b/nova/cells/filters/different_cell.py new file mode 100644 index 000000000000..00607fad81a4 --- /dev/null +++ b/nova/cells/filters/different_cell.py @@ -0,0 +1,62 @@ +# 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. + +""" +Different cell filter. + +A scheduler hint of 'different_cell' with a value of a full cell name may be +specified to route a build away from a particular cell. +""" + +import six + +from nova.cells import filters +from nova.cells import utils as cells_utils + + +class DifferentCellFilter(filters.BaseCellFilter): + """Different cell filter. Works by specifying a scheduler hint of + 'different_cell'. The value should be the full cell path. + """ + def filter_all(self, cells, filter_properties): + """Override filter_all() which operates on the full list + of cells... + """ + scheduler_hints = filter_properties.get('scheduler_hints') + if not scheduler_hints: + return cells + + cell_routes = scheduler_hints.get('different_cell') + if not cell_routes: + return cells + if isinstance(cell_routes, six.string_types): + cell_routes = [cell_routes] + + if not self.authorized(filter_properties['context']): + # No filtering, if not authorized. + return cells + + routing_path = filter_properties['routing_path'] + filtered_cells = [] + for cell in cells: + if not self._cell_state_matches(cell, routing_path, cell_routes): + filtered_cells.append(cell) + + return filtered_cells + + def _cell_state_matches(self, cell_state, routing_path, cell_routes): + cell_route = routing_path + if not cell_state.is_me: + cell_route += cells_utils.PATH_CELL_SEP + cell_state.name + if cell_route in cell_routes: + return True + return False diff --git a/nova/tests/unit/cells/test_cells_filters.py b/nova/tests/unit/cells/test_cells_filters.py index 482c804f3281..de78b2a978d3 100644 --- a/nova/tests/unit/cells/test_cells_filters.py +++ b/nova/tests/unit/cells/test_cells_filters.py @@ -17,6 +17,7 @@ Unit Tests for cells scheduler filters. """ from nova.cells import filters +from nova.cells import state from nova import context from nova.db.sqlalchemy import models from nova import test @@ -30,6 +31,7 @@ class FiltersTestCase(test.NoDBTestCase): filter_classes = filters.all_filters() class_names = [cls.__name__ for cls in filter_classes] self.assertIn("TargetCellFilter", class_names) + self.assertIn("DifferentCellFilter", class_names) class _FilterTestClass(test.NoDBTestCase): @@ -172,3 +174,57 @@ class TestTargetCellFilter(_FilterTestClass): 'cell': 'fake!cell!path', 'sched_kwargs': 'meow'} self.assertEqual(expected_info, info) + + +class TestDifferentCellFilter(_FilterTestClass): + filter_cls_name = 'nova.cells.filters.different_cell.DifferentCellFilter' + + def setUp(self): + super(TestDifferentCellFilter, self).setUp() + # We only load one filter so we know the first one is the one we want + self.policy.set_rules({'cells_scheduler_filter:DifferentCellFilter': + ''}) + self.cells = [state.CellState('1'), + state.CellState('2'), + state.CellState('3')] + + def test_missing_scheduler_hints(self): + filter_props = {'context': self.context} + # No filtering + self.assertEqual(self.cells, + self._filter_cells(self.cells, filter_props)) + + def test_no_different_cell_hint(self): + filter_props = {'scheduler_hints': {}, + 'context': self.context} + # No filtering + self.assertEqual(self.cells, + self._filter_cells(self.cells, filter_props)) + + def test_different_cell(self): + filter_props = {'scheduler_hints': {'different_cell': 'fake!2'}, + 'routing_path': 'fake', + 'context': self.context} + filtered_cells = self._filter_cells(self.cells, filter_props) + self.assertEqual(2, len(filtered_cells)) + self.assertNotIn(self.cells[1], filtered_cells) + + def test_different_multiple_cells(self): + filter_props = {'scheduler_hints': + {'different_cell': ['fake!1', 'fake!2']}, + 'routing_path': 'fake', + 'context': self.context} + filtered_cells = self._filter_cells(self.cells, filter_props) + self.assertEqual(1, len(filtered_cells)) + self.assertNotIn(self.cells[0], filtered_cells) + self.assertNotIn(self.cells[1], filtered_cells) + + def test_different_cell_specified_me_not_authorized(self): + self.policy.set_rules({'cells_scheduler_filter:DifferentCellFilter': + '!'}) + filter_props = {'scheduler_hints': {'different_cell': 'fake!2'}, + 'routing_path': 'fake', + 'context': self.context} + # No filtering, because not an admin. + self.assertEqual(self.cells, + self._filter_cells(self.cells, filter_props))