Add pci_device_pools to ComputeNode object

The compute_nodes table has a field called pci_stats
that is used to pass pci device availability to the
scheduler. The pci device data is obtained by the virt
drivers and passed to the resource tracker in the available
resource data. The resource tracker creates pci device pools
to track the number of similar devices as a resource.

The pci_stats field contains the pci pool data.

A new object versioned called PciDevicePool has been created
for the pci device pool data. The pci_device_pools field is added
to the ComputeNode object as a PciDevicePoolList object.
The type of the field is:

    ObjectField('PciDevicePoolList')

The format that the PciDevicePool object uses to store data in the
database is made backward compatible with the existing format.

There was an alternative patch by yjiang5 that does not version
the data in the field, this patch replaces that one. The original
can be found here Ifde89b209f2fcc2283894195b1e7e866cf28dccb

Co-Authored-By: Paul Murray <pmurray@hp.com>
Co-Authored-By: yunhong-jiang <yunhong-jiang@intel.com>
Co-Authored-By: Ed Leafe <ed@leafe.com>

Change-Id: I625f13061fddfb0cdb16c8e36336656b40696447
This commit is contained in:
Paul Murray 2014-11-28 19:30:22 +00:00
parent fec5ff1294
commit 6fbf84b8a4
9 changed files with 291 additions and 19 deletions

View File

@ -49,6 +49,7 @@ def register_all():
__import__('nova.objects.network_request')
__import__('nova.objects.numa')
__import__('nova.objects.pci_device')
__import__('nova.objects.pci_device_pool')
__import__('nova.objects.tag')
__import__('nova.objects.quotas')
__import__('nova.objects.security_group')

View File

@ -19,6 +19,7 @@ from nova import exception
from nova import objects
from nova.objects import base
from nova.objects import fields
from nova.objects import pci_device_pool
from nova import utils
@ -32,7 +33,8 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
# Version 1.6: Added supported_hv_specs
# Version 1.7: Added host field
# Version 1.8: Added get_by_host_and_nodename()
VERSION = '1.8'
# Version 1.9: Added pci_device_pools
VERSION = '1.9'
fields = {
'id': fields.IntegerField(read_only=True),
@ -60,9 +62,14 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
# NOTE(pmurray): the supported_hv_specs field maps to the
# supported_instances field in the database
'supported_hv_specs': fields.ListOfObjectsField('HVSpec'),
# NOTE(pmurray): the pci_device_pools field maps to the
# pci_stats field in the database
'pci_device_pools': fields.ObjectField('PciDevicePoolList',
nullable=True),
}
obj_relationships = {
'pci_device_pools': [('1.9', '1.0')],
'supported_hv_specs': [('1.6', '1.0')],
}
@ -110,9 +117,13 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
@staticmethod
def _from_db_object(context, compute, db_compute):
fields = set(compute.fields) - set(['stats', 'supported_hv_specs',
'host'])
special_cases = set([
'stats',
'supported_hv_specs',
'host',
'pci_device_pools',
])
fields = set(compute.fields) - special_cases
for key in fields:
compute[key] = db_compute[key]
@ -127,6 +138,8 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
for hv_spec in hv_specs]
compute['supported_hv_specs'] = hv_specs
pci_stats = db_compute.get('pci_stats')
compute.pci_device_pools = pci_device_pool.from_pci_stats(pci_stats)
compute._context = context
# Make sure that we correctly set the host field depending on either
@ -165,22 +178,31 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
db_compute['host'] = service.host
return cls._from_db_object(context, cls(), db_compute)
def _convert_stats_to_db_format(self, updates):
@staticmethod
def _convert_stats_to_db_format(updates):
stats = updates.pop('stats', None)
if stats is not None:
updates['stats'] = jsonutils.dumps(stats)
def _convert_host_ip_to_db_format(self, updates):
@staticmethod
def _convert_host_ip_to_db_format(updates):
host_ip = updates.pop('host_ip', None)
if host_ip:
updates['host_ip'] = str(host_ip)
def _convert_supported_instances_to_db_format(selfself, updates):
@staticmethod
def _convert_supported_instances_to_db_format(updates):
hv_specs = updates.pop('supported_hv_specs', None)
if hv_specs is not None:
hv_specs = [hv_spec.to_list() for hv_spec in hv_specs]
updates['supported_instances'] = jsonutils.dumps(hv_specs)
@staticmethod
def _convert_pci_stats_to_db_format(updates):
pools = updates.pop('pci_device_pools', None)
if pools:
updates['pci_stats'] = jsonutils.dumps(pools.obj_to_primitive())
@base.remotable
def create(self, context):
if self.obj_attr_is_set('id'):
@ -190,6 +212,7 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
self._convert_stats_to_db_format(updates)
self._convert_host_ip_to_db_format(updates)
self._convert_supported_instances_to_db_format(updates)
self._convert_pci_stats_to_db_format(updates)
db_compute = db.compute_node_create(context, updates)
self._from_db_object(context, self, db_compute)
@ -203,6 +226,7 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
self._convert_stats_to_db_format(updates)
self._convert_host_ip_to_db_format(updates)
self._convert_supported_instances_to_db_format(updates)
self._convert_pci_stats_to_db_format(updates)
db_compute = db.compute_node_update(context, self.id, updates)
self._from_db_object(context, self, db_compute)
@ -230,7 +254,8 @@ class ComputeNodeList(base.ObjectListBase, base.NovaObject):
# Version 1.6 ComputeNode version 1.6
# Version 1.7 ComputeNode version 1.7
# Version 1.8 ComputeNode version 1.8 + add get_all_by_host()
VERSION = '1.8'
# Version 1.9 ComputeNode version 1.9
VERSION = '1.9'
fields = {
'objects': fields.ListOfObjectsField('ComputeNode'),
}
@ -245,6 +270,7 @@ class ComputeNodeList(base.ObjectListBase, base.NovaObject):
'1.6': '1.6',
'1.7': '1.7',
'1.8': '1.8',
'1.9': '1.9',
}
@base.remotable_classmethod

