| 1 | #!/usr/bin/env python3 |
| 2 | """Disposable libvirt lifecycle check. Run on the VM host with the preview tools.""" |
| 3 | import json |
| 4 | import os |
| 5 | from pathlib import Path |
| 6 | import socket |
| 7 | import struct |
| 8 | import sys |
| 9 | import time |
| 10 | import uuid |
| 11 | import xml.etree.ElementTree as ET |
| 12 | import vms |
| 13 | |
| 14 | |
| 15 | def 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 | |
| 86 | if __name__ == '__main__': |
| 87 | main() |