]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Move oslo-incubator's scheduler module to cinder
authorMichał Dulko <michal.dulko@intel.com>
Tue, 24 Nov 2015 14:42:17 +0000 (15:42 +0100)
committerMichał Dulko <michal.dulko@intel.com>
Tue, 24 Nov 2015 14:49:31 +0000 (15:49 +0100)
oslo-incubator is ending its life and we should move remaining
dependencies from there to cinder namespace. This commit does so with
openstack.common.scheduler. Apart from that tests from oslo-incubator
repository are added.

Change-Id: I10d88c120c9c847826986483065f5493e91f89d6
Closes-Bug: 1519337

32 files changed:
cinder/openstack/common/scheduler/__init__.py [deleted file]
cinder/openstack/common/scheduler/filters/__init__.py [deleted file]
cinder/openstack/common/scheduler/weights/__init__.py [deleted file]
cinder/scheduler/base_filter.py [moved from cinder/openstack/common/scheduler/base_filter.py with 92% similarity]
cinder/scheduler/base_handler.py [moved from cinder/openstack/common/scheduler/base_handler.py with 96% similarity]
cinder/scheduler/base_weight.py [moved from cinder/openstack/common/scheduler/base_weight.py with 98% similarity]
cinder/scheduler/filters/__init__.py
cinder/scheduler/filters/affinity_filter.py
cinder/scheduler/filters/availability_zone_filter.py [moved from cinder/openstack/common/scheduler/filters/availability_zone_filter.py with 95% similarity]
cinder/scheduler/filters/capabilities_filter.py [moved from cinder/openstack/common/scheduler/filters/capabilities_filter.py with 91% similarity]
cinder/scheduler/filters/capacity_filter.py
cinder/scheduler/filters/driver_filter.py
cinder/scheduler/filters/extra_specs_ops.py [moved from cinder/openstack/common/scheduler/filters/extra_specs_ops.py with 100% similarity]
cinder/scheduler/filters/ignore_attempted_hosts_filter.py [moved from cinder/openstack/common/scheduler/filters/ignore_attempted_hosts_filter.py with 97% similarity]
cinder/scheduler/filters/instance_locality_filter.py
cinder/scheduler/filters/json_filter.py [moved from cinder/openstack/common/scheduler/filters/json_filter.py with 91% similarity]
cinder/scheduler/host_manager.py
cinder/scheduler/weights/__init__.py
cinder/scheduler/weights/capacity.py
cinder/scheduler/weights/chance.py
cinder/scheduler/weights/goodness.py
cinder/scheduler/weights/volume_number.py
cinder/tests/unit/scheduler/fake_hosts.py [new file with mode: 0644]
cinder/tests/unit/scheduler/test_allocated_capacity_weigher.py
cinder/tests/unit/scheduler/test_base_filter.py [new file with mode: 0644]
cinder/tests/unit/scheduler/test_capacity_weigher.py
cinder/tests/unit/scheduler/test_host_filters.py
cinder/tests/unit/scheduler/test_host_manager.py
cinder/tests/unit/scheduler/test_volume_number_weigher.py
cinder/tests/unit/scheduler/test_weights.py [new file with mode: 0644]
openstack-common.conf
setup.cfg

diff --git a/cinder/openstack/common/scheduler/__init__.py b/cinder/openstack/common/scheduler/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/cinder/openstack/common/scheduler/filters/__init__.py b/cinder/openstack/common/scheduler/filters/__init__.py
deleted file mode 100644 (file)
index dbd87a2..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-# 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)
diff --git a/cinder/openstack/common/scheduler/weights/__init__.py b/cinder/openstack/common/scheduler/weights/__init__.py
deleted file mode 100644 (file)
index f4f5f42..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-# 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)
similarity index 92%
rename from cinder/openstack/common/scheduler/base_filter.py
rename to cinder/scheduler/base_filter.py
index 35287ce08063d3cb4228de5a4eda93f36cec1019..2ce8681f96e766bbf6eec7c240380c35711ef51d 100644 (file)
@@ -19,7 +19,7 @@ Filter support
 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__)
 
