Fix NUMA fit testing in claims and filter class

This patch makes sure that the overall topology is considered when
checking wather a NUMA instance can fit on a NUMA host, as previously we
would only check the corresponding cells without considering the overall
topology.

It adds a method for checking it on the VirtNUMAHostTopology class and
make sure it's called when checking for placement in the scheduler and
confirming claims on compute hosts.

We also split out the method for getting the topology from a host
as it can be reused in the scheduler now.

Change-Id: I038c81a5241bf1fd2ce37f7eff69f89ecedf59ef
Closes-bug: #1369984
This commit is contained in:
Nikola Dipanov 2014-09-16 16:08:00 +02:00
parent 12b8b56807
commit 903ebc2b71
4 changed files with 119 additions and 17 deletions

View File

@ -28,12 +28,18 @@ class NUMATopologyFilter(filters.BaseHostFilter):
cpu_ratio = CONF.cpu_allocation_ratio
instance = filter_properties.get('instance_properties', {})
instance_topology = hardware.instance_topology_from_instance(instance)
host_topology, _fmt = hardware.host_topology_and_format_from_host(
host_state)
if instance_topology:
if host_state.numa_topology:
if host_topology:
if not hardware.VirtNUMAHostTopology.can_fit_instances(
host_topology, [instance_topology]):
return False
limit_cells = []
usage_after_instance = (
hardware.get_host_numa_usage_from_instance(
host_state, instance, never_serialize_result=True))
hardware.VirtNUMAHostTopology.usage_from_instances(
host_topology, [instance_topology]))
for cell in usage_after_instance.cells:
max_cell_memory = int(cell.memory * ram_ratio)
max_cell_cpu = len(cell.cpuset) * cpu_ratio

View File

@ -2087,6 +2087,23 @@ class HostFiltersTestCase(test.NoDBTestCase):
filt_cls = self.class_map['NUMATopologyFilter']()
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_numa_topology_filter_fail_fit(self):
instance_topology = hardware.VirtNUMAInstanceTopology(
cells=[hardware.VirtNUMATopologyCell(0, set([1]), 512),
hardware.VirtNUMATopologyCell(1, set([2]), 512),
hardware.VirtNUMATopologyCell(2, set([3]), 512)])
instance = fake_instance.fake_instance_obj(self.context)
instance.numa_topology = (
objects.InstanceNUMATopology.obj_from_topology(
instance_topology))
filter_properties = {
'instance_properties': jsonutils.to_primitive(
obj_base.obj_to_primitive(instance))}
host = fakes.FakeHostState('host1', 'node1',
{'numa_topology': fakes.NUMA_TOPOLOGY})
filt_cls = self.class_map['NUMATopologyFilter']()
self.assertFalse(filt_cls.host_passes(host, filter_properties))
def test_numa_topology_filter_fail_memory(self):
self.flags(ram_allocation_ratio=1)

View File

@ -831,6 +831,30 @@ class NUMATopologyTest(test.NoDBTestCase):
self.assertEqual(testitem["expect"].cells[i].memory,
topology.cells[i].memory)
def test_can_fit_isntances(self):
hosttopo = hw.VirtNUMAHostTopology([
hw.VirtNUMATopologyCellUsage(0, set([0, 1, 2, 3]), 1024),
hw.VirtNUMATopologyCellUsage(1, set([4, 6]), 512)
])
instance1 = hw.VirtNUMAInstanceTopology([
hw.VirtNUMATopologyCell(0, set([0, 1, 2]), 256),
hw.VirtNUMATopologyCell(1, set([4]), 256),
])
instance2 = hw.VirtNUMAInstanceTopology([
hw.VirtNUMATopologyCell(0, set([0, 1]), 256),
hw.VirtNUMATopologyCell(1, set([4, 6]), 256),
hw.VirtNUMATopologyCell(2, set([7, 8]), 256),
])
self.assertTrue(hw.VirtNUMAHostTopology.can_fit_instances(
hosttopo, []))
self.assertTrue(hw.VirtNUMAHostTopology.can_fit_instances(
hosttopo, [instance1]))
self.assertFalse(hw.VirtNUMAHostTopology.can_fit_instances(
hosttopo, [instance2]))
self.assertFalse(hw.VirtNUMAHostTopology.can_fit_instances(
hosttopo, [instance1, instance2]))
def test_host_usage_contiguous(self):
hosttopo = hw.VirtNUMAHostTopology([
hw.VirtNUMATopologyCellUsage(0, set([0, 1, 2, 3]), 1024),
@ -1183,6 +1207,11 @@ class NUMATopologyClaimsTest(test.NoDBTestCase):
cells=[
hw.VirtNUMATopologyCell(1, set([1]), 256),
hw.VirtNUMATopologyCell(2, set([5]), 1024)])
self.no_fit_instance = hw.VirtNUMAInstanceTopology(
cells=[
hw.VirtNUMATopologyCell(1, set([1]), 256),
hw.VirtNUMATopologyCell(2, set([2]), 256),
hw.VirtNUMATopologyCell(3, set([3]), 256)])
def test_claim_not_enough_info(self):
@ -1219,6 +1248,17 @@ class NUMATopologyClaimsTest(test.NoDBTestCase):
self.limits),
six.text_type)
# Instance fails if it won't fit the topology
self.assertIsInstance(
hw.VirtNUMAHostTopology.claim_test(
self.host, [self.no_fit_instance], self.limits),
six.text_type)
# Instance fails if it won't fit the topology even with no limits
self.assertIsInstance(
hw.VirtNUMAHostTopology.claim_test(
self.host, [self.no_fit_instance]), six.text_type)
class HelperMethodsTestCase(test.NoDBTestCase):
def setUp(self):

