guest: add convert_to_vnc()

This is the beginnings of support for a `virt-xml --convert-to-vnc`
option. Take an existing VM, strip out most of the previous graphics
config, and add VNC graphics.

We try to convert over some of the shared graphic bits, like listen
and port settings, if they were previously specified.

If spice GL was enabled, we convert to egl-headless config

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2024-09-17 10:02:59 -04:00 committed by Pavel Hrdina
parent c498c519ed
commit 229b905053
10 changed files with 269 additions and 4 deletions

View File

@ -0,0 +1,13 @@
<domain type='qemu'>
<name>convert-me</name>
<memory unit='KiB'>8388608</memory>
<currentMemory unit='KiB'>2097152</currentMemory>
<vcpu placement='static'>2</vcpu>
<os>
<type arch='i686'>hvm</type>
<boot dev='hd'/>
</os>
<devices>
</devices>
</domain>

View File

@ -0,0 +1,13 @@
<domain type="qemu">
<name>convert-me</name>
<memory unit="KiB">8388608</memory>
<currentMemory unit="KiB">2097152</currentMemory>
<vcpu placement="static">2</vcpu>
<os>
<type arch="i686">hvm</type>
<boot dev="hd"/>
</os>
<devices>
<graphics type="vnc" port="-1"/>
</devices>
</domain>

View File

@ -0,0 +1,31 @@
<domain type="kvm">
<name>convert-me</name>
<memory>2097152</memory>
<currentMemory>2097152</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch="x86_64" machine="q35">hvm</type>
<boot dev="network"/>
</os>
<features>
<acpi/>
<apic/>
<vmport state="off"/>
</features>
<devices>
<channel type="spicevmc">
<target type="virtio" name="com.redhat.spice.0"/>
</channel>
<graphics type="spice" port="-1" tlsPort="-1" autoport="yes">
<image compression="off"/>
</graphics>
<graphics type="vnc" port="5907"/>
<sound model="ich9"/>
<audio type='spice'/>
<video>
<model type="virtio"/>
</video>
<redirdev bus="usb" type="spicevmc"/>
<redirdev bus="usb" type="spicevmc"/>
</devices>
</domain>

View File

@ -0,0 +1,22 @@
<domain type="kvm">
<name>convert-me</name>
<memory>2097152</memory>
<currentMemory>2097152</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch="x86_64" machine="q35">hvm</type>
<boot dev="network"/>
</os>
<features>
<acpi/>
<apic/>
<vmport state="off"/>
</features>
<devices>
<graphics type="vnc" port="5907"/>
<sound model="ich9"/>
<video>
<model type="virtio"/>
</video>
</devices>
</domain>

View File

@ -0,0 +1,30 @@
<domain type="kvm">
<name>convert-me</name>
<memory>2097152</memory>
<currentMemory>2097152</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch="x86_64" machine="q35">hvm</type>
<boot dev="network"/>
</os>
<features>
<acpi/>
<apic/>
<vmport state="off"/>
</features>
<devices>
<channel type="spicevmc">
<target type="virtio" name="com.redhat.spice.0"/>
</channel>
<graphics type="spice" port="-1" tlsPort="-1" autoport="yes">
<image compression="off"/>
</graphics>
<sound model="ich9"/>
<audio type='spice'/>
<video>
<model type="virtio"/>
</video>
<redirdev bus="usb" type="spicevmc"/>
<redirdev bus="usb" type="spicevmc"/>
</devices>
</domain>

View File

@ -0,0 +1,22 @@
<domain type="kvm">
<name>convert-me</name>
<memory>2097152</memory>
<currentMemory>2097152</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch="x86_64" machine="q35">hvm</type>
<boot dev="network"/>
</os>
<features>
<acpi/>
<apic/>
<vmport state="off"/>
</features>
<devices>
<graphics type="vnc" port="-1"/>
<sound model="ich9"/>
<video>
<model type="virtio"/>
</video>
</devices>
</domain>

View File

@ -0,0 +1,20 @@
<domain type='qemu'>
<name>convert-me</name>
<memory unit='KiB'>8388608</memory>
<currentMemory unit='KiB'>2097152</currentMemory>
<vcpu placement='static'>2</vcpu>
<os>
<type arch='i686'>hvm</type>
<boot dev='hd'/>
</os>
<clock offset='utc'/>
<devices>
<graphics type='spice' port='5907' tlsPort='5901' autoport='no' passwd='sercet' passwdValidTo='2011-05-31T16:11:22' connected='disconnect' keymap='de' listen='127.0.0.1'>
<listen type='socket' socket='/tmp/spice.sock'/>
<listen type='address' address='127.0.0.1'/>
<gl enable='yes' rendernode='/dev/my/rendernode'/>
</graphics>
<graphics type='sdl'/>
</devices>
</domain>

