[PATCH] [Kimchi 0/4] Issue #817 - PCI attach/detach improvements.

From: Paulo Vital <pvital@linux.vnet.ibm.com> This is the backend part of the solution to Issue #817. In this solution the support for handle Libvirt events is added, as well, the attach and detach functions were modified to return AsyncTasks (then frontend can be informed when a device is really attached/detached). Patch "Add support to Libvirt Events." is the third version of previous submitted patch. Patch "Make detach device return an AsyncTask" needs submitted Wok patch "Update AsyncResource class with delete method." to work. Paulo Vital (4): Add support to Libvirt Events. Add Events support to VM's Devices attach/detach. Make attach device return an AsyncTask Make detach device return an AsyncTask control/vm/hostdevs.py | 6 +-- i18n.py | 4 ++ model/libvirtevents.py | 124 +++++++++++++++++++++++++++++++++++++++++++++ model/model.py | 6 ++- model/vmhostdevs.py | 135 ++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 254 insertions(+), 21 deletions(-) create mode 100644 model/libvirtevents.py -- 2.5.5

From: Paulo Vital <pvital@linux.vnet.ibm.com> This patch adds support to handle in Kimchi any Libvirt Event, just by adding a callback to process the event and register the event with the callback. A method called register_common_events() is responsible to register the common domain events to use a generic callback that logs into error log, the event happened and it's details. This patch is part of the solution for Kimchi Issue #817 Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- i18n.py | 4 ++ model/libvirtevents.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++++ model/model.py | 6 ++- 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 model/libvirtevents.py diff --git a/i18n.py b/i18n.py index 7cce796..8fa233f 100644 --- a/i18n.py +++ b/i18n.py @@ -324,4 +324,8 @@ messages = { "KCHLVMS0001E": _("Invalid volume group name parameter: %(name)s."), + "KCHEVENT0001E": _("Failed to register the default event implementation."), + "KCHEVENT0002E": _("Failed to register timeout event."), + "KCHEVENT0003E": _("Failed to Run the default event implementation."), + } diff --git a/model/libvirtevents.py b/model/libvirtevents.py new file mode 100644 index 0000000..041f126 --- /dev/null +++ b/model/libvirtevents.py @@ -0,0 +1,124 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2016 +# +# 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 cherrypy +import libvirt +import time + +from wok.exception import OperationFailed +from wok.utils import wok_log + + +class LibvirtEvents(object): + def __init__(self): + # Register default implementation of event handlers + if libvirt.virEventRegisterDefaultImpl() < 0: + raise OperationFailed('KCHEVENT0001E') + + # Run a background thread with the event loop. Using cherrypy + # BackgroundTask class due to issues when using threading module with + # cherrypy. + self.event_loop_thread = cherrypy.process.plugins.BackgroundTask( + 2, + self._event_loop_run + ) + self.event_loop_thread.setName('KimchiLibvirtEventLoop') + self.event_loop_thread.setDaemon(True) + self.event_loop_thread.start() + + # Set an event timeout to control the self._event_loop_run + if libvirt.virEventAddTimeout(0, self._kimchi_EventTimeout, None) < 0: + raise OperationFailed('KCHEVENT0002E') + + # Event loop method to be executed in background as thread + def _event_loop_run(self): + while True: + if libvirt.virEventRunDefaultImpl() < 0: + raise OperationFailed('KCHEVENT0003E') + + def is_event_loop_alive(self): + return self.event_loop_thread.isAlive() + + # Event loop handler used to limit length of waiting for any other event. + def _kimchi_EventTimeout(self, timer, opaque): + time.sleep(1) + + def domain_event_lifecycle_cb(self, conn, dom, event, detail, *args): + """ + Callback to handle Domain (VMs) events - VM Livecycle. + """ + evStrings = ("Defined", "Undefined", "Started", "Suspended", "Resumed", + "Stopped", "Shutdown", "PMSuspended", "Crashed") + evDetails = (("Added", "Updated"), + ("Removed", ), + ("Booted", "Migrated", "Restored", "Snapshot", "Wakeup"), + ("Paused", "Migrated", "IOError", "Watchdog", "Restored", + "Snapshot", "API error"), + ("Unpaused", "Migrated", "Snapshot"), + ("Shutdown", "Destroyed", "Crashed", "Migrated", "Saved", + "Failed", "Snapshot"), + ("Finished", ), + ("Memory", "Disk"), + ("Panicked")) + msg = "Libvirt Event: Domain %s %s %s" % (dom.name(), evStrings[event], + evDetails[event][detail]) + wok_log.error(msg) + + def domain_event_reboot_cb(self, conn, dom, *args): + """ + Callback to handle Domain (VMs) events - VM Reboot. + """ + msg = "Libvirt Event: Domain %s rebooted" % dom.name() + wok_log.error(msg) + + def domain_event_pmwakeup_cb(self, conn, dom, reason, *args): + """ + Callback to handle Domain (VMs) events - VM PM WakeUp. + """ + msg = "Libvirt Event: Domain %s system pmwakeup" % dom.name() + wok_log.error(msg) + + def domain_event_pmsuspend_cb(self, conn, dom, reason, *args): + """ + Callback to handle Domain (VMs) events - VM PM Suspend. + """ + msg = "Libvirt Event: Domain %s system pmsuspend" % dom.name() + wok_log.error(msg) + + def register_common_domain_events(self, conn): + """ + Register the most common Libvirt domain events to be handled. + """ + conn = conn.get() + conn.domainEventRegisterAny(None, + libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, + self.domain_event_lifecycle_cb, + None) + conn.domainEventRegisterAny(None, + libvirt.VIR_DOMAIN_EVENT_ID_REBOOT, + self.domain_event_reboot_cb, + None) + conn.domainEventRegisterAny(None, + libvirt.VIR_DOMAIN_EVENT_ID_PMWAKEUP, + self.domain_event_pmwakeup_cb, + None) + conn.domainEventRegisterAny(None, + libvirt.VIR_DOMAIN_EVENT_ID_PMSUSPEND, + self.domain_event_pmsuspend_cb, + None) diff --git a/model/model.py b/model/model.py index e44f804..aeb8b7d 100644 --- a/model/model.py +++ b/model/model.py @@ -26,6 +26,7 @@ from wok.plugins.kimchi import config from wok.utils import import_module, listPathModules from wok.plugins.kimchi.model.libvirtconnection import LibvirtConnection +from wok.plugins.kimchi.model.libvirtevents import LibvirtEvents class Model(BaseModel): @@ -43,8 +44,11 @@ class Model(BaseModel): return instances self.objstore = ObjectStore(objstore_loc or config.get_object_store()) + self.events = LibvirtEvents() self.conn = LibvirtConnection(libvirt_uri) - kargs = {'objstore': self.objstore, 'conn': self.conn} + self.events.register_common_domain_events(self.conn) + kargs = {'objstore': self.objstore, 'conn': self.conn, + 'eventsloop': self.events} models = [] # Import task model from Wok -- 2.5.5

