libvirt: Add configuration options to set SPICE compression settings

This patch adds the following SPICE-related options to the 'spice'
configuration group of a Nova configuration:

  - image_compression
  - jpeg_compression
  - zlib_compression
  - playback_compression
  - streaming_mode

These configuration options can be used to enable and set the SPICE
compression settings for libvirt (QEMU/KVM) provisioned instances.
Each configuration option is optional and can be set explictly to
configure the associated SPICE compression setting for libvirt. If all
configuration options are not set, then none of the SPICE compression
settings will be configured for libvirt, which corresponds to the
behavior before this change. In this case, the built-in defaults from
the libvirt backend (e.g. QEMU) are used.

Note that those options are only taken into account if SPICE support is
enabled (and the VNC support is disabled).

Implements: blueprint nova-support-spice-compression-algorithm
Change-Id: Ia7efeb1b1a04504721e1a5bdd1b5fa7a87cdb810
This commit is contained in:
Manuel Bentele 2022-02-07 10:30:38 +01:00
parent 26f24b7086
commit b5e0ed248f
7 changed files with 204 additions and 1 deletions

View File

@ -366,6 +366,16 @@ Replace ``IP_ADDRESS`` with the IP address from which the proxy is accessible
by the outside world. For example, this may be the management interface IP by the outside world. For example, this may be the management interface IP
address of the controller or the VIP. address of the controller or the VIP.
Optionally, the :program:`nova-compute` service supports the following
additional options to configure compression settings (algorithms and modes)
for SPICE consoles.
- :oslo.config:option:`spice.image_compression`
- :oslo.config:option:`spice.jpeg_compression`
- :oslo.config:option:`spice.zlib_compression`
- :oslo.config:option:`spice.playback_compression`
- :oslo.config:option:`spice.streaming_mode`
Serial Serial
------ ------

View File