View File

@ -0,0 +1,87 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# 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 copy
from oslo.serialization import jsonutils
import six
from nova import objects
from nova.objects import base
from nova.objects import fields
class PciDevicePool(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'product_id': fields.StringField(),
'vendor_id': fields.StringField(),
'tags': fields.DictOfNullableStringsField(),
'count': fields.IntegerField(),
}
# NOTE(pmurray): before this object existed the pci device pool data was
# stored as a dict. For backward compatibility we need to be able to read
# it in from a dict
@classmethod
def from_dict(cls, value):
pool_dict = copy.copy(value)
pool = cls()
pool.vendor_id = pool_dict.pop("vendor_id")
pool.product_id = pool_dict.pop("product_id")
pool.count = pool_dict.pop("count")
pool.tags = {}
pool.tags.update(pool_dict)
return pool
class PciDevicePoolList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial verison
# PciDevicePool <= 1.0
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('PciDevicePool'),
}
child_versions = {
'1.0': '1.0',
}
def from_pci_stats(pci_stats):
"""Create and return a PciDevicePoolList from the data stored in the db,
which can be either the serialized object, or, prior to the creation of the
device pool objects, a simple dict or a list of such dicts.
"""
pools = None
if isinstance(pci_stats, six.string_types):
try:
pci_stats = jsonutils.loads(pci_stats)
except (ValueError, TypeError):
pci_stats = None
if pci_stats:
# Check for object-ness, or old-style storage format.
if 'nova_object.namespace' in pci_stats:
pools = objects.PciDevicePoolList.obj_from_primitive(pci_stats)
else:
# This can be either a dict or a list of dicts
if isinstance(pci_stats, list):
pool_list = [objects.PciDevicePool.from_dict(stat)
for stat in pci_stats]
else:
pool_list = [objects.PciDevicePool.from_dict(pci_stats)]
pools = objects.PciDevicePoolList(objects=pool_list)
return pools

View File

