Files
harmony/examples/iot_vm_setup
Jean-Gabriel Gill-Couture 92150da12a
All checks were successful
Run Check Script / check (pull_request) Successful in 2m23s
feat(iot): label-selector targeting (replace target_devices with targetSelector)
DeploymentSpec.target_devices (flat string list) is gone. In its
place, DeploymentSpec.target_selector is a minimal
LabelSelector-shaped struct (matchLabels only for now, matchExpressions
deferred until there's a real need). Devices publish a labels map
in every AgentStatus heartbeat; operator resolves the selector
against the current fleet snapshot on each reconcile + aggregator
tick.

No legacy shim — the CRD is v1alpha1 and not yet deployed in the wild.

Aggregator consequences:
  - controller and aggregator now share a StatusSnapshots map so
    selector resolution sees the same data on both sides.
  - unreported is dropped: a device that has never heartbeated is
    invisible to the selector machinery, so the field no longer
    has clean semantics. "device went dark" can come back as a
    staleness metric later if needed.
  - controller's MissingTargets error is gone: zero matches is a
    legitimate state (devices may not have joined yet). The
    controller logs and fast-requeues (15s/30s) so a just-joining
    device picks the deployment up without needing a
    cross-task subscription.

Agent + setup Score:
  - Agent config grows a [labels] section (BTreeMap); the flat
    [agent].group field is gone. group becomes just one label.
  - IotDeviceSetupConfig takes a BTreeMap<String, String> instead
    of a String group. TOML render iterates the BTreeMap (ordered)
    so idempotent change detection still works cleanly.

CLI-facing:
  - example_iot_apply_deployment: --target-device -> --to, accepts
    comma-separated key=value pairs.
  - example_iot_vm_setup: --group -> --labels, same grammar.
  - smoke-a4.sh: VM publishes group=$GROUP,device=$DEVICE_ID;
    deploys target --to device=$DEVICE_ID so single-device smoke
    behavior is preserved while exercising the selector path.

CRD regenerated via chart/regen-crd.sh. 7 contract tests + 6
operator tests pass.
2026-04-22 11:13:42 -04:00
..

example_iot_vm_setup

End-to-end driver for the IoT walking-skeleton VM-as-device flow. Runs two Harmony Scores in sequence:

  1. KvmVmScore — provision a libvirt VM from an Ubuntu 24.04 cloud image with a cloud-init seed ISO that authorizes one SSH key. Returns the booted VM's IP.
  2. IotDeviceSetupScore — SSH into the VM (via the Ansible-backed HostConfigurationProvider) and install podman + the iot-agent binary, drop the TOML config, bring up the systemd unit.

After a successful run, the VM is a fleet member reporting to NATS under the --device-id you chose, carrying the --group label you passed.

One-time setup

WORK=/var/tmp/harmony-iot-smoke
mkdir -p "$WORK/ssh"

# 1. Ubuntu 24.04 cloud image (~700 MB) — cached between runs.
curl -o "$WORK/ubuntu-24.04-server-cloudimg-amd64.img" \
     https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img

# 2. SSH keypair the VM will trust.
ssh-keygen -t ed25519 -N '' -f "$WORK/ssh/id_ed25519"

# 3. Runtime deps — Harmony self-installs Ansible into a managed venv
#    under $HARMONY_DATA_DIR/ansible-venv on first run, so you only need
#    python3 + venv on the runner. No system-wide `ansible` needed.
# On Arch:
#   sudo pacman -S libvirt qemu-full xorriso python
# On Debian/Ubuntu:
#   sudo apt install libvirt-daemon-system qemu-kvm xorriso python3 python3-venv

# 4. libvirt default network.
sudo virsh net-start default
sudo virsh net-autostart default

Run

cargo build -p iot-agent-v0

cargo run -p example_iot_vm_setup -- \
  --base-image /var/tmp/harmony-iot-smoke/ubuntu-24.04-server-cloudimg-amd64.img \
  --ssh-pubkey /var/tmp/harmony-iot-smoke/ssh/id_ed25519.pub \
  --ssh-privkey /var/tmp/harmony-iot-smoke/ssh/id_ed25519 \
  --work-dir /var/tmp/harmony-iot-smoke \
  --agent-binary target/debug/iot-agent-v0 \
  --nats-url nats://192.168.122.1:4222

Changing groups

Re-running with a different --group rewrites /etc/iot-agent/config.toml on the VM and restarts the agent. The VM itself is untouched.

cargo run -p example_iot_vm_setup -- ... --group group-b

Full end-to-end via smoke test

See iot/scripts/smoke-a3.sh — stands up NATS in a podman container, runs this example, asserts the agent's status lands in NATS.