Cells: cell scheduler anti-affinity filter

Adds a new cell scheduler filter which can use a 'different_cell'
scheduler hint similar to the 'different_host' hint.  It expects a fully
qualified cell path as the value and filters out any matching cells.

Change-Id: I27acd19ac9b6ba4ad61a16db3b032e55113a376c
Implements: bp cells-scheduler-anti-affinity-filter
This commit is contained in:
Andrew Laski 2015-06-17 16:20:12 -04:00
parent cdd56e16fd
commit 596ee8796a
2 changed files with 118 additions and 0 deletions

View File

@ -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

View File

@ -17,6 +17,7 @@ Unit Tests for cells scheduler filters.
""" """
from nova.cells import filters from nova.cells import filters
from nova.cells import state
from nova import context from nova import context
from nova.db.sqlalchemy import models from nova.db.sqlalchemy import models
from nova import test from nova import test
@ -30,6 +31,7 @@ class FiltersTestCase(test.NoDBTestCase):
filter_classes = filters.all_filters() filter_classes = filters.all_filters()
class_names = [cls.__name__ for cls in filter_classes] class_names = [cls.__name__ for cls in filter_classes]
self.assertIn("TargetCellFilter", class_names) self.assertIn("TargetCellFilter", class_names)
self.assertIn("DifferentCellFilter", class_names)
class _FilterTestClass(test.NoDBTestCase): class _FilterTestClass(test.NoDBTestCase):
@ -172,3 +174,57 @@ class TestTargetCellFilter(_FilterTestClass):
'cell': 'fake!cell!path', 'cell': 'fake!cell!path',
'sched_kwargs': 'meow'} 'sched_kwargs': 'meow'}
self.assertEqual(expected_info, info) 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))