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:
parent
aebcf022ee
commit
96ab8ab058
|
@ -52,4 +52,5 @@ def register_all():
|
|||
__import__('nova.objects.security_group')
|
||||
__import__('nova.objects.security_group_rule')
|
||||
__import__('nova.objects.service')
|
||||
__import__('nova.objects.virt_cpu_topology')
|
||||
__import__('nova.objects.virtual_interface')
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -1056,6 +1056,7 @@ object_data = {
|
|||
'TestSubclassedObject': '1.6-c63feb2f2533b7d075490c04a2cc10dd',
|
||||
'VirtualInterface': '1.0-10fdac4c704102b6d57d6936d6d790d2',
|
||||
'VirtualInterfaceList': '1.0-accbf02628a8063c1d885077a2bf49b6',
|
||||
'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -375,7 +375,7 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
for topo_test in testdata:
|
||||
if type(topo_test["expect"]) == tuple:
|
||||
(preferred,
|
||||
maximum) = hw.VirtCPUTopology.get_topology_constraints(
|
||||
maximum) = hw._get_cpu_topology_constraints(
|
||||
topo_test["flavor"],
|
||||
topo_test["image"])
|
||||
|
||||
|
@ -387,11 +387,11 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
self.assertEqual(topo_test["expect"][5], maximum.threads)
|
||||
else:
|
||||
self.assertRaises(topo_test["expect"],
|
||||
hw.VirtCPUTopology.get_topology_constraints,
|
||||
hw._get_cpu_topology_constraints,
|
||||
topo_test["flavor"],
|
||||
topo_test["image"])
|
||||
|
||||
def test_possible_configs(self):
|
||||
def test_possible_topologies(self):
|
||||
testdata = [
|
||||
{
|
||||
"allow_threads": True,
|
||||
|
@ -481,11 +481,12 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
for topo_test in testdata:
|
||||
if type(topo_test["expect"]) == list:
|
||||
actual = []
|
||||
for topology in hw.VirtCPUTopology.get_possible_topologies(
|
||||
for topology in hw._get_possible_cpu_topologies(
|
||||
topo_test["vcpus"],
|
||||
hw.VirtCPUTopology(topo_test["maxsockets"],
|
||||
topo_test["maxcores"],
|
||||
topo_test["maxthreads"]),
|
||||
objects.VirtCPUTopology(
|
||||
sockets=topo_test["maxsockets"],
|
||||
cores=topo_test["maxcores"],
|
||||
threads=topo_test["maxthreads"]),
|
||||
topo_test["allow_threads"]):
|
||||
actual.append([topology.sockets,
|
||||
topology.cores,
|
||||
|
@ -494,14 +495,15 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
self.assertEqual(topo_test["expect"], actual)
|
||||
else:
|
||||
self.assertRaises(topo_test["expect"],
|
||||
hw.VirtCPUTopology.get_possible_topologies,
|
||||
hw._get_possible_cpu_topologies,
|
||||
topo_test["vcpus"],
|
||||
hw.VirtCPUTopology(topo_test["maxsockets"],
|
||||
topo_test["maxcores"],
|
||||
topo_test["maxthreads"]),
|
||||
objects.VirtCPUTopology(
|
||||
sockets=topo_test["maxsockets"],
|
||||
cores=topo_test["maxcores"],
|
||||
threads=topo_test["maxthreads"]),
|
||||
topo_test["allow_threads"])
|
||||
|
||||
def test_sorting_configs(self):
|
||||
def test_sorting_topologies(self):
|
||||
testdata = [
|
||||
{
|
||||
"allow_threads": True,
|
||||
|
@ -572,18 +574,18 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
|
||||
for topo_test in testdata:
|
||||
actual = []
|
||||
possible = hw.VirtCPUTopology.get_possible_topologies(
|
||||
possible = hw._get_possible_cpu_topologies(
|
||||
topo_test["vcpus"],
|
||||
hw.VirtCPUTopology(topo_test["maxsockets"],
|
||||
topo_test["maxcores"],
|
||||
topo_test["maxthreads"]),
|
||||
objects.VirtCPUTopology(sockets=topo_test["maxsockets"],
|
||||
cores=topo_test["maxcores"],
|
||||
threads=topo_test["maxthreads"]),
|
||||
topo_test["allow_threads"])
|
||||
|
||||
tops = hw.VirtCPUTopology.sort_possible_topologies(
|
||||
tops = hw._sort_possible_cpu_topologies(
|
||||
possible,
|
||||
hw.VirtCPUTopology(topo_test["sockets"],
|
||||
topo_test["cores"],
|
||||
topo_test["threads"]))
|
||||
objects.VirtCPUTopology(sockets=topo_test["sockets"],
|
||||
cores=topo_test["cores"],
|
||||
threads=topo_test["threads"]))
|
||||
for topology in tops:
|
||||
actual.append([topology.sockets,
|
||||
topology.cores,
|
||||
|
@ -705,7 +707,7 @@ class VCPUTopologyTest(test.NoDBTestCase):
|
|||
]
|
||||
|
||||
for topo_test in testdata:
|
||||
topology = hw.VirtCPUTopology.get_desirable_configs(
|
||||
topology = hw._get_desirable_cpu_topologies(
|
||||
topo_test["flavor"],
|
||||
topo_test["image"],
|
||||
topo_test["allow_threads"])[0]
|
||||
|
|
|
@ -215,347 +215,332 @@ class InstanceInfo(object):
|
|||
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):
|
||||
"""Create a new CPU topology object
|
||||
:param wanttopology: nova.objects.VirtCPUTopology instance for
|
||||
preferred topology
|
||||
|
||||
:param sockets: number of sockets, at least 1
|
||||
:param cores: number of cores, at least 1
|
||||
:param threads: number of threads, at least 1
|
||||
Calculate a score indicating how well this topology
|
||||
matches against a preferred topology. A score of 3
|
||||
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
|
||||
number of sockets, cores and threads to use for
|
||||
the virtual instance.
|
||||
"""
|
||||
:returns: score in range 0 (worst) to 3 (best)
|
||||
"""
|
||||
|
||||
self.sockets = sockets
|
||||
self.cores = cores
|
||||
self.threads = threads
|
||||
score = 0
|
||||
if (wanttopology.sockets != -1 and
|
||||
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
|
||||
matches against a preferred topology. A score of 3
|
||||
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
|
||||
:param flavor: Flavor object to read extra specs from
|
||||
:param image_meta: Image object to read image metadata from
|
||||
|
||||
: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
|
||||
if (wanttopology.sockets != -1 and
|
||||
self.sockets == wanttopology.sockets):
|
||||
score = score + 1
|
||||
if (wanttopology.cores != -1 and
|
||||
self.cores == wanttopology.cores):
|
||||
score = score + 1
|
||||
if (wanttopology.threads != -1 and
|
||||
self.threads == wanttopology.threads):
|
||||
score = score + 1
|
||||
return score
|
||||
hw:cpu_sockets - preferred socket count
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def get_topology_constraints(flavor, image_meta):
|
||||
"""Get the topology constraints declared in flavor or image
|
||||
In the image metadata this will look at
|
||||
|
||||
:param flavor: Flavor object to read extra specs from
|
||||
:param image_meta: Image object to read image metadata from
|
||||
hw_cpu_sockets - preferred socket count
|
||||
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
|
||||
in the flavor extra specs or the image metadata. In the flavor
|
||||
this will look for
|
||||
The image metadata must be strictly lower than any values
|
||||
set in the flavor. All values are, however, optional.
|
||||
|
||||
hw:cpu_sockets - preferred socket count
|
||||
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
|
||||
This will return a pair of nova.objects.VirtCPUTopology instances,
|
||||
the first giving the preferred socket/core/thread counts,
|
||||
and the second giving the upper limits on socket/core/
|
||||
thread counts.
|
||||
|
||||
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
|
||||
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
|
||||
exception.ImageVCPUTopologyRangeExceeded will be raised
|
||||
if the preferred counts set against the image exceed
|
||||
the maximum counts set against the image or flavor
|
||||
|
||||
The image metadata must be strictly lower than any values
|
||||
set in the flavor. All values are, however, optional.
|
||||
:returns: (preferred topology, maximum topology)
|
||||
"""
|
||||
|
||||
This will return a pair of VirtCPUTopology instances,
|
||||
the first giving the preferred socket/core/thread counts,
|
||||
and the second giving the upper limits on socket/core/
|
||||
thread counts.
|
||||
# Obtain the absolute limits from the flavor
|
||||
flvmaxsockets = int(flavor.extra_specs.get(
|
||||
"hw:cpu_max_sockets", 65536))
|
||||
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
|
||||
if the maximum counts set against the image exceed
|
||||
the maximum counts set against the flavor
|
||||
LOG.debug("Flavor limits %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": flvmaxsockets,
|
||||
"cores": flvmaxcores,
|
||||
"threads": flvmaxthreads})
|
||||
|
||||
exception.ImageVCPUTopologyRangeExceeded will be raised
|
||||
if the preferred counts set against the image exceed
|
||||
the maximum counts set against the image or flavor
|
||||
# Get any customized limits from the image
|
||||
maxsockets = int(image_meta.get("properties", {})
|
||||
.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
|
||||
flvmaxsockets = int(flavor.extra_specs.get(
|
||||
"hw:cpu_max_sockets", 65536))
|
||||
flvmaxcores = int(flavor.extra_specs.get(
|
||||
"hw:cpu_max_cores", 65536))
|
||||
flvmaxthreads = int(flavor.extra_specs.get(
|
||||
"hw:cpu_max_threads", 65536))
|
||||
# Image limits are not permitted to exceed the flavor
|
||||
# limits. ie they can only lower what the flavor defines
|
||||
if ((maxsockets > flvmaxsockets) or
|
||||
(maxcores > flvmaxcores) or
|
||||
(maxthreads > flvmaxthreads)):
|
||||
raise exception.ImageVCPULimitsRangeExceeded(
|
||||
sockets=maxsockets,
|
||||
cores=maxcores,
|
||||
threads=maxthreads,
|
||||
maxsockets=flvmaxsockets,
|
||||
maxcores=flvmaxcores,
|
||||
maxthreads=flvmaxthreads)
|
||||
|
||||
LOG.debug("Flavor limits %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": flvmaxsockets,
|
||||
"cores": flvmaxcores,
|
||||
"threads": flvmaxthreads})
|
||||
# Get any default preferred topology from the flavor
|
||||
flvsockets = int(flavor.extra_specs.get("hw:cpu_sockets", -1))
|
||||
flvcores = int(flavor.extra_specs.get("hw:cpu_cores", -1))
|
||||
flvthreads = int(flavor.extra_specs.get("hw:cpu_threads", -1))
|
||||
|
||||
# Get any customized limits from the image
|
||||
maxsockets = int(image_meta.get("properties", {})
|
||||
.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))
|
||||
LOG.debug("Flavor pref %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": flvsockets,
|
||||
"cores": flvcores,
|
||||
"threads": flvthreads})
|
||||
|
||||
LOG.debug("Image limits %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": maxsockets,
|
||||
"cores": maxcores,
|
||||
"threads": maxthreads})
|
||||
# If the image limits have reduced the flavor limits
|
||||
# we might need to discard the preferred topology
|
||||
# from the flavor
|
||||
if ((flvsockets > maxsockets) or
|
||||
(flvcores > maxcores) or
|
||||
(flvthreads > maxthreads)):
|
||||
flvsockets = flvcores = flvthreads = -1
|
||||
|
||||
# Image limits are not permitted to exceed the flavor
|
||||
# limits. ie they can only lower what the flavor defines
|
||||
if ((maxsockets > flvmaxsockets) or
|
||||
(maxcores > flvmaxcores) or
|
||||
(maxthreads > flvmaxthreads)):
|
||||
raise exception.ImageVCPULimitsRangeExceeded(
|
||||
sockets=maxsockets,
|
||||
cores=maxcores,
|
||||
threads=maxthreads,
|
||||
maxsockets=flvmaxsockets,
|
||||
maxcores=flvmaxcores,
|
||||
maxthreads=flvmaxthreads)
|
||||
# Finally see if the image has provided a preferred
|
||||
# topology to use
|
||||
sockets = int(image_meta.get("properties", {})
|
||||
.get("hw_cpu_sockets", -1))
|
||||
cores = int(image_meta.get("properties", {})
|
||||
.get("hw_cpu_cores", -1))
|
||||
threads = int(image_meta.get("properties", {})
|
||||
.get("hw_cpu_threads", -1))
|
||||
|
||||
# Get any default preferred topology from the flavor
|
||||
flvsockets = int(flavor.extra_specs.get("hw:cpu_sockets", -1))
|
||||
flvcores = int(flavor.extra_specs.get("hw:cpu_cores", -1))
|
||||
flvthreads = int(flavor.extra_specs.get("hw:cpu_threads", -1))
|
||||
LOG.debug("Image pref %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": sockets,
|
||||
"cores": cores,
|
||||
"threads": threads})
|
||||
|
||||
LOG.debug("Flavor pref %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": flvsockets,
|
||||
"cores": flvcores,
|
||||
"threads": flvthreads})
|
||||
# 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 the image limits have reduced the flavor limits
|
||||
# we might need to discard the preferred topology
|
||||
# from the flavor
|
||||
if ((flvsockets > maxsockets) or
|
||||
(flvcores > maxcores) or
|
||||
(flvthreads > maxthreads)):
|
||||
flvsockets = flvcores = flvthreads = -1
|
||||
# If no preferred topology was set against the image
|
||||
# then use the preferred topology from the flavor
|
||||
# We use 'and' not 'or', since if any value is set
|
||||
# against the image this invalidates the entire set
|
||||
# of values from the flavor
|
||||
if sockets == -1 and cores == -1 and threads == -1:
|
||||
sockets = flvsockets
|
||||
cores = flvcores
|
||||
threads = flvthreads
|
||||
|
||||
# Finally see if the image has provided a preferred
|
||||
# topology to use
|
||||
sockets = int(image_meta.get("properties", {})
|
||||
.get("hw_cpu_sockets", -1))
|
||||
cores = int(image_meta.get("properties", {})
|
||||
.get("hw_cpu_cores", -1))
|
||||
threads = int(image_meta.get("properties", {})
|
||||
.get("hw_cpu_threads", -1))
|
||||
LOG.debug("Chosen %(sockets)d:%(cores)d:%(threads)d limits "
|
||||
"%(maxsockets)d:%(maxcores)d:%(maxthreads)d",
|
||||
{"sockets": sockets, "cores": cores,
|
||||
"threads": threads, "maxsockets": maxsockets,
|
||||
"maxcores": maxcores, "maxthreads": maxthreads})
|
||||
|
||||
LOG.debug("Image pref %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": sockets,
|
||||
"cores": cores,
|
||||
"threads": threads})
|
||||
return (objects.VirtCPUTopology(sockets=sockets, cores=cores,
|
||||
threads=threads),
|
||||
objects.VirtCPUTopology(sockets=maxsockets, cores=maxcores,
|
||||
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
|
||||
# then use the preferred topology from the flavor
|
||||
# We use 'and' not 'or', since if any value is set
|
||||
# against the image this invalidates the entire set
|
||||
# of values from the flavor
|
||||
if sockets == -1 and cores == -1 and threads == -1:
|
||||
sockets = flvsockets
|
||||
cores = flvcores
|
||||
threads = flvthreads
|
||||
def _get_possible_cpu_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: nova.objects.VirtCPUTopology for upper limits
|
||||
:param allow_threads: if the hypervisor supports CPU threads
|
||||
|
||||
LOG.debug("Chosen %(sockets)d:%(cores)d:%(threads)d limits "
|
||||
"%(maxsockets)d:%(maxcores)d:%(maxthreads)d",
|
||||
{"sockets": sockets, "cores": cores,
|
||||
"threads": threads, "maxsockets": maxsockets,
|
||||
"maxcores": maxcores, "maxthreads": maxthreads})
|
||||
Given a total desired vCPU count and constraints on the
|
||||
maximum number of sockets, cores and threads, return a
|
||||
list of nova.objects.VirtCPUTopology instances that represent every
|
||||
possible topology that satisfies the constraints.
|
||||
|
||||
return (VirtCPUTopology(sockets, cores, threads),
|
||||
VirtCPUTopology(maxsockets, maxcores, maxthreads))
|
||||
exception.ImageVCPULimitsRangeImpossible is raised if
|
||||
it is impossible to achieve the total vcpu count given
|
||||
the maximum limits on sockets, cores & threads.
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
:returns: list of nova.objects.VirtCPUTopology instances
|
||||
"""
|
||||
|
||||
Given a total desired vCPU count and constraints on the
|
||||
maximum number of sockets, cores and threads, return a
|
||||
list of VirtCPUTopology instances that represent every
|
||||
possible topology that satisfies the constraints.
|
||||
# Clamp limits to number of vcpus to prevent
|
||||
# iterating over insanely large list
|
||||
maxsockets = min(vcpus, maxtopology.sockets)
|
||||
maxcores = min(vcpus, maxtopology.cores)
|
||||
maxthreads = min(vcpus, maxtopology.threads)
|
||||
|
||||
exception.ImageVCPULimitsRangeImpossible is raised if
|
||||
it is impossible to achieve the total vcpu count given
|
||||
the maximum limits on sockets, cores & threads.
|
||||
if not allow_threads:
|
||||
maxthreads = 1
|
||||
|
||||
: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
|
||||
# iterating over insanely large list
|
||||
maxsockets = min(vcpus, maxtopology.sockets)
|
||||
maxcores = min(vcpus, maxtopology.cores)
|
||||
maxthreads = min(vcpus, maxtopology.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
|
||||
# it might be more efficient to factorize the vcpu
|
||||
# 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:
|
||||
maxthreads = 1
|
||||
possible.append(o)
|
||||
|
||||
LOG.debug("Build topologies for %(vcpus)d vcpu(s) "
|
||||
"%(maxsockets)d:%(maxcores)d:%(maxthreads)d",
|
||||
{"vcpus": vcpus, "maxsockets": maxsockets,
|
||||
"maxcores": maxcores, "maxthreads": maxthreads})
|
||||
# We want to
|
||||
# - 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))
|
||||
|
||||
# Figure out all possible topologies that match
|
||||
# the required vcpus count and satisfy the declared
|
||||
# limits. If the total vCPU count were very high
|
||||
# it might be more efficient to factorize the vcpu
|
||||
# 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:
|
||||
possible.append(VirtCPUTopology(s, c, t))
|
||||
LOG.debug("Got %d possible topologies", len(possible))
|
||||
if len(possible) == 0:
|
||||
raise exception.ImageVCPULimitsRangeImpossible(vcpus=vcpus,
|
||||
sockets=maxsockets,
|
||||
cores=maxcores,
|
||||
threads=maxthreads)
|
||||
|
||||
# We want to
|
||||
# - 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))
|
||||
return possible
|
||||
|
||||
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
|
||||
def sort_possible_topologies(possible, wanttopology):
|
||||
"""Sort the topologies in order of preference
|
||||
:param possible: list of VirtCPUTopology instances
|
||||
:param wanttopology: VirtCPUTopology for preferred topology
|
||||
This takes the list of possible topologies and resorts
|
||||
it such that those configurations which most closely
|
||||
match the preferred topology are first.
|
||||
|
||||
This takes the list of possible topologies and resorts
|
||||
it such that those configurations which most closely
|
||||
match the preferred topology are first.
|
||||
:returns: sorted list of nova.objects.VirtCPUTopology instances
|
||||
"""
|
||||
|
||||
: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
|
||||
# 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 = topology.score(wanttopology)
|
||||
scores[score].append(topology)
|
||||
# Build list of all possible topologies sorted
|
||||
# by the match score, best match first
|
||||
desired = []
|
||||
desired.extend(scores[3])
|
||||
desired.extend(scores[2])
|
||||
desired.extend(scores[1])
|
||||
desired.extend(scores[0])
|
||||
|
||||
# Build list of all possible topologies sorted
|
||||
# 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
|
||||
|
||||
return desired
|
||||
|
||||
@staticmethod
|
||||
def get_desirable_configs(flavor, image_meta, allow_threads=True):
|
||||
"""Get desired CPU topologies according to settings
|
||||
def _get_desirable_cpu_topologies(flavor, image_meta, allow_threads=True):
|
||||
"""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 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
|
||||
|
||||
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 this list sorted in order of preference.
|
||||
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 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 "
|
||||
"and image_meta %(image_meta)s",
|
||||
{"flavor": flavor, "image_meta": image_meta})
|
||||
LOG.debug("Getting desirable topologies for flavor %(flavor)s "
|
||||
"and image_meta %(image_meta)s",
|
||||
{"flavor": flavor, "image_meta": image_meta})
|
||||
|
||||
preferred, maximum = (
|
||||
VirtCPUTopology.get_topology_constraints(flavor,
|
||||
image_meta))
|
||||
preferred, maximum = _get_cpu_topology_constraints(flavor, image_meta)
|
||||
|
||||
possible = VirtCPUTopology.get_possible_topologies(
|
||||
flavor.vcpus, maximum, allow_threads)
|
||||
desired = VirtCPUTopology.sort_possible_topologies(
|
||||
possible, preferred)
|
||||
possible = _get_possible_cpu_topologies(flavor.vcpus,
|
||||
maximum,
|
||||
allow_threads)
|
||||
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
|
||||
:param image_meta: ImageMeta object to query properties from
|
||||
:param allow_threads: if the hypervisor supports CPU threads
|
||||
def get_best_cpu_topology(flavor, image_meta, allow_threads=True):
|
||||
"""Get best CPU topology according to settings
|
||||
|
||||
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
|
||||
: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
|
||||
|
||||
: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,
|
||||
image_meta,
|
||||
allow_threads)[0]
|
||||
:returns: a nova.objects.VirtCPUTopology instance for best topology
|
||||
"""
|
||||
|
||||
return _get_desirable_cpu_topologies(flavor, image_meta, allow_threads)[0]
|
||||
|
||||
|
||||
class VirtPageSize(object):
|
||||
|
|
|
@ -3392,8 +3392,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
if cpu is None:
|
||||
return None
|
||||
|
||||
topology = hardware.VirtCPUTopology.get_best_config(flavor,
|
||||
image)
|
||||
topology = hardware.get_best_cpu_topology(flavor, image)
|
||||
|
||||
cpu.sockets = topology.sockets
|
||||
cpu.cores = topology.cores
|
||||
|
|
Loading…
Reference in New Issue