details: add xmleditor UI
Handling this is a bit different from other bits, because: 1) the <device> editing paradigm is unique. We need to replace the device in line in the XML which is a new operation 2) the New VM customize pattern is tricky and needs lots of special handling
This commit is contained in:
parent
df80852952
commit
a5f4033493
|
@ -5,6 +5,7 @@
|
|||
# See the COPYING file in the top-level directory.
|
||||
|
||||
import logging
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
@ -25,10 +26,13 @@ from .netlist import vmmNetworkList
|
|||
from .oslist import vmmOSList
|
||||
from .storagebrowse import vmmStorageBrowser
|
||||
from .vsockdetails import vmmVsockDetails
|
||||
from .xmleditor import vmmXMLEditor
|
||||
|
||||
|
||||
# Parameters that can be edited in the details window
|
||||
(EDIT_NAME,
|
||||
(EDIT_XML,
|
||||
|
||||
EDIT_NAME,
|
||||
EDIT_TITLE,
|
||||
EDIT_MACHTYPE,
|
||||
EDIT_FIRMWARE,
|
||||
|
@ -100,7 +104,7 @@ from .vsockdetails import vmmVsockDetails
|
|||
|
||||
EDIT_FS,
|
||||
|
||||
EDIT_HOSTDEV_ROMBAR) = range(1, 58)
|
||||
EDIT_HOSTDEV_ROMBAR) = range(1, 59)
|
||||
|
||||
|
||||
# Columns in hw list model
|
||||
|
@ -333,6 +337,33 @@ def _warn_cpu_thread_topo(threads, cpu_model):
|
|||
return False
|
||||
|
||||
|
||||
def _unindent_device_xml(xml):
|
||||
"""
|
||||
The device parsed from a domain will have no indent
|
||||
for the first line, but then <domain> expected indent
|
||||
from the remaining lines. Try to unindent the remaining
|
||||
lines so it looks nice in the XML editor.
|
||||
"""
|
||||
lines = xml.splitlines()
|
||||
if not xml.startswith("<") or len(lines) < 2:
|
||||
return xml
|
||||
|
||||
ret = ""
|
||||
unindent = 0
|
||||
for c in lines[1]:
|
||||
if c != " ":
|
||||
break
|
||||
unindent += 1
|
||||
|
||||
unindent = max(0, unindent - 2)
|
||||
ret = lines[0] + "\n"
|
||||
for line in lines[1:]:
|
||||
if re.match(r"^%s *<.*$" % (unindent * " "), line):
|
||||
line = line[unindent:]
|
||||
ret += line + "\n"
|
||||
return ret
|
||||
|
||||
|
||||
class vmmDetails(vmmGObjectUI):
|
||||
def __init__(self, vm, builder, topwin, is_customize_dialog):
|
||||
vmmGObjectUI.__init__(self, "details.ui",
|
||||
|
@ -398,6 +429,16 @@ class vmmDetails(vmmGObjectUI):
|
|||
self.vsockdetails.connect("changed-cid",
|
||||
lambda *x: self.enable_apply(x, EDIT_VSOCK_CID))
|
||||
|
||||
self._xmleditor = vmmXMLEditor(self.builder, self.topwin,
|
||||
self.widget("hw-panel-align"),
|
||||
self.widget("hw-panel"))
|
||||
self._xmleditor.connect("changed",
|
||||
lambda s: self.enable_apply(EDIT_XML))
|
||||
self._xmleditor.connect("xml-requested",
|
||||
self._xmleditor_xml_requested_cb)
|
||||
self._xmleditor.connect("xml-reset",
|
||||
self._xmleditor_xml_reset_cb)
|
||||
|
||||
self.oldhwkey = None
|
||||
self.addhwmenu = None
|
||||
self._addhwmenuitems = None
|
||||
|
@ -543,6 +584,8 @@ class vmmDetails(vmmGObjectUI):
|
|||
self.netlist = None
|
||||
self.vsockdetails.cleanup()
|
||||
self.vsockdetails = None
|
||||
self._xmleditor.cleanup()
|
||||
self._xmleditor = None
|
||||
|
||||
|
||||
##########################
|
||||
|
@ -967,6 +1010,10 @@ class vmmDetails(vmmGObjectUI):
|
|||
|
||||
try:
|
||||
dev = self.get_hw_row()[HW_LIST_COL_DEVICE]
|
||||
if dev:
|
||||
self._xmleditor.set_xml(_unindent_device_xml(dev.get_xml()))
|
||||
else:
|
||||
self._xmleditor.set_xml_from_libvirtobject(self.vm)
|
||||
|
||||
if pagetype == HW_LIST_TYPE_GENERAL:
|
||||
self.refresh_overview_page()
|
||||
|
@ -1045,6 +1092,12 @@ class vmmDetails(vmmGObjectUI):
|
|||
# External action listeners #
|
||||
#############################
|
||||
|
||||
def _xmleditor_xml_requested_cb(self, src):
|
||||
self.hw_selected()
|
||||
|
||||
def _xmleditor_xml_reset_cb(self, src):
|
||||
self.hw_selected()
|
||||
|
||||
def add_hardware(self, src_ignore):
|
||||
try:
|
||||
if self.addhw is None:
|
||||
|
@ -1389,7 +1442,12 @@ class vmmDetails(vmmGObjectUI):
|
|||
|
||||
success = False
|
||||
try:
|
||||
if pagetype is HW_LIST_TYPE_GENERAL:
|
||||
if self.edited(EDIT_XML):
|
||||
if dev:
|
||||
success = self._config_device_xml_apply(dev)
|
||||
else:
|
||||
success = self._config_domain_xml_apply()
|
||||
elif pagetype is HW_LIST_TYPE_GENERAL:
|
||||
success = self.config_overview_apply()
|
||||
elif pagetype is HW_LIST_TYPE_OS:
|
||||
success = self.config_os_apply()
|
||||
|
@ -1424,7 +1482,7 @@ class vmmDetails(vmmGObjectUI):
|
|||
elif pagetype is HW_LIST_TYPE_VSOCK:
|
||||
success = self.config_vsock_apply(dev)
|
||||
except Exception as e:
|
||||
return self.err.show_err(_("Error apply changes: %s") % e)
|
||||
return self.err.show_err(_("Error applying changes: %s") % e)
|
||||
|
||||
if success is not False:
|
||||
self.disable_apply()
|
||||
|
@ -1444,6 +1502,20 @@ class vmmDetails(vmmGObjectUI):
|
|||
def edited(self, pagetype):
|
||||
return pagetype in self.active_edits
|
||||
|
||||
def _config_domain_xml_apply(self):
|
||||
newxml = self._xmleditor.get_xml()
|
||||
def change_cb():
|
||||
return self.vm.define_xml(newxml)
|
||||
return vmmAddHardware.change_config_helper(
|
||||
change_cb, {}, self.vm, self.err)
|
||||
|
||||
def _config_device_xml_apply(self, devobj):
|
||||
newxml = self._xmleditor.get_xml()
|
||||
def change_cb():
|
||||
return self.vm.replace_device_xml(devobj, newxml)
|
||||
return vmmAddHardware.change_config_helper(
|
||||
change_cb, {}, self.vm, self.err)
|
||||
|
||||
def config_overview_apply(self):
|
||||
kwargs = {}
|
||||
hotplug_args = {}
|
||||
|
|
|
@ -468,6 +468,23 @@ class vmmDomain(vmmLibvirtObject):
|
|||
|
||||
self._redefine_xmlobj(xmlobj)
|
||||
|
||||
def replace_device_xml(self, devobj, newxml):
|
||||
"""
|
||||
When device XML is editing from the XML editor window.
|
||||
"""
|
||||
do_hotplug = False
|
||||
devclass = devobj.__class__
|
||||
newdev = devclass(devobj.conn, parsexml=newxml)
|
||||
|
||||
xmlobj = self._make_xmlobj_to_define()
|
||||
editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug)
|
||||
if not editdev:
|
||||
return
|
||||
|
||||
xmlobj.devices.replace_child(editdev, newdev)
|
||||
self._redefine_xmlobj(xmlobj)
|
||||
return editdev, newdev
|
||||
|
||||
def define_cpu(self, vcpus=_SENTINEL, maxvcpus=_SENTINEL,
|
||||
model=_SENTINEL, secure=_SENTINEL, sockets=_SENTINEL,
|
||||
cores=_SENTINEL, threads=_SENTINEL):
|
||||
|
@ -1603,6 +1620,7 @@ class vmmDomainVirtinst(vmmDomain):
|
|||
def __init__(self, conn, backend, key):
|
||||
vmmDomain.__init__(self, conn, backend, key)
|
||||
self._orig_xml = None
|
||||
self._orig_backend = self._backend
|
||||
|
||||
self._refresh_status()
|
||||
logging.debug("%s initialized with XML=\n%s", self, self._XMLDesc(0))
|
||||
|
@ -1640,6 +1658,64 @@ class vmmDomainVirtinst(vmmDomain):
|
|||
# XML handling #
|
||||
################
|
||||
|
||||
def _sync_disk_storage_params(self, origdisk, newdisk):
|
||||
"""
|
||||
When raw disk XML is edited from the customize wizard, the
|
||||
original DeviceDisk is completely blown away, but that will
|
||||
lose the storage creation info. This syncs that info across
|
||||
to the new DeviceDisk
|
||||
"""
|
||||
if origdisk.path != newdisk.path:
|
||||
return
|
||||
|
||||
if origdisk.get_vol_object():
|
||||
logging.debug(
|
||||
"Syncing vol_object=%s from origdisk=%s to newdisk=%s",
|
||||
origdisk.get_vol_object(), origdisk, newdisk)
|
||||
newdisk.set_vol_object(origdisk.get_vol_object(),
|
||||
origdisk.get_parent_pool())
|
||||
elif origdisk.get_vol_install():
|
||||
logging.debug(
|
||||
"Syncing vol_install=%s from origdisk=%s to newdisk=%s",
|
||||
origdisk.get_vol_install(), origdisk, newdisk)
|
||||
newdisk.set_vol_install(origdisk.get_vol_install())
|
||||
|
||||
def _replace_domain_xml(self, newxml):
|
||||
"""
|
||||
Blow away the Guest instance we are tracking internally with
|
||||
a new one from the xmleditor UI, and sync over all disk storage
|
||||
info afterwards
|
||||
"""
|
||||
newbackend = Guest(self._backend.conn, parsexml=newxml)
|
||||
newbackend.installer_instance = self._backend.installer_instance
|
||||
|
||||
for origdisk in self._backend.devices.disk:
|
||||
for newdisk in newbackend.devices.disk:
|
||||
if origdisk.compare_device(newdisk, newdisk.get_xml_idx()):
|
||||
self._sync_disk_storage_params(origdisk, newdisk)
|
||||
break
|
||||
|
||||
self._backend = newbackend
|
||||
|
||||
def replace_device_xml(self, devobj, newxml):
|
||||
"""
|
||||
Overwrite vmmDomain's implementation, since we need to wire in
|
||||
syncing disk details.
|
||||
"""
|
||||
if self._backend == self._orig_backend:
|
||||
# If the backend hasn't been replace yet, do it, so we don't
|
||||
# have a mix of is_build Guest with XML parsed objects which
|
||||
# might contain dragons
|
||||
self._replace_domain_xml(self._backend.get_xml())
|
||||
editdev, newdev = vmmDomain.replace_device_xml(self, devobj, newxml)
|
||||
if editdev.DEVICE_TYPE == "disk":
|
||||
self._sync_disk_storage_params(editdev, newdev)
|
||||
|
||||
def define_xml(self, xml):
|
||||
origxml = self._backend.get_xml()
|
||||
self._replace_domain_xml(xml)
|
||||
self._redefine_xml_internal(origxml, xml)
|
||||
|
||||
def define_name(self, newname):
|
||||
# We need to overwrite this, since the implementation for libvirt
|
||||
# needs to do some crazy stuff.
|
||||
|
|
|
@ -362,6 +362,8 @@ class DeviceDisk(Device):
|
|||
self._set_xmlpath(self.path)
|
||||
|
||||
def get_vol_object(self):
|
||||
if not self._storage_backend:
|
||||
return None
|
||||
return self._storage_backend.get_vol_object()
|
||||
def get_vol_install(self):
|
||||
if not self._storage_backend:
|
||||
|
|
Loading…
Reference in New Issue