ADR 0037 — pkg/oobi/client/ — Universal Service Mesh for Inter-MÓDULO Communication¶
- Status: Proposed (2026-05-13) — materialization in flight (PR #707)
- Date: 2026-05-13
- Deciders: TLSStress.Art project
- Targets: v4.x (alongside HyperBridge.Art HB-1.6 first adopter)
- Builds on: ADR 0019 (OOBI slot allocation), ADR 0024 (SPAN.Art),
ADR 0029 (Sealed audit log), ADR 0036 (HyperBridge.Art),
discuss_oobi_immutable_gateway_art_2026_05_10
Context¶
The TLSStress.Art bench has 38 MÓDULOs (as of v3.9 + HyperBridge.Art). Many of them need to talk to each other over HTTP:
- SPAN-1 receives
PacketBatchJSON from HyperBridge.Art (HB-1.6) - SPAN-2 receives
PacketBatchfrom SPAN-1 - SPAN-3 receives
MetadataEventfrom SPAN-2 - FLOW.Art receives
ValidationReportfrom SPAN-3 - VALIDATOR.Art receives anomaly tickets from N producers
- RELAY.Art receives operator commands from GATEWAY
- ... and many more
Pre-v3.10, every MÓDULO rolled its own HTTP client. Each one had to independently:
- Implement an HTTP client + retry policy
- Configure a hardcoded destination URL via flag (e.g.
--sink-url) - (Forget to) source-bind to the OOBI VXLAN overlay interface
- (Forget to) configure mTLS via the cert-manager
oobi-ca-issuer - (Forget to) handle failover when destination is HA pair
- Emit its own non-standardized metrics
- (Forget to) audit-log every outbound for ZTP-prem compliance
The result: communication that "works on the lab single-node" but breaks subtly in production multi-node deployments. The most common failure mode: pod's outbound packets escape via the host's default route (the customer's management LAN) instead of being encapsulated into the OOBI VXLAN underlay — silently bypassing the isolation guarantee that the entire ZTP-prem story depends on.
Decision¶
Introduce pkg/oobi/client/ as the single, universal HTTP
client that every MÓDULO uses to talk to every other MÓDULO. The
package encapsulates everything that makes inter-MÓDULO
communication correct across all deployment topologies:
| Concern | What oobi/client does |
|---|---|
| Slot → IP resolution | Lookup determinístico via canon.go. Operator never types a URL. |
| Source-bind on OOBI overlay | SO_BINDTODEVICE("oobi0") on every socket. Packets MUST exit via the overlay, never the default route. |
| mTLS via cert-manager | Loads tls.crt / tls.key / ca.crt from the canonical mount path /etc/oobi/tls/. TLS 1.3 minimum. |
| Primary → Standby failover | Threshold-based switch when primary fails N consecutive times. Auto fail-back via the same mechanism. |
| Retry with exponential backoff | 25ms base, capped at 5s, jittered to avoid thundering herd. |
| Aggressive timeouts | 500ms per request default. A hung peer never blocks the caller's data plane. |
| Standardized metrics | requests_total, errors_total{outcome}, latency mean+max. Same shape for every MÓDULO. |
| Single-node escape hatch | LocalOverride field bypasses the fabric entirely. Lab dev still works. |
| Audit hook | Optional callback receives one entry per outbound request → wires into the ZTP-prem sealed audit log. |
Deployment-agnostic by design¶
The same Config produces correct behavior in every topology:
│ Single │ Multi │ Bare-metal │ Single │
│ K8s │ K8s │ systemd │ systemd │
│ OOBI │ OOBI │ multi-host │ lab │
───────────────────────┼──────────┼──────────┼─────────────┼──────────┤
TargetSlot mode │ ✅ │ ✅ │ ✅ │ n/a │
→ resolves to .230 │ │ │ │ │
→ source-bind │ oobi0 (host on overlay) │ skipped │
→ traffic stays on │ overlay │ overlay │ overlay │ n/a │
LocalOverride mode │ n/a │ n/a │ n/a │ ✅ │
→ http://localhost │ │ │ │ direct │
The operator never knows or cares which deployment they're in. The
Config is identical across all four — only the LocalOverride
field distinguishes single-node lab from production.
Public API (target)¶
import (
"github.com/nollagluiz/AI_forSE/pkg/oobi"
"github.com/nollagluiz/AI_forSE/pkg/oobi/client"
)
c, err := client.New(client.Config{
SourceModule: "hyperbridge-art",
TargetSlot: oobi.SlotSPANPrimary, // .230
FailoverSlot: oobi.SlotSPANStandby, // .231
ServicePort: 9098,
TLS: &client.TLSConfig{}, // cert-manager defaults
AuditHook: sealedLog.Append,
})
defer c.Close()
err = c.PostJSON(ctx, "/batch", packetBatch)
Migration plan¶
The legacy --sink-url=http://... flag stays as override mode:
1. If --sink-url is set: legacy mode (becomes LocalOverride)
2. If --target-slot is set: oobi/client mode (preferred)
3. If neither: config error
Each MÓDULO migrates independently — no breaking change, no coordinated deploy. Migration order matches data flow:
| # | MÓDULO | Effort |
|---|---|---|
| 1 | HyperBridge.Art (HB-1.6, first adopter, #708) | already in plan |
| 2 | SPAN-1 (pkg/span-collector/) — talks to SPAN-2 |
~2h |
| 3 | SPAN-2 (pkg/span-tls-extractor/) — talks to SPAN-3 |
~2h |
| 4 | SPAN-3 (pkg/span-correlator/) — talks to FLOW.Art |
~2h |
| 5 | RELAY.Art, GATEWAY.Art, VALIDATOR.Art | ~2h each |
| ... | (remaining MÓDULOs as touched) | ~2h each |
Once all MÓDULOs have migrated, a future major bump removes
the legacy --sink-url flag and enforces slot-based addressing.
Consequences¶
Pros¶
- Correctness by construction. New MÓDULO author cannot accidentally skip source-bind, mTLS, or failover. The client handles all of it.
- One spec to certify. ZTP-prem compliance auditor inspects ONE package, not 38 ad-hoc HTTP clients.
- Operator UX. Operator configures slot numbers (canonical,
documented in
canon.go); never thinks about URLs. - Observability uniformity. Every MÓDULO emits the same metric shape, enabling cross-cutting Grafana dashboards.
- Test infrastructure.
LocalOverridemakes integration tests trivial — point at ahttptest.Serverand the same code path exercises the production-shape HTTP client.
Cons / risks¶
- Shared dependency. All 38 MÓDULOs now depend on
pkg/oobi. A bug here is a fleet-wide bug. Mitigation: this package is small (~600 LOC + tests), Tier A, frozen API surface, exhaustive test coverage. - CGO-free constraint. No prometheus/client_golang, no fancy
TLS extensions. The package exports a
Snapshot()and lets each MÓDULO render its own metrics format. Tradeoff: every MÓDULO's/metricshandler has 10 extra lines of boilerplate. - Linux-only source-bind.
SO_BINDTODEVICEdoesn't exist on macOS / BSD — those platforms are dev-only. Source-bind is a no-op there;oobi0interface doesn't exist anyway. - mTLS rollout coordination. First migrated MÓDULO must NOT
enable TLS until ALL its peers have certs mounted. Stage TLS
rollout per-MÓDULO with
TLS: nildefault + opt-in.
Compatibility¶
- Pre-migration MÓDULOs keep using
--sink-url— no change required. - Post-migration MÓDULOs use
TargetSlot. Both modes coexist across a fleet during the migration window. - systemd-deployed MÓDULOs with the canon
oobi0interface set up bysystemd-networkdwork identically to K8s pods.
References¶
- Code:
pkg/oobi/client/(PR #707) - First adopter:
pkg/hyperbridge-art/(HB-1.6, PR #708) - Sibling:
pkg/oobi/canon.go(slot map) - Overlay infra:
k8s/oobi/30-vtep-daemonset.yaml - Cross-ref: ADR 0019 (OOBI slot allocation), ADR 0029 (Sealed audit log), ADR 0036 (HyperBridge.Art)