Add AggregateCoreFilter

Implements blueprint per-aggregate-resource-ratio

* AggregateCoreFilter to support per-aggregate cpu_allocation_ratio
* Falls back to global setting if per-aggregate value not found

DocImpact

Change-Id: I9230f46e2490226f3c50d616aa173d4722095087
This commit is contained in:
Qiu Yu
2013-06-21 17:55:31 +08:00
parent f47c9c036e
commit 855be80b8f
3 changed files with 105 additions and 4 deletions

View File

@@ -73,6 +73,11 @@ There are some standard filter classes to use (:mod:`nova.scheduler.filters`):
* |ComputeFilter| - passes all hosts that are operational and enabled. * |ComputeFilter| - passes all hosts that are operational and enabled.
* |CoreFilter| - filters based on CPU core utilization. It passes hosts with * |CoreFilter| - filters based on CPU core utilization. It passes hosts with
sufficient number of CPU cores. sufficient number of CPU cores.
* |AggregateCoreFilter| - filters hosts by CPU core number with per-aggregate
cpu_allocation_ratio setting. If no per-aggregate value is found, it will
fall back to the global default cpu_allocation_ratio. If more than one value
is found for a host (meaning the host is in two differenet aggregate with
different ratio settings), the minimum value will be used.
* |IsolatedHostsFilter| - filter based on "image_isolated" and "host_isolated" * |IsolatedHostsFilter| - filter based on "image_isolated" and "host_isolated"
flags. flags.
* |JsonFilter| - allows simple JSON-based grammar for selecting hosts. * |JsonFilter| - allows simple JSON-based grammar for selecting hosts.
@@ -282,6 +287,7 @@ in :mod:`nova.tests.scheduler`.
.. |ComputeCapabilitiesFilter| replace:: :class:`ComputeCapabilitiesFilter <nova.scheduler.filters.compute_capabilities_filter.ComputeCapabilitiesFilter>` .. |ComputeCapabilitiesFilter| replace:: :class:`ComputeCapabilitiesFilter <nova.scheduler.filters.compute_capabilities_filter.ComputeCapabilitiesFilter>`
.. |ComputeFilter| replace:: :class:`ComputeFilter <nova.scheduler.filters.compute_filter.ComputeFilter>` .. |ComputeFilter| replace:: :class:`ComputeFilter <nova.scheduler.filters.compute_filter.ComputeFilter>`
.. |CoreFilter| replace:: :class:`CoreFilter <nova.scheduler.filters.core_filter.CoreFilter>` .. |CoreFilter| replace:: :class:`CoreFilter <nova.scheduler.filters.core_filter.CoreFilter>`
.. |AggregateCoreFilter| replace:: :class:`AggregateCoreFilter <nova.scheduler.filters.core_filter.AggregateCoreFilter>`
.. |IsolatedHostsFilter| replace:: :class:`IsolatedHostsFilter <nova.scheduler.filters.isolated_hosts_filter>` .. |IsolatedHostsFilter| replace:: :class:`IsolatedHostsFilter <nova.scheduler.filters.isolated_hosts_filter>`
.. |JsonFilter| replace:: :class:`JsonFilter <nova.scheduler.filters.json_filter.JsonFilter>` .. |JsonFilter| replace:: :class:`JsonFilter <nova.scheduler.filters.json_filter.JsonFilter>`
.. |RamFilter| replace:: :class:`RamFilter <nova.scheduler.filters.ram_filter.RamFilter>` .. |RamFilter| replace:: :class:`RamFilter <nova.scheduler.filters.ram_filter.RamFilter>`

View File

