vdi-cloud/tools/test-fixed-pci-qemu.py

118 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""Isolated PVE Q35 smoke test: no guest disk, network backend or passed GPU.
Runs a paused TCG QEMU process and checks PCI properties and usb-redir hotplug.
It never connects to a managed VM's QMP socket or changes PVE configuration.
"""
import importlib.util
import json
from pathlib import Path
import socket
import subprocess
import tempfile
import time
spec = importlib.util.spec_from_file_location('policy', Path(__file__).with_name('set-fixed-pci.py'))
policy = importlib.util.module_from_spec(spec)
spec.loader.exec_module(policy)
def main():
with tempfile.TemporaryDirectory(prefix='vdi-fixed-pci-smoke-') as tmp:
path = str(Path(tmp) / 'qmp.sock')
flags = [part for value in policy.FIXED_GLOBALS for part in ('-global', value)]
cmd = ['/usr/bin/kvm', '-machine', 'pc-q35-11.0+pve0,accel=tcg', '-m', '128',
'-nodefaults', '-display', 'none', '-S',
'-readconfig', '/usr/share/qemu-server/pve-q35-4.0.cfg',
'-qmp', 'unix:' + path + ',server=on,wait=off', *flags,
'-device', 'virtio-net-pci,id=diagnet,bus=pci.0,addr=0x12',
'-device', 'virtio-serial,id=diagserial,bus=pci.0,addr=0x8',
'-device', 'ahci,id=diagahci,bus=pci.0,addr=0x7']
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
try:
deadline = time.monotonic() + 15
while not Path(path).exists():
if proc.poll() is not None:
raise RuntimeError(proc.stderr.read().decode())
if time.monotonic() > deadline:
raise RuntimeError('QEMU startup timed out')
time.sleep(.1)
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.settimeout(5)
sock.connect(path)
with sock.makefile('rwb', buffering=0) as stream:
print('QEMU_VERSION', json.loads(stream.readline())['QMP']['version'])
sequence = 0
def call(name, arguments=None):
nonlocal sequence
sequence += 1
ident = sequence
obj = {'execute': name, 'id': ident}
if arguments is not None:
obj['arguments'] = arguments
stream.write(json.dumps(obj).encode() + b'\n')
while True:
line = stream.readline()
if not line:
raise RuntimeError('QMP disconnected')
obj = json.loads(line)
if obj.get('id') == ident:
if 'error' in obj:
raise RuntimeError(str(obj['error']))
return obj['return']
call('qmp_capabilities')
for port in range(1, 5):
value = call('qom-get', {'path': f'/machine/peripheral/ich9-pcie-port-{port}',
'property': 'hotplug'})
if value is not False:
raise RuntimeError('Root port remains hotpluggable')
print('PCIE_PORT_HOTPLUG_OFF: 4/4')
for bridge in range(4):
value = call('qom-get', {'path': f'/machine/peripheral/pci.{bridge}',
'property': 'shpc'})
if value is not False:
raise RuntimeError('SHPC remains enabled')
print('PCI_BRIDGE_SHPC_OFF: 4/4')
lpc = [p for p in call('qom-list', {'path': '/machine/unattached'})
if p['type'] == 'child<ICH9-LPC>']
if len(lpc) != 1:
raise RuntimeError('Cannot uniquely identify ICH9 LPC')
value = call('qom-get', {'path': '/machine/unattached/' + lpc[0]['name'],
'property': 'acpi-pci-hotplug-with-bridge-support'})
if value is not False:
raise RuntimeError('ACPI PCI hotplug remains enabled')
print('ACPI_PCI_HOTPLUG_OFF')
call('chardev-add', {'id': 'diag-channel',
'backend': {'type': 'null', 'data': {}}})
call('device_add', {'driver': 'usb-redir', 'id': 'diag-usb',
'chardev': 'diag-channel', 'bus': 'ehci-2.0',
'filter': '0x08:-1:-1:-1:1|-1:-1:-1:-1:0'})
if 'diag-usb' not in call('human-monitor-command', {'command-line': 'info usb'}):
raise RuntimeError('USB device not attached')
print('USB_REDIR_ADD_OK')
call('device_del', {'id': 'diag-usb'})
deadline = time.monotonic() + 5
while any(p['name'] == 'diag-usb' for p in
call('qom-list', {'path': '/machine/peripheral'})):
if time.monotonic() > deadline:
raise RuntimeError('USB removal pending')
time.sleep(.1)
# QEMU 11 can remove the anonymous backend with the device.
if any(p['label'] == 'diag-channel' for p in call('query-chardev')):
call('chardev-remove', {'id': 'diag-channel'})
print('USB_REDIR_REMOVE_OK')
call('quit')
proc.wait(timeout=5)
print('ISOLATED_SMOKE_PASS (Windows UI and physical USB not tested)')
finally:
if proc.poll() is None:
proc.terminate()
proc.wait(timeout=5)
print('QEMU_STDERR:', proc.stderr.read().decode())
if __name__ == '__main__':
main()