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:
guillaume-thouvenin 2013-12-23 21:07:48 +01:00
parent 5aa0cac98f
commit d5b8d5b18d
8 changed files with 162 additions and 8 deletions

View File

@ -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',

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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,