Convert hardware.VirtCPUTopology to nova object

All objects that have data that may eventually be serialized across the
wire in RPC comms, stored in a database, or potentially change their
schema, should be nova.objects. This patch changes the staticmethods on
the nova.virt.hardware.VirtCPUTopology class to a collection of private
(and one public) regular functions in nova.virt.hardware that consume and
produce nova.objects.virt_cpu_topology.VirtCPUTopology objects.

Change-Id: I2244aa7125b11568bc04d35e5de3eb2ee8c15167
This commit is contained in:
Jay Pipes 2014-11-01 22:15:19 -04:00
parent aebcf022ee
commit 96ab8ab058
7 changed files with 374 additions and 300 deletions

View File

@ -52,4 +52,5 @@ def register_all():
__import__('nova.objects.security_group') __import__('nova.objects.security_group')
__import__('nova.objects.security_group_rule') __import__('nova.objects.security_group_rule')
__import__('nova.objects.service') __import__('nova.objects.service')
__import__('nova.objects.virt_cpu_topology')
__import__('nova.objects.virtual_interface') __import__('nova.objects.virtual_interface')

View File

@ -0,0 +1,40 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova.objects import base
from nova.objects import fields
class VirtCPUTopology(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'sockets': fields.IntegerField(nullable=True, default=1),
'cores': fields.IntegerField(nullable=True, default=1),
'threads': fields.IntegerField(nullable=True, default=1),
}
# NOTE(jaypipes): for backward compatibility, the virt CPU topology
# data is stored in the database as a nested dict.
@classmethod
def from_dict(cls, data):
return cls(sockets=data.get('sockets'),
cores=data.get('cores'),
threads=data.get('threads'))
def to_dict(self):
return {
'sockets': self.sockets,
'cores': self.cores,
'threads': self.threads
}

View File

@ -1056,6 +1056,7 @@ object_data = {
'TestSubclassedObject': '1.6-c63feb2f2533b7d075490c04a2cc10dd', 'TestSubclassedObject': '1.6-c63feb2f2533b7d075490c04a2cc10dd',
'VirtualInterface': '1.0-10fdac4c704102b6d57d6936d6d790d2', 'VirtualInterface': '1.0-10fdac4c704102b6d57d6936d6d790d2',
'VirtualInterfaceList': '1.0-accbf02628a8063c1d885077a2bf49b6', 'VirtualInterfaceList': '1.0-accbf02628a8063c1d885077a2bf49b6',
'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563',
} }

View File

@ -0,0 +1,46 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova import objects
from nova.tests.unit.objects import test_objects
_top_dict = {
'sockets': 2,
'cores': 4,
'threads': 8
}
class _TestVirtCPUTopologyObject(object):
def test_object_from_dict(self):
top_obj = objects.VirtCPUTopology.from_dict(_top_dict)
self.compare_obj(top_obj, _top_dict)
def test_object_to_dict(self):
top_obj = objects.VirtCPUTopology()
top_obj.sockets = 2
top_obj.cores = 4
top_obj.threads = 8
spec = top_obj.to_dict()
self.assertEqual(_top_dict, spec)
class TestVirtCPUTopologyObject(test_objects._LocalTest,
_TestVirtCPUTopologyObject):
pass
class TestRemoteVirtCPUTopologyObject(test_objects._RemoteTest,
_TestVirtCPUTopologyObject):
pass

View File

