lachesis

module
v0.0.0-...-6ed2b60 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: Apache-2.0

README ΒΆ

πŸ“Š Lachesis

Billing-grade, per-tenant network telemetry for OpenStack β€” every byte attributed to the tenant that owns it, at line rate, in eBPF.

License made-with-Go Go Reference CI CodeQL GitHub last commit

⛩️ Architecture | πŸ› οΈ Operating | πŸ§ͺ Test strategy | πŸ‘· Contributing



Per-tenant, per-zone, per-direction traffic β€” live from a real OpenStack compute node. (Tenant/server names anonymised.)

πŸ₯½ Overview

Lachesis is an eBPF-based network-telemetry daemon for OpenStack. It attributes every byte on a compute node to the tenant that owns it β€” classifying north-south and east-west traffic into billing zones, re-attributing Octavia load-balancer bytes to the real client tenant, and exposing per-tenant Prometheus counters at line rate via TC clsact hooks.

It runs as one static Go binary per compute node on OVN-based OpenStack (Yoga), and is built for billing-grade accounting: crash-resilient, monotonic per-tenant counters that survive agent restarts, kernel-map churn, and VM lifecycle events.

πŸ€” Why Lachesis?

Generic flow tools (sFlow/NetFlow) and host exporters tell you how much traffic a node moved. They can't tell you, correctly and durably, which tenant to bill for it. Lachesis is built for exactly that:

  • Attribution, not just counting β€” bytes are keyed to the owning tenant and a billing zone (same-tenant, cross-tenant, infra, external, shared), decided in-kernel at each VM tap.
  • Billing-grade durability β€” a custom Prometheus collector (never CounterVec) plus a write-ahead log means counters never reset to zero on a restart, so rate() never goes negative and revenue is never double-counted or lost.
  • Octavia-aware β€” load-balancer bytes are folded back to the originating client tenant instead of vanishing into the amphora.
  • Line-rate, low-overhead β€” aggregation happens in a PERCPU_HASH in the kernel; userspace only drains deltas on a scrape tick, with zero-allocation hot paths gated by a CI benchmark.

✨ Features

Billing accuracy

  • Per-tenant, per-zone, per-direction byte & packet accounting β€” the billing metric lachesis_bytes_total{tenant_id,zone,direction,external_network}.
  • Five traffic zones β€” same_tenant, other_tenant, infra, external, shared β€” from a hybrid L2 MAC lookup plus an LPM zone trie.
  • Octavia re-attribution β€” LB bytes billed to the client tenant, not the amphora.
  • Per-server drill-down β€” an optional mortal per-server family for capacity views alongside the immortal per-tenant billing family.

eBPF data plane

  • TC clsact at every VM tap β€” kernel-side aggregation in a PERCPU_HASH, no per-packet copies to userspace.
  • Dynamic attach β€” netlink-driven; taps are attached/detached as VMs come and go, with idempotent replace and boot-time zombie-filter cleanup.
  • Read-don't-clear maps β€” the scrape loop computes deltas against remembered raw values; eviction is the GC's job alone, so a crash at any instant is non-destructive.

Operability

  • Live hot-reload (SIGHUP) β€” retune scrape/flush cadence, GC watermarks, ghost-grace and reconcile ceiling on a running agent β€” no restart, no counting gap. Invalid edits are rejected whole; a reload counter and /debug/config confirm what took effect.
  • A real /debug surface β€” operator pages for topology, zones, live IP/MAC β†’ tenant lookup, and topology-anomaly detection (route cycles, duplicate router MACs, multi-external-path VMs), plus stdlib pprof. /debug/config renders the effective config with secrets redacted, and runtime log level flips via PUT /debug/log-level.
  • Everything is one curl from a script β€” every /debug HTML page also serves its exact view model as JSON with ?format=json.
  • Two ready-to-import Grafana dashboards + a paging alert rule β€” multi-agent/cluster-aware via a $node variable (deploy/grafana/).

Resilience & observability

  • Crash-resilient by design β€” a custom prometheus.Collector (no counter resets on restart) and an atomic JSON write-ahead log (temp-file + rename + fsync, one .bak generation, additive schema).
  • Deep self-instrumentation β€” every subsystem reports on /metrics: scraper lag/errors, WAL flush latency, BPF map fill-vs-max, GC eviction rates, Neutron sync age & API errors, Kafka lag, collect-pass p50/p99.
  • Kafka-driven live metadata β€” near-instant MAC learning from Neutron events, with a periodic full reconcile as the freshness floor.
  • Lingering-ghost lifecycle β€” dying FIN/RST packets from a just-deleted port still attribute correctly for a grace window before the metadata is reaped.

