Files
pxe-server/playbook/shopfloor-setup/common/test/lib/qga.sh
cproudlock eaf2dbf167 test harness: smoke-pass B-enforce, fix four issues
Harness now passes 9/9 across baseline + heal + idempotent phases on the
win11 VM (Standard/Machine), with 6 drift scenarios applied + healed
between the baseline and heal cycles in ~30s total.

Fixes:

1. lib/qga-run.py - extracted the qga round-trip out of an inline
   `python3 - <<PY` heredoc. The inline form clobbered stdin (heredoc
   replaces stdin to feed python the script, leaving sys.stdin empty
   for the PowerShell snippet the function caller piped in).
2. lib/qga.sh - dropped `set -euo pipefail`. When sourced, it leaked
   into the harness shell. Then any captured `out=$(qga_run_ps ...)`
   that exited non-zero (verify-state.ps1 returns 1 on any FAIL,
   normal during drift phases) would silently abort the harness.
   Callers handle non-zero with `|| rc=$?`.
3. B-enforce/run.sh do_verify - rewritten to capture rc, parse summary
   line, distinguish expect_pass=true vs false, route to ok / fail
   helper without aborting the harness on a normal non-zero verify.
4. matrix.json WJF Defect Tracker entry - switched detection from File
   to Registry (uninstall key DisplayVersion). The MSI does not drop
   the Defect_Tracker.exe artifact at the documented path even though
   the manifest's File detection treats it as installed; the uninstall
   reg entry is the reliable install marker. v2 manifest's File
   detection path may also need fixing, separate task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:45:06 -04:00

102 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# qga.sh - host-side helpers for driving the win11 VM via qemu-guest-agent.
#
# Source this from harness scripts: `source "$(dirname "$0")/../lib/qga.sh"`
#
# All commands run as NT AUTHORITY\SYSTEM inside the VM (qemu-ga's service
# context). See reference-vm-qga-as-system memory note for why this is
# preferred over WinRM for dispatcher / manifest-engine tests.
#
# Sourced by harness scripts. Deliberately does NOT enable set -e because
# qga_run_ps returns non-zero whenever the inner PowerShell exits non-zero
# (expected during drift/verify phases), and a sourced set -e would silently
# abort the calling shell on every $(qga_run_ps ...) capture of a failing run.
# Callers that want strict mode should set it themselves AND wrap qga_run_ps
# captures with `|| rc=$?` so the non-zero exit does not propagate.
VM_DOMAIN="${VM_DOMAIN:-win11}"
VM_IP="${VM_IP:-192.168.122.225}"
HOST_SAMBA_USER="${HOST_SAMBA_USER:-camp}"
HOST_SAMBA_PASS="${HOST_SAMBA_PASS:-vos313}"
HOST_SAMBA_HOST="${HOST_SAMBA_HOST:-192.168.122.1}"
# Helper: fail loud
die() { echo "[ERROR] $*" >&2; exit 1; }
log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"; }
# Send a JSON command to qemu-ga, return parsed return field
qga() {
local payload="$1"
virsh -c qemu:///system qemu-agent-command "$VM_DOMAIN" "$payload" 2>/dev/null \
|| die "qga call failed: $payload"
}
# Run a PowerShell snippet inside the VM as SYSTEM. Stdin = snippet.
# Stdout = combined PS stdout, then optional "--- STDERR ---" block,
# then "--- exit N ---". Returns 0 iff the PS process returned 0.
#
# Implementation note: keep the python script in a sibling file rather
# than inlined via heredoc. An inline `python3 - <<PY ... PY` redirects
# stdin to the heredoc, which clobbers the PowerShell snippet the
# caller piped in.
QGA_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
qga_run_ps() {
python3 "$QGA_LIB_DIR/qga-run.py"
}
# Wait until qemu-ga responds to guest-ping
vm_wait_for_ready() {
local max=${1:-90}
local i=0
while ! virsh -c qemu:///system qemu-agent-command "$VM_DOMAIN" '{"execute":"guest-ping"}' >/dev/null 2>&1; do
((i++))
if (( i > max )); then die "VM never became ready ($max attempts)"; fi
sleep 1
done
}
# Make sure VM is up and qga is alive. Start if not.
vm_start_if_needed() {
local state
state=$(virsh -c qemu:///system domstate "$VM_DOMAIN" 2>/dev/null || echo unknown)
if [[ "$state" != "running" ]]; then
log "VM is $state - starting"
virsh -c qemu:///system start "$VM_DOMAIN" >/dev/null
fi
vm_wait_for_ready 90
}
# Mount the host samba share inside the VM as SYSTEM. Idempotent.
# After this call Z:\ inside the VM points at /home/camp/pxe-images/.
vm_mount_share() {
qga_run_ps <<PS
cmdkey /add:$HOST_SAMBA_HOST /user:$HOST_SAMBA_USER /pass:$HOST_SAMBA_PASS | Out-Null
net use Z: \\\\$HOST_SAMBA_HOST\\pxe-images /persistent:no 2>&1 | Out-Null
"Z reachable: \$(Test-Path 'Z:\\')"
PS
}
# Upload a single file from host to VM by reading it into VM via WriteAllBytes.
# Best for tiny files (config, single ps1). For bigger files use vm_mount_share + Copy-Item.
vm_put_file() {
local local_path="$1" remote_path="$2"
[[ -f "$local_path" ]] || die "no such file: $local_path"
local b64
b64=$(base64 -w0 < "$local_path")
qga_run_ps <<PS
\$bytes = [System.Convert]::FromBase64String('$b64')
\$dst = '$remote_path'
New-Item -ItemType Directory -Force -Path (Split-Path \$dst -Parent) -ErrorAction SilentlyContinue | Out-Null
[System.IO.File]::WriteAllBytes(\$dst, \$bytes)
"wrote \$dst (\$(\$bytes.Length) bytes)"
PS
}
# Revert the VM to the blank-slate snapshot, then start + wait.
vm_revert_to_blank_slate() {
log "reverting to blank-slate snapshot..."
sudo virsh -c qemu:///system snapshot-revert "$VM_DOMAIN" blank-slate \
|| die "snapshot-revert failed (need sudo without password, or run script as root)"
vm_start_if_needed
}