Merge "Make get_best_cpu_topology consider NUMA requested CPU topology"

This commit is contained in:
Jenkins 2015-01-20 16:08:17 +00:00 committed by Gerrit Code Review
commit ba01e01411
3 changed files with 161 additions and 18 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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(