diff --git a/nova/exception.py b/nova/exception.py index 1ede2c87a640..c383ef128e1a 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -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") diff --git a/nova/tests/virt/test_hardware.py b/nova/tests/virt/test_hardware.py new file mode 100644 index 000000000000..95e95472a32d --- /dev/null +++ b/nova/tests/virt/test_hardware.py @@ -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) diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py new file mode 100644 index 000000000000..300ef601307d --- /dev/null +++ b/nova/virt/hardware.py @@ -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]