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')
|
||||||
__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')
|
||||||
|
|
|
@ -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',
|
'TestSubclassedObject': '1.6-c63feb2f2533b7d075490c04a2cc10dd',
|
||||||
'VirtualInterface': '1.0-10fdac4c704102b6d57d6936d6d790d2',
|
'VirtualInterface': '1.0-10fdac4c704102b6d57d6936d6d790d2',
|
||||||
'VirtualInterfaceList': '1.0-accbf02628a8063c1d885077a2bf49b6',
|
'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:
|
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]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue