Merge "Cells: Add filtering and weight support"
This commit is contained in:
commit
a3e6133628
|
@ -3,6 +3,7 @@
|
||||||
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
|
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
|
||||||
"default": "rule:admin_or_owner",
|
"default": "rule:admin_or_owner",
|
||||||
|
|
||||||
|
"cells_scheduler_filter:TargetCellFilter": "is_admin:True",
|
||||||
|
|
||||||
"compute:create": "",
|
"compute:create": "",
|
||||||
"compute:create:attach_network": "",
|
"compute:create:attach_network": "",
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Cell scheduler filters
|
||||||
|
"""
|
||||||
|
|
||||||
|
from nova import filters
|
||||||
|
from nova.openstack.common import log as logging
|
||||||
|
from nova import policy
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCellFilter(filters.BaseFilter):
|
||||||
|
"""Base class for cell filters."""
|
||||||
|
|
||||||
|
def authorized(self, ctxt):
|
||||||
|
"""Return whether or not the context is authorized for this filter
|
||||||
|
based on policy.
|
||||||
|
The policy action is "cells_scheduler_filter:<name>" where <name>
|
||||||
|
is the name of the filter class.
|
||||||
|
"""
|
||||||
|
name = 'cells_scheduler_filter:' + self.__class__.__name__
|
||||||
|
target = {'project_id': ctxt.project_id,
|
||||||
|
'user_id': ctxt.user_id}
|
||||||
|
return policy.enforce(ctxt, name, target, do_raise=False)
|
||||||
|
|
||||||
|
def _filter_one(self, cell, filter_properties):
|
||||||
|
return self.cell_passes(cell, filter_properties)
|
||||||
|
|
||||||
|
def cell_passes(self, cell, filter_properties):
|
||||||
|
"""Return True if the CellState passes the filter, otherwise False.
|
||||||
|
Override this in a subclass.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class CellFilterHandler(filters.BaseFilterHandler):
|
||||||
|
def __init__(self):
|
||||||
|
super(CellFilterHandler, self).__init__(BaseCellFilter)
|
||||||
|
|
||||||
|
|
||||||
|
def all_filters():
|
||||||
|
"""Return a list of filter classes found in this directory.
|
||||||
|
|
||||||
|
This method is used as the default for available scheduler filters
|
||||||
|
and should return a list of all filter classes available.
|
||||||
|
"""
|
||||||
|
return CellFilterHandler().get_all_classes()
|
|
@ -0,0 +1,68 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Target cell filter.
|
||||||
|
|
||||||
|
A scheduler hint of 'target_cell' with a value of a full cell name may be
|
||||||
|
specified to route a build to a particular cell. No error handling is
|
||||||
|
done as there's no way to know whether the full path is a valid.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from nova.cells import filters
|
||||||
|
from nova.openstack.common import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TargetCellFilter(filters.BaseCellFilter):
|
||||||
|
"""Target cell filter. Works by specifying a scheduler hint of
|
||||||
|
'target_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
|
||||||
|
|
||||||
|
# This filter only makes sense at the top level, as a full
|
||||||
|
# cell name is specified. So we pop 'target_cell' out of the
|
||||||
|
# hints dict.
|
||||||
|
cell_name = scheduler_hints.pop('target_cell', None)
|
||||||
|
if not cell_name:
|
||||||
|
return cells
|
||||||
|
|
||||||
|
# This authorization is after popping off target_cell, so
|
||||||
|
# that in case this fails, 'target_cell' is not left in the
|
||||||
|
# dict when child cells go to schedule.
|
||||||
|
if not self.authorized(filter_properties['context']):
|
||||||
|
# No filtering, if not authorized.
|
||||||
|
return cells
|
||||||
|
|
||||||
|
LOG.info(_("Forcing direct route to %(cell_name)s because "
|
||||||
|
"of 'target_cell' scheduler hint"),
|
||||||
|
{'cell_name': cell_name})
|
||||||
|
|
||||||
|
scheduler = filter_properties['scheduler']
|
||||||
|
if cell_name == filter_properties['routing_path']:
|
||||||
|
return [scheduler.state_manager.get_my_state()]
|
||||||
|
ctxt = filter_properties['context']
|
||||||
|
scheduler.msg_runner.schedule_run_instance(ctxt, cell_name,
|
||||||
|
filter_properties['host_sched_kwargs'])
|
||||||
|
# Returning None means to skip further scheduling, because we
|
||||||
|
# handled it.
|
|
@ -16,11 +16,13 @@
|
||||||
"""
|
"""
|
||||||
Cells Scheduler
|
Cells Scheduler
|
||||||
"""
|
"""
|
||||||
import random
|
import copy
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from nova.cells import filters
|
||||||
|
from nova.cells import weights
|
||||||
from nova import compute
|
from nova import compute
|
||||||
from nova.compute import instance_actions
|
from nova.compute import instance_actions
|
||||||
from nova.compute import utils as compute_utils
|
from nova.compute import utils as compute_utils
|
||||||
|
@ -31,6 +33,16 @@ from nova.openstack.common import log as logging
|
||||||
from nova.scheduler import rpcapi as scheduler_rpcapi
|
from nova.scheduler import rpcapi as scheduler_rpcapi
|
||||||
|
|
||||||
cell_scheduler_opts = [
|
cell_scheduler_opts = [
|
||||||
|
cfg.ListOpt('scheduler_filter_classes',
|
||||||
|
default=['nova.cells.filters.all_filters'],
|
||||||
|
help='Filter classes the cells scheduler should use. '
|
||||||
|
'An entry of "nova.cells.filters.all_filters"'
|
||||||
|
'maps to all cells filters included with nova.'),
|
||||||
|
cfg.ListOpt('scheduler_weight_classes',
|
||||||
|
default=['nova.cells.weights.all_weighers'],
|
||||||
|
help='Weigher classes the cells scheduler should use. '
|
||||||
|
'An entry of "nova.cells.weights.all_weighers"'
|
||||||
|
'maps to all cell weighers included with nova.'),
|
||||||
cfg.IntOpt('scheduler_retries',
|
cfg.IntOpt('scheduler_retries',
|
||||||
default=10,
|
default=10,
|
||||||
help='How many retries when no cells are available.'),
|
help='How many retries when no cells are available.'),
|
||||||
|
@ -55,6 +67,12 @@ class CellsScheduler(base.Base):
|
||||||
self.state_manager = msg_runner.state_manager
|
self.state_manager = msg_runner.state_manager
|
||||||
self.compute_api = compute.API()
|
self.compute_api = compute.API()
|
||||||
self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
|
self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
|
||||||
|
self.filter_handler = filters.CellFilterHandler()
|
||||||
|
self.filter_classes = self.filter_handler.get_matching_classes(
|
||||||
|
CONF.cells.scheduler_filter_classes)
|
||||||
|
self.weight_handler = weights.CellWeightHandler()
|
||||||
|
self.weigher_classes = self.weight_handler.get_matching_classes(
|
||||||
|
CONF.cells.scheduler_weight_classes)
|
||||||
|
|
||||||
def _create_instances_here(self, ctxt, request_spec):
|
def _create_instances_here(self, ctxt, request_spec):
|
||||||
instance_values = request_spec['instance_properties']
|
instance_values = request_spec['instance_properties']
|
||||||
|
@ -79,11 +97,11 @@ class CellsScheduler(base.Base):
|
||||||
self.db.action_start(ctxt, action)
|
self.db.action_start(ctxt, action)
|
||||||
|
|
||||||
def _get_possible_cells(self):
|
def _get_possible_cells(self):
|
||||||
cells = set(self.state_manager.get_child_cells())
|
cells = self.state_manager.get_child_cells()
|
||||||
our_cell = self.state_manager.get_my_state()
|
our_cell = self.state_manager.get_my_state()
|
||||||
# Include our cell in the list, if we have any capacity info
|
# Include our cell in the list, if we have any capacity info
|
||||||
if not cells or our_cell.capacities:
|
if not cells or our_cell.capacities:
|
||||||
cells.add(our_cell)
|
cells.append(our_cell)
|
||||||
return cells
|
return cells
|
||||||
|
|
||||||
def _run_instance(self, message, host_sched_kwargs):
|
def _run_instance(self, message, host_sched_kwargs):
|
||||||
|
@ -91,33 +109,66 @@ class CellsScheduler(base.Base):
|
||||||
to try, raise exception.NoCellsAvailable
|
to try, raise exception.NoCellsAvailable
|
||||||
"""
|
"""
|
||||||
ctxt = message.ctxt
|
ctxt = message.ctxt
|
||||||
|
routing_path = message.routing_path
|
||||||
request_spec = host_sched_kwargs['request_spec']
|
request_spec = host_sched_kwargs['request_spec']
|
||||||
|
|
||||||
# The message we might forward to a child cell
|
|
||||||
cells = self._get_possible_cells()
|
|
||||||
if not cells:
|
|
||||||
raise exception.NoCellsAvailable()
|
|
||||||
cells = list(cells)
|
|
||||||
|
|
||||||
# Random selection for now
|
|
||||||
random.shuffle(cells)
|
|
||||||
target_cell = cells[0]
|
|
||||||
|
|
||||||
LOG.debug(_("Scheduling with routing_path=%(routing_path)s"),
|
LOG.debug(_("Scheduling with routing_path=%(routing_path)s"),
|
||||||
{'routing_path': message.routing_path})
|
{'routing_path': routing_path})
|
||||||
|
|
||||||
if target_cell.is_me:
|
filter_properties = copy.copy(host_sched_kwargs['filter_properties'])
|
||||||
# Need to create instance DB entries as the host scheduler
|
filter_properties.update({'context': ctxt,
|
||||||
# expects that the instance(s) already exists.
|
'scheduler': self,
|
||||||
self._create_instances_here(ctxt, request_spec)
|
'routing_path': routing_path,
|
||||||
# Need to record the create action in the db as the scheduler
|
'host_sched_kwargs': host_sched_kwargs,
|
||||||
# expects it to already exist.
|
'request_spec': request_spec})
|
||||||
self._create_action_here(ctxt, request_spec['instance_uuids'])
|
|
||||||
self.scheduler_rpcapi.run_instance(ctxt,
|
cells = self._get_possible_cells()
|
||||||
**host_sched_kwargs)
|
cells = self.filter_handler.get_filtered_objects(self.filter_classes,
|
||||||
return
|
cells,
|
||||||
self.msg_runner.schedule_run_instance(ctxt, target_cell,
|
filter_properties)
|
||||||
host_sched_kwargs)
|
# NOTE(comstud): I know this reads weird, but the 'if's are nested
|
||||||
|
# this way to optimize for the common case where 'cells' is a list
|
||||||
|
# containing at least 1 entry.
|
||||||
|
if not cells:
|
||||||
|
if cells is None:
|
||||||
|
# None means to bypass further scheduling as a filter
|
||||||
|
# took care of everything.
|
||||||
|
return
|
||||||
|
raise exception.NoCellsAvailable()
|
||||||
|
|
||||||
|
weighted_cells = self.weight_handler.get_weighed_objects(
|
||||||
|
self.weigher_classes, cells, filter_properties)
|
||||||
|
LOG.debug(_("Weighted cells: %(weighted_cells)s"),
|
||||||
|
{'weighted_cells': weighted_cells})
|
||||||
|
|
||||||
|
# Keep trying until one works
|
||||||
|
for weighted_cell in weighted_cells:
|
||||||
|
cell = weighted_cell.obj
|
||||||
|
try:
|
||||||
|
if cell.is_me:
|
||||||
|
# Need to create instance DB entry as scheduler
|
||||||
|
# thinks it's already created... At least how things
|
||||||
|
# currently work.
|
||||||
|
self._create_instances_here(ctxt, request_spec)
|
||||||
|
# Need to record the create action in the db as the
|
||||||
|
# scheduler expects it to already exist.
|
||||||
|
self._create_action_here(
|
||||||
|
ctxt, request_spec['instance_uuids'])
|
||||||
|
self.scheduler_rpcapi.run_instance(ctxt,
|
||||||
|
**host_sched_kwargs)
|
||||||
|
return
|
||||||
|
# Forward request to cell
|
||||||
|
self.msg_runner.schedule_run_instance(ctxt, cell,
|
||||||
|
host_sched_kwargs)
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
LOG.exception(_("Couldn't communicate with cell '%s'") %
|
||||||
|
cell.name)
|
||||||
|
# FIXME(comstud): Would be nice to kick this back up so that
|
||||||
|
# the parent cell could retry, if we had a parent.
|
||||||
|
msg = _("Couldn't communicate with any cells")
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.NoCellsAvailable()
|
||||||
|
|
||||||
def run_instance(self, message, host_sched_kwargs):
|
def run_instance(self, message, host_sched_kwargs):
|
||||||
"""Pick a cell where we should create a new instance."""
|
"""Pick a cell where we should create a new instance."""
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Cell Scheduler weights
|
||||||
|
"""
|
||||||
|
|
||||||
|
from nova import weights
|
||||||
|
|
||||||
|
|
||||||
|
class WeightedCell(weights.WeighedObject):
|
||||||
|
def __repr__(self):
|
||||||
|
return "WeightedCell [cell: %s, weight: %s]" % (
|
||||||
|
self.obj.name, self.weight)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCellWeigher(weights.BaseWeigher):
|
||||||
|
"""Base class for cell weights."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CellWeightHandler(weights.BaseWeightHandler):
|
||||||
|
object_class = WeightedCell
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(CellWeightHandler, self).__init__(BaseCellWeigher)
|
||||||
|
|
||||||
|
|
||||||
|
def all_weighers():
|
||||||
|
"""Return a list of weight plugin classes found in this directory."""
|
||||||
|
return CellWeightHandler().get_all_classes()
|
|
@ -0,0 +1,54 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Weigh cells by memory needed in a way that spreads instances.
|
||||||
|
"""
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from nova.cells import weights
|
||||||
|
|
||||||
|
ram_weigher_opts = [
|
||||||
|
cfg.FloatOpt('ram_weight_multiplier',
|
||||||
|
default=10.0,
|
||||||
|
help='Multiplier used for weighing ram. Negative '
|
||||||
|
'numbers mean to stack vs spread.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(ram_weigher_opts, group='cells')
|
||||||
|
|
||||||
|
|
||||||
|
class RamByInstanceTypeWeigher(weights.BaseCellWeigher):
|
||||||
|
"""Weigh cells by instance_type requested."""
|
||||||
|
|
||||||
|
def _weight_multiplier(self):
|
||||||
|
return CONF.cells.ram_weight_multiplier
|
||||||
|
|
||||||
|
def _weigh_object(self, cell, weight_properties):
|
||||||
|
"""
|
||||||
|
Use the 'ram_free' for a particular instance_type advertised from a
|
||||||
|
child cell's capacity to compute a weight. We want to direct the
|
||||||
|
build to a cell with a higher capacity. Since higher weights win,
|
||||||
|
we just return the number of units available for the instance_type.
|
||||||
|
"""
|
||||||
|
request_spec = weight_properties['request_spec']
|
||||||
|
instance_type = request_spec['instance_type']
|
||||||
|
memory_needed = instance_type['memory_mb']
|
||||||
|
|
||||||
|
ram_free = cell.capacities.get('ram_free', {})
|
||||||
|
units_by_mb = ram_free.get('units_by_mb', {})
|
||||||
|
|
||||||
|
return units_by_mb.get(str(memory_needed), 0)
|
|
@ -0,0 +1,33 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Weigh cells by their weight_offset in the DB. Cells with higher
|
||||||
|
weight_offsets in the DB will be preferred.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from nova.cells import weights
|
||||||
|
|
||||||
|
|
||||||
|
class WeightOffsetWeigher(weights.BaseCellWeigher):
|
||||||
|
"""
|
||||||
|
Weight cell by weight_offset db field.
|
||||||
|
Originally designed so you can set a default cell by putting
|
||||||
|
its weight_offset to 999999999999999 (highest weight wins)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _weigh_object(self, cell, weight_properties):
|
||||||
|
"""Returns whatever was in the DB for weight_offset."""
|
||||||
|
return cell.db_info.get('weight_offset', 0)
|
|
@ -54,8 +54,14 @@ class BaseFilterHandler(loadables.BaseLoader):
|
||||||
list_objs = list(objs)
|
list_objs = list(objs)
|
||||||
LOG.debug("Starting with %d host(s)", len(list_objs))
|
LOG.debug("Starting with %d host(s)", len(list_objs))
|
||||||
for filter_cls in filter_classes:
|
for filter_cls in filter_classes:
|
||||||
list_objs = list(filter_cls().filter_all(list_objs,
|
cls_name = filter_cls.__name__
|
||||||
filter_properties))
|
objs = filter_cls().filter_all(list_objs,
|
||||||
LOG.debug("Filter %s returned %d host(s)",
|
filter_properties)
|
||||||
filter_cls.__name__, len(list_objs))
|
if objs is None:
|
||||||
|
LOG.debug("Filter %(cls_name)s says to stop filtering",
|
||||||
|
{'cls_name': cls_name})
|
||||||
|
return
|
||||||
|
list_objs = list(objs)
|
||||||
|
LOG.debug("Filter %(cls_name)s returned %(obj_len)d host(s)",
|
||||||
|
{'cls_name': cls_name, 'obj_len': len(list_objs)})
|
||||||
return list_objs
|
return list_objs
|
||||||
|
|
|
@ -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)
|
|
@ -19,6 +19,8 @@ import time
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from nova.cells import filters
|
||||||
|
from nova.cells import weights
|
||||||
from nova.compute import vm_states
|
from nova.compute import vm_states
|
||||||
from nova import context
|
from nova import context
|
||||||
from nova import db
|
from nova import db
|
||||||
|
@ -29,6 +31,26 @@ from nova.tests.cells import fakes
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.import_opt('scheduler_retries', 'nova.cells.scheduler', group='cells')
|
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):
|
class CellsSchedulerTestCase(test.TestCase):
|
||||||
|
@ -36,6 +58,11 @@ class CellsSchedulerTestCase(test.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(CellsSchedulerTestCase, self).setUp()
|
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)
|
fakes.init(self)
|
||||||
self.msg_runner = fakes.get_message_runner('api-cell')
|
self.msg_runner = fakes.get_message_runner('api-cell')
|
||||||
self.scheduler = self.msg_runner.scheduler
|
self.scheduler = self.msg_runner.scheduler
|
||||||
|
@ -109,7 +136,8 @@ class CellsSchedulerTestCase(test.TestCase):
|
||||||
self.stubs.Set(self.msg_runner, 'schedule_run_instance',
|
self.stubs.Set(self.msg_runner, 'schedule_run_instance',
|
||||||
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.msg_runner.schedule_run_instance(self.ctxt,
|
||||||
self.my_cell_state, host_sched_kwargs)
|
self.my_cell_state, host_sched_kwargs)
|
||||||
|
|
||||||
|
@ -138,6 +166,7 @@ class CellsSchedulerTestCase(test.TestCase):
|
||||||
'run_instance', fake_rpc_run_instance)
|
'run_instance', fake_rpc_run_instance)
|
||||||
|
|
||||||
host_sched_kwargs = {'request_spec': self.request_spec,
|
host_sched_kwargs = {'request_spec': self.request_spec,
|
||||||
|
'filter_properties': {},
|
||||||
'other': 'stuff'}
|
'other': 'stuff'}
|
||||||
self.msg_runner.schedule_run_instance(self.ctxt,
|
self.msg_runner.schedule_run_instance(self.ctxt,
|
||||||
self.my_cell_state, host_sched_kwargs)
|
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):
|
def test_run_instance_retries_when_no_cells_avail(self):
|
||||||
self.flags(scheduler_retries=7, group='cells')
|
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': []}
|
call_info = {'num_tries': 0, 'errored_uuids': []}
|
||||||
|
|
||||||
|
@ -177,7 +207,8 @@ class CellsSchedulerTestCase(test.TestCase):
|
||||||
def test_run_instance_on_random_exception(self):
|
def test_run_instance_on_random_exception(self):
|
||||||
self.flags(scheduler_retries=7, group='cells')
|
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,
|
call_info = {'num_tries': 0,
|
||||||
'errored_uuids1': [],
|
'errored_uuids1': [],
|
||||||
|
@ -206,3 +237,148 @@ class CellsSchedulerTestCase(test.TestCase):
|
||||||
self.assertEqual(1, call_info['num_tries'])
|
self.assertEqual(1, call_info['num_tries'])
|
||||||
self.assertEqual(self.instance_uuids, call_info['errored_uuids1'])
|
self.assertEqual(self.instance_uuids, call_info['errored_uuids1'])
|
||||||
self.assertEqual(self.instance_uuids, call_info['errored_uuids2'])
|
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'])
|
||||||
|
|
|
@ -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)
|
|
@ -19,6 +19,8 @@ policy_data = """
|
||||||
{
|
{
|
||||||
"admin_api": "role:admin",
|
"admin_api": "role:admin",
|
||||||
|
|
||||||
|
"cells_scheduler_filter:TargetCellFilter": "is_admin:True",
|
||||||
|
|
||||||
"context_is_admin": "role:admin or role:administrator",
|
"context_is_admin": "role:admin or role:administrator",
|
||||||
"compute:create": "",
|
"compute:create": "",
|
||||||
"compute:create:attach_network": "",
|
"compute:create:attach_network": "",
|
||||||
|
|
|
@ -123,3 +123,36 @@ class FiltersTestCase(test.TestCase):
|
||||||
filter_objs_initial,
|
filter_objs_initial,
|
||||||
filter_properties)
|
filter_properties)
|
||||||
self.assertEqual(filter_objs_last, result)
|
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)
|
||||||
|
|
Loading…
Reference in New Issue