+++ /dev/null
-# Copyright (c) 2011 OpenStack Foundation.
-# 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.
-
-"""
-Scheduler host filters
-"""
-
-from cinder.openstack.common.scheduler import base_filter
-
-
-class BaseHostFilter(base_filter.BaseFilter):
- """Base class for host filters."""
- def _filter_one(self, obj, filter_properties):
- """Return True if the object passes the filter, otherwise False."""
- return self.host_passes(obj, filter_properties)
-
- def host_passes(self, host_state, filter_properties):
- """Return True if the HostState passes the filter, otherwise False.
- Override this in a subclass.
- """
- raise NotImplementedError()
-
-
-class HostFilterHandler(base_filter.BaseFilterHandler):
- def __init__(self, namespace):
- super(HostFilterHandler, self).__init__(BaseHostFilter, namespace)
+++ /dev/null
-# Copyright (c) 2011 OpenStack Foundation.
-# 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.
-
-"""
-Scheduler host weights
-"""
-
-
-from cinder.openstack.common.scheduler import base_weight
-
-
-class WeighedHost(base_weight.WeighedObject):
- def to_dict(self):
- return {
- 'weight': self.weight,
- 'host': self.obj.host,
- }
-
- def __repr__(self):
- return ("WeighedHost [host: %s, weight: %s]" %
- (self.obj.host, self.weight))
-
-
-class BaseHostWeigher(base_weight.BaseWeigher):
- """Base class for host weights."""
- pass
-
-
-class HostWeightHandler(base_weight.BaseWeightHandler):
- object_class = WeighedHost
-
- def __init__(self, namespace):
- super(HostWeightHandler, self).__init__(BaseHostWeigher, namespace)
import logging
from cinder.openstack.common._i18n import _LI
-from cinder.openstack.common.scheduler import base_handler
+from cinder.scheduler import base_handler
LOG = logging.getLogger(__name__)
"""Base class for all filter classes."""
def _filter_one(self, obj, filter_properties):
"""Return True if it passes the filter, False otherwise.
+
Override this in a subclass.
"""
return True
run_filter_once_per_request = False
def run_filter_for_index(self, index):
- """Return True if the filter needs to be run for the "index-th"
- instance in a request. Only need to override this if a filter
- needs anything other than "first only" or "all" behaviour.
+ """Return True if the filter needs to be run for n-th instances.
+
+ Only need to override this if a filter needs anything other than
+ "first only" or "all" behaviour.
"""
return not (self.run_filter_once_per_request and index > 0)
self.extension_manager = extension.ExtensionManager(modifier_namespace)
def _is_correct_class(self, cls):
- """Return whether an object is a class of the correct type and
- is not prefixed with an underscore.
+ """Return whether an object is a class of the correct type.
+
+ (or is not prefixed with an underscore)
"""
return (inspect.isclass(cls) and
not cls.__name__.startswith('_') and
import six
-from cinder.openstack.common.scheduler import base_handler
+from cinder.scheduler import base_handler
def normalize(weight_list, minval=None, maxval=None):
@abc.abstractmethod
def _weigh_object(self, obj, weight_properties):
- """Override in a subclass to specify a weight for a specific
- object.
- """
+ """Override in a subclass to specify a weight for a specific object."""
def weigh_objects(self, weighed_obj_list, weight_properties):
"""Weigh multiple objects.
+# Copyright (c) 2011 OpenStack Foundation.
+# 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.
+
+"""
+Scheduler host filters
+"""
+
+from cinder.scheduler import base_filter
+
+
+class BaseHostFilter(base_filter.BaseFilter):
+ """Base class for host filters."""
+ def _filter_one(self, obj, filter_properties):
+ """Return True if the object passes the filter, otherwise False."""
+ return self.host_passes(obj, filter_properties)
+
+ def host_passes(self, host_state, filter_properties):
+ """Return True if the HostState passes the filter, otherwise False.
+
+ Override this in a subclass.
+ """
+ raise NotImplementedError()
+
+
+class HostFilterHandler(base_filter.BaseFilterHandler):
+ def __init__(self, namespace):
+ super(HostFilterHandler, self).__init__(BaseHostFilter, namespace)
from oslo_log import log as logging
from oslo_utils import uuidutils
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
from cinder.volume import api as volume
LOG = logging.getLogger(__name__)
# License for the specific language governing permissions and limitations
# under the License.
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
class AvailabilityZoneFilter(filters.BaseHostFilter):
import six
-from cinder.openstack.common.scheduler import filters
-from cinder.openstack.common.scheduler.filters import extra_specs_ops
+from cinder.scheduler import filters
+from cinder.scheduler.filters import extra_specs_ops
LOG = logging.getLogger(__name__)
"""HostFilter to work with resource (instance & volume) type records."""
def _satisfies_extra_specs(self, capabilities, resource_type):
- """Check that the capabilities provided by the services satisfy
+ """Check if capabilities satisfy resource type requirements.
+
+ Check that the capabilities provided by the services satisfy
the extra specs associated with the resource type.
"""
extra_specs = resource_type.get('extra_specs', [])
from oslo_log import log as logging
from cinder.i18n import _LE, _LW
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
LOG = logging.getLogger(__name__)
import six
from cinder.i18n import _LW
-from cinder.openstack.common.scheduler import filters
from cinder.scheduler.evaluator import evaluator
+from cinder.scheduler import filters
LOG = logging.getLogger(__name__)
import logging
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
LOG = logging.getLogger(__name__)
from cinder.compute import nova
from cinder import exception
from cinder.i18n import _, _LW
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
from cinder.volume import utils as volume_utils
from oslo_serialization import jsonutils
import six
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
class JsonFilter(filters.BaseHostFilter):
- """Host Filter to allow simple JSON-based grammar for
- selecting hosts.
- """
+ """Host Filter to allow simple JSON-based grammar for selecting hosts."""
def _op_compare(self, args, op):
- """Returns True if the specified operator can successfully
+ """Compare first item of args with the rest using specified operator.
+
+ Returns True if the specified operator can successfully
compare the first item in the args with all the rest. Will
return False if only one item is in the list.
"""
}
def _parse_string(self, string, host_state):
- """Strings prefixed with $ are capability lookups in the
+ """Parse capability lookup strings.
+
+ Strings prefixed with $ are capability lookups in the
form '$variable' where 'variable' is an attribute in the
HostState class. If $variable is a dictionary, you may
use: $variable.dictkey
return result
def host_passes(self, host_state, filter_properties):
- """Return a list of hosts that can fulfill the requirements
- specified in the query.
- """
+ """Return a list of hosts that can fulfill query requirements."""
# TODO(zhiteng) Add description for filter_properties structure
# and scheduler_hints.
try:
from cinder import context as cinder_context
from cinder import exception
-from cinder.i18n import _LI, _LW
from cinder import objects
-from cinder.openstack.common.scheduler import filters
-from cinder.openstack.common.scheduler import weights
from cinder import utils
+from cinder.i18n import _LI, _LW
+from cinder.scheduler import filters
+from cinder.scheduler import weights
from cinder.volume import utils as vol_utils
+# Copyright (c) 2011 OpenStack Foundation.
+# 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.
+
+"""
+Scheduler host weights
+"""
+
+from cinder.scheduler import base_weight
+
+
+class WeighedHost(base_weight.WeighedObject):
+ def to_dict(self):
+ return {
+ 'weight': self.weight,
+ 'host': self.obj.host,
+ }
+
+ def __repr__(self):
+ return ("WeighedHost [host: %s, weight: %s]" %
+ (self.obj.host, self.weight))
+
+
+class BaseHostWeigher(base_weight.BaseWeigher):
+ """Base class for host weights."""
+ pass
+
+
+class HostWeightHandler(base_weight.BaseWeightHandler):
+ object_class = WeighedHost
+
+ def __init__(self, namespace):
+ super(HostWeightHandler, self).__init__(BaseHostWeigher, namespace)
from oslo_config import cfg
-from cinder.openstack.common.scheduler import weights
+from cinder.scheduler import weights
capacity_weight_opts = [
import random
-from cinder.openstack.common.scheduler import weights
+from cinder.scheduler import weights
class ChanceWeigher(weights.BaseHostWeigher):
import six
from cinder.i18n import _LW
-from cinder.openstack.common.scheduler import weights
from cinder.scheduler.evaluator import evaluator
+from cinder.scheduler import weights
LOG = logging.getLogger(__name__)
from oslo_log import log as logging
from cinder import db
-from cinder.openstack.common.scheduler import weights
+from cinder.scheduler import weights
LOG = logging.getLogger(__name__)
--- /dev/null
+# Copyright 2012 Intel Inc, OpenStack Foundation.
+# 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.
+
+"""
+Fakes For filters tests.
+"""
+
+import six
+
+
+class FakeHostManager(object):
+ """Defines fake hosts.
+
+ host1: free_ram_mb=1024-512-512=0, free_disk_gb=1024-512-512=0
+ host2: free_ram_mb=2048-512=1536 free_disk_gb=2048-512=1536
+ host3: free_ram_mb=4096-1024=3072 free_disk_gb=4096-1024=3072
+ host4: free_ram_mb=8192 free_disk_gb=8192
+ """
+
+ def __init__(self):
+ self.service_states = {
+ 'host1': {
+ 'compute': {'host_memory_free': 1073741824},
+ },
+ 'host2': {
+ 'compute': {'host_memory_free': 2147483648},
+ },
+ 'host3': {
+ 'compute': {'host_memory_free': 3221225472},
+ },
+ 'host4': {
+ 'compute': {'host_memory_free': 999999999},
+ },
+ }
+
+
+class FakeHostState(object):
+ def __init__(self, host, attribute_dict):
+ self.host = host
+ for (key, val) in six.iteritems(attribute_dict):
+ setattr(self, key, val)
from oslo_config import cfg
from cinder import context
-from cinder.openstack.common.scheduler import weights
-from cinder.scheduler.weights import capacity
+from cinder.scheduler import weights
from cinder import test
from cinder.tests.unit.scheduler import fakes
from cinder.volume import utils
if weight_properties is None:
weight_properties = {}
return self.weight_handler.get_weighed_objects(
- [capacity.AllocatedCapacityWeigher], hosts,
+ [weights.capacity.AllocatedCapacityWeigher], hosts,
weight_properties)[0]
@mock.patch('cinder.db.sqlalchemy.api.service_get_all_by_topic')
--- /dev/null
+# Copyright (c) 2013 OpenStack Foundation.
+#
+# 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.
+
+import mock
+from oslotest import moxstubout
+
+from cinder.scheduler import base_filter
+from cinder import test
+
+
+class TestBaseFilter(test.TestCase):
+
+ def setUp(self):
+ super(TestBaseFilter, self).setUp()
+ self.mox = self.useFixture(moxstubout.MoxStubout()).mox
+ self.filter = base_filter.BaseFilter()
+
+ def test_filter_one_is_called(self):
+ filters = [1, 2, 3, 4]
+ filter_properties = {'x': 'y'}
+ self.mox.StubOutWithMock(self.filter, '_filter_one')
+
+ self.filter._filter_one(1, filter_properties).AndReturn(False)
+ self.filter._filter_one(2, filter_properties).AndReturn(True)
+ self.filter._filter_one(3, filter_properties).AndReturn(True)
+ self.filter._filter_one(4, filter_properties).AndReturn(False)
+
+ self.mox.ReplayAll()
+
+ result = list(self.filter.filter_all(filters, filter_properties))
+ self.assertEqual([2, 3], result)
+
+
+class FakeExtension(object):
+
+ def __init__(self, plugin):
+ self.plugin = plugin
+
+
+class BaseFakeFilter(base_filter.BaseFilter):
+ pass
+
+
+class FakeFilter1(BaseFakeFilter):
+ """Derives from BaseFakeFilter and has a fake entry point defined.
+
+ Entry point is returned by fake ExtensionManager.
+ Should be included in the output of all_classes.
+ """
+ pass
+
+
+class FakeFilter2(BaseFakeFilter):
+ """Derives from BaseFakeFilter but has no entry point.
+
+ Should be not included in all_classes.
+ """
+ pass
+
+
+class FakeFilter3(base_filter.BaseFilter):
+ """Does not derive from BaseFakeFilter.
+
+ Should not be included.
+ """
+ pass
+
+
+class FakeFilter4(BaseFakeFilter):
+ """Derives from BaseFakeFilter and has an entry point.
+
+ Should be included.
+ """
+ pass
+
+
+class FakeFilter5(BaseFakeFilter):
+ """Derives from BaseFakeFilter but has no entry point.
+
+ Should not be included.
+ """
+ run_filter_once_per_request = True
+ pass
+
+
+class FakeExtensionManager(list):
+
+ def __init__(self, namespace):
+ classes = [FakeFilter1, FakeFilter3, FakeFilter4]
+ exts = map(FakeExtension, classes)
+ super(FakeExtensionManager, self).__init__(exts)
+ self.namespace = namespace
+
+
+class TestBaseFilterHandler(test.TestCase):
+
+ def setUp(self):
+ super(TestBaseFilterHandler, self).setUp()
+ self.stubs = self.useFixture(moxstubout.MoxStubout()).stubs
+ self.stubs.Set(base_filter.base_handler.extension, 'ExtensionManager',
+ FakeExtensionManager)
+ self.handler = base_filter.BaseFilterHandler(BaseFakeFilter,
+ 'fake_filters')
+
+ def test_get_all_classes(self):
+ # In order for a FakeFilter to be returned by get_all_classes, it has
+ # to comply with these rules:
+ # * It must be derived from BaseFakeFilter
+ # AND
+ # * It must have a python entrypoint assigned (returned by
+ # FakeExtensionManager)
+ expected = [FakeFilter1, FakeFilter4]
+ result = self.handler.get_all_classes()
+ self.assertEqual(expected, result)
+
+ def _get_filtered_objects(self, filter_classes, index=0):
+ filter_objs_initial = [1, 2, 3, 4]
+ filter_properties = {'x': 'y'}
+ return self.handler.get_filtered_objects(filter_classes,
+ filter_objs_initial,
+ filter_properties,
+ index)
+
+ @mock.patch.object(FakeFilter4, 'filter_all')
+ @mock.patch.object(FakeFilter3, 'filter_all', return_value=None)
+ def test_get_filtered_objects_return_none(self, fake3_filter_all,
+ fake4_filter_all):
+ filter_classes = [FakeFilter1, FakeFilter2, FakeFilter3, FakeFilter4]
+ result = self._get_filtered_objects(filter_classes)
+ self.assertIsNone(result)
+ self.assertFalse(fake4_filter_all.called)
+
+ def test_get_filtered_objects(self):
+ filter_objs_expected = [1, 2, 3, 4]
+ filter_classes = [FakeFilter1, FakeFilter2, FakeFilter3, FakeFilter4]
+ result = self._get_filtered_objects(filter_classes)
+ self.assertEqual(filter_objs_expected, result)
+
+ def test_get_filtered_objects_with_filter_run_once(self):
+ filter_objs_expected = [1, 2, 3, 4]
+ filter_classes = [FakeFilter5]
+
+ with mock.patch.object(FakeFilter5, 'filter_all',
+ return_value=filter_objs_expected
+ ) as fake5_filter_all:
+ result = self._get_filtered_objects(filter_classes)
+ self.assertEqual(filter_objs_expected, result)
+ self.assertEqual(1, fake5_filter_all.call_count)
+
+ result = self._get_filtered_objects(filter_classes, index=1)
+ self.assertEqual(filter_objs_expected, result)
+ self.assertEqual(1, fake5_filter_all.call_count)
+
+ result = self._get_filtered_objects(filter_classes, index=2)
+ self.assertEqual(filter_objs_expected, result)
+ self.assertEqual(1, fake5_filter_all.call_count)
from oslo_config import cfg
from cinder import context
-from cinder.openstack.common.scheduler import weights
-from cinder.scheduler.weights import capacity
+from cinder.scheduler import weights
from cinder import test
from cinder.tests.unit.scheduler import fakes
from cinder.volume import utils
if weight_properties is None:
weight_properties = {'size': 1}
return self.weight_handler.get_weighed_objects(
- [capacity.CapacityWeigher],
+ [weights.capacity.CapacityWeigher],
hosts,
weight_properties)
from cinder import context
from cinder import db
from cinder import exception
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
from cinder import test
from cinder.tests.unit.scheduler import fakes
from cinder.tests.unit import utils
{'local_to_instance': 'e29b11d4-15ef-34a9-a716-598a6f0b5467'}}
self.assertRaises(exception.APITimeout,
filt_cls.host_passes, host, filter_properties)
+
+
+class TestFilter(filters.BaseHostFilter):
+ pass
+
+
+class TestBogusFilter(object):
+ """Class that doesn't inherit from BaseHostFilter."""
+ pass
+
+
+class ExtraSpecsOpsTestCase(test.TestCase):
+ def _do_extra_specs_ops_test(self, value, req, matches):
+ assertion = self.assertTrue if matches else self.assertFalse
+ assertion(filters.extra_specs_ops.match(value, req))
+
+ def test_extra_specs_matches_simple(self):
+ self._do_extra_specs_ops_test(
+ value='1',
+ req='1',
+ matches=True)
+
+ def test_extra_specs_fails_simple(self):
+ self._do_extra_specs_ops_test(
+ value='',
+ req='1',
+ matches=False)
+
+ def test_extra_specs_fails_simple2(self):
+ self._do_extra_specs_ops_test(
+ value='3',
+ req='1',
+ matches=False)
+
+ def test_extra_specs_fails_simple3(self):
+ self._do_extra_specs_ops_test(
+ value='222',
+ req='2',
+ matches=False)
+
+ def test_extra_specs_fails_with_bogus_ops(self):
+ self._do_extra_specs_ops_test(
+ value='4',
+ req='> 2',
+ matches=False)
+
+ def test_extra_specs_matches_with_op_eq(self):
+ self._do_extra_specs_ops_test(
+ value='123',
+ req='= 123',
+ matches=True)
+
+ def test_extra_specs_matches_with_op_eq2(self):
+ self._do_extra_specs_ops_test(
+ value='124',
+ req='= 123',
+ matches=True)
+
+ def test_extra_specs_fails_with_op_eq(self):
+ self._do_extra_specs_ops_test(
+ value='34',
+ req='= 234',
+ matches=False)
+
+ def test_extra_specs_fails_with_op_eq3(self):
+ self._do_extra_specs_ops_test(
+ value='34',
+ req='=',
+ matches=False)
+
+ def test_extra_specs_matches_with_op_seq(self):
+ self._do_extra_specs_ops_test(
+ value='123',
+ req='s== 123',
+ matches=True)
+
+ def test_extra_specs_fails_with_op_seq(self):
+ self._do_extra_specs_ops_test(
+ value='1234',
+ req='s== 123',
+ matches=False)
+
+ def test_extra_specs_matches_with_op_sneq(self):
+ self._do_extra_specs_ops_test(
+ value='1234',
+ req='s!= 123',
+ matches=True)
+
+ def test_extra_specs_fails_with_op_sneq(self):
+ self._do_extra_specs_ops_test(
+ value='123',
+ req='s!= 123',
+ matches=False)
+
+ def test_extra_specs_fails_with_op_sge(self):
+ self._do_extra_specs_ops_test(
+ value='1000',
+ req='s>= 234',
+ matches=False)
+
+ def test_extra_specs_fails_with_op_sle(self):
+ self._do_extra_specs_ops_test(
+ value='1234',
+ req='s<= 1000',
+ matches=False)
+
+ def test_extra_specs_fails_with_op_sl(self):
+ self._do_extra_specs_ops_test(
+ value='2',
+ req='s< 12',
+ matches=False)
+
+ def test_extra_specs_fails_with_op_sg(self):
+ self._do_extra_specs_ops_test(
+ value='12',
+ req='s> 2',
+ matches=False)
+
+ def test_extra_specs_matches_with_op_in(self):
+ self._do_extra_specs_ops_test(
+ value='12311321',
+ req='<in> 11',
+ matches=True)
+
+ def test_extra_specs_matches_with_op_in2(self):
+ self._do_extra_specs_ops_test(
+ value='12311321',
+ req='<in> 12311321',
+ matches=True)
+
+ def test_extra_specs_matches_with_op_in3(self):
+ self._do_extra_specs_ops_test(
+ value='12311321',
+ req='<in> 12311321 <in>',
+ matches=True)
+
+ def test_extra_specs_fails_with_op_in(self):
+ self._do_extra_specs_ops_test(
+ value='12310321',
+ req='<in> 11',
+ matches=False)
+
+ def test_extra_specs_fails_with_op_in2(self):
+ self._do_extra_specs_ops_test(
+ value='12310321',
+ req='<in> 11 <in>',
+ matches=False)
+
+ def test_extra_specs_matches_with_op_is(self):
+ self._do_extra_specs_ops_test(
+ value=True,
+ req='<is> True',
+ matches=True)
+
+ def test_extra_specs_matches_with_op_is2(self):
+ self._do_extra_specs_ops_test(
+ value=False,
+ req='<is> False',
+ matches=True)
+
+ def test_extra_specs_matches_with_op_is3(self):
+ self._do_extra_specs_ops_test(
+ value=False,
+ req='<is> Nonsense',
+ matches=True)
+
+ def test_extra_specs_fails_with_op_is(self):
+ self._do_extra_specs_ops_test(
+ value=True,
+ req='<is> False',
+ matches=False)
+
+ def test_extra_specs_fails_with_op_is2(self):
+ self._do_extra_specs_ops_test(
+ value=False,
+ req='<is> True',
+ matches=False)
+
+ def test_extra_specs_matches_with_op_or(self):
+ self._do_extra_specs_ops_test(
+ value='12',
+ req='<or> 11 <or> 12',
+ matches=True)
+
+ def test_extra_specs_matches_with_op_or2(self):
+ self._do_extra_specs_ops_test(
+ value='12',
+ req='<or> 11 <or> 12 <or>',
+ matches=True)
+
+ def test_extra_specs_fails_with_op_or(self):
+ self._do_extra_specs_ops_test(
+ value='13',
+ req='<or> 11 <or> 12',
+ matches=False)
+
+ def test_extra_specs_fails_with_op_or2(self):
+ self._do_extra_specs_ops_test(
+ value='13',
+ req='<or> 11 <or> 12 <or>',
+ matches=False)
+
+ def test_extra_specs_matches_with_op_le(self):
+ self._do_extra_specs_ops_test(
+ value='2',
+ req='<= 10',
+ matches=True)
+
+ def test_extra_specs_fails_with_op_le(self):
+ self._do_extra_specs_ops_test(
+ value='3',
+ req='<= 2',
+ matches=False)
+
+ def test_extra_specs_matches_with_op_ge(self):
+ self._do_extra_specs_ops_test(
+ value='3',
+ req='>= 1',
+ matches=True)
+
+ def test_extra_specs_fails_with_op_ge(self):
+ self._do_extra_specs_ops_test(
+ value='2',
+ req='>= 3',
+ matches=False)
+
+
+class BasicFiltersTestCase(HostFiltersTestCase):
+ """Test case for host filters."""
+
+ def setUp(self):
+ super(BasicFiltersTestCase, self).setUp()
+ self.json_query = jsonutils.dumps(
+ ['and', ['>=', '$free_ram_mb', 1024],
+ ['>=', '$free_disk_mb', 200 * 1024]])
+
+ def test_all_filters(self):
+ # Double check at least a couple of known filters exist
+ self.assertTrue('JsonFilter' in self.class_map)
+ self.assertTrue('CapabilitiesFilter' in self.class_map)
+ self.assertTrue('AvailabilityZoneFilter' in self.class_map)
+ self.assertTrue('IgnoreAttemptedHostsFilter' in self.class_map)
+
+ def _do_test_type_filter_extra_specs(self, ecaps, especs, passes):
+ filt_cls = self.class_map['CapabilitiesFilter']()
+ capabilities = {'enabled': True}
+ capabilities.update(ecaps)
+ service = {'disabled': False}
+ filter_properties = {'resource_type': {'name': 'fake_type',
+ 'extra_specs': especs}}
+ host = fakes.FakeHostState('host1',
+ {'free_capacity_gb': 1024,
+ 'capabilities': capabilities,
+ 'service': service})
+ assertion = self.assertTrue if passes else self.assertFalse
+ assertion(filt_cls.host_passes(host, filter_properties))
+
+ def test_capability_filter_passes_extra_specs_simple(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'opt1': '1', 'opt2': '2'},
+ especs={'opt1': '1', 'opt2': '2'},
+ passes=True)
+
+ def test_capability_filter_fails_extra_specs_simple(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'opt1': '1', 'opt2': '2'},
+ especs={'opt1': '1', 'opt2': '222'},
+ passes=False)
+
+ def test_capability_filter_passes_extra_specs_complex(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'opt1': 10, 'opt2': 5},
+ especs={'opt1': '>= 2', 'opt2': '<= 8'},
+ passes=True)
+
+ def test_capability_filter_fails_extra_specs_complex(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'opt1': 10, 'opt2': 5},
+ especs={'opt1': '>= 2', 'opt2': '>= 8'},
+ passes=False)
+
+ def test_capability_filter_passes_scope_extra_specs(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'scope_lv1': {'opt1': 10}},
+ especs={'capabilities:scope_lv1:opt1': '>= 2'},
+ passes=True)
+
+ def test_capability_filter_passes_fakescope_extra_specs(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'scope_lv1': {'opt1': 10}, 'opt2': 5},
+ especs={'scope_lv1:opt1': '= 2', 'opt2': '>= 3'},
+ passes=True)
+
+ def test_capability_filter_fails_scope_extra_specs(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'scope_lv1': {'opt1': 10}},
+ especs={'capabilities:scope_lv1:opt1': '<= 2'},
+ passes=False)
+
+ def test_capability_filter_passes_multi_level_scope_extra_specs(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'scope_lv0': {'scope_lv1':
+ {'scope_lv2': {'opt1': 10}}}},
+ especs={'capabilities:scope_lv0:scope_lv1:scope_lv2:opt1': '>= 2'},
+ passes=True)
+
+ def test_capability_filter_fails_wrong_scope_extra_specs(self):
+ self._do_test_type_filter_extra_specs(
+ ecaps={'scope_lv0': {'opt1': 10}},
+ especs={'capabilities:scope_lv1:opt1': '>= 2'},
+ passes=False)
+
+ def test_json_filter_passes(self):
+ filt_cls = self.class_map['JsonFilter']()
+ filter_properties = {'resource_type': {'memory_mb': 1024,
+ 'root_gb': 200,
+ 'ephemeral_gb': 0},
+ 'scheduler_hints': {'query': self.json_query}}
+ capabilities = {'enabled': True}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 1024,
+ 'free_disk_mb': 200 * 1024,
+ 'capabilities': capabilities})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_passes_with_no_query(self):
+ filt_cls = self.class_map['JsonFilter']()
+ filter_properties = {'resource_type': {'memory_mb': 1024,
+ 'root_gb': 200,
+ 'ephemeral_gb': 0}}
+ capabilities = {'enabled': True}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 0,
+ 'free_disk_mb': 0,
+ 'capabilities': capabilities})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_fails_on_memory(self):
+ filt_cls = self.class_map['JsonFilter']()
+ filter_properties = {'resource_type': {'memory_mb': 1024,
+ 'root_gb': 200,
+ 'ephemeral_gb': 0},
+ 'scheduler_hints': {'query': self.json_query}}
+ capabilities = {'enabled': True}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 1023,
+ 'free_disk_mb': 200 * 1024,
+ 'capabilities': capabilities})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_fails_on_disk(self):
+ filt_cls = self.class_map['JsonFilter']()
+ filter_properties = {'resource_type': {'memory_mb': 1024,
+ 'root_gb': 200,
+ 'ephemeral_gb': 0},
+ 'scheduler_hints': {'query': self.json_query}}
+ capabilities = {'enabled': True}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 1024,
+ 'free_disk_mb': (200 * 1024) - 1,
+ 'capabilities': capabilities})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_fails_on_caps_disabled(self):
+ filt_cls = self.class_map['JsonFilter']()
+ json_query = jsonutils.dumps(
+ ['and', ['>=', '$free_ram_mb', 1024],
+ ['>=', '$free_disk_mb', 200 * 1024],
+ '$capabilities.enabled'])
+ filter_properties = {'resource_type': {'memory_mb': 1024,
+ 'root_gb': 200,
+ 'ephemeral_gb': 0},
+ 'scheduler_hints': {'query': json_query}}
+ capabilities = {'enabled': False}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 1024,
+ 'free_disk_mb': 200 * 1024,
+ 'capabilities': capabilities})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_fails_on_service_disabled(self):
+ filt_cls = self.class_map['JsonFilter']()
+ json_query = jsonutils.dumps(
+ ['and', ['>=', '$free_ram_mb', 1024],
+ ['>=', '$free_disk_mb', 200 * 1024],
+ ['not', '$service.disabled']])
+ filter_properties = {'resource_type': {'memory_mb': 1024,
+ 'local_gb': 200},
+ 'scheduler_hints': {'query': json_query}}
+ capabilities = {'enabled': True}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 1024,
+ 'free_disk_mb': 200 * 1024,
+ 'capabilities': capabilities})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_happy_day(self):
+ """Test json filter more thoroughly."""
+ filt_cls = self.class_map['JsonFilter']()
+ raw = ['and',
+ '$capabilities.enabled',
+ ['=', '$capabilities.opt1', 'match'],
+ ['or',
+ ['and',
+ ['<', '$free_ram_mb', 30],
+ ['<', '$free_disk_mb', 300]],
+ ['and',
+ ['>', '$free_ram_mb', 30],
+ ['>', '$free_disk_mb', 300]]]]
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+
+ # Passes
+ capabilities = {'enabled': True, 'opt1': 'match'}
+ service = {'disabled': False}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 10,
+ 'free_disk_mb': 200,
+ 'capabilities': capabilities,
+ 'service': service})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ # Passes
+ capabilities = {'enabled': True, 'opt1': 'match'}
+ service = {'disabled': False}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 40,
+ 'free_disk_mb': 400,
+ 'capabilities': capabilities,
+ 'service': service})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ # Fails due to capabilities being disabled
+ capabilities = {'enabled': False, 'opt1': 'match'}
+ service = {'disabled': False}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 40,
+ 'free_disk_mb': 400,
+ 'capabilities': capabilities,
+ 'service': service})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ # Fails due to being exact memory/disk we don't want
+ capabilities = {'enabled': True, 'opt1': 'match'}
+ service = {'disabled': False}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 30,
+ 'free_disk_mb': 300,
+ 'capabilities': capabilities,
+ 'service': service})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ # Fails due to memory lower but disk higher
+ capabilities = {'enabled': True, 'opt1': 'match'}
+ service = {'disabled': False}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 20,
+ 'free_disk_mb': 400,
+ 'capabilities': capabilities,
+ 'service': service})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ # Fails due to capabilities 'opt1' not equal
+ capabilities = {'enabled': True, 'opt1': 'no-match'}
+ service = {'enabled': True}
+ host = fakes.FakeHostState('host1',
+ {'free_ram_mb': 20,
+ 'free_disk_mb': 400,
+ 'capabilities': capabilities,
+ 'service': service})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_basic_operators(self):
+ filt_cls = self.class_map['JsonFilter']()
+ host = fakes.FakeHostState('host1',
+ {'capabilities': {'enabled': True}})
+ # (operator, arguments, expected_result)
+ ops_to_test = [
+ ['=', [1, 1], True],
+ ['=', [1, 2], False],
+ ['<', [1, 2], True],
+ ['<', [1, 1], False],
+ ['<', [2, 1], False],
+ ['>', [2, 1], True],
+ ['>', [2, 2], False],
+ ['>', [2, 3], False],
+ ['<=', [1, 2], True],
+ ['<=', [1, 1], True],
+ ['<=', [2, 1], False],
+ ['>=', [2, 1], True],
+ ['>=', [2, 2], True],
+ ['>=', [2, 3], False],
+ ['in', [1, 1], True],
+ ['in', [1, 1, 2, 3], True],
+ ['in', [4, 1, 2, 3], False],
+ ['not', [True], False],
+ ['not', [False], True],
+ ['or', [True, False], True],
+ ['or', [False, False], False],
+ ['and', [True, True], True],
+ ['and', [False, False], False],
+ ['and', [True, False], False],
+ # Nested ((True or False) and (2 > 1)) == Passes
+ ['and', [['or', True, False], ['>', 2, 1]], True]]
+
+ for (op, args, expected) in ops_to_test:
+ raw = [op] + args
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertEqual(expected,
+ filt_cls.host_passes(host, filter_properties))
+
+ # This results in [False, True, False, True] and if any are True
+ # then it passes...
+ raw = ['not', True, False, True, False]
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ # This results in [False, False, False] and if any are True
+ # then it passes...which this doesn't
+ raw = ['not', True, True, True]
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_unknown_operator_raises(self):
+ filt_cls = self.class_map['JsonFilter']()
+ raw = ['!=', 1, 2]
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ host = fakes.FakeHostState('host1',
+ {'capabilities': {'enabled': True}})
+ self.assertRaises(KeyError,
+ filt_cls.host_passes, host, filter_properties)
+
+ def test_json_filter_empty_filters_pass(self):
+ filt_cls = self.class_map['JsonFilter']()
+ host = fakes.FakeHostState('host1',
+ {'capabilities': {'enabled': True}})
+
+ raw = []
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+ raw = {}
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_invalid_num_arguments_fails(self):
+ filt_cls = self.class_map['JsonFilter']()
+ host = fakes.FakeHostState('host1',
+ {'capabilities': {'enabled': True}})
+
+ raw = ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]]
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ raw = ['>', 1]
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_json_filter_unknown_variable_ignored(self):
+ filt_cls = self.class_map['JsonFilter']()
+ host = fakes.FakeHostState('host1',
+ {'capabilities': {'enabled': True}})
+
+ raw = ['=', '$........', 1, 1]
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ raw = ['=', '$foo', 2, 2]
+ filter_properties = {
+ 'scheduler_hints': {
+ 'query': jsonutils.dumps(raw),
+ },
+ }
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ @staticmethod
+ def _make_zone_request(zone, is_admin=False):
+ ctxt = context.RequestContext('fake', 'fake', is_admin=is_admin)
+ return {
+ 'context': ctxt,
+ 'request_spec': {
+ 'resource_properties': {
+ 'availability_zone': zone
+ }
+ }
+ }
+
+ def test_availability_zone_filter_same(self):
+ filt_cls = self.class_map['AvailabilityZoneFilter']()
+ service = {'availability_zone': 'nova'}
+ request = self._make_zone_request('nova')
+ host = fakes.FakeHostState('host1',
+ {'service': service})
+ self.assertTrue(filt_cls.host_passes(host, request))
+
+ def test_availability_zone_filter_different(self):
+ filt_cls = self.class_map['AvailabilityZoneFilter']()
+ service = {'availability_zone': 'nova'}
+ request = self._make_zone_request('bad')
+ host = fakes.FakeHostState('host1',
+ {'service': service})
+ self.assertFalse(filt_cls.host_passes(host, request))
+
+ def test_availability_zone_filter_empty(self):
+ filt_cls = self.class_map['AvailabilityZoneFilter']()
+ service = {'availability_zone': 'nova'}
+ request = {}
+ host = fakes.FakeHostState('host1',
+ {'service': service})
+ self.assertTrue(filt_cls.host_passes(host, request))
+
+ def test_ignore_attempted_hosts_filter_disabled(self):
+ # Test case where re-scheduling is disabled.
+ filt_cls = self.class_map['IgnoreAttemptedHostsFilter']()
+ host = fakes.FakeHostState('host1', {})
+ filter_properties = {}
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ def test_ignore_attempted_hosts_filter_pass(self):
+ # Node not previously tried.
+ filt_cls = self.class_map['IgnoreAttemptedHostsFilter']()
+ host = fakes.FakeHostState('host1', {})
+ attempted = dict(num_attempts=2, hosts=['host2'])
+ filter_properties = dict(retry=attempted)
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ def test_ignore_attempted_hosts_filter_fail(self):
+ # Node was already tried.
+ filt_cls = self.class_map['IgnoreAttemptedHostsFilter']()
+ host = fakes.FakeHostState('host1', {})
+ attempted = dict(num_attempts=2, hosts=['host1'])
+ filter_properties = dict(retry=attempted)
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
from cinder import exception
from cinder import objects
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
from cinder.scheduler import host_manager
from cinder import test
from cinder.tests.unit.objects import test_service
from cinder import context
from cinder.db.sqlalchemy import api
-from cinder.openstack.common.scheduler import weights
-from cinder.scheduler.weights import volume_number
+from cinder.scheduler import weights
from cinder import test
from cinder.tests.unit.scheduler import fakes
from cinder.volume import utils
if weight_properties is None:
weight_properties = {'context': self.context}
return self.weight_handler.get_weighed_objects(
- [volume_number.VolumeNumberWeigher],
+ [weights.volume_number.VolumeNumberWeigher],
hosts,
weight_properties)[0]
--- /dev/null
+# Copyright 2011-2012 OpenStack Foundation.
+# 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.
+
+"""
+Tests For Scheduler weights.
+"""
+
+from cinder.scheduler import base_weight
+from cinder import test
+
+
+class TestWeightHandler(test.TestCase):
+ def test_no_multiplier(self):
+ class FakeWeigher(base_weight.BaseWeigher):
+ def _weigh_object(self, *args, **kwargs):
+ pass
+
+ self.assertEqual(1.0,
+ FakeWeigher().weight_multiplier())
+
+ def test_no_weight_object(self):
+ class FakeWeigher(base_weight.BaseWeigher):
+ def weight_multiplier(self, *args, **kwargs):
+ pass
+ self.assertRaises(TypeError,
+ FakeWeigher)
+
+ def test_normalization(self):
+ # weight_list, expected_result, minval, maxval
+ map_ = (
+ ((), (), None, None),
+ ((0.0, 0.0), (0.0, 0.0), None, None),
+ ((1.0, 1.0), (0.0, 0.0), None, None),
+
+ ((20.0, 50.0), (0.0, 1.0), None, None),
+ ((20.0, 50.0), (0.0, 0.375), None, 100.0),
+ ((20.0, 50.0), (0.4, 1.0), 0.0, None),
+ ((20.0, 50.0), (0.2, 0.5), 0.0, 100.0),
+ )
+ for seq, result, minval, maxval in map_:
+ ret = base_weight.normalize(seq, minval=minval, maxval=maxval)
+ self.assertEqual(result, tuple(ret))
# The list of modules to copy from oslo-incubator
module=imageutils
-module=scheduler
-module=scheduler.filters
-module=scheduler.weights
# The base module to hold the copy of openstack.common
base=cinder
[entry_points]
cinder.scheduler.filters =
- AvailabilityZoneFilter = cinder.openstack.common.scheduler.filters.availability_zone_filter:AvailabilityZoneFilter
- CapabilitiesFilter = cinder.openstack.common.scheduler.filters.capabilities_filter:CapabilitiesFilter
+ AvailabilityZoneFilter = cinder.scheduler.filters.availability_zone_filter:AvailabilityZoneFilter
+ CapabilitiesFilter = cinder.scheduler.filters.capabilities_filter:CapabilitiesFilter
CapacityFilter = cinder.scheduler.filters.capacity_filter:CapacityFilter
DifferentBackendFilter = cinder.scheduler.filters.affinity_filter:DifferentBackendFilter
DriverFilter = cinder.scheduler.filters.driver_filter:DriverFilter
- JsonFilter = cinder.openstack.common.scheduler.filters.json_filter:JsonFilter
- RetryFilter = cinder.openstack.common.scheduler.filters.ignore_attempted_hosts_filter:IgnoreAttemptedHostsFilter
+ JsonFilter = cinder.scheduler.filters.json_filter:JsonFilter
+ RetryFilter = cinder.scheduler.filters.ignore_attempted_hosts_filter:IgnoreAttemptedHostsFilter
SameBackendFilter = cinder.scheduler.filters.affinity_filter:SameBackendFilter
InstanceLocalityFilter = cinder.scheduler.filters.instance_locality_filter:InstanceLocalityFilter
cinder.scheduler.weights =