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):
@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
--- /dev/null
+# 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
--- /dev/null
+# 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')
#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
#
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