A Python pip package to automate testing of whole operating systems with an image recognition based approach and libvirt (qemu). Inspired by openQA.
Project description
OS Tester
A Python pip package to automate testing of whole operating systems with an image recognition based approach and libvirt (qemu). Inspired by openQA.
Example
from os_tester.vm import vm
from os_tester.stages import stages
import libvirt
# SELinux Policy for allowing Qemu to access image files:
# ausearch -c 'qemu-system-x86' --raw | audit2allow -M my-qemusystemx86
# semodule -X 300 -i my-qemusystemx86.pp
# NVME: http://blog.frankenmichl.de/2018/02/13/add-nvme-device-to-vm/
# dd if=/dev/zero of=/tmp/test_vm_1.img bs=1M count=8192
# Or:
# qemu-img create -f qcow2 /tmp/test_vm_1.qcow2 8G
def get_vm_xml(name: str, title: str, uuid: str, isoPath: str, vmImagePath: str) -> str:
ramGiB: int = 2
numCpus: int = 2
return f"""
<domain type="kvm" xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<name>{name}</name>
<uuid>{uuid}</uuid>
<title>{title}</title>
<memory unit="GiB">{ramGiB}</memory>
<currentMemory unit="GiB">{ramGiB}</currentMemory>
<vcpu placement="static">{numCpus}</vcpu>
<os firmware='efi'>
<!--To get a list of all machines: qemu-system-x86_64 -machine help-->
<type arch="x86_64" machine="q35">hvm</type>
<bootmenu enable="yes"/>
<boot dev="hd"/>
<boot dev="cdrom"/>
</os>
<features>
<acpi/>
<apic/>
</features>
<clock offset="localtime">
<timer name="rtc" tickpolicy="catchup"/>
<timer name="pit" tickpolicy="delay"/>
<timer name="hpet" present="no"/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<pm>
<suspend-to-mem enabled="no"/>
<suspend-to-disk enabled="no"/>
</pm>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<controller type='pci' index='0' model='pcie-root'/>
<controller type='pci' index='1' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='1' port='0x10'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</controller>
<disk type="file" device="cdrom">
<driver name="qemu" type="raw"/>
<source file="{isoPath}" startupPolicy="mandatory"/>
<target dev="hdc" bus="sata"/>
<readonly/>
<address type="drive" controller="0" bus="0" target="0" unit="2"/>
</disk>
<interface type="user">
<mac address="52:54:00:8d:ce:97"/>
<model type="virtio"/>
<address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x00"/>
</interface>
<serial type="pty">
<target type="isa-serial" port="0">
<model name="isa-serial"/>
</target>
</serial>
<console type="pty">
<target type="serial" port="0"/>
</console>
<input type="tablet" bus="usb">
<address type="usb" bus="0" port="2"/>
</input>
<input type="mouse" bus="ps2"/>
<input type="keyboard" bus="ps2"/>
<memballoon model="virtio">
<address type="pci" domain="0x0000" bus="0x05" slot="0x00" function="0x0"/>
</memballoon>
</devices>
<qemu:commandline>
<qemu:arg value='-drive'/>
<qemu:arg value='file={vmImagePath},format=qcow2,if=none,id=nvdisk1,media=disk'/>
<qemu:arg value='-device'/>
<qemu:arg value='nvme,bootindex=1,drive=nvdisk1,serial=1234,id=nvme0,bus=pcie.0,addr=0x04'/>
</qemu:commandline>
</domain>
"""
if __name__ == "__main__":
# Connect to qemu
conn: libvirt.virConnect = libvirt.open("qemu:///system")
uuid: str = "1e6cae9f-41d7-4fca-8033-fbd538a65173" # Replace with your (random?) UUID
vmObj: vm = vm(conn, uuid, debugPlt=False)
# Delete eventually existing VMs
if vmObj.try_load():
print(f"Deleting existing VM for UUID '{uuid}'...")
vmObj.destroy()
exit(0)
print(f"VM destroyed.")
else:
print(f"No existing VM found for UUID '{uuid}'.")
# Create and start a new VM
vmXml: str = get_vm_xml(
"test_vm_1",
"Test_VM_1",
uuid,
"<PATH_TO_THE_ISO_FILE_TO_BOOT_FROM>",
"test_vm_1.qcow2", # qemu-img create -f qcow2 test_vm_1.qcow2 8G
)
vmObj.create(vmXml)
# Load stages automation.
# We expect the `stages.yml` and referenced files inside the stages directory.
basePath: str = "stages"
stagesObj: stages = stages(basePath)
print(stagesObj)
vmObj.run_stages(stagesObj)
print("All stages done. Exiting...")
conn.close()
exit(0)
Stages
Stages are defined as a YAML file. The schema for it is available under stages_schema.yml.
The following shows an example of such a file:
stages:
- stage: Bootloader Selection
timeout_s: 15
paths:
- path:
checks:
- file:
path: 0.png
ssim_geq: 0.99
actions:
- keyboard_key:
value: up
duration_s: 0.25
- keyboard_key:
value: ret
duration_s: 0.25
nextStage: Installation Started
- path:
checks:
- file:
path: 0_1.png
ssim_geq: 0.99
actions:
- keyboard_key:
value: up
duration_s: 0.25
- keyboard_key:
value: up
duration_s: 0.25
- keyboard_key:
value: ret
duration_s: 0.25
nextStage: Installation Started
- stage: Installation Started
timeout_s: 600
paths:
- path:
checks:
- file:
path: 1.png
ssim_geq: 0.99
actions:
- keyboard_key:
value: up
duration_s: 0.25
nextStage: Installation Complete
- stage: Installation Complete
timeout_s: 600
paths:
- path:
checks:
- file:
path: 2.png
ssim_geq: 0.99
actions:
- keyboard_key:
value: tab
duration_s: 0.25
- keyboard_key:
value: tab
duration_s: 0.25
- keyboard_key:
value: ret
duration_s: 0.25
nextStage: Enter LUKS Password
- stage: Enter LUKS Password
timeout_s: 600
paths:
- path:
checks:
- file:
path: 3.png
ssim_geq: 0.99
actions:
- keyboard_text:
value: something
duration_s: 0.25
- keyboard_key:
value: ret
duration_s: 0.25
nextStage: None
Building the pip-Package
To build the pip package run:
rm -rf dist/
python3 -m build
The output is then available inside the dist/ directory.
Install Locally
python3 -m pip install --force-reinstall --no-deps dist/*.whl
Upload
twine upload dist/*
pre-commit
Before committing you have to run pre-commit to check for linting and type errors.
For this first install pre-commit.
dnf install pre-commit
pre-commit install
To run pre-commit manually run:
pre-commit run --all-files
Testing
To run the unit tests locally:
# System deps (needed to build libvirt-python)
dnf install libvirt-devel pkgconf-pkg-config
# Python deps
python3 -m pip install -r requirements.txt
python3 -m pip install pytest
# Run tests
pytest -q
On Debian/Ubuntu, replace the system deps line with:
sudo apt-get install libvirt-dev pkg-config
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file os_tester-1.2.0.tar.gz.
File metadata
- Download URL: os_tester-1.2.0.tar.gz
- Upload date:
- Size: 28.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3e17d7cddef907c4b34f665fff3be52a693a4bbcf463f3914031dc45d915377
|
|
| MD5 |
b8b70ce067eed755964b0a0f8e064d71
|
|
| BLAKE2b-256 |
5edf78077dd8a3d19e41534984da0d93ee998b3e8094c73080ca3b99bb390147
|
File details
Details for the file os_tester-1.2.0-py3-none-any.whl.
File metadata
- Download URL: os_tester-1.2.0-py3-none-any.whl
- Upload date:
- Size: 24.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3e178538e954985768d6bcbff190ed29e345df3cd3091345eab91ae1a44b7d2
|
|
| MD5 |
a2e3209be9c602c87c9d522af8eaddfc
|
|
| BLAKE2b-256 |
0f2157f86f2b85537a8327da8e755f4b65688123401d8a5d7dd27710d6977792
|