@ -375,7 +375,7 @@ class VCPUTopologyTest(test.NoDBTestCase):
for topo_test in testdata: for topo_test in testdata:
if type(topo_test["expect"]) == tuple: if type(topo_test["expect"]) == tuple:
(preferred, (preferred,
maximum) = hw.VirtCPUTopology.get_topology_constraints( maximum) = hw._get_cpu_topology_constraints(
topo_test["flavor"], topo_test["flavor"],
topo_test["image"]) topo_test["image"])
@ -387,11 +387,11 @@ class VCPUTopologyTest(test.NoDBTestCase):
self.assertEqual(topo_test["expect"][5], maximum.threads) self.assertEqual(topo_test["expect"][5], maximum.threads)
else: else:
self.assertRaises(topo_test["expect"], self.assertRaises(topo_test["expect"],
hw.VirtCPUTopology.get_topology_constraints, hw._get_cpu_topology_constraints,
topo_test["flavor"], topo_test["flavor"],
topo_test["image"]) topo_test["image"])
def test_possible_configs(self): def test_possible_topologies(self):
testdata = [ testdata = [
{ {
"allow_threads": True, "allow_threads": True,
@ -481,11 +481,12 @@ class VCPUTopologyTest(test.NoDBTestCase):
for topo_test in testdata: for topo_test in testdata:
if type(topo_test["expect"]) == list: if type(topo_test["expect"]) == list:
actual = [] actual = []
for topology in hw.VirtCPUTopology.get_possible_topologies( for topology in hw._get_possible_cpu_topologies(
topo_test["vcpus"], topo_test["vcpus"],
hw.VirtCPUTopology(topo_test["maxsockets"], objects.VirtCPUTopology(
topo_test["maxcores"], sockets=topo_test["maxsockets"],
topo_test["maxthreads"]), cores=topo_test["maxcores"],
threads=topo_test["maxthreads"]),
topo_test["allow_threads"]): topo_test["allow_threads"]):
actual.append([topology.sockets, actual.append([topology.sockets,
topology.cores, topology.cores,
@ -494,14 +495,15 @@ class VCPUTopologyTest(test.NoDBTestCase):
self.assertEqual(topo_test["expect"], actual) self.assertEqual(topo_test["expect"], actual)
else: else:
self.assertRaises(topo_test["expect"], self.assertRaises(topo_test["expect"],
hw.VirtCPUTopology.get_possible_topologies, hw._get_possible_cpu_topologies,
topo_test["vcpus"], topo_test["vcpus"],
hw.VirtCPUTopology(topo_test["maxsockets"], objects.VirtCPUTopology(
topo_test["maxcores"], sockets=topo_test["maxsockets"],
topo_test["maxthreads"]), cores=topo_test["maxcores"],
threads=topo_test["maxthreads"]),
topo_test["allow_threads"]) topo_test["allow_threads"])
def test_sorting_configs(self): def test_sorting_topologies(self):
testdata = [ testdata = [
{ {
"allow_threads": True, "allow_threads": True,
@ -572,18 +574,18 @@ class VCPUTopologyTest(test.NoDBTestCase):
for topo_test in testdata: for topo_test in testdata:
actual = [] actual = []
possible = hw.VirtCPUTopology.get_possible_topologies( possible = hw._get_possible_cpu_topologies(
topo_test["vcpus"], topo_test["vcpus"],
hw.VirtCPUTopology(topo_test["maxsockets"], objects.VirtCPUTopology(sockets=topo_test["maxsockets"],
topo_test["maxcores"], cores=topo_test["maxcores"],
topo_test["maxthreads"]), threads=topo_test["maxthreads"]),
topo_test["allow_threads"]) topo_test["allow_threads"])
tops = hw.VirtCPUTopology.sort_possible_topologies( tops = hw._sort_possible_cpu_topologies(
possible, possible,
hw.VirtCPUTopology(topo_test["sockets"], objects.VirtCPUTopology(sockets=topo_test["sockets"],
topo_test["cores"], cores=topo_test["cores"],
topo_test["threads"])) threads=topo_test["threads"]))
for topology in tops: for topology in tops:
actual.append([topology.sockets, actual.append([topology.sockets,
topology.cores, topology.cores,
@ -705,7 +707,7 @@ class VCPUTopologyTest(test.NoDBTestCase):
] ]
for topo_test in testdata: for topo_test in testdata:
topology = hw.VirtCPUTopology.get_desirable_configs( topology = hw._get_desirable_cpu_topologies(
topo_test["flavor"], topo_test["flavor"],
topo_test["image"], topo_test["image"],
topo_test["allow_threads"])[0] topo_test["allow_threads"])[0]

View File

@ -215,347 +215,332 @@ class InstanceInfo(object):
self.__dict__ == other.__dict__) self.__dict__ == other.__dict__)
class VirtCPUTopology(object): def _score_cpu_topology(topology, wanttopology):
"""Calculate score for the topology against a desired configuration
def __init__(self, sockets, cores, threads): :param wanttopology: nova.objects.VirtCPUTopology instance for
"""Create a new CPU topology object preferred topology
:param sockets: number of sockets, at least 1 Calculate a score indicating how well this topology
:param cores: number of cores, at least 1 matches against a preferred topology. A score of 3
:param threads: number of threads, at least 1 indicates an exact match for sockets, cores and threads.
A score of 2 indicates a match of sockets & cores or
sockets & threads or cores and threads. A score of 1
indicates a match of sockets or cores or threads. A
score of 0 indicates no match
Create a new CPU topology object representing the :returns: score in range 0 (worst) to 3 (best)
number of sockets, cores and threads to use for """
the virtual instance.
"""
self.sockets = sockets score = 0
self.cores = cores if (wanttopology.sockets != -1 and
self.threads = threads topology.sockets == wanttopology.sockets):
score = score + 1
if (wanttopology.cores != -1 and
topology.cores == wanttopology.cores):
score = score + 1
if (wanttopology.threads != -1 and
topology.threads == wanttopology.threads):
score = score + 1
return score
def score(self, wanttopology):
"""Calculate score for the topology against a desired configuration
:param wanttopology: VirtCPUTopology instance for preferred topology def _get_cpu_topology_constraints(flavor, image_meta):
"""Get the topology constraints declared in flavor or image
Calculate a score indicating how well this topology :param flavor: Flavor object to read extra specs from
matches against a preferred topology. A score of 3 :param image_meta: Image object to read image metadata from
indicates an exact match for sockets, cores and threads.
A score of 2 indicates a match of sockets & cores or
sockets & threads or cores and threads. A score of 1
indicates a match of sockets or cores or threads. A
score of 0 indicates no match
:returns: score in range 0 (worst) to 3 (best) Gets the topology constraints from the configuration defined
""" in the flavor extra specs or the image metadata. In the flavor
this will look for
score = 0 hw:cpu_sockets - preferred socket count
if (wanttopology.sockets != -1 and hw:cpu_cores - preferred core count
self.sockets == wanttopology.sockets): hw:cpu_threads - preferred thread count
score = score + 1 hw:cpu_maxsockets - maximum socket count
if (wanttopology.cores != -1 and hw:cpu_maxcores - maximum core count
self.cores == wanttopology.cores): hw:cpu_maxthreads - maximum thread count
score = score + 1
if (wanttopology.threads != -1 and
self.threads == wanttopology.threads):
score = score + 1
return score
@staticmethod In the image metadata this will look at
def get_topology_constraints(flavor, image_meta):
"""Get the topology constraints declared in flavor or image
:param flavor: Flavor object to read extra specs from hw_cpu_sockets - preferred socket count
:param image_meta: Image object to read image metadata from hw_cpu_cores - preferred core count
hw_cpu_threads - preferred thread count
hw_cpu_maxsockets - maximum socket count
hw_cpu_maxcores - maximum core count
hw_cpu_maxthreads - maximum thread count
Gets the topology constraints from the configuration defined The image metadata must be strictly lower than any values
in the flavor extra specs or the image metadata. In the flavor set in the flavor. All values are, however, optional.
this will look for
hw:cpu_sockets - preferred socket count This will return a pair of nova.objects.VirtCPUTopology instances,
hw:cpu_cores - preferred core count the first giving the preferred socket/core/thread counts,
hw:cpu_threads - preferred thread count and the second giving the upper limits on socket/core/
hw:cpu_maxsockets - maximum socket count thread counts.
hw:cpu_maxcores - maximum core count
hw:cpu_maxthreads - maximum thread count
In the image metadata this will look at exception.ImageVCPULimitsRangeExceeded will be raised
if the maximum counts set against the image exceed
the maximum counts set against the flavor
hw_cpu_sockets - preferred socket count exception.ImageVCPUTopologyRangeExceeded will be raised
hw_cpu_cores - preferred core count if the preferred counts set against the image exceed
hw_cpu_threads - preferred thread count the maximum counts set against the image or flavor
hw_cpu_maxsockets - maximum socket count
hw_cpu_maxcores - maximum core count
hw_cpu_maxthreads - maximum thread count
The image metadata must be strictly lower than any values :returns: (preferred topology, maximum topology)
set in the flavor. All values are, however, optional. """
This will return a pair of VirtCPUTopology instances, # Obtain the absolute limits from the flavor
the first giving the preferred socket/core/thread counts, flvmaxsockets = int(flavor.extra_specs.get(
and the second giving the upper limits on socket/core/ "hw:cpu_max_sockets", 65536))
thread counts. flvmaxcores = int(flavor.extra_specs.get(
"hw:cpu_max_cores", 65536))
flvmaxthreads = int(flavor.extra_specs.get(
"hw:cpu_max_threads", 65536))
exception.ImageVCPULimitsRangeExceeded will be raised LOG.debug("Flavor limits %(sockets)d:%(cores)d:%(threads)d",
if the maximum counts set against the image exceed {"sockets": flvmaxsockets,
the maximum counts set against the flavor "cores": flvmaxcores,
"threads": flvmaxthreads})
exception.ImageVCPUTopologyRangeExceeded will be raised # Get any customized limits from the image
if the preferred counts set against the image exceed maxsockets = int(image_meta.get("properties", {})
the maximum counts set against the image or flavor .get("hw_cpu_max_sockets", flvmaxsockets))
maxcores = int(image_meta.get("properties", {})
.get("hw_cpu_max_cores", flvmaxcores))
maxthreads = int(image_meta.get("properties", {})
.get("hw_cpu_max_threads", flvmaxthreads))
:returns: (preferred topology, maximum topology) LOG.debug("Image limits %(sockets)d:%(cores)d:%(threads)d",
""" {"sockets": maxsockets,
"cores": maxcores,
"threads": maxthreads})
# Obtain the absolute limits from the flavor # Image limits are not permitted to exceed the flavor
flvmaxsockets = int(flavor.extra_specs.get( # limits. ie they can only lower what the flavor defines
"hw:cpu_max_sockets", 65536)) if ((maxsockets > flvmaxsockets) or
flvmaxcores = int(flavor.extra_specs.get( (maxcores > flvmaxcores) or
"hw:cpu_max_cores", 65536)) (maxthreads > flvmaxthreads)):
flvmaxthreads = int(flavor.extra_specs.get( raise exception.ImageVCPULimitsRangeExceeded(
"hw:cpu_max_threads", 65536)) sockets=maxsockets,
cores=maxcores,
threads=maxthreads,
maxsockets=flvmaxsockets,
maxcores=flvmaxcores,
maxthreads=flvmaxthreads)
LOG.debug("Flavor limits %(sockets)d:%(cores)d:%(threads)d", # Get any default preferred topology from the flavor
{"sockets": flvmaxsockets, flvsockets = int(flavor.extra_specs.get("hw:cpu_sockets", -1))
"cores": flvmaxcores, flvcores = int(flavor.extra_specs.get("hw:cpu_cores", -1))
"threads": flvmaxthreads}) flvthreads = int(flavor.extra_specs.get("hw:cpu_threads", -1))
# Get any customized limits from the image LOG.debug("Flavor pref %(sockets)d:%(cores)d:%(threads)d",
maxsockets = int(image_meta.get("properties", {}) {"sockets": flvsockets,
.get("hw_cpu_max_sockets", flvmaxsockets)) "cores": flvcores,
maxcores = int(image_meta.get("properties", {}) "threads": flvthreads})
.get("hw_cpu_max_cores", flvmaxcores))
maxthreads = int(image_meta.get("properties", {})
.get("hw_cpu_max_threads", flvmaxthreads))
LOG.debug("Image limits %(sockets)d:%(cores)d:%(threads)d", # If the image limits have reduced the flavor limits
{"sockets": maxsockets, # we might need to discard the preferred topology
"cores": maxcores, # from the flavor
"threads": maxthreads}) if ((flvsockets > maxsockets) or
(flvcores > maxcores) or
(flvthreads > maxthreads)):
flvsockets = flvcores = flvthreads = -1
# Image limits are not permitted to exceed the flavor # Finally see if the image has provided a preferred
# limits. ie they can only lower what the flavor defines # topology to use
if ((maxsockets > flvmaxsockets) or sockets = int(image_meta.get("properties", {})
(maxcores > flvmaxcores) or .get("hw_cpu_sockets", -1))
(maxthreads > flvmaxthreads)): cores = int(image_meta.get("properties", {})
raise exception.ImageVCPULimitsRangeExceeded( .get("hw_cpu_cores", -1))
sockets=maxsockets, threads = int(image_meta.get("properties", {})
cores=maxcores, .get("hw_cpu_threads", -1))
threads=maxthreads,
maxsockets=flvmaxsockets,
maxcores=flvmaxcores,
maxthreads=flvmaxthreads)
# Get any default preferred topology from the flavor LOG.debug("Image pref %(sockets)d:%(cores)d:%(threads)d",
flvsockets = int(flavor.extra_specs.get("hw:cpu_sockets", -1)) {"sockets": sockets,
flvcores = int(flavor.extra_specs.get("hw:cpu_cores", -1)) "cores": cores,
flvthreads = int(flavor.extra_specs.get("hw:cpu_threads", -1)) "threads": threads})
LOG.debug("Flavor pref %(sockets)d:%(cores)d:%(threads)d", # Image topology is not permitted to exceed image/flavor
{"sockets": flvsockets, # limits
"cores": flvcores, if ((sockets > maxsockets) or
"threads": flvthreads}) (cores > maxcores) or
(threads > maxthreads)):
raise exception.ImageVCPUTopologyRangeExceeded(
sockets=sockets,
cores=cores,
threads=threads,
maxsockets=maxsockets,
maxcores=maxcores,
maxthreads=maxthreads)
# If the image limits have reduced the flavor limits # If no preferred topology was set against the image
# we might need to discard the preferred topology # then use the preferred topology from the flavor
# from the flavor # We use 'and' not 'or', since if any value is set
if ((flvsockets > maxsockets) or # against the image this invalidates the entire set
(flvcores > maxcores) or # of values from the flavor
(flvthreads > maxthreads)): if sockets == -1 and cores == -1 and threads == -1:
flvsockets = flvcores = flvthreads = -1 sockets = flvsockets
cores = flvcores
threads = flvthreads
# Finally see if the image has provided a preferred LOG.debug("Chosen %(sockets)d:%(cores)d:%(threads)d limits "
# topology to use "%(maxsockets)d:%(maxcores)d:%(maxthreads)d",
sockets = int(image_meta.get("properties", {}) {"sockets": sockets, "cores": cores,
.get("hw_cpu_sockets", -1)) "threads": threads, "maxsockets": maxsockets,
cores = int(image_meta.get("properties", {}) "maxcores": maxcores, "maxthreads": maxthreads})
.get("hw_cpu_cores", -1))
threads = int(image_meta.get("properties", {})
.get("hw_cpu_threads", -1))
LOG.debug("Image pref %(sockets)d:%(cores)d:%(threads)d", return (objects.VirtCPUTopology(sockets=sockets, cores=cores,
{"sockets": sockets, threads=threads),
"cores": cores, objects.VirtCPUTopology(sockets=maxsockets, cores=maxcores,
"threads": threads}) threads=maxthreads))
# Image topology is not permitted to exceed image/flavor
# limits
if ((sockets > maxsockets) or
(cores > maxcores) or
(threads > maxthreads)):
raise exception.ImageVCPUTopologyRangeExceeded(
sockets=sockets,
cores=cores,
threads=threads,
maxsockets=maxsockets,
maxcores=maxcores,
maxthreads=maxthreads)
# If no preferred topology was set against the image def _get_possible_cpu_topologies(vcpus, maxtopology, allow_threads):
# then use the preferred topology from the flavor """Get a list of possible topologies for a vCPU count
# We use 'and' not 'or', since if any value is set :param vcpus: total number of CPUs for guest instance
# against the image this invalidates the entire set :param maxtopology: nova.objects.VirtCPUTopology for upper limits
# of values from the flavor :param allow_threads: if the hypervisor supports CPU threads
if sockets == -1 and cores == -1 and threads == -1:
sockets = flvsockets
cores = flvcores
threads = flvthreads
LOG.debug("Chosen %(sockets)d:%(cores)d:%(threads)d limits " Given a total desired vCPU count and constraints on the
"%(maxsockets)d:%(maxcores)d:%(maxthreads)d", maximum number of sockets, cores and threads, return a
{"sockets": sockets, "cores": cores, list of nova.objects.VirtCPUTopology instances that represent every
"threads": threads, "maxsockets": maxsockets, possible topology that satisfies the constraints.
"maxcores": maxcores, "maxthreads": maxthreads})
return (VirtCPUTopology(sockets, cores, threads), exception.ImageVCPULimitsRangeImpossible is raised if
VirtCPUTopology(maxsockets, maxcores, maxthreads)) it is impossible to achieve the total vcpu count given
the maximum limits on sockets, cores & threads.
@staticmethod :returns: list of nova.objects.VirtCPUTopology instances
def get_possible_topologies(vcpus, maxtopology, allow_threads): """
"""Get a list of possible topologies for a vCPU count
:param vcpus: total number of CPUs for guest instance
:param maxtopology: VirtCPUTopology for upper limits
:param allow_threads: if the hypervisor supports CPU threads
Given a total desired vCPU count and constraints on the # Clamp limits to number of vcpus to prevent
maximum number of sockets, cores and threads, return a # iterating over insanely large list
list of VirtCPUTopology instances that represent every maxsockets = min(vcpus, maxtopology.sockets)
possible topology that satisfies the constraints. maxcores = min(vcpus, maxtopology.cores)
maxthreads = min(vcpus, maxtopology.threads)
exception.ImageVCPULimitsRangeImpossible is raised if if not allow_threads:
it is impossible to achieve the total vcpu count given maxthreads = 1
the maximum limits on sockets, cores & threads.
:returns: list of VirtCPUTopology instances LOG.debug("Build topologies for %(vcpus)d vcpu(s) "
""" "%(maxsockets)d:%(maxcores)d:%(maxthreads)d",
{"vcpus": vcpus, "maxsockets": maxsockets,
"maxcores": maxcores, "maxthreads": maxthreads})
# Clamp limits to number of vcpus to prevent # Figure out all possible topologies that match
# iterating over insanely large list # the required vcpus count and satisfy the declared
maxsockets = min(vcpus, maxtopology.sockets) # limits. If the total vCPU count were very high
maxcores = min(vcpus, maxtopology.cores) # it might be more efficient to factorize the vcpu
maxthreads = min(vcpus, maxtopology.threads) # count and then only iterate over its factors, but
# that's overkill right now
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 not allow_threads: possible.append(o)
maxthreads = 1
LOG.debug("Build topologies for %(vcpus)d vcpu(s) " # We want to
"%(maxsockets)d:%(maxcores)d:%(maxthreads)d", # - Minimize threads (ie larger sockets * cores is best)
{"vcpus": vcpus, "maxsockets": maxsockets, # - Prefer sockets over cores
"maxcores": maxcores, "maxthreads": maxthreads}) possible = sorted(possible, reverse=True,
key=lambda x: (x.sockets * x.cores,
x.sockets,
x.threads))
# Figure out all possible topologies that match LOG.debug("Got %d possible topologies", len(possible))
# the required vcpus count and satisfy the declared if len(possible) == 0:
# limits. If the total vCPU count were very high raise exception.ImageVCPULimitsRangeImpossible(vcpus=vcpus,
# it might be more efficient to factorize the vcpu sockets=maxsockets,
# count and then only iterate over its factors, but cores=maxcores,
# that's overkill right now threads=maxthreads)
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:
possible.append(VirtCPUTopology(s, c, t))
# We want to return possible
# - Minimize threads (ie larger sockets * cores is best)
# - Prefer sockets over cores
possible = sorted(possible, reverse=True,
key=lambda x: (x.sockets * x.cores,
x.sockets,
x.threads))
LOG.debug("Got %d possible topologies", len(possible))
if len(possible) == 0:
raise exception.ImageVCPULimitsRangeImpossible(vcpus=vcpus,
sockets=maxsockets,
cores=maxcores,
threads=maxthreads)
return possible def _sort_possible_cpu_topologies(possible, wanttopology):
"""Sort the topologies in order of preference
:param possible: list of nova.objects.VirtCPUTopology instances
:param wanttopology: nova.objects.VirtCPUTopology for preferred
topology
@staticmethod This takes the list of possible topologies and resorts
def sort_possible_topologies(possible, wanttopology): it such that those configurations which most closely
"""Sort the topologies in order of preference match the preferred topology are first.
:param possible: list of VirtCPUTopology instances
:param wanttopology: VirtCPUTopology for preferred topology
This takes the list of possible topologies and resorts :returns: sorted list of nova.objects.VirtCPUTopology instances
it such that those configurations which most closely """
match the preferred topology are first.
:returns: sorted list of VirtCPUTopology instances # Look at possible topologies and score them according
""" # to how well they match the preferred topologies
# We don't use python's sort(), since we want to
# preserve the sorting done when populating the
# 'possible' list originally
scores = collections.defaultdict(list)
for topology in possible:
score = _score_cpu_topology(topology, wanttopology)
scores[score].append(topology)
# Look at possible topologies and score them according # Build list of all possible topologies sorted
# to how well they match the preferred topologies # by the match score, best match first
# We don't use python's sort(), since we want to desired = []
# preserve the sorting done when populating the desired.extend(scores[3])
# 'possible' list originally desired.extend(scores[2])
scores = collections.defaultdict(list) desired.extend(scores[1])
for topology in possible: desired.extend(scores[0])
score = topology.score(wanttopology)
scores[score].append(topology)
# Build list of all possible topologies sorted return desired
# by the match score, best match first
desired = []
desired.extend(scores[3])
desired.extend(scores[2])
desired.extend(scores[1])
desired.extend(scores[0])
return desired
@staticmethod def _get_desirable_cpu_topologies(flavor, image_meta, allow_threads=True):
def get_desirable_configs(flavor, image_meta, allow_threads=True): """Get desired CPU topologies according to settings
"""Get desired CPU topologies according to settings
:param flavor: Flavor object to query extra specs from :param flavor: Flavor object to query extra specs from
:param image_meta: ImageMeta object to query properties from :param image_meta: ImageMeta object to query properties from
:param allow_threads: if the hypervisor supports CPU threads :param allow_threads: if the hypervisor supports CPU threads
Look at the properties set in the flavor extra specs and Look at the properties set in the flavor extra specs and
the image metadata and build up a list of all possible the image metadata and build up a list of all possible
valid CPU topologies that can be used in the guest. Then valid CPU topologies that can be used in the guest. Then
return this list sorted in order of preference. return this list sorted in order of preference.
:returns: sorted list of VirtCPUTopology instances :returns: sorted list of nova.objects.VirtCPUTopology instances
""" """
LOG.debug("Getting desirable topologies for flavor %(flavor)s " LOG.debug("Getting desirable topologies for flavor %(flavor)s "
"and image_meta %(image_meta)s", "and image_meta %(image_meta)s",
{"flavor": flavor, "image_meta": image_meta}) {"flavor": flavor, "image_meta": image_meta})
preferred, maximum = ( preferred, maximum = _get_cpu_topology_constraints(flavor, image_meta)
VirtCPUTopology.get_topology_constraints(flavor,
image_meta))
possible = VirtCPUTopology.get_possible_topologies( possible = _get_possible_cpu_topologies(flavor.vcpus,
flavor.vcpus, maximum, allow_threads) maximum,
desired = VirtCPUTopology.sort_possible_topologies( allow_threads)
possible, preferred) desired = _sort_possible_cpu_topologies(possible, preferred)
return desired return desired
@staticmethod
def get_best_config(flavor, image_meta, allow_threads=True):
"""Get bst CPU topology according to settings
:param flavor: Flavor object to query extra specs from def get_best_cpu_topology(flavor, image_meta, allow_threads=True):
:param image_meta: ImageMeta object to query properties from """Get best CPU topology according to settings
:param allow_threads: if the hypervisor supports CPU threads
Look at the properties set in the flavor extra specs and :param flavor: Flavor object to query extra specs from
the image metadata and build up a list of all possible :param image_meta: ImageMeta object to query properties from
valid CPU topologies that can be used in the guest. Then :param allow_threads: if the hypervisor supports CPU threads
return the best topology to use
:returns: a VirtCPUTopology instance for best topology Look at the properties set in the flavor extra specs and
""" the image metadata and build up a list of all possible
valid CPU topologies that can be used in the guest. Then
return the best topology to use
return VirtCPUTopology.get_desirable_configs(flavor, :returns: a nova.objects.VirtCPUTopology instance for best topology
image_meta, """
allow_threads)[0]
return _get_desirable_cpu_topologies(flavor, image_meta, allow_threads)[0]
class VirtPageSize(object): class VirtPageSize(object):

View File

@ -3392,8 +3392,7 @@ class LibvirtDriver(driver.ComputeDriver):
if cpu is None: if cpu is None:
return None return None
topology = hardware.VirtCPUTopology.get_best_config(flavor, topology = hardware.get_best_cpu_topology(flavor, image)
image)
cpu.sockets = topology.sockets cpu.sockets = topology.sockets
cpu.cores = topology.cores cpu.cores = topology.cores