fsdetails: Rework XML building logic

Make it work more like gfxdetails. The problem with the current
approach is that it requires effectively rebuilding the whole device
to match the original device when we want to edit a single field,
which is error prone.

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2020-11-13 17:04:50 -05:00
parent fca338d2d1
commit 488f153655
6 changed files with 191 additions and 156 deletions

View File

@ -507,8 +507,8 @@ def testAddLXCFilesystem(app):
# Add File+nbd share # Add File+nbd share
tab = _select_hw(addhw, "Filesystem", "filesystem-tab") tab = _select_hw(addhw, "Filesystem", "filesystem-tab")
tab.combo_select("Type:", "File") tab.combo_select("Type:", "file")
tab.combo_select("Driver:", "Nbd") tab.combo_select("Driver:", "nbd")
tab.combo_select("Format:", "qcow2") tab.combo_select("Format:", "qcow2")
source = tab.find("Source path:", "text") source = tab.find("Source path:", "text")
@ -539,7 +539,7 @@ def testAddLXCFilesystem(app):
# Add RAM type # Add RAM type
_open_addhw(app, details) _open_addhw(app, details)
tab = _select_hw(addhw, "Filesystem", "filesystem-tab") tab = _select_hw(addhw, "Filesystem", "filesystem-tab")
tab.combo_select("Type:", "Ram") tab.combo_select("Type:", "ram")
tab.find("Usage:", "spin button").set_text("12345") tab.find("Usage:", "spin button").set_text("12345")
tab.find("Target path:", "text").set_text("/mem") tab.find("Target path:", "text").set_text("/mem")
_finish(addhw, check=details) _finish(addhw, check=details)

View File

@ -248,15 +248,21 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkComboBox" id="fs-mode-combo"> <object class="GtkComboBox" id="fs-type-combo">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<signal name="changed" handler="on_fs_mode_combo_changed" swapped="no"/> <property name="has-entry">True</property>
<signal name="changed" handler="on_fs_type_combo_changed" swapped="no"/>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can-focus">False</property>
</object>
</child>
</object> </object>
<packing> <packing>
<property name="left-attach">1</property> <property name="left-attach">1</property>
<property name="top-attach">3</property> <property name="top-attach">0</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -264,7 +270,13 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="has-entry">True</property>
<signal name="changed" handler="on_fs_driver_combo_changed" swapped="no"/> <signal name="changed" handler="on_fs_driver_combo_changed" swapped="no"/>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can-focus">False</property>
</object>
</child>
</object> </object>
<packing> <packing>
<property name="left-attach">1</property> <property name="left-attach">1</property>
@ -272,15 +284,21 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkComboBox" id="fs-type-combo"> <object class="GtkComboBox" id="fs-mode-combo">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<signal name="changed" handler="on_fs_type_combo_changed" swapped="no"/> <property name="has-entry">True</property>
<signal name="changed" handler="on_fs_mode_combo_changed" swapped="no"/>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can-focus">False</property>
</object>
</child>
</object> </object>
<packing> <packing>
<property name="left-attach">1</property> <property name="left-attach">1</property>
<property name="top-attach">0</property> <property name="top-attach">3</property>
</packing> </packing>
</child> </child>
</object> </object>

View File

@ -1538,7 +1538,7 @@ class vmmAddHardware(vmmGObjectUI):
return dev return dev
def _build_filesystem(self): def _build_filesystem(self):
return self._fsdetails.build_xmlobj() return self._fsdetails.build_device()
def _build_smartcard(self): def _build_smartcard(self):
mode = uiutil.get_list_selection(self.widget("smartcard-mode")) mode = uiutil.get_list_selection(self.widget("smartcard-mode"))

View File

