Skip to main content

Servicing Tests

Servicing tests validate multi-update workflows on pre-built VM images using storm-trident run servicing. Unlike E2E tests which use netlaunch and an installer ISO for initial provisioning, servicing tests start from a QCOW2 image that already has Trident and an OS installed, then run repeated A/B updates with optional rollback testing.

The VM images are defined in tests/images/trident-vm-testimage/ and built with Image Customizer from a qemu_guest base image downloaded from MCR.

VM Image Types​

The servicing scenario expects a QCOW2 image matching the pattern trident-vm-*-testimage.qcow2 in the artifacts directory. The pipeline-tested image types are:

ImageBootloaderIntegrityUKIConfig File
trident-vm-grub-verity-testimagegrub2Root verityNoupdateimg-grub-verity.yaml
trident-vm-usr-verity-testimagesystemd-boot/usr verityYesbaseimg-usr-verity.yaml

All image configs live under tests/images/trident-vm-testimage/base/. The base image is qemu_guest (see step 4 for how to obtain it).

trident-vm-grub-verity-testimage uses grub2 with root dm-verity. The root filesystem is read-only and integrity-protected, with /var on a separate partition and an /etc overlay service for runtime state. It uses the updateimg-grub-verity.yaml config which includes SSH access, network configuration, and sudoers for the test user.

trident-vm-usr-verity-testimage uses systemd-boot with a Unified Kernel Image (UKI) and /usr dm-verity. This is a preview feature (previewFeatures: uki) that requires ukify on the build host. It uses the baseimg-usr-verity.yaml config which defines the full runtime layout.

COSI Update Images​

During the update loop, the servicing scenario serves COSI files over HTTP using netlisten. It expects COSI files in two directories within the artifacts dir:

  • <artifacts-dir>/update-a/ — COSI image served on port 8000 (configurable via --update-port-a)
  • <artifacts-dir>/update-b/ — COSI image served on port 8001 (configurable via --update-port-b)

The update loop alternates between these two images across iterations.

Prerequisites​

  • Linux host with root access
  • libvirt and QEMU installed and configured
  • Docker (for building images with Image Customizer)
  • oras CLI (for downloading base images from MCR)
  • Go 1.24+ (for building Go tools)
  • Rust (latest stable, for building Trident)

See Dependencies for full build dependency details.

Building Dependencies​

1. Build Trident and RPMs​

make build
make bin/trident-rpms.tar.gz

2. Build Go Tools​

make bin/storm-trident
make bin/netlisten

3. Generate SSH Keys​

make artifacts/id_rsa

4. Download the qemu_guest Base Image​

The VM test images are built from a qemu_guest base image. Download the minimal-os image from MCR and rename it to qemu_guest.vhdx:

mkdir -p artifacts
oras pull mcr.microsoft.com/azurelinux/3.0/image/minimal-os:latest \
--output ./artifacts --platform linux/amd64
mv artifacts/image.vhdx artifacts/qemu_guest.vhdx

The minimal-os image is a publicly available Azure Linux 3.0 base image that works as a drop-in qemu_guest replacement. Image Customizer installs all required packages (including VirtIO drivers and SSH) during the VM image build step, so the base image's pre-installed package set does not matter.

Internal shortcut

If you have access to the internal Azure DevOps artifacts feed, the Makefile downloads qemu_guest automatically (requires az login). You can also download it directly: ./tests/images/testimages.py download-image qemu_guest

5. Build the VM Image​

Choose an image type and build the QCOW2:

# For grub with root verity:
make artifacts/trident-vm-grub-verity-testimage.qcow2

# For UKI with usr verity:
make artifacts/trident-vm-usr-verity-testimage.qcow2

6. Prepare Update Images​

The update loop needs two distinct COSI images — Trident rejects updates where the new image has the same filesystem UUIDs as the currently installed one. Use --clones 2 to build two images with unique UUIDs, then rename them to the same filename in each directory (the update loop picks the filename from update-a and applies it to both update configs):

mkdir -p artifacts/update-a artifacts/update-b

# Build two clones of the COSI image (produces _0 and _1 suffixed files)
sudo ./tests/images/testimages.py build trident-vm-grub-verity-testimage \
--output-dir ./artifacts --clones 2

# Move the clones into the update directories with the same filename
mv artifacts/trident-vm-grub-verity-testimage_0.cosi \
artifacts/update-a/trident-vm-grub-verity-testimage.cosi
mv artifacts/trident-vm-grub-verity-testimage_1.cosi \
artifacts/update-b/trident-vm-grub-verity-testimage.cosi
note

Both the QCOW2 and COSI images must be built from the same base image. Trident validates that the COSI's VARIANT_ID in /etc/os-release matches the host's.

Running the Servicing Scenario​

The servicing scenario requires root access for VM creation via virt-install:

sudo bin/storm-trident run servicing -- \
--artifacts-dir ./artifacts \
--output-path /tmp/servicing-output \
--platform qemu \
--ssh-private-key-path ./artifacts/id_rsa \
--verbose

Test Cases​

The servicing scenario runs these test cases in order:

  1. publish-sig-image — Publishes the image to Azure SIG (skipped for QEMU)
  2. deploy-vm — Finds a trident-vm-*-testimage.qcow2 in artifacts, copies it to booted.qcow2, and creates a QEMU VM
  3. check-deployment — Verifies the VM booted and is accessible via SSH
  4. update-loop — Runs repeated A/B updates: starts netlisten servers on ports 8000/8001 to serve COSI images, SSHes into the VM, edits the Host Configuration, and triggers trident grpc-client update with stage then finalize operations
  5. rollback — Tests rollback after update (only when --rollback is enabled)
  6. collect-logs — Fetches Trident logs from the VM via SSH
  7. cleanup-vm — Destroys the QEMU VM

Flags​

FlagDescriptionDefault
--artifacts-dirDirectory containing VM images and COSI files/tmp
--output-pathOutput directory for logs./output
--platformqemu or azureqemu
--ssh-private-key-pathPath to SSH private key~/.ssh/id_rsa
--userSSH user on the VMtestuser
--rollbackEnable rollback testingfalse
--retry-countNumber of update retry attempts3
--update-port-aPort for first update server8000
--update-port-bPort for second update server8001
--verboseEnable verbose loggingfalse
--force-cleanupForce VM cleanup on exitfalse
--test-case-to-runRun a specific test case onlyall
--expected-volumeExpected active volume after updatevolume-a
--rollback-retry-countNumber of rollback retry attempts3