]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Sync scheduler module from oslo-incubator
authorJames Carey <jecarey@us.ibm.com>
Wed, 29 Jul 2015 22:55:03 +0000 (22:55 +0000)
committerJames Carey <jecarey@us.ibm.com>
Thu, 6 Aug 2015 04:29:11 +0000 (04:29 +0000)
The scheduler and scheduler.weights modules have not been
updated since early in the Icehouse release cycle.  This
patch brings the version in Cinder up to date with what is in
oslo-incubator.

Current HEAD in OSLO:
---------------------
commit 20d7dc57819a70abdff967299542068946d75ac1
Date:   Wed Jul 29 03:49:46 2015 +0000
    Updated from global requirements

    Change-Id: Iba0e4f9545fc9ba82f080a0fec672761dcfeaeec

Patches included with sync by file:
---------------------
base_filter.py
 - 2fbf5065 - Remove oslo.log code and clean up versionutils API
 - 5d40e143 - Remove code that moved to oslo.i18n
 - 2af88ece - Use _LI instead of _ for info message translation
 - 4a47188e - Allow filters to only run once per request if their data is static
 - fcf517d7 - Update oslo log messages with translation domains
 - cae33101 - Stop looping all filters if no objects return
 base_handler.py
 - 6fa29aee - Trivial: Make vertical white space after license header consistent
 - 35660dac7 - Enable hacking H401 test
 - 5dcc43b1 - Break out common functionality for filters and weights
base_weight.py
 - 825cb870 - Upgrade to hacking 0.10
 - a2fa4878 - Fix common.scheduler.base_weight.BaseWeigher to be Python3 compat
 - e47bc70e - Normalize Scheduler Weights

 Note: Key changes were made that impact cinder:
   (1) The weigher base class changed the name of _weight_mulitipler() to
       weight_multiplier() which impacted cinder/scheduler/weights/capacity.py
       and cinder/scheduler/weights/volume_number.py.
   (2) Application of the weight multiplier was moved from the weigher to the
       handler and normalization of the weights prior to applying the weight
       multiplier was added.  This impacted cinder weigher test cases which
       were checking for non-normalized weights.
   (3) The normalization does not support the use of infinite weights which
       are used by the cinder capacity weigher.  When an infinite value is
       used, it is not known what the other weights will be, so this adds an
       override to CapacityWeigher for the weigh_objects() method that wraps
       the parent method to use the returned complete set of weights to
       replace any infinite weights with values that are much larger than
       the largest non-infinite weight.

Change-Id: Iad9bb431f0876e4957598c0ac0d3c8d7f67199fc

cinder/openstack/common/scheduler/base_filter.py
cinder/openstack/common/scheduler/base_handler.py
cinder/openstack/common/scheduler/base_weight.py
cinder/scheduler/weights/capacity.py
cinder/scheduler/weights/volume_number.py
cinder/tests/unit/scheduler/test_allocated_capacity_weigher.py
cinder/tests/unit/scheduler/test_capacity_weigher.py
cinder/tests/unit/scheduler/test_volume_number_weigher.py

index e31e7a270d00ba4caff29a59eb98ede984ab8101..35287ce08063d3cb4228de5a4eda93f36cec1019 100644 (file)
 """
 Filter support
 """
+import logging
 
+from cinder.openstack.common._i18n import _LI
 from cinder.openstack.common.scheduler import base_handler
 
+LOG = logging.getLogger(__name__)
+
 
 class BaseFilter(object):
     """Base class for all filter classes."""
@@ -39,6 +43,17 @@ class BaseFilter(object):
             if self._filter_one(obj, filter_properties):
                 yield obj
 
+    # Set to true in a subclass if a filter only needs to be run once
+    # for each request rather than for each instance
+    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 not (self.run_filter_once_per_request and index > 0)
+
 
 class BaseFilterHandler(base_handler.BaseHandler):
     """Base class to handle loading filter classes.
@@ -47,7 +62,34 @@ class BaseFilterHandler(base_handler.BaseHandler):
     """
 
     def get_filtered_objects(self, filter_classes, objs,
-                             filter_properties):
+                             filter_properties, index=0):
+        """Get objects after filter
+
+        :param filter_classes: filters that will be used to filter the
+                               objects
+        :param objs: objects that will be filtered
+        :param filter_properties: client filter properties
+        :param index: This value needs to be increased in the caller
+                      function of get_filtered_objects when handling
+                      each resource.
+        """
+        list_objs = list(objs)
+        LOG.debug("Starting with %d host(s)", len(list_objs))
         for filter_cls in filter_classes:
