Support SR-IOV networking in the PCI modules

This patch takes care of changing the PCI modules in support of SR-IOV
networking:
  -- Updated pci_whitelist to use devspec that is introduced in this
     patch: https://review.openstack.org/#/c/99043/
  -- For stats pool, to keep the existing behavior, pool keys always
     include product_id and vendor_id. Meanwhile, to work with tags
     and support pci requests based on tags, pools are ordered based
     on the number of keys of the pool. This makes sure that legacy
     pci requests will be satisfied with the pools in the beginning,
     possiblly with devices that are not tagged.

Partially Implements: blueprint pci-passthrough-sriov
Change-Id: I9da7bd921f098c958d6fb711a4d03a76862e95c7
This commit is contained in:
Robert Li 2014-09-09 10:16:23 -04:00 committed by Baodong (Robert) Li
parent e274c45dcd
commit 5a8ae0f1d0
12 changed files with 218 additions and 133 deletions

View File

@ -14,18 +14,21 @@ from nova import db
from nova.objects import base
from nova.objects import fields
from nova.openstack.common import jsonutils
from nova import utils
class InstancePCIRequest(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Add request_id
VERSION = '1.1'
fields = {
'count': fields.IntegerField(),
'spec': fields.ListOfDictOfNullableStringsField(),
'alias_name': fields.StringField(),
'alias_name': fields.StringField(nullable=True),
# A stashed request related to a resize, not current
'is_new': fields.BooleanField(default=False),
'request_id': fields.UUIDField(nullable=True),
}
def obj_load_attr(self, attr):
@ -39,16 +42,30 @@ class InstancePCIRequest(base.NovaObject):
def new(self):
return self.is_new
def obj_make_compatible(self, primitive, target_version):
target_version = utils.convert_version_to_tuple(target_version)
if target_version < (1, 1) and 'request_id' in primitive:
del primitive['request_id']
class InstancePCIRequests(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: InstancePCIRequest 1.1
VERSION = '1.1'
fields = {
'instance_uuid': fields.UUIDField(),
'requests': fields.ListOfObjectsField('InstancePCIRequest'),
}
def obj_make_compatible(self, primitive, target_version):
target_version = utils.convert_version_to_tuple(target_version)
if target_version < (1, 1) and 'requests' in primitive:
for index, request in enumerate(self.requests):
request.obj_make_compatible(
primitive['requests'][index]['nova_object.data'], '1.0')
primitive['requests'][index]['nova_object.version'] = '1.0'
@base.remotable_classmethod
def get_by_instance_uuid(cls, context, instance_uuid):
obj_pci_requests = cls(instance_uuid=instance_uuid)
@ -65,7 +82,8 @@ class InstancePCIRequests(base.NovaObject):
for request in requests:
request_obj = InstancePCIRequest(
count=request['count'], spec=request['spec'],
alias_name=request['alias_name'], is_new=request['is_new'])
alias_name=request['alias_name'], is_new=request['is_new'],
request_id=request['request_id'])
request_obj.obj_reset_changes()
obj_pci_requests.requests.append(request_obj)
@ -117,7 +135,8 @@ class InstancePCIRequests(base.NovaObject):
blob = [{'count': x.count,
'spec': x.spec,
'alias_name': x.alias_name,
'is_new': x.is_new} for x in self.requests]
'is_new': x.is_new,
'request_id': x.request_id} for x in self.requests]
requests = jsonutils.dumps(blob)
db.instance_extra_update_by_uuid(context, self.instance_uuid,
{'pci_requests': requests})

View File