From: Paulo Vital <pvital@linux.vnet.ibm.com> Register libvirt events to handle and add callbacks to log information about device's add and remove into VMs. This patch is part of the solution for Kimchi Issue #817 Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- model/vmhostdevs.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/model/vmhostdevs.py b/model/vmhostdevs.py index 5f33d10..c40c5c2 100644 --- a/model/vmhostdevs.py +++ b/model/vmhostdevs.py @@ -45,7 +45,9 @@ WINDOW_SIZE_BAR = 0x800000000 class VMHostDevsModel(object): def __init__(self, **kargs): self.conn = kargs['conn'] + self.events = kargs['eventsloop'] self.caps = CapabilitiesModel(**kargs) + self._register_device_event() def get_list(self, vmid): dom = VMModel.get_vm(vmid, self.conn) @@ -371,6 +373,36 @@ class VMHostDevsModel(object): dom.attachDeviceFlags(xmlstr, get_vm_config_flag(dom, mode='all')) return dev_info['name'] + def _vm_event_device_added_cb(self, conn, dom, dev, opaque): + """ + Callback to register event when device is added to domain + """ + msg = "Device %s added to domain %s" % (dev, dom.name()) + wok_log.error(msg) + + def _vm_event_device_removed_cb(self, conn, dom, dev, opaque): + """ + Callback to register event when device is removed from a domain + """ + msg = "Device %s removed from domain %s" % (dev, dom.name()) + wok_log.error(msg) + + def _register_device_event(self): + """ + Register Libvirt domain events related to add/remove devices. + """ + conn = self.conn.get() + dev_events = {'add': [libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_ADDED, + self._vm_event_device_added_cb], + 'remove':[libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED, + self._vm_event_device_removed_cb]} + try: + for event in dev_events.keys(): + conn.domainEventRegisterAny(None, dev_events[event][0], + dev_events[event][1], None) + except Exception, e: + wok_log.error("Error registering domain event: ", e) + class VMHostDevModel(object): def __init__(self, **kargs): -- 2.5.5

