Add PCI device filters support
Cloud administrator may decide the devices that can be assigned to VM based on vendor_id or device_id etc. This is whitelist based filter. No device will be assigned except explicitly defined. The configuration option 'pci_passthrough_whitelist' can be defined multi times, each for one type of device. bp:pci-passthrough-base Change-Id: I03f2a0c098198a32abedff0773e9f94e3d7d2f28 Signed-off-by: Yunhong Jiang <yunhong.jiang@intel.com> Signed-off-by: Yongli He <yongli.he@intel.com>
This commit is contained in:
parent
45f1c598b7
commit
5d38362957
|
@ -1677,6 +1677,16 @@
|
|||
#pci_alias=
|
||||
|
||||
|
||||
#
|
||||
# Options defined in nova.pci.pci_whitelist
|
||||
#
|
||||
|
||||
# White list of PCI devices available to VMs. For example:
|
||||
# pci_passthrough_whitelist = [{"vendor_id": "8086",
|
||||
# "product_id": "0443"}] (multi valued)
|
||||
#pci_passthrough_whitelist=
|
||||
|
||||
|
||||
#
|
||||
# Options defined in nova.scheduler.driver
|
||||
#
|
||||
|
|
|
@ -1419,3 +1419,7 @@ class MissingParameter(NovaException):
|
|||
ec2_code = 'MissingParameter'
|
||||
msg_fmt = _("Not enough parameters: %(reason)s")
|
||||
code = 400
|
||||
|
||||
|
||||
class PciConfigInvalidWhitelist(Invalid):
|
||||
mst_fmt = _("Invalid PCI devices Whitelist config %(reason)s")
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2013 Intel, Inc.
|
||||
# Copyright (c) 2013 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 jsonschema
|
||||
|
||||
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 oslo.config import cfg
|
||||
|
||||
pci_opts = [cfg.MultiStrOpt('pci_passthrough_whitelist',
|
||||
default=[],
|
||||
help='White list of PCI devices available to VMs. '
|
||||
'For example: pci_passthrough_whitelist = '
|
||||
'[{"vendor_id": "8086", "product_id": "0443"}]'
|
||||
)
|
||||
]
|
||||
CONF = cfg.CONF
|
||||
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.
|
||||
|
||||
Not all devices on compute node can be assigned to guest, the
|
||||
cloud administrator decides the devices that can be assigned
|
||||
based on vendor_id or product_id etc. If no white list specified,
|
||||
no device will be assignable.
|
||||
"""
|
||||
|
||||
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=str(e))
|
||||
|
||||
return specs
|
||||
|
||||
def __init__(self, whitelist_spec=None):
|
||||
"""White list constructor
|
||||
|
||||
For example, followed json string specifies that devices whose
|
||||
vendor_id is '8086' and product_id is '1520' can be assigned
|
||||
to guest.
|
||||
'[{"product_id":"1520", "vendor_id":"8086"}]'
|
||||
|
||||
:param whitelist_spec: A json string for a list of dictionaries,
|
||||
each dictionary specifies the pci device
|
||||
properties requirement.
|
||||
"""
|
||||
super(PciHostDevicesWhiteList, self).__init__()
|
||||
if whitelist_spec:
|
||||
self.spec = self._parse_white_list_from_config(whitelist_spec)
|
||||
else:
|
||||
self.spec = None
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def get_pci_devices_filter():
|
||||
return PciHostDevicesWhiteList(CONF.pci_passthrough_whitelist)
|
|
@ -0,0 +1,99 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2012 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 nova import exception
|
||||
from nova.objects import pci_device
|
||||
from nova.pci import pci_whitelist
|
||||
from nova import test
|
||||
|
||||
|
||||
dev_dict = {
|
||||
'compute_node_id': 1,
|
||||
'address': 'a',
|
||||
'product_id': '0001',
|
||||
'vendor_id': '8086',
|
||||
'status': 'available',
|
||||
}
|
||||
|
||||
|
||||
class PciHostDevicesWhiteListTestCase(test.TestCase):
|
||||
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"}]'
|
||||
parsed = pci_whitelist.PciHostDevicesWhiteList([white_list])
|
||||
self.assertEqual(parsed.spec, [{'vendor_id': '8086',
|
||||
'product_id': '0001'}])
|
||||
|
||||
def test_whitelist_empty(self):
|
||||
dev = pci_device.PciDevice.create(dev_dict)
|
||||
parsed = pci_whitelist.PciHostDevicesWhiteList()
|
||||
self.assertEqual(parsed.device_assignable(dev), False)
|
||||
|
||||
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'}])
|
||||
|
||||
def test_device_assignable(self):
|
||||
dev = pci_device.PciDevice.create(dev_dict)
|
||||
white_list = '[{"product_id":"0001", "vendor_id":"8086"}]'
|
||||
parsed = pci_whitelist.PciHostDevicesWhiteList([white_list])
|
||||
self.assertEqual(parsed.device_assignable(dev), True)
|
||||
|
||||
def test_device_assignable_multiple(self):
|
||||
dev = pci_device.PciDevice.create(dev_dict)
|
||||
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)
|
||||
|
||||
def test_get_pci_devices_filter(self):
|
||||
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 = pci_device.PciDevice.create(dev_dict)
|
||||
self.assertEqual(pci_filter.device_assignable(dev), True)
|
Loading…
Reference in New Issue