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.
This commit is contained in:
parent
9d11c7eae3
commit
e8531b1f40
10519
ui/vmm-details.ui
10519
ui/vmm-details.ui
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,474 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 3.6 -->
|
||||
<object class="GtkDialog" id="snapshot-new">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="title" translatable="yes">Create snapshot</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<signal name="delete-event" handler="on_snapshot_new_delete_event" swapped="no"/>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="dialog-vbox1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox" id="dialog-action_area1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="snapshot-new-cancel">
|
||||
<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_snapshot_new_cancel_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="snapshot-new-ok">
|
||||
<property name="label" translatable="yes">Finish</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="clicked" handler="on_snapshot_new_ok_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Name:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="snapshot-new-name">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="invisible_char">●</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="0">snapshot-new-cancel</action-widget>
|
||||
<action-widget response="0">snapshot-new-ok</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkWindow" id="snapshot-top-window">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="snapshot-top-box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">12</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkPaned" id="spaned1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="position">200</property>
|
||||
<property name="position_set">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow7">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="snapshot-list">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="treeview-selection"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">False</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkNotebook" id="snapshot-notebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="sbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="salignment12">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="top_padding">3</property>
|
||||
<property name="left_padding">5</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="sgrid1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<property name="column_spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="slabel94">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
<property name="label" translatable="yes">Description:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="width">1</property>
|
||||
<property name="height">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow8">
|
||||
<property name="height_request">80</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="snapshot-description">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="width">1</property>
|
||||
<property name="height">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label92">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Status:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="width">1</property>
|
||||
<property name="height">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">3</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="snapshot-status-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-cancel</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="snapshot-status-text">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label">Shut down</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="width">1</property>
|
||||
<property name="height">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label93">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Timestamp:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="width">1</property>
|
||||
<property name="height">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="snapshot-timestamp">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label">label</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="width">1</property>
|
||||
<property name="height">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="snapshot-title">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label"><b>snapshot 'foo' (current)</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="width">2</property>
|
||||
<property name="height">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label89">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">details</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="snapshot-error-label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">error label</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label90">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">empty</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox10">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="snapshot-add">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="tooltip_markup" translatable="yes">Create new snapshot</property>
|
||||
<property name="tooltip_text" translatable="yes">Create new snapshot</property>
|
||||
<signal name="clicked" handler="on_snapshot_add_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-add</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="snapshot-start">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="tooltip_markup" translatable="yes">Revert guest to selected snapshot</property>
|
||||
<property name="tooltip_text" translatable="yes">Revert guest to selected snapshot</property>
|
||||
<signal name="clicked" handler="on_snapshot_start_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="someicon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-media-play</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="snapshot-delete">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="tooltip_markup" translatable="yes">Delete selected snapshot</property>
|
||||
<property name="tooltip_text" translatable="yes">Delete selected snapshot</property>
|
||||
<signal name="clicked" handler="on_snapshot_delete_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="image11">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-delete</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="buttonbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="snapshot-apply">
|
||||
<property name="label">gtk-apply</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="tooltip_markup" translatable="yes">Save updated snapshot metadata</property>
|
||||
<property name="tooltip_text" translatable="yes">Save updated snapshot metadata</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="clicked" handler="on_snapshot_apply_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -189,8 +189,11 @@ class vmmGObjectUI(vmmGObject):
|
|||
self.builder.set_translation_domain("virt-manager")
|
||||
self.builder.add_from_string(file(uifile).read())
|
||||
|
||||
self.topwin = self.widget(windowname)
|
||||
self.topwin.hide()
|
||||
if not topwin:
|
||||
self.topwin = self.widget(windowname)
|
||||
self.topwin.hide()
|
||||
else:
|
||||
self.topwin = topwin
|
||||
else:
|
||||
self.builder = builder
|
||||
self.topwin = topwin
|
||||
|
|
|
@ -35,6 +35,7 @@ from virtManager.baseclass import vmmGObjectUI
|
|||
from virtManager.addhardware import vmmAddHardware
|
||||
from virtManager.choosecd import vmmChooseCD
|
||||
from virtManager.console import vmmConsolePages
|
||||
from virtManager.snapshots import vmmSnapshotPage
|
||||
from virtManager.serialcon import vmmSerialConsole
|
||||
from virtManager.graphwidgets import Sparkline
|
||||
|
||||
|
@ -42,8 +43,7 @@ import virtinst
|
|||
from virtinst import util
|
||||
|
||||
|
||||
# Parameters that can be edited in the details window
|
||||
EDIT_TOTAL = 39
|
||||
# Parameters that can be editted in the details window
|
||||
(EDIT_NAME,
|
||||
EDIT_ACPI,
|
||||
EDIT_APIC,
|
||||
|
@ -95,36 +95,36 @@ EDIT_WATCHDOG_ACTION,
|
|||
EDIT_CONTROLLER_MODEL,
|
||||
|
||||
EDIT_TPM_TYPE,
|
||||
) = range(EDIT_TOTAL)
|
||||
) = range(1, 40)
|
||||
|
||||
|
||||
# Columns in hw list model
|
||||
HW_LIST_COL_LABEL = 0
|
||||
HW_LIST_COL_ICON_NAME = 1
|
||||
HW_LIST_COL_ICON_SIZE = 2
|
||||
HW_LIST_COL_TYPE = 3
|
||||
HW_LIST_COL_DEVICE = 4
|
||||
(HW_LIST_COL_LABEL,
|
||||
HW_LIST_COL_ICON_NAME,
|
||||
HW_LIST_COL_ICON_SIZE,
|
||||
HW_LIST_COL_TYPE,
|
||||
HW_LIST_COL_DEVICE) = range(5)
|
||||
|
||||
# Types for the hw list model: numbers specify what order they will be listed
|
||||
HW_LIST_TYPE_GENERAL = 0
|
||||
HW_LIST_TYPE_STATS = 1
|
||||
HW_LIST_TYPE_CPU = 2
|
||||
HW_LIST_TYPE_MEMORY = 3
|
||||
HW_LIST_TYPE_BOOT = 4
|
||||
HW_LIST_TYPE_DISK = 5
|
||||
HW_LIST_TYPE_NIC = 6
|
||||
HW_LIST_TYPE_INPUT = 7
|
||||
HW_LIST_TYPE_GRAPHICS = 8
|
||||
HW_LIST_TYPE_SOUND = 9
|
||||
HW_LIST_TYPE_CHAR = 10
|
||||
HW_LIST_TYPE_HOSTDEV = 11
|
||||
HW_LIST_TYPE_VIDEO = 12
|
||||
HW_LIST_TYPE_WATCHDOG = 13
|
||||
HW_LIST_TYPE_CONTROLLER = 14
|
||||
HW_LIST_TYPE_FILESYSTEM = 15
|
||||
HW_LIST_TYPE_SMARTCARD = 16
|
||||
HW_LIST_TYPE_REDIRDEV = 17
|
||||
HW_LIST_TYPE_TPM = 18
|
||||
(HW_LIST_TYPE_GENERAL,
|
||||
HW_LIST_TYPE_STATS,
|
||||
HW_LIST_TYPE_CPU,
|
||||
HW_LIST_TYPE_MEMORY,
|
||||
HW_LIST_TYPE_BOOT,
|
||||
HW_LIST_TYPE_DISK,
|
||||
HW_LIST_TYPE_NIC,
|
||||
HW_LIST_TYPE_INPUT,
|
||||
HW_LIST_TYPE_GRAPHICS,
|
||||
HW_LIST_TYPE_SOUND,
|
||||
HW_LIST_TYPE_CHAR,
|
||||
HW_LIST_TYPE_HOSTDEV,
|
||||
HW_LIST_TYPE_VIDEO,
|
||||
HW_LIST_TYPE_WATCHDOG,
|
||||
HW_LIST_TYPE_CONTROLLER,
|
||||
HW_LIST_TYPE_FILESYSTEM,
|
||||
HW_LIST_TYPE_SMARTCARD,
|
||||
HW_LIST_TYPE_REDIRDEV,
|
||||
HW_LIST_TYPE_TPM) = range(19)
|
||||
|
||||
remove_pages = [HW_LIST_TYPE_NIC, HW_LIST_TYPE_INPUT,
|
||||
HW_LIST_TYPE_GRAPHICS, HW_LIST_TYPE_SOUND, HW_LIST_TYPE_CHAR,
|
||||
|
@ -134,15 +134,16 @@ remove_pages = [HW_LIST_TYPE_NIC, HW_LIST_TYPE_INPUT,
|
|||
HW_LIST_TYPE_REDIRDEV, HW_LIST_TYPE_TPM]
|
||||
|
||||
# Boot device columns
|
||||
BOOT_DEV_TYPE = 0
|
||||
BOOT_LABEL = 1
|
||||
BOOT_ICON = 2
|
||||
BOOT_ACTIVE = 3
|
||||
(BOOT_DEV_TYPE,
|
||||
BOOT_LABEL,
|
||||
BOOT_ICON,
|
||||
BOOT_ACTIVE) = range(4)
|
||||
|
||||
# Main tab pages
|
||||
PAGE_CONSOLE = 0
|
||||
PAGE_DETAILS = 1
|
||||
PAGE_DYNAMIC_OFFSET = 2
|
||||
(PAGE_CONSOLE,
|
||||
PAGE_DETAILS,
|
||||
PAGE_SNAPSHOTS,
|
||||
PAGE_DYNAMIC_OFFSET) = range(4)
|
||||
|
||||
|
||||
def prettyify_disk_bus(bus):
|
||||
|
@ -374,6 +375,8 @@ class vmmDetails(vmmGObjectUI):
|
|||
self._cpu_copy_host = False
|
||||
|
||||
self.console = vmmConsolePages(self.vm, self.builder, self.topwin)
|
||||
self.snapshots = vmmSnapshotPage(self.vm, self.builder, self.topwin)
|
||||
self.widget("snapshot-placeholder").add(self.snapshots.top_box)
|
||||
|
||||
# Set default window size
|
||||
w, h = self.vm.get_details_window_size()
|
||||
|
@ -400,6 +403,7 @@ class vmmDetails(vmmGObjectUI):
|
|||
|
||||
"on_control_vm_details_toggled": self.details_console_changed,
|
||||
"on_control_vm_console_toggled": self.details_console_changed,
|
||||
"on_control_snapshots_toggled": self.details_console_changed,
|
||||
"on_control_run_clicked": self.control_vm_run,
|
||||
"on_control_shutdown_clicked": self.control_vm_shutdown,
|
||||
"on_control_pause_toggled": self.control_vm_pause,
|
||||
|
@ -425,6 +429,7 @@ class vmmDetails(vmmGObjectUI):
|
|||
"on_details_menu_view_manager_activate": self.view_manager,
|
||||
"on_details_menu_view_details_toggled": self.details_console_changed,
|
||||
"on_details_menu_view_console_toggled": self.details_console_changed,
|
||||
"on_details_menu_view_snapshots_toggled": self.details_console_changed,
|
||||
|
||||
"on_details_pages_switch_page": self.switch_page,
|
||||
|
||||
|
@ -576,6 +581,8 @@ class vmmDetails(vmmGObjectUI):
|
|||
|
||||
self.console.cleanup()
|
||||
self.console = None
|
||||
self.snapshots.cleanup()
|
||||
self.snapshots = None
|
||||
|
||||
self.vm = None
|
||||
self.conn = None
|
||||
|
@ -1369,10 +1376,10 @@ class vmmDetails(vmmGObjectUI):
|
|||
if not src.get_active():
|
||||
return
|
||||
|
||||
is_details = False
|
||||
if (src == self.widget("control-vm-details") or
|
||||
src == self.widget("details-menu-view-details")):
|
||||
is_details = True
|
||||
is_details = (src == self.widget("control-vm-details") or
|
||||
src == self.widget("details-menu-view-details"))
|
||||
is_snapshot = (src == self.widget("control-snapshots") or
|
||||
src == self.widget("details-menu-view-snapshots"))
|
||||
|
||||
pages = self.widget("details-pages")
|
||||
if pages.get_current_page() == PAGE_DETAILS:
|
||||
|
@ -1383,29 +1390,40 @@ class vmmDetails(vmmGObjectUI):
|
|||
|
||||
if is_details:
|
||||
pages.set_current_page(PAGE_DETAILS)
|
||||
elif is_snapshot:
|
||||
self.snapshots.show_page()
|
||||
pages.set_current_page(PAGE_SNAPSHOTS)
|
||||
else:
|
||||
pages.set_current_page(self.last_console_page)
|
||||
|
||||
def sync_details_console_view(self, is_details):
|
||||
def sync_details_console_view(self, newpage):
|
||||
details = self.widget("control-vm-details")
|
||||
details_menu = self.widget("details-menu-view-details")
|
||||
console = self.widget("control-vm-console")
|
||||
console_menu = self.widget("details-menu-view-console")
|
||||
snapshot = self.widget("control-snapshots")
|
||||
snapshot_menu = self.widget("details-menu-view-snapshots")
|
||||
|
||||
is_details = newpage == PAGE_DETAILS
|
||||
is_snapshot = newpage == PAGE_SNAPSHOTS
|
||||
is_console = not is_details and not is_snapshot
|
||||
|
||||
try:
|
||||
self.ignoreDetails = True
|
||||
|
||||
details.set_active(is_details)
|
||||
details_menu.set_active(is_details)
|
||||
console.set_active(not is_details)
|
||||
console_menu.set_active(not is_details)
|
||||
snapshot.set_active(is_snapshot)
|
||||
snapshot_menu.set_active(is_snapshot)
|
||||
console.set_active(is_console)
|
||||
console_menu.set_active(is_console)
|
||||
finally:
|
||||
self.ignoreDetails = False
|
||||
|
||||
def switch_page(self, ignore1=None, ignore2=None, newpage=None):
|
||||
self.page_refresh(newpage)
|
||||
|
||||
self.sync_details_console_view(newpage == PAGE_DETAILS)
|
||||
self.sync_details_console_view(newpage)
|
||||
self.console.set_allow_fullscreen()
|
||||
|
||||
if newpage == PAGE_CONSOLE or newpage >= PAGE_DYNAMIC_OFFSET:
|
||||
|
@ -1467,8 +1485,7 @@ class vmmDetails(vmmGObjectUI):
|
|||
if not run:
|
||||
self.activate_default_console_page()
|
||||
|
||||
self.widget("overview-status-text").set_text(
|
||||
self.vm.run_status())
|
||||
self.widget("overview-status-text").set_text(self.vm.run_status())
|
||||
self.widget("overview-status-icon").set_from_icon_name(
|
||||
self.vm.run_status_icon_name(), Gtk.IconSize.MENU)
|
||||
|
||||
|
@ -1507,6 +1524,9 @@ class vmmDetails(vmmGObjectUI):
|
|||
self._show_serial_tab(name, serialidx)
|
||||
break
|
||||
|
||||
|
||||
# activate_* are called from engine.py via CLI options
|
||||
|
||||
def activate_default_page(self):
|
||||
pages = self.widget("details-pages")
|
||||
pages.set_current_page(PAGE_CONSOLE)
|
||||
|
@ -2166,7 +2186,8 @@ class vmmDetails(vmmGObjectUI):
|
|||
if self.widget("security-type-box").get_sensitive():
|
||||
semodel = self.get_text("security-model")
|
||||
|
||||
add_define(self.vm.define_seclabel, semodel, setype, selabel, relabel)
|
||||
add_define(self.vm.define_seclabel,
|
||||
semodel, setype, selabel, relabel)
|
||||
|
||||
if self.edited(EDIT_DESC):
|
||||
desc_widget = self.widget("overview-description")
|
||||
|
|
|
@ -139,6 +139,34 @@ class vmmInspectionData(object):
|
|||
self.applications = None
|
||||
|
||||
|
||||
class vmmDomainSnapshot(vmmLibvirtObject):
|
||||
"""
|
||||
Class wrapping a virDomainSnapshot object
|
||||
"""
|
||||
def __init__(self, conn, backend):
|
||||
vmmLibvirtObject.__init__(self, conn, backend, backend.getName())
|
||||
|
||||
self._xmlbackend = None
|
||||
self.refresh_xml()
|
||||
|
||||
def get_name(self):
|
||||
return self.xml.name
|
||||
def _XMLDesc(self, flags):
|
||||
rawxml = self._backend.getXMLDesc(flags=flags)
|
||||
self._xmlbackend = virtinst.DomainSnapshot(self.conn.get_backend(),
|
||||
rawxml)
|
||||
return self._xmlbackend.get_xml_config()
|
||||
|
||||
def _get_xml_backend(self):
|
||||
return self._xmlbackend
|
||||
xml = property(_get_xml_backend)
|
||||
|
||||
def is_current(self):
|
||||
return self._backend.isCurrent()
|
||||
def delete(self):
|
||||
self._backend.delete()
|
||||
|
||||
|
||||
class vmmDomain(vmmLibvirtObject):
|
||||
"""
|
||||
Class wrapping virDomain libvirt objects. Is also extended to be
|
||||
|
@ -172,6 +200,7 @@ class vmmDomain(vmmLibvirtObject):
|
|||
self._is_management_domain = None
|
||||
self._id = None
|
||||
self._name = None
|
||||
self._snapshot_list = None
|
||||
|
||||
self._inactive_xml_flags = 0
|
||||
self._active_xml_flags = 0
|
||||
|
@ -182,6 +211,7 @@ class vmmDomain(vmmLibvirtObject):
|
|||
self._getjobinfo_supported = None
|
||||
self.managedsave_supported = False
|
||||
self.remote_console_supported = False
|
||||
self.snapshots_supported = False
|
||||
|
||||
self._guest = None
|
||||
self._guest_to_define = None
|
||||
|
@ -201,6 +231,11 @@ class vmmDomain(vmmLibvirtObject):
|
|||
|
||||
self._libvirt_init()
|
||||
|
||||
def _cleanup(self):
|
||||
for snap in self._snapshot_list or []:
|
||||
snap.cleanup()
|
||||
self._snapshot_list = None
|
||||
|
||||
def _get_getvcpus_supported(self):
|
||||
if self._getvcpus_supported is None:
|
||||
self._getvcpus_supported = True
|
||||
|
@ -232,6 +267,9 @@ class vmmDomain(vmmLibvirtObject):
|
|||
self.remote_console_supported = self.conn.check_domain_support(
|
||||
self._backend,
|
||||
self.conn.SUPPORT_DOMAIN_CONSOLE_STREAM)
|
||||
self.snapshots_supported = self.conn.check_domain_support(
|
||||
self._backend,
|
||||
self.conn.SUPPORT_DOMAIN_LIST_SNAPSHOTS)
|
||||
|
||||
# Determine available XML flags (older libvirt versions will error
|
||||
# out if passed SECURE_XML, INACTIVE_XML, etc)
|
||||
|
@ -282,6 +320,7 @@ class vmmDomain(vmmLibvirtObject):
|
|||
prettyname = "%s %s" % (vendor, product)
|
||||
ret.append(error % prettyname)
|
||||
|
||||
|
||||
###########################
|
||||
# Misc API getter methods #
|
||||
###########################
|
||||
|
@ -339,6 +378,7 @@ class vmmDomain(vmmLibvirtObject):
|
|||
return "-"
|
||||
return str(i)
|
||||
|
||||
|
||||
#############################
|
||||
# Internal XML handling API #
|
||||
#############################
|
||||
|
@ -448,7 +488,6 @@ class vmmDomain(vmmLibvirtObject):
|
|||
self.emit("config-changed")
|
||||
|
||||
# Device Add/Remove
|
||||
|
||||
def add_device(self, devobj):
|
||||
"""
|
||||
Redefine guest with appended device XML 'devxml'
|
||||
|
@ -948,6 +987,30 @@ class vmmDomain(vmmLibvirtObject):
|
|||
def open_console(self, devname, stream, flags=0):
|
||||
return self._backend.openConsole(devname, stream, flags)
|
||||
|
||||
def refresh_snapshots(self):
|
||||
self._snapshot_list = None
|
||||
|
||||
def list_snapshots(self):
|
||||
if self._snapshot_list is None:
|
||||
newlist = []
|
||||
for rawsnap in self._backend.listAllSnapshots():
|
||||
newlist.append(vmmDomainSnapshot(self.conn, rawsnap))
|
||||
self._snapshot_list = newlist
|
||||
return self._snapshot_list[:]
|
||||
|
||||
def revert_to_snapshot(self, snap):
|
||||
self._backend.revertToSnapshot(snap.get_backend())
|
||||
self.idle_add(self.force_update_status)
|
||||
|
||||
def create_snapshot(self, xml, redefine=False):
|
||||
flags = 0
|
||||
if redefine:
|
||||
flags = (flags | libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)
|
||||
|
||||
logging.debug("Creating snapshot flags=%s xml=\n%s", flags, xml)
|
||||
self._backend.snapshotCreateXML(xml, flags)
|
||||
|
||||
|
||||
########################
|
||||
# XML Parsing routines #
|
||||
########################
|
||||
|
@ -1539,24 +1602,12 @@ class vmmDomain(vmmLibvirtObject):
|
|||
return self.status() in [libvirt.VIR_DOMAIN_PAUSED]
|
||||
|
||||
def run_status_icon_name(self):
|
||||
status_icons = {
|
||||
libvirt.VIR_DOMAIN_BLOCKED: "state_running",
|
||||
libvirt.VIR_DOMAIN_CRASHED: "state_shutoff",
|
||||
libvirt.VIR_DOMAIN_PAUSED: "state_paused",
|
||||
libvirt.VIR_DOMAIN_RUNNING: "state_running",
|
||||
libvirt.VIR_DOMAIN_SHUTDOWN: "state_shutoff",
|
||||
libvirt.VIR_DOMAIN_SHUTOFF: "state_shutoff",
|
||||
libvirt.VIR_DOMAIN_NOSTATE: "state_running",
|
||||
# VIR_DOMAIN_PMSUSPENDED
|
||||
7: "state_paused",
|
||||
}
|
||||
|
||||
status = self.status()
|
||||
if status not in status_icons:
|
||||
if status not in uihelpers.vm_status_icons:
|
||||
logging.debug("Unknown status %d, using NOSTATE")
|
||||
status = libvirt.VIR_DOMAIN_NOSTATE
|
||||
|
||||
return status_icons[status]
|
||||
return uihelpers.vm_status_icons[status]
|
||||
|
||||
def force_update_status(self):
|
||||
"""
|
||||
|
|
|
@ -62,14 +62,6 @@ COL_DISK = 3
|
|||
COL_NETWORK = 4
|
||||
|
||||
|
||||
try:
|
||||
import gi
|
||||
gi.check_version("3.7.4")
|
||||
can_set_row_none = True
|
||||
except (ValueError, AttributeError):
|
||||
can_set_row_none = False
|
||||
|
||||
|
||||
def _style_get_prop(widget, propname):
|
||||
value = GObject.Value()
|
||||
value.init(GObject.TYPE_INT)
|
||||
|
@ -903,7 +895,7 @@ class vmmManager(vmmGObjectUI):
|
|||
|
||||
if config_changed:
|
||||
desc = vm.get_description()
|
||||
if not can_set_row_none:
|
||||
if not uihelpers.can_set_row_none:
|
||||
desc = desc or ""
|
||||
row[ROW_HINT] = util.xml_escape(desc)
|
||||
except libvirt.libvirtError, e:
|
||||
|
@ -922,7 +914,7 @@ class vmmManager(vmmGObjectUI):
|
|||
|
||||
row = self.rows[self.vm_row_key(vm)]
|
||||
new_icon = self.get_inspection_icon_pixbuf(vm, 16, 16)
|
||||
if not can_set_row_none:
|
||||
if not uihelpers.can_set_row_none:
|
||||
new_icon = new_icon or ""
|
||||
row[ROW_INSPECTION_OS_ICON] = new_icon
|
||||
model.row_changed(row.path, row.iter)
|
||||
|
|
|
@ -0,0 +1,365 @@
|
|||
#
|
||||
# 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)
|
||||
|
||||
self.widget("snapshot-new-ok").set_image(
|
||||
Gtk.Image.new_from_stock(Gtk.STOCK_NEW, Gtk.IconSize.BUTTON))
|
||||
|
||||
|
||||
###################
|
||||
# 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:
|
||||
desc = snap.xml.description
|
||||
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 ""
|
||||
desc = snap and snap.xml.description or ""
|
||||
state = snap and snap.xml.state or "shutoff"
|
||||
timestamp = ""
|
||||
if snap:
|
||||
timestamp = str(datetime.datetime.fromtimestamp(
|
||||
snap.xml.creationTime))
|
||||
|
||||
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 ""
|
||||
|
||||
snap.xml.description = desc
|
||||
newxml = snap.xml.get_xml_config()
|
||||
self.vm.create_snapshot(newxml, redefine=True)
|
||||
snap.refresh_xml()
|
||||
self._set_snapshot_state(snap)
|
||||
|
||||
# XXX refresh in place
|
||||
|
||||
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()],
|
||||
_("Creating snapshot"),
|
||||
_("Creating virtual machine snapshot"),
|
||||
self.topwin)
|
||||
|
||||
error, details = progWin.run()
|
||||
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()
|
||||
|
||||
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'") %
|
||||
snap.get_name())
|
||||
self._refresh_snapshots()
|
||||
|
||||
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,
|
||||
_("Error deleting snapshot '%s'") % snap.get_name())
|
||||
self._refresh_snapshots()
|
||||
|
||||
|
||||
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))
|
|
@ -39,6 +39,25 @@ OPTICAL_DEV_KEY = 3
|
|||
OPTICAL_MEDIA_KEY = 4
|
||||
OPTICAL_IS_VALID = 5
|
||||
|
||||
try:
|
||||
import gi
|
||||
gi.check_version("3.7.4")
|
||||
can_set_row_none = True
|
||||
except (ValueError, AttributeError):
|
||||
can_set_row_none = False
|
||||
|
||||
vm_status_icons = {
|
||||
libvirt.VIR_DOMAIN_BLOCKED: "state_running",
|
||||
libvirt.VIR_DOMAIN_CRASHED: "state_shutoff",
|
||||
libvirt.VIR_DOMAIN_PAUSED: "state_paused",
|
||||
libvirt.VIR_DOMAIN_RUNNING: "state_running",
|
||||
libvirt.VIR_DOMAIN_SHUTDOWN: "state_shutoff",
|
||||
libvirt.VIR_DOMAIN_SHUTOFF: "state_shutoff",
|
||||
libvirt.VIR_DOMAIN_NOSTATE: "state_running",
|
||||
# VIR_DOMAIN_PMSUSPENDED
|
||||
7: "state_paused",
|
||||
}
|
||||
|
||||
|
||||
############################################################
|
||||
# Helpers for shared storage UI between create/addhardware #
|
||||
|
|
Loading…
Reference in New Issue