From: Paulo Vital <pvital@linux.vnet.ibm.com> Modified VMHostDevs class to be an AsyncCollection and return an AsyncTask when creating a new resource (a.k.a. attach a device into a VM). This patch is part of the solution for Kimchi Issue #817 Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- control/vm/hostdevs.py | 4 +-- model/vmhostdevs.py | 79 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/control/vm/hostdevs.py b/control/vm/hostdevs.py index 8a82db0..b0c4d1d 100644 --- a/control/vm/hostdevs.py +++ b/control/vm/hostdevs.py @@ -17,7 +17,7 @@ # 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 wok.control.base import Collection, Resource +from wok.control.base import AsyncCollection, Resource from wok.control.utils import UrlSubNode @@ -33,7 +33,7 @@ VMHOSTDEV_REQUESTS = { @UrlSubNode("hostdevs") -class VMHostDevs(Collection): +class VMHostDevs(AsyncCollection): def __init__(self, model, vmid): super(VMHostDevs, self).__init__(model) self.resource = VMHostDev diff --git a/model/vmhostdevs.py b/model/vmhostdevs.py index c40c5c2..a3dfef3 100644 --- a/model/vmhostdevs.py +++ b/model/vmhostdevs.py @@ -27,8 +27,9 @@ from operator import itemgetter from wok.exception import InvalidOperation, InvalidParameter, NotFoundError from wok.exception import OperationFailed +from wok.model.tasks import TaskModel from wok.rollbackcontext import RollbackContext -from wok.utils import run_command, wok_log +from wok.utils import add_task, run_command, wok_log from wok.plugins.kimchi.model.config import CapabilitiesModel from wok.plugins.kimchi.model.host import DeviceModel, DevicesModel @@ -46,7 +47,9 @@ class VMHostDevsModel(object): def __init__(self, **kargs): self.conn = kargs['conn'] self.events = kargs['eventsloop'] + self.objstore = kargs['objstore'] self.caps = CapabilitiesModel(**kargs) + self.task = TaskModel(**kargs) self._register_device_event() def get_list(self, vmid): @@ -72,7 +75,11 @@ class VMHostDevsModel(object): dev_info = DeviceModel(conn=self.conn).lookup(dev_name) if dev_info['device_type'] == 'pci': - return self._attach_pci_device(vmid, dev_info) + taskid = add_task(u'/plugins/kimchi/vms/%s/hostdevs/' % + VMModel.get_vm(vmid, self.conn).name(), + self._attach_pci_device, self.objstore, + {'vmid': vmid, 'dev_info': dev_info}) + return self.task.lookup(taskid) with RollbackContext() as rollback: try: @@ -83,13 +90,14 @@ class VMHostDevsModel(object): else: rollback.prependDefer(dev.reAttach) - attach_device = getattr( - self, '_attach_%s_device' % dev_info['device_type']) - - info = attach_device(vmid, dev_info) rollback.commitAll() - return info + taskid = add_task(u'/plugins/kimchi/vms/%s/hostdevs/' % + VMModel.get_vm(vmid, self.conn).name(), + '_attach_%s_device' % dev_info['device_type'], + self.objstore, {'vmid': vmid, 'dev_info': dev_info}) + + return self.task.lookup(taskid) def _get_pci_device_xml(self, dev_info, slot, is_multifunction): if 'detach_driver' not in dev_info: @@ -164,7 +172,10 @@ class VMHostDevsModel(object): return free+1 - def _attach_pci_device(self, vmid, dev_info): + def _attach_pci_device(self, cb, params): + cb('Attaching PCI device') + vmid = params['vmid'] + dev_info = params['dev_info'] self._validate_pci_passthrough_env() dom = VMModel.get_vm(vmid, self.conn) @@ -229,10 +240,12 @@ class VMHostDevsModel(object): with RollbackContext() as rollback: for pci_info in pci_infos: pci_info['detach_driver'] = driver + cb('Reading source device XML') xmlstr = self._get_pci_device_xml(pci_info, slot, is_multifunction) try: + cb('Attaching device to VM') dom.attachDeviceFlags(xmlstr, device_flags) except libvirt.libvirtError: wok_log.error( @@ -243,7 +256,7 @@ class VMHostDevsModel(object): xmlstr, device_flags) rollback.commitAll() - return dev_info['name'] + cb('OK', True) def _count_3D_devices_attached(self, dom): counter = 0 @@ -350,11 +363,27 @@ class VMHostDevsModel(object): mode='subsystem', type='scsi', sgio='unfiltered') return etree.tostring(host_dev) - def _attach_scsi_device(self, vmid, dev_info): - xmlstr = self._get_scsi_device_xml(dev_info) + def _attach_scsi_device(self, cb, params): + cb('Attaching SCSI device...') + vmid = params['vmid'] + dev_info = params['dev_info'] dom = VMModel.get_vm(vmid, self.conn) - dom.attachDeviceFlags(xmlstr, get_vm_config_flag(dom, mode='all')) - return dev_info['name'] + + with RollbackContext() as rollback: + cb('Reading source device XML') + xmlstr = self._get_scsi_device_xml(dev_info) + try: + cb('Attaching device to VM') + dom.attachDeviceFlags(xmlstr, get_vm_config_flag(dom, + mode='all')) + except libvirt.libvirtError: + wok_log.error('Failed to attach host device %s to VM %s: \n%s', + dev_info['name'], vmid, xmlstr) + raise + rollback.prependDefer(dom.detachDeviceFlags, xmlstr, device_flags) + rollback.commitAll() + + cb('OK', True) def _get_usb_device_xml(self, dev_info): source = E.source( @@ -367,11 +396,27 @@ class VMHostDevsModel(object): ype='usb', managed='yes') return etree.tostring(host_dev) - def _attach_usb_device(self, vmid, dev_info): - xmlstr = self._get_usb_device_xml(dev_info) + def _attach_usb_device(self, cb, params): + cb('Attaching USB device...') + vmid = params['vmid'] + dev_info = params['dev_info'] dom = VMModel.get_vm(vmid, self.conn) - dom.attachDeviceFlags(xmlstr, get_vm_config_flag(dom, mode='all')) - return dev_info['name'] + + with RollbackContext() as rollback: + cb('Reading source device XML') + xmlstr = self._get_usb_device_xml(dev_info) + try: + cb('Attaching device to VM') + dom.attachDeviceFlags(xmlstr, get_vm_config_flag(dom, + mode='all')) + except libvirt.libvirtError: + wok_log.error('Failed to attach host device %s to VM %s: \n%s', + dev_info['name'], vmid, xmlstr) + raise + rollback.prependDefer(dom.detachDeviceFlags, xmlstr, device_flags) + rollback.commitAll() + + cb('OK', True) def _vm_event_device_added_cb(self, conn, dom, dev, opaque): """ -- 2.5.5

From: Paulo Vital <pvital@linux.vnet.ibm.com> Modified VMHostDev class to be an AsyncResource and return an AsyncTask when deleting a specific resource (a.k.a. detach a device from a VM). This patch is part of the solution for Kimchi Issue #817 Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- control/vm/hostdevs.py | 4 ++-- model/vmhostdevs.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/control/vm/hostdevs.py b/control/vm/hostdevs.py index b0c4d1d..e9fd5dc 100644 --- a/control/vm/hostdevs.py +++ b/control/vm/hostdevs.py @@ -17,7 +17,7 @@ # 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 wok.control.base import AsyncCollection, Resource +from wok.control.base import AsyncCollection, AsyncResource from wok.control.utils import UrlSubNode @@ -46,7 +46,7 @@ class VMHostDevs(AsyncCollection): }) -class VMHostDev(Resource): +class VMHostDev(AsyncResource): def __init__(self, model, vmid, ident): super(VMHostDev, self).__init__(model, ident) self.vmid = vmid diff --git a/model/vmhostdevs.py b/model/vmhostdevs.py index a3dfef3..8122f55 100644 --- a/model/vmhostdevs.py +++ b/model/vmhostdevs.py @@ -452,6 +452,8 @@ class VMHostDevsModel(object): class VMHostDevModel(object): def __init__(self, **kargs): self.conn = kargs['conn'] + self.objstore = kargs['objstore'] + self.task = TaskModel(**kargs) def lookup(self, vmid, dev_name): dom = VMModel.get_vm(vmid, self.conn) @@ -489,6 +491,23 @@ class VMHostDevModel(object): raise NotFoundError('KCHVMHDEV0001E', {'vmid': vmid, 'dev_name': dev_name}) + task_params = {'vmid': vmid, + 'dev_name': dev_name, + 'dom': dom, + 'hostdev': hostdev} + task_uri = u'/plugins/kimchi/vms/%s/hostdevs/%s' % \ + (VMModel.get_vm(vmid, self.conn).name(), dev_name) + taskid = add_task(task_uri, self._detach_device, self.objstore, + task_params) + return self.task.lookup(taskid) + + def _detach_device(self, cb, params): + cb('Detaching device.') + vmid = params['vmid'] + dev_name = params['dev_name'] + dom = params['dom'] + hostdev = params['hostdev'] + pci_devs = [(DeviceModel.deduce_dev_name(e, self.conn), e) for e in hostdev if e.attrib['type'] == 'pci'] @@ -502,11 +521,14 @@ class VMHostDevModel(object): for e in hostdev: if DeviceModel.deduce_dev_name(e, self.conn) == dev_name: xmlstr = etree.tostring(e) + cb('Detaching device from VM...') dom.detachDeviceFlags( xmlstr, get_vm_config_flag(dom, mode='all')) if e.attrib['type'] == 'pci': + cb('Deleting affected PCI devices...') self._delete_affected_pci_devices(dom, dev_name, pci_devs) if is_3D_device: + cb('Updating MMIO from VM...') devsmodel = VMHostDevsModel(conn=self.conn) devsmodel.update_mmio_guest(vmid, False) break @@ -514,6 +536,8 @@ class VMHostDevModel(object): raise NotFoundError('KCHVMHDEV0001E', {'vmid': vmid, 'dev_name': dev_name}) + cb('OK', True) + def _delete_affected_pci_devices(self, dom, dev_name, pci_devs): dev_model = DeviceModel(conn=self.conn) try: -- 2.5.5

Do not consider this patch-set. I found PEP8 errors and will submit a new version On Apr 14 04:49PM, pvital@linux.vnet.ibm.com wrote:
From: Paulo Vital <pvital@linux.vnet.ibm.com>
This is the backend part of the solution to Issue #817. In this solution the support for handle Libvirt events is added, as well, the attach and detach functions were modified to return AsyncTasks (then frontend can be informed when a device is really attached/detached).
Patch "Add support to Libvirt Events." is the third version of previous submitted patch.
Patch "Make detach device return an AsyncTask" needs submitted Wok patch "Update AsyncResource class with delete method." to work.
Paulo Vital (4): Add support to Libvirt Events. Add Events support to VM's Devices attach/detach. Make attach device return an AsyncTask Make detach device return an AsyncTask
control/vm/hostdevs.py | 6 +-- i18n.py | 4 ++ model/libvirtevents.py | 124 +++++++++++++++++++++++++++++++++++++++++++++ model/model.py | 6 ++- model/vmhostdevs.py | 135 ++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 254 insertions(+), 21 deletions(-) create mode 100644 model/libvirtevents.py
-- 2.5.5
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel
-- Paulo Ricardo Paz Vital Linux Technology Center, IBM Systems http://www.ibm.com/linux/ltc/
participants (2)
-
Paulo Ricardo Paz Vital
-
pvital@linux.vnet.ibm.com