Terraform (bpg/proxmox) provisions two Ubuntu 24.04 cloud-init VMs on a Proxmox host, one running obmp-docker's `deploy.sh --scope full-stack` and the other running GoBGP as a BMP-source stand-in. `scripts/run-test.sh` runs the six assertions from docs/design.md (containers healthy, BMP peer up, ip_rib/bgp_peers non-zero, Grafana healthcheck). Layout: terraform/ provider + VM + cloud-init snippet resources terraform/cloud-init/ user-data templates (store + bmpgen) scripts/ run-test.sh, teardown.sh docs/design.md topology, assertion set, and rationale Cred handling: terraform.tfvars is gitignored; endpoint + api_token can also come from PROXMOX_VE_ENDPOINT / PROXMOX_VE_API_TOKEN env vars. Not validated locally: `terraform init && terraform validate` -- the terraform binary is not on the authoring host. YAML + shell + HCL brace balance verified. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5.8 KiB
Design: Two-VM Portability Test
Goal
Prove that obmp-docker deploys and ingests on a fresh host with only the
inputs a new operator would have (repo URL, a hypervisor, an SSH key). The
existing deploy.sh claims portability; this test is what turns the claim
into evidence.
Topology
+------------------------+ BMP TCP :ROUTER_FACING_PORT
| store VM (VM-A) | <----------------------------------+
| ubuntu 24.04 cloud | |
| docker + deploy.sh | |
| full-stack profile | |
| | |
| ROUTER_FACING_IP = | |
| <VM-A ip> | |
| ROUTER_FACING_PORT = | +----------+---------+
| 1790 (IANA BMP) | | bmpgen VM (VM-B) |
| | | ubuntu 24.04 |
+------------------------+ | docker |
| gobgp container |
| (BMP client) |
+-------------------+
| |
+---- shared L2 (Proxmox bridge) or routed subnet ----+
Two VMs, both stock Ubuntu 24.04 cloud images, both provisioned entirely
by cloud-init. No manual step between terraform apply and the
assertions running.
Why two VMs, not one
The deploy.sh --scope full-stack --profile test option already spins up
GoBGP inside the same compose network as the collector. That proves the
compose is internally consistent, but it doesn't exercise the actual
router-integration surface (ROUTER_FACING_IP/ROUTER_FACING_PORT,
compose's ${ROUTER_FACING_PORT:-1790}:5000 publish, cross-host TCP,
management-plane reachability). A second VM is the smallest topology that
makes those things real.
If the two-VM test passes, "deploy.sh works AND routers can reach it" holds. If it fails, we know exactly which of the two.
What VM-A runs
Cloud-init on VM-A does:
- Install
docker.io,docker-compose-plugin,git. git clonethe obmp-docker repo at a pinned branch/commit.- Write a minimal
.envfrom.env.examplewith:HOST_IP= VM-A's own primary IPROUTER_FACING_IP= sameROUTER_FACING_PORT= 1790 (test uses IANA port, not the lab transitional 5000)OBMP_AUTH_MODE=local(no reverse proxy in this topology)
- Run
./deploy.sh --scope full-stack --auth local --yes. - Write a completion marker to
/var/lib/cloud/portability-test.donewith exit status so the test harness can poll it.
What VM-B runs
Cloud-init on VM-B does:
- Install
docker.io. docker runa GoBGP container with a config file that:- Sets local AS to a private test ASN (65099).
- Establishes a BMP session to
<VM-A ip>:1790. - Injects a small synthetic route set so
ip_ribgets non-zero rows.
- Write a completion marker.
GoBGP is minimal on purpose — a real router would test more, but each extra dial-in path (BGP-LS, gNMI, NETCONF) needs a real IOS-XR environment or an emulator like XRd. The minimum route-monitoring path is enough to catch the portability failure modes.
Assertion set (run by scripts/run-test.sh)
Once cloud-init on both VMs is done (poll the completion markers, or 15 min timeout), SSH into VM-A and run:
| # | Assertion | Query / check |
|---|---|---|
| 1 | All obmp containers healthy | docker ps --format '{{.Names}} {{.Status}}' grep unhealthy = 0 |
| 2 | Collector accepted a BMP session from VM-B | docker logs obmp-collector contains "peer up" for VM-B's IP |
| 3 | routers table has 1 row |
SELECT count(*) FROM routers = 1 |
| 4 | bgp_peers has ≥ 1 UP session |
SELECT count(*) FROM bgp_peers WHERE state='UP' ≥ 1 |
| 5 | ip_rib has ≥ 1 row (GoBGP injected some routes) |
SELECT count(*) FROM ip_rib ≥ 1 |
| 6 | Grafana healthcheck returns 200 | curl -sf http://<VM-A>:3000/api/health |
Pass = all six green. Failure emits the first failing assertion + a
tarball of docker logs, .env, deploy.sh output for diagnosis.
Why bpg/proxmox
Two candidate providers exist for Proxmox: Telmate/proxmox (original,
slower moving) and bpg/proxmox (fork, active, modern cloud-init
handling). We pick bpg because:
- Cloud-init
user_data_file_idis first-class — we can hand-craft the cloud-init YAML and reference it by snippet ID rather than shoving it through the provider's per-field API. proxmox_virtual_environment_download_filehandles the cloud-image download idempotently.- The type system + docs are noticeably better on 0.60+.
Why cloud-init and not Ansible
Adding Ansible would double the surface. Cloud-init is enough for "install a few packages, drop a repo, run a script" — anything more complex is a smell that we're testing the wrong thing.
Why a separate repo
Test infra should not gate product commits. If the two-VM test suite grows (multi-vendor router VMs, prod-like tuning tests, longer-running soak variants), it earns its own release cadence. Keeping the code separate keeps that door open.