@@ -17,6 +17,7 @@
from oslo.config import cfg from oslo.config import cfg
from nova import db
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova.scheduler import filters from nova.scheduler import filters
@@ -24,14 +25,19 @@ LOG = logging.getLogger(__name__)
cpu_allocation_ratio_opt = cfg.FloatOpt('cpu_allocation_ratio', cpu_allocation_ratio_opt = cfg.FloatOpt('cpu_allocation_ratio',
default=16.0, default=16.0,
help='Virtual CPU to Physical CPU allocation ratio') help='Virtual CPU to physical CPU allocation ratio which affects '
'all CPU filters. This configuration specifies a global ratio '
'for CoreFilter. For AggregateCoreFilter, it will fall back to '
'this configuration value if no per-aggregate setting found.')
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opt(cpu_allocation_ratio_opt) CONF.register_opt(cpu_allocation_ratio_opt)
class CoreFilter(filters.BaseHostFilter): class BaseCoreFilter(filters.BaseHostFilter):
"""CoreFilter filters based on CPU core utilization."""
def _get_cpu_allocation_ratio(self, host_state, filter_properties):
raise NotImplementedError
def host_passes(self, host_state, filter_properties): def host_passes(self, host_state, filter_properties):
"""Return True if host has sufficient CPU cores.""" """Return True if host has sufficient CPU cores."""
@@ -45,7 +51,9 @@ class CoreFilter(filters.BaseHostFilter):
return True return True
instance_vcpus = instance_type['vcpus'] instance_vcpus = instance_type['vcpus']
vcpus_total = host_state.vcpus_total * CONF.cpu_allocation_ratio cpu_allocation_ratio = self._get_cpu_allocation_ratio(host_state,
filter_properties)
vcpus_total = host_state.vcpus_total * cpu_allocation_ratio
# Only provide a VCPU limit to compute if the virt driver is reporting # Only provide a VCPU limit to compute if the virt driver is reporting
# an accurate count of installed VCPUs. (XenServer driver does not) # an accurate count of installed VCPUs. (XenServer driver does not)
@@ -53,3 +61,44 @@ class CoreFilter(filters.BaseHostFilter):
host_state.limits['vcpu'] = vcpus_total host_state.limits['vcpu'] = vcpus_total
return (vcpus_total - host_state.vcpus_used) >= instance_vcpus return (vcpus_total - host_state.vcpus_used) >= instance_vcpus
class CoreFilter(BaseCoreFilter):
"""CoreFilter filters based on CPU core utilization."""
def _get_cpu_allocation_ratio(self, host_state, filter_properties):
return CONF.cpu_allocation_ratio
class AggregateCoreFilter(BaseCoreFilter):
"""AggregateRamFilter with per-aggregate CPU subscription flag.
Fall back to global cpu_allocation_ratio if no per-aggregate setting found.
"""
def _get_cpu_allocation_ratio(self, host_state, filter_properties):
context = filter_properties['context'].elevated()
# TODO(uni): DB query in filter is a performance hit, especially for
# system with lots of hosts. Will need a general solution here to fix
# all filters with aggregate DB call things.
metadata = db.aggregate_metadata_get_by_host(
context, host_state.host, key='cpu_allocation_ratio')
aggregate_vals = metadata.get('cpu_allocation_ratio', set())
num_values = len(aggregate_vals)
if num_values == 0:
return CONF.cpu_allocation_ratio
if num_values > 1:
LOG.warning(_("%(num_values)d ratio values found, "
"of which the minimum value will be used."),
{'num_values': num_values})
try:
ratio = float(min(aggregate_vals))
except ValueError as e:
LOG.warning(_("Could not decode cpu_allocation_ratio: "
"'%(e)s'") % locals())
ratio = CONF.cpu_allocation_ratio
return ratio

View File

@@ -1317,6 +1317,52 @@ class HostFiltersTestCase(test.NoDBTestCase):
{'vcpus_total': 4, 'vcpus_used': 8}) {'vcpus_total': 4, 'vcpus_used': 8})
self.assertFalse(filt_cls.host_passes(host, filter_properties)) self.assertFalse(filt_cls.host_passes(host, filter_properties))
def test_aggregate_core_filter_value_error(self):
filt_cls = self.class_map['AggregateCoreFilter']()
filter_properties = {'context': self.context,
'instance_type': {'vcpus': 1}}
self.flags(cpu_allocation_ratio=2)
host = fakes.FakeHostState('host1', 'node1',
{'vcpus_total': 4, 'vcpus_used': 7})
self._create_aggregate_with_host(name='fake_aggregate',
hosts=['host1'],
metadata={'cpu_allocation_ratio': 'XXX'})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
self.assertEqual(4 * 2, host.limits['vcpu'])
def test_aggregate_core_filter_default_value(self):
filt_cls = self.class_map['AggregateCoreFilter']()
filter_properties = {'context': self.context,
'instance_type': {'vcpus': 1}}
self.flags(cpu_allocation_ratio=2)
host = fakes.FakeHostState('host1', 'node1',
{'vcpus_total': 4, 'vcpus_used': 8})
# False: fallback to default flag w/o aggregates
self.assertFalse(filt_cls.host_passes(host, filter_properties))
self._create_aggregate_with_host(name='fake_aggregate',
hosts=['host1'],
metadata={'cpu_allocation_ratio': '3'})
# True: use ratio from aggregates
self.assertTrue(filt_cls.host_passes(host, filter_properties))
self.assertEqual(4 * 3, host.limits['vcpu'])
def test_aggregate_core_filter_conflict_values(self):
filt_cls = self.class_map['AggregateCoreFilter']()
filter_properties = {'context': self.context,
'instance_type': {'vcpus': 1}}
self.flags(cpu_allocation_ratio=1)
host = fakes.FakeHostState('host1', 'node1',
{'vcpus_total': 4, 'vcpus_used': 8})
self._create_aggregate_with_host(name='fake_aggregate1',
hosts=['host1'],
metadata={'cpu_allocation_ratio': '2'})
self._create_aggregate_with_host(name='fake_aggregate2',
hosts=['host1'],
metadata={'cpu_allocation_ratio': '3'})
# use the minimum ratio from aggregates
self.assertFalse(filt_cls.host_passes(host, filter_properties))
self.assertEqual(4 * 2, host.limits['vcpu'])
@staticmethod @staticmethod
def _make_zone_request(zone, is_admin=False): def _make_zone_request(zone, is_admin=False):
ctxt = context.RequestContext('fake', 'fake', is_admin=is_admin) ctxt = context.RequestContext('fake', 'fake', is_admin=is_admin)