1#!/usr/bin/env python3
2"""Disposable libvirt lifecycle check. Run on the VM host with the preview tools."""
3import json
4import os
5from pathlib import Path
6import socket
7import struct
8import sys
9import time
10import uuid
11import xml.etree.ElementTree as ET
12import vms
13
14
15def main():
16 name = 'vm-check-' + uuid.uuid4().hex[:10]
17 preset = name + '-preset'
18 clone = name + '-clone'
19 created = []
20 source = {'name': name, 'owner': 'fixture-user', 'username': 'fixture', 'description': 'Disposable VM lifecycle fixture', 'mode': 'iso', 'image': 'blank',
21 'firmware': 'uefi', 'platform': 'linux', 'vcpus': 1, 'memory': 2**29, 'disk': 2**30,
22 'autostart': False, 'start': True}
23 try:
24 vms.validate('create', source)
25 vms.create(source)
26 created.append(name)
27 root = ET.fromstring(vms.virsh('dumpxml', name))
28 assert root.findtext('metadata/' + vms.NS + 'vm/' + vms.NS + 'platform') == 'linux'
29 port = vms.console_target(name)['port']
30 with socket.create_connection(('127.0.0.1', port), timeout=5) as screen:
31 assert screen.recv(12).startswith(b'RFB ')
32 if len(sys.argv) > 1:
33 with socket.socket(socket.AF_UNIX) as screen:
34 screen.settimeout(5)
35 screen.connect(sys.argv[1])
36 screen.sendall(json.dumps({'operation': 'vm.console', 'payload': {'name': name}}).encode() + b'\n')
37 length = struct.unpack('!I', screen.recv(4))[0]
38 assert json.loads(screen.recv(length)) == {'value': None}
39 assert screen.recv(12).startswith(b'RFB ')
40 vms.virsh('destroy', name)
41 installer = vms.UPLOADS / (name + '.iso')
42 vms.managed_directory(vms.UPLOADS)
43 installer.write_bytes(b'disposable optical fixture' * 4096)
44 try:
45 vms.change_media({'name': name, 'image': 'upload:' + installer.name})
46 vms.virsh('start', name)
47 vms.change_media({'name': name, 'image': ''})
48 live = ET.fromstring(vms.virsh('dumpxml', name)).find('./devices/disk[@device="cdrom"]/source')
49 assert live is None or not live.get('file'), vms.virsh('dumpxml', name)
50 saved = ET.fromstring(vms.virsh('dumpxml', name, '--inactive')).find('./devices/disk[@device="cdrom"]/source')
51 assert saved is None or not saved.get('file'), vms.virsh('dumpxml', name, '--inactive')
52 vms.virsh('destroy', name)
53 finally:
54 installer.unlink(missing_ok=True)
55 vms.save_preset({'name': name, 'id': preset, 'os': 'Fixture Linux', 'description': 'Snapshot fixture'})
56 prepared = vms.read_preset(preset)
57 assert prepared['capacity'] == 2**30 and prepared['firmware'] == 'uefi'
58 assert any(item['id'] == preset for item in vms.library()['presets'])
59 nvram = (vms.PRESETS / preset / 'nvram.fd').read_bytes()
60 copy = {**source, 'name': clone, 'mode': 'preset', 'image': preset, 'disk': 2**31, 'start': False}
61 vms.create(copy)
62 created.append(clone)
63 assert (vms.DISKS / clone / 'nvram.fd').read_bytes() == nvram
64 assert vms.info(vms.DISKS / clone / 'disk.qcow2')['virtual-size'] == 2**31
65 vms.command('qemu-io', '-f', 'qcow2', '-c', 'write -P 0x5a 0 4096', str(vms.DISKS / clone / 'disk.qcow2'))
66 vms.command('qemu-io', '-f', 'qcow2', '-c', 'read -P 0 0 4096', str(vms.DISKS / name / 'disk.qcow2'))
67 vms.command('qemu-io', '-f', 'qcow2', '-c', 'read -P 0 0 4096', str(vms.PRESETS / preset / 'disk.qcow2'))
68 vms.virsh('undefine', name, '--nvram')
69 created.remove(name)
70 vms.virsh('start', clone)
71 assert vms.virsh('domstate', clone).strip() == 'running'
72 print(json.dumps({'real_vnc': 'passed', 'host_console_stream': 'passed', 'iso_attach_live_eject': 'passed',
73 'prepared_preset': 'passed', 'cloned_nvram': 'passed', 'independent_disks': 'passed',
74 'boot_after_source_removal': 'passed'}))
75 finally:
76 import subprocess
77 for guest in created:
78 subprocess.run(['virsh', '-c', 'qemu:///system', 'destroy', guest], capture_output=True)
79 subprocess.run(['virsh', '-c', 'qemu:///system', 'undefine', guest, '--nvram'], capture_output=True)
80 import shutil
81 for guest in [name, clone]:
82 shutil.rmtree(vms.DISKS / guest, ignore_errors=True)
83 shutil.rmtree(vms.PRESETS / preset, ignore_errors=True)
84
85
86if __name__ == '__main__':
87 main()