Skip to content

Commit 958faad

Browse files
committed
Enhance runtime service to support optional OTLP/gRPC spans alongside OTLP/HTTP. Update configuration to include gRPC listener address and modify documentation to reflect new capabilities. Improve test coverage for gRPC functionality and ensure source summary handling in the ingestion process.
1 parent d9af119 commit 958faad

16 files changed

Lines changed: 370 additions & 60 deletions

File tree

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Bering is a discovery and publishing layer for service topology and endpoint con
55
It supports two operating modes:
66

77
- deterministic batch discovery from trace files and directories
8-
- long-running runtime discovery that accepts OTLP/HTTP spans and publishes rolling snapshot envelopes for observability consumers
8+
- long-running runtime discovery that accepts OTLP/HTTP and optional OTLP/gRPC spans and publishes rolling snapshot envelopes for observability consumers
99

1010
Bering owns discovery and discovery-side public contracts. It does not own simulation, gating, chaos execution, or policy decisions.
1111

@@ -40,7 +40,7 @@ cmd/bering CLI entrypoint
4040
internal/app command wiring
4141
internal/config serve-mode config parsing and validation
4242
internal/connectors/traces file/dir trace loading and normalization
43-
internal/connectors/otlp OTLP/HTTP request decoding into normalized spans
43+
internal/connectors/otlp OTLP request decoding into normalized spans
4444
internal/discovery source-agnostic discovery engine and overlay application
4545
internal/model stable core model structs, semantic checks, canonical IO
4646
internal/overlay generic discovery overlay loader
@@ -59,7 +59,7 @@ scripts/ci CI helper scripts
5959
```bash
6060
bering discover --input <trace-file|dir> [--out bering-model.json] [--snapshot-out bering-snapshot.json] [--replicas replicas.yaml|json] [--overlay overlay.yaml] [--discovered-at RFC3339]
6161
bering validate --input <bering-model.json|bering-snapshot.json>
62-
bering serve --config configs/serve.sample.yaml [--listen :4318] [--window-size 30s] [--flush-interval 5s]
62+
bering serve --config configs/serve.sample.yaml [--listen :4318] [--grpc-listen :4317] [--window-size 30s] [--flush-interval 5s]
6363
```
6464

6565
## Quickstart
@@ -100,11 +100,12 @@ go run ./cmd/bering serve --config configs/serve.sample.yaml
100100
The runtime service exposes:
101101

102102
- `POST /v1/traces` for OTLP/HTTP trace ingest
103+
- OTLP/gRPC trace export service on the configured gRPC address
103104
- `GET /healthz`
104105
- `GET /readyz`
105106
- `GET /metrics`
106107

107-
The primary integration path is standard OpenTelemetry Collector or SDK exporters sending spans to Bering over OTLP/HTTP. No custom Collector build is required.
108+
The primary integration path remains standard OpenTelemetry Collector or SDK exporters sending spans to Bering over OTLP/HTTP. OTLP/gRPC is also supported for grpc-first collector topologies. No custom Collector build is required.
108109

109110
### 5) Use the stable model with Sheaft
110111

configs/serve.sample.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
server:
22
listen_address: ":4318"
3+
grpc_listen_address: ":4317"
34
max_request_bytes: 5242880
45
runtime:
56
flush_interval: 5s

docs/architecture.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Bering now has two user-facing flows built on one normalized discovery core.
1414

1515
### Runtime flow
1616

17-
1. `bering serve` accepts OTLP/HTTP `POST /v1/traces` requests.
18-
2. OTLP requests are normalized into the same internal `traces.Span` shape used by batch mode.
17+
1. `bering serve` accepts OTLP/HTTP `POST /v1/traces` requests and optional OTLP/gRPC trace export requests.
18+
2. Both OTLP transports are normalized into the same internal `traces.Span` shape used by batch mode.
1919
3. Spans are accumulated in a bounded active tumbling window.
2020
4. On schedule, the window closes and Bering runs discovery.
2121
5. Bering computes a stable topology digest, diffs against the previous snapshot, and writes the snapshot to sinks.
@@ -43,7 +43,7 @@ Fields used today:
4343
- timestamps: `start_time`, `end_time`
4444
- discovery-relevant attributes: HTTP and messaging attributes
4545

46-
This keeps discovery logic source-agnostic. File JSON, OTLP/HTTP, and future adapters all converge on the same shape.
46+
This keeps discovery logic source-agnostic. File JSON, OTLP/HTTP, OTLP/gRPC, and future adapters all converge on the same shape.
4747

4848
## Contracts
4949

docs/migration-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ You can now opt into additional discovery-side features.
1717
- `--overlay` for generic discovery metadata overlays
1818
- `--snapshot-out` for a snapshot envelope in batch mode
1919
- `bering serve` for OTLP/HTTP runtime discovery
20+
- optional OTLP/gRPC ingest on a separate runtime listener
2021

2122
## Sheaft and other downstream model consumers
2223

docs/mvp-scope-and-limits.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## In scope
44

55
- deterministic batch discovery from trace files/directories
6-
- long-running runtime service that accepts OTLP/HTTP spans
6+
- long-running runtime service that accepts OTLP/HTTP spans, with optional OTLP/gRPC ingest
77
- stable core model artifacts (`io.mb3r.bering.model`)
88
- snapshot envelopes for observability/runtime consumers (`io.mb3r.bering.snapshot`)
99
- generic discovery overlays for metadata and predicate references

docs/runtime-config.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ TCP listen address for the HTTP server.
1212

