os-tester


Nameos-tester JSON
Version 0.3.0 PyPI version JSON
download
home_pageNone
SummaryA Python pip package to automate testing of whole operating systems with an image recognition based approach and libvirt (qemu). Inspired by openQA.
upload_time2024-08-05 08:22:00
maintainerNone
docs_urlNone
authorNone
requires_python>=3.7
licenseGNU General Public License v3 (GPLv3)
keywords testing os qemu libvirt
VCS
bugtrack_url
requirements scikit-image opencv-python-headless libvirt-python PyYAML numpy matplotlib
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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](examples/example.png)

```python
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`](stages_schema.yml).
The following shows an example of such a file:
```yaml
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:
```bash
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`.

```bash
dnf install pre-commit
pre-commit install
```

To run `pre-commit` manually run:
```bash
pre-commit run --all-files
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "os-tester",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "Fabian Sauter <fabian.sauter+pip@apsensing.com>",
    "keywords": "testing, os, qemu, libvirt",
    "author": null,
    "author_email": "Fabian Sauter <fabian.sauter+pip@apsensing.com>",
    "download_url": "https://files.pythonhosted.org/packages/ae/37/4f9c2dc99fbb0b2560f687bde627ef9cf969c0532a5ef896049401ffdcb6/os_tester-0.3.0.tar.gz",
    "platform": null,
    "description": "# OS Tester\nA Python pip package to automate testing of whole operating systems with an image recognition based approach and libvirt (qemu). Inspired by openQA.\n\n## Example\n\n![example_when_debug_is_enabled](examples/example.png)\n\n```python\nfrom os_tester.vm import vm\nfrom os_tester.stages import stages\nimport libvirt\n\n# SELinux Policy for allowing Qemu to access image files:\n# ausearch -c 'qemu-system-x86' --raw | audit2allow -M my-qemusystemx86\n# semodule -X 300 -i my-qemusystemx86.pp\n\n# NVME: http://blog.frankenmichl.de/2018/02/13/add-nvme-device-to-vm/\n# dd if=/dev/zero of=/tmp/test_vm_1.img bs=1M count=8192\n# Or:\n# qemu-img create -f qcow2 /tmp/test_vm_1.qcow2 8G\n\n\ndef get_vm_xml(name: str, title: str, uuid: str, isoPath: str, vmImagePath: str) -> str:\n    ramGiB: int = 2\n    numCpus: int = 2\n\n    return f\"\"\"\n<domain type=\"kvm\" xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>\n  <name>{name}</name>\n  <uuid>{uuid}</uuid>\n  <title>{title}</title>\n  <memory unit=\"GiB\">{ramGiB}</memory>\n  <currentMemory unit=\"GiB\">{ramGiB}</currentMemory>\n  <vcpu placement=\"static\">{numCpus}</vcpu>\n  <os firmware='efi'>\n  <!--To get a list of all machines: qemu-system-x86_64 -machine help-->\n    <type arch=\"x86_64\" machine=\"q35\">hvm</type>\n    <bootmenu enable=\"yes\"/>\n    <boot dev=\"hd\"/>\n    <boot dev=\"cdrom\"/>\n  </os>\n  <features>\n    <acpi/>\n    <apic/>\n  </features>\n  <clock offset=\"localtime\">\n    <timer name=\"rtc\" tickpolicy=\"catchup\"/>\n    <timer name=\"pit\" tickpolicy=\"delay\"/>\n    <timer name=\"hpet\" present=\"no\"/>\n  </clock>\n  <on_poweroff>destroy</on_poweroff>\n  <on_reboot>restart</on_reboot>\n  <on_crash>restart</on_crash>\n  <pm>\n    <suspend-to-mem enabled=\"no\"/>\n    <suspend-to-disk enabled=\"no\"/>\n  </pm>\n  <devices>\n    <emulator>/usr/bin/qemu-system-x86_64</emulator>\n    <controller type='pci' index='0' model='pcie-root'/>\n    <controller type='pci' index='1' model='pcie-root-port'>\n      <model name='pcie-root-port'/>\n      <target chassis='1' port='0x10'/>\n      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>\n    </controller>\n    <disk type=\"file\" device=\"cdrom\">\n      <driver name=\"qemu\" type=\"raw\"/>\n      <source file=\"{isoPath}\" startupPolicy=\"mandatory\"/>\n      <target dev=\"hdc\" bus=\"sata\"/>\n      <readonly/>\n      <address type=\"drive\" controller=\"0\" bus=\"0\" target=\"0\" unit=\"2\"/>\n    </disk>\n    <interface type=\"user\">\n      <mac address=\"52:54:00:8d:ce:97\"/>\n      <model type=\"virtio\"/>\n      <address type=\"pci\" domain=\"0x0000\" bus=\"0x01\" slot=\"0x00\" function=\"0x00\"/>\n    </interface>\n    <serial type=\"pty\">\n      <target type=\"isa-serial\" port=\"0\">\n        <model name=\"isa-serial\"/>\n      </target>\n    </serial>\n    <console type=\"pty\">\n      <target type=\"serial\" port=\"0\"/>\n    </console>\n    <input type=\"tablet\" bus=\"usb\">\n      <address type=\"usb\" bus=\"0\" port=\"2\"/>\n    </input>\n    <input type=\"mouse\" bus=\"ps2\"/>\n    <input type=\"keyboard\" bus=\"ps2\"/>\n    <memballoon model=\"virtio\">\n      <address type=\"pci\" domain=\"0x0000\" bus=\"0x05\" slot=\"0x00\" function=\"0x0\"/>\n    </memballoon>\n  </devices>\n  <qemu:commandline>\n\t  <qemu:arg value='-drive'/>\n\t  <qemu:arg value='file={vmImagePath},format=qcow2,if=none,id=nvdisk1,media=disk'/>\n\t  <qemu:arg value='-device'/>\n\t  <qemu:arg value='nvme,bootindex=1,drive=nvdisk1,serial=1234,id=nvme0,bus=pcie.0,addr=0x04'/>\n  </qemu:commandline>\n</domain>\n    \"\"\"\n\nif __name__ == \"__main__\":\n    # Connect to qemu\n    conn: libvirt.virConnect = libvirt.open(\"qemu:///system\")\n\n    uuid: str = \"1e6cae9f-41d7-4fca-8033-fbd538a65173\" # Replace with your (random?) UUID\n    vmObj: vm = vm(conn, uuid, debugPlt=False)\n\n    # Delete eventually existing VMs\n    if vmObj.try_load():\n        print(f\"Deleting existing VM for UUID '{uuid}'...\")\n        vmObj.destroy()\n        exit(0)\n        print(f\"VM destroyed.\")\n    else:\n        print(f\"No existing VM found for UUID '{uuid}'.\")\n\n    # Create and start a new VM\n    vmXml: str = get_vm_xml(\n        \"test_vm_1\",\n        \"Test_VM_1\",\n        uuid,\n        \"<PATH_TO_THE_ISO_FILE_TO_BOOT_FROM>\",\n        \"test_vm_1.qcow2\", # qemu-img create -f qcow2 test_vm_1.qcow2 8G\n    )\n    vmObj.create(vmXml)\n\n    # Load stages automation.\n    # We expect the `stages.yml` and referenced files inside the stages directory.\n    basePath: str = \"stages\"\n    stagesObj: stages = stages(basePath)\n    print(stagesObj)\n    \n    vmObj.run_stages(stagesObj)\n\n    print(\"All stages done. Exiting...\")\n    conn.close()\n    exit(0)\n```\n\n### Stages\nStages are defined as a YAML file. The schema for it is available under [`stages_schema.yml`](stages_schema.yml).\nThe following shows an example of such a file:\n```yaml\nstages:\n  - stage: Bootloader Selection\n    timeout_s: 15\n    paths:\n      - path:\n          check:\n            file: 0.png\n            mse_leq: 0.1\n            ssim_geq: 0.99\n          actions:\n            - keyboard_key:\n                value: up\n                duration_s: 0.25\n            - keyboard_key:\n                value: ret\n                duration_s: 0.25\n          nextStage: Installation Started\n      - path:\n          check:\n            file: 0_1.png\n            mse_leq: 0.1\n            ssim_geq: 0.99\n          actions:\n            - keyboard_key:\n                value: up\n                duration_s: 0.25\n            - keyboard_key:\n                value: up\n                duration_s: 0.25\n            - keyboard_key:\n                value: ret\n                duration_s: 0.25\n          nextStage: Installation Started\n\n  - stage: Installation Started\n    timeout_s: 600\n    paths:\n      - path:\n          check:\n            file: 1.png\n            mse_leq: 0.1\n            ssim_geq: 0.99\n          actions:\n            - keyboard_key:\n                value: up\n                duration_s: 0.25\n          nextStage: Installation Complete\n\n  - stage: Installation Complete\n    timeout_s: 600\n    paths:\n      - path:\n          check:\n            file: 2.png\n            mse_leq: 0.1\n            ssim_geq: 0.99\n          actions:\n            - keyboard_key:\n                value: tab\n                duration_s: 0.25\n            - keyboard_key:\n                value: tab\n                duration_s: 0.25\n            - keyboard_key:\n                value: ret\n                duration_s: 0.25\n          nextStage: Enter LUKS Password\n\n  - stage: Enter LUKS Password\n    timeout_s: 600\n    paths:\n      - path:\n          check:\n            file: 3.png\n            mse_leq: 0.1\n            ssim_geq: 0.99\n          actions:\n            - keyboard_text:\n                value: something\n                duration_s: 0.25\n            - keyboard_key:\n                value: ret\n                duration_s: 0.25\n          nextStage: None\n\n```\n\n## Building the pip-Package\n\nTo build the pip package run:\n```bash\npython3 -m build\n```\nThe output is then available inside the `dist/` directory.\n\n## pre-commit\nBefore committing you have to run `pre-commit` to check for linting and type errors.\nFor this first install `pre-commit`.\n\n```bash\ndnf install pre-commit\npre-commit install\n```\n\nTo run `pre-commit` manually run:\n```bash\npre-commit run --all-files\n```\n",
    "bugtrack_url": null,
    "license": "GNU General Public License v3 (GPLv3)",
    "summary": "A Python pip package to automate testing of whole operating systems with an image recognition based approach and libvirt (qemu). Inspired by openQA.",
    "version": "0.3.0",
    "project_urls": {
        "Homepage": "https://github.com/AP-Sensing/os-tester",
        "Issues": "https://github.com/AP-Sensing/os-tester/issues",
        "Repository": "https://github.com/AP-Sensing/os-tester"
    },
    "split_keywords": [
        "testing",
        " os",
        " qemu",
        " libvirt"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6a98d36fb349d5984e54adec31675d2567190cd8fd10b5174302b881a93967fa",
                "md5": "4ea2d6d78f203db7199df7f83804ac80",
                "sha256": "0f939af09f7cf3fedbf0a1a78175db62c011bc80484ada764e284f6cfd9ef13a"
            },
            "downloads": -1,
            "filename": "os_tester-0.3.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4ea2d6d78f203db7199df7f83804ac80",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 22420,
            "upload_time": "2024-08-05T08:21:59",
            "upload_time_iso_8601": "2024-08-05T08:21:59.292308Z",
            "url": "https://files.pythonhosted.org/packages/6a/98/d36fb349d5984e54adec31675d2567190cd8fd10b5174302b881a93967fa/os_tester-0.3.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ae374f9c2dc99fbb0b2560f687bde627ef9cf969c0532a5ef896049401ffdcb6",
                "md5": "7ca64aa1360a891299ca2a26b814644d",
                "sha256": "d016a6c29b2335c5a2dc28dbd9d459629fcf43e304eb8c7871581ab916bd1f87"
            },
            "downloads": -1,
            "filename": "os_tester-0.3.0.tar.gz",
            "has_sig": false,
            "md5_digest": "7ca64aa1360a891299ca2a26b814644d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 24971,
            "upload_time": "2024-08-05T08:22:00",
            "upload_time_iso_8601": "2024-08-05T08:22:00.425630Z",
            "url": "https://files.pythonhosted.org/packages/ae/37/4f9c2dc99fbb0b2560f687bde627ef9cf969c0532a5ef896049401ffdcb6/os_tester-0.3.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-08-05 08:22:00",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "AP-Sensing",
    "github_project": "os-tester",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "scikit-image",
            "specs": [
                [
                    "==",
                    "0.24.0"
                ]
            ]
        },
        {
            "name": "opencv-python-headless",
            "specs": []
        },
        {
            "name": "libvirt-python",
            "specs": []
        },
        {
            "name": "PyYAML",
            "specs": []
        },
        {
            "name": "numpy",
            "specs": []
        },
        {
            "name": "matplotlib",
            "specs": []
        }
    ],
    "lcname": "os-tester"
}
        
Elapsed time: 0.29558s