Merge "Make get_best_cpu_topology consider NUMA requested CPU topology"
This commit is contained in:
commit
ba01e01411
|
@ -458,6 +458,32 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
"maxthreads": 4,
|
||||
"expect": exception.ImageVCPULimitsRangeImpossible,
|
||||
},
|
||||
{
|
||||
"allow_threads": True,
|
||||
"specified_threads": 2,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 4,
|
||||
"maxcores": 2,
|
||||
"maxthreads": 4,
|
||||
"expect": [
|
||||
[4, 1, 2],
|
||||
[2, 2, 2],
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": False,
|
||||
"specified_threads": 2,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 8,
|
||||
"maxcores": 8,
|
||||
"maxthreads": 2,
|
||||
"expect": [
|
||||
[8, 1, 1],
|
||||
[4, 2, 1],
|
||||
[2, 4, 1],
|
||||
[1, 8, 1],
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
for topo_test in testdata:
|
||||
|
@ -469,7 +495,8 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
sockets=topo_test["maxsockets"],
|
||||
cores=topo_test["maxcores"],
|
||||
threads=topo_test["maxthreads"]),
|
||||
topo_test["allow_threads"]):
|
||||
topo_test["allow_threads"],
|
||||
topo_test.get("specified_threads")):
|
||||
actual.append([topology.sockets,
|
||||
topology.cores,
|
||||
topology.threads])
|
||||
|
@ -483,7 +510,8 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
sockets=topo_test["maxsockets"],
|
||||
cores=topo_test["maxcores"],
|
||||
threads=topo_test["maxthreads"]),
|
||||
topo_test["allow_threads"])
|
||||
topo_test["allow_threads"],
|
||||
topo_test.get("specified_threads"))
|
||||
|
||||
def test_sorting_topologies(self):
|
||||
testdata = [
|
||||
|
@ -561,7 +589,8 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
objects.VirtCPUTopology(sockets=topo_test["maxsockets"],
|
||||
cores=topo_test["maxcores"],
|
||||
threads=topo_test["maxthreads"]),
|
||||
topo_test["allow_threads"])
|
||||
topo_test["allow_threads"],
|
||||
None)
|
||||
|
||||
tops = hw._sort_possible_cpu_topologies(
|
||||
possible,
|
||||
|
@ -694,13 +723,73 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
},
|
||||
"expect": [16, 1, 1]
|
||||
},
|
||||
{ # NUMA needs threads, only cores requested by flavor
|
||||
"allow_threads": True,
|
||||
"flavor": objects.Flavor(vcpus=4, memory_mb=2048,
|
||||
extra_specs={
|
||||
"hw:cpu_cores": "2",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_max_cores": 2,
|
||||
}
|
||||
},
|
||||
"numa_topology": objects.InstanceNUMATopology(
|
||||
cells=[
|
||||
objects.InstanceNUMACell(
|
||||
id=0, cpuset=set([0, 1]), memory=1024,
|
||||
cpu_topology=objects.VirtCPUTopology(
|
||||
sockets=1, cores=1, threads=2)),
|
||||
objects.InstanceNUMACell(
|
||||
id=1, cpuset=set([2, 3]), memory=1024)]),
|
||||
"expect": [1, 2, 2]
|
||||
},
|
||||
{ # NUMA needs threads, but more than requested by flavor - the
|
||||
# least amount of threads wins
|
||||
"allow_threads": True,
|
||||
"flavor": objects.Flavor(vcpus=4, memory_mb=2048,
|
||||
extra_specs={
|
||||
"hw:cpu_threads": "2",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {}
|
||||
},
|
||||
"numa_topology": objects.InstanceNUMATopology(
|
||||
cells=[
|
||||
objects.InstanceNUMACell(
|
||||
id=0, cpuset=set([0, 1, 2, 3]), memory=2048,
|
||||
cpu_topology=objects.VirtCPUTopology(
|
||||
sockets=1, cores=1, threads=4))]),
|
||||
"expect": [2, 1, 2]
|
||||
},
|
||||
{ # NUMA needs different number of threads per cell - the least
|
||||
# amount of threads wins
|
||||
"allow_threads": True,
|
||||
"flavor": objects.Flavor(vcpus=8, memory_mb=2048,
|
||||
extra_specs={}),
|
||||
"image": {
|
||||
"properties": {}
|
||||
},
|
||||
"numa_topology": objects.InstanceNUMATopology(
|
||||
cells=[
|
||||
objects.InstanceNUMACell(
|
||||
id=0, cpuset=set([0, 1, 2, 3]), memory=1024,
|
||||
cpu_topology=objects.VirtCPUTopology(
|
||||
sockets=1, cores=2, threads=2)),
|
||||
objects.InstanceNUMACell(
|
||||
id=1, cpuset=set([4, 5, 6, 7]), memory=1024,
|
||||
cpu_topology=objects.VirtCPUTopology(
|
||||
sockets=1, cores=1, threads=4))]),
|
||||
"expect": [4, 1, 2]
|
||||
},
|
||||
]
|
||||
|
||||
for topo_test in testdata:
|
||||
topology = hw._get_desirable_cpu_topologies(
|
||||
topo_test["flavor"],
|
||||
topo_test["image"],
|
||||
topo_test["allow_threads"])[0]
|
||||
topo_test["allow_threads"],
|
||||
topo_test.get("numa_topology"))[0]
|
||||
|
||||
self.assertEqual(topo_test["expect"][0], topology.sockets)
|
||||
self.assertEqual(topo_test["expect"][1], topology.cores)
|
||||
|
|
|
@ -401,11 +401,14 @@ def _get_cpu_topology_constraints(flavor, image_meta):
|
|||
threads=maxthreads))
|
||||
|
||||
|
||||
def _get_possible_cpu_topologies(vcpus, maxtopology, allow_threads):
|
||||
def _get_possible_cpu_topologies(vcpus, maxtopology,
|
||||
allow_threads, specified_threads):
|
||||
"""Get a list of possible topologies for a vCPU count
|
||||
:param vcpus: total number of CPUs for guest instance
|
||||
:param maxtopology: nova.objects.VirtCPUTopology for upper limits
|
||||
:param allow_threads: if the hypervisor supports CPU threads
|
||||
:param specified_threads: if there is a specific request for threads we
|
||||
should attempt to honour
|
||||
|
||||
Given a total desired vCPU count and constraints on the
|
||||
maximum number of sockets, cores and threads, return a
|
||||
|
@ -426,6 +429,9 @@ def _get_possible_cpu_topologies(vcpus, maxtopology, allow_threads):
|
|||
maxthreads = min(vcpus, maxtopology.threads)
|
||||
|
||||
if not allow_threads:
|
||||
# NOTE (ndipanov): If we don't support threads - it doesn't matter that
|
||||
# they are specified by the NUMA logic.
|
||||
specified_threads = None
|
||||
maxthreads = 1
|
||||
|
||||
LOG.debug("Build topologies for %(vcpus)d vcpu(s) "
|
||||
|
@ -433,6 +439,12 @@ def _get_possible_cpu_topologies(vcpus, maxtopology, allow_threads):
|
|||
{"vcpus": vcpus, "maxsockets": maxsockets,
|
||||
"maxcores": maxcores, "maxthreads": maxthreads})
|
||||
|
||||
def _get_topology_for_vcpus(vcpus, sockets, cores, threads):
|
||||
if threads * cores * sockets == vcpus:
|
||||
return objects.VirtCPUTopology(sockets=sockets,
|
||||
cores=cores,
|
||||
threads=threads)
|
||||
|
||||
# Figure out all possible topologies that match
|
||||
# the required vcpus count and satisfy the declared
|
||||
# limits. If the total vCPU count were very high
|
||||
|
@ -442,12 +454,15 @@ def _get_possible_cpu_topologies(vcpus, maxtopology, allow_threads):
|
|||
possible = []
|
||||
for s in range(1, maxsockets + 1):
|
||||
for c in range(1, maxcores + 1):
|
||||
for t in range(1, maxthreads + 1):
|
||||
if t * c * s == vcpus:
|
||||
o = objects.VirtCPUTopology(sockets=s, cores=c,
|
||||
threads=t)
|
||||
|
||||
if specified_threads:
|
||||
o = _get_topology_for_vcpus(vcpus, s, c, specified_threads)
|
||||
if o is not None:
|
||||
possible.append(o)
|
||||
else:
|
||||
for t in range(1, maxthreads + 1):
|
||||
o = _get_topology_for_vcpus(vcpus, s, c, t)
|
||||
if o is not None:
|
||||
possible.append(o)
|
||||
|
||||
# We want to
|
||||
# - Minimize threads (ie larger sockets * cores is best)
|
||||
|
@ -501,12 +516,28 @@ def _sort_possible_cpu_topologies(possible, wanttopology):
|
|||
return desired
|
||||
|
||||
|
||||
def _get_desirable_cpu_topologies(flavor, image_meta, allow_threads=True):
|
||||
def _threads_requested_by_user(flavor, image_meta):
|
||||
keys = ("cpu_threads", "cpu_maxthreads")
|
||||
if any(flavor.extra_specs.get("hw:%s" % key) for key in keys):
|
||||
return True
|
||||
|
||||
if any(image_meta.get("properties", {}).get("hw_%s" % key)
|
||||
for key in keys):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _get_desirable_cpu_topologies(flavor, image_meta, allow_threads=True,
|
||||
numa_topology=None):
|
||||
"""Get desired CPU topologies according to settings
|
||||
|
||||
:param flavor: Flavor object to query extra specs from
|
||||
:param image_meta: ImageMeta object to query properties from
|
||||
:param allow_threads: if the hypervisor supports CPU threads
|
||||
:param numa_topology: InstanceNUMATopology object that may contain
|
||||
additional topology constraints (such as threading
|
||||
information) that we should consider
|
||||
|
||||
Look at the properties set in the flavor extra specs and
|
||||
the image metadata and build up a list of all possible
|
||||
|
@ -522,20 +553,39 @@ def _get_desirable_cpu_topologies(flavor, image_meta, allow_threads=True):
|
|||
|
||||
preferred, maximum = _get_cpu_topology_constraints(flavor, image_meta)
|
||||
|
||||
specified_threads = None
|
||||
if numa_topology:
|
||||
min_requested_threads = None
|
||||
cell_topologies = [cell.cpu_topology for cell in numa_topology.cells
|
||||
if cell.cpu_topology]
|
||||
if cell_topologies:
|
||||
min_requested_threads = min(
|
||||
topo.threads for topo in cell_topologies)
|
||||
if min_requested_threads:
|
||||
if _threads_requested_by_user(flavor, image_meta):
|
||||
min_requested_threads = min(preferred.threads,
|
||||
min_requested_threads)
|
||||
specified_threads = max(1, min_requested_threads)
|
||||
|
||||
possible = _get_possible_cpu_topologies(flavor.vcpus,
|
||||
maximum,
|
||||
allow_threads)
|
||||
allow_threads,
|
||||
specified_threads)
|
||||
desired = _sort_possible_cpu_topologies(possible, preferred)
|
||||
|
||||
return desired
|
||||
|
||||
|
||||
def get_best_cpu_topology(flavor, image_meta, allow_threads=True):
|
||||
def get_best_cpu_topology(flavor, image_meta, allow_threads=True,
|
||||
numa_topology=None):
|
||||
"""Get best CPU topology according to settings
|
||||
|
||||
:param flavor: Flavor object to query extra specs from
|
||||
:param image_meta: ImageMeta object to query properties from
|
||||
:param allow_threads: if the hypervisor supports CPU threads
|
||||
:param numa_topology: InstanceNUMATopology object that may contain
|
||||
additional topology constraints (such as threading
|
||||
information) that we should consider
|
||||
|
||||
Look at the properties set in the flavor extra specs and
|
||||
the image metadata and build up a list of all possible
|
||||
|
@ -545,7 +595,8 @@ def get_best_cpu_topology(flavor, image_meta, allow_threads=True):
|
|||
:returns: a nova.objects.VirtCPUTopology instance for best topology
|
||||
"""
|
||||
|
||||
return _get_desirable_cpu_topologies(flavor, image_meta, allow_threads)[0]
|
||||
return _get_desirable_cpu_topologies(flavor, image_meta,
|
||||
allow_threads, numa_topology)[0]
|
||||
|
||||
|
||||
class VirtNUMATopologyCell(object):
|
||||
|
|
|
@ -3029,18 +3029,20 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
|
||||
return cpu
|
||||
|
||||
def _get_guest_cpu_config(self, flavor, image, guest_cpu_numa):
|
||||
def _get_guest_cpu_config(self, flavor, image,
|
||||
guest_cpu_numa_config, instance_numa_topology):
|
||||
cpu = self._get_guest_cpu_model_config()
|
||||
|
||||
if cpu is None:
|
||||
return None
|
||||
|
||||
topology = hardware.get_best_cpu_topology(flavor, image)
|
||||
topology = hardware.get_best_cpu_topology(
|
||||
flavor, image, numa_topology=instance_numa_topology)
|
||||
|
||||
cpu.sockets = topology.sockets
|
||||
cpu.cores = topology.cores
|
||||
cpu.threads = topology.threads
|
||||
cpu.numa = guest_cpu_numa
|
||||
cpu.numa = guest_cpu_numa_config
|
||||
|
||||
return cpu
|
||||
|
||||
|
@ -3818,7 +3820,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
self._update_guest_cputune(guest, flavor, virt_type)
|
||||
|
||||
guest.cpu = self._get_guest_cpu_config(
|
||||
flavor, image_meta, guest_numa_config.numaconfig)
|
||||
flavor, image_meta, guest_numa_config.numaconfig,
|
||||
instance.numa_topology)
|
||||
|
||||
if 'root' in disk_mapping:
|
||||
root_device_name = block_device.prepend_dev(
|
||||
|
|
Loading…
Reference in New Issue