-            objs = filter_cls().filter_all(objs, filter_properties)
-        return list(objs)
+            cls_name = filter_cls.__name__
+            filter_class = filter_cls()
+
+            if filter_class.run_filter_for_index(index):
+                objs = filter_class.filter_all(list_objs, filter_properties)
+                if objs is None:
+                    LOG.debug("Filter %(cls_name)s says to stop filtering",
+                              {'cls_name': cls_name})
+                    return
+                list_objs = list(objs)
+                msg = (_LI("Filter %(cls_name)s returned %(obj_len)d host(s)")
+                       % {'cls_name': cls_name, 'obj_len': len(list_objs)})
+                if not list_objs:
+                    LOG.info(msg)
+                    break
+                LOG.debug(msg)
+        return list_objs
index 1808d2c61d3f046ca4d45c8aed1f7206a87ca2cb..44c8eca5b86f9b6518c2d1e7b267b2f1147055cb 100644 (file)
@@ -12,6 +12,7 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+
 """
 A common base for handling extension classes.
 
index a6ba75d360344c17ed47e952beeb24ae0cc2a6bf..b2429b33f5866ce85a5b64ff1675e553b684a955 100644 (file)
 Pluggable Weighing support
 """
 
+import abc
+
+import six
+
 from cinder.openstack.common.scheduler import base_handler
 
 
+def normalize(weight_list, minval=None, maxval=None):
+    """Normalize the values in a list between 0 and 1.0.
+
+    The normalization is made regarding the lower and upper values present in
+    weight_list. If the minval and/or maxval parameters are set, these values
+    will be used instead of the minimum and maximum from the list.
+
+    If all the values are equal, they are normalized to 0.
+    """
+
+    if not weight_list:
+        return ()
+
+    if maxval is None:
+        maxval = max(weight_list)
+
+    if minval is None:
+        minval = min(weight_list)
+
+    maxval = float(maxval)
+    minval = float(minval)
+
+    if minval == maxval:
+        return [0] * len(weight_list)
+
+    range_ = maxval - minval
+    return ((i - minval) / range_ for i in weight_list)
+
+
 class WeighedObject(object):
     """Object with weight information."""
     def __init__(self, obj, weight):
@@ -30,28 +63,61 @@ class WeighedObject(object):
         return "<WeighedObject '%s': %s>" % (self.obj, self.weight)
 
 
+@six.add_metaclass(abc.ABCMeta)
 class BaseWeigher(object):
-    """Base class for pluggable weighers."""
-    def _weight_multiplier(self):
-        """How weighted this weigher should be.  Normally this would
-        be overridden in a subclass based on a config value.
+    """Base class for pluggable weighers.
+
+    The attributes maxval and minval can be specified to set up the maximum
+    and minimum values for the weighed objects. These values will then be
+    taken into account in the normalization step, instead of taking the values
+    from the calculated weights.
+    """
+
+    minval = None
+    maxval = None
+
+    def weight_multiplier(self):
+        """How weighted this weigher should be.
+
+        Override this method in a subclass, so that the returned value is
+        read from a configuration option to permit operators specify a
+        multiplier for the weigher.
         """
         return 1.0
 
+    @abc.abstractmethod
     def _weigh_object(self, obj, weight_properties):
         """Override in a subclass to specify a weight for a specific
         object.
         """
-        return 0.0
 
     def weigh_objects(self, weighed_obj_list, weight_properties):
-        """Weigh multiple objects.  Override in a subclass if you need
-        need access to all objects in order to manipulate weights.
+        """Weigh multiple objects.
+
+        Override in a subclass if you need access to all objects in order
+        to calculate weights. Do not modify the weight of an object here,
+        just return a list of weights.
         """
-        constant = self._weight_multiplier()
+        # Calculate the weights
+        weights = []
         for obj in weighed_obj_list:
-            obj.weight += (constant *
-                           self._weigh_object(obj.obj, weight_properties))
+            weight = self._weigh_object(obj.obj, weight_properties)
+
+            # Record the min and max values if they are None. If they anything
+            # but none we assume that the weigher has set them
+            if self.minval is None:
+                self.minval = weight
+            if self.maxval is None:
+                self.maxval = weight
+
+            if weight < self.minval:
+                self.minval = weight
+            elif weight > self.maxval:
+                self.maxval = weight
+
+            weights.append(weight)
+
+        return weights
 
 
 class BaseWeightHandler(base_handler.BaseHandler):
