Merge "virt: add helper module for determining VCPU topology"
This commit is contained in:
commit
83041380c8
|
@ -1583,3 +1583,18 @@ class NoBlockMigrationForConfigDriveInLibVirt(NovaException):
|
|||
|
||||
class UnshelveException(NovaException):
|
||||
msg_fmt = _("Error during unshelve instance %(instance_id)s: %(reason)s")
|
||||
|
||||
|
||||
class ImageVCPULimitsRangeExceeded(Invalid):
|
||||
msg_fmt = _("Image vCPU limits %(sockets)d:%(cores)d:%(threads)d "
|
||||
"exceeds permitted %(maxsockets)d:%(maxcores)d:%(maxthreads)d")
|
||||
|
||||
|
||||
class ImageVCPUTopologyRangeExceeded(Invalid):
|
||||
msg_fmt = _("Image vCPU topology %(sockets)d:%(cores)d:%(threads)d "
|
||||
"exceeds permitted %(maxsockets)d:%(maxcores)d:%(maxthreads)d")
|
||||
|
||||
|
||||
class ImageVCPULimitsRangeImpossible(Invalid):
|
||||
msg_fmt = _("Requested vCPU limits %(sockets)d:%(cores)d:%(threads)d "
|
||||
"are impossible to satisfy for vcpus count %(vcpus)d")
|
||||
|
|
|
@ -0,0 +1,496 @@
|
|||
# Copyright 2014 Red Hat, Inc.
|
||||
#
|
||||
# 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 exception
|
||||
from nova import test
|
||||
from nova.virt import hardware as hw
|
||||
|
||||
|
||||
class FakeFlavor():
|
||||
def __init__(self, vcpus, extra_specs):
|
||||
self.vcpus = vcpus
|
||||
self.extra_specs = extra_specs
|
||||
|
||||
|
||||
class VCPUTopologyTest(test.NoDBTestCase):
|
||||
|
||||
def test_validate_config(self):
|
||||
testdata = [
|
||||
{ # Flavor sets preferred topology only
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "8",
|
||||
"hw:cpu_cores": "2",
|
||||
"hw:cpu_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {}
|
||||
},
|
||||
"expect": (
|
||||
8, 2, 1, 65536, 65536, 65536
|
||||
)
|
||||
},
|
||||
{ # Image topology overrides flavor
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "8",
|
||||
"hw:cpu_cores": "2",
|
||||
"hw:cpu_threads": "1",
|
||||
"hw:cpu_max_threads": "2",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_sockets": "4",
|
||||
"hw_cpu_cores": "2",
|
||||
"hw_cpu_threads": "2",
|
||||
}
|
||||
},
|
||||
"expect": (
|
||||
4, 2, 2, 65536, 65536, 2,
|
||||
)
|
||||
},
|
||||
{ # Partial image topology overrides flavor
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "8",
|
||||
"hw:cpu_cores": "2",
|
||||
"hw:cpu_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_sockets": "2",
|
||||
}
|
||||
},
|
||||
"expect": (
|
||||
2, -1, -1, 65536, 65536, 65536,
|
||||
)
|
||||
},
|
||||
{ # Restrict use of threads
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_max_threads": "2",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_max_threads": "1",
|
||||
}
|
||||
},
|
||||
"expect": (
|
||||
-1, -1, -1, 65536, 65536, 1,
|
||||
)
|
||||
},
|
||||
{ # Force use of at least two sockets
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_max_cores": "8",
|
||||
"hw:cpu_max_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {}
|
||||
},
|
||||
"expect": (
|
||||
-1, -1, -1, 65536, 8, 1
|
||||
)
|
||||
},
|
||||
{ # Image limits reduce flavor
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_max_cores": "8",
|
||||
"hw:cpu_max_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_max_cores": "4",
|
||||
}
|
||||
},
|
||||
"expect": (
|
||||
-1, -1, -1, 65536, 4, 1
|
||||
)
|
||||
},
|
||||
{ # Image limits kill flavor preferred
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "2",
|
||||
"hw:cpu_cores": "8",
|
||||
"hw:cpu_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_max_cores": "4",
|
||||
}
|
||||
},
|
||||
"expect": (
|
||||
-1, -1, -1, 65536, 4, 65536
|
||||
)
|
||||
},
|
||||
{ # Image limits cannot exceed flavor
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_max_cores": "8",
|
||||
"hw:cpu_max_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_max_cores": "16",
|
||||
}
|
||||
},
|
||||
"expect": exception.ImageVCPULimitsRangeExceeded,
|
||||
},
|
||||
{ # Image preferred cannot exceed flavor
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_max_cores": "8",
|
||||
"hw:cpu_max_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_cores": "16",
|
||||
}
|
||||
},
|
||||
"expect": exception.ImageVCPUTopologyRangeExceeded,
|
||||
},
|
||||
]
|
||||
|
||||
for test in testdata:
|
||||
if type(test["expect"]) == tuple:
|
||||
(preferred,
|
||||
maximum) = hw.VirtCPUTopology.get_topology_constraints(
|
||||
test["flavor"],
|
||||
test["image"])
|
||||
|
||||
self.assertEqual(test["expect"][0], preferred.sockets)
|
||||
self.assertEqual(test["expect"][1], preferred.cores)
|
||||
self.assertEqual(test["expect"][2], preferred.threads)
|
||||
self.assertEqual(test["expect"][3], maximum.sockets)
|
||||
self.assertEqual(test["expect"][4], maximum.cores)
|
||||
self.assertEqual(test["expect"][5], maximum.threads)
|
||||
else:
|
||||
self.assertRaises(test["expect"],
|
||||
hw.VirtCPUTopology.get_topology_constraints,
|
||||
test["flavor"],
|
||||
test["image"])
|
||||
|
||||
def test_possible_configs(self):
|
||||
testdata = [
|
||||
{
|
||||
"allow_threads": True,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 8,
|
||||
"maxcores": 8,
|
||||
"maxthreads": 2,
|
||||
"expect": [
|
||||
[8, 1, 1],
|
||||
[4, 2, 1],
|
||||
[2, 4, 1],
|
||||
[1, 8, 1],
|
||||
[4, 1, 2],
|
||||
[2, 2, 2],
|
||||
[1, 4, 2],
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": False,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 8,
|
||||
"maxcores": 8,
|
||||
"maxthreads": 2,
|
||||
"expect": [
|
||||
[8, 1, 1],
|
||||
[4, 2, 1],
|
||||
[2, 4, 1],
|
||||
[1, 8, 1],
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": True,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 1024,
|
||||
"maxcores": 1024,
|
||||
"maxthreads": 2,
|
||||
"expect": [
|
||||
[8, 1, 1],
|
||||
[4, 2, 1],
|
||||
[2, 4, 1],
|
||||
[1, 8, 1],
|
||||
[4, 1, 2],
|
||||
[2, 2, 2],
|
||||
[1, 4, 2],
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": True,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 1024,
|
||||
"maxcores": 1,
|
||||
"maxthreads": 2,
|
||||
"expect": [
|
||||
[8, 1, 1],
|
||||
[4, 1, 2],
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": True,
|
||||
"vcpus": 7,
|
||||
"maxsockets": 8,
|
||||
"maxcores": 8,
|
||||
"maxthreads": 2,
|
||||
"expect": [
|
||||
[7, 1, 1],
|
||||
[1, 7, 1],
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": True,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 2,
|
||||
"maxcores": 1,
|
||||
"maxthreads": 1,
|
||||
"expect": exception.ImageVCPULimitsRangeImpossible,
|
||||
},
|
||||
{
|
||||
"allow_threads": False,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 2,
|
||||
"maxcores": 1,
|
||||
"maxthreads": 4,
|
||||
"expect": exception.ImageVCPULimitsRangeImpossible,
|
||||
},
|
||||
]
|
||||
|
||||
for test in testdata:
|
||||
if type(test["expect"]) == list:
|
||||
actual = []
|
||||
for topology in hw.VirtCPUTopology.get_possible_topologies(
|
||||
test["vcpus"],
|
||||
hw.VirtCPUTopology(test["maxsockets"],
|
||||
test["maxcores"],
|
||||
test["maxthreads"]),
|
||||
test["allow_threads"]):
|
||||
actual.append([topology.sockets,
|
||||
topology.cores,
|
||||
topology.threads])
|
||||
|
||||
self.assertEqual(test["expect"], actual)
|
||||
else:
|
||||
self.assertRaises(test["expect"],
|
||||
hw.VirtCPUTopology.get_possible_topologies,
|
||||
test["vcpus"],
|
||||
hw.VirtCPUTopology(test["maxsockets"],
|
||||
test["maxcores"],
|
||||
test["maxthreads"]),
|
||||
test["allow_threads"])
|
||||
|
||||
def test_sorting_configs(self):
|
||||
testdata = [
|
||||
{
|
||||
"allow_threads": True,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 8,
|
||||
"maxcores": 8,
|
||||
"maxthreads": 2,
|
||||
"sockets": 4,
|
||||
"cores": 2,
|
||||
"threads": 1,
|
||||
"expect": [
|
||||
[4, 2, 1], # score = 2
|
||||
[8, 1, 1], # score = 1
|
||||
[2, 4, 1], # score = 1
|
||||
[1, 8, 1], # score = 1
|
||||
[4, 1, 2], # score = 1
|
||||
[2, 2, 2], # score = 1
|
||||
[1, 4, 2], # score = 1
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": True,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 1024,
|
||||
"maxcores": 1024,
|
||||
"maxthreads": 2,
|
||||
"sockets": -1,
|
||||
"cores": 4,
|
||||
"threads": -1,
|
||||
"expect": [
|
||||
[2, 4, 1], # score = 1
|
||||
[1, 4, 2], # score = 1
|
||||
[8, 1, 1], # score = 0
|
||||
[4, 2, 1], # score = 0
|
||||
[1, 8, 1], # score = 0
|
||||
[4, 1, 2], # score = 0
|
||||
[2, 2, 2], # score = 0
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": True,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 1024,
|
||||
"maxcores": 1,
|
||||
"maxthreads": 2,
|
||||
"sockets": -1,
|
||||
"cores": -1,
|
||||
"threads": 2,
|
||||
"expect": [
|
||||
[4, 1, 2], # score = 1
|
||||
[8, 1, 1], # score = 0
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_threads": False,
|
||||
"vcpus": 8,
|
||||
"maxsockets": 1024,
|
||||
"maxcores": 1,
|
||||
"maxthreads": 2,
|
||||
"sockets": -1,
|
||||
"cores": -1,
|
||||
"threads": 2,
|
||||
"expect": [
|
||||
[8, 1, 1], # score = 0
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
for test in testdata:
|
||||
actual = []
|
||||
possible = hw.VirtCPUTopology.get_possible_topologies(
|
||||
test["vcpus"],
|
||||
hw.VirtCPUTopology(test["maxsockets"],
|
||||
test["maxcores"],
|
||||
test["maxthreads"]),
|
||||
test["allow_threads"])
|
||||
|
||||
tops = hw.VirtCPUTopology.sort_possible_topologies(
|
||||
possible,
|
||||
hw.VirtCPUTopology(test["sockets"],
|
||||
test["cores"],
|
||||
test["threads"]))
|
||||
for topology in tops:
|
||||
actual.append([topology.sockets,
|
||||
topology.cores,
|
||||
topology.threads])
|
||||
|
||||
self.assertEqual(test["expect"], actual)
|
||||
|
||||
def test_best_config(self):
|
||||
testdata = [
|
||||
{ # Flavor sets preferred topology only
|
||||
"allow_threads": True,
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "8",
|
||||
"hw:cpu_cores": "2",
|
||||
"hw:cpu_threads": "1"
|
||||
}),
|
||||
"image": {
|
||||
"properties": {}
|
||||
},
|
||||
"expect": [8, 2, 1],
|
||||
},
|
||||
{ # Image topology overrides flavor
|
||||
"allow_threads": True,
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "8",
|
||||
"hw:cpu_cores": "2",
|
||||
"hw:cpu_threads": "1",
|
||||
"hw:cpu_maxthreads": "2",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_sockets": "4",
|
||||
"hw_cpu_cores": "2",
|
||||
"hw_cpu_threads": "2",
|
||||
}
|
||||
},
|
||||
"expect": [4, 2, 2],
|
||||
},
|
||||
{ # Image topology overrides flavor
|
||||
"allow_threads": False,
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "8",
|
||||
"hw:cpu_cores": "2",
|
||||
"hw:cpu_threads": "1",
|
||||
"hw:cpu_maxthreads": "2",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_sockets": "4",
|
||||
"hw_cpu_cores": "2",
|
||||
"hw_cpu_threads": "2",
|
||||
}
|
||||
},
|
||||
"expect": [8, 2, 1],
|
||||
},
|
||||
{ # Partial image topology overrides flavor
|
||||
"allow_threads": True,
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "8",
|
||||
"hw:cpu_cores": "2",
|
||||
"hw:cpu_threads": "1"
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_sockets": "2"
|
||||
}
|
||||
},
|
||||
"expect": [2, 8, 1],
|
||||
},
|
||||
{ # Restrict use of threads
|
||||
"allow_threads": True,
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_max_threads": "1"
|
||||
}),
|
||||
"image": {
|
||||
"properties": {}
|
||||
},
|
||||
"expect": [16, 1, 1]
|
||||
},
|
||||
{ # Force use of at least two sockets
|
||||
"allow_threads": True,
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_max_cores": "8",
|
||||
"hw:cpu_max_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {}
|
||||
},
|
||||
"expect": [16, 1, 1]
|
||||
},
|
||||
{ # Image limits reduce flavor
|
||||
"allow_threads": True,
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_max_sockets": "8",
|
||||
"hw:cpu_max_cores": "8",
|
||||
"hw:cpu_max_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_max_sockets": 4,
|
||||
}
|
||||
},
|
||||
"expect": [4, 4, 1]
|
||||
},
|
||||
{ # Image limits kill flavor preferred
|
||||
"allow_threads": True,
|
||||
"flavor": FakeFlavor(16, {
|
||||
"hw:cpu_sockets": "2",
|
||||
"hw:cpu_cores": "8",
|
||||
"hw:cpu_threads": "1",
|
||||
}),
|
||||
"image": {
|
||||
"properties": {
|
||||
"hw_cpu_max_cores": 4,
|
||||
}
|
||||
},
|
||||
"expect": [16, 1, 1]
|
||||
},
|
||||
]
|
||||
|
||||
for test in testdata:
|
||||
topology = hw.VirtCPUTopology.get_desirable_configs(
|
||||
test["flavor"],
|
||||
test["image"],
|
||||
test["allow_threads"])[0]
|
||||
|
||||
self.assertEqual(test["expect"][0], topology.sockets)
|
||||
self.assertEqual(test["expect"][1], topology.cores)
|
||||
self.assertEqual(test["expect"][2], topology.threads)
|
|
@ -0,0 +1,363 @@
|
|||
# Copyright 2014 Red Hat, Inc
|
||||
#
|
||||
# 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.
|
||||
|
||||
import collections
|
||||
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VirtCPUTopology(object):
|
||||
|
||||
def __init__(self, sockets, cores, threads):
|
||||
"""Create a new CPU topology object
|
||||
|
||||
:param sockets: number of sockets, at least 1
|
||||
:param cores: number of cores, at least 1
|
||||
:param threads: number of threads, at least 1
|
||||
|
||||
Create a new CPU topology object representing the
|
||||
number of sockets, cores and threads to use for
|
||||
the virtual instance.
|
||||
"""
|
||||
|
||||
self.sockets = sockets
|
||||
self.cores = cores
|
||||
self.threads = threads
|
||||
|
||||
def score(self, wanttopology):
|
||||
"""Calculate score for the topology against a desired configuration
|
||||
|
||||
:param wanttopology: VirtCPUTopology instance for preferred topology
|
||||
|
||||
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
|
||||
|
||||
:returns: score in range 0 (worst) to 3 (best)
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def get_topology_constraints(flavor, image_meta):
|
||||
"""Get the topology constraints declared in flavour or image
|
||||
|
||||
:param flavor: Flavor object to read extra specs from
|
||||
:param image_meta: Image object to read image metadata from
|
||||
|
||||
Gets the topology constraints from the configuration defined
|
||||
in the flavor extra specs or the image metadata. In the flavor
|
||||
this will look for
|
||||
|
||||
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
|
||||
|
||||
In the image metadata this will look at
|
||||
|
||||
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
|
||||
|
||||
The image metadata must be strictly lower than any values
|
||||
set in the flavor. All values are, however, optional.
|
||||
|
||||
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.
|
||||
|
||||
exception.ImageVCPULimitsRangeExceeded will be raised
|
||||
if the maximum counts set against the image exceed
|
||||
the maximum counts set against the flavor
|
||||
|
||||
exception.ImageVCPUTopologyRangeExceeded will be raised
|
||||
if the preferred counts set against the image exceed
|
||||
the maximum counts set against the image or flavor
|
||||
|
||||
:returns: (preferred topology, maximum topology)
|
||||
"""
|
||||
|
||||
# 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))
|
||||
|
||||
LOG.debug("Flavor limits %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": flvmaxsockets,
|
||||
"cores": flvmaxcores,
|
||||
"threads": flvmaxthreads})
|
||||
|
||||
# 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("Image limits %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": maxsockets,
|
||||
"cores": maxcores,
|
||||
"threads": maxthreads})
|
||||
|
||||
# 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)
|
||||
|
||||
# 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("Flavor pref %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": flvsockets,
|
||||
"cores": flvcores,
|
||||
"threads": flvthreads})
|
||||
|
||||
# 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
|
||||
|
||||
# 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("Image pref %(sockets)d:%(cores)d:%(threads)d",
|
||||
{"sockets": sockets,
|
||||
"cores": cores,
|
||||
"threads": threads})
|
||||
|
||||
# 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
|
||||
|
||||
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})
|
||||
|
||||
return (VirtCPUTopology(sockets, cores, threads),
|
||||
VirtCPUTopology(maxsockets, maxcores, maxthreads))
|
||||
|
||||
@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
|
||||
|
||||
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.
|
||||
|
||||
exception.ImageVCPULimitsRangeImpossible is raised if
|
||||
it is impossible to achieve the total vcpu count given
|
||||
the maximum limits on sockets, cores & threads.
|
||||
|
||||
:returns: list of VirtCPUTopology instances
|
||||
"""
|
||||
|
||||
# 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)
|
||||
|
||||
if not allow_threads:
|
||||
maxthreads = 1
|
||||
|
||||
LOG.debug("Build topologies for %(vcpus)d vcpu(s) "
|
||||
"%(maxsockets)d:%(maxcores)d:%(maxthreads)d",
|
||||
{"vcpus": vcpus, "maxsockets": maxsockets,
|
||||
"maxcores": maxcores, "maxthreads": maxthreads})
|
||||
|
||||
# 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))
|
||||
|
||||
# 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))
|
||||
|
||||
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
|
||||
|
||||
@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.
|
||||
|
||||
: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 = 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])
|
||||
|
||||
return desired
|
||||
|
||||
@staticmethod
|
||||
def get_desirable_configs(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
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
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))
|
||||
|
||||
possible = VirtCPUTopology.get_possible_topologies(
|
||||
flavor.vcpus, maximum, allow_threads)
|
||||
desired = VirtCPUTopology.sort_possible_topologies(
|
||||
possible, preferred)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
:returns: a VirtCPUTopology instance for best topology
|
||||
"""
|
||||
|
||||
return VirtCPUTopology.get_desirable_configs(flavor,
|
||||
image_meta,
|
||||
allow_threads)[0]
|
Loading…
Reference in New Issue