@ -1645,7 +1645,7 @@ class vmmDetails(vmmGObjectUI):
kwargs = {} kwargs = {}
if self._edited(EDIT_FS): if self._edited(EDIT_FS):
kwargs["newdev"] = self.fsDetails.build_xmlobj() kwargs["newdev"] = self.fsDetails.update_device(devobj)
return vmmAddHardware.change_config_helper(self.vm.define_filesystem, return vmmAddHardware.change_config_helper(self.vm.define_filesystem,
kwargs, self.vm, self.err, kwargs, self.vm, self.err,
@ -2269,7 +2269,6 @@ class vmmDetails(vmmGObjectUI):
def _refresh_filesystem_page(self, dev): def _refresh_filesystem_page(self, dev):
self.fsDetails.set_dev(dev) self.fsDetails.set_dev(dev)
self.fsDetails.update_fs_rows()
def _refresh_boot_page(self): def _refresh_boot_page(self):
# Refresh autostart # Refresh autostart

View File

@ -14,6 +14,19 @@ from ..baseclass import vmmGObjectUI
from ..storagebrowse import vmmStorageBrowser from ..storagebrowse import vmmStorageBrowser
_EDIT_FS_ENUM = range(1, 9)
(
_EDIT_FS_TYPE,
_EDIT_FS_DRIVER,
_EDIT_FS_SOURCE,
_EDIT_FS_RAM_SOURCE,
_EDIT_FS_MODE,
_EDIT_FS_READONLY,
_EDIT_FS_TARGET,
_EDIT_FS_FORMAT,
) = _EDIT_FS_ENUM
class vmmFSDetails(vmmGObjectUI): class vmmFSDetails(vmmGObjectUI):
__gsignals__ = { __gsignals__ = {
"changed": (vmmGObjectUI.RUN_FIRST, None, []) "changed": (vmmGObjectUI.RUN_FIRST, None, [])
@ -26,54 +39,54 @@ class vmmFSDetails(vmmGObjectUI):
self.vm = vm self.vm = vm
self.conn = vm.conn self.conn = vm.conn
self._dev = None self._storage_browser = None
self.storage_browser = None self._active_edits = []
def _e(edittype):
def signal_cb(*args):
self._change_cb(edittype)
return signal_cb
self.builder.connect_signals({ self.builder.connect_signals({
"on_fs_type_combo_changed": self.change_field, "on_fs_source_browse_clicked": self._browse_fs_source_cb,
"on_fs_driver_combo_changed": self.change_field, "on_fs_type_combo_changed": _e(_EDIT_FS_TYPE),
"on_fs_source_browse_clicked": self.browse_fs_source, "on_fs_driver_combo_changed": _e(_EDIT_FS_DRIVER),
"on_fs_mode_combo_changed": self.notify_change, "on_fs_mode_combo_changed": _e(_EDIT_FS_MODE),
"on_fs_readonly_toggled": self.notify_change, "on_fs_readonly_toggled": _e(_EDIT_FS_READONLY),
"on_fs_format_combo_changed": self.notify_change, "on_fs_format_combo_changed": _e(_EDIT_FS_FORMAT),
"on_fs_source_changed": self.notify_change, "on_fs_source_changed": _e(_EDIT_FS_SOURCE),
"on_fs_ram_source_changed": self.notify_change, "on_fs_ram_source_changed": _e(_EDIT_FS_RAM_SOURCE),
"on_fs_target_changed": self.notify_change, "on_fs_target_changed": _e(_EDIT_FS_TARGET),
}) })
self.set_initial_state() self._init_ui()
self.top_box = self.widget("vmm-fs-details") self.top_box = self.widget("vmm-fs-details")
def _cleanup(self): def _cleanup(self):
self.vm = None self.vm = None
self.conn = None self.conn = None
self._dev = None
if self.storage_browser: if self._storage_browser:
self.storage_browser.cleanup() self._storage_browser.cleanup()
self.storage_browser = None self._storage_browser = None
########################## ##########################
# Initialization methods # # Initialization methods #
########################## ##########################
def set_initial_state(self): def _init_ui(self):
def simple_store_set(comboname, values, sort=True, capitalize=True): def simple_store_set(comboname, values):
combo = self.widget(comboname) combo = self.widget(comboname)
# [XML value, label] # [XML value, label]
model = Gtk.ListStore(str, str) model = Gtk.ListStore(str, str)
combo.set_model(model) combo.set_model(model)
uiutil.init_combo_text_column(combo, 1) uiutil.init_combo_text_column(combo, 1)
if sort:
model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
for xmlval in values: for xmlval in values:
label = xmlval label = xmlval
if xmlval is None: if xmlval is None:
label = "Default" label = _("Hypervisor default")
if capitalize:
label = label.capitalize()
model.append([xmlval, label]) model.append([xmlval, label])
# Filesystem widgets # Filesystem widgets
@ -82,24 +95,72 @@ class vmmFSDetails(vmmGObjectUI):
[DeviceFilesystem.TYPE_MOUNT, [DeviceFilesystem.TYPE_MOUNT,
DeviceFilesystem.TYPE_FILE, DeviceFilesystem.TYPE_FILE,
DeviceFilesystem.TYPE_BLOCK, DeviceFilesystem.TYPE_BLOCK,
DeviceFilesystem.TYPE_RAM], sort=False) DeviceFilesystem.TYPE_RAM])
else: else:
simple_store_set("fs-type-combo", [DeviceFilesystem.TYPE_MOUNT]) simple_store_set("fs-type-combo", [DeviceFilesystem.TYPE_MOUNT])
simple_store_set("fs-mode-combo", DeviceFilesystem.MODES + [None]) simple_store_set("fs-mode-combo",
[DeviceFilesystem.MODE_MAPPED,
DeviceFilesystem.MODE_SQUASH,
None])
drivers = [] simple_store_set("fs-driver-combo",
if self.conn.is_lxc() or self.conn.is_test(): [DeviceFilesystem.DRIVER_LOOP,
drivers += [DeviceFilesystem.DRIVER_LOOP, DeviceFilesystem.DRIVER_NBD,
DeviceFilesystem.DRIVER_NBD] None])
simple_store_set("fs-driver-combo", drivers + [None])
simple_store_set("fs-format-combo", ["raw", "qcow2"], capitalize=False) simple_store_set("fs-format-combo", ["raw", "qcow2"])
self.widget("fs-readonly").set_visible( self.widget("fs-readonly").set_visible(
self.conn.is_qemu() or self.conn.is_qemu() or
self.conn.is_test() or self.conn.is_test() or
self.conn.is_lxc()) self.conn.is_lxc())
##############
# UI syncing #
##############
def _sync_ui(self):
fstype = uiutil.get_list_selection(self.widget("fs-type-combo"))
fsdriver = uiutil.get_list_selection(self.widget("fs-driver-combo"))
ismount = bool(fstype == DeviceFilesystem.TYPE_MOUNT)
show_mode = bool(ismount)
uiutil.set_grid_row_visible(self.widget("fs-mode-combo"), show_mode)
show_ram_source = fstype == DeviceFilesystem.TYPE_RAM
uiutil.set_grid_row_visible(
self.widget("fs-ram-source-box"), show_ram_source)
uiutil.set_grid_row_visible(
self.widget("fs-source-box"), not show_ram_source)
show_format = bool(
fsdriver == DeviceFilesystem.DRIVER_NBD)
uiutil.set_grid_row_visible(
self.widget("fs-format-combo"), show_format)
show_driver_combo = fstype == DeviceFilesystem.TYPE_FILE
show_mode_combo = (fstype == DeviceFilesystem.TYPE_MOUNT and
(self.conn.is_qemu() or self.conn.is_test()))
if fstype == DeviceFilesystem.TYPE_TEMPLATE:
source_text = _("Te_mplate:")
else:
source_text = _("_Source path:")
self.widget("fs-source-title").set_text(source_text)
self.widget("fs-source-title").set_use_underline(True)
uiutil.set_grid_row_visible(
self.widget("fs-mode-combo"), show_mode_combo)
uiutil.set_grid_row_visible(
self.widget("fs-driver-combo"), show_driver_combo)
##############
# Public API #
##############
def reset_state(self): def reset_state(self):
self.widget("fs-type-combo").set_active(0) self.widget("fs-type-combo").set_active(0)
self.widget("fs-mode-combo").set_active(0) self.widget("fs-mode-combo").set_active(0)
@ -108,30 +169,11 @@ class vmmFSDetails(vmmGObjectUI):
self.widget("fs-source").set_text("") self.widget("fs-source").set_text("")
self.widget("fs-target").set_text("") self.widget("fs-target").set_text("")
self.widget("fs-readonly").set_active(False) self.widget("fs-readonly").set_active(False)
self._sync_ui()
self._active_edits = []
# Getters
def get_config_fs_mode(self):
return uiutil.get_list_selection(self.widget("fs-mode-combo"),
check_visible=True)
def get_config_fs_type(self):
return uiutil.get_list_selection(self.widget("fs-type-combo"),
check_visible=True)
def get_config_fs_readonly(self):
return self.widget("fs-readonly").get_active()
def get_config_fs_driver(self):
return uiutil.get_list_selection(self.widget("fs-driver-combo"),
check_visible=True)
def get_config_fs_format(self):
return uiutil.get_list_selection(self.widget("fs-format-combo"),
check_visible=True)
# Setters
def set_dev(self, dev): def set_dev(self, dev):
self._dev = dev self.reset_state()
uiutil.set_list_selection( uiutil.set_list_selection(
self.widget("fs-type-combo"), dev.type) self.widget("fs-type-combo"), dev.type)
@ -149,58 +191,68 @@ class vmmFSDetails(vmmGObjectUI):
self.widget("fs-target").set_text(dev.target or "") self.widget("fs-target").set_text(dev.target or "")
self.widget("fs-readonly").set_active(dev.readonly) self.widget("fs-readonly").set_active(dev.readonly)
uiutil.set_grid_row_visible( self._active_edits = []
self.widget("fs-type-combo"), self.conn.is_container_only())
# listeners ###################
def notify_change(self, ignore): # Device building #
self.emit("changed") ###################
def browse_fs_source(self, ignore1): def _set_values(self, dev):
self._browse_file(self.widget("fs-source"), isdir=True) fstype = uiutil.get_list_selection(self.widget("fs-type-combo"))
usage = uiutil.spin_get_helper(self.widget("fs-ram-source-spin"))
def update_fs_rows(self): source = self.widget("fs-source").get_text()
fstype = self.get_config_fs_type() target = self.widget("fs-target").get_text()
fsdriver = self.get_config_fs_driver() readonly = self.widget("fs-readonly").get_active()
ismount = bool(
fstype == DeviceFilesystem.TYPE_MOUNT or
self.conn.is_qemu() or self.conn.is_test())
show_mode = bool(ismount) fsformat = uiutil.get_list_selection(self.widget("fs-format-combo"))
uiutil.set_grid_row_visible(self.widget("fs-mode-combo"), show_mode) if not self.widget("fs-format-combo").get_visible():
fsformat = None
show_ram_source = fstype == DeviceFilesystem.TYPE_RAM mode = uiutil.get_list_selection(self.widget("fs-mode-combo"))
uiutil.set_grid_row_visible( if not self.widget("fs-mode-combo").get_visible():
self.widget("fs-ram-source-box"), show_ram_source) mode = None
uiutil.set_grid_row_visible(
self.widget("fs-source-box"), not show_ram_source)
show_format = bool( driver = uiutil.get_list_selection(self.widget("fs-driver-combo"))
fsdriver == DeviceFilesystem.DRIVER_NBD) if not self.widget("fs-driver-combo").get_visible():
uiutil.set_grid_row_visible( driver = None
self.widget("fs-format-combo"), show_format)
show_mode_combo = False if _EDIT_FS_TYPE in self._active_edits:
show_driver_combo = False dev.type = fstype
if fstype == DeviceFilesystem.TYPE_TEMPLATE: if (_EDIT_FS_RAM_SOURCE in self._active_edits or
source_text = _("Te_mplate:") _EDIT_FS_SOURCE in self._active_edits):
else: if fstype == DeviceFilesystem.TYPE_RAM:
source_text = _("_Source path:") dev.source = usage
show_mode_combo = self.conn.is_qemu() or self.conn.is_test() dev.source_units = 'MiB'
show_driver_combo = (self.conn.is_lxc() or else:
self.conn.is_test()) dev.source = source
if _EDIT_FS_TARGET in self._active_edits:
dev.target = target
if _EDIT_FS_MODE in self._active_edits:
dev.accessmode = mode
if _EDIT_FS_READONLY in self._active_edits:
dev.readonly = readonly
if _EDIT_FS_DRIVER in self._active_edits:
dev.driver_type = driver
if _EDIT_FS_FORMAT in self._active_edits:
dev.driver_format = fsformat
self.widget("fs-source-title").set_text(source_text) def build_device(self):
self.widget("fs-source-title").set_use_underline(True) self._active_edits = _EDIT_FS_ENUM[:]
uiutil.set_grid_row_visible(
self.widget("fs-mode-combo"), show_mode_combo)
uiutil.set_grid_row_visible(
self.widget("fs-driver-combo"), show_driver_combo)
def change_field(self, src): conn = self.conn.get_backend()
self.update_fs_rows() dev = DeviceFilesystem(conn)
self.notify_change(src) self._set_values(dev)
dev.validate_target(dev.target)
dev.validate()
return dev
def update_device(self, dev):
newdev = DeviceFilesystem(dev.conn, parsexml=dev.get_xml())
self._set_values(newdev)
return newdev
#################### ####################
@ -216,54 +268,23 @@ class vmmFSDetails(vmmGObjectUI):
self.config.CONFIG_DIR_FS or self.config.CONFIG_DIR_FS or
self.config.CONFIG_DIR_IMAGE) self.config.CONFIG_DIR_IMAGE)
if self.storage_browser is None: if self._storage_browser is None:
self.storage_browser = vmmStorageBrowser(self.conn) self._storage_browser = vmmStorageBrowser(self.conn)
self.storage_browser.set_finish_cb(set_storage_cb) self._storage_browser.set_finish_cb(set_storage_cb)
self.storage_browser.set_browse_reason(reason) self._storage_browser.set_browse_reason(reason)
self._storage_browser.show(self.topwin.get_ancestor(Gtk.Window))
self.storage_browser.show(self.topwin.get_ancestor(Gtk.Window))
################### #############
# Device building # # Listeners #
################### #############
def _build_xmlobj(self): def _change_cb(self, edittype):
conn = self.conn.get_backend() self._sync_ui()
source = self.widget("fs-source").get_text() if edittype not in self._active_edits:
target = self.widget("fs-target").get_text() self._active_edits.append(edittype)
usage = uiutil.spin_get_helper(self.widget("fs-ram-source-spin")) self.emit("changed")
mode = self.get_config_fs_mode()
fstype = self.get_config_fs_type()
readonly = self.get_config_fs_readonly()
driver = self.get_config_fs_driver()
fsformat = self.get_config_fs_format()
dev = DeviceFilesystem(conn) def _browse_fs_source_cb(self, src):
if fstype == DeviceFilesystem.TYPE_RAM: self._browse_file(self.widget("fs-source"), isdir=True)
dev.source = usage
dev.source_units = 'MiB'
else:
dev.source = source
dev.target = target
dev.validate_target(target)
if mode:
dev.accessmode = mode
if fstype:
dev.type = fstype
if readonly:
dev.readonly = readonly
if driver:
dev.driver_type = driver
if driver == DeviceFilesystem.DRIVER_LOOP:
dev.driver_format = "raw"
elif driver == DeviceFilesystem.DRIVER_NBD:
dev.driver_format = fsformat
dev.validate()
return dev
def build_xmlobj(self):
self._dev = self._build_xmlobj()
return self._dev

View File

@ -19,15 +19,12 @@ class DeviceFilesystem(Device):
TYPE_BLOCK = "block" TYPE_BLOCK = "block"
TYPE_RAM = "ram" TYPE_RAM = "ram"
MODE_PASSTHROUGH = "passthrough"
MODE_MAPPED = "mapped" MODE_MAPPED = "mapped"
MODE_SQUASH = "squash" MODE_SQUASH = "squash"
MODES = [MODE_PASSTHROUGH, MODE_MAPPED, MODE_SQUASH]
DRIVER_LOOP = "loop" DRIVER_LOOP = "loop"
DRIVER_NBD = "nbd" DRIVER_NBD = "nbd"
_type_prop = XMLProperty("./@type") _type_prop = XMLProperty("./@type")
accessmode = XMLProperty("./@accessmode") accessmode = XMLProperty("./@accessmode")
model = XMLProperty("./@model") model = XMLProperty("./@model")