View File

@ -0,0 +1,20 @@
<domain type="qemu">
<name>convert-me</name>
<memory unit="KiB">8388608</memory>
<currentMemory unit="KiB">2097152</currentMemory>
<vcpu placement="static">2</vcpu>
<os>
<type arch="i686">hvm</type>
<boot dev="hd"/>
</os>
<clock offset="utc"/>
<devices>
<graphics type="vnc" port="5907" keymap="de" listen="127.0.0.1" passwd="sercet" passwdValidTo="2011-05-31T16:11:22">
<listen type="socket" socket="/tmp/spice.sock"/>
<listen type="address" address="127.0.0.1"/>
</graphics>
<graphics type="egl-headless">
<gl rendernode="/dev/my/rendernode"/>
</graphics>
</devices>
</domain>

View File

@ -1215,3 +1215,17 @@ def testConvertToQ35():
_test("convert-to-q35-win10")
_test("convert-to-q35-f39", num_pcie_root_ports=5)
def testConvertToVNC():
conn = utils.URIs.openconn(utils.URIs.kvm_x86)
def _test(filename_base):
guest, outfile = _get_test_content(conn, filename_base)
guest.convert_to_vnc()
_alter_compare(conn, guest.get_xml(), outfile)
_test("convert-to-vnc-empty")
_test("convert-to-vnc-spice-devices")
_test("convert-to-vnc-spice-manyopts")
_test("convert-to-vnc-has-vnc")

View File

@ -828,6 +828,84 @@ class Guest(XMLBuilder):
self.add_q35_pcie_controllers()
def _convert_spice_gl_to_egl_headless(self):
if not self.has_spice():
return
spicedev = [g for g in self.devices.graphics if g.type == "spice"][0]
if not spicedev.gl:
return
dev = DeviceGraphics(self.conn)
dev.type = "egl-headless"
dev.set_defaults(self.conn)
if spicedev.rendernode:
dev.rendernode = spicedev.rendernode
self.add_device(dev)
def _convert_to_vnc_graphics(self):
"""
If there's already VNC graphics configured, we leave it intact,
but rip out all evidence of other graphics devices.
If there's other non-VNC, non-egl-headless configured, we try to
inplace convert the first device we encounter.
If there's no graphics configured, set up a default VNC config.`
"""
vnc_devs = [g for g in self.devices.graphics if g.type == "vnc"]
# We ignore egl-headless, it's not a true graphical frontend
other_devs = [g for g in self.devices.graphics if
g.type != "vnc" and g.type != "egl-headless"]
# Guest already had a vnc device.
# Remove all other devs and we are done
if vnc_devs:
for g in other_devs:
self.remove_device(g)
return
# We didn't find any non-vnc device to convert.
# Add a vnc device with default config
if not other_devs:
dev = DeviceGraphics(self.conn)
dev.type = dev.TYPE_VNC
dev.set_defaults(self.conn)
self.add_device(dev)
return
# Convert the pre-existing graphics device to vnc
# Remove the rest
dev = other_devs.pop(0)
srcdev = DeviceGraphics(self.conn, dev.get_xml())
for g in other_devs:
self.remove_device(g)
dev.clear()
dev.type = dev.TYPE_VNC
dev.keymap = srcdev.keymap
dev.port = srcdev.port
dev.autoport = srcdev.autoport
dev.passwd = srcdev.passwd
dev.passwdValidTo = srcdev.passwdValidTo
dev.listen = srcdev.listen
for listen in srcdev.listens:
srcdev.remove_child(listen)
dev.add_child(listen)
dev.set_defaults(self)
def convert_to_vnc(self):
"""
Convert existing XML to have one VNC graphics connection.
"""
self._convert_spice_gl_to_egl_headless()
# Rip out spice graphics devices unconditionally.
# Could be necessary if XML is in broken state.
self._force_remove_spice_devices()
self._convert_to_vnc_graphics()
def set_defaults(self, _guest):
self.set_capabilities_defaults()
@ -1277,10 +1355,12 @@ class Guest(XMLBuilder):
if redirdev.type == "spicevmc":
self.devices.remove_child(redirdev)
def _remove_spice_devices(self, rmdev):
if rmdev.DEVICE_TYPE != "graphics" or self.has_spice():
return
def _force_remove_spice_devices(self):
self._remove_spice_audio()
self._remove_spice_channels()
self._remove_spice_usbredir()
def _remove_spice_devices(self, rmdev):
if rmdev.DEVICE_TYPE != "graphics" or self.has_spice():
return
self._force_remove_spice_devices()