Convert hardware.VirtCPUTopology to nova object

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -375,7 +375,7 @@ class VCPUTopologyTest(test.NoDBTestCase):
for topo_test in testdata:
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]

View File

@ -215,28 +215,11 @@ class InstanceInfo(object):
self.__dict__ == other.__dict__)
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):
def _score_cpu_topology(topology, wanttopology):
"""Calculate score for the topology against a desired configuration
:param wanttopology: VirtCPUTopology instance for preferred topology
:param wanttopology: nova.objects.VirtCPUTopology instance for
preferred topology
Calculate a score indicating how well this topology
matches against a preferred topology. A score of 3
@ -251,18 +234,18 @@ class VirtCPUTopology(object):
score = 0
if (wanttopology.sockets != -1 and
self.sockets == wanttopology.sockets):
topology.sockets == wanttopology.sockets):
score = score + 1
if (wanttopology.cores != -1 and
self.cores == wanttopology.cores):
topology.cores == wanttopology.cores):
score = score + 1
if (wanttopology.threads != -1 and
self.threads == wanttopology.threads):
topology.threads == wanttopology.threads):
score = score + 1
return score
@staticmethod
def get_topology_constraints(flavor, image_meta):
def _get_cpu_topology_constraints(flavor, image_meta):
"""Get the topology constraints declared in flavor or image
:param flavor: Flavor object to read extra specs from
@ -291,7 +274,7 @@ class VirtCPUTopology(object):
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,
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.
@ -407,26 +390,28 @@ class VirtCPUTopology(object):
"threads": threads, "maxsockets": maxsockets,
"maxcores": maxcores, "maxthreads": maxthreads})
return (VirtCPUTopology(sockets, cores, threads),
VirtCPUTopology(maxsockets, maxcores, maxthreads))
return (objects.VirtCPUTopology(sockets=sockets, cores=cores,
threads=threads),
objects.VirtCPUTopology(sockets=maxsockets, cores=maxcores,
threads=maxthreads))
@staticmethod
def get_possible_topologies(vcpus, maxtopology, allow_threads):
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: VirtCPUTopology for upper limits
:param maxtopology: nova.objects.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
list of nova.objects.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
:returns: list of nova.objects.VirtCPUTopology instances
"""
# Clamp limits to number of vcpus to prevent
@ -454,7 +439,10 @@ class VirtCPUTopology(object):
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))
o = objects.VirtCPUTopology(sockets=s, cores=c,
threads=t)
possible.append(o)
# We want to
# - Minimize threads (ie larger sockets * cores is best)
@ -473,17 +461,18 @@ class VirtCPUTopology(object):
return possible
@staticmethod
def sort_possible_topologies(possible, wanttopology):
def _sort_possible_cpu_topologies(possible, wanttopology):
"""Sort the topologies in order of preference
:param possible: list of VirtCPUTopology instances
:param wanttopology: VirtCPUTopology for preferred topology
:param possible: list of nova.objects.VirtCPUTopology instances
:param wanttopology: nova.objects.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
:returns: sorted list of nova.objects.VirtCPUTopology instances
"""
# Look at possible topologies and score them according
@ -493,7 +482,7 @@ class VirtCPUTopology(object):
# 'possible' list originally
scores = collections.defaultdict(list)
for topology in possible:
score = topology.score(wanttopology)
score = _score_cpu_topology(topology, wanttopology)
scores[score].append(topology)
# Build list of all possible topologies sorted
@ -506,8 +495,8 @@ class VirtCPUTopology(object):
return desired
@staticmethod
def get_desirable_configs(flavor, image_meta, allow_threads=True):
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
@ -519,27 +508,25 @@ class VirtCPUTopology(object):
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})
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
@staticmethod
def get_best_config(flavor, image_meta, allow_threads=True):
"""Get bst CPU topology according to settings
def get_best_cpu_topology(flavor, image_meta, allow_threads=True):
"""Get best CPU topology according to settings
:param flavor: Flavor object to query extra specs from
:param image_meta: ImageMeta object to query properties from
@ -550,12 +537,10 @@ class VirtCPUTopology(object):
valid CPU topologies that can be used in the guest. Then
return the best topology to use
:returns: a VirtCPUTopology instance for best topology
:returns: a nova.objects.VirtCPUTopology instance for best topology
"""
return VirtCPUTopology.get_desirable_configs(flavor,
image_meta,
allow_threads)[0]
return _get_desirable_cpu_topologies(flavor, image_meta, allow_threads)[0]
class VirtPageSize(object):

View File

@ -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