From 50dc29276414c977acf4ba455f21f1634986b47f Mon Sep 17 00:00:00 2001 From: ling-yun Date: Wed, 25 Jun 2014 10:40:38 +0800 Subject: [PATCH] Support Volume Num Weighter Currently cinder support choosing volume backend according to free_capacity and allocated_capacity. Volume Num Weighter is that scheduler could choose volume backend based on volume number in volume backend, which could provide another mean to help improve volume-backends' IO balance and volumes' IO performance, see details in ref [1] and [2]. blueprint support-volume-num-weighter Ref: [1]https://blueprints.launchpad.net/cinder/+spec/support-volume-num-weighter [2]https://review.openstack.org/#/c/99683/ Change-Id: Id9275ed954f1b35ee8d7bee0f0b61cc3a7c48f63 --- cinder/db/api.py | 5 +- cinder/db/sqlalchemy/api.py | 27 +++-- cinder/scheduler/weights/volume_number.py | 60 ++++++++++++ .../scheduler/test_volume_number_weigher.py | 98 +++++++++++++++++++ etc/cinder/cinder.conf.sample | 9 ++ setup.cfg | 1 + 6 files changed, 188 insertions(+), 12 deletions(-) create mode 100644 cinder/scheduler/weights/volume_number.py create mode 100644 cinder/tests/scheduler/test_volume_number_weigher.py diff --git a/cinder/db/api.py b/cinder/db/api.py index de2664b7e..1d4ac6279 100644 --- a/cinder/db/api.py +++ b/cinder/db/api.py @@ -215,10 +215,11 @@ def volume_create(context, values): return IMPL.volume_create(context, values) -def volume_data_get_for_host(context, host): +def volume_data_get_for_host(context, host, count_only=False): """Get (volume_count, gigabytes) for project.""" return IMPL.volume_data_get_for_host(context, - host) + host, + count_only) def volume_data_get_for_project(context, project_id): diff --git a/cinder/db/sqlalchemy/api.py b/cinder/db/sqlalchemy/api.py index fd6e74bc5..38a5aaaf7 100644 --- a/cinder/db/sqlalchemy/api.py +++ b/cinder/db/sqlalchemy/api.py @@ -1004,16 +1004,23 @@ def volume_create(context, values): @require_admin_context -def volume_data_get_for_host(context, host): - result = model_query(context, - func.count(models.Volume.id), - func.sum(models.Volume.size), - read_deleted="no").\ - filter_by(host=host).\ - first() - - # NOTE(vish): convert None to 0 - return (result[0] or 0, result[1] or 0) +def volume_data_get_for_host(context, host, count_only=False): + if count_only: + result = model_query(context, + func.count(models.Volume.id), + read_deleted="no").\ + filter_by(host=host).\ + first() + return result[0] or 0 + else: + result = model_query(context, + func.count(models.Volume.id), + func.sum(models.Volume.size), + read_deleted="no").\ + filter_by(host=host).\ + first() + # NOTE(vish): convert None to 0 + return (result[0] or 0, result[1] or 0) @require_admin_context diff --git a/cinder/scheduler/weights/volume_number.py b/cinder/scheduler/weights/volume_number.py new file mode 100644 index 000000000..87203628a --- /dev/null +++ b/cinder/scheduler/weights/volume_number.py @@ -0,0 +1,60 @@ +# Copyright (c) 2014 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. +""" +Weighers that weigh hosts by volume number in backends: + +1. Volume Number Weigher. Weigh hosts by their volume number. + +The default is to spread volumes across all hosts evenly. If you prefer +stacking, you can set the 'volume_number_multiplier' option to a positive +number and the weighing has the opposite effect of the default. +""" + + +from oslo.config import cfg + +from cinder import db +from cinder.openstack.common import log as logging +from cinder.openstack.common.scheduler import weights + + +LOG = logging.getLogger(__name__) + + +volume_number_weight_opts = [ + cfg.FloatOpt('volume_number_multiplier', + default=-1.0, + help='Multiplier used for weighing volume number. ' + 'Negative numbers mean to spread vs stack.'), +] + +CONF = cfg.CONF +CONF.register_opts(volume_number_weight_opts) + + +class VolumeNumberWeigher(weights.BaseHostWeigher): + def _weight_multiplier(self): + """Override the weight multiplier.""" + return CONF.volume_number_multiplier + + def _weigh_object(self, host_state, weight_properties): + """Less volume number weights win. + We want spreading to be the default. + """ + context = weight_properties['context'] + volume_number = db.volume_data_get_for_host(context=context, + host=host_state.host, + count_only=True) + return volume_number diff --git a/cinder/tests/scheduler/test_volume_number_weigher.py b/cinder/tests/scheduler/test_volume_number_weigher.py new file mode 100644 index 000000000..ca596cf04 --- /dev/null +++ b/cinder/tests/scheduler/test_volume_number_weigher.py @@ -0,0 +1,98 @@ +# Copyright 2014 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 Volume Number Weigher. +""" + +import mock + +from oslo.config import cfg + +from cinder import context +from cinder.db.sqlalchemy import api +from cinder.openstack.common.scheduler.weights import HostWeightHandler +from cinder.scheduler.weights.volume_number import VolumeNumberWeigher +from cinder import test +from cinder.tests.scheduler import fakes + +CONF = cfg.CONF + + +def fake_volume_data_get_for_host(context, host, count_only=False): + if host == 'host1': + return 1 + elif host == 'host2': + return 2 + elif host == 'host3': + return 3 + elif host == 'host4': + return 4 + else: + return 1 + + +class VolumeNumberWeigherTestCase(test.TestCase): + def setUp(self): + super(VolumeNumberWeigherTestCase, self).setUp() + self.context = context.get_admin_context() + self.host_manager = fakes.FakeHostManager() + self.weight_handler = HostWeightHandler('cinder.scheduler.weights') + + def _get_weighed_host(self, hosts, weight_properties=None): + if weight_properties is None: + weight_properties = {'context': self.context} + return self.weight_handler.get_weighed_objects([VolumeNumberWeigher], + hosts, + weight_properties)[0] + + @mock.patch('cinder.db.sqlalchemy.api.service_get_all_by_topic') + def _get_all_hosts(self, _mock_service_get_all_by_topic, disabled=False): + ctxt = context.get_admin_context() + fakes.mock_host_manager_db_calls(_mock_service_get_all_by_topic, + disabled=disabled) + host_states = self.host_manager.get_all_host_states(ctxt) + _mock_service_get_all_by_topic.assert_called_once_with( + ctxt, CONF.volume_topic, disabled=disabled) + return host_states + + def test_volume_number_weight_multiplier1(self): + self.flags(volume_number_multiplier=-1.0) + hostinfo_list = self._get_all_hosts() + + # host1: 1 volume + # host2: 2 volumes + # host3: 3 volumes + # host4: 4 volumes + # 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.obj.host, 'host1') + + def test_volume_number_weight_multiplier2(self): + self.flags(volume_number_multiplier=1.0) + hostinfo_list = self._get_all_hosts() + + # host1: 1 volume + # host2: 2 volumes + # host3: 3 volumes + # host4: 4 volumes + # so, host4 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, 4.0) + self.assertEqual(weighed_host.obj.host, 'host4') diff --git a/etc/cinder/cinder.conf.sample b/etc/cinder/cinder.conf.sample index f5a8cc005..a14d45edc 100644 --- a/etc/cinder/cinder.conf.sample +++ b/etc/cinder/cinder.conf.sample @@ -891,6 +891,15 @@ #allocated_capacity_weight_multiplier=-1.0 +# +# Options defined in cinder.scheduler.weights.volume_number +# + +# Multiplier used for weighing volume number. Negative numbers +# mean to spread vs stack. (floating point value) +#volume_number_multiplier=-1.0 + + # # Options defined in cinder.transfer.api # diff --git a/setup.cfg b/setup.cfg index e967d0d37..3d47126b1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,6 +48,7 @@ cinder.scheduler.weights = AllocatedCapacityWeigher = cinder.scheduler.weights.capacity:AllocatedCapacityWeigher CapacityWeigher = cinder.scheduler.weights.capacity:CapacityWeigher ChanceWeigher = cinder.scheduler.weights.chance:ChanceWeigher + VolumeNumberWeigher = cinder.scheduler.weights.volume_number:VolumeNumberWeigher console_scripts = cinder-rootwrap = oslo.rootwrap.cmd:main # These are for backwards compat with Havana notification_driver configuration values -- 2.45.2