@ -84,6 +84,59 @@ Agent. With the Spice agent installed the following features are enabled:
* Better mouse integration - The mouse can be captured and released without * Better mouse integration - The mouse can be captured and released without
needing to click inside the console or press keys to release it. The needing to click inside the console or press keys to release it. The
performance of mouse movement is also improved. performance of mouse movement is also improved.
"""),
cfg.StrOpt('image_compression',
advanced=True,
choices=[
('auto_glz', 'enable image compression mode to choose between glz '
'and quic algorithm, based on image properties'),
('auto_lz', 'enable image compression mode to choose between lz '
'and quic algorithm, based on image properties'),
('quic', 'enable image compression based on the SFALIC algorithm'),
('glz', 'enable image compression using lz with history based '
'global dictionary'),
('lz', 'enable image compression with the Lempel-Ziv algorithm'),
('off', 'disable image compression')
],
help="""
Configure the SPICE image compression (lossless).
"""),
cfg.StrOpt('jpeg_compression',
advanced=True,
choices=[
('auto', 'enable JPEG image compression automatically'),
('never', 'disable JPEG image compression'),
('always', 'enable JPEG image compression')
],
help="""
Configure the SPICE wan image compression (lossy for slow links).
"""),
cfg.StrOpt('zlib_compression',
advanced=True,
choices=[
('auto', 'enable zlib image compression automatically'),
('never', 'disable zlib image compression'),
('always', 'enable zlib image compression')
],
help="""
Configure the SPICE wan image compression (lossless for slow links).
"""),
cfg.BoolOpt('playback_compression',
advanced=True,
help="""
Enable the SPICE audio stream compression (using celt).
"""),
cfg.StrOpt('streaming_mode',
advanced=True,
choices=[
('filter', 'SPICE server adds additional filters to decide if '
'video streaming should be activated'),
('all', 'any fast-refreshing window can be encoded into a video '
'stream'),
('off', 'no video detection and (lossy) compression is performed')
],
help="""
Configure the SPICE video stream detection and (lossy) compression.
"""), """),
cfg.URIOpt('html5proxy_base_url', cfg.URIOpt('html5proxy_base_url',
default='http://127.0.0.1:6082/spice_auto.html', default='http://127.0.0.1:6082/spice_auto.html',

View File

@ -1537,7 +1537,7 @@ class LibvirtConfigGuestInputTest(LibvirtConfigBaseTest):
class LibvirtConfigGuestGraphicsTest(LibvirtConfigBaseTest): class LibvirtConfigGuestGraphicsTest(LibvirtConfigBaseTest):
def test_config_graphics(self): def test_config_graphics_vnc(self):
obj = config.LibvirtConfigGuestGraphics() obj = config.LibvirtConfigGuestGraphics()
obj.type = "vnc" obj.type = "vnc"
obj.autoport = True obj.autoport = True
@ -1549,6 +1549,30 @@ class LibvirtConfigGuestGraphicsTest(LibvirtConfigBaseTest):
<graphics type="vnc" autoport="yes" keymap="en_US" listen="127.0.0.1"/> <graphics type="vnc" autoport="yes" keymap="en_US" listen="127.0.0.1"/>
""") """)
def test_config_graphics_spice(self):
obj = config.LibvirtConfigGuestGraphics()
obj.type = "spice"
obj.autoport = False
obj.keymap = "en_US"
obj.listen = "127.0.0.1"
obj.image_compression = "auto_glz"
obj.jpeg_compression = "auto"
obj.zlib_compression = "always"
obj.playback_compression = True
obj.streaming_mode = "filter"
xml = obj.to_xml()
self.assertXmlEqual(xml, """
<graphics type="spice" autoport="no" keymap="en_US" listen="127.0.0.1">
<image compression="auto_glz"/>
<jpeg compression="auto"/>
<zlib compression="always"/>
<playback compression="on"/>
<streaming mode="filter"/>
</graphics>
""")
class LibvirtConfigGuestHostdev(LibvirtConfigBaseTest): class LibvirtConfigGuestHostdev(LibvirtConfigBaseTest):

View File

@ -5793,6 +5793,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(cfg.devices[3].type, 'vnc') self.assertEqual(cfg.devices[3].type, 'vnc')
self.assertEqual(cfg.devices[3].listen, '10.0.0.1') self.assertEqual(cfg.devices[3].listen, '10.0.0.1')
self.assertIsNone(cfg.devices[3].keymap) self.assertIsNone(cfg.devices[3].keymap)
self.assertIsNone(cfg.devices[3].image_compression)
self.assertIsNone(cfg.devices[3].jpeg_compression)
self.assertIsNone(cfg.devices[3].zlib_compression)
self.assertIsNone(cfg.devices[3].playback_compression)
self.assertIsNone(cfg.devices[3].streaming_mode)
def test_get_guest_config_with_vnc_and_tablet(self): def test_get_guest_config_with_vnc_and_tablet(self):
self.flags(enabled=True, group='vnc') self.flags(enabled=True, group='vnc')
@ -5823,6 +5828,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
vconfig.LibvirtConfigMemoryBalloon) vconfig.LibvirtConfigMemoryBalloon)
self.assertEqual(cfg.devices[3].type, 'vnc') self.assertEqual(cfg.devices[3].type, 'vnc')
self.assertIsNone(cfg.devices[3].image_compression)
self.assertIsNone(cfg.devices[3].jpeg_compression)
self.assertIsNone(cfg.devices[3].zlib_compression)
self.assertIsNone(cfg.devices[3].playback_compression)
self.assertIsNone(cfg.devices[3].streaming_mode)
self.assertEqual(cfg.devices[5].type, 'tablet') self.assertEqual(cfg.devices[5].type, 'tablet')
def test_get_guest_config_with_spice_and_tablet(self): def test_get_guest_config_with_spice_and_tablet(self):
@ -5859,6 +5869,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(cfg.devices[3].type, 'spice') self.assertEqual(cfg.devices[3].type, 'spice')
self.assertEqual(cfg.devices[3].listen, '10.0.0.1') self.assertEqual(cfg.devices[3].listen, '10.0.0.1')
self.assertIsNone(cfg.devices[3].keymap) self.assertIsNone(cfg.devices[3].keymap)
self.assertIsNone(cfg.devices[3].image_compression)
self.assertIsNone(cfg.devices[3].jpeg_compression)
self.assertIsNone(cfg.devices[3].zlib_compression)
self.assertIsNone(cfg.devices[3].playback_compression)
self.assertIsNone(cfg.devices[3].streaming_mode)
self.assertEqual(cfg.devices[5].type, 'tablet') self.assertEqual(cfg.devices[5].type, 'tablet')
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock()) @mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
@ -5918,8 +5933,57 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(cfg.devices[3].target_name, "com.redhat.spice.0") self.assertEqual(cfg.devices[3].target_name, "com.redhat.spice.0")
self.assertEqual(cfg.devices[3].type, 'spicevmc') self.assertEqual(cfg.devices[3].type, 'spicevmc')
self.assertEqual(cfg.devices[4].type, "spice") self.assertEqual(cfg.devices[4].type, "spice")
self.assertIsNone(cfg.devices[4].image_compression)
self.assertIsNone(cfg.devices[4].jpeg_compression)
self.assertIsNone(cfg.devices[4].zlib_compression)
self.assertIsNone(cfg.devices[4].playback_compression)
self.assertIsNone(cfg.devices[4].streaming_mode)
self.assertEqual(cfg.devices[5].type, video_type) self.assertEqual(cfg.devices[5].type, video_type)
def test_get_guest_config_with_spice_compression(self):
self.flags(enabled=False, group='vnc')
self.flags(virt_type='kvm', group='libvirt')
self.flags(enabled=True,
agent_enabled=False,
image_compression='auto_lz',
jpeg_compression='never',
zlib_compression='always',
playback_compression=False,
streaming_mode='all',
server_listen='10.0.0.1',
group='spice')
self.flags(pointer_model='usbtablet')
cfg = self._get_guest_config_with_graphics()
self.assertEqual(len(cfg.devices), 9)
self.assertIsInstance(cfg.devices[0],
vconfig.LibvirtConfigGuestDisk)
self.assertIsInstance(cfg.devices[1],
vconfig.LibvirtConfigGuestDisk)
self.assertIsInstance(cfg.devices[2],
vconfig.LibvirtConfigGuestSerial)
self.assertIsInstance(cfg.devices[3],
vconfig.LibvirtConfigGuestGraphics)
self.assertIsInstance(cfg.devices[4],
vconfig.LibvirtConfigGuestVideo)
self.assertIsInstance(cfg.devices[5],
vconfig.LibvirtConfigGuestInput)
self.assertIsInstance(cfg.devices[6],
vconfig.LibvirtConfigGuestRng)
self.assertIsInstance(cfg.devices[7],
vconfig.LibvirtConfigGuestUSBHostController)
self.assertIsInstance(cfg.devices[8],
vconfig.LibvirtConfigMemoryBalloon)
self.assertEqual(cfg.devices[3].type, 'spice')
self.assertEqual(cfg.devices[3].listen, '10.0.0.1')
self.assertEqual(cfg.devices[3].image_compression, 'auto_lz')
self.assertEqual(cfg.devices[3].jpeg_compression, 'never')
self.assertEqual(cfg.devices[3].zlib_compression, 'always')
self.assertFalse(cfg.devices[3].playback_compression)
self.assertEqual(cfg.devices[3].streaming_mode, 'all')
@mock.patch.object(host.Host, 'get_guest') @mock.patch.object(host.Host, 'get_guest')
@mock.patch.object(libvirt_driver.LibvirtDriver, @mock.patch.object(libvirt_driver.LibvirtDriver,
'_get_serial_ports_from_guest') '_get_serial_ports_from_guest')

