2014-01-21 00:09:13 +08:00
|
|
|
# Copyright (C) 2009, 2013, 2014 Red Hat, Inc.
|
2009-11-21 00:39:22 +08:00
|
|
|
# Copyright (C) 2009 Cole Robinson <crobinso@redhat.com>
|
|
|
|
#
|
2018-04-04 21:35:41 +08:00
|
|
|
# This work is licensed under the GNU GPLv2 or later.
|
2018-03-21 03:00:02 +08:00
|
|
|
# See the COPYING file in the top-level directory.
|
2009-11-21 00:39:22 +08:00
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
from gi.repository import Gtk
|
2009-11-21 00:39:22 +08:00
|
|
|
|
2019-06-17 09:12:39 +08:00
|
|
|
from virtinst import log
|
|
|
|
|
2018-03-09 02:42:45 +08:00
|
|
|
from .asyncjob import vmmAsyncJob
|
|
|
|
|
2014-01-27 06:42:24 +08:00
|
|
|
|
2009-12-01 05:21:04 +08:00
|
|
|
####################################################################
|
|
|
|
# Build toolbar shutdown button menu (manager and details toolbar) #
|
|
|
|
####################################################################
|
|
|
|
|
2013-09-23 03:13:41 +08:00
|
|
|
class _VMMenu(Gtk.Menu):
|
2013-09-23 03:44:58 +08:00
|
|
|
def __init__(self, src, current_vm_cb, show_open=True):
|
2013-09-23 01:24:59 +08:00
|
|
|
Gtk.Menu.__init__(self)
|
|
|
|
self._parent = src
|
|
|
|
self._current_vm_cb = current_vm_cb
|
2013-09-23 03:44:58 +08:00
|
|
|
self._show_open = show_open
|
|
|
|
|
2013-09-23 01:24:59 +08:00
|
|
|
self._init_state()
|
|
|
|
|
2018-03-09 02:42:45 +08:00
|
|
|
def _add_action(self, label, widgetname, cb,
|
|
|
|
iconname="system-shutdown"):
|
2013-09-23 03:13:41 +08:00
|
|
|
if label.startswith("gtk-"):
|
|
|
|
item = Gtk.ImageMenuItem.new_from_stock(label, None)
|
|
|
|
else:
|
2013-09-23 01:24:59 +08:00
|
|
|
item = Gtk.ImageMenuItem.new_with_mnemonic(label)
|
|
|
|
|
2013-09-23 03:13:41 +08:00
|
|
|
if iconname:
|
|
|
|
if iconname.startswith("gtk-"):
|
|
|
|
icon = Gtk.Image.new_from_stock(iconname, Gtk.IconSize.MENU)
|
|
|
|
else:
|
|
|
|
icon = Gtk.Image.new_from_icon_name(iconname,
|
|
|
|
Gtk.IconSize.MENU)
|
|
|
|
item.set_image(icon)
|
2013-09-23 01:24:59 +08:00
|
|
|
|
2018-03-09 02:42:45 +08:00
|
|
|
item.vmm_widget_name = widgetname
|
|
|
|
if cb:
|
|
|
|
def _cb(_menuitem):
|
|
|
|
_vm = self._current_vm_cb()
|
|
|
|
if _vm:
|
|
|
|
return cb(self._parent, _vm)
|
|
|
|
item.connect("activate", _cb)
|
|
|
|
|
2013-09-23 03:13:41 +08:00
|
|
|
self.add(item)
|
|
|
|
return item
|
2013-09-23 01:24:59 +08:00
|
|
|
|
2013-09-23 03:13:41 +08:00
|
|
|
def _init_state(self):
|
|
|
|
raise NotImplementedError()
|
|
|
|
def update_widget_states(self, vm):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
|
|
class VMShutdownMenu(_VMMenu):
|
2018-03-09 02:42:45 +08:00
|
|
|
"""
|
|
|
|
Shutdown submenu for reboot, forceoff, reset, etc.
|
|
|
|
"""
|
2013-09-23 03:13:41 +08:00
|
|
|
def _init_state(self):
|
2018-03-09 02:42:45 +08:00
|
|
|
self._add_action(_("_Reboot"), "reboot", VMActionUI.reboot)
|
|
|
|
self._add_action(_("_Shut Down"), "shutdown", VMActionUI.shutdown)
|
|
|
|
self._add_action(_("F_orce Reset"), "reset", VMActionUI.reset)
|
|
|
|
self._add_action(_("_Force Off"), "destroy", VMActionUI.destroy)
|
2013-09-23 03:13:41 +08:00
|
|
|
self.add(Gtk.SeparatorMenuItem())
|
2018-03-09 02:42:45 +08:00
|
|
|
self._add_action(_("Sa_ve"), "save", VMActionUI.save,
|
|
|
|
iconname=Gtk.STOCK_SAVE)
|
2013-09-23 03:13:41 +08:00
|
|
|
|
|
|
|
self.show_all()
|
|
|
|
|
2013-09-23 01:24:59 +08:00
|
|
|
def update_widget_states(self, vm):
|
|
|
|
statemap = {
|
|
|
|
"reboot": bool(vm and vm.is_stoppable()),
|
|
|
|
"shutdown": bool(vm and vm.is_stoppable()),
|
|
|
|
"reset": bool(vm and vm.is_stoppable()),
|
|
|
|
"destroy": bool(vm and vm.is_destroyable()),
|
2018-03-09 02:42:45 +08:00
|
|
|
"save": bool(vm and vm.is_destroyable()),
|
2013-09-23 01:24:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for child in self.get_children():
|
|
|
|
name = getattr(child, "vmm_widget_name", None)
|
|
|
|
if name in statemap:
|
|
|
|
child.set_sensitive(statemap[name])
|
2010-05-12 23:42:59 +08:00
|
|
|
|
2015-04-12 02:01:45 +08:00
|
|
|
if name == "reset":
|
|
|
|
child.set_tooltip_text(None)
|
2019-06-08 04:06:52 +08:00
|
|
|
if vm and not vm.conn.support.conn_domain_reset():
|
2015-04-12 02:01:45 +08:00
|
|
|
child.set_tooltip_text(_("Hypervisor does not support "
|
|
|
|
"domain reset."))
|
|
|
|
child.set_sensitive(False)
|
|
|
|
|
2013-07-09 21:20:43 +08:00
|
|
|
|
2013-09-23 03:13:41 +08:00
|
|
|
class VMActionMenu(_VMMenu):
|
2018-03-09 02:42:45 +08:00
|
|
|
"""
|
|
|
|
VM submenu for run, pause, shutdown, clone, etc
|
|
|
|
"""
|
2013-09-23 03:13:41 +08:00
|
|
|
def _init_state(self):
|
2018-03-09 02:42:45 +08:00
|
|
|
self._add_action(_("_Run"), "run", VMActionUI.run,
|
|
|
|
iconname=Gtk.STOCK_MEDIA_PLAY)
|
|
|
|
self._add_action(_("_Pause"), "suspend", VMActionUI.suspend,
|
|
|
|
Gtk.STOCK_MEDIA_PAUSE)
|
|
|
|
self._add_action(_("R_esume"), "resume", VMActionUI.resume,
|
|
|
|
Gtk.STOCK_MEDIA_PAUSE)
|
|
|
|
s = self._add_action(_("_Shut Down"), "shutdown", None)
|
2013-09-23 03:13:41 +08:00
|
|
|
s.set_submenu(VMShutdownMenu(self._parent, self._current_vm_cb))
|
|
|
|
|
|
|
|
self.add(Gtk.SeparatorMenuItem())
|
2018-03-09 02:42:45 +08:00
|
|
|
self._add_action(_("Clone..."), "clone",
|
2018-03-15 06:17:03 +08:00
|
|
|
VMActionUI.clone, iconname=None)
|
2018-03-09 02:42:45 +08:00
|
|
|
self._add_action(_("Migrate..."), "migrate",
|
2018-03-15 05:50:22 +08:00
|
|
|
VMActionUI.migrate, iconname=None)
|
2018-03-09 02:42:45 +08:00
|
|
|
self._add_action(_("_Delete"), "delete",
|
2018-03-15 05:48:17 +08:00
|
|
|
VMActionUI.delete, iconname=Gtk.STOCK_DELETE)
|
2013-09-23 03:13:41 +08:00
|
|
|
|
2013-09-23 03:44:58 +08:00
|
|
|
if self._show_open:
|
|
|
|
self.add(Gtk.SeparatorMenuItem())
|
2018-03-09 02:42:45 +08:00
|
|
|
self._add_action(Gtk.STOCK_OPEN, "show",
|
2018-03-15 17:53:58 +08:00
|
|
|
VMActionUI.show, iconname=None)
|
2013-09-23 03:13:41 +08:00
|
|
|
|
|
|
|
self.show_all()
|
|
|
|
|
|
|
|
def update_widget_states(self, vm):
|
|
|
|
statemap = {
|
|
|
|
"run": bool(vm and vm.is_runable()),
|
|
|
|
"shutdown": bool(vm and vm.is_stoppable()),
|
|
|
|
"suspend": bool(vm and vm.is_stoppable()),
|
|
|
|
"resume": bool(vm and vm.is_paused()),
|
|
|
|
"migrate": bool(vm and vm.is_stoppable()),
|
2016-10-06 23:12:59 +08:00
|
|
|
"clone": bool(vm and vm.is_clonable()),
|
2013-09-23 03:13:41 +08:00
|
|
|
}
|
|
|
|
vismap = {
|
|
|
|
"suspend": bool(vm and not vm.is_paused()),
|
|
|
|
"resume": bool(vm and vm.is_paused()),
|
|
|
|
}
|
|
|
|
|
|
|
|
for child in self.get_children():
|
|
|
|
name = getattr(child, "vmm_widget_name", None)
|
2015-04-12 02:14:24 +08:00
|
|
|
if child.get_submenu():
|
|
|
|
child.get_submenu().update_widget_states(vm)
|
2013-09-23 03:13:41 +08:00
|
|
|
if name in statemap:
|
|
|
|
child.set_sensitive(statemap[name])
|
|
|
|
if name in vismap:
|
|
|
|
child.set_visible(vismap[name])
|
|
|
|
|
|
|
|
def change_run_text(self, text):
|
|
|
|
for child in self.get_children():
|
|
|
|
if getattr(child, "vmm_widget_name", None) == "run":
|
|
|
|
child.get_child().set_label(text)
|
2018-03-09 02:42:45 +08:00
|
|
|
|
|
|
|
|
|
|
|
class VMActionUI(object):
|
|
|
|
"""
|
|
|
|
Singleton object for handling VM actions, asking for confirmation,
|
|
|
|
showing errors/progress dialogs, etc.
|
|
|
|
"""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def save_cancel(asyncjob, vm):
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Cancelling save job")
|
2018-03-09 02:42:45 +08:00
|
|
|
if not vm:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
vm.abort_job()
|
|
|
|
except Exception as e:
|
2019-06-17 09:12:39 +08:00
|
|
|
log.exception("Error cancelling save job")
|
2018-03-09 02:42:45 +08:00
|
|
|
asyncjob.show_warning(_("Error cancelling save job: %s") % str(e))
|
|
|
|
return
|
|
|
|
|
|
|
|
asyncjob.job_canceled = True
|
|
|
|
return
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def save(src, vm):
|
|
|
|
if not src.err.chkbox_helper(src.config.get_confirm_poweroff,
|
|
|
|
src.config.set_confirm_poweroff,
|
|
|
|
text1=_("Are you sure you want to save '%s'?") % vm.get_name()):
|
|
|
|
return
|
|
|
|
|
|
|
|
_cancel_cb = None
|
2020-08-27 00:05:54 +08:00
|
|
|
if vm.supports_domain_job_info():
|
2018-03-09 02:42:45 +08:00
|
|
|
_cancel_cb = (VMActionUI.save_cancel, vm)
|
|
|
|
|
|
|
|
def cb(asyncjob):
|
|
|
|
vm.save(meter=asyncjob.get_meter())
|
|
|
|
def finish_cb(error, details):
|
|
|
|
if error is not None:
|
|
|
|
error = _("Error saving domain: %s") % error
|
|
|
|
src.err.show_err(error, details=details)
|
|
|
|
|
|
|
|
progWin = vmmAsyncJob(cb, [],
|
|
|
|
finish_cb, [],
|
|
|
|
_("Saving Virtual Machine"),
|
|
|
|
_("Saving virtual machine memory to disk "),
|
|
|
|
src.topwin, cancel_cb=_cancel_cb)
|
|
|
|
progWin.run()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def destroy(src, vm):
|
|
|
|
if not src.err.chkbox_helper(
|
|
|
|
src.config.get_confirm_forcepoweroff,
|
|
|
|
src.config.set_confirm_forcepoweroff,
|
|
|
|
text1=_("Are you sure you want to force poweroff '%s'?" %
|
|
|
|
vm.get_name()),
|
|
|
|
text2=_("This will immediately poweroff the VM without "
|
|
|
|
"shutting down the OS and may cause data loss.")):
|
|
|
|
return
|
|
|
|
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Destroying vm '%s'", vm.get_name())
|
2018-03-09 02:42:45 +08:00
|
|
|
vmmAsyncJob.simple_async_noshow(vm.destroy, [], src,
|
|
|
|
_("Error shutting down domain"))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def suspend(src, vm):
|
|
|
|
if not src.err.chkbox_helper(src.config.get_confirm_pause,
|
|
|
|
src.config.set_confirm_pause,
|
|
|
|
text1=_("Are you sure you want to pause '%s'?" %
|
|
|
|
vm.get_name())):
|
|
|
|
return
|
|
|
|
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Pausing vm '%s'", vm.get_name())
|
2018-03-09 02:42:45 +08:00
|
|
|
vmmAsyncJob.simple_async_noshow(vm.suspend, [], src,
|
|
|
|
_("Error pausing domain"))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def resume(src, vm):
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Unpausing vm '%s'", vm.get_name())
|
2018-03-09 02:42:45 +08:00
|
|
|
vmmAsyncJob.simple_async_noshow(vm.resume, [], src,
|
|
|
|
_("Error unpausing domain"))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def run(src, vm):
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Starting vm '%s'", vm.get_name())
|
2018-03-09 02:42:45 +08:00
|
|
|
|
|
|
|
if vm.has_managed_save():
|
|
|
|
def errorcb(error, details):
|
|
|
|
# This is run from the main thread
|
|
|
|
res = src.err.show_err(
|
2020-07-14 15:41:49 +08:00
|
|
|
_("Error restoring domain: %s") % error,
|
2018-03-09 02:42:45 +08:00
|
|
|
details=details,
|
|
|
|
text2=_(
|
|
|
|
"The domain could not be restored. Would you like\n"
|
|
|
|
"to remove the saved state and perform a regular\n"
|
|
|
|
"start up?"),
|
|
|
|
dialog_type=Gtk.MessageType.WARNING,
|
|
|
|
buttons=Gtk.ButtonsType.YES_NO,
|
|
|
|
modal=True)
|
|
|
|
|
|
|
|
if not res:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
vm.remove_saved_image()
|
|
|
|
VMActionUI.run(src, vm)
|
|
|
|
except Exception as e:
|
|
|
|
src.err.show_err(_("Error removing domain state: %s")
|
|
|
|
% str(e))
|
|
|
|
|
|
|
|
# VM will be restored, which can take some time, so show progress
|
|
|
|
title = _("Restoring Virtual Machine")
|
|
|
|
text = _("Restoring virtual machine memory from disk")
|
|
|
|
vmmAsyncJob.simple_async(vm.startup, [], src,
|
|
|
|
title, text, "", errorcb=errorcb)
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Regular startup
|
|
|
|
errorintro = _("Error starting domain")
|
|
|
|
vmmAsyncJob.simple_async_noshow(vm.startup, [], src, errorintro)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def shutdown(src, vm):
|
|
|
|
if not src.err.chkbox_helper(src.config.get_confirm_poweroff,
|
|
|
|
src.config.set_confirm_poweroff,
|
|
|
|
text1=_("Are you sure you want to poweroff '%s'?" %
|
|
|
|
vm.get_name())):
|
|
|
|
return
|
|
|
|
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Shutting down vm '%s'", vm.get_name())
|
2018-03-09 02:42:45 +08:00
|
|
|
vmmAsyncJob.simple_async_noshow(vm.shutdown, [], src,
|
|
|
|
_("Error shutting down domain"))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def reboot(src, vm):
|
|
|
|
if not src.err.chkbox_helper(src.config.get_confirm_poweroff,
|
|
|
|
src.config.set_confirm_poweroff,
|
|
|
|
text1=_("Are you sure you want to reboot '%s'?" %
|
|
|
|
vm.get_name())):
|
|
|
|
return
|
|
|
|
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Rebooting vm '%s'", vm.get_name())
|
2018-03-09 02:42:45 +08:00
|
|
|
vmmAsyncJob.simple_async_noshow(vm.reboot, [], src,
|
|
|
|
_("Error rebooting domain"))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def reset(src, vm):
|
|
|
|
if not src.err.chkbox_helper(
|
|
|
|
src.config.get_confirm_forcepoweroff,
|
|
|
|
src.config.set_confirm_forcepoweroff,
|
|
|
|
text1=_("Are you sure you want to force reset '%s'?" %
|
|
|
|
vm.get_name()),
|
|
|
|
text2=_("This will immediately reset the VM without "
|
|
|
|
"shutting down the OS and may cause data loss.")):
|
|
|
|
return
|
|
|
|
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Resetting vm '%s'", vm.get_name())
|
2018-03-09 02:42:45 +08:00
|
|
|
vmmAsyncJob.simple_async_noshow(vm.reset, [], src,
|
|
|
|
_("Error resetting domain"))
|
2018-03-15 05:48:17 +08:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def delete(src, vm):
|
|
|
|
from .delete import vmmDeleteDialog
|
|
|
|
vmmDeleteDialog.show_instance(src, vm)
|
2018-03-15 05:50:22 +08:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def migrate(src, vm):
|
|
|
|
from .migrate import vmmMigrateDialog
|
|
|
|
vmmMigrateDialog.show_instance(src, vm)
|
2018-03-15 06:17:03 +08:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def clone(src, vm):
|
|
|
|
from .clone import vmmCloneVM
|
|
|
|
vmmCloneVM.show_instance(src, vm)
|
2018-03-15 17:53:58 +08:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def show(src, vm):
|
2019-05-06 04:25:35 +08:00
|
|
|
from .vmwindow import vmmVMWindow
|
|
|
|
vmmVMWindow.get_instance(src, vm).show()
|