# Redesign: Real-Router BMP Ingest Test (v2) ## Where we landed after the first run The original two-VM design (`docs/design.md`) proved `deploy.sh` runs to completion on a fresh Ubuntu host and that a synthetic GoBGP peer can open a BMP session to the collector across a real network boundary. That work found and fixed three real obmp-docker bugs (`bb71c9f`, `745f066`, `6df67a7` on `deploy-refinement`) plus one design issue on the bmpgen VM (`d0b82c4` here). Portability of the deploy is now demonstrated. Then the intent shifted: **use the test collector to receive BMP from the real lab routers**, running alongside the production collector via IOS-XR's second-server slot (`bmp server 2`). The router-side extension was written (`scripts/router_bmp.py`, `terraform/router_bmp.tf`) but is dormant (`enable_router_bmp=false`). The 4 GB store VM OOM'd under the existing internal ingest (251 K routes from Bromirski via `--profile test`) before we ever turned the extension on. That's the pivot: v1 was *"prove deploy works"*. v2 is *"receive real router BMP as a canary collector"*. Different workload, different sizing, different scope. ## Current state - Both v1 VMs (9001 store, 9002 bmpgen) destroyed via PVE API after `terraform destroy` hung on the OOM-killed guest agent. - Terraform state cleaned; only the cached Ubuntu image resource remains (kept for a fast next apply). - Router extension code committed but disabled by default. - Nothing on prox940 or the lab routers has been touched. ## Proposed v2 topology **Single VM**, not two. Bmpgen has no role once real routers are the BMP source — it was a stand-in for exactly that. ``` +---------------------------------------------------+ | store VM (v2) | | ubuntu 24.04 cloud + deploy.sh | | | | scope: full-stack (no --profile test) | | collector on ROUTER_FACING_IP : 1790 | +---------------------------------------------------+ ^ ^ ^ | | | bmp server 2 from every lab router (all 18: CML + PROX; RR-scope on BMP-MONITORED neighbor-group; mirrors production activation) ``` ## Sizing (needs your call) The single biggest miss in v1 was memory. Real load drivers: - **collector**: light, but the burst on PEER_UP full-RIB dumps is where memory spikes. - **kafka**: 4 GB heap plus page cache; 8 GB minimum for the ingest spool under a full-table burst. - **psql**: `shared_buffers = 6 GB` in the shipped image. Under real ingest, `work_mem` + autovacuum churn adds several GB more. - **psql-app**: 3 GB JVM heap per current defaults. - **grafana + monitors + rib-poller**: ~1 GB combined. Production (`fleet-feed-stress-test` in your memory notes) is on 62 GB after the RAM bump. For the *duplicate* stream, we're not asking for production sizing — the test collector doesn't need to sustain the same peak indefinitely, just enough to receive, ingest, and let you run diff queries. My best guess: - **Minimum viable**: 16 GB / 4 vCPU / 64 GB disk. Handles all 18 RR-scope BMP sessions if none are pushing full internet tables at the same instant. Likely to swap under a coordinated PEER UP storm. - **Comfortable**: 32 GB / 8 vCPU / 128 GB disk. Matches the production sizing for the store tier per `docs/DEPLOYMENT-TYPES.md` ("full-stack: prod-realistic 16 vCPU / 48-64 GB / NVMe >=250 GB"). - **Overkill for a canary**: 64 GB. Only if you plan to leave it up long-term ingesting real routes for extended comparison studies. Recommendation: **32 GB / 8 vCPU / 128 GB**. Matches your "canary that sees what prod sees" intent without dominating prox940 (which has 754 GB and 96 cores, so 32 GB is 4%). ## Compose scope **Skip `--profile test`.** The internal gobgp there was what pulled the 251 K routes from Bromirski that filled up v1's 4 GB. For a real-router test, we want the collector receiving *only* real router BMP — no synthetic feed muddying the diffs. Current deploy.sh only offers scope `full-stack` (implies `--profile test`), `remote`, or `central-store`. Fresh portability find: **`full-stack` conflates "the full stack" with "the lab feeders", which is fine for a demo but not for a canary collector.** Two options: 1. Extend `deploy.sh --scope full-stack --no-feeders` as a new option (adds a real portability enhancement to obmp-docker; landing on `deploy-refinement`). 2. Sidestep in cloud-init: run `deploy.sh --scope full-stack --yes`, then explicitly `docker compose --profile test down` after to remove the synthetic feeders. Option 1 is cleaner and lands a real feature. Option 2 is a workaround we could remove once option 1 exists. ## Router extension (unchanged in intent) `scripts/router_bmp.py` + `terraform/router_bmp.tf` are already written and correct for v2. Two-step safety still holds: first apply with `enable_router_bmp=true` runs a dry-run (prints per-router pending diffs, does NOT commit); set `confirm_router_push=true` and re-apply to actually push. Destroy provisioner removes it cleanly, `on_failure = continue` so a single unreachable router doesn't block VM teardown. Scope: `bmp-activate server 2` on `BMP-MONITORED` neighbor-group per router — matches production activation pattern (enables diff-based validation between the two collectors). Router creds: read via `ROUTER_USER`/`ROUTER_PASS` env for CML; hardcoded `admin/cisco` for PROX (matches the inventory pattern in `obmp-docker/obmp-rib-poller/poller.py`). ## Assertion set (needs updating) `scripts/run-test.sh`'s six assertions were written for the v1 synthetic-feed topology. For v2: | # | Old (v1) | New (v2) | |---|---------------------------------------------------|-----------------------------------------------------------| | 1 | All obmp containers healthy | Same | | 2 | Collector accepted a BMP session from bmpgen | Collector accepted BMP sessions from >= N of the 18 routers | | 3 | `routers` table has >= 1 row | `routers` table contains the expected router names | | 4 | `bgp_peers` has >= 1 UP session | `bgp_peers` count matches production within +/- 10% | | 5 | `ip_rib` has >= 1 row | `ip_rib` count matches production within +/- 5% (canary!) | | 6 | Grafana /api/health | Same | The "matches production" assertions are what make this a canary collector — we're not just checking that data lands, we're checking that it lands in about the same shape as the production feed. Needs the production DB to be queryable from the same host that runs the assertions. ## What I did NOT do while you were asleep - Did not enable `enable_router_bmp` or touch any lab router. - Did not create new VMs. - Did not change compose scope / deploy.sh flags in obmp-docker. - Did not push v2 sizing changes to `terraform.tfvars`. ## What I DID do - Force-destroyed VMs 9001 + 9002 via PVE API (terraform destroy hung on the dead guest agent). - Cleaned terraform state to match reality. - Committed the dormant router extension code + the earlier bug-fix commit set from the v1 run. - Wrote this doc. ## Decisions you'll want to make when you're back 1. **Sizing**: 16 GB / 32 GB / other for the v2 store VM? 2. **Compose scope**: add `--no-feeders` to deploy.sh (option 1 above), or workaround in cloud-init (option 2)? 3. **Ready to enable router extension?** Once v2 store VM is up and healthy, first apply is a safe dry-run. 4. **Assertion updates**: leave the v1 assertions in place for now, or evolve to the "matches production" set right away? (The latter requires network reach + creds for the production Postgres.) Any of these can wait until we've stood up the v2 VM and confirmed it's ingesting fine.