@@ -59,7 +125,7 @@ class BaseWeightHandler(base_handler.BaseHandler):
 
     def get_weighed_objects(self, weigher_classes, obj_list,
                             weighing_properties):
-        """Return a sorted (highest score first) list of WeighedObjects."""
+        """Return a sorted (descending), normalized list of WeighedObjects."""
 
         if not obj_list:
             return []
@@ -67,6 +133,15 @@ class BaseWeightHandler(base_handler.BaseHandler):
         weighed_objs = [self.object_class(obj, 0.0) for obj in obj_list]
         for weigher_cls in weigher_classes:
             weigher = weigher_cls()
-            weigher.weigh_objects(weighed_objs, weighing_properties)
+            weights = weigher.weigh_objects(weighed_objs, weighing_properties)
+
+            # Normalize the weights
+            weights = normalize(weights,
+                                minval=weigher.minval,
+                                maxval=weigher.maxval)
+
+            for i, weight in enumerate(weights):
+                obj = weighed_objs[i]
+                obj.weight += weigher.weight_multiplier() * weight
 
         return sorted(weighed_objs, key=lambda x: x.weight, reverse=True)
index 88d322145a9efb9a6b84b20531b970857edd4c11..56c580ba3741cfa99caa857c26f91fde22a8a8ff 100644 (file)
@@ -61,12 +61,44 @@ capacity_weight_opts = [
 CONF = cfg.CONF
 CONF.register_opts(capacity_weight_opts)
 
+OFFSET_MIN = 10000
+OFFSET_MULT = 100
+
 
 class CapacityWeigher(weights.BaseHostWeigher):
-    def _weight_multiplier(self):
+    def weight_multiplier(self):
         """Override the weight multiplier."""
         return CONF.capacity_weight_multiplier
 
+    def weigh_objects(self, weighed_obj_list, weight_properties):
+        """Override the weigh objects.
+
+
+        This override calls the parent to do the weigh objects and then
+        replaces any infinite weights with a value that is a multiple of the
+        delta between the min and max values.
+
+        NOTE(jecarey): the infinite weight value is only used when the
+        smallest value is being favored (negative multiplier).  When the
+        largest weight value is being used a weight of -1 is used instead.
+        See _weigh_object method.
+        """
+        tmp_weights = super(weights.BaseHostWeigher, self).weigh_objects(
+            weighed_obj_list, weight_properties)
+
+        if math.isinf(self.maxval):
+            # NOTE(jecarey): if all weights were infinite then parent
+            # method returns 0 for all of the weights.  Thus self.minval
+            # cannot be infinite at this point
+            copy_weights = [w for w in tmp_weights if not math.isinf(w)]
+            self.maxval = max(copy_weights)
+            offset = (self.maxval - self.minval) * OFFSET_MULT
+            self.maxval += OFFSET_MIN if offset == 0.0 else offset
+            tmp_weights = [self.maxval if math.isinf(w) else w
+                           for w in tmp_weights]
+
+        return tmp_weights
+
     def _weigh_object(self, host_state, weight_properties):
         """Higher weights win.  We want spreading to be the default."""
         reserved = float(host_state.reserved_percentage) / 100
@@ -96,7 +128,7 @@ class CapacityWeigher(weights.BaseHostWeigher):
 
 
 class AllocatedCapacityWeigher(weights.BaseHostWeigher):
-    def _weight_multiplier(self):
+    def weight_multiplier(self):
         """Override the weight multiplier."""
         return CONF.allocated_capacity_weight_multiplier
 
index 54a9d3394fd3dc8d3dd9f41ae29ff1a8f30fa05b..3b9c4196c8c350441c5a5de807ba4457505629ee 100644 (file)
@@ -44,7 +44,7 @@ CONF.register_opts(volume_number_weight_opts)
 
 
 class VolumeNumberWeigher(weights.BaseHostWeigher):
-    def _weight_multiplier(self):
+    def weight_multiplier(self):
         """Override the weight multiplier."""
         return CONF.volume_number_multiplier
 
index 9ed3732f9ec4879d6da15dcfeb2970b832e65dc7..e0f09df65390942443fb2c83447f4ca545480000 100644 (file)
@@ -57,14 +57,15 @@ class AllocatedCapacityWeigherTestCase(test.TestCase):
     def test_default_of_spreading_first(self):
         hostinfo_list = self._get_all_hosts()
 
-        # host1: allocated_capacity_gb=0, weight=0
+        # host1: allocated_capacity_gb=0, weight=0        Norm=0.0
         # host2: allocated_capacity_gb=1748, weight=-1748
         # host3: allocated_capacity_gb=256, weight=-256
-        # host4: allocated_capacity_gb=1848, weight=-1848
+        # host4: allocated_capacity_gb=1848, weight=-1848 Norm=-1.0
+        # host5: allocated_capacity_gb=1548, weight=-1540
 
         # so, host1 should win:
         weighed_host = self._get_weighed_host(hostinfo_list)
-        self.assertEqual(0, weighed_host.weight)
+        self.assertEqual(0.0, weighed_host.weight)
         self.assertEqual(
             'host1', utils.extract_host(weighed_host.obj.host))
 
@@ -72,14 +73,15 @@ class AllocatedCapacityWeigherTestCase(test.TestCase):
         self.flags(allocated_capacity_weight_multiplier=1.0)
         hostinfo_list = self._get_all_hosts()
 
-        # host1: allocated_capacity_gb=0, weight=0
+        # host1: allocated_capacity_gb=0, weight=0          Norm=0.0
         # host2: allocated_capacity_gb=1748, weight=1748
         # host3: allocated_capacity_gb=256, weight=256
-        # host4: allocated_capacity_gb=1848, weight=1848
+        # host4: allocated_capacity_gb=1848, weight=1848    Norm=1.0
+        # host5: allocated_capacity_gb=1548, weight=1540
 
         # so, host4 should win:
         weighed_host = self._get_weighed_host(hostinfo_list)
-        self.assertEqual(1848.0, weighed_host.weight)
+        self.assertEqual(1.0, weighed_host.weight)
         self.assertEqual(
             'host4', utils.extract_host(weighed_host.obj.host))
 
@@ -87,13 +89,14 @@ class AllocatedCapacityWeigherTestCase(test.TestCase):
         self.flags(allocated_capacity_weight_multiplier=-2.0)
         hostinfo_list = self._get_all_hosts()
 
-        # host1: allocated_capacity_gb=0, weight=0
+        # host1: allocated_capacity_gb=0, weight=0        Norm=0.0
         # host2: allocated_capacity_gb=1748, weight=-3496
         # host3: allocated_capacity_gb=256, weight=-512
-        # host4: allocated_capacity_gb=1848, weight=-3696
+        # host4: allocated_capacity_gb=1848, weight=-3696 Norm=-2.0
+        # host5: allocated_capacity_gb=1548, weight=-3080
 
         # so, host1 should win:
         weighed_host = self._get_weighed_host(hostinfo_list)
-        self.assertEqual(weighed_host.weight, 0)
+        self.assertEqual(0.0, weighed_host.weight)
         self.assertEqual(
             'host1', utils.extract_host(weighed_host.obj.host))
index 25d8909e5e1ae31dc33e19dfe460e230b66ec88a..7776fd633e783941344c1adc9a3b6dad0533d3b1 100644 (file)
@@ -66,19 +66,24 @@ class CapacityWeigherTestCase(test.TestCase):
         # host1: thin_provisioning_support = False
         #        free_capacity_gb=1024,
         #        free=1024-math.floor(1024*0.1)=922
+        #        Norm=0.837837837838
         # host2: thin_provisioning_support = True
         #        free_capacity_gb=300,
         #        free=2048*1.5-1748-math.floor(2048*0.1)=1120
+        #        Norm=1.0
         # host3: thin_provisioning_support = False
         #        free_capacity_gb=512, free=256-512*0=256
+        #        Norm=0.292383292383
         # host4: thin_provisioning_support = True
         #        free_capacity_gb=200,
         #        free=2048*1.0-2047-math.floor(2048*0.05)=-101
+        #        Norm=0.0
         # host5: free_capacity_gb=unknown free=-1
+        #        Norm=0.0819000819001
 
         # so, host2 should win:
         weighed_host = self._get_weighed_host(hostinfo_list)
-        self.assertEqual(weighed_host.weight, 1120.0)
+        self.assertEqual(weighed_host.weight, 1.0)
         self.assertEqual(
             utils.extract_host(weighed_host.obj.host), 'host2')
 
@@ -89,19 +94,24 @@ class CapacityWeigherTestCase(test.TestCase):
         # host1: thin_provisioning_support = False
         #        free_capacity_gb=1024,
         #        free=-(1024-math.floor(1024*0.1))=-922
+        #        Norm=-0.00829542413701
         # host2: thin_provisioning_support = True
         #        free_capacity_gb=300,
         #        free=-(2048*1.5-1748-math.floor(2048*0.1))=-1120
+        #        Norm=-0.00990099009901
         # host3: thin_provisioning_support = False
         #        free_capacity_gb=512, free=-(256-512*0)=-256
+        #        Norm=--0.002894884083
         # host4: thin_provisioning_support = True
         #        free_capacity_gb=200,
         #        free=-(2048*1.0-2047-math.floor(2048*0.05))=101
+        #        Norm=0.0
         # host5: free_capacity_gb=unknown free=-float('inf')
+        #        Norm=-1.0
 
         # so, host4 should win:
         weighed_host = self._get_weighed_host(hostinfo_list)
-        self.assertEqual(weighed_host.weight, 101.0)
+        self.assertEqual(weighed_host.weight, 0.0)
         self.assertEqual(
             utils.extract_host(weighed_host.obj.host), 'host4')
 
@@ -112,18 +122,23 @@ class CapacityWeigherTestCase(test.TestCase):
         # host1: thin_provisioning_support = False
         #        free_capacity_gb=1024,
         #        free=(1024-math.floor(1024*0.1))*2=1844
+        #        Norm=1.67567567568
         # host2: thin_provisioning_support = True
         #        free_capacity_gb=300,
         #        free=(2048*1.5-1748-math.floor(2048*0.1))*2=2240
+        #        Norm=2.0
         # host3: thin_provisioning_support = False
         #        free_capacity_gb=512, free=(256-512*0)*2=512
+        #        Norm=0.584766584767
         # host4: thin_provisioning_support = True
         #        free_capacity_gb=200,
         #        free=(2048*1.0-2047-math.floor(2048*0.05))*2=-202
+        #        Norm=0.0
         # host5: free_capacity_gb=unknown free=-2
+        #        Norm=0.1638001638
 
         # so, host2 should win:
         weighed_host = self._get_weighed_host(hostinfo_list)
-        self.assertEqual(weighed_host.weight, 1120.0 * 2)
+        self.assertEqual(weighed_host.weight, 1.0 * 2)
         self.assertEqual(
             utils.extract_host(weighed_host.obj.host), 'host2')
index 3cccffe54c2ae95bbaa7fa0e87521795b43ea256..82b4a09d4235bcfc8c97d63f0201a39d7a507a5c 100644 (file)
@@ -76,16 +76,16 @@ class VolumeNumberWeigherTestCase(test.TestCase):
         self.flags(volume_number_multiplier=-1.0)
         hostinfo_list = self._get_all_hosts()
 
-        # host1: 1 volume
+        # host1: 1 volume    Norm=0.0
         # host2: 2 volumes
         # host3: 3 volumes
         # host4: 4 volumes
-        # host5: 5 volumes
+        # host5: 5 volumes   Norm=-1.0
         # so, host1 should win:
         with mock.patch.object(api, 'volume_data_get_for_host',
                                fake_volume_data_get_for_host):
             weighed_host = self._get_weighed_host(hostinfo_list)
-            self.assertEqual(weighed_host.weight, -1.0)
+            self.assertEqual(weighed_host.weight, 0.0)
             self.assertEqual(utils.extract_host(weighed_host.obj.host),
                              'host1')
 
@@ -93,15 +93,15 @@ class VolumeNumberWeigherTestCase(test.TestCase):
         self.flags(volume_number_multiplier=1.0)
         hostinfo_list = self._get_all_hosts()
 
-        # host1: 1 volume
+        # host1: 1 volume      Norm=0
         # host2: 2 volumes
         # host3: 3 volumes
         # host4: 4 volumes
-        # host5: 5 volumes
+        # host5: 5 volumes     Norm=1
         # so, host5 should win:
         with mock.patch.object(api, 'volume_data_get_for_host',
                                fake_volume_data_get_for_host):
             weighed_host = self._get_weighed_host(hostinfo_list)
-            self.assertEqual(weighed_host.weight, 5.0)
+            self.assertEqual(weighed_host.weight, 1.0)
             self.assertEqual(utils.extract_host(weighed_host.obj.host),
                              'host5')