mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-02-25 18:55:27 -06:00
Support job cancellation for migration, save functions
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user