@ -33,7 +33,8 @@ class Service(base.NovaPersistentObject, base.NovaObject):
# Version 1.5: ComputeNode version 1.6
# Version 1.6: ComputeNode version 1.7
# Version 1.7: ComputeNode version 1.8
VERSION = '1.7'
# Version 1.8: ComputeNode version 1.9
VERSION = '1.8'
fields = {
'id': fields.IntegerField(read_only=True),
@ -49,7 +50,7 @@ class Service(base.NovaPersistentObject, base.NovaObject):
obj_relationships = {
'compute_node': [('1.1', '1.4'), ('1.3', '1.5'), ('1.5', '1.6'),
('1.7', '1.8')],
('1.7', '1.8'), ('1.8', '1.9')],
}
@staticmethod
@ -143,7 +144,8 @@ class ServiceList(base.ObjectListBase, base.NovaObject):
# Version 1.3: Service version 1.5
# Version 1.4: Service version 1.6
# Version 1.5: Service version 1.7
VERSION = '1.5'
# Version 1.6: Service version 1.8
VERSION = '1.6'
fields = {
'objects': fields.ListOfObjectsField('Service'),
@ -156,6 +158,7 @@ class ServiceList(base.ObjectListBase, base.NovaObject):
'1.3': '1.5',
'1.4': '1.6',
'1.5': '1.7',
'1.6': '1.8',
}
@base.remotable_classmethod

View File

@ -0,0 +1,36 @@
# Copyright 2014 IBM Corp.
# All Rights Reserved.
#
# 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 pci_device_pool
# This represents the format that PCI device pool info was stored in the DB
# before this info was made into objects.
fake_pool_dict = {
'product_id': 'fake-product',
'vendor_id': 'fake-vendor',
't1': 'v1',
't2': 'v2',
'count': 2,
}
fake_pool = pci_device_pool.PciDevicePool(count=5,
product_id='foo',
vendor_id='bar',
tags={'t1': 'v1', 't2': 'v2'})
fake_pool_primitive = fake_pool.obj_to_primitive()
fake_pool_list = pci_device_pool.PciDevicePoolList(objects=[fake_pool])
fake_pool_list_primitive = fake_pool_list.obj_to_primitive()

View File

@ -22,6 +22,7 @@ from nova import objects
from nova.objects import compute_node
from nova.objects import hv_spec
from nova.objects import service
from nova.tests.unit import fake_pci_device_pools
from nova.tests.unit.objects import test_objects
NOW = timeutils.utcnow().replace(microsecond=0)
@ -41,6 +42,7 @@ fake_supported_hv_specs = [fake_hv_spec]
# for backward compatibility, each supported instance object
# is stored as a list in the database
fake_supported_hv_specs_db_format = jsonutils.dumps([fake_hv_spec.to_list()])
fake_pci = jsonutils.dumps(fake_pci_device_pools.fake_pool_list_primitive)
fake_compute_node = {
'created_at': NOW,
'updated_at': None,
@ -69,6 +71,7 @@ fake_compute_node = {
'host_ip': fake_host_ip,
'numa_topology': fake_numa_topology_db_format,
'supported_instances': fake_supported_hv_specs_db_format,
'pci_stats': fake_pci,
}
# FIXME(sbauza) : For compatibility checking, to be removed once we are sure
# that all computes are running latest DB version with host field in it.
@ -81,13 +84,26 @@ class _TestComputeNodeObject(object):
obj_val = [inst.to_list() for inst in obj_val]
self.json_comparator(expected, obj_val)
def pci_device_pools_comparator(self, expected, obj_val):
obj_val = obj_val.obj_to_primitive()
self.json_loads_comparator(expected, obj_val)
def json_loads_comparator(self, expected, obj_val):
# NOTE(edleafe): This is necessary because the dumps() version of the
# PciDevicePoolList doesn't maintain ordering, so the output string
# doesn't always match.
self.assertEqual(jsonutils.loads(expected), obj_val)
def comparators(self):
return {'stats': self.json_comparator,
'host_ip': self.str_comparator,
'supported_hv_specs': self.supported_hv_specs_comparator}
'supported_hv_specs': self.supported_hv_specs_comparator,
'pci_device_pools': self.pci_device_pools_comparator,
}
def subs(self):
return {'supported_hv_specs': 'supported_instances'}
return {'supported_hv_specs': 'supported_instances',
'pci_device_pools': 'pci_stats'}
def test_get_by_id(self):
self.mox.StubOutWithMock(db, 'compute_node_get')
@ -325,6 +341,12 @@ class _TestComputeNodeObject(object):
primitive = compute.obj_to_primitive(target_version='1.6')
self.assertNotIn('host', primitive)
def test_compat_pci_device_pools(self):
compute = compute_node.ComputeNode()
compute.pci_device_pools = fake_pci_device_pools.fake_pool_list
primitive = compute.obj_to_primitive(target_version='1.8')
self.assertNotIn('pci_device_pools', primitive)
class TestComputeNodeObject(test_objects._LocalTest,
_TestComputeNodeObject):