πŸš€ Quick start

The build is fully containerized (eBPF toolchain in Docker); the agent runs as root on a Linux compute node.

1. Build

task setup      # pull the eBPF builder image
task generate   # bpf2go β€” compile telemetry.c, generate the loader
task binary     # build ./build/agent (linux/amd64)

2. Run (on an OVN compute node, as root)

cp deploy/agent/config.example.yaml /etc/lachesis/config.yaml   # edit to taste
./build/agent -config /etc/lachesis/config.yaml

Configure via that YAML and/or LACHESIS_* env vars (e.g. LACHESIS_HTTP_LISTEN, LACHESIS_BPF_ATTACH_INTERFACES). Then look at it:

curl -s localhost:9090/metrics | grep lachesis_bytes_total   # the billing family
open  http://localhost:9090/debug                            # operator surface

3. Visualize β€” bring up the local Prometheus + Grafana stack:

cd deploy/grafana && docker compose up      # Grafana on :3000, Prometheus on :9091

The dashboards above ship in deploy/grafana/dashboards/. See Operating for the full runbook.

πŸ› Architecture

Four layers, one binary per compute node:

  L4  Prometheus + WAL      custom Collector Β· GlobalState Β· JSON write-ahead log
  L3  Go agent              scraper Β· delta math Β· Octavia attribution Β· Netlink watcher
  L2  OpenStack metadata    per-tenant zone trie + MAC map, synced from the Neutron API
  L1  eBPF (TC clsact)      tc_telemetry_in/out Β· PERCPU_HASH Β· LPM trie Β· mac_tenant_map

