Skip to main content

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

example_when_debug_is_enabled

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:
          check:
            file: 0.png
            mse_leq: 0.1
            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:
          check:
            file: 0_1.png
            mse_leq: 0.1
            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:
          check:
            file: 1.png
            mse_leq: 0.1
            ssim_geq: 0.99
          actions:
            - keyboard_key:
                value: up
                duration_s: 0.25
          nextStage: Installation Complete

  - stage: Installation Complete
    timeout_s: 600
    paths:
      - path:
          check:
            file: 2.png
            mse_leq: 0.1
            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:
          check:
            file: 3.png
            mse_leq: 0.1
            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:

python3 -m build

The output is then available inside the dist/ directory.

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

os_tester-0.3.0.tar.gz (25.0 kB view details)

Uploaded Source

Built Distribution

os_tester-0.3.0-py3-none-any.whl (22.4 kB view details)

Uploaded Python 3

File details

Details for the file os_tester-0.3.0.tar.gz.

File metadata

  • Download URL: os_tester-0.3.0.tar.gz
  • Upload date:
  • Size: 25.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.4

File hashes

Hashes for os_tester-0.3.0.tar.gz
Algorithm Hash digest
SHA256 d016a6c29b2335c5a2dc28dbd9d459629fcf43e304eb8c7871581ab916bd1f87
MD5 7ca64aa1360a891299ca2a26b814644d
BLAKE2b-256 ae374f9c2dc99fbb0b2560f687bde627ef9cf969c0532a5ef896049401ffdcb6

See more details on using hashes here.

File details

Details for the file os_tester-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: os_tester-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 22.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.4

File hashes

Hashes for os_tester-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0f939af09f7cf3fedbf0a1a78175db62c011bc80484ada764e284f6cfd9ef13a
MD5 4ea2d6d78f203db7199df7f83804ac80
BLAKE2b-256 6a98d36fb349d5984e54adec31675d2567190cd8fd10b5174302b881a93967fa

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page