Merge "port pci stats from nova to zun"
This commit is contained in:
commit
a386cbe0ad
@ -578,6 +578,12 @@ class PciDeviceNotFound(NotFound):
|
||||
message = _("PCI Device %(node_id)s:%(address)s not found.")
|
||||
|
||||
|
||||
class PciDevicePoolEmpty(ZunException):
|
||||
message = _(
|
||||
"Attempt to consume PCI device %(compute_node_uuid)s:%(address)s "
|
||||
"from empty pool")
|
||||
|
||||
|
||||
class CapsuleAlreadyExists(ResourceExists):
|
||||
message = _("A capsule with %(field)s %(value)s already exists.")
|
||||
|
||||
|
@ -25,6 +25,7 @@ from zun.conf import network
|
||||
from zun.conf import neutron_client
|
||||
from zun.conf import nova_client
|
||||
from zun.conf import path
|
||||
from zun.conf import pci
|
||||
from zun.conf import profiler
|
||||
from zun.conf import scheduler
|
||||
from zun.conf import services
|
||||
@ -51,3 +52,4 @@ profiler.register_opts(CONF)
|
||||
neutron_client.register_opts(CONF)
|
||||
network.register_opts(CONF)
|
||||
websocket_proxy.register_opts(CONF)
|
||||
pci.register_opts(CONF)
|
||||
|
122
zun/conf/pci.py
Normal file
122
zun/conf/pci.py
Normal file
@ -0,0 +1,122 @@
|
||||
# Copyright (c) 2017 Intel, Inc.
|
||||
# Copyright (c) 2017 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
pci_group = cfg.OptGroup(
|
||||
name='pci',
|
||||
title='PCI passthrough options')
|
||||
|
||||
pci_opts = [
|
||||
cfg.MultiStrOpt('alias',
|
||||
default=[],
|
||||
help="""
|
||||
An alias for a PCI passthrough device requirement.
|
||||
|
||||
Possible Values:
|
||||
|
||||
* A list of JSON values which describe the aliases. For example:
|
||||
|
||||
alias = {
|
||||
"name": "QuickAssist",
|
||||
"product_id": "0443",
|
||||
"vendor_id": "8086",
|
||||
"device_type": "PCI"
|
||||
}
|
||||
|
||||
defines an alias for the Intel QuickAssist card. (multi valued). Valid key
|
||||
values are :
|
||||
|
||||
* "name": Name of the PCI alias.
|
||||
* "product_id": Product ID of the device in hexadecimal.
|
||||
* "vendor_id": Vendor ID of the device in hexadecimal.
|
||||
* "device_type": Type of PCI device. Valid values are: "type-PCI",
|
||||
"PF" and "VF".
|
||||
"""),
|
||||
cfg.MultiStrOpt('passthrough_whitelist',
|
||||
default=[],
|
||||
help="""
|
||||
White list of PCI devices available to VMs.
|
||||
|
||||
Possible values:
|
||||
|
||||
* A JSON dictionary which describe a whitelisted PCI device. It should take
|
||||
the following format:
|
||||
|
||||
["vendor_id": "<id>",] ["product_id": "<id>",]
|
||||
["address": "[[[[<domain>]:]<bus>]:][<slot>][.[<function>]]" |
|
||||
"devname": "<name>",]
|
||||
{"<tag>": "<tag_value>",}
|
||||
|
||||
Where '[' indicates zero or one occurrences, '{' indicates zero or multiple
|
||||
occurrences, and '|' mutually exclusive options. Note that any missing
|
||||
fields are automatically wildcarded.
|
||||
|
||||
Valid key values are :
|
||||
|
||||
* "vendor_id": Vendor ID of the device in hexadecimal.
|
||||
* "product_id": Product ID of the device in hexadecimal.
|
||||
* "address": PCI address of the device.
|
||||
* "devname": Device name of the device (for e.g. interface name). Not all
|
||||
PCI devices have a name.
|
||||
* "<tag>": Additional <tag> and <tag_value> used for matching PCI devices.
|
||||
Supported <tag>: "physical_network".
|
||||
|
||||
The address key supports traditional glob style and regular expression
|
||||
syntax. Valid examples are:
|
||||
|
||||
passthrough_whitelist = {"devname":"eth0",
|
||||
"physical_network":"physnet"}
|
||||
passthrough_whitelist = {"address":"*:0a:00.*"}
|
||||
passthrough_whitelist = {"address":":0a:00.",
|
||||
"physical_network":"physnet1"}
|
||||
passthrough_whitelist = {"vendor_id":"1137",
|
||||
"product_id":"0071"}
|
||||
passthrough_whitelist = {"vendor_id":"1137",
|
||||
"product_id":"0071",
|
||||
"address": "0000:0a:00.1",
|
||||
"physical_network":"physnet1"}
|
||||
passthrough_whitelist = {"address":{"domain": ".*",
|
||||
"bus": "02", "slot": "01",
|
||||
"function": "[2-7]"},
|
||||
"physical_network":"physnet1"}
|
||||
passthrough_whitelist = {"address":{"domain": ".*",
|
||||
"bus": "02", "slot": "0[1-2]",
|
||||
"function": ".*"},
|
||||
"physical_network":"physnet1"}
|
||||
|
||||
The following are invalid, as they specify mutually exclusive options:
|
||||
|
||||
passthrough_whitelist = {"devname":"eth0",
|
||||
"physical_network":"physnet",
|
||||
"address":"*:0a:00.*"}
|
||||
|
||||
* A JSON list of JSON dictionaries corresponding to the above format. For
|
||||
example:
|
||||
|
||||
passthrough_whitelist = [{"product_id":"0001", "vendor_id":"8086"},
|
||||
{"product_id":"0002", "vendor_id":"8086"}]
|
||||
""")
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(pci_group)
|
||||
conf.register_opts(pci_opts, group=pci_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {pci_group: pci_opts}
|
308
zun/pci/stats.py
Normal file
308
zun/pci/stats.py
Normal file
@ -0,0 +1,308 @@
|
||||
# Copyright (c) 2017 Intel, Inc.
|
||||
# Copyright (c) 2017 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 copy
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from zun.common import exception
|
||||
from zun.objects import fields
|
||||
from zun.objects import pci_device_pool
|
||||
from zun.pci import utils
|
||||
from zun.pci import whitelist
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PciDeviceStats(object):
|
||||
|
||||
"""PCI devices summary information.
|
||||
|
||||
According to the PCI SR-IOV spec, a PCI physical function can have up to
|
||||
256 PCI virtual functions, thus the number of assignable PCI functions in
|
||||
a cloud can be big. The scheduler needs to know all device availability
|
||||
information in order to determine which compute hosts can support a PCI
|
||||
request. Passing individual virtual device information to the scheduler
|
||||
does not scale, so we provide summary information.
|
||||
|
||||
Usually the virtual functions provided by a host PCI device have the same
|
||||
value for most properties, like vendor_id, product_id and class type.
|
||||
The PCI stats class summarizes this information for the scheduler.
|
||||
|
||||
The pci stats information is maintained exclusively by compute node
|
||||
resource tracker and updated to database. The scheduler fetches the
|
||||
information and selects the compute node accordingly. If a compute
|
||||
node is selected, the resource tracker allocates the devices to the
|
||||
instance and updates the pci stats information.
|
||||
|
||||
This summary information will be helpful for cloud management also.
|
||||
"""
|
||||
|
||||
pool_keys = ['product_id', 'vendor_id', 'numa_node', 'dev_type']
|
||||
|
||||
def __init__(self, stats=None, dev_filter=None):
|
||||
super(PciDeviceStats, self).__init__()
|
||||
# NOTE(sbauza): Stats are a PCIDevicePoolList object
|
||||
self.pools = [pci_pool.to_dict()
|
||||
for pci_pool in stats] if stats else []
|
||||
self.pools.sort(key=lambda item: len(item))
|
||||
self.dev_filter = dev_filter or whitelist.Whitelist(
|
||||
CONF.pci.passthrough_whitelist)
|
||||
|
||||
def _equal_properties(self, dev, entry, matching_keys):
|
||||
return all(dev.get(prop) == entry.get(prop)
|
||||
for prop in matching_keys)
|
||||
|
||||
def _find_pool(self, dev_pool):
|
||||
"""Return the first pool that matches dev."""
|
||||
for pool in self.pools:
|
||||
pool_keys = pool.copy()
|
||||
del pool_keys['count']
|
||||
del pool_keys['devices']
|
||||
equal = self._equal_properties(dev_pool, pool_keys,
|
||||
dev_pool.keys())
|
||||
if (len(pool_keys.keys()) == len(dev_pool.keys()) and equal):
|
||||
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 = self.dev_filter.get_devspec(dev)
|
||||
if not devspec:
|
||||
return
|
||||
tags = devspec.get_tags()
|
||||
pool = {k: getattr(dev, k) for k in self.pool_keys}
|
||||
if tags:
|
||||
pool.update(tags)
|
||||
return pool
|
||||
|
||||
def add_device(self, 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(key=lambda item: len(item))
|
||||
pool = dev_pool
|
||||
pool['count'] += 1
|
||||
pool['devices'].append(dev)
|
||||
|
||||
@staticmethod
|
||||
def _decrease_pool_count(pool_list, pool, count=1):
|
||||
"""Decrement pool's size by count.
|
||||
|
||||
If pool becomes empty, remove pool from pool_list.
|
||||
"""
|
||||
if pool['count'] > count:
|
||||
pool['count'] -= count
|
||||
count = 0
|
||||
else:
|
||||
count -= pool['count']
|
||||
pool_list.remove(pool)
|
||||
return count
|
||||
|
||||
def remove_device(self, dev):
|
||||
"""Remove one device from the first pool that it matches."""
|
||||
dev_pool = self._create_pool_keys_from_dev(dev)
|
||||
if dev_pool:
|
||||
pool = self._find_pool(dev_pool)
|
||||
if not pool:
|
||||
raise exception.PciDevicePoolEmpty(
|
||||
compute_node_uuid=dev.compute_node_uuid,
|
||||
address=dev.address)
|
||||
pool['devices'].remove(dev)
|
||||
self._decrease_pool_count(self.pools, pool)
|
||||
|
||||
def get_free_devs(self):
|
||||
free_devs = []
|
||||
for pool in self.pools:
|
||||
free_devs.extend(pool['devices'])
|
||||
return free_devs
|
||||
|
||||
def consume_requests(self, pci_requests, numa_cells=None):
|
||||
alloc_devices = []
|
||||
for request in pci_requests:
|
||||
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)
|
||||
if numa_cells:
|
||||
pools = self._filter_pools_for_numa_cells(pools, numa_cells)
|
||||
pools = self._filter_non_requested_pfs(request, pools)
|
||||
# Failed to allocate the required number of devices
|
||||
# Return the devices already allocated back to their pools
|
||||
if sum([pool['count'] for pool in pools]) < count:
|
||||
LOG.error("Failed to allocate PCI devices for container."
|
||||
" Unassigning devices back to pools."
|
||||
" This should not happen, since the scheduler"
|
||||
" should have accurate information, and allocation"
|
||||
" during claims is controlled via a hold"
|
||||
" on the compute node semaphore")
|
||||
for d in range(len(alloc_devices)):
|
||||
self.add_device(alloc_devices.pop())
|
||||
return None
|
||||
for pool in pools:
|
||||
if pool['count'] >= count:
|
||||
num_alloc = count
|
||||
else:
|
||||
num_alloc = pool['count']
|
||||
count -= num_alloc
|
||||
pool['count'] -= num_alloc
|
||||
for d in range(num_alloc):
|
||||
pci_dev = pool['devices'].pop()
|
||||
self._handle_device_dependents(pci_dev)
|
||||
pci_dev.request_id = request.request_id
|
||||
alloc_devices.append(pci_dev)
|
||||
if count == 0:
|
||||
break
|
||||
return alloc_devices
|
||||
|
||||
def _handle_device_dependents(self, pci_dev):
|
||||
"""Remove device dependents or a parent from pools.
|
||||
|
||||
In case the device is a PF, all of it's dependent VFs should
|
||||
be removed from pools count, if these are present.
|
||||
When the device is a VF, it's parent PF pool count should be
|
||||
decreased, unless it is no longer in a pool.
|
||||
"""
|
||||
if pci_dev.dev_type == fields.PciDeviceType.SRIOV_PF:
|
||||
vfs_list = pci_dev.child_devices
|
||||
if vfs_list:
|
||||
for vf in vfs_list:
|
||||
self.remove_device(vf)
|
||||
elif pci_dev.dev_type == fields.PciDeviceType.SRIOV_VF:
|
||||
try:
|
||||
parent = pci_dev.parent_device
|
||||
# Make sure not to decrease PF pool count if this parent has
|
||||
# been already removed from pools
|
||||
if parent in self.get_free_devs():
|
||||
self.remove_device(parent)
|
||||
except exception.PciDeviceNotFound:
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def _filter_pools_for_spec(pools, request_specs):
|
||||
return [pool for pool in pools
|
||||
if utils.pci_device_prop_match(pool, request_specs)]
|
||||
|
||||
@staticmethod
|
||||
def _filter_pools_for_numa_cells(pools, numa_cells):
|
||||
# Some systems don't report numa node info for pci devices, in
|
||||
# that case None is reported in pci_device.numa_node, by adding None
|
||||
# to numa_cells we allow assigning those devices to instances with
|
||||
# numa topology
|
||||
numa_cells = [None] + [cell.id for cell in numa_cells]
|
||||
# filter out pools which numa_node is not included in numa_cells
|
||||
return [pool for pool in pools if any(utils.pci_device_prop_match(
|
||||
pool, [{'numa_node': cell}]) for cell in numa_cells)]
|
||||
|
||||
def _filter_non_requested_pfs(self, request, matching_pools):
|
||||
# Remove SRIOV_PFs from pools, unless it has been explicitly requested
|
||||
# This is especially needed in cases where PFs and VFs has the same
|
||||
# product_id.
|
||||
if all(spec.get('dev_type') != fields.PciDeviceType.SRIOV_PF for
|
||||
spec in request.spec):
|
||||
matching_pools = self._filter_pools_for_pfs(matching_pools)
|
||||
return matching_pools
|
||||
|
||||
@staticmethod
|
||||
def _filter_pools_for_pfs(pools):
|
||||
return [pool for pool in pools
|
||||
if not pool.get('dev_type') == fields.PciDeviceType.SRIOV_PF]
|
||||
|
||||
def _apply_request(self, pools, request, numa_cells=None):
|
||||
# NOTE(vladikr): This code maybe open to race conditions.
|
||||
# Two concurrent requests may succeed when called support_requests
|
||||
# because this method does not remove related devices from the pools
|
||||
count = request.count
|
||||
matching_pools = self._filter_pools_for_spec(pools, request.spec)
|
||||
if numa_cells:
|
||||
matching_pools = self._filter_pools_for_numa_cells(matching_pools,
|
||||
numa_cells)
|
||||
matching_pools = self._filter_non_requested_pfs(request,
|
||||
matching_pools)
|
||||
if sum([pool['count'] for pool in matching_pools]) < count:
|
||||
return False
|
||||
else:
|
||||
for pool in matching_pools:
|
||||
count = self._decrease_pool_count(pools, pool, count)
|
||||
if not count:
|
||||
break
|
||||
return True
|
||||
|
||||
def support_requests(self, requests, numa_cells=None):
|
||||
"""Check if the pci requests can be met.
|
||||
|
||||
Scheduler checks compute node's PCI stats to decide if a
|
||||
container can be scheduled into the node. Support does not
|
||||
mean real allocation.
|
||||
If numa_cells is provided then only devices contained in
|
||||
those nodes are considered.
|
||||
"""
|
||||
# note (yjiang5): this function has high possibility to fail,
|
||||
# so no exception should be triggered for performance reason.
|
||||
pools = copy.deepcopy(self.pools)
|
||||
return all([self._apply_request(pools, r, numa_cells)
|
||||
for r in requests])
|
||||
|
||||
def apply_requests(self, requests, numa_cells=None):
|
||||
"""Apply PCI requests to the PCI stats.
|
||||
|
||||
This is used in container creation, when the scheduler has to
|
||||
maintain how the resources are consumed by the container.
|
||||
If numa_cells is provided then only devices contained in
|
||||
those nodes are considered.
|
||||
"""
|
||||
if not all([self._apply_request(self.pools, r, numa_cells)
|
||||
for r in requests]):
|
||||
raise exception.PciDeviceRequestFailed(requests=requests)
|
||||
|
||||
def __iter__(self):
|
||||
# 'devices' shouldn't be part of stats
|
||||
pools = []
|
||||
for pool in self.pools:
|
||||
tmp = {k: v for k, v in pool.items() if k != 'devices'}
|
||||
pools.append(tmp)
|
||||
return iter(pools)
|
||||
|
||||
def clear(self):
|
||||
"""Clear all the stats maintained."""
|
||||
self.pools = []
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.pools == other.pools
|
||||
|
||||
if six.PY2:
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def to_device_pools_obj(self):
|
||||
"""Return the contents of the pools as a PciDevicePoolList object."""
|
||||
stats = [x for x in self]
|
||||
return pci_device_pool.from_pci_stats(stats)
|
38
zun/tests/unit/pci/fakes.py
Normal file
38
zun/tests/unit/pci/fakes.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2017 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 zun.pci import whitelist
|
||||
|
||||
|
||||
def fake_pci_whitelist():
|
||||
devspec = mock.Mock()
|
||||
devspec.get_tags.return_value = None
|
||||
patcher = mock.patch.object(whitelist.Whitelist, 'get_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
|
290
zun/tests/unit/pci/test_stats.py
Normal file
290
zun/tests/unit/pci/test_stats.py
Normal file
@ -0,0 +1,290 @@
|
||||
# Copyright (c) 2017 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 mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from zun.common import exception
|
||||
from zun import objects
|
||||
from zun.objects import fields
|
||||
from zun.pci import stats
|
||||
from zun.pci import whitelist
|
||||
from zun.tests import base
|
||||
from zun.tests.unit.pci import fakes
|
||||
|
||||
CONF = cfg.CONF
|
||||
fake_pci_1 = {
|
||||
'compute_node_uuid': 1,
|
||||
'address': '0000:00:00.1',
|
||||
'product_id': 'p1',
|
||||
'vendor_id': 'v1',
|
||||
'status': 'available',
|
||||
'extra_k1': 'v1',
|
||||
'request_id': None,
|
||||
'numa_node': 0,
|
||||
'dev_type': fields.PciDeviceType.STANDARD,
|
||||
'parent_addr': None,
|
||||
}
|
||||
|
||||
|
||||
fake_pci_2 = dict(fake_pci_1, vendor_id='v2',
|
||||
product_id='p2',
|
||||
address='0000:00:00.2',
|
||||
numa_node=1)
|
||||
|
||||
|
||||
fake_pci_3 = dict(fake_pci_1, address='0000:00:00.3')
|
||||
|
||||
fake_pci_4 = dict(fake_pci_1, vendor_id='v3',
|
||||
product_id='p3',
|
||||
address='0000:00:00.3',
|
||||
numa_node=None)
|
||||
|
||||
|
||||
class PciDeviceStatsTestCase(base.TestCase):
|
||||
def _create_fake_devs(self):
|
||||
self.fake_dev_1 = objects.PciDevice.create(None, fake_pci_1)
|
||||
self.fake_dev_2 = objects.PciDevice.create(None, fake_pci_2)
|
||||
self.fake_dev_3 = objects.PciDevice.create(None, fake_pci_3)
|
||||
self.fake_dev_4 = objects.PciDevice.create(None, fake_pci_4)
|
||||
|
||||
for dev in [self.fake_dev_1, self.fake_dev_2,
|
||||
self.fake_dev_3, self.fake_dev_4]:
|
||||
self.pci_stats.add_device(dev)
|
||||
|
||||
def setUp(self):
|
||||
super(PciDeviceStatsTestCase, self).setUp()
|
||||
self.pci_stats = stats.PciDeviceStats()
|
||||
# The following two calls need to be made before adding the devices.
|
||||
patcher = fakes.fake_pci_whitelist()
|
||||
self.addCleanup(patcher.stop)
|
||||
self._create_fake_devs()
|
||||
|
||||
def test_add_device(self):
|
||||
self.assertEqual(len(self.pci_stats.pools), 3)
|
||||
self.assertEqual(set([d['vendor_id'] for d in self.pci_stats]),
|
||||
set(['v1', 'v2', 'v3']))
|
||||
self.assertEqual(set([d['count'] for d in self.pci_stats]),
|
||||
set([1, 2]))
|
||||
|
||||
def test_remove_device(self):
|
||||
self.pci_stats.remove_device(self.fake_dev_2)
|
||||
self.assertEqual(len(self.pci_stats.pools), 2)
|
||||
self.assertEqual(self.pci_stats.pools[0]['count'], 2)
|
||||
self.assertEqual(self.pci_stats.pools[0]['vendor_id'], 'v1')
|
||||
|
||||
def test_remove_device_exception(self):
|
||||
self.pci_stats.remove_device(self.fake_dev_2)
|
||||
self.assertRaises(exception.PciDevicePoolEmpty,
|
||||
self.pci_stats.remove_device,
|
||||
self.fake_dev_2)
|
||||
|
||||
def test_pci_stats_equivalent(self):
|
||||
pci_stats2 = stats.PciDeviceStats()
|
||||
for dev in [self.fake_dev_1,
|
||||
self.fake_dev_2,
|
||||
self.fake_dev_3,
|
||||
self.fake_dev_4]:
|
||||
pci_stats2.add_device(dev)
|
||||
self.assertEqual(self.pci_stats, pci_stats2)
|
||||
|
||||
def test_pci_stats_not_equivalent(self):
|
||||
pci_stats2 = stats.PciDeviceStats()
|
||||
for dev in [self.fake_dev_1,
|
||||
self.fake_dev_2,
|
||||
self.fake_dev_3]:
|
||||
pci_stats2.add_device(dev)
|
||||
self.assertNotEqual(self.pci_stats, pci_stats2)
|
||||
|
||||
def test_object_create(self):
|
||||
m = self.pci_stats.to_device_pools_obj()
|
||||
new_stats = stats.PciDeviceStats(m)
|
||||
|
||||
self.assertEqual(len(new_stats.pools), 3)
|
||||
self.assertEqual(set([d['count'] for d in new_stats]),
|
||||
set([1, 2]))
|
||||
self.assertEqual(set([d['vendor_id'] for d in new_stats]),
|
||||
set(['v1', 'v2', 'v3']))
|
||||
|
||||
@mock.patch(
|
||||
'zun.pci.whitelist.Whitelist._parse_white_list_from_config')
|
||||
def test_white_list_parsing(self, mock_whitelist_parse):
|
||||
white_list = '{"product_id":"0001", "vendor_id":"8086"}'
|
||||
CONF.set_override('passthrough_whitelist', white_list, 'pci')
|
||||
pci_stats = stats.PciDeviceStats()
|
||||
pci_stats.add_device(self.fake_dev_2)
|
||||
pci_stats.remove_device(self.fake_dev_2)
|
||||
self.assertEqual(1, mock_whitelist_parse.call_count)
|
||||
|
||||
|
||||
class PciDeviceStatsWithTagsTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PciDeviceStatsWithTagsTestCase, self).setUp()
|
||||
white_list = ['{"vendor_id":"1137","product_id":"0071",'
|
||||
'"address":"*:0a:00.*","physical_network":"physnet1"}',
|
||||
'{"vendor_id":"1137","product_id":"0072"}']
|
||||
self.config(passthrough_whitelist=white_list, group='pci')
|
||||
dev_filter = whitelist.Whitelist(white_list)
|
||||
self.pci_stats = stats.PciDeviceStats(dev_filter=dev_filter)
|
||||
|
||||
def _create_pci_devices(self):
|
||||
self.pci_tagged_devices = []
|
||||
for dev in range(4):
|
||||
pci_dev = {'compute_node_uuid': 1,
|
||||
'address': '0000:0a:00.%d' % dev,
|
||||
'vendor_id': '1137',
|
||||
'product_id': '0071',
|
||||
'status': 'available',
|
||||
'request_id': None,
|
||||
'dev_type': 'PCI',
|
||||
'parent_addr': None,
|
||||
'numa_node': 0}
|
||||
self.pci_tagged_devices.append(objects.PciDevice.create(None,
|
||||
pci_dev))
|
||||
|
||||
self.pci_untagged_devices = []
|
||||
for dev in range(3):
|
||||
pci_dev = {'compute_node_uuid': 1,
|
||||
'address': '0000:0b:00.%d' % dev,
|
||||
'vendor_id': '1137',
|
||||
'product_id': '0072',
|
||||
'status': 'available',
|
||||
'request_id': None,
|
||||
'dev_type': 'PCI',
|
||||
'parent_addr': None,
|
||||
'numa_node': 0}
|
||||
self.pci_untagged_devices.append(objects.PciDevice.create(None,
|
||||
pci_dev))
|
||||
|
||||
for dev in self.pci_tagged_devices:
|
||||
self.pci_stats.add_device(dev)
|
||||
|
||||
for dev in self.pci_untagged_devices:
|
||||
self.pci_stats.add_device(dev)
|
||||
|
||||
def _assertPoolContent(self, pool, vendor_id, product_id, count, **tags):
|
||||
self.assertEqual(vendor_id, pool['vendor_id'])
|
||||
self.assertEqual(product_id, pool['product_id'])
|
||||
self.assertEqual(count, pool['count'])
|
||||
if tags:
|
||||
for k, v in tags.items():
|
||||
self.assertEqual(v, pool[k])
|
||||
|
||||
def _assertPools(self):
|
||||
# Pools are ordered based on the number of keys. 'product_id',
|
||||
# 'vendor_id' are always part of the keys. When tags are present,
|
||||
# they are also part of the keys. In this test class, we have
|
||||
# two pools with the second one having the tag 'physical_network'
|
||||
# and the value 'physnet1'
|
||||
self.assertEqual(2, len(self.pci_stats.pools))
|
||||
self._assertPoolContent(self.pci_stats.pools[0], '1137', '0072',
|
||||
len(self.pci_untagged_devices))
|
||||
self.assertEqual(self.pci_untagged_devices,
|
||||
self.pci_stats.pools[0]['devices'])
|
||||
self._assertPoolContent(self.pci_stats.pools[1], '1137', '0071',
|
||||
len(self.pci_tagged_devices),
|
||||
physical_network='physnet1')
|
||||
self.assertEqual(self.pci_tagged_devices,
|
||||
self.pci_stats.pools[1]['devices'])
|
||||
|
||||
def test_add_devices(self):
|
||||
self._create_pci_devices()
|
||||
self._assertPools()
|
||||
|
||||
def test_add_device_no_devspec(self):
|
||||
self._create_pci_devices()
|
||||
pci_dev = {'compute_node_uuid': 1,
|
||||
'address': '0000:0c:00.1',
|
||||
'vendor_id': '2345',
|
||||
'product_id': '0172',
|
||||
'status': 'available',
|
||||
'parent_addr': None,
|
||||
'request_id': None}
|
||||
pci_dev_obj = objects.PciDevice.create(None, pci_dev)
|
||||
self.pci_stats.add_device(pci_dev_obj)
|
||||
# There should be no change
|
||||
self.assertIsNone(
|
||||
self.pci_stats._create_pool_keys_from_dev(pci_dev_obj))
|
||||
self._assertPools()
|
||||
|
||||
def test_remove_device_no_devspec(self):
|
||||
self._create_pci_devices()
|
||||
pci_dev = {'compute_node_uuid': 1,
|
||||
'address': '0000:0c:00.1',
|
||||
'vendor_id': '2345',
|
||||
'product_id': '0172',
|
||||
'status': 'available',
|
||||
'parent_addr': None,
|
||||
'request_id': None}
|
||||
pci_dev_obj = objects.PciDevice.create(None, pci_dev)
|
||||
self.pci_stats.remove_device(pci_dev_obj)
|
||||
# There should be no change
|
||||
self.assertIsNone(
|
||||
self.pci_stats._create_pool_keys_from_dev(pci_dev_obj))
|
||||
self._assertPools()
|
||||
|
||||
def test_remove_device(self):
|
||||
self._create_pci_devices()
|
||||
dev1 = self.pci_untagged_devices.pop()
|
||||
self.pci_stats.remove_device(dev1)
|
||||
dev2 = self.pci_tagged_devices.pop()
|
||||
self.pci_stats.remove_device(dev2)
|
||||
self._assertPools()
|
||||
|
||||
|
||||
class PciDeviceVFPFStatsTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PciDeviceVFPFStatsTestCase, self).setUp()
|
||||
white_list = ['{"vendor_id":"8086","product_id":"1528"}',
|
||||
'{"vendor_id":"8086","product_id":"1515"}']
|
||||
self.config(passthrough_whitelist=white_list, group='pci')
|
||||
self.pci_stats = stats.PciDeviceStats()
|
||||
|
||||
def _create_pci_devices(self, vf_product_id=1515, pf_product_id=1528):
|
||||
self.sriov_pf_devices = []
|
||||
for dev in range(2):
|
||||
pci_dev = {'compute_node_uuid': 1,
|
||||
'address': '0000:81:00.%d' % dev,
|
||||
'vendor_id': '8086',
|
||||
'product_id': '%d' % pf_product_id,
|
||||
'status': 'available',
|
||||
'request_id': None,
|
||||
'dev_type': fields.PciDeviceType.SRIOV_PF,
|
||||
'parent_addr': None,
|
||||
'numa_node': 0}
|
||||
dev_obj = objects.PciDevice.create(None, pci_dev)
|
||||
dev_obj.child_devices = []
|
||||
self.sriov_pf_devices.append(dev_obj)
|
||||
|
||||
self.sriov_vf_devices = []
|
||||
for dev in range(8):
|
||||
pci_dev = {'compute_node_uuid': 1,
|
||||
'address': '0000:81:10.%d' % dev,
|
||||
'vendor_id': '8086',
|
||||
'product_id': '%d' % vf_product_id,
|
||||
'status': 'available',
|
||||
'request_id': None,
|
||||
'dev_type': fields.PciDeviceType.SRIOV_VF,
|
||||
'parent_addr': '0000:81:00.%d' % int(dev / 4),
|
||||
'numa_node': 0}
|
||||
dev_obj = objects.PciDevice.create(None, pci_dev)
|
||||
dev_obj.parent_device = self.sriov_pf_devices[int(dev / 4)]
|
||||
dev_obj.parent_device.child_devices.append(dev_obj)
|
||||
self.sriov_vf_devices.append(dev_obj)
|
||||
|
||||
list(map(self.pci_stats.add_device, self.sriov_pf_devices))
|
||||
list(map(self.pci_stats.add_device, self.sriov_vf_devices))
|
Loading…
Reference in New Issue
Block a user