benzene.mesh_fleet

Cloud service discovery and fleet trace-mappers for a Benzene mesh — the two adapters a mesh grows once it spans a real fleet. Distribution: benzene-mesh-fleet (depends on benzene-core, benzene-mesh).

pip install benzene-mesh-fleet            # StaticDiscovery + all three trace-mappers, no SDK
pip install "benzene-mesh-fleet[aws]"     # + boto3 for AWS Cloud Map discovery
pip install "benzene-mesh-fleet[azure]"
pip install "benzene-mesh-fleet[kubernetes]"

Overview

Two adjacent capabilities the mesh needs once it spans a real fleet, both of which the port already had the model for and only needed the adapters:

Every cloud SDK is an optional, lazily-imported extra ([aws] / [azure] / [kubernetes]) and every client is injectable, so the whole package — and its tests — import and run with no SDK present. Mirrors .NET's Benzene.Mesh.Discovery.* and Benzene.Mesh.Fleet.*.

Discover the mesh

Discovery is a runtime_checkable protocol: a single async def discover(self) -> list[ServiceEndpoint]. It must never raise for an empty mesh — an unpopulated namespace or a registry with no matching services is an empty list, not an error.

ServiceEndpoint is a frozen, hashable dataclass — the lowest common denominator every cloud registry can produce: name (the mesh service identity, the same value a benzene.mesh.ServiceDescriptor carries in service), address (a URL or host the poller/router reaches), and free-form metadata (availability zone, instance id, ARN, labels — whatever the registry knew and the port did not model).

from benzene.mesh_fleet import KubernetesDiscovery, ServiceEndpoint, StaticDiscovery

# The SDK-free default / test double.
discovery = StaticDiscovery([ServiceEndpoint("orders", "https://orders.svc")])
endpoints = await discovery.discover()

# Or read a live registry (boto3 / azure / kubernetes imported lazily, client injectable):
discovery = KubernetesDiscovery(namespace="mesh", label_selector="benzene.io/mesh=true")
endpoints = await discovery.discover()  # [] for an empty namespace, never an error

Two invariants hold across all three cloud adapters (per the Discovery contract): an empty registry is an empty list rather than an error, and a discovered service with no resolvable address is skipped rather than emitted with a blank one.

Map traces to any backend

A mesh TraceEvent already is a cross-language span, so a TraceMapper is a pure field/units transform — no backend SDK, no network, no clock. TraceMapper is a runtime_checkable protocol with one method, map(trace) -> dict[str, Any], where a trace is either a single TraceEvent or the iterable of spans sharing a trace_id.

from benzene.mesh_fleet import JaegerTraceMapper, TempoTraceMapper, XRayTraceMapper

jaeger_doc = JaegerTraceMapper().map(spans)  # {"traceID", "spans", "processes"}, microseconds
tempo_doc = TempoTraceMapper().map(spans)    # OTLP-JSON {"resourceSpans"}, Unix nanoseconds
xray_doc = XRayTraceMapper().map(spans)      # X-Ray segment + subsegments, epoch seconds

The backends differ mostly in time units, which is where fidelity matters most:

Exports

Discovery, ServiceEndpoint, StaticDiscovery, AwsCloudMapDiscovery, AzureDiscovery, KubernetesDiscovery, TraceMapper, JaegerTraceMapper, TempoTraceMapper, XRayTraceMapper.

See also