View File

@ -1095,8 +1095,8 @@ object_data = {
'BandwidthUsageList': '1.2-5b564cbfd5ae6e106443c086938e7602',
'BlockDeviceMapping': '1.5-9968ffe513e7672484b0f528b034cd0f',
'BlockDeviceMappingList': '1.6-ee2ed2eb3f3f2f54d573ccea0ff2eeaa',
'ComputeNode': '1.8-117273c242796142ef637d39a9270a6a',
'ComputeNodeList': '1.8-c22863c12a4d6f5edca61d2d62ed5896',
'ComputeNode': '1.9-d59bebd3176d86f0f7ea02086732a0d4',
'ComputeNodeList': '1.9-4fdeaf7dce98f5736f0ed239c9265c65',
'DNSDomain': '1.0-5bdc288d7c3b723ce86ede998fd5c9ba',
'DNSDomainList': '1.0-cfb3e7e82be661501c31099523154db4',
'EC2InstanceMapping': '1.0-627baaf4b12c9067200979bdc4558a99',
@ -1140,6 +1140,8 @@ object_data = {
'NUMATopology': '1.2-790f6bdff85bf6e5677f409f3a4f1c6a',
'PciDevice': '1.2-29e35c3199f3b98ce66e5d1212612818',
'PciDeviceList': '1.1-2896df4f5b06579e5f35adba5fcae9db',
'PciDevicePool': '1.0-d6ed1abe611c9947345a44155abe6f11',
'PciDevicePoolList': '1.0-d31e08e0ff620a4df7cc2014b6c50da8',
'Quotas': '1.2-36098cf2143e6535873c3fa3d6fe56f7',
'QuotasNoOp': '1.2-164c628906b170fd946a7672e85e4935',
'S3ImageMapping': '1.0-9225943a44a91ad0349b9fd8bd3f3ce2',
@ -1149,9 +1151,11 @@ object_data = {
'SecurityGroupRuleList': '1.1-667fca3a9928f23d2d10e61962c55f3c',
'Service': '1.7-82bbfd46a744a9c89bc44b47a1b81683',
'ServiceList': '1.5-f137850fbd69933a69a03eae572b05f0',
'TestSubclassedObject': '1.6-87177ccbefd7a740a9e261f958e15b00',
'Service': '1.8-82bbfd46a744a9c89bc44b47a1b81683',
'ServiceList': '1.6-f8bd332b71ff1c3a86b88b6070072fd4',
'Tag': '1.0-a11531f4e4e3166eef6243d6d58a18bd',
'TagList': '1.0-e89bf8c8055f1f1d654fb44f0abf1f53',
'TestSubclassedObject': '1.6-87177ccbefd7a740a9e261f958e15b00',
'VirtualInterface': '1.0-10fdac4c704102b6d57d6936d6d790d2',
'VirtualInterfaceList': '1.0-accbf02628a8063c1d885077a2bf49b6',
'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563',
@ -1160,6 +1164,7 @@ object_data = {
object_relationships = {
'BlockDeviceMapping': {'Instance': '1.17'},
'ComputeNode': {'PciDevicePoolList': '1.0'},
'FixedIP': {'Instance': '1.17', 'Network': '1.2',
'VirtualInterface': '1.0',
'FloatingIPList': '1.7'},
@ -1174,7 +1179,7 @@ object_relationships = {
'InstanceNUMACell': {'VirtCPUTopology': '1.0'},
'MyObj': {'MyOwnedObject': '1.0'},
'SecurityGroupRule': {'SecurityGroup': '1.1'},
'Service': {'ComputeNode': '1.8'},
'Service': {'ComputeNode': '1.9'},
'TestSubclassedObject': {'MyOwnedObject': '1.0'}
}

View File

@ -0,0 +1,79 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# 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 copy
from nova import objects
from nova.objects import pci_device_pool
from nova import test
from nova.tests.unit import fake_pci_device_pools as fake_pci
from nova.tests.unit.objects import test_objects
class _TestPciDevicePoolObject(object):
def test_pci_pool_from_dict_not_distructive(self):
test_dict = copy.copy(fake_pci.fake_pool_dict)
objects.PciDevicePool.from_dict(test_dict)
self.assertEqual(fake_pci.fake_pool_dict, test_dict)
def test_pci_pool_from_dict(self):
pool_obj = objects.PciDevicePool.from_dict(fake_pci.fake_pool_dict)
self.assertEqual(pool_obj.product_id, 'fake-product')
self.assertEqual(pool_obj.vendor_id, 'fake-vendor')
self.assertEqual(pool_obj.tags, {'t1': 'v1', 't2': 'v2'})
self.assertEqual(pool_obj.count, 2)
def test_pci_pool_from_dict_no_tags(self):
dict_notag = copy.copy(fake_pci.fake_pool_dict)
dict_notag.pop('t1')
dict_notag.pop('t2')
pool_obj = objects.PciDevicePool.from_dict(dict_notag)
self.assertEqual(pool_obj.tags, {})
class TestPciDevicePoolObject(test_objects._LocalTest,
_TestPciDevicePoolObject):
pass
class TestRemotePciDevicePoolObject(test_objects._RemoteTest,
_TestPciDevicePoolObject):
pass
class TestConvertPciStats(test.NoDBTestCase):
def test_from_pci_stats_obj(self):
prim = fake_pci.fake_pool_list_primitive
pools = pci_device_pool.from_pci_stats(prim)
self.assertIsInstance(pools, pci_device_pool.PciDevicePoolList)
self.assertEqual(len(pools), 1)
def test_from_pci_stats_dict(self):
prim = fake_pci.fake_pool_dict
pools = pci_device_pool.from_pci_stats(prim)
self.assertIsInstance(pools, pci_device_pool.PciDevicePoolList)
self.assertEqual(len(pools), 1)
def test_from_pci_stats_list_of_dicts(self):
prim = fake_pci.fake_pool_dict
pools = pci_device_pool.from_pci_stats([prim, prim])
self.assertIsInstance(pools, pci_device_pool.PciDevicePoolList)
self.assertEqual(len(pools), 2)
def test_from_pci_stats_bad(self):
prim = "not a valid json string for an object"
pools = pci_device_pool.from_pci_stats(prim)
self.assertIsNone(pools)

View File

@ -13,6 +13,7 @@
# under the License.
import mock
from oslo.serialization import jsonutils
from oslo.utils import timeutils
from nova import db
@ -45,13 +46,25 @@ class _TestServiceObject(object):
obj_val = [inst.to_list() for inst in obj_val]
self.json_comparator(expected, obj_val)
def pci_device_pools_comparator(self, expected, obj_val):
obj_val = obj_val.obj_to_primitive()
self.json_loads_comparator(expected, obj_val)
def json_loads_comparator(self, expected, obj_val):
# NOTE(edleafe): This is necessary because the dumps() version of the
# PciDevicePoolList doesn't maintain ordering, so the output string
# doesn't always match.
self.assertEqual(jsonutils.loads(expected), obj_val)
def comparators(self):
return {'stats': self.json_comparator,
'host_ip': self.str_comparator,
'supported_hv_specs': self.supported_hv_specs_comparator}
'supported_hv_specs': self.supported_hv_specs_comparator,
'pci_device_pools': self.pci_device_pools_comparator}
def subs(self):
return {'supported_hv_specs': 'supported_instances'}
return {'supported_hv_specs': 'supported_instances',
'pci_device_pools': 'pci_stats'}
def _test_query(self, db_method, obj_method, *args, **kwargs):
self.mox.StubOutWithMock(db, db_method)