XenAPI: Add the support for updating the status of the host.
We added a function into the xenhost plugin to get information about PCI devices. Roughly we run the lspci command on dom0. This information will be used to get the list of pci devices that are passed on the pciback.hide dom0 command line. The hide option is used to hide the devices from the normal guest drivers and assign them to the pciback kernel driver at boot on dom0 instead of their normal driver. We will parse the output of the lspci command to find which device is using the pciback kernel driver and thus to know if it has been passed to the pciback.hide option. This information will be used to perform the match with the list of pci devices provided in pci_whitelist into /etc/nova/nova.conf. Implements: blueprint pci-passthrough-xenapi Change-Id: I465fc5d29f3c47ab0079adcfcc2d7d6501bd4b20
This commit is contained in:
parent
5aa0cac98f
commit
d5b8d5b18d
@ -33,7 +33,8 @@ class XenAPIDriverTestCase(stubs.XenAPITestBaseNoDB):
|
||||
'host_hostname': 'somename',
|
||||
'supported_instances': 'x86_64',
|
||||
'host_cpu_info': {'cpu_count': 50},
|
||||
'vcpus_used': 10}
|
||||
'vcpus_used': 10,
|
||||
'pci_passthrough_devices': ''}
|
||||
|
||||
def test_available_resource(self):
|
||||
self.flags(connection_url='test_url',
|
||||
|
@ -2034,6 +2034,18 @@ class XenAPIHostTestCase(stubs.XenAPITestBase):
|
||||
stats = self.conn.get_host_stats(True)
|
||||
self.assertEqual(stats['vcpus_used'], 4)
|
||||
|
||||
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"}]'])
|
||||
stats = self.conn.get_host_stats()
|
||||
self.assertEqual(len(stats['pci_passthrough_devices']), 1)
|
||||
|
||||
def test_pci_passthrough_devices_no_whitelist(self):
|
||||
stats = self.conn.get_host_stats()
|
||||
self.assertEqual(len(stats['pci_passthrough_devices']), 0)
|
||||
|
||||
def test_host_state_missing_sr(self):
|
||||
def fake_safe_find_sr(session):
|
||||
raise exception.StorageRepositoryNotFound('not there')
|
||||
|
@ -59,7 +59,7 @@ class XenAPISession(object):
|
||||
# changed in development environments.
|
||||
# MAJOR VERSION: Incompatible changes with the plugins
|
||||
# MINOR VERSION: Compatible changes, new plguins, etc
|
||||
PLUGIN_REQUIRED_VERSION = '1.1'
|
||||
PLUGIN_REQUIRED_VERSION = '1.2'
|
||||
|
||||
def __init__(self, url, user, pw):
|
||||
import XenAPI
|
||||
|
@ -482,7 +482,9 @@ class XenAPIDriver(driver.ComputeDriver):
|
||||
# arch_filter.py - see libvirt/driver.py get_cpu_info
|
||||
'cpu_info': jsonutils.dumps(host_stats['host_cpu_info']),
|
||||
'supported_instances': jsonutils.dumps(
|
||||
host_stats['supported_instances'])}
|
||||
host_stats['supported_instances']),
|
||||
'pci_passthrough_devices': jsonutils.dumps(
|
||||
host_stats['pci_passthrough_devices'])}
|
||||
|
||||
return dic
|
||||
|
||||
|
@ -689,6 +689,33 @@ class SessionBase(object):
|
||||
def _plugin_xenhost_host_uptime(self, method, args):
|
||||
return jsonutils.dumps({"uptime": "fake uptime"})
|
||||
|
||||
def _plugin_xenhost_get_pci_device_details(self, method, args):
|
||||
"""Simulate the ouput of three pci devices.
|
||||
|
||||
Both of those devices are available for pci passtrough but
|
||||
only one will match with the pci whitelist used in the
|
||||
method test_pci_passthrough_devices_*().
|
||||
Return a single list.
|
||||
|
||||
"""
|
||||
# Driver is not pciback
|
||||
dev_bad1 = ["Slot:\t86:10.0", "Class:\t0604", "Vendor:\t10b5",
|
||||
"Device:\t8747", "Rev:\tba", "Driver:\tpcieport", "\n"]
|
||||
# Driver is pciback but vendor and device are bad
|
||||
dev_bad2 = ["Slot:\t88:00.0", "Class:\t0300", "Vendor:\t0bad",
|
||||
"Device:\tcafe", "SVendor:\t10de", "SDevice:\t100d",
|
||||
"Rev:\ta1", "Driver:\tpciback", "\n"]
|
||||
# Driver is pciback and vendor, device are used for matching
|
||||
dev_good = ["Slot:\t87:00.0", "Class:\t0300", "Vendor:\t10de",
|
||||
"Device:\t11bf", "SVendor:\t10de", "SDevice:\t100d",
|
||||
"Rev:\ta1", "Driver:\tpciback", "\n"]
|
||||
|
||||
lspci_output = "\n".join(dev_bad1 + dev_bad2 + dev_good)
|
||||
return pickle.dumps(lspci_output)
|
||||
|
||||
def _plugin_xenhost_get_pci_type(self, method, args):
|
||||
return pickle.dumps("type-PCI")
|
||||
|
||||
def _plugin_console_get_console_log(self, method, args):
|
||||
dom_id = args["dom_id"]
|
||||
if dom_id == 0:
|
||||
@ -696,7 +723,7 @@ class SessionBase(object):
|
||||
return base64.b64encode(zlib.compress("dom_id: %s" % dom_id))
|
||||
|
||||
def _plugin_nova_plugin_version_get_version(self, method, args):
|
||||
return pickle.dumps("1.1")
|
||||
return pickle.dumps("1.2")
|
||||
|
||||
def _plugin_xenhost_query_gc(self, method, args):
|
||||
return pickle.dumps("False")
|
||||
|
@ -17,6 +17,8 @@
|
||||
Management class for host-related functions (start, reboot, etc).
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from nova.compute import task_states
|
||||
from nova.compute import vm_states
|
||||
from nova import conductor
|
||||
@ -27,6 +29,7 @@ from nova.objects import instance as instance_obj
|
||||
from nova.openstack.common.gettextutils import _
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.pci import pci_whitelist
|
||||
from nova.virt.xenapi import pool_states
|
||||
from nova.virt.xenapi import vm_utils
|
||||
|
||||
@ -149,8 +152,84 @@ class HostState(object):
|
||||
super(HostState, self).__init__()
|
||||
self._session = session
|
||||
self._stats = {}
|
||||
self._pci_device_filter = pci_whitelist.get_pci_devices_filter()
|
||||
self.update_status()
|
||||
|
||||
def _get_passthrough_devices(self):
|
||||
"""Get a list pci devices that are available for pci passthtough.
|
||||
|
||||
We use a plugin to get the output of the lspci command runs on dom0.
|
||||
From this list we will extract pci devices that are using the pciback
|
||||
kernel driver. Then we compare this list to the pci whitelist to get
|
||||
a new list of pci devices that can be used for pci passthrough.
|
||||
|
||||
:returns: a list of pci devices available for pci passthrough.
|
||||
"""
|
||||
def _compile_hex(pattern):
|
||||
"""
|
||||
Return a compiled regular expression pattern into which we have
|
||||
replaced occurences of hex by [\da-fA-F].
|
||||
"""
|
||||
return re.compile(pattern.replace("hex", r"[\da-fA-F]"))
|
||||
|
||||
def _parse_pci_device_string(dev_string):
|
||||
"""
|
||||
Exctract information from the device string about the slot, the
|
||||
vendor and the product ID. The string is as follow:
|
||||
"Slot:\tBDF\nClass:\txxxx\nVendor:\txxxx\nDevice:\txxxx\n..."
|
||||
Return a dictionary with informations about the device.
|
||||
"""
|
||||
slot_regex = _compile_hex(r"Slot:\t"
|
||||
r"((?:hex{4}:)?" # Domain: (optional)
|
||||
r"hex{2}:" # Bus:
|
||||
r"hex{2}\." # Device.
|
||||
r"hex{1})") # Function
|
||||
vendor_regex = _compile_hex(r"\nVendor:\t(hex+)")
|
||||
product_regex = _compile_hex(r"\nDevice:\t(hex+)")
|
||||
|
||||
slot_id = slot_regex.findall(dev_string)
|
||||
vendor_id = vendor_regex.findall(dev_string)
|
||||
product_id = product_regex.findall(dev_string)
|
||||
|
||||
if not slot_id or not vendor_id or not product_id:
|
||||
raise exception.NovaException(
|
||||
_("Failed to parse information about"
|
||||
" a pci device for passthrough"))
|
||||
|
||||
type_pci = self._session.call_plugin_serialized(
|
||||
'xenhost', 'get_pci_type', slot_id[0])
|
||||
|
||||
return {'label': '_'.join(['label',
|
||||
vendor_id[0],
|
||||
product_id[0]]),
|
||||
'vendor_id': vendor_id[0],
|
||||
'product_id': product_id[0],
|
||||
'address': slot_id[0],
|
||||
'dev_id': '_'.join(['pci', slot_id[0]]),
|
||||
'dev_type': type_pci,
|
||||
'status': 'available'}
|
||||
|
||||
# Devices are separated by a blank line. That is why we
|
||||
# use "\n\n" as separator.
|
||||
lspci_out = self._session.call_plugin_serialized(
|
||||
'xenhost', 'get_pci_device_details')
|
||||
pci_list = lspci_out.split("\n\n")
|
||||
|
||||
# For each device of the list, check if it uses the pciback
|
||||
# kernel driver and if it does, get informations and add it
|
||||
# to the list of passthrough_devices. Ignore it if the driver
|
||||
# is not pciback.
|
||||
passthrough_devices = []
|
||||
|
||||
for dev_string_info in pci_list:
|
||||
if "Driver:\tpciback" in dev_string_info:
|
||||
new_dev = _parse_pci_device_string(dev_string_info)
|
||||
|
||||
if self._pci_device_filter.device_assignable(new_dev):
|
||||
passthrough_devices.append(new_dev)
|
||||
|
||||
return passthrough_devices
|
||||
|
||||
def get_host_stats(self, refresh=False):
|
||||
"""Return the current state of the host. If 'refresh' is
|
||||
True, run the update first.
|
||||
@ -196,6 +275,7 @@ class HostState(object):
|
||||
for vm_ref, vm_rec in vm_utils.list_vms(self._session):
|
||||
vcpus_used = vcpus_used + int(vm_rec['VCPUs_max'])
|
||||
data['vcpus_used'] = vcpus_used
|
||||
data['pci_passthrough_devices'] = self._get_passthrough_devices()
|
||||
self._stats = data
|
||||
|
||||
|
||||
|
@ -24,7 +24,8 @@ import utils
|
||||
|
||||
# 1.0 - Initial version.
|
||||
# 1.1 - New call to check GC status
|
||||
PLUGIN_VERSION = "1.1"
|
||||
# 1.2 - Added support for pci passthrough devices
|
||||
PLUGIN_VERSION = "1.2"
|
||||
|
||||
def get_version(session):
|
||||
return PLUGIN_VERSION
|
||||
|
@ -27,8 +27,8 @@ except ImportError:
|
||||
import simplejson as json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import sys
|
||||
import time
|
||||
import xmlrpclib
|
||||
|
||||
import utils
|
||||
@ -403,11 +403,42 @@ def query_gc(session, sr_uuid, vdi_uuid):
|
||||
# Example output: "Currently running: True"
|
||||
return result[19:].strip() == "True"
|
||||
|
||||
def get_pci_device_details(session):
|
||||
"""Returns a string that is a list of pci devices with details.
|
||||
|
||||
This string is obtained by running the command lspci. With -vmm option,
|
||||
it dumps PCI device data in machine readable form. This verbose format
|
||||
display a sequence of records separated by a blank line. We will also
|
||||
use option "-n" to get vendor_id and device_id as numeric values and
|
||||
the "-k" option to get the kernel driver used if any.
|
||||
"""
|
||||
return _run_command(["lspci", "-vmmnk"])
|
||||
|
||||
|
||||
def get_pci_type(session, pci_device):
|
||||
"""Returns the type of the PCI device (type-PCI, type-VF or type-PF).
|
||||
|
||||
pci-device -- The address of the pci device
|
||||
"""
|
||||
# We need to add the domain if it is missing
|
||||
if pci_device.count(':') == 1:
|
||||
pci_device = "0000:" + pci_device
|
||||
output = _run_command(["ls", "/sys/bus/pci/devices/" + pci_device + "/"])
|
||||
|
||||
if "physfn" in output:
|
||||
return "type-VF"
|
||||
if "virtfn" in output:
|
||||
return "type-PF"
|
||||
return "type-PCI"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Support both serialized and non-serialized plugin approaches
|
||||
_, methodname = xmlrpclib.loads(sys.argv[1])
|
||||
if methodname in ['query_gc']:
|
||||
utils.register_plugin_calls(query_gc)
|
||||
if methodname in ['query_gc', 'get_pci_device_details', 'get_pci_type']:
|
||||
utils.register_plugin_calls(query_gc,
|
||||
get_pci_device_details,
|
||||
get_pci_type)
|
||||
|
||||
XenAPIPlugin.dispatch(
|
||||
{"host_data": host_data,
|
||||
|
Loading…
Reference in New Issue
Block a user