Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
#
|
|
|
|
# Copyright (C) 2013 Red Hat, Inc.
|
|
|
|
# Copyright (C) 2013 Cole Robinson <crobinso@redhat.com>
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program 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 General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
|
|
# MA 02110-1301 USA.
|
|
|
|
#
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
import logging
|
|
|
|
|
|
|
|
# pylint: disable=E0611
|
|
|
|
from gi.repository import Gdk
|
|
|
|
from gi.repository import Gtk
|
|
|
|
# pylint: enable=E0611
|
|
|
|
|
|
|
|
import libvirt
|
|
|
|
|
|
|
|
import virtinst
|
|
|
|
from virtinst import util
|
|
|
|
|
|
|
|
from virtManager import uihelpers
|
|
|
|
from virtManager.baseclass import vmmGObjectUI
|
|
|
|
from virtManager.asyncjob import vmmAsyncJob
|
|
|
|
|
|
|
|
|
|
|
|
def _snapshot_state_icon_name(state):
|
|
|
|
statemap = {
|
|
|
|
"nostate": libvirt.VIR_DOMAIN_NOSTATE,
|
|
|
|
"running": libvirt.VIR_DOMAIN_RUNNING,
|
|
|
|
"blocked": libvirt.VIR_DOMAIN_BLOCKED,
|
|
|
|
"paused": libvirt.VIR_DOMAIN_PAUSED,
|
|
|
|
"shutdown": libvirt.VIR_DOMAIN_SHUTDOWN,
|
|
|
|
"shutoff": libvirt.VIR_DOMAIN_SHUTOFF,
|
|
|
|
"crashed": libvirt.VIR_DOMAIN_CRASHED,
|
|
|
|
"pmsuspended": 7,
|
|
|
|
}
|
|
|
|
|
|
|
|
if state == "disk-snapshot" or state not in statemap:
|
|
|
|
state = "shutoff"
|
|
|
|
return uihelpers.vm_status_icons[statemap[state]]
|
|
|
|
|
|
|
|
|
|
|
|
class vmmSnapshotPage(vmmGObjectUI):
|
|
|
|
def __init__(self, vm, builder, topwin):
|
|
|
|
vmmGObjectUI.__init__(self, "vmm-snapshots.ui",
|
|
|
|
None, builder=builder, topwin=topwin)
|
|
|
|
|
|
|
|
self.vm = vm
|
|
|
|
|
|
|
|
self._initial_populate = False
|
|
|
|
|
|
|
|
self._init_ui()
|
|
|
|
|
|
|
|
self._snapshot_new = self.widget("snapshot-new")
|
|
|
|
self._snapshot_new.set_transient_for(self.topwin)
|
|
|
|
|
|
|
|
self.builder.connect_signals({
|
|
|
|
"on_snapshot_add_clicked": self._on_add_clicked,
|
|
|
|
"on_snapshot_delete_clicked": self._on_delete_clicked,
|
|
|
|
"on_snapshot_start_clicked": self._on_start_clicked,
|
|
|
|
"on_snapshot_apply_clicked": self._on_apply_clicked,
|
|
|
|
|
|
|
|
# 'Create' dialog
|
|
|
|
"on_snapshot_new_delete_event": self._snapshot_new_close,
|
|
|
|
"on_snapshot_new_ok_clicked": self._on_new_ok_clicked,
|
|
|
|
"on_snapshot_new_cancel_clicked" : self._snapshot_new_close,
|
|
|
|
})
|
|
|
|
|
|
|
|
self.top_box = self.widget("snapshot-top-box")
|
|
|
|
self.widget("snapshot-top-window").remove(self.top_box)
|
|
|
|
|
|
|
|
self.widget("snapshot-list").get_selection().connect("changed",
|
|
|
|
self._snapshot_selected)
|
|
|
|
self._set_snapshot_state(None)
|
|
|
|
|
|
|
|
|
|
|
|
##############
|
|
|
|
# Init stuff #
|
|
|
|
##############
|
|
|
|
|
|
|
|
def _cleanup(self):
|
|
|
|
self.vm = None
|
|
|
|
|
|
|
|
self._snapshot_new.destroy()
|
|
|
|
self._snapshot_new = None
|
|
|
|
|
|
|
|
def _init_ui(self):
|
|
|
|
self.widget("snapshot-notebook").set_show_tabs(False)
|
|
|
|
|
|
|
|
buf = Gtk.TextBuffer()
|
|
|
|
buf.connect("changed", self._description_changed)
|
|
|
|
self.widget("snapshot-description").set_buffer(buf)
|
|
|
|
|
|
|
|
# XXX: This should be a TreeStore, heirarchy is important
|
|
|
|
# for external snapshots.
|
|
|
|
# [handle, name, tooltip, is_current]
|
|
|
|
model = Gtk.ListStore(object, str, str, bool)
|
|
|
|
model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
|
|
|
|
|
|
|
|
col = Gtk.TreeViewColumn("")
|
|
|
|
col.set_min_width(150)
|
|
|
|
col.set_expand(True)
|
|
|
|
col.set_spacing(6)
|
|
|
|
img = Gtk.CellRendererPixbuf()
|
|
|
|
img.set_property("icon-name", Gtk.STOCK_YES)
|
|
|
|
img.set_property("stock-size", Gtk.IconSize.MENU)
|
|
|
|
img.set_property("xalign", 0)
|
|
|
|
txt = Gtk.CellRendererText()
|
|
|
|
col.pack_start(txt, False)
|
|
|
|
col.pack_start(img, True)
|
|
|
|
col.add_attribute(txt, 'text', 1)
|
|
|
|
col.add_attribute(img, 'visible', 3)
|
|
|
|
|
|
|
|
slist = self.widget("snapshot-list")
|
|
|
|
slist.set_model(model)
|
|
|
|
slist.set_tooltip_column(2)
|
|
|
|
slist.append_column(col)
|
|
|
|
|
|
|
|
|
|
|
|
###################
|
|
|
|
# Functional bits #
|
|
|
|
###################
|
|
|
|
|
|
|
|
def _get_current_snapshot(self):
|
|
|
|
widget = self.widget("snapshot-list")
|
|
|
|
selection = widget.get_selection()
|
|
|
|
model, treepath = selection.get_selected()
|
|
|
|
if treepath is None:
|
|
|
|
return None
|
|
|
|
return model[treepath][0]
|
|
|
|
|
|
|
|
def _refresh_snapshots(self):
|
|
|
|
self.vm.refresh_snapshots()
|
|
|
|
self._populate_snapshot_list()
|
|
|
|
|
|
|
|
def show_page(self):
|
|
|
|
if not self._initial_populate:
|
|
|
|
self._populate_snapshot_list()
|
|
|
|
|
|
|
|
def _set_error_page(self, msg):
|
|
|
|
self._set_snapshot_state(None)
|
|
|
|
self.widget("snapshot-notebook").set_current_page(1)
|
|
|
|
self.widget("snapshot-error-label").set_text(msg)
|
|
|
|
|
|
|
|
def _populate_snapshot_list(self):
|
|
|
|
model = self.widget("snapshot-list").get_model()
|
|
|
|
model.clear()
|
|
|
|
|
|
|
|
if not self.vm.snapshots_supported:
|
|
|
|
self._set_error_page(_("Libvirt connection does not support "
|
|
|
|
"snapshots."))
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
snapshots = self.vm.list_snapshots()
|
|
|
|
except Exception, e:
|
|
|
|
logging.exception(e)
|
|
|
|
self._set_error_page(_("Error refreshing snapshot list: %s") %
|
|
|
|
str(e))
|
|
|
|
return
|
|
|
|
|
|
|
|
do_select = None
|
|
|
|
for snap in snapshots:
|
2013-09-09 16:14:16 -05:00
|
|
|
desc = snap.xmlobj.description
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
if not uihelpers.can_set_row_none:
|
|
|
|
desc = desc or ""
|
|
|
|
|
|
|
|
# XXX: For disk snapshots, this isn't sufficient for determining
|
|
|
|
# 'current' status
|
|
|
|
current = bool(snap.is_current())
|
|
|
|
|
|
|
|
treeiter = model.append([snap, snap.get_name(),
|
|
|
|
desc, current])
|
|
|
|
if current:
|
|
|
|
do_select = treeiter
|
|
|
|
|
|
|
|
self._set_snapshot_state(None)
|
|
|
|
if len(model):
|
|
|
|
if do_select is None:
|
|
|
|
do_select = model.get_iter_from_string("0")
|
|
|
|
self.widget("snapshot-list").get_selection().select_iter(do_select)
|
|
|
|
|
|
|
|
self._initial_populate = True
|
|
|
|
|
|
|
|
def _set_snapshot_state(self, snap=None):
|
|
|
|
self.widget("snapshot-notebook").set_current_page(0)
|
|
|
|
|
|
|
|
name = snap and snap.get_name() or ""
|
2013-09-09 16:14:16 -05:00
|
|
|
desc = snap and snap.xmlobj.description or ""
|
|
|
|
state = snap and snap.xmlobj.state or "shutoff"
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
timestamp = ""
|
|
|
|
if snap:
|
|
|
|
timestamp = str(datetime.datetime.fromtimestamp(
|
2013-09-09 16:14:16 -05:00
|
|
|
snap.xmlobj.creationTime))
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
|
|
|
|
current = ""
|
|
|
|
if snap and snap.is_current():
|
|
|
|
current = " (current)"
|
|
|
|
title = ""
|
|
|
|
if name:
|
|
|
|
title = "<b>Snapshot '%s'%s:</b>" % (util.xml_escape(name),
|
|
|
|
current)
|
|
|
|
|
|
|
|
self.widget("snapshot-title").set_markup(title)
|
|
|
|
self.widget("snapshot-timestamp").set_text(timestamp)
|
|
|
|
self.widget("snapshot-description").get_buffer().set_text(desc)
|
|
|
|
|
|
|
|
self.widget("snapshot-status-text").set_text(state)
|
|
|
|
self.widget("snapshot-status-icon").set_from_icon_name(
|
|
|
|
_snapshot_state_icon_name(state),
|
|
|
|
Gtk.IconSize.MENU)
|
|
|
|
|
|
|
|
self.widget("snapshot-add").set_sensitive(True)
|
|
|
|
self.widget("snapshot-delete").set_sensitive(bool(snap))
|
|
|
|
self.widget("snapshot-start").set_sensitive(bool(snap))
|
|
|
|
self.widget("snapshot-apply").set_sensitive(False)
|
|
|
|
|
|
|
|
|
|
|
|
#############
|
|
|
|
# Listeners #
|
|
|
|
#############
|
|
|
|
|
|
|
|
def _snapshot_new_close(self, *args, **kwargs):
|
|
|
|
ignore = args
|
|
|
|
ignore = kwargs
|
|
|
|
self._snapshot_new.hide()
|
|
|
|
return 1
|
|
|
|
|
|
|
|
def _description_changed(self, ignore):
|
|
|
|
self.widget("snapshot-apply").set_sensitive(True)
|
|
|
|
|
|
|
|
def _on_apply_clicked(self, ignore):
|
|
|
|
snap = self._get_current_snapshot()
|
|
|
|
if not snap:
|
|
|
|
return
|
|
|
|
|
|
|
|
desc_widget = self.widget("snapshot-description")
|
|
|
|
desc = desc_widget.get_buffer().get_property("text") or ""
|
|
|
|
|
2013-09-09 16:14:16 -05:00
|
|
|
snap.xmlobj.description = desc
|
|
|
|
newxml = snap.xmlobj.get_xml_config()
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
self.vm.create_snapshot(newxml, redefine=True)
|
|
|
|
snap.refresh_xml()
|
|
|
|
self._set_snapshot_state(snap)
|
|
|
|
|
|
|
|
# XXX refresh in place
|
|
|
|
|
2013-09-06 19:59:01 -05:00
|
|
|
def _finish_cb(self, error, details):
|
|
|
|
self.topwin.set_sensitive(True)
|
|
|
|
self.topwin.get_window().set_cursor(
|
|
|
|
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
|
|
|
|
|
|
|
|
if error is not None:
|
|
|
|
error = _("Error creating snapshot: %s") % error
|
|
|
|
self.err.show_err(error, details=details)
|
|
|
|
return
|
|
|
|
|
|
|
|
self._refresh_snapshots()
|
|
|
|
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
def _on_new_ok_clicked(self, ignore):
|
|
|
|
name = self.widget("snapshot-new-name").get_text()
|
|
|
|
|
|
|
|
newsnap = virtinst.DomainSnapshot(self.vm.conn.get_backend())
|
|
|
|
newsnap.name = name
|
|
|
|
|
|
|
|
# XXX: all manner of flags here: live, quiesce, atomic, etc.
|
|
|
|
# most aren't relevant for internal?
|
|
|
|
|
|
|
|
self.topwin.set_sensitive(False)
|
|
|
|
self.topwin.get_window().set_cursor(
|
|
|
|
Gdk.Cursor.new(Gdk.CursorType.WATCH))
|
|
|
|
|
|
|
|
self._snapshot_new_close()
|
|
|
|
progWin = vmmAsyncJob(
|
|
|
|
lambda ignore, xml: self.vm.create_snapshot(xml),
|
|
|
|
[newsnap.get_xml_config()],
|
2013-09-06 19:59:01 -05:00
|
|
|
self._finish_cb, [],
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
_("Creating snapshot"),
|
|
|
|
_("Creating virtual machine snapshot"),
|
|
|
|
self.topwin)
|
2013-09-06 19:59:01 -05:00
|
|
|
progWin.run()
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
|
|
|
|
def _on_add_clicked(self, ignore):
|
|
|
|
snap = self._get_current_snapshot()
|
|
|
|
if not snap:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self._snapshot_new.is_visible():
|
|
|
|
return
|
|
|
|
|
|
|
|
# XXX: generate name
|
|
|
|
# XXX: default focus, tab order, default action, esc key, alt
|
|
|
|
self.widget("snapshot-new-name").set_text("foo")
|
|
|
|
self._snapshot_new.show()
|
|
|
|
|
|
|
|
def _on_start_clicked(self, ignore):
|
|
|
|
snap = self._get_current_snapshot()
|
|
|
|
if not snap:
|
|
|
|
return
|
|
|
|
|
|
|
|
# XXX: Not true with external disk snapshots, disk changes are
|
|
|
|
# encoded in the latest snapshot
|
|
|
|
# XXX: Don't run current?
|
|
|
|
# XXX: Warn about state change?
|
|
|
|
result = self.err.yes_no(_("Are you sure you want to revert to "
|
|
|
|
"snapshot '%s'? All disk changes since "
|
|
|
|
"the last snapshot will be discarded.") %
|
|
|
|
snap.get_name())
|
|
|
|
if not result:
|
|
|
|
return
|
|
|
|
|
|
|
|
logging.debug("Revertin to snapshot '%s'", snap.get_name())
|
|
|
|
vmmAsyncJob.simple_async_noshow(self.vm.revert_to_snapshot,
|
|
|
|
[snap], self,
|
|
|
|
_("Error reverting to snapshot '%s'") %
|
2013-09-06 19:59:01 -05:00
|
|
|
snap.get_name(),
|
|
|
|
finish_cb=self._refresh_snapshots)
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
|
|
|
|
def _on_delete_clicked(self, ignore):
|
|
|
|
snap = self._get_current_snapshot()
|
|
|
|
if not snap:
|
|
|
|
return
|
|
|
|
|
|
|
|
result = self.err.yes_no(_("Are you sure you want to permanently "
|
|
|
|
"delete the snapshot '%s'?") %
|
|
|
|
snap.get_name())
|
|
|
|
if not result:
|
|
|
|
return
|
|
|
|
|
|
|
|
# XXX: how does the work for 'current' snapshot?
|
|
|
|
# XXX: all sorts of flags here like 'delete children', do we care?
|
|
|
|
|
|
|
|
logging.debug("Deleting snapshot '%s'", snap.get_name())
|
|
|
|
vmmAsyncJob.simple_async_noshow(snap.delete, [], self,
|
2013-09-06 19:59:01 -05:00
|
|
|
_("Error deleting snapshot '%s'") % snap.get_name(),
|
|
|
|
finish_cb=self._refresh_snapshots)
|
Initial snapshot support
This adds initial UI for managing snapshots: list, run/revert, delete,
add, and redefining (for changing <description>) supported, but currently
only for internal snapshots. The UI is mostly in its final form except for
some bells and whistles.
The real remaining question is what do we want to advertise and support.
Internal (qcow2) snapshots are by far the simplest to manage, very
mature, and already have the semantics we want.
However most recent libvirt and qemu work has been to facilitate
external snapshots, which are more extensible and can be performed
live, and with qemu-ga coordination for extra safety. However
they make things much harder for virt-manager at the moment.
Until we have a plan, this work should be considered experimental
and not be relied upon.
2013-08-02 09:18:47 -05:00
|
|
|
|
|
|
|
|
|
|
|
def _snapshot_selected(self, selection):
|
|
|
|
model, treepath = selection.get_selected()
|
|
|
|
if treepath is None:
|
|
|
|
self._set_error_page(_("No snapshot selected."))
|
|
|
|
return
|
|
|
|
|
|
|
|
snap = model[treepath][0]
|
|
|
|
|
|
|
|
try:
|
|
|
|
self._set_snapshot_state(snap)
|
|
|
|
except Exception, e:
|
|
|
|
logging.exception(e)
|
|
|
|
self._set_error_page(_("Error selecting snapshot: %s") % str(e))
|