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:
@@ -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>`
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user