@@ -28,6 +28,7 @@ class BaseFilter(object):
     """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
@@ -48,9 +49,10 @@ class BaseFilter(object):
     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)
 
similarity index 96%
rename from cinder/openstack/common/scheduler/base_handler.py
rename to cinder/scheduler/base_handler.py
index 44c8eca5b86f9b6518c2d1e7b267b2f1147055cb..bc8e142171c916f796234f3eb238170b8a1b3a9a 100644 (file)
@@ -32,8 +32,9 @@ class BaseHandler(object):
         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
similarity index 98%
rename from cinder/openstack/common/scheduler/base_weight.py
rename to cinder/scheduler/base_weight.py
index b2429b33f5866ce85a5b64ff1675e553b684a955..0ea982ca888a4fddda379955ba9ae1aa20e0cd41 100644 (file)
@@ -21,7 +21,7 @@ import abc
 
 import six
 
-from cinder.openstack.common.scheduler import base_handler
+from cinder.scheduler import base_handler
 
 
 def normalize(weight_list, minval=None, maxval=None):
@@ -87,9 +87,7 @@ class BaseWeigher(object):
 
     @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.
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f29852420f6c8c4d1d517a41be04bd511f1e5c26 100644 (file)
@@ -0,0 +1,39 @@
+# 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)
index c55e548705c99236a4143ed3590f3ffd80258a7c..f6887d13cbdbef3f7e6b2ac9b2b3274cb30efd9f 100644 (file)
@@ -17,7 +17,7 @@
 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__)
similarity index 95%
rename from cinder/openstack/common/scheduler/filters/availability_zone_filter.py
rename to cinder/scheduler/filters/availability_zone_filter.py
index 8e7f1f03c327262a4f2ff87c1a6ed5ca7c358ce0..0ff245d6f521b7ee5439a52e61ff59d2ebe0cc9b 100644 (file)
@@ -13,7 +13,7 @@
 #    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):
similarity index 91%
rename from cinder/openstack/common/scheduler/filters/capabilities_filter.py
rename to cinder/scheduler/filters/capabilities_filter.py
index 9f4d50a33a57f5ffdb74110605d72c90b63d911e..fe88caae7292a76214cfff74297c82063383225a 100644 (file)
@@ -17,8 +17,8 @@ import logging
 
 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__)
 
@@ -27,7 +27,9 @@ class CapabilitiesFilter(filters.BaseHostFilter):
     """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', [])
index 199b95e46769e60d4a91345efcd3cbd9155c3a13..ed43e2eab28c4c21a49197f43f6728124774f82f 100644 (file)
@@ -22,7 +22,7 @@ import math
 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__)
index a406c4ca57d85817432e9e345db44b2d6f957d22..b57532413e52eb518ceff76dfc959b7d693e71b4 100644 (file)
@@ -17,8 +17,8 @@ from oslo_log import log as logging
 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__)
similarity index 97%
rename from cinder/openstack/common/scheduler/filters/ignore_attempted_hosts_filter.py
rename to cinder/scheduler/filters/ignore_attempted_hosts_filter.py
index 33cdd3fee349a3ac293dd18c801fcbfcb4299c82..db1071d820acfba4f451311fbf62fd277910fc61 100644 (file)
@@ -15,7 +15,7 @@
 
 import logging
 
-from cinder.openstack.common.scheduler import filters
+from cinder.scheduler import filters
 
 LOG = logging.getLogger(__name__)
 
index 6046f9440707256b8c8e50f7c25793c6a97b34ad..2fcee13775964f3b8e7e5559ce223ec576093162 100644 (file)
@@ -19,7 +19,7 @@ from oslo_utils import uuidutils
 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
 
 
similarity index 91%
rename from cinder/openstack/common/scheduler/filters/json_filter.py
rename to cinder/scheduler/filters/json_filter.py
index 74d4443e1e97b5e11cb94f9ebaa2e8f7557da8e7..dbbca066dd7c46cd27b56a17a6ed3871bf788250 100644 (file)
@@ -18,15 +18,15 @@ import operator
 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.
         """
@@ -88,7 +88,9 @@ class JsonFilter(filters.BaseHostFilter):
     }
 
     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
@@ -126,9 +128,7 @@ class JsonFilter(filters.BaseHostFilter):
         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:
index 9cf70739bdbf8ac994f9ed1cfd280e24fd8407ae..d4c1135eab752278a0ccd33a74e4983f1cb09d0c 100644 (file)
@@ -25,11 +25,11 @@ from oslo_utils import timeutils
 
 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
 
 
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b122c9756ee2014d4fdd4123929dd9376f669857 100644 (file)
@@ -0,0 +1,44 @@
+# 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)
index 56c580ba3741cfa99caa857c26f91fde22a8a8ff..6dec05bf334f12e59d31439f826a827fa9d4a613 100644 (file)
@@ -44,7 +44,7 @@ import math
 
 from oslo_config import cfg
 
-from cinder.openstack.common.scheduler import weights
+from cinder.scheduler import weights
 
 
 capacity_weight_opts = [
index 4e79b79a2c8a22ebd05fe992f4d601cdd6e01168..809e7548da61cb67a6868c0581a35bc91375dc11 100644 (file)
@@ -20,7 +20,7 @@ Used to spread volumes randomly across a list of equally suitable hosts.
 
 import random
 
-from cinder.openstack.common.scheduler import weights
+from cinder.scheduler import weights
 
 
 class ChanceWeigher(weights.BaseHostWeigher):
index fa6fc09594fd77dbff417e320e257097becedb3a..8758d71c8270a72e47391465a00fb4fd62cd88e1 100644 (file)
@@ -16,8 +16,8 @@ from oslo_log import log as logging
 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__)
index 3b9c4196c8c350441c5a5de807ba4457505629ee..619989f3499f5bb15881636937cc433c63232cf4 100644 (file)
@@ -26,7 +26,7 @@ from oslo_config import cfg
 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__)
diff --git a/cinder/tests/unit/scheduler/fake_hosts.py b/cinder/tests/unit/scheduler/fake_hosts.py
new file mode 100644 (file)
index 0000000..8cc988f
--- /dev/null
@@ -0,0 +1,53 @@
+# 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)
index e0f09df65390942443fb2c83447f4ca545480000..f01c9e8bb5f259dc5bcd55d40ca43264300976b6 100644 (file)
@@ -21,8 +21,7 @@ import mock
 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
@@ -41,7 +40,7 @@ class AllocatedCapacityWeigherTestCase(test.TestCase):
         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')
diff --git a/cinder/tests/unit/scheduler/test_base_filter.py b/cinder/tests/unit/scheduler/test_base_filter.py
new file mode 100644 (file)
index 0000000..7eae7e3
--- /dev/null
@@ -0,0 +1,168 @@
+# 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)
index 11f496c20558ad3c23ea6f161ba93ff975856b64..fb6826a655e2ecba214b881d7eac626b76068232 100644 (file)
@@ -20,8 +20,7 @@ import mock
 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
@@ -40,7 +39,7 @@ class CapacityWeigherTestCase(test.TestCase):
         if weight_properties is None:
             weight_properties = {'size': 1}
         return self.weight_handler.get_weighed_objects(
-            [capacity.CapacityWeigher],
+            [weights.capacity.CapacityWeigher],
             hosts,
             weight_properties)
 
index 659119dff87e775297ed2460314d5dbd7dd31a04..cacd3d85a9f69c61b22ef7b186de3dd91c8bd2e6 100644 (file)
@@ -23,7 +23,7 @@ from cinder.compute import nova
 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
@@ -988,3 +988,674 @@ class InstanceLocalityFilterTestCase(HostFiltersTestCase):
                 {'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))
index c31292c9d31d01552f3021a71216bb42810b777f..d0c38c52ed4cc3ea8ad2ce11e4ff67b98b1584dd 100644 (file)
@@ -24,7 +24,7 @@ from oslo_utils import timeutils
 
 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
index b5b81c41a5c2e9414910b073f4c7f10c04c9a745..56e6b345046ae86e5417f18f4b5dccd1203aa31c 100644 (file)
@@ -21,8 +21,7 @@ from oslo_config import cfg
 
 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
@@ -58,7 +57,7 @@ class VolumeNumberWeigherTestCase(test.TestCase):
         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]
 
diff --git a/cinder/tests/unit/scheduler/test_weights.py b/cinder/tests/unit/scheduler/test_weights.py
new file mode 100644 (file)
index 0000000..687bd1d
--- /dev/null
@@ -0,0 +1,54 @@
+# 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))
index 2faec5d23bb6d8cfcea7c46520ef4b1ad9f8863c..4c4d23365ea6dc58fb7cbac1ed1d5012d90c414a 100644 (file)
@@ -2,9 +2,6 @@
 
 # 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
index 9ef5d3fb30c06748cbadd0d80e22ff5242e59e78..c1c7f6d056fad20cdca34e7ad83917d8069a1ba4 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -27,13 +27,13 @@ packages =
 
 [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 =