@ -267,11 +267,20 @@ class PciDevTracker(object):
dev.compute_node_id = node_id
def get_instance_pci_devs(inst):
"""Get the devices assigned to the instances."""
def get_instance_pci_devs(inst, request_id=None):
"""Get the devices allocated to one or all requests for an instance.
- For generic PCI request, the request id is None.
- For sr-iov networking, the request id is a valid uuid
- There are a couple of cases where all the PCI devices allocated to an
instance need to be returned. Refer to libvirt driver that handles
soft_reboot and hard_boot of 'xen' instances.
"""
if isinstance(inst, objects.Instance):
return inst.pci_devices
pci_devices = inst.pci_devices
else:
ctxt = context.get_admin_context()
return objects.PciDeviceList.get_by_instance_uuid(
pci_devices = objects.PciDeviceList.get_by_instance_uuid(
ctxt, inst['uuid'])
return [device for device in pci_devices if
device.request_id == request_id or request_id == 'all']

View File

@ -21,6 +21,7 @@ from nova.i18n import _LE
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova.pci import pci_utils
from nova.pci import pci_whitelist
LOG = logging.getLogger(__name__)
@ -50,31 +51,57 @@ class PciDeviceStats(object):
This summary information will be helpful for cloud management also.
"""
pool_keys = ['product_id', 'vendor_id', 'extra_info']
pool_keys = ['product_id', 'vendor_id']
def __init__(self, stats=None):
super(PciDeviceStats, self).__init__()
self.pools = jsonutils.loads(stats) if stats else []
self.pools.sort(self.pool_cmp)
def _equal_properties(self, dev, entry):
def _equal_properties(self, dev, entry, matching_keys):
return all(dev.get(prop) == entry.get(prop)
for prop in self.pool_keys)
for prop in matching_keys)
def _get_first_pool(self, dev):
def _find_pool(self, dev_pool):
"""Return the first pool that matches dev."""
return next((pool for pool in self.pools
if self._equal_properties(dev, pool)), None)
for pool in self.pools:
pool_keys = pool.copy()
del pool_keys['count']
del pool_keys['devices']
if (len(pool_keys.keys()) == len(dev_pool.keys()) and
self._equal_properties(dev_pool, pool_keys, dev_pool.keys())):
return pool
def _create_pool_keys_from_dev(self, dev):
"""create a stats pool dict that this dev is supposed to be part of
Note that this pool dict contains the stats pool's keys and their
values. 'count' and 'devices' are not included.
"""
# Don't add a device that doesn't have a matching device spec.
# This can happen during initial sync up with the controller
devspec = pci_whitelist.get_pci_device_devspec(dev)
if not devspec:
return
tags = devspec.get_tags()
pool = dict((k, dev.get(k)) for k in self.pool_keys)
if tags:
pool.update(tags)
return pool
def add_device(self, dev):
"""Add a device to the first matching pool."""
pool = self._get_first_pool(dev)
if not pool:
pool = dict((k, dev.get(k)) for k in self.pool_keys)
pool['count'] = 0
pool['devices'] = []
self.pools.append(pool)
pool['count'] += 1
pool['devices'].append(dev)
"""Add a device to its matching pool."""
dev_pool = self._create_pool_keys_from_dev(dev)
if dev_pool:
pool = self._find_pool(dev_pool)
if not pool:
dev_pool['count'] = 0
dev_pool['devices'] = []
self.pools.append(dev_pool)
self.pools.sort(self.pool_cmp)
pool = dev_pool
pool['count'] += 1
pool['devices'].append(dev)
@staticmethod
def _decrease_pool_count(pool_list, pool, count=1):
@ -92,7 +119,8 @@ class PciDeviceStats(object):
def remove_device(self, dev):
"""Remove one device from the first pool that it matches."""
pool = self._get_first_pool(dev)
dev_pool = self._create_pool_keys_from_dev(dev)
pool = self._find_pool(dev_pool)
if not pool:
raise exception.PciDevicePoolEmpty(
compute_node_id=dev.compute_node_id, address=dev.address)
@ -108,8 +136,8 @@ class PciDeviceStats(object):
def consume_requests(self, pci_requests):
alloc_devices = []
for request in pci_requests:
count = request.get('count', 1)
spec = request.get('spec', [])
count = request.count
spec = request.spec
# For now, keep the same algorithm as during scheduling:
# a spec may be able to match multiple pools.
pools = self._filter_pools_for_spec(self.pools, spec)
@ -135,6 +163,7 @@ class PciDeviceStats(object):
pool['count'] -= num_alloc
for d in range(num_alloc):
pci_dev = pool['devices'].pop()
pci_dev.request_id = request.request_id
alloc_devices.append(pci_dev)
if count == 0:
break
@ -146,7 +175,7 @@ class PciDeviceStats(object):
if pci_utils.pci_device_prop_match(pool, request_specs)]
def _apply_request(self, pools, request):
count = request['count']
count = request.count
matching_pools = self._filter_pools_for_spec(pools, request['spec'])
if sum([pool['count'] for pool in matching_pools]) < count:
return False
@ -178,6 +207,10 @@ class PciDeviceStats(object):
if not all([self._apply_request(self.pools, r) for r in requests]):
raise exception.PciDeviceRequestFailed(requests=requests)
@staticmethod
def pool_cmp(dev1, dev2):
return len(dev1) - len(dev2)
def __iter__(self):
# 'devices' shouldn't be part of stats
pools = []

View File

@ -14,14 +14,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import jsonschema
from oslo.config import cfg
import six
from nova import exception
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova.pci import pci_utils
from nova.pci import pci_devspec
pci_opts = [cfg.MultiStrOpt('pci_passthrough_whitelist',
default=[],
@ -36,28 +32,6 @@ CONF.register_opts(pci_opts)
LOG = logging.getLogger(__name__)
_PCI_VENDOR_PATTERN = "^(hex{4})$".replace("hex", "[\da-fA-F]")
_WHITELIST_SCHEMA = {
"type": "array",
"items":
{
"type": "object",
"additionalProperties": False,
"properties": {
"product_id": {
"type": "string",
"pattern": _PCI_VENDOR_PATTERN
},
"vendor_id": {
"type": "string",
"pattern": _PCI_VENDOR_PATTERN
},
},
"required": ["product_id", "vendor_id"]
}
}
class PciHostDevicesWhiteList(object):
"""White list class to decide assignable pci devices.
@ -71,13 +45,9 @@ class PciHostDevicesWhiteList(object):
def _parse_white_list_from_config(self, whitelists):
"""Parse and validate the pci whitelist from the nova config."""
specs = []
try:
for jsonspecs in whitelists:
spec = jsonutils.loads(jsonspecs)
jsonschema.validate(spec, _WHITELIST_SCHEMA)
specs.extend(spec)
except Exception as e:
raise exception.PciConfigInvalidWhitelist(reason=six.text_type(e))
for jsonspec in whitelists:
spec = pci_devspec.PciDeviceSpec(jsonspec)
specs.append(spec)
return specs
@ -95,19 +65,29 @@ class PciHostDevicesWhiteList(object):
"""
super(PciHostDevicesWhiteList, self).__init__()
if whitelist_spec:
self.spec = self._parse_white_list_from_config(whitelist_spec)
self.specs = self._parse_white_list_from_config(whitelist_spec)
else:
self.spec = None
self.specs = []
def device_assignable(self, dev):
"""Check if a device can be assigned to a guest.
:param dev: A dictionary describing the device properties
"""
if self.spec is None:
return False
return pci_utils.pci_device_prop_match(dev, self.spec)
for spec in self.specs:
if spec.match(dev):
return spec
def get_devspec(self, pci_dev):
for spec in self.specs:
if spec.match_pci_obj(pci_dev):
return spec
def get_pci_devices_filter():
return PciHostDevicesWhiteList(CONF.pci_passthrough_whitelist)
def get_pci_device_devspec(pci_dev):
dev_filter = get_pci_devices_filter()
return dev_filter.get_devspec(pci_dev)

View File

@ -26,6 +26,7 @@ from nova import exception
from nova import objects
from nova.pci import pci_manager
from nova import test
from nova.tests.pci import pci_fakes
class FakeResourceHandler(object):
@ -166,6 +167,7 @@ class ClaimTestCase(test.NoDBTestCase):
self._claim, limits=limits, root_gb=10, ephemeral_gb=40,
memory_mb=16384)
@pci_fakes.patch_pci_whitelist
def test_pci_pass(self, mock_get):
dev_dict = {
'compute_node_id': 1,
@ -180,8 +182,9 @@ class ClaimTestCase(test.NoDBTestCase):
spec=[{'vendor_id': 'v', 'product_id': 'p'}])
mock_get.return_value = objects.InstancePCIRequests(
requests=[request])
claim._test_pci()
self.assertIsNone(claim._test_pci())
@pci_fakes.patch_pci_whitelist
def test_pci_fail(self, mock_get):
dev_dict = {
'compute_node_id': 1,
@ -198,6 +201,7 @@ class ClaimTestCase(test.NoDBTestCase):
requests=[request])
claim._test_pci()
@pci_fakes.patch_pci_whitelist
def test_pci_pass_no_requests(self, mock_get):
dev_dict = {
'compute_node_id': 1,
@ -208,7 +212,7 @@ class ClaimTestCase(test.NoDBTestCase):
self.tracker.new_pci_tracker()
self.tracker.pci_tracker.set_hvdevs([dev_dict])
claim = self._claim()
claim._test_pci()
self.assertIsNone(claim._test_pci())
def test_ext_resources(self, mock_get):
self._claim()

View File

@ -35,6 +35,7 @@ from nova import rpc
from nova import test
from nova.tests.compute.monitors import test_monitors
from nova.tests.objects import test_migration
from nova.tests.pci import pci_fakes
from nova.virt import driver
@ -89,8 +90,7 @@ class FakeVirtDriver(driver.ComputeDriver):
self.pci_stats = [{
'count': 1,
'vendor_id': 'v1',
'product_id': 'p1',
'extra_info': {'extra_k1': 'v1'}}] if self.pci_support else []
'product_id': 'p1'}] if self.pci_support else []
if stats is not None:
self.stats = stats
@ -453,6 +453,10 @@ class BaseTrackerTestCase(BaseTestCase):
self.stubs.Set(db, 'migration_get_in_progress_by_host_and_node',
self._fake_migration_get_in_progress_by_host_and_node)
# Note that this must be called before the call to _init_tracker()
patcher = pci_fakes.fake_pci_whitelist()
self.addCleanup(patcher.stop)
self._init_tracker()
self.limits = self._limits()

View File

@ -18,6 +18,7 @@ from nova.tests.objects import test_objects
FAKE_UUID = '79a53d6b-0893-4838-a971-15f4f382e7c2'
FAKE_REQUEST_UUID = '69b53d6b-0793-4839-c981-f5c4f382e7d2'
# NOTE(danms): Yes, these are the same right now, but going forward,
# we have changes to make which will be reflected in the format
@ -27,12 +28,14 @@ fake_pci_requests = [
'spec': [{'vendor_id': '8086',
'device_id': '1502'}],
'alias_name': 'alias_1',
'is_new': False},
'is_new': False,
'request_id': FAKE_REQUEST_UUID},
{'count': 2,
'spec': [{'vendor_id': '6502',
'device_id': '07B5'}],
'alias_name': 'alias_2',
'is_new': True},
'is_new': True,
'request_id': FAKE_REQUEST_UUID},
]
fake_legacy_pci_requests = [
@ -117,13 +120,15 @@ class _TestInstancePCIRequests(object):
count=1,
spec=[{'foo': 'bar'}, {'baz': 'bat'}],
alias_name='alias_1',
is_new=False)])
is_new=False,
request_id=FAKE_REQUEST_UUID)])
requests.save()
self.assertEqual(FAKE_UUID, mock_update.call_args_list[0][0][1])
self.assertEqual(
[{'count': 1, 'is_new': False,
'alias_name': 'alias_1',
'spec': [{'foo': 'bar'}, {'baz': 'bat'}]}],
'spec': [{'foo': 'bar'}, {'baz': 'bat'}],
'request_id': FAKE_REQUEST_UUID}],
jsonutils.loads(
mock_update.call_args_list[0][0][2]['pci_requests']))
@ -161,6 +166,20 @@ class _TestInstancePCIRequests(object):
request = objects.InstancePCIRequest(is_new=False)
self.assertFalse(request.new)
def test_backport_1_0(self):
requests = objects.InstancePCIRequests(
requests=[objects.InstancePCIRequest(count=1,
request_id=FAKE_UUID),
objects.InstancePCIRequest(count=2,
request_id=FAKE_UUID)])
primitive = requests.obj_to_primitive(target_version='1.0')
backported = objects.InstancePCIRequests.obj_from_primitive(
primitive)
self.assertEqual('1.0', backported.VERSION)
self.assertEqual(2, len(backported.requests))
self.assertFalse(backported.requests[0].obj_attr_is_set('request_id'))
self.assertFalse(backported.requests[1].obj_attr_is_set('request_id'))
class TestInstancePCIRequests(test_objects._LocalTest,
_TestInstancePCIRequests):

View File

@ -0,0 +1,38 @@
# Copyright (c) 2014 OpenStack Foundation
# 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 functools
import mock
from nova.pci import pci_whitelist
def fake_pci_whitelist():
devspec = mock.Mock()
devspec.get_tags.return_value = None
patcher = mock.patch.object(pci_whitelist, 'get_pci_device_devspec',
return_value=devspec)
patcher.start()
return patcher
def patch_pci_whitelist(f):
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
patcher = fake_pci_whitelist()
f(self, *args, **kwargs)
patcher.stop()
return wrapper

View File

@ -27,6 +27,7 @@ from nova.pci import pci_device
from nova.pci import pci_manager
from nova import test
from nova.tests.api.openstack import fakes
from nova.tests.pci import pci_fakes
fake_pci = {
@ -105,6 +106,8 @@ class PciDevTrackerTestCase(test.TestCase):
super(PciDevTrackerTestCase, self).setUp()
self.stubs.Set(db, 'pci_device_get_all_by_node',
self._fake_get_pci_devices)
patcher = pci_fakes.fake_pci_whitelist()
self.addCleanup(patcher.stop)
self._create_fake_instance()
self.tracker = pci_manager.PciDevTracker(1)

View File

@ -18,6 +18,7 @@ from nova import objects
from nova.openstack.common import jsonutils
from nova.pci import pci_stats as pci
from nova import test
from nova.tests.pci import pci_fakes
fake_pci_1 = {
'compute_node_id': 1,
@ -38,16 +39,16 @@ fake_pci_2 = dict(fake_pci_1, vendor_id='v2',
fake_pci_3 = dict(fake_pci_1, address='0000:00:00.3')
pci_requests = [{'count': 1,
'spec': [{'vendor_id': 'v1'}]},
{'count': 1,
'spec': [{'vendor_id': 'v2'}]}]
pci_requests = [objects.InstancePCIRequest(count=1,
spec=[{'vendor_id': 'v1'}]),
objects.InstancePCIRequest(count=1,
spec=[{'vendor_id': 'v2'}])]
pci_requests_multiple = [{'count': 1,
'spec': [{'vendor_id': 'v1'}]},
{'count': 3,
'spec': [{'vendor_id': 'v2'}]}]
pci_requests_multiple = [objects.InstancePCIRequest(count=1,
spec=[{'vendor_id': 'v1'}]),
objects.InstancePCIRequest(count=3,
spec=[{'vendor_id': 'v2'}])]
class PciDeviceStatsTestCase(test.NoDBTestCase):
@ -62,6 +63,9 @@ class PciDeviceStatsTestCase(test.NoDBTestCase):
def setUp(self):
super(PciDeviceStatsTestCase, self).setUp()
self.pci_stats = pci.PciDeviceStats()
# The following two calls need to be made before adding the devices.
patcher = pci_fakes.fake_pci_whitelist()
self.addCleanup(patcher.stop)
self._create_fake_devs()
def test_add_device(self):

View File

@ -13,18 +13,17 @@
# License for the specific language governing permissions and limitations
# under the License.
from nova import exception
from nova import objects
from nova.pci import pci_whitelist
from nova import test
dev_dict = {
'compute_node_id': 1,
'address': 'a',
'address': '0000:00:0a.1',
'product_id': '0001',
'vendor_id': '8086',
'status': 'available',
'phys_function': '0000:00:0a.0',
}
@ -32,66 +31,39 @@ class PciHostDevicesWhiteListTestCase(test.NoDBTestCase):
def setUp(self):
super(PciHostDevicesWhiteListTestCase, self).setUp()
def test_whitelist_wrong_format(self):
white_list = '[{"vendor_x_id":"8086", "product_id":"0001"}]'
self.assertRaises(
exception.PciConfigInvalidWhitelist,
pci_whitelist.PciHostDevicesWhiteList, white_list
)
white_list = '[{"vendor_id":"80863", "product_id":"0001"}]'
self.assertRaises(
exception.PciConfigInvalidWhitelist,
pci_whitelist.PciHostDevicesWhiteList, white_list
)
def test_whitelist_missed_fields(self):
white_list = '[{"vendor_id":"80863"}]'
self.assertRaises(
exception.PciConfigInvalidWhitelist,
pci_whitelist.PciHostDevicesWhiteList, white_list
)
def test_whitelist(self):
white_list = '[{"product_id":"0001", "vendor_id":"8086"}]'
white_list = '{"product_id":"0001", "vendor_id":"8086"}'
parsed = pci_whitelist.PciHostDevicesWhiteList([white_list])
self.assertEqual(parsed.spec, [{'vendor_id': '8086',
'product_id': '0001'}])
self.assertEqual(1, len(parsed.specs))
def test_whitelist_empty(self):
dev = objects.PciDevice.create(dev_dict)
parsed = pci_whitelist.PciHostDevicesWhiteList()
self.assertEqual(parsed.device_assignable(dev), False)
self.assertFalse(parsed.device_assignable(dev_dict))
def test_whitelist_multiple(self):
white_list_1 = '[{"product_id":"0001", "vendor_id":"8086"}]'
white_list_2 = '[{"product_id":"0002", "vendor_id":"8087"}]'
parsed = pci_whitelist.PciHostDevicesWhiteList(
[white_list_1, white_list_2])
self.assertEqual(parsed.spec,
[{'vendor_id': '8086', 'product_id': '0001'},
{'vendor_id': '8087', 'product_id': '0002'}])
wl1 = '{"product_id":"0001", "vendor_id":"8086"}'
wl2 = '{"product_id":"0002", "vendor_id":"8087"}'
parsed = pci_whitelist.PciHostDevicesWhiteList([wl1, wl2])
self.assertEqual(2, len(parsed.specs))
def test_device_assignable(self):
dev = objects.PciDevice.create(dev_dict)
white_list = '[{"product_id":"0001", "vendor_id":"8086"}]'
white_list = '{"product_id":"0001", "vendor_id":"8086"}'
parsed = pci_whitelist.PciHostDevicesWhiteList([white_list])
self.assertEqual(parsed.device_assignable(dev), True)
self.assertIsNotNone(parsed.device_assignable(dev_dict))
def test_device_assignable_multiple(self):
dev = objects.PciDevice.create(dev_dict)
white_list_1 = '[{"product_id":"0001", "vendor_id":"8086"}]'
white_list_2 = '[{"product_id":"0002", "vendor_id":"8087"}]'
white_list_1 = '{"product_id":"0001", "vendor_id":"8086"}'
white_list_2 = '{"product_id":"0002", "vendor_id":"8087"}'
parsed = pci_whitelist.PciHostDevicesWhiteList(
[white_list_1, white_list_2])
self.assertEqual(parsed.device_assignable(dev), True)
dev.vendor_id = '8087'
dev.product_id = '0002'
self.assertEqual(parsed.device_assignable(dev), True)
self.assertIsNotNone(parsed.device_assignable(dev_dict))
dev_dict1 = dev_dict.copy()
dev_dict1['vendor_id'] = '8087'
dev_dict1['product_id'] = '0002'
self.assertIsNotNone(parsed.device_assignable(dev_dict1))
def test_get_pci_devices_filter(self):
white_list_1 = '[{"product_id":"0001", "vendor_id":"8086"}]'
white_list_1 = '{"product_id":"0001", "vendor_id":"8086"}'
self.flags(pci_passthrough_whitelist=[white_list_1])
pci_filter = pci_whitelist.get_pci_devices_filter()
dev = objects.PciDevice.create(dev_dict)
self.assertEqual(pci_filter.device_assignable(dev), True)
self.assertIsNotNone(pci_filter.device_assignable(dev_dict))

View File

@ -2053,8 +2053,8 @@ class XenAPIHostTestCase(stubs.XenAPITestBase):
def test_pci_passthrough_devices_whitelist(self):
# NOTE(guillaume-thouvenin): This pci whitelist will be used to
# match with _plugin_xenhost_get_pci_device_details method in fake.py.
self.flags(pci_passthrough_whitelist=
['[{"vendor_id":"10de", "product_id":"11bf"}]'])
white_list = '{"vendor_id":"10de", "product_id":"11bf"}'
self.flags(pci_passthrough_whitelist=[white_list])
stats = self.conn.get_host_stats()
self.assertEqual(len(stats['pci_passthrough_devices']), 1)