diff --git a/tests/testdriver.xml b/tests/testdriver.xml index b0b447cae..57bcaf77d 100644 --- a/tests/testdriver.xml +++ b/tests/testdriver.xml @@ -1408,6 +1408,19 @@ ba + + dir-vol + 1000000 + 50000 + + + + 0700 + 10736 + 10736 + + + iso-vol 1000000 diff --git a/ui/host.ui b/ui/host.ui index 15d6a2e5f..dc890e886 100644 --- a/ui/host.ui +++ b/ui/host.ui @@ -3,16 +3,6 @@ - - True - False - gtk-delete - - - True - False - gtk-new - True False @@ -152,8 +142,8 @@ True False - 1 Hostname: + 1 1 @@ -166,8 +156,8 @@ True False - 1 Hypervisor: + 1 2 @@ -180,8 +170,8 @@ True False - 1 Memory: + 1 3 @@ -194,8 +184,8 @@ True False - 1 Logical CPUs: + 1 4 @@ -208,8 +198,8 @@ True False - 1 Architecture: + 1 5 @@ -222,10 +212,10 @@ True False - 0 example.com True end + 0 1 @@ -239,8 +229,8 @@ True False - 0 Xen + 0 1 @@ -254,8 +244,8 @@ True False - 0 2000 MiB + 0 1 @@ -269,8 +259,8 @@ True False - 0 4 + 0 1 @@ -284,8 +274,8 @@ True False - 0 x86_64 + 0 1 @@ -299,8 +289,8 @@ True False - 1 Connection: + 1 GTK_FILL @@ -311,10 +301,10 @@ True False - 0 example.com True end + 0 1 @@ -327,10 +317,10 @@ True False - 0 A_utoconnect: True config-autoconnect + 0 6 @@ -414,8 +404,8 @@ True False - 1 60% + 1 False @@ -478,8 +468,8 @@ True False - 1 1.59 GiB of 2.2 GiB + 1 False @@ -603,8 +593,8 @@ True False - 0 Running + 0 True @@ -638,9 +628,9 @@ True False - 0 label True + 0 1 @@ -651,8 +641,8 @@ True False - 0 label + 0 1 @@ -663,8 +653,8 @@ True False - 0 Device: + 0 0 @@ -675,8 +665,8 @@ True False - 0 State: + 0 0 @@ -687,10 +677,10 @@ True False - 0 A_utostart: True net-autostart + 0 0 @@ -701,9 +691,9 @@ True False - 0 Domain: True + 0 0 @@ -714,8 +704,8 @@ True False - 0 Name: + 0 0 @@ -754,9 +744,9 @@ True False - 0 label True + 0 1 @@ -767,9 +757,9 @@ True False - 0 label True + 0 1 @@ -780,8 +770,8 @@ True False - 0 <network-addr> via <gateway-addr> + 0 1 @@ -809,8 +799,8 @@ True False - 0 NAT to any device + 0 True @@ -828,8 +818,8 @@ True False - 0 Network: + 0 0 @@ -840,8 +830,8 @@ True False - 0 DHCP range: + 0 0 @@ -852,8 +842,8 @@ True False - 0 Forwarding: + 0 0 @@ -864,8 +854,8 @@ True False - 0 Static Route: + 0 0 @@ -904,9 +894,9 @@ True False - 0 label True + 0 1 @@ -917,9 +907,9 @@ True False - 0 label True + 0 1 @@ -930,9 +920,9 @@ True False - 0 <network-addr> via <gateway-addr> True + 0 1 @@ -960,8 +950,8 @@ True False - 0 Routed + 0 True @@ -979,8 +969,8 @@ True False - 0 Network: + 0 0 @@ -991,8 +981,8 @@ True False - 0 DHCP range: + 0 0 @@ -1003,8 +993,8 @@ True False - 0 Forwarding: + 0 0 @@ -1015,8 +1005,8 @@ True False - 0 Static Route: + 0 0 @@ -1098,8 +1088,8 @@ True False - 0 Average (KiB/sec): + 0 0 @@ -1110,8 +1100,8 @@ True False - 0 Burst (KiB): + 0 0 @@ -1133,8 +1123,8 @@ True False - 0 Peak (KiB/sec): + 0 0 @@ -1208,8 +1198,8 @@ True False - 0 Burst (KiB/sec): + 0 0 @@ -1220,8 +1210,8 @@ True False - 0 Peak (KiB/sec): + 0 0 @@ -1232,8 +1222,8 @@ True False - 0 Average (KiB/sec): + 0 0 @@ -1468,541 +1458,11 @@ - + True False - 3 - - True - True - 3 - 200 - - - True - True - never - in - - - 134 - True - True - False - - - - - - - - False - True - - - - - True - True - False - - - True - False - 6 - - - True - False - 6 - - - True - False - 5 - 8 - - - True - False - 0 - Pool Type: - - - 0 - 2 - - - - - True - False - 0 - Pool Type - True - - - 1 - 2 - - - - - True - False - 0 - <b>Poolname:</b> - True - - - 0 - 0 - - - - - True - False - 0 - <span size="large">1234 GiB Free</span> / <i>6000 GiB In Use</i> - True - - - 1 - 0 - - - - - True - False - 3 - - - True - False - 0 - gtk-missing-image - - - False - True - 0 - - - - - True - False - Active - - - False - False - 1 - - - - - 1 - 4 - - - - - Some label - True - True - False - True - 0.5 - True - - - - 1 - 5 - - - - - True - False - 0 - label - True - - - 1 - 3 - - - - - True - True - - - - 1 - 1 - - - - - True - False - 0 - Name: - - - 0 - 1 - - - - - True - False - 0 - Location: - - - 0 - 3 - - - - - True - False - 0 - State: - - - 0 - 4 - - - - - True - False - 0 - A_utostart: - True - pool-autostart - - - 0 - 5 - - - - - False - True - 0 - - - - - True - False - 3 - - - True - False - 6 - - - True - False - 0 - <b>Volumes</b> - True - - - False - False - 0 - - - - - True - True - True - True - Refresh volume list - - - - True - False - gtk-refresh - - - - - False - True - 1 - - - - - False - False - 0 - - - - - True - True - in - - - True - True - - - - - - - - - - - True - True - 1 - - - - - True - True - 1 - - - - - - - - - True - False - info - - - False - - - - - True - False - some error here - - - 1 - - - - - True - False - error - - - 1 - False - - - - - True - True - - - - - True - True - 0 - - - - - True - False - 3 - - - True - False - - - True - True - False - Add Pool - - - - True - False - gtk-add - - - - - False - False - 0 - - - - - True - True - False - Start Pool - - - - True - False - gtk-media-play - - - - - False - False - 1 - - - - - True - True - False - Stop Pool - - - - True - False - gtk-cancel - - - - - False - False - 2 - - - - - True - True - False - Delete Pool - - - - True - False - gtk-delete - - - - - False - False - 3 - - - - - True - True - 0 - - - - - True - False - 6 - - - _New Volume - True - True - False - image22 - True - - - - False - False - 0 - - - - - _Delete Volume - True - True - False - image1 - True - - - - False - False - 1 - - - - - gtk-apply - True - True - False - True - - - - False - False - 2 - - - - - False - True - 1 - - - - - False - True - 1 - + @@ -2084,9 +1544,9 @@ True False - 0 <b>Name</b> True + 0 2 @@ -2098,9 +1558,9 @@ True False + MAC: 0 0 - MAC: 1 @@ -2113,10 +1573,10 @@ True False - 0 - 0 insert mac True + 0 + 0 1 @@ -2130,8 +1590,8 @@ True False - 0 State: + 0 2 @@ -2183,8 +1643,8 @@ True False - 0 Start mode: + 0 3 @@ -2212,8 +1672,8 @@ False - 0 unknown startmode + 0 True @@ -2234,8 +1694,8 @@ True False - 0 In use by: + 0 4 @@ -2248,8 +1708,8 @@ True False - 0 foo, bar + 0 1 @@ -2283,8 +1743,8 @@ True False - 0 Mode: + 0 GTK_FILL @@ -2295,8 +1755,8 @@ True False - 0 label + 0 1 @@ -2308,8 +1768,8 @@ True False - 0 Address: + 0 1 @@ -2322,9 +1782,9 @@ True False - 0 label True + 0 1 @@ -2367,8 +1827,8 @@ True False - 0 Mode: + 0 GTK_FILL @@ -2379,8 +1839,8 @@ True False - 0 label + 0 1 @@ -2392,9 +1852,9 @@ True False + Address: 0 0 - Address: 1 @@ -2407,10 +1867,10 @@ True False - 0 - 0 label True + 0 + 0 1 @@ -2446,9 +1906,9 @@ True False - 0 <b>Slave Interfaces</b> True + 0 False diff --git a/ui/storagebrowse.ui b/ui/storagebrowse.ui index 7dca9112b..b12169809 100644 --- a/ui/storagebrowse.ui +++ b/ui/storagebrowse.ui @@ -2,19 +2,9 @@ - - True - False - gtk-new - - - True - False - gtk-open - False - 12 + 6 Choose Storage Volume 750 500 @@ -22,265 +12,11 @@ dialog - + True False - vertical - 12 - - True - False - 10 - - - 150 - True - True - never - in - - - True - True - - - - - - - - False - True - 0 - - - - - True - False - vertical - 6 - - - True - False - 6 - - - True - False - <b>Volumes</b> - True - - - False - True - 0 - - - - - True - True - True - - - - True - False - gtk-refresh - - - - - False - True - 1 - - - - - True - True - True - - - - True - False - gtk-delete - - - - - False - True - 2 - - - - - False - True - 0 - - - - - True - True - in - - - True - True - - - - - - - - - - - True - True - 1 - - - - - True - True - 1 - - - - - True - True - 0 - - - - - True - False - False - 12 - - - True - False - start - - - True - True - True - - - - True - False - 2 - 2 - - - True - False - _Browse Local - True - browse-local - - - - - - - False - False - 0 - - - - - True - True - 0 - - - - - True - False - 6 - start - - - _New Volume - True - True - True - image1 - True - - - - False - True - 0 - True - - - - - gtk-cancel - True - True - True - True - - - - False - True - 1 - True - - - - - Choose _Volume - True - True - True - image2 - True - - - - False - True - 2 - True - - - - - False - True - 1 - - - - - False - False - 1 - + diff --git a/ui/storagelist.ui b/ui/storagelist.ui new file mode 100644 index 000000000..a659565e9 --- /dev/null +++ b/ui/storagelist.ui @@ -0,0 +1,634 @@ + + + + + + True + False + gtk-apply + + + True + False + 3 + + + True + False + end + 3 + + + True + False + + + True + True + False + Add Pool + + + + True + False + gtk-add + + + + + False + False + 0 + + + + + True + True + False + Start Pool + + + + True + False + gtk-media-play + + + + + False + False + 1 + + + + + True + True + False + Stop Pool + + + + True + False + gtk-cancel + + + + + False + False + 2 + + + + + True + True + False + Delete Pool + + + + True + False + gtk-delete + + + + + False + False + 3 + + + + + True + True + 0 + + + + + True + False + 6 + + + True + True + True + Browse local filesystem + + + + True + False + 2 + 2 + + + True + False + _Browse Local + True + + + + + + + False + False + 0 + + + + + gtk-cancel + True + True + True + Cancel and close dialog + True + + + + True + True + 1 + + + + + Choose Volume + True + True + True + Choose the selected volume + image3 + + + + True + True + 2 + + + + + gtk-apply + True + True + False + Apply pool changes + True + + + + False + False + 3 + + + + + False + True + 1 + + + + + 0 + 1 + + + + + True + True + True + True + 3 + 200 + + + True + True + never + in + + + 134 + True + True + False + + + + + + + + False + True + + + + + True + True + False + + + True + False + 6 + + + True + False + 6 + + + True + False + 5 + 8 + + + True + False + Pool Type: + 0 + + + 0 + 2 + + + + + True + False + Pool Type + True + 0 + + + 1 + 2 + + + + + True + False + <b>Poolname:</b> + True + 0 + + + 0 + 0 + + + + + True + False + <span size="large">1234 GiB Free</span> / <i>6000 GiB In Use</i> + True + 0 + + + 1 + 0 + + + + + True + False + 3 + + + True + False + 0 + gtk-missing-image + + + False + True + 0 + + + + + True + False + Active + + + False + False + 1 + + + + + 1 + 4 + + + + + Some label + True + True + False + True + 0 + True + + + + 1 + 5 + + + + + True + False + label + True + 0 + + + 1 + 3 + + + + + True + True + + + + 1 + 1 + + + + + True + False + Name: + 0 + + + 0 + 1 + + + + + True + False + Location: + 0 + + + 0 + 3 + + + + + True + False + State: + 0 + + + 0 + 4 + + + + + True + False + A_utostart: + True + pool-autostart + 0 + + + 0 + 5 + + + + + False + True + 0 + + + + + True + False + 3 + + + True + False + 6 + + + True + False + <b>Volumes</b> + True + 0 + + + False + False + 0 + + + + + True + False + + + True + True + False + True + + + + True + False + gtk-add + + + + + False + False + 0 + + + + + True + True + True + True + Refresh volume list + + + + True + False + gtk-refresh + + + + + False + True + 1 + + + + + True + True + False + Delete volume + True + + + + True + False + gtk-delete + + + + + False + False + 2 + + + + + True + True + 1 + + + + + False + False + 0 + + + + + True + True + in + + + True + True + + + + + + + + + + + + True + True + 1 + + + + + True + True + 1 + + + + + + + + + True + False + info + + + False + + + + + True + False + some error here + + + 1 + + + + + True + False + error + + + 1 + False + + + + + True + True + + + + + 0 + 0 + + + + diff --git a/virtManager/addhardware.py b/virtManager/addhardware.py index d874cd484..51f7605e4 100644 --- a/virtManager/addhardware.py +++ b/virtManager/addhardware.py @@ -2025,9 +2025,8 @@ class vmmAddHardware(vmmGObjectUI): if self.storage_browser is None: self.storage_browser = vmmStorageBrowser(conn) - self.storage_browser.stable_defaults = self.vm.stable_defaults() - + self.storage_browser.set_stable_defaults(self.vm.stable_defaults()) self.storage_browser.set_finish_cb(set_storage_cb) self.storage_browser.set_browse_reason(reason) - self.storage_browser.show(self.topwin, conn) + self.storage_browser.show(self.topwin) diff --git a/virtManager/choosecd.py b/virtManager/choosecd.py index 7b1ee0404..d952492e5 100644 --- a/virtManager/choosecd.py +++ b/virtManager/choosecd.py @@ -150,7 +150,7 @@ class vmmChooseCD(vmmGObjectUI): self.storage_browser = vmmStorageBrowser(self.conn) self.storage_browser.set_finish_cb(self.set_storage_path) - self.storage_browser.stable_defaults = self.vm.stable_defaults() + self.storage_browser.set_stable_defaults(self.vm.stable_defaults()) if self.media_type == vmmMediaCombo.MEDIA_FLOPPY: self.storage_browser.set_browse_reason( @@ -158,4 +158,4 @@ class vmmChooseCD(vmmGObjectUI): else: self.storage_browser.set_browse_reason( self.config.CONFIG_DIR_ISO_MEDIA) - self.storage_browser.show(self.topwin, self.conn) + self.storage_browser.show(self.topwin) diff --git a/virtManager/clone.py b/virtManager/clone.py index 273433338..78f70d3e1 100644 --- a/virtManager/clone.py +++ b/virtManager/clone.py @@ -832,8 +832,11 @@ class vmmCloneVM(vmmGObjectUI): def callback(src_ignore, txt): self.widget("change-storage-new").set_text(txt) + if self.storage_browser and self.storage_browser.conn != self.conn: + self.storage_browser.cleanup() + self.storage_browser = None if self.storage_browser is None: self.storage_browser = vmmStorageBrowser(self.conn) self.storage_browser.set_finish_cb(callback) - self.storage_browser.show(self.topwin, self.conn) + self.storage_browser.show(self.topwin) diff --git a/virtManager/create.py b/virtManager/create.py index db72c4e81..5bbe50447 100644 --- a/virtManager/create.py +++ b/virtManager/create.py @@ -2087,12 +2087,14 @@ class vmmCreate(vmmGObjectUI): widget = self.widget(cbwidget) widget.set_text(text) + if self.storage_browser and self.storage_browser.conn != self.conn: + self.storage_browser.cleanup() + self.storage_browser = None if self.storage_browser is None: self.storage_browser = vmmStorageBrowser(self.conn) - self.storage_browser.stable_defaults = self._stable_defaults() - + self.storage_browser.set_stable_defaults(self._stable_defaults()) self.storage_browser.set_vm_name(self.get_config_name()) self.storage_browser.set_finish_cb(callback) self.storage_browser.set_browse_reason(reason) - self.storage_browser.show(self.topwin, self.conn) + self.storage_browser.show(self.topwin) diff --git a/virtManager/createvol.py b/virtManager/createvol.py index 1625a6795..e3dbd2f3d 100644 --- a/virtManager/createvol.py +++ b/virtManager/createvol.py @@ -337,6 +337,10 @@ class vmmCreateVolume(vmmGObjectUI): return self.err.val_err(info, details, modal=self.topwin.get_modal()) def _browse_file(self): + if self.storage_browser and self.storage_browser.conn != self.conn: + self.storage_browser.cleanup() + self.storage_browser = None + if self.storage_browser is None: def cb(src, text): ignore = src @@ -346,8 +350,7 @@ class vmmCreateVolume(vmmGObjectUI): self.storage_browser = vmmStorageBrowser(self.conn) self.storage_browser.set_finish_cb(cb) self.storage_browser.topwin.set_modal(self.topwin.get_modal()) - self.storage_browser.can_new_volume = False self.storage_browser.set_browse_reason( self.config.CONFIG_DIR_IMAGE) - self.storage_browser.show(self.topwin, self.conn) + self.storage_browser.show(self.topwin) diff --git a/virtManager/details.py b/virtManager/details.py index e90f6c461..0dd5e30d9 100644 --- a/virtManager/details.py +++ b/virtManager/details.py @@ -1589,7 +1589,7 @@ class vmmDetails(vmmGObjectUI): self.storage_browser.set_finish_cb(callback) self.storage_browser.set_browse_reason(reason) - self.storage_browser.show(self.topwin, self.conn) + self.storage_browser.show(self.topwin) def boot_kernel_toggled(self, src): self.widget("boot-kernel-box").set_sensitive(src.get_active()) diff --git a/virtManager/error.py b/virtManager/error.py index 42f0a5ac9..7dc53a7c6 100644 --- a/virtManager/error.py +++ b/virtManager/error.py @@ -54,19 +54,27 @@ class vmmErrorDialog(vmmGObject): self._parent = parent self._simple = None + # Allows the error owner to easily override default modality + self._modal_default = False + def _cleanup(self): pass + def set_modal_default(self, val): + self._modal_default = val def set_parent(self, parent): self._parent = parent def get_parent(self): return self._parent def show_err(self, summary, details=None, title="", - modal=False, debug=True, + modal=None, debug=True, dialog_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.CLOSE, text2=None): + if modal is None: + modal = self._modal_default + if details is None: details = summary tb = "".join(traceback.format_exc()).strip() @@ -93,6 +101,7 @@ class vmmErrorDialog(vmmGObject): details=details, title=title, modal=modal) + ################################### # Simple one shot message dialogs # ################################### diff --git a/virtManager/fsdetails.py b/virtManager/fsdetails.py index 51aac66d1..a2318a497 100644 --- a/virtManager/fsdetails.py +++ b/virtManager/fsdetails.py @@ -342,16 +342,18 @@ class vmmFSDetails(vmmGObjectUI): if path: textent.set_text(path) - conn = self.conn reason = (isdir and self.config.CONFIG_DIR_FS or self.config.CONFIG_DIR_IMAGE) + + if self.storage_browser and self.storage_browser.conn != self.conn: + self.storage_browser.cleanup() + self.storage_browser = None if self.storage_browser is None: - self.storage_browser = vmmStorageBrowser(conn) - - self.storage_browser.stable_defaults = self.vm.stable_defaults() + self.storage_browser = vmmStorageBrowser(self.conn) + self.storage_browser.set_stable_defaults(self.vm.stable_defaults()) self.storage_browser.set_finish_cb(set_storage_cb) self.storage_browser.set_browse_reason(reason) - self.storage_browser.show(self.topwin.get_ancestor(Gtk.Window), conn) + self.storage_browser.show(self.topwin.get_ancestor(Gtk.Window)) diff --git a/virtManager/host.py b/virtManager/host.py index ab7f307be..ba277e6a2 100644 --- a/virtManager/host.py +++ b/virtManager/host.py @@ -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("%s:" % - 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( - """%s Free / %s In Use""" % - (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(""" """) - 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%s" % (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 "%s%%" % int(per) diff --git a/virtManager/storagebrowse.py b/virtManager/storagebrowse.py index d4bd67284..671ffb9a2 100644 --- a/virtManager/storagebrowse.py +++ b/virtManager/storagebrowse.py @@ -20,13 +20,9 @@ import logging -from gi.repository import Gtk - -from . import host -from .asyncjob import vmmAsyncJob -from .createvol import vmmCreateVolume -from .baseclass import vmmGObjectUI from . import uiutil +from .baseclass import vmmGObjectUI +from .storagelist import vmmStorageList class vmmStorageBrowser(vmmGObjectUI): @@ -34,43 +30,33 @@ class vmmStorageBrowser(vmmGObjectUI): vmmGObjectUI.__init__(self, "storagebrowse.ui", "vmm-storage-browse") self.conn = conn - self.conn_signal_ids = [] - self.can_new_volume = True self._first_run = False self._finish_cb = None - # Add Volume wizard - self.addvol = None + # Passed to browse_local + self._browse_reason = None - # Name of VM we are choosing storage for, can be used to recommend - # volume name if creating - self.vm_name = None + # Whether we should abide stable defaults + self._stable_defaults = False - # Arguments to pass to util.browse_local for local storage - self.browse_reason = None - self.local_args = {} - - self.stable_defaults = False - self._in_refresh = False + self.storagelist = vmmStorageList(self.conn, self.builder, self.topwin, + self._vol_sensitive_cb) + self._init_ui() self.builder.connect_signals({ "on_vmm_storage_browse_delete_event" : self.close, - "on_browse_cancel_clicked" : self.close, - "on_browse_local_clicked" : self.browse_local, - "on_new_volume_clicked" : self.new_volume, - "on_choose_volume_clicked" : self.finish, - "on_pool_refresh_clicked": self.pool_refresh, - "on_vol_delete_clicked": self.delete_vol, - "on_vol_list_row_activated" : self.finish, - "on_vol_list_changed": self.vol_selected, }) self.bind_escape_key_close() - self.set_initial_state() - def show(self, parent, conn): + def show(self, parent): logging.debug("Showing storage browser") - self.reset_state(conn) + if not self._first_run: + self._first_run = True + pool = self.conn.get_default_pool() + uiutil.set_row_selection(self.storagelist.widget("pool-list"), + pool and pool.get_connkey() or None) + self.topwin.set_transient_for(parent) self.topwin.present() self.conn.schedule_priority_tick(pollpool=True) @@ -78,319 +64,112 @@ class vmmStorageBrowser(vmmGObjectUI): def close(self, ignore1=None, ignore2=None): logging.debug("Closing storage browser") self.topwin.hide() - if self.addvol: - self.addvol.close() + self.storagelist.close() return 1 def _cleanup(self): - self.remove_conn() self.conn = None - if self.addvol: - self.addvol.cleanup() - self.addvol = None + self.storagelist.cleanup() + self.storagelist = None - def remove_conn(self): - if not self.conn: - return - for i in self.conn_signal_ids: - self.conn.disconnect(i) + ############## + # Public API # + ############## def set_finish_cb(self, callback): self._finish_cb = callback - def set_browse_reason(self, reason): - self.browse_reason = reason - - def set_local_arg(self, arg, val): - self.local_args[arg] = val - + self._browse_reason = reason def set_vm_name(self, name): - self.vm_name = name + self.storagelist.set_name_hint(name) + def set_stable_defaults(self, val): + self._stable_defaults = val - def set_initial_state(self): - pool_list = self.widget("pool-list") - host.init_pool_list(pool_list, self.pool_selected) + def _init_ui(self): + self.storagelist.connect("browse-clicked", self._browse_clicked) + self.storagelist.connect("volume-chosen", self._volume_chosen) + self.storagelist.connect("cancel-clicked", self.close) - # (Key, Name, Cap, Format, Used By, sensitive) - vol_list = self.widget("vol-list") - volListModel = Gtk.ListStore(str, str, str, str, str, bool) - vol_list.set_model(volListModel) - - volCol = Gtk.TreeViewColumn(_("Name")) - vol_txt1 = Gtk.CellRendererText() - volCol.pack_start(vol_txt1, True) - volCol.add_attribute(vol_txt1, 'text', 1) - volCol.add_attribute(vol_txt1, 'sensitive', 5) - volCol.set_sort_column_id(1) - 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.add_attribute(vol_txt2, 'sensitive', 5) - volSizeCol.set_sort_column_id(2) - vol_list.append_column(volSizeCol) - - volPathCol = Gtk.TreeViewColumn(_("Format")) - vol_txt4 = Gtk.CellRendererText() - volPathCol.pack_start(vol_txt4, False) - volPathCol.add_attribute(vol_txt4, 'text', 3) - volPathCol.add_attribute(vol_txt4, 'sensitive', 5) - volPathCol.set_sort_column_id(3) - vol_list.append_column(volPathCol) - - volUseCol = Gtk.TreeViewColumn(_("Used By")) - vol_txt5 = Gtk.CellRendererText() - volUseCol.pack_start(vol_txt5, False) - volUseCol.add_attribute(vol_txt5, 'text', 4) - volUseCol.add_attribute(vol_txt5, 'sensitive', 5) - volUseCol.set_sort_column_id(4) - vol_list.append_column(volUseCol) - - volListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING) - - def reset_state(self, conn): - self.remove_conn() - self.conn = conn - - self.repopulate_storage_pools() - - ids = [] - ids.append(self.conn.connect("pool-added", - self.repopulate_storage_pools)) - ids.append(self.conn.connect("pool-removed", - self.repopulate_storage_pools)) - ids.append(self.conn.connect("pool-started", - self.refresh_storage_pool)) - ids.append(self.conn.connect("pool-stopped", - self.refresh_storage_pool)) - self.conn_signal_ids = ids - - # FIXME: Need a connection specific "vol-added" function? - # Won't be able to pick that change up from outside? - - if not self._first_run: - self._first_run = True - pool = self.conn.get_default_pool() - uiutil.set_row_selection(self.widget("pool-list"), - pool and pool.get_connkey() or None) - - # Manually trigger vol_selected, so buttons are in the correct state - self.vol_selected() - self.pool_selected() + self.widget("storage-align").add(self.storagelist.top_box) + self.err.set_modal_default(True) + self.storagelist.err.set_modal_default(True) tooltip = "" is_remote = self.conn.is_remote() - self.widget("browse-local").set_sensitive(not is_remote) + self.storagelist.widget("browse-local").set_sensitive(not is_remote) if is_remote: tooltip = _("Cannot use local storage on remote connection.") - self.widget("browse-local").set_tooltip_text(tooltip) + self.storagelist.widget("browse-local").set_tooltip_text(tooltip) - # Set data based on browse type - self.local_args["dialog_type"] = None - self.local_args["browse_reason"] = self.browse_reason + uiutil.set_grid_row_visible( + self.storagelist.widget("pool-autostart"), False) + uiutil.set_grid_row_visible( + self.storagelist.widget("pool-name-entry"), False) + uiutil.set_grid_row_visible( + self.storagelist.widget("pool-type"), False) + uiutil.set_grid_row_visible( + self.storagelist.widget("pool-state-box"), False) + self.storagelist.widget("browse-local").set_visible(True) + self.storagelist.widget("browse-cancel").set_visible(True) + self.storagelist.widget("choose-volume").set_visible(True) + self.storagelist.widget("choose-volume").set_sensitive(False) + self.storagelist.widget("pool-apply").set_visible(False) - data = self.config.browse_reason_data.get(self.browse_reason) + data = self.config.browse_reason_data.get(self._browse_reason) + allow_create = True if data: self.topwin.set_title(data["storage_title"]) - self.local_args["dialog_name"] = data["local_title"] - self.local_args["dialog_type"] = data.get("dialog_type") - self.local_args["choose_button"] = data.get("choose_button") + allow_create = data["enable_create"] - self.widget("new-volume").set_visible( - self.can_new_volume and self.allow_create()) + self.storagelist.widget("vol-add").set_sensitive(allow_create) - # Convenience helpers - def allow_create(self): - data = self.config.browse_reason_data.get(self.browse_reason) - if not data: - return True + ############# + # Listeners # + ############# - return data["enable_create"] - - def current_pool(self): - row = uiutil.get_list_selection(self.widget("pool-list"), None) - if not row: - return - - connkey = row[0] - try: - return self.conn.get_pool(connkey) - except KeyError: - return None - - def current_vol_row(self): - if not self.current_pool(): - return - return uiutil.get_list_selection(self.widget("vol-list"), None) - - def current_vol(self): - pool = self.current_pool() - row = self.current_vol_row() - if not pool or not row: - return - return pool.get_volume(row[0]) - - def refresh_storage_pool(self, src, connkey): + def _browse_clicked(self, src): ignore = src + return self._browse_local() - pool_list = self.widget("pool-list") - host.refresh_pool_in_list(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 repopulate_storage_pools(self, src=None, connkey=None): + def _volume_chosen(self, src, volume): ignore = src - ignore = connkey - pool_list = self.widget("pool-list") - host.populate_storage_pools(pool_list, self.conn, self.current_pool()) + self._finish(volume.get_target_path()) - def delete_vol(self, src_ignore): - vol = self.current_vol() - if vol is None: - return + def _vol_sensitive_cb(self, fmt): + if ((self._browse_reason == self.config.CONFIG_DIR_FS) + and fmt != 'dir'): + return False + elif self._stable_defaults: + if fmt == "vmdk": + return False + return True - 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.populate_storage_volumes() - self.idle_add(idlecb) + #################### + # Internal helpers # + #################### - logging.debug("Deleting volume '%s'", vol.get_name()) - vmmAsyncJob.simple_async_noshow(cb, [], self, - _("Error refreshing volume '%s'") % vol.get_name()) + def _browse_local(self): + dialog_type = None + dialog_name = None + choose_button = None - def pool_refresh(self, src_ignore): - if self._in_refresh: - logging.debug("Already refreshing the pool, skipping") - return + data = self.config.browse_reason_data.get(self._browse_reason) + if data: + dialog_name = data["local_title"] or None + dialog_type = data.get("dialog_type") + choose_button = data.get("choose_button") - 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()) - - # Listeners - def pool_selected(self, src_ignore=None): - pool = self.current_pool() - - can_new_vol = False - tt = "" - if pool: - pool.tick() - can_new_vol = (pool.is_active() and - pool.supports_volume_creation()) - if not can_new_vol: - tt = _("Pool does not support volume creation") - - self.widget("new-volume").set_sensitive(can_new_vol) - self.widget("new-volume").set_tooltip_text(tt) - - self.populate_storage_volumes() - - def vol_selected(self, ignore=None): - vol = self.current_vol_row() - canchoose = bool(vol and vol[5]) - self.widget("choose-volume").set_sensitive(canchoose) - self.widget("vol-delete").set_sensitive(canchoose) - - def refresh_current_pool(self, createvol=None): - cp = self.current_pool() - if cp is None: - return - cp.refresh() - - self.refresh_storage_pool(None, cp.get_connkey()) - name = createvol and createvol.vol.name or None - - vol_list = self.widget("vol-list") - def select_volume(model, path, it, volume_name): - if model.get(it, 0)[0] == volume_name: - uiutil.set_list_selection(vol_list, path) - - vol_list.get_model().foreach(select_volume, name) - - def new_volume(self, src_ignore): - pool = self.current_pool() - if pool is None: - return - - 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.set_modal(True) - self.addvol.set_name_hint(self.vm_name) - self.addvol.show(self.topwin) - except Exception, e: - self.show_err(_("Error launching volume wizard: %s") % str(e)) - - def browse_local(self, src_ignore): - if not self.local_args.get("dialog_name"): - self.local_args["dialog_name"] = None - - filename = self.err.browse_local( - self.conn, **self.local_args) + filename = self.err.browse_local(self.conn, + dialog_type=dialog_type, browse_reason=self._browse_reason, + dialog_name=dialog_name, choose_button=choose_button) if filename: - self._do_finish(path=filename) + self._finish(filename) - def finish(self, ignore=None, ignore1=None, ignore2=None): - self._do_finish() - - def _do_finish(self, path=None): - if not path: - path = self.current_vol().get_target_path() + def _finish(self, path): if self._finish_cb: self._finish_cb(self, path) self.close() - - - # Do stuff! - def populate_storage_volumes(self): - list_widget = self.widget("vol-list") - pool = self.current_pool() - - def sensitive_cb(fmt): - if ((self.browse_reason == self.config.CONFIG_DIR_FS) - and fmt != 'dir'): - return False - elif self.stable_defaults: - if fmt == "vmdk": - return False - return True - - host.populate_storage_volumes(list_widget, pool, sensitive_cb) - - def show_err(self, info, details=None): - self.err.show_err(info, - details=details, - modal=True) diff --git a/virtManager/storagelist.py b/virtManager/storagelist.py new file mode 100644 index 000000000..5cffb7fe4 --- /dev/null +++ b/virtManager/storagelist.py @@ -0,0 +1,712 @@ +# +# Copyright (C) 2015 Red Hat, Inc. +# +# 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 logging + +from gi.repository import Gdk +from gi.repository import Gtk +from gi.repository import GObject + +from virtinst import StoragePool +from virtinst import VirtualDisk + +from . import uiutil +from .asyncjob import vmmAsyncJob +from .baseclass import vmmGObjectUI +from .createpool import vmmCreatePool +from .createvol import vmmCreateVolume + + +EDIT_POOL_IDS = ( +EDIT_POOL_NAME, +EDIT_POOL_AUTOSTART, +) = range(2) + +VOL_NUM_COLUMNS = 6 +(VOL_COLUMN_KEY, + VOL_COLUMN_NAME, + VOL_COLUMN_CAPACITY, + VOL_COLUMN_FORMAT, + VOL_COLUMN_INUSEBY, + VOL_COLUMN_SENSITIVE) = range(VOL_NUM_COLUMNS) + +POOL_NUM_COLUMNS = 4 +(POOL_COLUMN_CONNKEY, + POOL_COLUMN_LABEL, + POOL_COLUMN_ISACTIVE, + POOL_COLUMN_PERCENT) = range(POOL_NUM_COLUMNS) + +ICON_RUNNING = "state_running" +ICON_SHUTOFF = "state_shutoff" + + +def _get_pool_size_percent(pool): + 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 "%s%%" % int(per) + + +class vmmStorageList(vmmGObjectUI): + __gsignals__ = { + "browse-clicked": (GObject.SignalFlags.RUN_FIRST, None, []), + "volume-chosen": (GObject.SignalFlags.RUN_FIRST, None, [object]), + "cancel-clicked": (GObject.SignalFlags.RUN_FIRST, None, []), + } + + def __init__(self, conn, builder, topwin, vol_sensitive_cb=None): + vmmGObjectUI.__init__(self, "storagelist.ui", + None, builder=builder, topwin=topwin) + self.conn = conn + + # Callback function for setting volume row sensitivity. Used + # by storage browser to disallow selecting certain volumes + self._vol_sensitive_cb = vol_sensitive_cb + + # Name hint passed to addvol. Set by storagebrowser + self._name_hint = None + + self._active_edits = [] + self._addpool = None + self._addvol = None + self._in_refresh = False + self._volmenu = None + self.top_box = self.widget("storage-grid") + + self.builder.connect_signals({ + "on_pool_add_clicked" : self._pool_add, + "on_pool_stop_clicked": self._pool_stop, + "on_pool_start_clicked": self._pool_start, + "on_pool_delete_clicked": self._pool_delete, + "on_pool_refresh_clicked": self._pool_refresh, + "on_pool_apply_clicked": (lambda *x: self._pool_apply()), + + "on_vol_delete_clicked": self._vol_delete, + "on_vol_list_button_press_event": self._vol_popup_menu, + "on_vol_list_changed": self._vol_selected, + "on_vol_add_clicked" : self._vol_add, + + "on_browse_cancel_clicked" : self._cancel_clicked, + "on_browse_local_clicked" : self._browse_local_clicked, + "on_choose_volume_clicked" : self._choose_volume_clicked, + "on_vol_list_row_activated" : self._vol_list_row_activated, + + "on_pool_name_changed": (lambda *x: + self._enable_pool_apply(x, EDIT_POOL_NAME)), + "on_pool_autostart_toggled": self._pool_autostart_changed, + }) + + self._init_ui() + + + def _cleanup(self): + try: + self.conn.disconnect_by_func(self._conn_pools_changed) + self.conn.disconnect_by_func(self._conn_pools_changed) + self.conn.disconnect_by_func(self._reselect_pool) + self.conn.disconnect_by_func(self._reselect_pool) + self.conn.disconnect_by_func(self._conn_state_changed) + except: + pass + + self.conn = None + + if self._addpool: + self._addpool.cleanup() + self._addpool = None + + if self._addvol: + self._addvol.cleanup() + self._addvol = None + + self._volmenu.destroy() + self._volmenu = None + + def close(self, ignore1=None, ignore2=None): + if self._addvol: + self._addvol.close() + if self._addpool: + self._addpool.close() + if self._volmenu: + self._volmenu.hide() + + + ########################## + # Initialization methods # + ########################## + + def _init_ui(self): + self.widget("storage-pages").set_show_tabs(False) + + # These are enabled in storagebrowser.py + self.widget("browse-local").set_visible(False) + self.widget("browse-cancel").set_visible(False) + self.widget("choose-volume").set_visible(False) + + # Volume list popup menu + 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._vol_copy_path) + self._volmenu.add(volCopyPath) + + # Volume list + # [key, name, capacity, format, in use by string, sensitive] + volListModel = Gtk.ListStore(str, str, str, str, str, bool) + 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', VOL_COLUMN_NAME) + volCol.add_attribute(vol_txt1, 'sensitive', VOL_COLUMN_SENSITIVE) + volCol.set_sort_column_id(VOL_COLUMN_NAME) + 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', VOL_COLUMN_CAPACITY) + volSizeCol.add_attribute(vol_txt2, 'sensitive', VOL_COLUMN_SENSITIVE) + volSizeCol.set_sort_column_id(VOL_COLUMN_CAPACITY) + 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', VOL_COLUMN_FORMAT) + volFormatCol.add_attribute(vol_txt3, 'sensitive', VOL_COLUMN_SENSITIVE) + volFormatCol.set_sort_column_id(VOL_COLUMN_FORMAT) + 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', VOL_COLUMN_INUSEBY) + volUseCol.add_attribute(vol_txt4, 'sensitive', VOL_COLUMN_SENSITIVE) + volUseCol.set_sort_column_id(VOL_COLUMN_INUSEBY) + self.widget("vol-list").append_column(volUseCol) + + volListModel.set_sort_column_id(VOL_COLUMN_NAME, + Gtk.SortType.ASCENDING) + + # Init pool list + # [connkey, label, pool.is_active(), percent string] + pool_list = self.widget("pool-list") + poolListModel = Gtk.ListStore(str, str, bool, str) + pool_list.set_model(poolListModel) + + 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', POOL_COLUMN_LABEL) + poolCol.add_attribute(pool_txt, 'sensitive', POOL_COLUMN_ISACTIVE) + poolCol.add_attribute(pool_per, 'markup', POOL_COLUMN_PERCENT) + pool_list.append_column(poolCol) + poolListModel.set_sort_column_id(POOL_COLUMN_LABEL, + Gtk.SortType.ASCENDING) + + pool_list.get_selection().connect("changed", self._pool_selected) + pool_list.get_selection().set_select_function( + (lambda *x: self._confirm_changes()), None) + + # Populate list and connect conn signals + self._populate_pools() + self.conn.connect("pool-added", self._conn_pools_changed) + self.conn.connect("pool-removed", self._conn_pools_changed) + self.conn.connect("pool-started", self._reselect_pool) + self.conn.connect("pool-stopped", self._reselect_pool) + self.conn.connect("state-changed", self._conn_state_changed) + + self._conn_state_changed() + + + ############### + # Public APIs # + ############### + + def refresh_page(self): + self._populate_vols() + self.conn.schedule_priority_tick(pollpool=True) + + def set_name_hint(self, val): + self._name_hint = val + + + #################### + # Internal helpers # + #################### + + 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 _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 _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 _reselect_pool(self, src, connkey): + ignore = src + + for row in self.widget("pool-list").get_model(): + if row[POOL_COLUMN_CONNKEY] != connkey: + continue + + # Update active sensitivity and percent available for passed key + pool = self.conn.get_pool(connkey) + row[POOL_COLUMN_ISACTIVE] = pool.is_active() + row[POOL_COLUMN_PERCENT] = _get_pool_size_percent(pool) + break + + 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(""" """) + self.widget("pool-type").set_text("") + self.widget("pool-location").set_text("") + self.widget("pool-state-icon").set_from_icon_name( + 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 _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("%s:" % 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( + """%s Free / %s In Use""" % + (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 ICON_RUNNING) or 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._populate_vols() + + 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 _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 _populate_pools(self): + pool_list = self.widget("pool-list") + curpool = self._current_pool() + + # Prevent events while the model is modified + model = pool_list.get_model() + pool_list.set_model(None) + pool_list.get_selection().unselect_all() + model.clear() + + for pool in self.conn.list_pools(): + name = pool.get_name() + typ = StoragePool.get_pool_type_desc(pool.get_type()) + label = "%s\n%s" % (name, typ) + + row = [None] * POOL_NUM_COLUMNS + row[POOL_COLUMN_CONNKEY] = pool.get_connkey() + row[POOL_COLUMN_LABEL] = label + row[POOL_COLUMN_ISACTIVE] = pool.is_active() + row[POOL_COLUMN_PERCENT] = _get_pool_size_percent(pool) + + model.append(row) + pool_list.set_model(model) + + uiutil.set_row_selection(pool_list, + curpool and curpool.get_connkey() or None) + + def _populate_vols(self): + list_widget = self.widget("vol-list") + pool = self._current_pool() + 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.") + + sensitive = True + if self._vol_sensitive_cb: + sensitive = self._vol_sensitive_cb(fmt) + + row = [None] * VOL_NUM_COLUMNS + row[VOL_COLUMN_KEY] = key + row[VOL_COLUMN_NAME] = name + row[VOL_COLUMN_CAPACITY] = cap + row[VOL_COLUMN_FORMAT] = fmt + row[VOL_COLUMN_INUSEBY] = namestr + row[VOL_COLUMN_SENSITIVE] = sensitive + model.append(row) + + def _confirm_changes(self): + if not self._active_edits: + return True + + if self.err.chkbox_helper( + self.config.get_confirm_unapplied, + self.config.set_confirm_unapplied, + text1=(_("There are unapplied changes. " + "Would you like to apply them now?")), + chktext=_("Don't warn me again."), + default=False): + + if all([edit in EDIT_POOL_IDS for edit in self._active_edits]): + self._pool_apply() + + self._active_edits = [] + return True + + + ############# + # Listeners # + ############# + + def _conn_state_changed(self, ignore=None): + conn_active = self.conn.is_active() + self.widget("pool-add").set_sensitive(conn_active and + self.conn.is_storage_capable()) + + if not conn_active: + self._set_storage_error_page(_("Connection not active.")) + self._populate_pools() + return + + if not self.conn.is_storage_capable(): + self._set_storage_error_page( + _("Libvirt connection does not support storage management.")) + + def _cancel_clicked(self, src): + ignore = src + self.emit("cancel-clicked") + + def _browse_local_clicked(self, src): + ignore = src + self.emit("browse-clicked") + + def _choose_volume_clicked(self, src): + ignore = src + self.emit("volume-chosen", self._current_vol()) + + def _vol_list_row_activated(self, src, treeiter, viewcol): + ignore = src + ignore = treeiter + ignore = viewcol + self.emit("volume-chosen", self._current_vol()) + + 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 _reselect_current_pool(self, ignore1=None): + cp = self._current_pool() + if cp is None: + return + + cp.refresh() + self._reselect_pool(None, cp.get_connkey()) + + def _conn_pools_changed(self, src, connkey): + ignore = src + ignore = connkey + self._populate_pools() + + def _pool_autostart_changed(self, src): + ignore = src + 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 _vol_selected(self, src): + model, treeiter = src.get_selected() + self.widget("vol-delete").set_sensitive(bool(treeiter)) + + can_choose = bool(treeiter and model[treeiter][VOL_COLUMN_SENSITIVE]) + self.widget("choose-volume").set_sensitive(can_choose) + + def _vol_popup_menu(self, widget_ignore, event): + if event.button != 3: + return + + self._volmenu.popup(None, None, None, None, 0, event.time) + + + ######################### + # Pool action listeners # + ######################### + + def _pool_stop(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 _pool_start(self, src): + ignore = src + 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 _pool_add(self, src): + ignore = src + 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 _pool_delete(self, src): + ignore = src + 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 = src + 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._reselect_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 _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._populate_pools) + except Exception, e: + self.err.show_err(_("Error changing pool settings: %s") % str(e)) + return + + self._disable_pool_apply() + + + ########################### + # Volume action listeners # + ########################### + + def _vol_copy_path(self, src): + ignore = src + 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 _vol_add(self, src): + ignore = src + 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._reselect_current_pool) + else: + self._addvol.set_parent_pool(self.conn, pool) + self._addvol.set_modal(self.topwin.get_modal()) + self._addvol.set_name_hint(self._name_hint) + self._addvol.show(self.topwin) + except Exception, e: + self.err.show_err(_("Error launching volume wizard: %s") % str(e)) + + def _vol_delete(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._reselect_current_pool() + self._populate_vols() + 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())