
on 2014/10/04 01:06, Aline Manera wrote:
On 09/30/2014 07:00 AM, Zhou Zheng Sheng wrote:
This patch enbales Kimchi's VM to use host devices directly, and it greatly improves the related device performance. The user can assign PCI, USB and SCSI LUN directly to VM, as long as the host supports one of Intel VT-d, AMD IOMMU or POWER sPAPR technology and runs a recent release of Linux kernel.
This patch adds a sub-collection "hostdevs" to the URI vms/vm-name/. The front-end can GET vms/vm-name/hostdevs and vms/vm-name/hostdevs/dev-name or POST (assign) vms/vm-name/hostdevs and DELETE (dismiss) vms/vm-name/hostdevs/dev-name
The eligible devices to assign are the devices listed by the URI host/devices?_passthrough=1 When assigning a host PCI device to VM, all the eligible PCI devices in the same IOMMU group are also automatically assigned, and vice versa when dismissing a host PIC device from the VM.
Some examples:
Assign a USB device: curl -k -u root -H "Content-Type: application/json" \ -H "Accept: application/json" \ -X POST -d '{"name": "usb_1_1_6"}' \ 'https://127.0.0.1:8001/vms/rhel65/hostdevs'
Assign a PCI device: -d '{"name": "pci_0000_0d_00_0"}'
Assign a SCSI LUN: -d '{"name": "scsi_1_0_0_0"}'
List assigned devices: curl -k -u root -H "Content-Type: application/json" \ -H "Accept: application/json" \ 'https://127.0.0.1:8001/vms/rhel65/hostdevs' The above command should print following. [ { "type":"scsi", "name":"scsi_1_0_0_0" }, { "type":"usb", "name":"usb_1_1_6" }, { "type":"pci", "name":"pci_0000_0d_00_0" }, { "type":"pci", "name":"pci_0000_03_00_0" } ] Notice that the device pci_0000_03_00_0 is also assigned automatically.
The assigned devices are hot-plugged to VM and also written to the domain XML. When it's possible, it enables VFIO for PCI device assignment.
On distribution with old Linux kernel, there are many limitations with PCI passthrough and it's hardly useful. This patch tries to adapt to old kernel but it's better to use a newer kernel with vfio support. Thus this patch also provide a new capability in /config/capabilities.
curl -k -u root -H "Content-Type: application/json" \ -H "Accept: application/json" \ 'https://127.0.0.1:8001/config/capabilities'
The above command should print following. { "blah": "blah", ... "kernel_vfio":true }
Just to make clear for a UI perspective: when "kernel_vfio" is false, we should disable PCI passthrough on UI, right?
Yes. In the Web UI patch, if the front-end sees "kernel_vfio" is false, it only lists the host PCI devices, but prevent user from add or remove them. I can add this information to this commit message.
v1: Handle the devices in the VM template.
v2: Handle the devices in the VM sub-resource "hostdevs".
v3: No change.
v4: Not all domain XMLs contain hostdev node. Deal with the case.
v5: Change _passthrough='1' to _passthrough='true'. When attaching and detaching a device, do not use VIR_DOMAIN_AFFECT_CURRENT flag, instead, use kimchi.model.utils.get_vm_config_flag() to correctly set the device flag.
v11: Add Capability kernel_vfio to indicate if Linux kernel is new enough to support vfio.
Signed-off-by: Zhou Zheng Sheng <zhshzhou@linux.vnet.ibm.com> --- src/kimchi/control/vm/hostdevs.py | 44 ++++++ src/kimchi/featuretests.py | 10 +- src/kimchi/i18n.py | 7 + src/kimchi/model/config.py | 6 +- src/kimchi/model/vmhostdevs.py | 305 ++++++++++++++++++++++++++++++++++++++ src/kimchi/rollbackcontext.py | 3 + 6 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 src/kimchi/control/vm/hostdevs.py create mode 100644 src/kimchi/model/vmhostdevs.py
diff --git a/src/kimchi/control/vm/hostdevs.py b/src/kimchi/control/vm/hostdevs.py new file mode 100644 index 0000000..81fe8ec --- /dev/null +++ b/src/kimchi/control/vm/hostdevs.py @@ -0,0 +1,44 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from kimchi.control.base import Collection, Resource +from kimchi.control.utils import UrlSubNode + + +@UrlSubNode("hostdevs") +class VMHostDevs(Collection): + def __init__(self, model, vmid): + super(VMHostDevs, self).__init__(model) + self.resource = VMHostDev + self.vmid = vmid + self.resource_args = [self.vmid, ] + self.model_args = [self.vmid, ] + + +class VMHostDev(Resource): + def __init__(self, model, vmid, ident): + super(VMHostDev, self).__init__(model, ident) + self.vmid = vmid + self.ident = ident
+ self.info = {}
Why that is needed?
I think it's because I copied the boilerplate code from "src/kimchi/control/vm/ifaces.py" and "src/kimchi/control/vm/storages.py". They have this "self.info = {}". After reading the code, I think it's not needed. I'll remove it.
diff --git a/src/kimchi/model/vmhostdevs.py b/src/kimchi/model/vmhostdevs.py new file mode 100644 index 0000000..0d002b5 --- /dev/null +++ b/src/kimchi/model/vmhostdevs.py @@ -0,0 +1,305 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import glob +import os + +import libvirt +from lxml import etree, objectify + +from kimchi.exception import InvalidOperation, InvalidParameter, NotFoundError +from kimchi.model.config import CapabilitiesModel +from kimchi.model.host import DeviceModel, DevicesModel +from kimchi.model.utils import get_vm_config_flag +from kimchi.model.vms import DOM_STATE_MAP, VMModel +from kimchi.rollbackcontext import RollbackContext +from kimchi.utils import kimchi_log, run_command + + +class VMHostDevsModel(object): + def __init__(self, **kargs): + self.conn = kargs['conn'] + + def get_list(self, vmid): + dom = VMModel.get_vm(vmid, self.conn) + xmlstr = dom.XMLDesc(0) + root = objectify.fromstring(xmlstr) + try: + hostdev = root.devices.hostdev + except AttributeError: + return [] + + return [self._deduce_dev_name(e) for e in hostdev] + + @staticmethod + def _toint(num_str): + if num_str.startswith('0x'): + return int(num_str, 16) + elif num_str.startswith('0'): + return int(num_str, 8) + else: + return int(num_str) +
+ def _deduce_dev_name(self, e): + dev_types = { + 'pci': self._deduce_dev_name_pci, + 'scsi': self._deduce_dev_name_scsi, + 'usb': self._deduce_dev_name_usb, + } + return dev_types[e.attrib['type']](e) +
To avoid having a map for it:
return getattr(self, "_deduce_dev_name_%s" % type)(e)
Thank you! I'll change it.
+ attach_device = { + 'pci': self._attach_pci_device, + 'scsi': self._attach_scsi_device, + 'usb_device': self._attach_usb_device, + }[dev_info['device_type']] + return attach_device(vmid, dev_info) +
Same here.
return getattr(self, "_attach_%s_device" % dev_info['device_type'])(vmid, dev_info)
Yes
+ def _get_pci_device_xml(self, dev_info): + if 'detach_driver' not in dev_info: + dev_info['detach_driver'] = 'kvm' +
+ xmlstr = ''' + <hostdev mode='subsystem' type='pci' managed='yes'> + <source> + <address domain='%(domain)s' bus='%(bus)s' slot='%(slot)s' + function='%(function)s'/> + </source> + <driver name='%(detach_driver)s'/> + </hostdev>''' % dev_info
We are on a movement to use etree.builder to create the XML. It would be good to have it in that way too.
OK.
+ return xmlstr + + @staticmethod + def _validate_pci_passthrough_env(): + # Linux kernel < 3.5 doesn't provide /sys/kernel/iommu_groups + if os.path.isdir('/sys/kernel/iommu_groups'): + if not glob.glob('/sys/kernel/iommu_groups/*'): + raise InvalidOperation("KCHVMHDEV0003E") + + # Enable virt_use_sysfs on RHEL6 and older distributions + # In recent Fedora, there is no virt_use_sysfs. + out, err, rc = run_command(['getsebool', 'virt_use_sysfs']) + if rc == 0 and out.rstrip('\n') != "virt_use_sysfs --> on": + out, err, rc = run_command(['setsebool', '-P', + 'virt_use_sysfs=on']) + if rc != 0: + kimchi_log.warning("Unable to turn on sebool virt_use_sysfs") + + def _attach_pci_device(self, vmid, dev_info): + self._validate_pci_passthrough_env() + + dom = VMModel.get_vm(vmid, self.conn) + # Due to libvirt limitation, we don't support live assigne device to + # vfio driver. + driver = ('vfio' if DOM_STATE_MAP[dom.info()[0]] == "shutoff" and + CapabilitiesModel().kernel_vfio else 'kvm') + + # Attach all PCI devices in the same IOMMU group + dev_model = DeviceModel(conn=self.conn) + devs_model = DevicesModel(conn=self.conn) + affected_names = devs_model.get_list( + _passthrough_affected_by=dev_info['name']) + passthrough_names = devs_model.get_list( + _cap='pci', _passthrough='true') + group_names = list(set(affected_names) & set(passthrough_names)) + pci_infos = [dev_model.lookup(dev_name) for dev_name in group_names] + pci_infos.append(dev_info) + + device_flags = get_vm_config_flag(dom, mode='all') + + with RollbackContext() as rollback: + for pci_info in pci_infos: + pci_info['detach_driver'] = driver + xmlstr = self._get_pci_device_xml(pci_info) + try: + dom.attachDeviceFlags(xmlstr, device_flags) + except libvirt.libvirtError: + kimchi_log.error( + 'Failed to attach host device %s to VM %s: \n%s', + pci_info['name'], vmid, xmlstr) + raise + rollback.prependDefer(dom.detachDeviceFlags, + xmlstr, device_flags) + rollback.commitAll() + + return dev_info['name'] + + def _get_scsi_device_xml(self, dev_info): + xmlstr = '''
+ <hostdev mode='subsystem' type='scsi' sgio='unfiltered'> + <source> + <adapter name='scsi_host%(host)s'/> + <address type='scsi' bus='%(bus)s' target='%(target)s' + unit='%(lun)s'/> + </source> + </hostdev>''' % dev_info
Same here about etree.builder
+ return xmlstr + + def _attach_scsi_device(self, vmid, dev_info): + xmlstr = self._get_scsi_device_xml(dev_info) + dom = VMModel.get_vm(vmid, self.conn) + dom.attachDeviceFlags(xmlstr, get_vm_config_flag(dom, mode='all')) + return dev_info['name'] + + def _get_usb_device_xml(self, dev_info): + xmlstr = ''' + <hostdev mode='subsystem' type='usb' managed='yes'> + <source startupPolicy='optional'> + <vendor id='%s'/> + <product id='%s'/> + <address bus='%s' device='%s'/> + </source> + </hostdev>''' % (dev_info['vendor']['id'], dev_info['product']['id'], + dev_info['bus'], dev_info['device'])
And all other related XML creation =)
Yes.