Support job cancellation for migration, save functions

This commit is contained in:
Wen Congyang
2010-12-08 12:52:33 -05:00
parent 28b7a444f6
commit 45fab0153f
5 changed files with 133 additions and 6 deletions

View File

@@ -24,6 +24,7 @@ import gtk
import gobject
from virtManager import util
from virtManager.error import vmmErrorDialog
# This thin wrapper only exists so we can put debugging
# code in the run() method every now & then
@@ -40,7 +41,7 @@ class vmmAsyncJob(gobject.GObject):
def __init__(self, config, callback, args=None,
text=_("Please wait a few moments..."),
title=_("Operation in progress"),
run_main=True):
run_main=True, cancel_back=None, cancel_args=None):
gobject.GObject.__init__(self)
self.config = config
self.run_main = bool(run_main)
@@ -48,6 +49,20 @@ class vmmAsyncJob(gobject.GObject):
self.window = gtk.glade.XML(config.get_glade_dir() + \
"/vmm-progress.glade",
"vmm-progress", domain="virt-manager")
self.topwin = self.window.get_widget("vmm-progress")
self.err = vmmErrorDialog(self.topwin)
self.window.signal_autoconnect({
"on_async_job_delete_event" : self.delete,
"on_async_job_cancel_clicked" : self.cancel,
})
self.cancel_job = cancel_back
self.cancel_args = cancel_args or []
self.cancel_args.append(self)
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
self.window.get_widget("pbar-text").set_text(text)
self.topwin = self.window.get_widget("vmm-progress")
@@ -89,6 +104,26 @@ class vmmAsyncJob(gobject.GObject):
self.topwin.destroy()
def delete(self, ignore1=None, ignore2=None):
thread_active = (self.bg_thread.isAlive() or not self.run_main)
if self.cancel_job and thread_active:
res = self.err.warn_chkbox(
text1=_("Cancel the job before closing window?"),
buttons=gtk.BUTTONS_YES_NO)
if res:
# The job may end after we click 'Yes', so check whether the
# thread is active again
thread_active = (self.bg_thread.isAlive() or not self.run_main)
if thread_active:
self.cancel()
def show_warning(self, summary):
self.err.ok(summary)
def cancel(self, ignore1=None, ignore2=None):
if self.cancel_job:
self.cancel_job(*self.cancel_args)
def pulse_pbar(self, progress="", stage=None):
gtk.gdk.threads_enter()
try:

View File

@@ -828,6 +828,8 @@ class vmmDomain(vmmDomainBase):
self.getvcpus_supported = support.check_domain_support(self._backend,
support.SUPPORT_DOMAIN_GETVCPUS)
self.managedsave_supported = self.connection.get_dom_managedsave_supported(self._backend)
self.getjobinfo_supported = support.check_domain_support(self._backend,
support.SUPPORT_DOMAIN_JOB_INFO)
self.toggle_sample_network_traffic()
self.toggle_sample_disk_io()
@@ -1001,6 +1003,9 @@ class vmmDomain(vmmDomainBase):
if self.get_autostart() != val:
self._backend.setAutostart(val)
def abort_job(self):
self._backend.abortJob()
def migrate_set_max_downtime(self, max_downtime, flag=0):
self._backend.migrateSetMaxDowntime(max_downtime, flag)

View File

@@ -825,15 +825,37 @@ class vmmEngine(gobject.GObject):
if not path:
return
if vm.getjobinfo_supported:
_cancel_back = self._save_cancel
_cancel_args = [vm]
else:
_cancel_back = None
_cancel_args = [None]
progWin = vmmAsyncJob(self.config, 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()
if error is not None:
src.err.show_err(_("Error saving domain: %s") % error, details)
def _save_cancel(self, vm, asyncjob):
if not vm:
return
try:
vm.abort_job()
except Exception, e:
asyncjob.show_warning(str(e))
return
asyncjob.job_canceled = True
return
def _save_callback(self, vm, file_to_save, asyncjob):
try:
conn = util.dup_conn(self.config, vm.connection,
@@ -842,7 +864,11 @@ class vmmEngine(gobject.GObject):
newvm.save(file_to_save)
except Exception, e:
asyncjob.set_error(str(e), "".join(traceback.format_exc()))
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()))
def _do_restore_domain(self, src, uri):
conn = self._lookup_connection(uri)

View File

@@ -445,13 +445,22 @@ class vmmMigrateDialog(gobject.GObject):
self.topwin.set_sensitive(False)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
if self.vm.getjobinfo_supported:
_cancel_back = self.cancel_migration
_cancel_args = [self.vm]
else:
_cancel_back = None
_cancel_args = [None]
progWin = vmmAsyncJob(self.config, 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. "
"This may take awhile.") %
(self.vm.get_name(), srchost, dsthost)))
(self.vm.get_name(), srchost, dsthost)),
cancel_back=_cancel_back,
cancel_args=_cancel_args)
progWin.run()
error, details = progWin.get_error()
@@ -481,6 +490,19 @@ class vmmMigrateDialog(gobject.GObject):
logging.warning("Error setting migrate downtime: %s" % e)
return False
def cancel_migration(self, vm, asyncjob):
if not vm:
return
try:
vm.abort_job()
except Exception, e:
asyncjob.show_warning(str(e))
return
asyncjob.job_canceled = True
return
def _async_migrate(self, origvm, origdconn, migrate_uri, rate, live,
secure, max_downtime, asyncjob):
errinfo = None
@@ -511,8 +533,12 @@ class vmmMigrateDialog(gobject.GObject):
if timer:
gobject.source_remove(timer)
except Exception, e:
errinfo = (str(e), ("Unable to migrate guest:\n %s" %
"".join(traceback.format_exc())))
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])

View File

@@ -12,6 +12,7 @@
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="urgency_hint">True</property>
<signal name="delete_event" handler="on_async_job_delete_event"/>
<child>
<widget class="GtkAlignment" id="alignment134">
<property name="visible">True</property>
@@ -82,6 +83,40 @@
<property name="position">2</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="spacing">12</property>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
</widget>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="cancel-async-job">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_async_job_cancel_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</widget>
</child>
</widget>