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:
parent
12b8b56807
commit
903ebc2b71
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue