Using KVM to create a test execution environment with a set of worker/guest VMs to execute tests using ansible playbooks and pytest. The tests are downloaded to the guest VMs via git clone. The generated test logs are copied back to the KVM host.
Here is our test setup.
- Our localhost is the KVM host
- Create a set of VMs as templates that has ubuntu OS installed
- Clone a new set of worker/guest VMs from the template
- Add default "kenneth" to allow sudo access
- Add SSH support so ansible can access the guest VMs
- Locate IP address of guest VMs and update inventory/inventory.ini file
- Set up ssh-key on KVM host and upload them to guest VMs for remote access without passwd
- Clone a set of guest VMS using the templates
- Enable authorized_keys for target VMs
- Install a new OS manually
- Clone the new VM with fully installed OS for safe keep
- Login to new VM used as template to find the IP address
- Used the clone VM to clone a new VM for project
- Update the hostname for the new VM
- Login to new VM to find the IP address
- Update inventory/inventory.ini
- Authenticate the guest host by login and answer yes
- Enable authorized_keys for target hosts
- Playbook Execution
- Ansible Playbook Execution
- Perform an one time manual operation to install the OS
- Use virt-clone to save copies of template VMs to prevent them from accidential modification
sudo virt-install \
--virt-type=kvm \
--name kvm-master-a1-1-u2004-6 \
--memory 10240 \
--vcpus=8 \
--os-variant ubuntu20.04 \
--hvm \
--cdrom /home/kenneth-wong/Downloads/ISO/ubuntu-20.04.6-desktop-amd64.iso \
--network=bridge=bridge0,model=virtio \
--graphics spice,listen=127.0.0.1 \
--video qxl \
--disk size=100,path=/var/lib/libvirt/images/kvm-master-a1-1-u2004-6.qcow2,bus=virtio,format=qcow2 \
--channel unix,target_type=virtio,name=org.qemu.guest_agent.0 \
--channel spicevmcsudo virt-install \
--virt-type=kvm \
--name kvm-master-a1-2-u2204-5 \
--memory 10240 \
--vcpus=8 \
--os-variant ubuntu22.04 \
--hvm \
--cdrom /home/kenneth-wong/Downloads/ISO/ubuntu-22.04.5-desktop-amd64.iso \
--network=bridge=bridge0,model=virtio \
--graphics spice,listen=127.0.0.1 \
--video qxl \
--disk size=100,path=/var/lib/libvirt/images/kvm-master-a1-2-u2204-5.qcow2,bus=virtio,format=qcow2 \
--channel unix,target_type=virtio,name=org.qemu.guest_agent.0 \
--channel spicevmcsudo virt-install \
--virt-type=kvm \
--name kvm-master-a1-3-u2404-1 \
--memory 10240 \
--vcpus=8 \
--os-variant ubuntu24.04 \
--hvm \
--cdrom /home/kenneth-wong/Downloads/ISO/ubuntu-24.04-1-desktop-amd64.iso \
--network=bridge=bridge0,model=virtio \
--graphics spice,listen=127.0.0.1 \
--video qxl \
--disk size=100,path=/var/lib/libvirt/images/kvm-master-a1-3-u2404.1.qcow2,bus=virtio,format=qcow2 \
--channel unix,target_type=virtio,name=org.qemu.guest_agent.0 \
--channel spicevmcsudo virt-install \
--virt-type=kvm \
--name kvm-master-a1-4-u2410 \
--memory 10240 \
--vcpus=8 \
--os-variant ubuntu24.04 \
--hvm \
--cdrom /home/kenneth-wong/Downloads/ISO/ubuntu-24.10-desktop-amd64.iso \
--network=bridge=bridge0,model=virtio \
--graphics spice,listen=127.0.0.1 \
--video qxl \
--disk size=100,path=/var/lib/libvirt/images/kvm-master-a1-4-u2410.qcow2,bus=virtio,format=qcow2 \
--channel unix,target_type=virtio,name=org.qemu.guest_agent.0 \
--channel spicevmcsudo virt-clone --original kvm-master-a1-1-u2004-6 --name kvm-master-a1-1-u2004-6-clone --file /var/lib/libvirt/images/kvm-master-a1-1-u2004-6-clone.qcow2
sudo virt-clone --original kvm-master-a1-2-u2204-5 --name kvm-master-a1-2-u2204-5-clone --file /var/lib/libvirt/images/kvm-master-a1-2-u2204-5-clone.qcow2
sudo virt-clone --original kvm-master-a1-3-u2404-1 --name kvm-master-a1-3-u2404-1-clone --file /var/lib/libvirt/images/kvm-master-a1-3-u2404-1-clone.qcow2
sudo virt-clone --original kvm-master-a1-4-u2410 --name kvm-master-a1-4-u2410-clone --file /var/lib/libvirt/images/kvm-master-a1-4-u2410-clone.qcow2sudo virt-sysprep -d kvm-master-a1-1-u2004-6-clone --hostname kvm-master-a1-1-u2004-6-clone
sudo virt-sysprep -d kvm-master-a1-2-u2204-5-clone --hostname kvm-master-a1-2-u2204-5-clone
sudo virt-sysprep -d kvm-master-a1-3-u2404-1-clone --hostname kvm-master-a1-3-u2404-1-clone
sudo virt-sysprep -d kvm-master-a1-4-u2410-clone --hostname kvm-master-a1-4-u2410-clonekvm-master-a1-1-u2004-6-clone kvm-master-a1-2-u2204-5-clone kvm-master-a1-3-u2404-1-clone kvm-master-a1-4-u2410-clone
sudo visudo
kenneth ALL=(ALL) NOPASSWD: ALLInstall openssh-server (guest vm)
sudo apt update && sudo apt upgrade -y
sudo apt install -y openssh-serversudo systemctl status ssh
sudo systemctl enable ssh
sudo systemctl start ssh
sudo systemctl status sship assh-keygen -t ed25519 -C "Kenneth Wong"
# paraphase: 1....ssh-keygen -t ed25519 -C "ansible"
# paraphase:ssh-copy-id -f -i /home/kenneth/.ssh/id_ed25519_kenneth.pub kenneth@192.168.1.XXX
ssh-copy-id -f -i /home/kenneth/.ssh/id_ed25519_kenneth.pub kenneth@192.168.1.XXX
ssh-copy-id -f -i /home/kenneth/.ssh/id_ed25519_kenneth.pub kenneth@192.168.1.XXX
ssh-copy-id -f -i /home/kenneth/.ssh/id_ed25519_kenneth.pub kenneth@192.168.1.XXXUp to this point all operations performed on the VMs templates. It has a set of manual operations to configure the VMs templates.
``bash sudo virt-clone --original kvm-master-a1-1-u2004-6-clone --name kvm-a1-1-u2004-6 --file /var/lib/libvirt/images/kvm-a1-1-u2004-6.qcow2 sudo virt-clone --original kvm-master-a1-2-u2204-5-clone --name kvm-a1-2-u2204-5 --file /var/lib/libvirt/images/kvm-a1-2-u2204-5.qcow2 sudo virt-clone --original kvm-master-a1-3-u2404-1-clone --name kvm-a1-3-u2404-1 --file /var/lib/libvirt/images/kvm-a1-3-u2404-1.qcow2 sudo virt-clone --original kvm-master-a1-4-u2410-clone --name kvm-a1-4-u2410 --file /var/lib/libvirt/images/kvm-a1-4-u2410.qcow2
sudo virt-clone --original kvm-master-a1-1-u2004-6-clone --name kvm-a-big-u2004-6 --file /var/lib/libvirt/images/kvm-a-big-u2004-6.qcow2 sudo virt-clone --original kvm-master-a1-2-u2204-5-clone --name kvm-a-big-u2204-5 --file /var/lib/libvirt/images/kvm-a-big-u2204-5.qcow2 sudo virt-clone --original kvm-master-a1-3-u2404-1-clone --name kvm-a-big-u2404-1 --file /var/lib/libvirt/images/kvm-a-big-u2404-1.qcow2 sudo virt-clone --original kvm-master-a1-4-u2410-clone --name kvm-a-big-u2410 --file /var/lib/libvirt/images/kvm-a-big-u2410.qcow2
-------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------
### Update the hostname for the new VM (#virt-sysprep | kvm_host)
``bash
sudo virt-sysprep -d kvm-a1-1-u2004-6 --hostname kvm-a1-1-u2004-6 --operations defaults,-ssh-hostkeys,-ssh-userdir
sudo virt-sysprep -d kvm-a1-2-u2204-5 --hostname kvm-a2-2-u2204-5 --operations defaults,-ssh-hostkeys,-ssh-userdir
sudo virt-sysprep -d kvm-a1-3-u2404-1 --hostname kvm-a1-3-u2404-1 --operations defaults,-ssh-hostkeys,-ssh-userdir
sudo virt-sysprep -d kvm-a1-4-u2410 --hostname kvm-a1-4-u2410 --operations defaults,-ssh-hostkeys,-ssh-userdir
sudo virt-sysprep -d kvm-a-big-u2004-6 --hostname kvm-a-big-u2004-6 --operations defaults,-ssh-hostkeys,-ssh-userdir
sudo virt-sysprep -d kvm-a-big-u2204-5 --hostname kvm-a-big-u2204-5 --operations defaults,-ssh-hostkeys,-ssh-userdir
sudo virt-sysprep -d kvm-a-big-u2404-1 --hostname kvm-a-big-u2404-1 --operations defaults,-ssh-hostkeys,-ssh-userdir
sudo virt-sysprep -d kvm-a-big-u2410 --hostname kvm-a-big-u2410 --operations defaults,-ssh-hostkeys,-ssh-userdir
ip a``bash [kvm_guests_group1] kvm-a1-2-u2204-5 ansible_host=192.168.1.57 kvm-a1-3-u2404-1 ansible_host=192.168.1.58 kvm-a1-4-u2410 ansible_host=192.168.1.59
[kvm_guests:children] kvm_guests_group1
[kvm_guests_a_big_group] kvm-a-big-u2204-5 ansible_host=192.168.1.14 kvm-a-big-u2404-1 ansible_host=192.168.1.15 kvm-a-big-u2410 ansible_host=192.168.1.17
--------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------
## Authenticate the guest host by login and answer yes
--------------------------------------------------------------------------------------------------
``bash
ssh kenneth@kvm-a1-2-u2204-5.local
ssh kenneth@kvm-a1-3-u2404-1.local
ssh kenneth@kvm-a1-4-u2410.local
ssh kenneth@kvm-a-big-u2204-5.local
ssh kenneth@kvm-a-big-u2404-1.local
ssh kenneth@kvm-a-big-u2410.local
``bash ssh-keygen -f '/home/kenneth/.ssh/known_hosts' -R 'kvm-a1-2-u2204-5.local' ssh-keygen -f '/home/kenneth/.ssh/known_hosts' -R 'kvm-a1-3-u2404-1.local' ssh-keygen -f '/home/kenneth/.ssh/known_hosts' -R 'kvm-a1-4-u2410.local'
ssh-keygen -f '/home/kenneth/.ssh/known_hosts' -R 'kvm-a-big-u2204-5.local' ssh-keygen -f '/home/kenneth/.ssh/known_hosts' -R 'kvm-a-big-u2404-1.local' ssh-keygen -f '/home/kenneth/.ssh/known_hosts' -R 'kvm-a-big-u2410.local'
--------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------
## Playbook Execution (kvm_host)
--------------------------------------------------------------------------------------------------
```bash
ssh kenneth@kvm-master-a1-1-u2410.local
ssh kenneth@kvm-a1-1-u2410.local
ansible kvm_guests_group1 -m ping
ansible kvm_guests_a_big_group -m ping
ansible all -m ping
ansible all -m debug -a "var=ansible_host"
time ansible-playbook kvm_guest_vms.yml
This playbook manages KVM guest VMs.
- Standard run:
time ansible-playbook kvm_guest_vms.yml- Run for specific target VMs:
time ansible-playbook kvm_guest_vms.yml -e "target_vm=['kvm-a1-4-u2410']"
time ansible-playbook kvm_guest_vms.yml -e "target_vm=['kvm-a2-4-u2410']"
time ansible-playbook kvm_guest_vms.yml -e "target_vm=['kvm-a3-4-u2410']"- Control whether to include testme tests:
time ansible-playbook kvm_guest_vms.yml -e "include_testme=true"
time ansible-playbook kvm_guest_vms.yml -e "include_testme=false"- Combine multiple options with verbosity:
time ansible-playbook kvm_guest_vms.yml -v -e "target_vm=['kvm-a1-4-u2410']" -e "include_testme=true"
time ansible-playbook kvm_guest_vms.yml -v -e "target_vm=['kvm-a2-4-u2410']" -e "include_testme=true"
time ansible-playbook kvm_guest_vms.yml -v -e "target_vm=['kvm-a3-4-u2410']" -e "include_testme=true"
time ansible-playbook kvm_guest_vms.yml -v -e "target_vm=['kvm-a-big-u2204-5']" -e "include_testme=true"
time ansible-playbook kvm_guest_vms.yml -v -e "target_vm=['kvm-a-big-u2404-1']" -e "include_testme=true"
time ansible-playbook kvm_guest_vms.yml -v -e "target_vm=['kvm-a-big-u2410']" -e "include_testme=true"At this point all the guest VMs are configured with required packages to run test
This playbook manages Python environments and runs tests on guest VMs.
- Standard run (delete repos, use default Python version for OS):
time ansible-playbook load_uvprog2025.yml -v- Run without deleting repositories:
time ansible-playbook load_uvprog2025.yml -v -e "delete_repo=false"- Run with a specific Python version:
time ansible-playbook load_uvprog2025.yml -v -e "python_version_override=3.11"
time ansible-playbook load_uvprog2025.yml -v -e "python_version_override=3.12"- Run for specific target VMs:
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a1-4-u2410']"- Control whether to include testme tests:
time ansible-playbook load_uvprog2025.yml -v -e "include_testme=true"
time ansible-playbook load_uvprog2025.yml -v -e "include_testme=false"- Combine multiple options:
--------------------------------------------------------------------------------------------------
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a1-4-u2410']" -e "include_testme=true" -e "delete_repo=true" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a1-4-u2410']" -e "include_testme=true" -e "delete_repo=false" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a1-4-u2410']" -e "include_testme=false" -e "delete_repo=true" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a1-4-u2410']" -e "include_testme=false" -e "delete_repo=false" -e "python_version_override=3.12"
--------------------------------------------------------------------------------------------------
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a-big-u2410']" -e "include_testme=true" -e "delete_repo=true" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a-big-u2410']" -e "include_testme=true" -e "delete_repo=false" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a-big-u2410']" -e "include_testme=false" -e "delete_repo=true" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a-big-u2410']" -e "include_testme=false" -e "delete_repo=false" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "include_testme=true" -e "delete_repo=true" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "include_testme=true" -e "delete_repo=false" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "include_testme=false" -e "delete_repo=true" -e "python_version_override=3.12"
time ansible-playbook load_uvprog2025.yml -v -e "include_testme=false" -e "delete_repo=false" -e "python_version_override=3.12"This role processes pytest results, formats XML output, and generates consolidated reports across multiple VMs.
- Extracts test information from pytest output logs
- Parses JUnit XML test results
- Formats XML test results for improved readability
- Generates enhanced test reports in multiple formats (MD, JSON, CSV)
- Consolidates reports from multiple VMs into a single document
- Maintains a version history of reports
To run specific parts of the role, use the Ansible --tags parameter:
| Tag | Description |
|---|---|
load |
Run the load_uvprog2025 role (includes test execution) |
format |
Format XML test results |
xml |
Same as format |
generate |
Generate test reports |
reports |
Generate and consolidate test reports |
consolidate |
Consolidate reports into a single document |
gather |
Extract test information from logs |
load,generate,reports: Run tests, generate and consolidate reportsgenerate,reports: Only generate reports from existing test resultsformat,generate,reports: Format XML, generate and consolidate reports
| Option | Description | Default | Possible Values |
|---------------------------|-------------------------------------|---------|-------------------------------------------- -|
| delete_repo | Delete existing repositories | true | true, false |
| python_version_override | Override default Python version | (empty) | 3.11, 3.12 |
| target_vm | List of VMs to target | (all VMs) | List of VM names, e.g. ['kvm-a1-4-u2410'] |
| include_testme | Include tests marked with 'testme' | true | true, false |
The playbooks include intelligent concurrency management:
- When
target_vmis not specified: Tasks run on multiple VMs concurrently (up to 4 VMs at once) - When
include_testme=true: Tasks run testme tag
This behavior allows for faster execution when targeting specific VMs or running testme tests, while providing more controlled execution for standard test runs.
- 3.11 (default for Ubuntu 20.04/22.04)
- 3.12 (default for Ubuntu 24.04/24.10)
- The
delete_repoandpython_version_overrideoptions are primarily for theload_uvprog2025.ymlplaybook - Both playbooks support targeting specific VMs and controlling testme test inclusion
- Python 3.10 may be available on some systems with the deadsnakes PPA but is not installed by default
- Using the
timecommand before the playbook helps track execution duration - The verbosity flag (
-v) provides more detailed output during execution
# Run the whole process (run tests, generate reports, consolidate)
time ansible-playbook load_uvprog2025.yml -v -e "include_testme=true" -e "delete_repo=false" -e "python_version_override=3.12"
# Only generate and consolidate reports from existing test results
time ansible-playbook load_uvprog2025.yml -v --tags "generate,reports"
# Format XML and generate reports without running tests
time ansible-playbook load_uvprog2025.yml -v --tags "format,generate,reports"
# Target specific VMs
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a-big-u2410']"
time ansible-playbook load_uvprog2025.yml -v -e "target_vm=['kvm-a-big-u2410']" -e "include_testme=false"- Python 3
- python3-lxml package (will be installed by the role if not present)
- Ansible community.general collection (for the xml module)
Available variables are listed below, along with default values (see defaults/main.yml):
# Project directory name
uvprog: "uvprog2025"
# Report configuration
reports_dir: "{{ playbook_dir }}/test_reports"
reports_history_dir: "{{ reports_dir }}/history"
consolidated_report_path: "{{ playbook_dir }}/consolidated_report.md"
report_formats:
- md
- json
- csv
# XML formatting configuration
format_xml: true # Set to false to disable XML formatting
xml_backup: true # Set to false to skip creating backup of original XML