The boot order is strict and sync-pointed (out-of-order startup silently misclassifies flows, so it isn't left to chance): clean up orphaned TC filters β†’ load eBPF objects β†’ Neutron cold-start populates metadata before any packet β†’ subscribe netlink then sweep existing taps β†’ restore state from the WAL β†’ start workers.

The full design narrative β€” data structures, the classification algorithm, the billing contract, and every decision record β€” lives in docs/architecture.

🩺 Operability at a glance

Health and internals are visible without attaching a debugger:

# retune a running agent β€” no restart, no counting gap
vi /etc/lachesis/config.yaml && kill -HUP "$(pidof agent)"
curl -s localhost:9090/metrics | grep lachesis_config_reloads_total   # result="applied"

# resolve an IP or MAC to its tenant + zone, as JSON
curl -s 'localhost:9090/debug/lookup?ip=10.0.0.5&format=json'


The agent instruments itself: scrape lag, WAL latency, BPF map fill, GC, Neutron sync age and Kafka lag all on /metrics.

πŸ§ͺ Testing

  • task test β€” unit tests (no privileges required).
  • task test-integration β€” privileged-Docker eBPF/kernel tests.
  • task bench-gate β€” the zero-allocation benchmark gate on the hot paths.
  • scenariotest β€” the live tier: stand up a declared OpenStack topology, drive real traffic, and assert per-tenant/zone/direction byte attribution end-to-end against a running agent, with automatic teardown.

See Test strategy for the full picture.

πŸ§‘β€πŸ’» Community

πŸ“„ License

Licensed under the Apache License 2.0. Copyright Β© 2026 Bigstack co., ltd.

Directories ΒΆ

Path Synopsis
cmd
agent command
Command agent runs the CubeCOS network-telemetry data-plane reader.
Command agent runs the CubeCOS network-telemetry data-plane reader.
loadtest command
Command loadtest exercises the CubeCOS network-telemetry agent under sustained TCP traffic and verifies the agent process stays inside its resource budget (RSS, CPU).
Command loadtest exercises the CubeCOS network-telemetry agent under sustained TCP traffic and verifies the agent process stays inside its resource budget (RSS, CPU).
scenariotest command
Command scenariotest realizes a declared topology, drives traffic across it, and asserts the agent's /metrics deltas match.
Command scenariotest realizes a declared topology, drives traffic across it, and asserts the agent's /metrics deltas match.
scenariotest/scenarios
Package scenarios is the canonical registry of scenariotest scenarios.
Package scenarios is the canonical registry of scenariotest scenarios.
internal
agent
Package agent is the composition root: it wires state, scraper, metrics, runtime and logging onto an HTTP server β€” the glue between the BPF data plane and the Prometheus endpoint.
Package agent is the composition root: it wires state, scraper, metrics, runtime and logging onto an HTTP server β€” the glue between the BPF data plane and the Prometheus endpoint.
boot
Package boot tracks the agent's startup phases as one monotonically advancing sequence.
Package boot tracks the agent's startup phases as one monotonically advancing sequence.
bpf
Package bpf exposes the kernel↔userspace ABI for the telemetry agent.
Package bpf exposes the kernel↔userspace ABI for the telemetry agent.
config
Package config holds the agent's runtime configuration as nested sections β€” one Go file per section, each owning its type, defaults and Validate; this file aggregates them.
Package config holds the agent's runtime configuration as nested sections β€” one Go file per section, each owning its type, defaults and Validate; this file aggregates them.
debug
Package debug serves the agent's operator-facing /debug surface: an HTML index with sync health and anomaly counts, detail pages for attribution state, and the stdlib pprof handlers.
Package debug serves the agent's operator-facing /debug surface: an HTML index with sync health and anomaly counts, detail pages for attribution state, and the stdlib pprof handlers.
gc
Package gc owns the agent's eviction work: the lingering-ghost sweep (this file) and pressure-relief eviction (pressure.go).
Package gc owns the agent's eviction work: the lingering-ghost sweep (this file) and pressure-relief eviction (pressure.go).
kafka
Package kafka consumes oslo.messaging notifications and kicks a Neutron reconcile on each committed metadata change, so metadata refreshes within a pass instead of waiting for the periodic net.
Package kafka consumes oslo.messaging notifications and kicks a Neutron reconcile on each committed metadata change, so metadata refreshes within a pass instead of waiting for the periodic net.
kernelwriter
Package kernelwriter pushes userspace metadata and trie entries into the kernel BPF maps.
Package kernelwriter pushes userspace metadata and trie entries into the kernel BPF maps.
loadtest
Package loadtest implements the CubeCOS network-telemetry resource budget test: spawn the agent as a subprocess, drive sustained TCP traffic through a netns + veth, and verify the agent stays inside configured RSS and CPU thresholds while actually observing the load on its BPF data plane.
Package loadtest implements the CubeCOS network-telemetry resource budget test: spawn the agent as a subprocess, drive sustained TCP traffic through a netns + veth, and verify the agent stays inside configured RSS and CPU thresholds while actually observing the load on its BPF data plane.
logging
Package logging configures the agent's structured logger.
Package logging configures the agent's structured logger.
metadata
Package metadata is the userspace mirror of the kernel `mac_tenant_map`, mapping a VM MAC to a *TenantMeta carrying the attributes the kernel cannot store.
Package metadata is the userspace mirror of the kernel `mac_tenant_map`, mapping a VM MAC to a *TenantMeta carrying the attributes the kernel cannot store.
metrics
Package metrics implements the Prometheus custom Collector that exposes the billing families from state.GlobalState.
Package metrics implements the Prometheus custom Collector that exposes the billing families from state.GlobalState.
netlink
Package netlink discovers tap interfaces via RTM_NEWLINK / RTM_DELLINK and dynamically attaches the telemetry TC programs to each match, replacing the earlier static single-interface attach.
Package netlink discovers tap interfaces via RTM_NEWLINK / RTM_DELLINK and dynamically attaches the telemetry TC programs to each match, replacing the earlier static single-interface attach.
neutron
Package neutron is the OpenStack metadata path: Keystone auth, the Neutron API client, the trie builder, and the Neutron struct carrying one sync's outputs to the rest of the agent.
Package neutron is the OpenStack metadata path: Keystone auth, the Neutron API client, the trie builder, and the Neutron struct carrying one sync's outputs to the rest of the agent.
osclient
Package osclient is the shared OpenStack client bootstrap: the Credentials vocabulary, the admin-openrc parser (ParseOpenRC), and Keystone v3 authentication (Authenticate, AuthenticateProject).
Package osclient is the shared OpenStack client bootstrap: the Credentials vocabulary, the admin-openrc parser (ParseOpenRC), and Keystone v3 authentication (Authenticate, AuthenticateProject).
perfbench
Package perfbench measures the per-packet runtime of the telemetry classifier via BPF_PROG_TEST_RUN.
Package perfbench measures the per-packet runtime of the telemetry classifier via BPF_PROG_TEST_RUN.
reconcile
Package reconcile owns the periodic Neutron reconcile: a 5-minute safety net that re-fetches the full snapshot and applies only the rows that changed, so metadata staleness stays bounded by one interval even through a Kafka outage.
Package reconcile owns the periodic Neutron reconcile: a 5-minute safety net that re-fetches the full snapshot and applies only the rows that changed, so metadata staleness stays bounded by one interval even through a Kafka outage.
runtime
Package runtime wires reload and debug controls onto the agent: the Manager holds the current config, re-reads the YAML on SIGHUP, applies the hot-reloadable fields, and serves /debug.
Package runtime wires reload and debug controls onto the agent: the Manager holds the current config, re-reads the YAML on SIGHUP, applies the hot-reloadable fields, and serves /debug.
scenariotest
Package scenariotest is the core of the live-validation harness: the scenario DSL, the step seam, the run-state, the report, and the Cloud / MetricsSource / VMExec interfaces through which everything reaches a real cluster.
Package scenariotest is the core of the live-validation harness: the scenario DSL, the step seam, the run-state, the report, and the Cloud / MetricsSource / VMExec interfaces through which everything reaches a real cluster.
scenariotest/agentctl
Package agentctl drives the telemetry agent on a compute host: the systemd lifecycle, the on-host config, the durable state a cold restart destroys, and the readiness gate proving the process cycled.
Package agentctl drives the telemetry agent on a compute host: the systemd lifecycle, the on-host config, the durable state a cold restart destroys, and the readiness gate proving the process cycled.
scenariotest/agentmetrics
Package agentmetrics is the live scenariotest.MetricsSource: it reads a running agent's observability surfaces over HTTP β€” the Prometheus text exposition at /metrics and the JSON /debug queries on the same listener β€” and parses them into the harness's sample types.
Package agentmetrics is the live scenariotest.MetricsSource: it reads a running agent's observability surfaces over HTTP β€” the Prometheus text exposition at /metrics and the JSON /debug queries on the same listener β€” and parses them into the harness's sample types.
scenariotest/assert
Package assert evaluates a scenario's declared expectations against the agents' live /metrics as lower bounds on the delta from the drive-time baseline, polling until every expectation passes or the stabilize deadline fires.
Package assert evaluates a scenario's declared expectations against the agents' live /metrics as lower bounds on the delta from the drive-time baseline, polling until every expectation passes or the stabilize deadline fires.
scenariotest/down
Package down tears a realized scenario back down: every resource the run-state records, in dependency order, idempotently.
Package down tears a realized scenario back down: every resource the run-state records, in dependency order, idempotently.
scenariotest/drive
Package drive pushes a scenario's declared flows as real traffic between the realized VMs, over SSH, after gating on the agents having learned the participating MACs.
Package drive pushes a scenario's declared flows as real traffic between the realized VMs, over SSH, after gating on the agents having learned the participating MACs.
scenariotest/fake
Package fake is the harness's shared test double: an in-memory scenariotest.Cloud that models the Neutron/Nova behaviour the phases actually depend on (floating-IP reachability, port binding, router-route ordering), plus the matching MetricsSource and VMExec.
Package fake is the harness's shared test double: an in-memory scenariotest.Cloud that models the Neutron/Nova behaviour the phases actually depend on (floating-IP reachability, port binding, router-route ordering), plus the matching MetricsSource and VMExec.
scenariotest/gate
Package gate holds the poll-until-predicate waits the harness uses to sequence a scenario against a cluster that changes asynchronously.
Package gate holds the poll-until-predicate waits the harness uses to sequence a scenario against a cluster that changes asynchronously.
scenariotest/openstack
Package openstack is the gophercloud-backed scenariotest.Cloud: every live OpenStack call the harness makes to realize, mutate and tear down a scenario's topology.
Package openstack is the gophercloud-backed scenariotest.Cloud: every live OpenStack call the harness makes to realize, mutate and tear down a scenario's topology.
scenariotest/preflight
Package preflight verifies, without mutating anything, that a live cluster can run a scenario: every prerequisite resolves, any pinned hypervisor exists, and every configured agent is scraping and exposing the metric families the harness depends on.
Package preflight verifies, without mutating anything, that a live cluster can run a scenario: every prerequisite resolves, any pinned hypervisor exists, and every configured agent is scraping and exposing the metric families the harness depends on.
scenariotest/realize
Package realize stands a scenario's declared topology up on the live cluster and blocks until the agents have attached to the new taps.
Package realize stands a scenario's declared topology up on the live cluster and blocks until the agents have attached to the new taps.
scenariotest/remote
Package remote runs commands on hosts the harness does not own β€” the scenario VMs it drives traffic through, and the agent hosts it restarts.
Package remote runs commands on hosts the harness does not own β€” the scenario VMs it drives traffic through, and the agent hosts it restarts.
scenariotest/run
Package run composes the whole loop β€” preflight, up, the scenario's step script, down β€” into the single command CI and operators invoke.
Package run composes the whole loop β€” preflight, up, the scenario's step script, down β€” into the single command CI and operators invoke.
scenariotest/steps
Package steps is the step vocabulary a scripted scenario is written in: an ordered program the run executes between up and down, able to express the mid-run lifecycle events the classic drive-all/assert-all loop cannot β€” deleting a VM, waiting out the agent's ghost sweep, rebooting a MAC under another tenant, restarting an agent cold.
Package steps is the step vocabulary a scripted scenario is written in: an ordered program the run executes between up and down, able to express the mid-run lifecycle events the classic drive-all/assert-all loop cannot β€” deleting a VM, waiting out the agent's ghost sweep, rebooting a MAC under another tenant, restarting an agent cold.
scraper
Package scraper drives BPF telemetry-map collection: one goroutine drains the kernel map on a tick and folds each reading into state.GlobalState.
Package scraper drives BPF telemetry-map collection: one goroutine drains the kernel map on a tick and folds each reading into state.GlobalState.
state
Package state owns the agent's authoritative cumulative counters, keyed by bpf.FlowKey β€” the staging area between the scraper, which feeds raw kernel readings, and the metrics Collector, which emits cumulatives to Prometheus.
Package state owns the agent's authoritative cumulative counters, keyed by bpf.FlowKey β€” the staging area between the scraper, which feeds raw kernel readings, and the metrics Collector, which emits cumulatives to Prometheus.
tcattach
Package tcattach binds BPF programs to a netlink link via the kernel's TC clsact qdisc and a direct-action filter at the ingress or egress hook.
Package tcattach binds BPF programs to a netlink link via the kernel's TC clsact qdisc and a direct-action filter at the ingress or egress hook.
testenv/bpfunit
Package bpfunit drives BPF programs through BPF_PROG_TEST_RUN for unit testing and microbenchmarking.
Package bpfunit drives BPF programs through BPF_PROG_TEST_RUN for unit testing and microbenchmarking.
testenv/bpfunit/fixtures
Package fixtures contains BPF programs used only by the test infrastructure.
Package fixtures contains BPF programs used only by the test infrastructure.
testenv/netns
Package netns provides Linux network namespace lifecycle helpers for tests.
Package netns provides Linux network namespace lifecycle helpers for tests.
testenv/scenario
Package scenario is a declarative builder for the Neutron snapshots that exercise neutron.BuildTrie in tests, hiding the struct-literal boilerplate while producing the same neutron.Snapshot production consumes.
Package scenario is a declarative builder for the Neutron snapshots that exercise neutron.BuildTrie in tests, hiding the struct-literal boilerplate while producing the same neutron.Snapshot production consumes.
testenv/traffic
Package traffic generates network traffic for testenv-based tests.
Package traffic generates network traffic for testenv-based tests.
tunables
Package tunables holds the agent's hot-reloadable operational knobs behind one atomic snapshot, swapped whole on each SIGHUP.
Package tunables holds the agent's hot-reloadable operational knobs behind one atomic snapshot, swapped whole on each SIGHUP.
unresolved
Package unresolved implements the UnresolvedBuffer: the late-binding holding area for flows whose VM-side MAC is not yet in the metadata map.
Package unresolved implements the UnresolvedBuffer: the late-binding holding area for flows whose VM-side MAC is not yet in the metadata map.
wal
Package wal is the agent's write-ahead log: an atomic JSON snapshot of state.GlobalState, flushed every 60s and read back on boot to seed state before the scraper starts.
Package wal is the agent's write-ahead log: an atomic JSON snapshot of state.GlobalState, flushed every 60s and read back on boot to seed state before the scraper starts.
zombie
Package zombie removes orphan TC telemetry filters left by a previous agent run that exited without unloading them.
Package zombie removes orphan TC telemetry filters left by a previous agent run that exited without unloading them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL