Break out shared storage UI to storagelist.py (bz 1060433)

We were already sharing a chunk of this in a haphazard way. Now officially
break it all out, similar to netlist.py. This mostly unifies the views
of host->storage and storagebrowser.py
This commit is contained in:
Cole Robinson
2015-04-09 12:31:24 -04:00
parent 6634053533
commit 058c06972e
15 changed files with 1565 additions and 1726 deletions
+17 -530
View File
@@ -22,20 +22,16 @@ import logging
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk
from virtinst import VirtualDisk
from virtinst import StoragePool
from virtinst import Interface
from . import uiutil
from .asyncjob import vmmAsyncJob
from .createnet import vmmCreateNetwork
from .createpool import vmmCreatePool
from .createvol import vmmCreateVolume
from .createinterface import vmmCreateInterface
from .baseclass import vmmGObjectUI
from .createnet import vmmCreateNetwork
from .createinterface import vmmCreateInterface
from .graphwidgets import Sparkline
from .storagelist import vmmStorageList
INTERFACE_PAGE_INFO = 0
INTERFACE_PAGE_ERROR = 1
@@ -46,11 +42,6 @@ EDIT_NET_AUTOSTART,
EDIT_NET_QOS,
) = range(3)
EDIT_POOL_IDS = (
EDIT_POOL_NAME,
EDIT_POOL_AUTOSTART,
) = range(100, 102)
EDIT_INTERFACE_IDS = (
EDIT_INTERFACE_STARTMODE,
) = range(200, 201)
@@ -76,11 +67,7 @@ class vmmHost(vmmGObjectUI):
self.ICON_SHUTOFF = "state_shutoff"
self.addnet = None
self.addpool = None
self.addvol = None
self.addinterface = None
self.volmenu = None
self._in_refresh = False
self.active_edits = []
@@ -88,8 +75,9 @@ class vmmHost(vmmGObjectUI):
self.memory_usage_graph = None
self.init_conn_state()
self.init_net_state()
self.storagelist = None
self.init_storage_state()
self.init_net_state()
self.init_interface_state()
self.builder.connect_signals({
@@ -111,20 +99,6 @@ class vmmHost(vmmGObjectUI):
"on_net_name_changed": (lambda *x:
self.enable_net_apply(x, EDIT_NET_NAME)),
"on_pool_add_clicked" : self.add_pool,
"on_vol_add_clicked" : self.add_vol,
"on_pool_stop_clicked": self.stop_pool,
"on_pool_start_clicked": self.start_pool,
"on_pool_delete_clicked": self.delete_pool,
"on_pool_refresh_clicked": self.pool_refresh,
"on_pool_autostart_toggled": self.pool_autostart_changed,
"on_vol_delete_clicked": self.delete_vol,
"on_vol_list_button_press_event": self.popup_vol_menu,
"on_pool_apply_clicked": (lambda *x: self.pool_apply()),
"on_vol_list_changed": self.vol_selected,
"on_pool_name_changed": (lambda *x:
self.enable_pool_apply(x, EDIT_POOL_NAME)),
"on_interface_add_clicked" : self.add_interface,
"on_interface_start_clicked" : self.start_interface,
"on_interface_stop_clicked" : self.stop_interface,
@@ -153,7 +127,6 @@ class vmmHost(vmmGObjectUI):
})
self.repopulate_networks()
self.repopulate_storage_pools()
self.repopulate_interfaces()
self.conn.connect("net-added", self.repopulate_networks)
@@ -161,11 +134,6 @@ class vmmHost(vmmGObjectUI):
self.conn.connect("net-started", self.refresh_network)
self.conn.connect("net-stopped", self.refresh_network)
self.conn.connect("pool-added", self.repopulate_storage_pools)
self.conn.connect("pool-removed", self.repopulate_storage_pools)
self.conn.connect("pool-started", self.refresh_storage_pool)
self.conn.connect("pool-stopped", self.refresh_storage_pool)
self.conn.connect("interface-added", self.repopulate_interfaces)
self.conn.connect("interface-removed", self.repopulate_interfaces)
self.conn.connect("interface-started", self.refresh_interface)
@@ -173,7 +141,11 @@ class vmmHost(vmmGObjectUI):
self.conn.connect("state-changed", self.conn_state_changed)
self.conn.connect("resources-sampled", self.refresh_resources)
self.reset_state()
self.refresh_resources()
self.conn_state_changed()
self.widget("config-autoconnect").set_active(
self.conn.get_autoconnect())
def init_net_state(self):
@@ -200,51 +172,9 @@ class vmmHost(vmmGObjectUI):
netListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING)
def init_storage_state(self):
self.widget("storage-pages").set_show_tabs(False)
self.storagelist = vmmStorageList(self.conn, self.builder, self.topwin)
self.widget("storage-align").add(self.storagelist.top_box)
self.volmenu = Gtk.Menu()
volCopyPath = Gtk.ImageMenuItem.new_with_label(_("Copy Volume Path"))
volCopyImage = Gtk.Image()
volCopyImage.set_from_stock(Gtk.STOCK_COPY, Gtk.IconSize.MENU)
volCopyPath.set_image(volCopyImage)
volCopyPath.show()
volCopyPath.connect("activate", self.copy_vol_path)
self.volmenu.add(volCopyPath)
volListModel = Gtk.ListStore(str, str, str, str, str)
self.widget("vol-list").set_model(volListModel)
volCol = Gtk.TreeViewColumn("Volumes")
vol_txt1 = Gtk.CellRendererText()
volCol.pack_start(vol_txt1, True)
volCol.add_attribute(vol_txt1, 'text', 1)
volCol.set_sort_column_id(1)
self.widget("vol-list").append_column(volCol)
volSizeCol = Gtk.TreeViewColumn("Size")
vol_txt2 = Gtk.CellRendererText()
volSizeCol.pack_start(vol_txt2, False)
volSizeCol.add_attribute(vol_txt2, 'text', 2)
volSizeCol.set_sort_column_id(2)
self.widget("vol-list").append_column(volSizeCol)
volFormatCol = Gtk.TreeViewColumn("Format")
vol_txt3 = Gtk.CellRendererText()
volFormatCol.pack_start(vol_txt3, False)
volFormatCol.add_attribute(vol_txt3, 'text', 3)
volFormatCol.set_sort_column_id(3)
self.widget("vol-list").append_column(volFormatCol)
volUseCol = Gtk.TreeViewColumn("Used By")
vol_txt4 = Gtk.CellRendererText()
volUseCol.pack_start(vol_txt4, False)
volUseCol.add_attribute(vol_txt4, 'text', 4)
volUseCol.set_sort_column_id(4)
self.widget("vol-list").append_column(volUseCol)
volListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING)
self.init_pool_list()
def init_interface_state(self):
self.widget("interface-pages").set_show_tabs(False)
@@ -346,25 +276,17 @@ class vmmHost(vmmGObjectUI):
def _cleanup(self):
self.conn = None
self.storagelist.cleanup()
self.storagelist = None
if self.addnet:
self.addnet.cleanup()
self.addnet = None
if self.addpool:
self.addpool.cleanup()
self.addpool = None
if self.addvol:
self.addvol.cleanup()
self.addvol = None
if self.addinterface:
self.addinterface.cleanup()
self.addinterface = None
self.volmenu.destroy()
self.volmenu = None
self.cpu_usage_graph.destroy()
self.cpu_usage_graph = None
@@ -380,13 +302,6 @@ class vmmHost(vmmGObjectUI):
def exit_app(self, src_ignore):
self.emit("action-exit-app")
def reset_state(self):
self.refresh_resources()
self.conn_state_changed()
# Update autostart value
auto = self.conn.get_autoconnect()
self.widget("config-autoconnect").set_active(auto)
def page_changed(self, src, child, pagenum):
ignore = src
@@ -396,8 +311,7 @@ class vmmHost(vmmGObjectUI):
self.repopulate_networks()
self.conn.schedule_priority_tick(pollnet=True)
elif pagenum == 2:
self.repopulate_storage_volumes()
self.conn.schedule_priority_tick(pollpool=True)
self.storagelist.refresh_page()
elif pagenum == 3:
self.repopulate_interfaces()
self.conn.schedule_priority_tick(polliface=True)
@@ -425,18 +339,14 @@ class vmmHost(vmmGObjectUI):
self.widget("menu_file_restore_saved").set_sensitive(conn_active)
self.widget("net-add").set_sensitive(conn_active and
self.conn.is_network_capable())
self.widget("pool-add").set_sensitive(conn_active and
self.conn.is_storage_capable())
self.widget("interface-add").set_sensitive(conn_active and
self.conn.is_interface_capable())
if not conn_active:
self.set_net_error_page(_("Connection not active."))
self.set_storage_error_page(_("Connection not active."))
self.set_interface_error_page(_("Connection not active."))
self.repopulate_networks()
self.repopulate_storage_pools()
self.repopulate_interfaces()
return
@@ -445,10 +355,6 @@ class vmmHost(vmmGObjectUI):
_("Libvirt connection does not support virtual network "
"management."))
if not self.conn.is_storage_capable():
self.set_storage_error_page(
_("Libvirt connection does not support storage management."))
if not self.conn.is_interface_capable():
self.set_interface_error_page(
_("Libvirt connection does not support interface management."))
@@ -800,313 +706,6 @@ class vmmHost(vmmGObjectUI):
curnet and curnet.get_connkey() or None)
# ------------------------------
# Storage Manager methods
# ------------------------------
def stop_pool(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
logging.debug("Stopping pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.stop, [], self,
_("Error stopping pool '%s'") % pool.get_name())
def start_pool(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
logging.debug("Starting pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.start, [], self,
_("Error starting pool '%s'") % pool.get_name())
def delete_pool(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
result = self.err.yes_no(_("Are you sure you want to permanently "
"delete the pool %s?") % pool.get_name())
if not result:
return
logging.debug("Deleting pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.delete, [], self,
_("Error deleting pool '%s'") % pool.get_name())
def pool_refresh(self, src_ignore):
if self._in_refresh:
logging.debug("Already refreshing the pool, skipping")
return
if not self.confirm_changes():
return
pool = self.current_pool()
if pool is None:
return
self._in_refresh = True
def cb():
try:
pool.refresh()
self.idle_add(self.refresh_current_pool)
finally:
self._in_refresh = False
logging.debug("Refresh pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(cb, [], self,
_("Error refreshing pool '%s'") % pool.get_name())
def delete_vol(self, src_ignore):
vol = self.current_vol()
if vol is None:
return
result = self.err.yes_no(_("Are you sure you want to permanently "
"delete the volume %s?") % vol.get_name())
if not result:
return
def cb():
vol.delete()
def idlecb():
self.refresh_current_pool()
self.repopulate_storage_volumes()
self.idle_add(idlecb)
logging.debug("Deleting volume '%s'", vol.get_name())
vmmAsyncJob.simple_async_noshow(cb, [], self,
_("Error refreshing volume '%s'") % vol.get_name())
def add_pool(self, src_ignore):
logging.debug("Launching 'Add Pool' wizard")
try:
if self.addpool is None:
self.addpool = vmmCreatePool(self.conn)
self.addpool.show(self.topwin)
except Exception, e:
self.err.show_err(_("Error launching pool wizard: %s") % str(e))
def add_vol(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
logging.debug("Launching 'Add Volume' wizard for pool '%s'",
pool.get_name())
try:
if self.addvol is None:
self.addvol = vmmCreateVolume(self.conn, pool)
self.addvol.connect("vol-created", self.refresh_current_pool)
else:
self.addvol.set_parent_pool(self.conn, pool)
self.addvol.show(self.topwin)
except Exception, e:
self.err.show_err(_("Error launching volume wizard: %s") % str(e))
def refresh_current_pool(self, ignore1=None):
cp = self.current_pool()
if cp is None:
return
cp.refresh()
self.refresh_storage_pool(None, cp.get_connkey())
def current_pool(self):
connkey = uiutil.get_list_selection(self.widget("pool-list"), 0)
try:
return connkey and self.conn.get_pool(connkey)
except KeyError:
return None
def current_vol(self):
pool = self.current_pool()
if not pool:
return None
connkey = uiutil.get_list_selection(self.widget("vol-list"), 0)
try:
return connkey and pool.get_volume(connkey)
except KeyError:
return None
def pool_apply(self):
pool = self.current_pool()
if pool is None:
return
logging.debug("Applying changes for pool '%s'", pool.get_name())
try:
if EDIT_POOL_AUTOSTART in self.active_edits:
auto = self.widget("pool-autostart").get_active()
pool.set_autostart(auto)
if EDIT_POOL_NAME in self.active_edits:
pool.define_name(self.widget("pool-name-entry").get_text())
self.idle_add(self.repopulate_storage_pools)
except Exception, e:
self.err.show_err(_("Error changing pool settings: %s") % str(e))
return
self.disable_pool_apply()
def disable_pool_apply(self):
for i in EDIT_POOL_IDS:
if i in self.active_edits:
self.active_edits.remove(i)
self.widget("pool-apply").set_sensitive(False)
def enable_pool_apply(self, *arglist):
edittype = arglist[-1]
self.widget("pool-apply").set_sensitive(True)
if edittype not in self.active_edits:
self.active_edits.append(edittype)
def pool_autostart_changed(self, src_ignore):
auto = self.widget("pool-autostart").get_active()
self.widget("pool-autostart").set_label(auto and
_("On Boot") or
_("Never"))
self.enable_pool_apply(EDIT_POOL_AUTOSTART)
def set_storage_error_page(self, msg):
self.reset_pool_state()
self.widget("storage-pages").set_current_page(1)
self.widget("storage-error-label").set_text(msg)
def pool_selected(self, src):
model, treeiter = src.get_selected()
if treeiter is None:
self.set_storage_error_page(_("No storage pool selected."))
return
self.widget("storage-pages").set_current_page(0)
connkey = model[treeiter][0]
try:
self.populate_pool_state(connkey)
except Exception, e:
logging.exception(e)
self.set_storage_error_page(_("Error selecting pool: %s") % e)
self.disable_pool_apply()
def populate_pool_state(self, connkey):
pool = self.conn.get_pool(connkey)
pool.tick()
auto = pool.get_autostart()
active = pool.is_active()
# Set pool details state
self.widget("pool-details").set_sensitive(True)
self.widget("pool-name").set_markup("<b>%s:</b>" %
pool.get_name())
self.widget("pool-name-entry").set_text(pool.get_name())
self.widget("pool-name-entry").set_editable(not active)
self.widget("pool-sizes").set_markup(
"""<span size="large">%s Free</span> / <i>%s In Use</i>""" %
(pool.get_pretty_available(), pool.get_pretty_allocation()))
self.widget("pool-type").set_text(
StoragePool.get_pool_type_desc(pool.get_type()))
self.widget("pool-location").set_text(
pool.get_target_path())
self.widget("pool-state-icon").set_from_icon_name(
((active and self.ICON_RUNNING) or self.ICON_SHUTOFF),
Gtk.IconSize.BUTTON)
self.widget("pool-state").set_text(
(active and _("Active")) or _("Inactive"))
self.widget("pool-autostart").set_label(
(auto and _("On Boot")) or _("Never"))
self.widget("pool-autostart").set_active(auto)
self.widget("vol-list").set_sensitive(active)
self.repopulate_storage_volumes()
self.widget("pool-delete").set_sensitive(not active)
self.widget("pool-stop").set_sensitive(active)
self.widget("pool-start").set_sensitive(not active)
self.widget("vol-add").set_sensitive(active)
self.widget("vol-add").set_tooltip_text(_("Create new volume"))
self.widget("vol-delete").set_sensitive(False)
if active and not pool.supports_volume_creation():
self.widget("vol-add").set_sensitive(False)
self.widget("vol-add").set_tooltip_text(
_("Pool does not support volume creation"))
def refresh_storage_pool(self, src, connkey):
ignore = src
refresh_pool_in_list(self.widget("pool-list"), self.conn, connkey)
curpool = self.current_pool()
if curpool.get_connkey() != connkey:
return
# Currently selected pool changed state: force a 'pool_selected' to
# update vol list
self.pool_selected(self.widget("pool-list").get_selection())
def reset_pool_state(self):
self.widget("pool-details").set_sensitive(False)
self.widget("pool-name").set_text("")
self.widget("pool-name-entry").set_text("")
self.widget("pool-sizes").set_markup("""<span size="large"> </span>""")
self.widget("pool-type").set_text("")
self.widget("pool-location").set_text("")
self.widget("pool-state-icon").set_from_icon_name(self.ICON_SHUTOFF,
Gtk.IconSize.BUTTON)
self.widget("pool-state").set_text(_("Inactive"))
self.widget("vol-list").get_model().clear()
self.widget("pool-autostart").set_label(_("Never"))
self.widget("pool-autostart").set_active(False)
self.widget("pool-delete").set_sensitive(False)
self.widget("pool-stop").set_sensitive(False)
self.widget("pool-start").set_sensitive(False)
self.widget("vol-add").set_sensitive(False)
self.widget("vol-delete").set_sensitive(False)
self.widget("vol-list").set_sensitive(False)
self.disable_pool_apply()
def vol_selected(self, src):
model, treeiter = src.get_selected()
ignore = model
if treeiter is None:
self.widget("vol-delete").set_sensitive(False)
return
self.widget("vol-delete").set_sensitive(True)
def popup_vol_menu(self, widget_ignore, event):
if event.button != 3:
return
self.volmenu.popup(None, None, None, None, 0, event.time)
def copy_vol_path(self, ignore=None):
vol = self.current_vol()
if not vol:
return
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
target_path = vol.get_target_path()
if target_path:
clipboard.set_text(target_path, -1)
def repopulate_storage_pools(self, src=None, connkey=None):
ignore = src
ignore = connkey
pool_list = self.widget("pool-list")
populate_storage_pools(pool_list, self.conn, self.current_pool())
def repopulate_storage_volumes(self):
list_widget = self.widget("vol-list")
pool = self.current_pool()
populate_storage_volumes(list_widget, pool, None)
#############################
# Interface manager methods #
#############################
@@ -1382,9 +981,7 @@ class vmmHost(vmmGObjectUI):
chktext=_("Don't warn me again."),
default=False):
if all([edit in EDIT_POOL_IDS for edit in self.active_edits]):
self.pool_apply()
elif all([edit in EDIT_NET_IDS for edit in self.active_edits]):
if all([edit in EDIT_NET_IDS for edit in self.active_edits]):
self.net_apply()
elif all([edit in EDIT_INTERFACE_IDS
for edit in self.active_edits]):
@@ -1392,113 +989,3 @@ class vmmHost(vmmGObjectUI):
self.active_edits = []
return True
def init_pool_list(self):
pool_list = self.widget("pool-list")
init_pool_list(pool_list, self.pool_selected)
sel = pool_list.get_selection()
sel.set_select_function((lambda *x: self.confirm_changes()),
None)
# These functions are broken out, since they are used by storage browser
# dialog.
def init_pool_list(pool_list, changed_func):
poolListModel = Gtk.ListStore(str, str, bool, str)
pool_list.set_model(poolListModel)
pool_list.get_selection().connect("changed", changed_func)
poolCol = Gtk.TreeViewColumn("Storage Pools")
pool_txt = Gtk.CellRendererText()
pool_per = Gtk.CellRendererText()
poolCol.pack_start(pool_per, False)
poolCol.pack_start(pool_txt, True)
poolCol.add_attribute(pool_txt, 'markup', 1)
poolCol.add_attribute(pool_txt, 'sensitive', 2)
poolCol.add_attribute(pool_per, 'markup', 3)
pool_list.append_column(poolCol)
poolListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING)
def refresh_pool_in_list(pool_list, conn, connkey):
for row in pool_list.get_model():
if row[0] != connkey:
continue
# Update active sensitivity and percent available for passed key
row[3] = get_pool_size_percent(conn, connkey)
row[2] = conn.get_pool(connkey).is_active()
return
def populate_storage_pools(pool_list, conn, curpool):
model = pool_list.get_model()
# Prevent events while the model is modified
pool_list.set_model(None)
pool_list.get_selection().unselect_all()
model.clear()
for pool in conn.list_pools():
connkey = pool.get_connkey()
per = get_pool_size_percent(conn, connkey)
pool = conn.get_pool(connkey)
name = pool.get_name()
typ = StoragePool.get_pool_type_desc(pool.get_type())
label = "%s\n<span size='small'>%s</span>" % (name, typ)
model.append([connkey, label, pool.is_active(), per])
pool_list.set_model(model)
uiutil.set_row_selection(pool_list,
curpool and curpool.get_connkey() or None)
def populate_storage_volumes(list_widget, pool, sensitive_cb):
vols = pool and pool.get_volumes() or {}
model = list_widget.get_model()
list_widget.get_selection().unselect_all()
model.clear()
for key in vols.keys():
vol = vols[key]
try:
path = vol.get_target_path()
name = vol.get_pretty_name(pool.get_type())
cap = vol.get_pretty_capacity()
fmt = vol.get_format() or ""
except:
logging.debug("Error getting volume info for '%s', "
"hiding it", key, exc_info=True)
continue
namestr = None
try:
if path:
names = VirtualDisk.path_in_use_by(vol.conn.get_backend(),
path)
namestr = ", ".join(names)
if not namestr:
namestr = None
except:
logging.exception("Failed to determine if storage volume in "
"use.")
row = [key, name, cap, fmt, namestr]
if sensitive_cb:
row.append(sensitive_cb(fmt))
model.append(row)
def get_pool_size_percent(conn, connkey):
pool = conn.get_pool(connkey)
cap = pool.get_capacity()
alloc = pool.get_allocation()
if not cap or alloc is None:
per = 0
else:
per = int(((float(alloc) / float(cap)) * 100))
return "<span size='small' color='#484848'>%s%%</span>" % int(per)