View File

@ -2047,6 +2047,12 @@ class LibvirtConfigGuestGraphics(LibvirtConfigGuestDevice):
self.keymap = None self.keymap = None
self.listen = None self.listen = None
self.image_compression = None
self.jpeg_compression = None
self.zlib_compression = None
self.playback_compression = None
self.streaming_mode = None
def format_dom(self): def format_dom(self):
dev = super(LibvirtConfigGuestGraphics, self).format_dom() dev = super(LibvirtConfigGuestGraphics, self).format_dom()
@ -2057,6 +2063,24 @@ class LibvirtConfigGuestGraphics(LibvirtConfigGuestDevice):
if self.listen: if self.listen:
dev.set("listen", self.listen) dev.set("listen", self.listen)
if self.type == "spice":
if self.image_compression is not None:
dev.append(etree.Element(
'image', compression=self.image_compression))
if self.jpeg_compression is not None:
dev.append(etree.Element(
'jpeg', compression=self.jpeg_compression))
if self.zlib_compression is not None:
dev.append(etree.Element(
'zlib', compression=self.zlib_compression))
if self.playback_compression is not None:
dev.append(etree.Element(
'playback', compression=self.get_on_off_str(
self.playback_compression)))
if self.streaming_mode is not None:
dev.append(etree.Element(
'streaming', mode=self.streaming_mode))
return dev return dev

View File

@ -7302,6 +7302,11 @@ class LibvirtDriver(driver.ComputeDriver):
graphics = vconfig.LibvirtConfigGuestGraphics() graphics = vconfig.LibvirtConfigGuestGraphics()
graphics.type = "spice" graphics.type = "spice"
graphics.listen = CONF.spice.server_listen graphics.listen = CONF.spice.server_listen
graphics.image_compression = CONF.spice.image_compression
graphics.jpeg_compression = CONF.spice.jpeg_compression
graphics.zlib_compression = CONF.spice.zlib_compression
graphics.playback_compression = CONF.spice.playback_compression
graphics.streaming_mode = CONF.spice.streaming_mode
guest.add_device(graphics) guest.add_device(graphics)
add_video_driver = True add_video_driver = True

View File

@ -0,0 +1,23 @@
---
features:
- |
The following SPICE-related options are added to the ``spice``
configuration group of a Nova configuration:
- ``image_compression``
- ``jpeg_compression``
- ``zlib_compression``
- ``playback_compression``
- ``streaming_mode``
These configuration options can be used to enable and set the
SPICE compression settings for libvirt (QEMU/KVM) provisioned
instances. Each configuration option is optional and can be set
explictly to configure the associated SPICE compression setting
for libvirt. If all configuration options are not set, then none
of the SPICE compression settings will be configured for libvirt,
which corresponds to the behavior before this change. In this case,
the built-in defaults from the libvirt backend (e.g. QEMU) are used.
Note that those options are only taken into account if SPICE support
is enabled (and the VNC support is disabled).