View File

@ -844,6 +844,22 @@ class VirtNUMAHostTopology(VirtNUMATopology):
cell_class = VirtNUMATopologyCellUsage
@staticmethod
def can_fit_instances(host, instances):
"""Test if the instance topology can fit into the host
Returns True if all the cells of the all the instance topologies in
'instances' exist in the given 'host' topology. False otherwise.
"""
if not host:
return True
host_cells = set(cell.id for cell in host.cells)
instances_cells = [set(cell.id for cell in instance.cells)
for instance in instances]
return all(instance_cells <= host_cells
for instance_cells in instances_cells)
@classmethod
def usage_from_instances(cls, host, instances, free=False):
"""Get host topology usage
@ -893,7 +909,14 @@ class VirtNUMAHostTopology(VirtNUMATopology):
:returns: None if the claim succeeds or text explaining the error.
"""
if not all((host, limits, instances)):
if not (host and instances):
return
if not cls.can_fit_instances(host, instances):
return (_("Requested instance NUMA topology cannot fit "
"the given host NUMA topology."))
if not limits:
return
claimed_host = cls.usage_from_instances(host, instances)
@ -901,8 +924,8 @@ class VirtNUMAHostTopology(VirtNUMATopology):
for claimed_cell, limit_cell in zip(claimed_host.cells, limits.cells):
if (claimed_cell.memory_usage > limit_cell.memory_limit or
claimed_cell.cpu_usage > limit_cell.cpu_limit):
return (_("Requested instance NUMA topology cannot fit "
"the given host NUMA topology."))
return (_("Requested instance NUMA topology is too large for "
"the given host NUMA topology limits."))
# TODO(ndipanov): Remove when all code paths are using objects
@ -956,6 +979,31 @@ def instance_topology_from_instance(instance):
return instance_numa_topology
# TODO(ndipanov): Remove when all code paths are using objects
def host_topology_and_format_from_host(host):
"""Convenience method for getting the numa_topology out of hosts
Since we may get a host as either a dict, a db object, or an actual
ComputeNode object, or an instance of HostState class, this makes sure we
get beck either None, or an instance of VirtNUMAHostTopology class.
:returns: A two-tuple, first element is the topology itself or None, second
is a boolean set to True if topology was in json format.
"""
was_json = False
try:
host_numa_topology = host.get('numa_topology')
except AttributeError:
host_numa_topology = host.numa_topology
if host_numa_topology is not None and isinstance(
host_numa_topology, six.string_types):
was_json = True
host_numa_topology = VirtNUMAHostTopology.from_json(host_numa_topology)
return host_numa_topology, was_json
# TODO(ndipanov): Remove when all code paths are using objects
def get_host_numa_usage_from_instance(host, instance, free=False,
never_serialize_result=False):
@ -978,21 +1026,12 @@ def get_host_numa_usage_from_instance(host, instance, free=False,
:returns: numa_usage in the format it was on the host or
VirtNUMAHostTopology instance if never_serialize_result was True
"""
jsonify_result = False
instance_numa_topology = instance_topology_from_instance(instance)
if instance_numa_topology:
instance_numa_topology = [instance_numa_topology]
try:
host_numa_topology = host.get('numa_topology')
except AttributeError:
host_numa_topology = host.numa_topology
if host_numa_topology is not None and isinstance(
host_numa_topology, six.string_types):
jsonify_result = True
host_numa_topology = VirtNUMAHostTopology.from_json(host_numa_topology)
host_numa_topology, jsonify_result = host_topology_and_format_from_host(
host)
updated_numa_topology = (
VirtNUMAHostTopology.usage_from_instances(