1313
Example: `":4318"`
1414

15+
### `server.grpc_listen_address`
16+
17+
Optional TCP listen address for the OTLP/gRPC server.
18+
19+
Example: `":4317"`
20+
21+
Leave empty to disable OTLP/gRPC.
22+
1523
### `server.max_request_bytes`
1624

1725
Maximum OTLP request body size in bytes after decompression.
@@ -88,6 +96,7 @@ overlays:
8896

8997
- `--config`
9098
- `--listen`
99+
- `--grpc-listen`
91100
- `--flush-interval`
92101
- `--window-size`
93102
- `--max-in-memory-spans`

docs/trace-input-format.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Trace Input Format
22

3-
Bering supports two batch JSON formats and one runtime network ingest path.
3+
Bering supports two batch JSON formats and two runtime network ingest paths.
44

55
## 1) Normalized spans JSON
66

@@ -67,6 +67,15 @@ Supported request encodings:
6767

6868
This is the primary integration path for any standard OpenTelemetry Collector or SDK exporter.
6969

70+
## 4) Runtime OTLP/gRPC ingest
71+
72+
`bering serve` optionally accepts OTLP/gRPC on `server.grpc_listen_address`.
73+
74+
Supported request shape:
75+
76+
- standard `opentelemetry.proto.collector.trace.v1.TraceService/Export`
77+
- protobuf OTLP payloads normalized through the same span conversion path as OTLP/HTTP
78+
7079
## Discovery-relevant attributes
7180

7281
### HTTP endpoint inference

examples/collector/otelcol.sidecar.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ exporters:
1212
endpoint: http://bering:4318
1313
tls:
1414
insecure: true
15+
# Alternative OTLP/gRPC exporter when Bering is configured with grpc_listen_address.
16+
otlp/bering:
17+
endpoint: bering:4317
18+
tls:
19+
insecure: true
1520

1621
service:
1722
pipelines:

internal/app/app.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ func (r Runner) runServe(args []string) int {
233233

234234
configPath := fs.String("config", "", "Path to serve config YAML/JSON")
235235
listen := fs.String("listen", "", "Override config server listen address")
236+
grpcListen := fs.String("grpc-listen", "", "Override config server OTLP/gRPC listen address")
236237
flushInterval := fs.String("flush-interval", "", "Override config runtime flush interval (e.g. 5s)")
237238
windowSize := fs.String("window-size", "", "Override config runtime window size (e.g. 30s)")
238239
maxInMemory := fs.Int("max-in-memory-spans", -1, "Override config runtime max in-memory spans")
@@ -256,6 +257,9 @@ func (r Runner) runServe(args []string) int {
256257
if strings.TrimSpace(*listen) != "" {
257258
cfg.Server.ListenAddress = strings.TrimSpace(*listen)
258259
}
260+
if strings.TrimSpace(*grpcListen) != "" {
261+
cfg.Server.GRPCListenAddress = strings.TrimSpace(*grpcListen)
262+
}
259263
if strings.TrimSpace(*flushInterval) != "" {
260264
parsed, err := time.ParseDuration(strings.TrimSpace(*flushInterval))
261265
if err != nil {
@@ -320,7 +324,7 @@ func (r Runner) printUsage() {
320324
fmt.Fprintln(r.stdout, "Usage:")
321325
fmt.Fprintln(r.stdout, " bering discover --input <trace-file|dir> [--out bering-model.json] [--snapshot-out bering-snapshot.json] [--replicas replicas.yaml|json] [--overlay overlay.yaml] [--discovered-at RFC3339]")
322326
fmt.Fprintln(r.stdout, " bering validate --input <bering-model.json|bering-snapshot.json>")
323-
fmt.Fprintln(r.stdout, " bering serve --config configs/serve.sample.yaml [--listen :8080] [--window-size 30s] [--flush-interval 5s]")
327+
fmt.Fprintln(r.stdout, " bering serve --config configs/serve.sample.yaml [--listen :8080] [--grpc-listen :4317] [--window-size 30s] [--flush-interval 5s]")
324328
}
325329

326330
func (r Runner) printf(format string, args ...any) {

internal/app/app_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestDiscoverAndValidate_NormalizedFixture(t *testing.T) {
4646
if err != nil {
4747
t.Fatalf("read expected output: %v", err)
4848
}
49-
if !bytes.Equal(actualRaw, expectedRaw) {
49+
if !bytes.Equal(normalizeNewlines(actualRaw), normalizeNewlines(expectedRaw)) {
5050
t.Fatalf("discover output mismatch\nactual:\n%s\nexpected:\n%s", actualRaw, expectedRaw)
5151
}
5252

@@ -96,7 +96,7 @@ func TestDiscoverAndValidate_OTLPFixture(t *testing.T) {
9696
if err != nil {
9797
t.Fatalf("read expected output: %v", err)
9898
}
99-
if !bytes.Equal(actualRaw, expectedRaw) {
99+
if !bytes.Equal(normalizeNewlines(actualRaw), normalizeNewlines(expectedRaw)) {
100100
t.Fatalf("discover output mismatch\nactual:\n%s\nexpected:\n%s", actualRaw, expectedRaw)
101101
}
102102

@@ -169,3 +169,7 @@ func repoRoot(t *testing.T) string {
169169
}
170170
return filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
171171
}
172+
173+
func normalizeNewlines(raw []byte) []byte {
174+
return bytes.ReplaceAll(raw, []byte("\r\n"), []byte("\n"))
175+
}

0 commit comments

Comments
 (0)