Documentation
¶
Overview ¶
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.
Two invariants:
- Stored TenantMeta values are IMMUTABLE. Several MACs may share one pointer, and a scrape may hold it across an update — replace the pointer, never mutate in place.
- The map is a strict SUPERSET of the kernel's. Insert userspace-first, delete kernel-first after the ghost window. The ordering is the caller's responsibility.
The MAC encoding matches the kernel's `mac_to_u64`; that agreement is what makes cross-language lookups consistent.
docs/architecture/data-structures.md#lingering-ghost
Index ¶
- Constants
- func ExternalNetworkLabel(extNet string, zone bpf.ZoneCode) string
- func FlowExternalLabel(routers *RouterMACs, vmExtNet string, key bpf.FlowKey) string
- func PeerMAC(key bpf.FlowKey) uint64
- func SettleResolver(routers *RouterMACs, pick func(bpf.FlowKey) (*TenantMeta, bool)) func(bpf.FlowKey) (string, string, string, bool)
- func VMMAC(key bpf.FlowKey) uint64
- type Attribution
- type Resolver
- type RouterMACs
- type ShardedMetadataMap
- func (s *ShardedMetadataMap) Delete(mac uint64)
- func (s *ShardedMetadataMap) Insert(mac uint64, meta *TenantMeta)
- func (s *ShardedMetadataMap) Len() int
- func (s *ShardedMetadataMap) Lookup(mac uint64) (*TenantMeta, bool)
- func (s *ShardedMetadataMap) MarkDelete(mac uint64, at time.Time) bool
- func (s *ShardedMetadataMap) Range(f func(mac uint64, meta *TenantMeta) bool)
- type TenantInterner
- type TenantMeta
Constants ¶
const ( TenantIDUnset uint32 = 0 UnknownTenantID = "unknown" )
The two "no tenant" sentinels, kept together because they describe the same absence on two sides:
- TenantIDUnset — the kernel-facing u32. Real interned IDs start at 1, so a zero-valued field never collides with a real tenant.
- UnknownTenantID — the `tenant_id` label for an unresolved MAC. Single source of truth: every producer must emit the SAME string, because a cold-start rate() query spans the transition from unknown to resolved. Lives here, not in metrics, because L2 must not import L4.
const NoExternalNetwork = "none"
NoExternalNetwork is the `external_network` label sentinel for series that have no external network: every non-EXTERNAL zone, and EXTERNAL-zone traffic from a VM with no resolved external path. Prometheus requires a consistent label set per metric, so the label is always present and this is its "absent" value. Single source of truth for the same reason as UnknownTenantID: it lives here (not metrics) because metadata must not import metrics (L2 → L4).
Variables ¶
This section is empty.
Functions ¶
func ExternalNetworkLabel ¶
ExternalNetworkLabel applies the zone gate that keeps the external_network label meaningful and low-cardinality: only EXTERNAL-zone series carry a real network label; everything else — other zones, and external flows of a VM with no resolved external path — gets the NoExternalNetwork sentinel. It is the per-VM half of the label rule; every emitter labels through FlowExternalLabel, which layers the per-flow router-MAC resolution on top of this gate.
func FlowExternalLabel ¶
func FlowExternalLabel(routers *RouterMACs, vmExtNet string, key bpf.FlowKey) string
FlowExternalLabel resolves the external_network label for one flow: per-flow via the peer router-interface MAC when the router map knows it (the network that actually carried the flow), else the per-VM attribution (FIP / gateway-IP rule), both behind the ExternalNetworkLabel zone gate. Single source for every emitter — the Collector's live aggregation, the ghost sweep's settle fold, and the reconciler's attribution-change folds all label through here, so a flow's settled bytes land in exactly the bucket its live series occupied. routers may be nil (tests without a router map): pure per-VM fallback.
func PeerMAC ¶
PeerMAC returns the non-VM-side MAC of key — the remote endpoint's MAC at the tap, which for routed traffic is a router interface's. It is VMMAC's mirror under the same directional swap and lives beside it so the swap rule stays in exactly one file.
func SettleResolver ¶
func SettleResolver(routers *RouterMACs, pick func(bpf.FlowKey) (*TenantMeta, bool)) func(bpf.FlowKey) (string, string, string, bool)
SettleResolver adapts a per-flow TenantMeta pick into the resolve callback a settle fold takes. pick both gates which rows fold and selects the meta whose attribution each row folds under; the returned closure completes it with the (tenant, external_network) pair the Collector emits for that row — FlowExternalLabel behind the zone gate. Every fold site resolves through here so its rows land in exactly the series their live flows occupied; a fold that built the pair by hand could half-label (skip the router-map lookup, say) and teleport settled bytes between series.
func VMMAC ¶
VMMAC returns the VM-side MAC of key as a bpf.MACKey u64, applying the directional swap (CLAUDE.md "Critical Invariants" and bpf/telemetry.c `handle_packet`): on INGRESS the VM is the source, on EGRESS the destination. It is the single source of the swap rule — both Resolver.Resolve and the UnresolvedBuffer classifier key off it, so the "which MAC is the VM" decision lives in exactly one place.
Types ¶
type Attribution ¶
Attribution is everything the metrics Collector resolves per flow at scrape time: the tenant label, the per-server export identity, and the zone-gated external-network label. Returned by value — the Collector calls this on every live row inside Collect, so it must not allocate.
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
Resolver implements `metrics.TenantResolver` against a *ShardedMetadataMap. It picks the VM-side MAC out of a bpf.FlowKey per the directional swap (CLAUDE.md "Critical Invariants" and bpf/telemetry.c `handle_packet`):
- INGRESS (VM sending): vm_mac = key.SrcMac
- EGRESS (VM receiving): vm_mac = key.DstMac
On lookup miss it returns the UnknownTenantID / NoExternalNetwork sentinels; on hit, the entry's attribution fields with the external_network label resolved per flow (FlowExternalLabel: peer router-interface MAC first, per-VM attribution as the fallback). Safe for concurrent use (read-only).
func NewResolver ¶
func NewResolver(m *ShardedMetadataMap, routers *RouterMACs) *Resolver
NewResolver wraps m and routers; both must outlive the Resolver. routers may be nil (a resolver without per-flow router attribution — the per-VM fallback then always applies).
type RouterMACs ¶
type RouterMACs struct {
// contains filtered or unexported fields
}
RouterMACs holds the router-interface-MAC → external-network-label map behind an atomic pointer: one owner (the reconciler, seeded by cold start) replaces the whole map per sync; readers (the Collector's per-row resolution, the settle folds) load it lock-free. The population is small — one entry per gatewayed router interface — so whole-map replacement per pass is cheaper than any locking scheme, and the swap gives readers a consistent view by construction.
func NewRouterMACs ¶
func NewRouterMACs() *RouterMACs
NewRouterMACs returns an empty store; lookups miss until the first Replace (cold start, before any scrape — the boot order guarantees it).
func (*RouterMACs) Lookup ¶
func (r *RouterMACs) Lookup(mac uint64) (string, bool)
Lookup resolves a peer MAC to its router's external-network label. Zero-alloc: one atomic load and one map read (the Collect hot-path constraint).
func (*RouterMACs) Replace ¶
func (r *RouterMACs) Replace(m map[uint64]string)
Replace swaps the whole map in. The caller must not mutate m after handing it over — readers hold the pointer.
func (*RouterMACs) Snapshot ¶
func (r *RouterMACs) Snapshot() map[uint64]string
Snapshot returns the current map for diffing. Read-only by the same contract as Replace.
type ShardedMetadataMap ¶
type ShardedMetadataMap struct {
// contains filtered or unexported fields
}
ShardedMetadataMap is the userspace MAC→[*TenantMeta] store. Safe for concurrent use; see package doc for the immutability and superset-of-mac_tenant_map invariants.
func New ¶
func New() *ShardedMetadataMap
New returns an empty ShardedMetadataMap with all shards pre-allocated.
func (*ShardedMetadataMap) Delete ¶
func (s *ShardedMetadataMap) Delete(mac uint64)
Delete unconditionally removes mac. Intended for the GC after the Lingering Ghost window has expired and the kernel `mac_tenant_map` entry has already been removed. Most call sites that observe a Neutron deletion event should call ShardedMetadataMap.MarkDelete instead.
Full rationale: docs/architecture/data-structures.md#map-lifecycle-invariants
func (*ShardedMetadataMap) Insert ¶
func (s *ShardedMetadataMap) Insert(mac uint64, meta *TenantMeta)
Insert stores meta under mac, replacing any prior pointer. Callers must treat meta as immutable from the moment Insert returns; other goroutines may already hold the prior pointer and will continue to read its (frozen) fields.
func (*ShardedMetadataMap) Len ¶
func (s *ShardedMetadataMap) Len() int
Len returns the total number of entries across every shard. It acquires each shard's read lock in turn, so it is O(numShards) in lock operations and not intended for hot-path use.
func (*ShardedMetadataMap) Lookup ¶
func (s *ShardedMetadataMap) Lookup(mac uint64) (*TenantMeta, bool)
Lookup returns the metadata pointer for mac, or (nil, false) if unknown. The returned pointer is safe to retain past the call: the immutability invariant guarantees its fields will not change.
func (*ShardedMetadataMap) MarkDelete ¶
func (s *ShardedMetadataMap) MarkDelete(mac uint64, at time.Time) bool
MarkDelete sets `DeleteAt = at` on the entry for mac by replacing its *TenantMeta pointer with a copy whose DeleteAt field is updated. Returns false if mac is unknown. The kernel `mac_tenant_map` entry is NOT touched; that deletion is the GC's responsibility once the grace window passes.
func (*ShardedMetadataMap) Range ¶
func (s *ShardedMetadataMap) Range(f func(mac uint64, meta *TenantMeta) bool)
Range calls f for every live entry, shard-by-shard under a per-shard read lock — so f must not call any method that write-locks the same shard, which deadlocks. Returning false stops early. Snapshot semantics are weak: later shards may reflect concurrent writes.
type TenantInterner ¶
type TenantInterner struct {
// contains filtered or unexported fields
}
TenantInterner bridges the userspace project UUID to the compact u32 the kernel maps key on.
The mapping is in-memory and rebuilt every boot, which is safe only because no u32 leaks outside the kernel maps — bpf.FlowKey and [state.Record] are u32-free by invariant, so WAL-restored state merges cleanly whatever this boot assigns.
One mutex: volume is bounded by tenant count, and the scrape path reads ProjectID strings directly rather than consulting it.
func NewTenantInterner ¶
func NewTenantInterner() *TenantInterner
NewTenantInterner returns an empty interner. The first [Intern] call against a non-empty ProjectID will return 1.
func (*TenantInterner) Intern ¶
func (t *TenantInterner) Intern(projectID string) uint32
Intern returns the u32 ID for projectID, assigning a new monotonic ID on first sight. An empty projectID always returns TenantIDUnset without mutating state — useful for "unbound" or "admin-owned" rows where no project assignment is meaningful.
func (*TenantInterner) Len ¶
func (t *TenantInterner) Len() int
Len returns the number of interned project IDs.
func (*TenantInterner) Lookup ¶
func (t *TenantInterner) Lookup(projectID string) (uint32, bool)
Lookup returns the u32 for projectID without assigning a new one. Returns (TenantIDUnset, false) for unknown or empty input.
func (*TenantInterner) Reverse ¶
func (t *TenantInterner) Reverse(id uint32) (string, bool)
Reverse returns the ProjectID for an interned id. Returns ("", false) for TenantIDUnset or any never-assigned id. Used by `/debug` endpoints that want a human-readable label.
type TenantMeta ¶
type TenantMeta struct {
// ProjectID is the Keystone project UUID (e.g.
// "8e1b...c4f2"). It is emitted as the `tenant_id` Prometheus
// label and is the user-visible billing identity.
ProjectID string
// ServerID is the Neutron port's device_id — the Nova instance
// UUID for VM ports. Emitted as the `server_id` label on the
// per-server metric family; empty when the port carries no device
// binding.
//
// Billing tiers: docs/architecture/billing.md
ServerID string
// PortID is the Neutron port UUID. Emitted as the `port_id` label on
// the per-server family so a server's traffic is broken out per
// port; consumers aggregate back to server_id. A MAC maps to
// exactly one port, so it is stable for the life of the MAC — a
// recreated port is a new MAC, hence a new series.
//
// Billing tiers: docs/architecture/billing.md
PortID string
// ExternalNetwork is the human-facing label of the external
// network this VM's egress leaves through — its floating IP's
// network, or its router's external gateway network (network name,
// falling back to ID when the name is empty). Empty when the VM has
// no external path. Only EXTERNAL-zone series carry it; see
// [ExternalNetworkLabel] for the zone gate every emitter applies.
ExternalNetwork string
// IsAmphora marks an Octavia load-balancer Amphora port. The
// per-packet hot path branches on this flag to attribute LB
// traffic to the load-balancer owner rather than the admin
// project that owns the Amphora itself.
//
// Full rationale: docs/architecture/scenarios.md
IsAmphora bool
// DeleteAt is zero for live entries. The Lingering Ghost window
// sets it on a Neutron `port.deleted` / `subnet.deleted` event:
// MarkDelete callers use now + the live `gc.ghost_grace` tunable
// (default 60s — internal/tunables), and the GC drops the entry
// once `DeleteAt < now`.
//
// Lingering Ghost: docs/architecture/data-structures.md#lingering-ghost
DeleteAt time.Time
}
TenantMeta is the userspace metadata for one VM MAC. Once stored it is IMMUTABLE — see the package doc.
func (TenantMeta) SameAttribution ¶
func (m TenantMeta) SameAttribution(o TenantMeta) bool
SameAttribution reports whether two metas carry the same attribution identity — every field EXCEPT the lifecycle ones. It is the reconcile change-detection's single comparison point.
Implemented as a whole-struct compare with lifecycle fields zeroed, NOT a field list: a new field is then attribution-compared BY DEFAULT, which fails safe. A spurious settle is idempotent; a missed one silently mislabels — the bug this replaced. A new LIFECYCLE field must be zeroed here and classified in the schema guard test.