asyncjob: Simplify error reporting

This commit is contained in:
Cole Robinson 2010-12-10 09:57:42 -05:00
parent a1e54f8476
commit d0d20148f7
16 changed files with 221 additions and 279 deletions

View File

@ -989,25 +989,35 @@ class vmmAddHardware(vmmGObjectUI):
######################
def setup_device(self):
if (self._dev.virtual_device_type ==
if (self._dev.virtual_device_type !=
virtinst.VirtualDevice.VIRTUAL_DEV_DISK):
progWin = vmmAsyncJob(self.do_file_allocate,
self._dev.setup_dev(self.conn.vmm)
return
def do_file_allocate(asyncjob, disk):
meter = vmmCreateMeter(asyncjob)
# If creating disk via storage API, we need to thread
# off a new connection
if disk.vol_install:
newconn = util.dup_lib_conn(disk.conn)
disk.conn = newconn
logging.debug("Starting background file allocate process")
disk.setup_dev(self.conn.vmm, meter=meter)
logging.debug("Allocation completed")
progWin = vmmAsyncJob(do_file_allocate,
[self._dev],
title=_("Creating Storage File"),
text=_("Allocation of disk storage may take "
"a few minutes to complete."))
progWin.run()
error, details = progWin.get_error()
if error:
return (error, details)
return progWin.run()
else:
self._dev.setup_dev(self.conn.vmm)
def add_device(self):
ret = self.setup_device()
if ret:
if ret and ret[0]:
# Encountered an error
return (True, ret)
@ -1046,24 +1056,6 @@ class vmmAddHardware(vmmGObjectUI):
return (False, None)
def do_file_allocate(self, disk, asyncjob):
meter = vmmCreateMeter(asyncjob)
newconn = None
try:
# If creating disk via storage API, we need to thread
# off a new connection
if disk.vol_install:
newconn = util.dup_lib_conn(disk.conn)
disk.conn = newconn
logging.debug("Starting background file allocate process")
disk.setup_dev(self.conn.vmm, meter=meter)
logging.debug("Allocation completed")
except Exception, e:
details = (_("Unable to complete install: '%s'") %
"".join(traceback.format_exc()))
error = _("Unable to complete install: '%s'") % str(e)
asyncjob.set_error(error, details)
###########################
# Page validation methods #

View File

@ -20,6 +20,8 @@
import logging
import threading
import traceback
import gtk
import gobject
@ -30,38 +32,35 @@ from virtManager.baseclass import vmmGObjectUI
# code in the run() method every now & then
class asyncJobWorker(threading.Thread):
def __init__(self, callback, args):
threading.Thread.__init__(self, target=callback, args=args)
args = [callback] + args
threading.Thread.__init__(self, target=cb_wrapper, args=args)
def run(self):
threading.Thread.run(self)
def cb_wrapper(callback, asyncjob, *args, **kwargs):
try:
callback(asyncjob, *args, **kwargs)
except Exception, e:
asyncjob.set_error(e, "".join(traceback.format_exc()))
# Displays a progress bar while executing the "callback" method.
class vmmAsyncJob(vmmGObjectUI):
def __init__(self, callback, args=None,
text=_("Please wait a few moments..."),
title=_("Operation in progress"),
def __init__(self, callback, args, title, text,
run_main=True, cancel_back=None, cancel_args=None):
vmmGObjectUI.__init__(self, "vmm-progress.glade", "vmm-progress")
self.topwin.set_title(title)
self.window.signal_autoconnect({
"on_async_job_delete_event" : self.delete,
"on_async_job_cancel_clicked" : self.cancel,
})
self.run_main = bool(run_main)
self.cancel_job = cancel_back
self.cancel_args = cancel_args or []
self.cancel_args.append(self)
self.cancel_args = [self] + self.cancel_args
if self.cancel_job:
self.window.get_widget("cancel-async-job").show()
else:
self.window.get_widget("cancel-async-job").hide()
self.job_canceled = False
# Callback sets this if there is an error
self._error_info = None
self._data = None
@ -69,11 +68,18 @@ class vmmAsyncJob(vmmGObjectUI):
self.pbar = self.window.get_widget("pbar")
self.window.get_widget("pbar-text").set_text(text)
args.append(self)
args = [self] + args
self.bg_thread = asyncJobWorker(callback, args)
self.bg_thread.setDaemon(True)
self.is_pulsing = True
self.window.signal_autoconnect({
"on_async_job_delete_event" : self.delete,
"on_async_job_cancel_clicked" : self.cancel,
})
self.topwin.set_title(title)
def run(self):
timer = util.safe_timeout_add(100, self.exit_if_necessary)
self.topwin.present()
@ -98,6 +104,7 @@ class vmmAsyncJob(vmmGObjectUI):
self.exit_if_necessary(force_exit=True)
self.topwin.destroy()
return self._get_error()
def delete(self, ignore1=None, ignore2=None):
thread_active = (self.bg_thread.isAlive() or not self.run_main)
@ -175,7 +182,7 @@ class vmmAsyncJob(vmmGObjectUI):
def set_error(self, error, details):
self._error_info = (error, details)
def get_error(self):
def _get_error(self):
if not self._error_info:
return (None, None)
return self._error_info

View File

@ -38,7 +38,7 @@ class vmmGObject(gobject.GObject):
def get_hal_helper(self):
from virtManager import halhelper
return virtManager.halhelper.get_hal_helper()
return halhelper.get_hal_helper()
class vmmGObjectUI(vmmGObject):
def __init__(self, filename, windowname):

View File

@ -709,26 +709,23 @@ class vmmCloneVM(vmmGObjectUI):
if self.clone_design.clone_devices:
text = title + _(" and selected storage (this may take a while)")
progWin = vmmAsyncJob(self._async_clone, [],
title=title, text=text)
progWin.run()
error, details = progWin.get_error()
progWin = vmmAsyncJob(self._async_clone, [], title, text)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
if error is not None:
self.err.show_err(error, details)
error = (_("Error creating virtual machine clone '%s': %s") %
(self.clone_design.clone_name, error))
self.err.show_err(error, error + "\n" + details)
else:
self.close()
self.conn.tick(noStatsUpdate=True)
def _async_clone(self, asyncjob):
newconn = None
error = None
details = None
try:
try:
self.orig_vm.set_cloning(True)
@ -746,15 +743,6 @@ class vmmCloneVM(vmmGObjectUI):
finally:
self.orig_vm.set_cloning(False)
except Exception, e:
error = (_("Error creating virtual machine clone '%s': %s") %
(self.clone_design.clone_name, str(e)))
details = "".join(traceback.format_exc())
if error:
asyncjob.set_error(error, details)
return
def change_storage_browse(self, ignore):
cs = self.change_storage_window

View File

@ -21,7 +21,6 @@
import gobject
import gtk
import sys
import time
import traceback
import threading
@ -1583,16 +1582,14 @@ class vmmCreate(vmmGObjectUI):
"and retrieval of the installation "
"images may take a few minutes to "
"complete."))
progWin.run()
error, details = progWin.get_error()
if error != None:
self.err.show_err(error, details)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
if error:
error = (_("Unable to complete install: '%s'") % error)
self.err.show_err(error, error + "\n" + details)
self.failed_guest = self.guest
return
@ -1610,23 +1607,16 @@ class vmmCreate(vmmGObjectUI):
self.close()
def do_install(self, guest, asyncjob):
def do_install(self, asyncjob, guest):
meter = vmmCreateMeter(asyncjob)
error = None
details = None
try:
logging.debug("Starting background install process")
guest.conn = util.dup_conn(self.conn).vmm
for dev in guest.get_all_devices():
dev.conn = guest.conn
dom = guest.start_install(False, meter = meter)
if dom == None:
error = _("Guest installation failed to complete")
details = error
logging.error("Guest install did not return a domain")
else:
guest.start_install(False, meter = meter)
logging.debug("Install completed")
# Make sure we pick up the domain object
@ -1644,17 +1634,6 @@ class vmmCreate(vmmGObjectUI):
util.connect_opt_out(vm, "status-changed",
self.check_install_status, guest)
except:
(_type, value, stacktrace) = sys.exc_info ()
# Detailed error message, in English so it can be Googled.
details = ("Unable to complete install '%s'" %
(str(_type) + " " + str(value) + "\n" +
traceback.format_exc (stacktrace)))
error = (_("Unable to complete install: '%s'") % str(value))
if error:
asyncjob.set_error(error, details)
def check_install_status(self, vm, ignore1, ignore2, virtinst_guest=None):
if vm.is_crashed():

View File

@ -21,7 +21,6 @@
import gobject
import gtk
import sys
import traceback
import logging
@ -1115,44 +1114,28 @@ class vmmCreateInterface(vmmGObjectUI):
title=_("Creating virtual interface"),
text=_("The virtual interface is now being "
"created."))
progWin.run()
error, details = progWin.get_error()
if error != None:
self.err.show_err(error, details)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
if error:
return
error = _("Error creating interface: '%s'") % error
self.err.show_err(error, error + "\n" + details)
else:
# FIXME: Hmm, shouldn't we emit a signal here rather than do this?
self.conn.tick(noStatsUpdate=True)
self.close()
def do_install(self, activate, asyncjob):
def do_install(self, asyncjob, activate):
meter = vmmCreateMeter(asyncjob)
error = None
details = None
try:
self.interface.conn = util.dup_conn(self.conn).vmm
self.interface.install(meter, create=activate)
logging.debug("Install completed")
except:
(_type, value, stacktrace) = sys.exc_info ()
# Detailed error message, in English so it can be Googled.
details = ("Error creating interface: '%s'" %
(str(_type) + " " + str(value) + "\n" +
traceback.format_exc (stacktrace)))
error = (_("Error creating interface: '%s'") % str(value))
if error:
asyncjob.set_error(error, details)
def show_help(self, ignore):
# No help available yet.

View File

@ -396,21 +396,21 @@ class vmmCreatePool(vmmGObjectUI):
title=_("Creating storage pool..."),
text=_("Creating the storage pool may take a "
"while..."))
progWin.run()
error, details = progWin.get_error()
if error is not None:
self.err.show_err(error, details)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
if not error:
if error:
error = _("Error creating pool: %s") % error
self.err.show_err(error, error + "\n" + details)
else:
self.close()
def _async_pool_create(self, asyncjob):
def _async_pool_create(self, asyncjob, *args, **kwargs):
print args, kwargs
newconn = None
try:
# Open a seperate connection to install on since this is async
newconn = util.dup_lib_conn(self._pool.conn)
meter = vmmCreateMeter(asyncjob)
@ -421,10 +421,6 @@ class vmmCreatePool(vmmGObjectUI):
poolobj = self._pool.install(create=True, meter=meter, build=build)
poolobj.setAutostart(True)
logging.debug("Pool creating succeeded.")
except Exception, e:
error = _("Error creating pool: %s") % str(e)
details = "".join(traceback.format_exc())
asyncjob.set_error(error, details)
def page_changed(self, notebook_ignore, page_ignore, page_number):
if page_number == PAGE_NAME:

View File

@ -203,25 +203,22 @@ class vmmCreateVolume(vmmGObjectUI):
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
progWin = vmmAsyncJob(self._async_vol_create, [],
title=_("Creating storage volume..."),
text=_("Creating the storage volume may take a "
_("Creating storage volume..."),
_("Creating the storage volume may take a "
"while..."))
progWin.run()
error, details = progWin.get_error()
if error is not None:
self.show_err(error, details)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
if not error:
if error:
error = _("Error creating vol: %s") % error
self.show_err(error, error + "\n" + details)
else:
self.emit("vol-created")
self.close()
def _async_vol_create(self, asyncjob):
newconn = None
try:
newconn = util.dup_conn(self.conn).vmm
# Lookup different pool obj
@ -231,10 +228,6 @@ class vmmCreateVolume(vmmGObjectUI):
meter = vmmCreateMeter(asyncjob)
logging.debug("Starting backround vol creation.")
self.vol.install(meter=meter)
except Exception, e:
error = _("Error creating vol: %s") % str(e)
details = "".join(traceback.format_exc())
asyncjob.set_error(error, details)
def validate(self):
name = self.window.get_widget("vol-name").get_text()

View File

@ -132,8 +132,7 @@ class vmmDeleteDialog(vmmGObjectUI):
progWin = vmmAsyncJob(self._async_delete, [devs],
title=title, text=text)
progWin.run()
error, details = progWin.get_error()
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
@ -145,11 +144,9 @@ class vmmDeleteDialog(vmmGObjectUI):
self.conn.tick(noStatsUpdate=True)
def _async_delete(self, paths, asyncjob):
def _async_delete(self, asyncjob, paths):
newconn = None
storage_errors = []
error = None
details = None
try:
# Open a seperate connection to install on since this is async

View File

@ -200,6 +200,7 @@ class vmmDomainBase(vmmLibvirtObject):
# attempt may result in a lookup failure. If device is present
# in the active XML, assume all is good.
if find_device(self._get_guest(), origdev):
logging.debug("Device in active config but not inactive config.")
return
raise RuntimeError(_("Could not find specified device in the "

View File

@ -104,9 +104,9 @@ def check_packagekit(errbox):
progWin = vmmAsyncJob(_do_async_search,
[session, pk_control],
_("Searching for available hypervisors..."),
_("Searching for available hypervisors..."),
run_main=False)
progWin.run()
error, ignore = progWin.get_error()
error, ignore = progWin.run()
if error:
return
@ -146,7 +146,7 @@ def check_packagekit(errbox):
return (True, LIBVIRT_DAEMON in do_install)
def _do_async_search(session, pk_control, asyncjob):
def _do_async_search(asyncjob, session, pk_control):
found = []
try:
for name in PACKAGEKIT_PACKAGES:
@ -827,15 +827,16 @@ class vmmEngine(vmmGObject):
progWin = vmmAsyncJob(self._save_callback,
[vm, path],
_("Saving Virtual Machine"),
_("Saving Virtual Machine"),
cancel_back=_cancel_back,
cancel_args=_cancel_args)
progWin.run()
error, details = progWin.get_error()
error, details = progWin.run()
if error is not None:
src.err.show_err(_("Error saving domain: %s") % error, details)
error = _("Error saving domain: %s") % error
src.err.show_err(error, error + "\n" + details)
def _save_cancel(self, vm, asyncjob):
def _save_cancel(self, asyncjob, vm):
logging.debug("Cancelling save job")
if not vm:
return
@ -850,18 +851,17 @@ class vmmEngine(vmmGObject):
asyncjob.job_canceled = True
return
def _save_callback(self, vm, file_to_save, asyncjob):
def _save_callback(self, asyncjob, vm, file_to_save):
try:
conn = util.dup_conn(vm.connection)
newvm = conn.get_vm(vm.get_uuid())
newvm.save(file_to_save)
except Exception, e:
if not (isinstance(e, libvirt.libvirtError) and
asyncjob.job_canceled):
# If job is cancelled, we should not report the error
# to user.
asyncjob.set_error(str(e), "".join(traceback.format_exc()))
# If job is cancelled, don't report error to user.
if isinstance(e, libvirt.libvirtError) and asyncjob.job_canceled:
return
raise e
def _do_restore_domain(self, src, uri):
conn = self._lookup_connection(uri)
@ -879,23 +879,19 @@ class vmmEngine(vmmGObject):
return
progWin = vmmAsyncJob(self._restore_saved_callback,
[path, conn], _("Restoring Virtual Machine"))
progWin.run()
error, details = progWin.get_error()
[path, conn],
_("Restoring Virtual Machine"),
_("Restoring Virtual Machine"))
error, details = progWin.run()
if error is not None:
src.err.show_err(error, details,
title=_("Error restoring domain"))
error = _("Error restoring domain: %s") % error
src.err.show_err(error, error + "\n" + details)
def _restore_saved_callback(self, file_to_load, conn, asyncjob):
try:
def _restore_saved_callback(self, asyncjob, file_to_load, conn):
ignore = asyncjob
newconn = util.dup_conn(conn)
newconn.restore(file_to_load)
except Exception, e:
err = (_("Error restoring domain '%s': %s") %
(file_to_load, str(e)))
details = "".join(traceback.format_exc())
asyncjob.set_error(err, details)
def _do_destroy_domain(self, src, uri, uuid):
conn = self._lookup_connection(uri)

View File

@ -21,6 +21,7 @@
import virtinst
from virtinst import Interface
from virtManager import util
from virtManager.libvirtobject import vmmLibvirtObject
class vmmInterface(vmmLibvirtObject):
@ -118,7 +119,7 @@ class vmmInterface(vmmLibvirtObject):
return doc.serialize()
self._redefine_xml(set_start_xml)
self._redefine(util.xml_parse_wrapper, set_start_xml)
def get_slaves(self):
@ -211,4 +212,19 @@ class vmmInterface(vmmLibvirtObject):
ret = " %s\n" % ret
return ret
def _redefine(self, xml_func, *args):
"""
Helper function for altering a redefining VM xml
@param xml_func: Function to alter the running XML. Takes the
original XML as its first argument.
@param args: Extra arguments to pass to xml_func
"""
origxml = self._xml_to_redefine()
# Sanitize origxml to be similar to what we will get back
origxml = util.xml_parse_wrapper(origxml, lambda d, c: d.serialize())
newxml = xml_func(origxml, *args)
self._redefine_xml(newxml)
vmmLibvirtObject.type_register(vmmInterface)

View File

@ -51,9 +51,6 @@ class vmmLibvirtObject(vmmGObject):
self._xml = None
self._is_xml_valid = False
# Cached XML that accumulates changes to define
self._xml_to_define = None
# These should be set by the child classes if necessary
self._inactive_xml_flags = 0
self._active_xml_flags = 0
@ -128,7 +125,7 @@ class vmmLibvirtObject(vmmGObject):
# Internal API functions #
##########################
def __xml_to_redefine(self):
def _xml_to_redefine(self):
return _sanitize_xml(self.get_xml(inactive=True))
def _redefine_helper(self, origxml, newxml):
@ -143,13 +140,15 @@ class vmmLibvirtObject(vmmGObject):
self.get_name(), diff)
self._define(newxml)
else:
logging.debug("Redefine requested, but XML didn't change!")
# Make sure we have latest XML
self.refresh_xml(forcesignal=True)
return
def _redefine_xml(self, newxml):
origxml = self.__xml_to_redefine()
origxml = self._xml_to_redefine()
return self._redefine_helper(origxml, newxml)
vmmGObject.type_register(vmmLibvirtObject)

View File

@ -447,22 +447,21 @@ class vmmMigrateDialog(vmmGObjectUI):
progWin = vmmAsyncJob(self._async_migrate,
[self.vm, destconn, uri, rate, live, secure,
max_downtime],
title=_("Migrating VM '%s'" % self.vm.get_name()),
text=(_("Migrating VM '%s' from %s to %s. "
_("Migrating VM '%s'" % self.vm.get_name()),
(_("Migrating VM '%s' from %s to %s. "
"This may take awhile.") %
(self.vm.get_name(), srchost, dsthost)),
cancel_back=_cancel_back,
cancel_args=_cancel_args)
progWin.run()
error, details = progWin.get_error()
if error:
self.err.show_err(error, details)
error, details = progWin.run()
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
if error is None:
if error:
error = _("Unable to migrate guest: %s") % error
self.err.show_err(error, error + "\n" + details)
else:
self.conn.tick(noStatsUpdate=True)
destconn.tick(noStatsUpdate=True)
self.close()
@ -482,7 +481,7 @@ class vmmMigrateDialog(vmmGObjectUI):
logging.warning("Error setting migrate downtime: %s" % e)
return False
def cancel_migration(self, vm, asyncjob):
def cancel_migration(self, asyncjob, vm):
logging.debug("Cancelling migrate job")
if not vm:
return
@ -497,10 +496,9 @@ class vmmMigrateDialog(vmmGObjectUI):
asyncjob.job_canceled = True
return
def _async_migrate(self, origvm, origdconn, migrate_uri, rate, live,
secure, max_downtime, asyncjob):
errinfo = None
try:
def _async_migrate(self, asyncjob,
origvm, origdconn, migrate_uri, rate, live,
secure, max_downtime):
try:
ignore = vmmCreateMeter(asyncjob)
@ -525,15 +523,10 @@ class vmmMigrateDialog(vmmGObjectUI):
if timer:
gobject.source_remove(timer)
except Exception, e:
if not (isinstance(e, libvirt.libvirtError) and
asyncjob.job_canceled):
# If job is cancelled, we should not report the error
# to user.
errinfo = (str(e), ("Unable to migrate guest:\n %s" %
"".join(traceback.format_exc())))
finally:
if errinfo:
asyncjob.set_error(errinfo[0], errinfo[1])
# If job is cancelled, don't report error
if isinstance(e, libvirt.libvirtError) and asyncjob.job_canceled:
return
raise e
vmmGObjectUI.type_register(vmmMigrateDialog)

View File

@ -53,7 +53,7 @@ class vmmStoragePool(vmmLibvirtObject):
def set_active(self, state):
self.active = state
self._update_xml()
self.refresh_xml()
def is_active(self):
return self.active
@ -67,11 +67,11 @@ class vmmStoragePool(vmmLibvirtObject):
def start(self):
self.pool.create(0)
self._update_xml()
self.refresh_xml()
def stop(self):
self.pool.destroy()
self._update_xml()
self.refresh_xml()
def delete(self, nodelete=True):
if nodelete:

View File

@ -234,8 +234,10 @@ def browse_local(parent, dialog_name, conn, start_folder=None,
return ret
def dup_lib_conn(libconn):
vmmconn = _dup_all_conn(None, libconn)
return vmmconn.vmm
conn = _dup_all_conn(None, libconn)
if isinstance(conn, virtManager.connection.vmmConnection):
return conn.vmm
return conn
def dup_conn(conn):
return _dup_all_conn(conn, None)