@intentius/chant-lexicon-otel 0.95.1 → 0.96.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -9
- package/dist/catalog.d.ts +2 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/collector.d.ts +4 -1
- package/dist/collector.d.ts.map +1 -1
- package/dist/components/connectors.d.ts +156 -0
- package/dist/components/connectors.d.ts.map +1 -0
- package/dist/components/filtering.d.ts +95 -0
- package/dist/components/filtering.d.ts.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/k8s-receivers.d.ts +74 -0
- package/dist/components/k8s-receivers.d.ts.map +1 -0
- package/dist/components/sampling.d.ts +260 -0
- package/dist/components/sampling.d.ts.map +1 -0
- package/dist/define.d.ts +31 -4
- package/dist/define.d.ts.map +1 -1
- package/dist/genai.d.ts +165 -0
- package/dist/genai.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +8 -7
- package/dist/lint/audit-catalog.d.ts +2 -1
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/otel-helpers.d.ts +1 -1
- package/dist/lint/post-synth/otel-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/otel101.d.ts +1 -1
- package/dist/lint/post-synth/otel103.d.ts +1 -1
- package/dist/lint/post-synth/otel112.d.ts +8 -0
- package/dist/lint/post-synth/otel112.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +70 -0
- package/dist/metric-names.d.ts +121 -0
- package/dist/metric-names.d.ts.map +1 -0
- package/dist/model.d.ts +23 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/okf/index.md +15 -0
- package/dist/okf/rules/OTEL112.md +11 -0
- package/dist/okf/types/CountConnector.md +9 -0
- package/dist/okf/types/FilterProcessor.md +9 -0
- package/dist/okf/types/ForwardConnector.md +9 -0
- package/dist/okf/types/K8sClusterReceiver.md +9 -0
- package/dist/okf/types/KubeletStatsReceiver.md +9 -0
- package/dist/okf/types/LoadBalancingExporter.md +9 -0
- package/dist/okf/types/ProbabilisticSamplerProcessor.md +9 -0
- package/dist/okf/types/RedactionProcessor.md +9 -0
- package/dist/okf/types/RoutingConnector.md +9 -0
- package/dist/okf/types/ServiceGraphConnector.md +9 -0
- package/dist/okf/types/SpanMetricsConnector.md +9 -0
- package/dist/okf/types/SumConnector.md +9 -0
- package/dist/okf/types/TailSamplingProcessor.md +9 -0
- package/dist/okf/types/TransformProcessor.md +9 -0
- package/dist/pipeline.d.ts +9 -3
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/rules/otel-helpers.ts +1 -1
- package/dist/rules/otel101.ts +1 -1
- package/dist/rules/otel103.ts +1 -1
- package/dist/rules/otel112.ts +17 -0
- package/dist/semconv.d.ts +33 -0
- package/dist/semconv.d.ts.map +1 -0
- package/dist/serializer.d.ts +1 -1
- package/dist/skills/chant-otel.md +28 -5
- package/dist/topology.d.ts +34 -5
- package/dist/topology.d.ts.map +1 -1
- package/dist/validate-config.d.ts +6 -2
- package/dist/validate-config.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/catalog.ts +2 -1
- package/src/codegen/docs.ts +14 -8
- package/src/collector.ts +9 -1
- package/src/components/connectors.ts +276 -0
- package/src/components/filtering.test.ts +192 -0
- package/src/components/filtering.ts +192 -0
- package/src/components/index.ts +4 -0
- package/src/components/k8s-receivers.test.ts +123 -0
- package/src/components/k8s-receivers.ts +107 -0
- package/src/components/sampling.test.ts +464 -0
- package/src/components/sampling.ts +521 -0
- package/src/connectors.test.ts +388 -0
- package/src/define.ts +36 -4
- package/src/genai.test.ts +526 -0
- package/src/genai.ts +466 -0
- package/src/generated/lexicon-otel.json +70 -0
- package/src/index.ts +39 -0
- package/src/lint/audit-catalog.ts +11 -2
- package/src/lint/post-synth/index.ts +2 -0
- package/src/lint/post-synth/otel-helpers.ts +1 -1
- package/src/lint/post-synth/otel101.ts +1 -1
- package/src/lint/post-synth/otel103.ts +1 -1
- package/src/lint/post-synth/otel112.ts +17 -0
- package/src/metric-names.test.ts +80 -0
- package/src/metric-names.ts +176 -0
- package/src/model.ts +26 -4
- package/src/otelcol-validate.test.ts +143 -0
- package/src/pipeline.ts +9 -3
- package/src/plugin.test.ts +1 -0
- package/src/plugin.ts +1 -1
- package/src/semconv.ts +63 -0
- package/src/serializer.ts +1 -1
- package/src/skills/chant-otel.md +28 -5
- package/src/topology.ts +54 -8
- package/src/validate-config.ts +92 -4
- package/src/validate.ts +8 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
6
|
+
import { load } from "js-yaml";
|
|
7
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
8
|
+
import { makePostSynthCtx } from "@intentius/chant-test-utils";
|
|
9
|
+
import { otelSerializer } from "../serializer";
|
|
10
|
+
import { collectorTopology } from "../topology";
|
|
11
|
+
import { validateCollectorEntities } from "../validate-config";
|
|
12
|
+
import { otel107 } from "../lint/post-synth/otel107";
|
|
13
|
+
import {
|
|
14
|
+
BatchProcessor,
|
|
15
|
+
DebugExporter,
|
|
16
|
+
LoadBalancingExporter,
|
|
17
|
+
MemoryLimiterProcessor,
|
|
18
|
+
OtlpExporter,
|
|
19
|
+
OtlpReceiver,
|
|
20
|
+
Pipeline,
|
|
21
|
+
ProbabilisticSamplerProcessor,
|
|
22
|
+
TailSamplingProcessor,
|
|
23
|
+
loadBalancingEndpoints,
|
|
24
|
+
type TailSamplingPolicy,
|
|
25
|
+
} from "../index";
|
|
26
|
+
|
|
27
|
+
function entities(record: Record<string, unknown>): Map<string, Declarable> {
|
|
28
|
+
return new Map(Object.entries(record) as Array<[string, Declarable]>);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function yamlOf(record: Record<string, unknown>): string {
|
|
32
|
+
const out = otelSerializer.serialize(entities(record));
|
|
33
|
+
return typeof out === "string" ? out : out.primary;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function problems(e: Declarable): string[] {
|
|
37
|
+
return otel107.check(makePostSynthCtx("otel", "", new Map([["x", e]]))).map((d) => d.message);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const POLICIES: TailSamplingPolicy[] = [
|
|
41
|
+
{
|
|
42
|
+
name: "errors",
|
|
43
|
+
type: "status_code",
|
|
44
|
+
status_code: { status_codes: ["ERROR"] },
|
|
45
|
+
},
|
|
46
|
+
{ name: "slow", type: "latency", latency: { threshold_ms: 2000 } },
|
|
47
|
+
{
|
|
48
|
+
name: "agent-failed",
|
|
49
|
+
type: "string_attribute",
|
|
50
|
+
string_attribute: { key: "gen_ai.outcome", values: ["failed"] },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "big-response",
|
|
54
|
+
type: "numeric_attribute",
|
|
55
|
+
numeric_attribute: { key: "gen_ai.usage.output_tokens", min_value: 4000 },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "rate",
|
|
59
|
+
type: "rate_limiting",
|
|
60
|
+
rate_limiting: { spans_per_second: 500 },
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "checkout-sampled",
|
|
64
|
+
type: "and",
|
|
65
|
+
and: {
|
|
66
|
+
and_sub_policy: [
|
|
67
|
+
{
|
|
68
|
+
name: "checkout",
|
|
69
|
+
type: "string_attribute",
|
|
70
|
+
string_attribute: { key: "service.name", values: ["checkout"] },
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "tenth",
|
|
74
|
+
type: "probabilistic",
|
|
75
|
+
probabilistic: { sampling_percentage: 10 },
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "budget",
|
|
82
|
+
type: "composite",
|
|
83
|
+
composite: {
|
|
84
|
+
max_total_spans_per_second: 1000,
|
|
85
|
+
policy_order: ["c-errors", "c-rest"],
|
|
86
|
+
composite_sub_policy: [
|
|
87
|
+
{
|
|
88
|
+
name: "c-errors",
|
|
89
|
+
type: "status_code",
|
|
90
|
+
status_code: { status_codes: ["ERROR"] },
|
|
91
|
+
},
|
|
92
|
+
{ name: "c-rest", type: "always_sample" },
|
|
93
|
+
],
|
|
94
|
+
rate_allocation: [
|
|
95
|
+
{ policy: "c-errors", percent: 60 },
|
|
96
|
+
{ policy: "c-rest", percent: 40 },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "rest",
|
|
102
|
+
type: "probabilistic",
|
|
103
|
+
probabilistic: { sampling_percentage: 5 },
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
/** The gateway side: receive OTLP, head-sample, tail-sample whole traces, export. */
|
|
108
|
+
function gateway(): Record<string, unknown> {
|
|
109
|
+
const otlp = new OtlpReceiver({
|
|
110
|
+
protocols: { grpc: { endpoint: "0.0.0.0:4317" } },
|
|
111
|
+
});
|
|
112
|
+
const limiter = new MemoryLimiterProcessor({
|
|
113
|
+
check_interval: "1s",
|
|
114
|
+
limit_percentage: 80,
|
|
115
|
+
spike_limit_percentage: 20,
|
|
116
|
+
});
|
|
117
|
+
const head = new ProbabilisticSamplerProcessor({
|
|
118
|
+
sampling_percentage: 50,
|
|
119
|
+
mode: "proportional",
|
|
120
|
+
});
|
|
121
|
+
const tail = new TailSamplingProcessor({
|
|
122
|
+
decision_wait: "10s",
|
|
123
|
+
num_traces: 50000,
|
|
124
|
+
policies: POLICIES,
|
|
125
|
+
});
|
|
126
|
+
const batch = new BatchProcessor({});
|
|
127
|
+
const tempo = new OtlpExporter({
|
|
128
|
+
name: "tempo",
|
|
129
|
+
endpoint: "tempo:4317",
|
|
130
|
+
tls: { insecure: true },
|
|
131
|
+
});
|
|
132
|
+
const traces = new Pipeline({
|
|
133
|
+
signal: "traces",
|
|
134
|
+
receivers: [otlp],
|
|
135
|
+
processors: [limiter, head, tail, batch],
|
|
136
|
+
exporters: [tempo],
|
|
137
|
+
});
|
|
138
|
+
return { otlp, limiter, head, tail, batch, tempo, traces };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The agent side: route every span of a trace to one gateway replica. */
|
|
142
|
+
function agent(): Record<string, unknown> {
|
|
143
|
+
const otlp = new OtlpReceiver({
|
|
144
|
+
protocols: { grpc: { endpoint: "0.0.0.0:4317" } },
|
|
145
|
+
});
|
|
146
|
+
const k8s = new LoadBalancingExporter({
|
|
147
|
+
name: "gateway",
|
|
148
|
+
routing_key: "traceID",
|
|
149
|
+
protocol: { otlp: { tls: { insecure: true }, timeout: "5s" } },
|
|
150
|
+
resolver: {
|
|
151
|
+
k8s: { service: "otel-gateway-headless.observability", ports: [4317] },
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
const fixed = new LoadBalancingExporter({
|
|
155
|
+
name: "fixed",
|
|
156
|
+
resolver: { static: { hostnames: ["gw-0.gw:4317", "gw-1.gw:4317"] } },
|
|
157
|
+
});
|
|
158
|
+
const debug = new DebugExporter({});
|
|
159
|
+
const traces = new Pipeline({
|
|
160
|
+
signal: "traces",
|
|
161
|
+
receivers: [otlp],
|
|
162
|
+
exporters: [k8s],
|
|
163
|
+
});
|
|
164
|
+
const logs = new Pipeline({
|
|
165
|
+
signal: "logs",
|
|
166
|
+
receivers: [otlp],
|
|
167
|
+
exporters: [fixed, debug],
|
|
168
|
+
});
|
|
169
|
+
return { otlp, k8s, fixed, debug, traces, logs };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
describe("tail_sampling", () => {
|
|
173
|
+
test("serializes policies under the key their type names", () => {
|
|
174
|
+
const parsed = load(yamlOf(gateway())) as any;
|
|
175
|
+
const ts = parsed.processors.tail_sampling;
|
|
176
|
+
expect(ts.decision_wait).toBe("10s");
|
|
177
|
+
expect(ts.num_traces).toBe(50000);
|
|
178
|
+
expect(ts.policies.map((p: any) => p.type)).toEqual([
|
|
179
|
+
"status_code",
|
|
180
|
+
"latency",
|
|
181
|
+
"string_attribute",
|
|
182
|
+
"numeric_attribute",
|
|
183
|
+
"rate_limiting",
|
|
184
|
+
"and",
|
|
185
|
+
"composite",
|
|
186
|
+
"probabilistic",
|
|
187
|
+
]);
|
|
188
|
+
expect(ts.policies[0]).toEqual({
|
|
189
|
+
name: "errors",
|
|
190
|
+
type: "status_code",
|
|
191
|
+
status_code: { status_codes: ["ERROR"] },
|
|
192
|
+
});
|
|
193
|
+
expect(ts.policies[5].and.and_sub_policy[1]).toEqual({
|
|
194
|
+
name: "tenth",
|
|
195
|
+
type: "probabilistic",
|
|
196
|
+
probabilistic: { sampling_percentage: 10 },
|
|
197
|
+
});
|
|
198
|
+
expect(ts.policies[6].composite.rate_allocation).toEqual([
|
|
199
|
+
{ policy: "c-errors", percent: 60 },
|
|
200
|
+
{ policy: "c-rest", percent: 40 },
|
|
201
|
+
]);
|
|
202
|
+
expect(parsed.service.pipelines.traces.processors).toEqual([
|
|
203
|
+
"memory_limiter",
|
|
204
|
+
"probabilistic_sampler",
|
|
205
|
+
"tail_sampling",
|
|
206
|
+
"batch",
|
|
207
|
+
]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("renders the policy list in block style", () => {
|
|
211
|
+
const yaml = yamlOf({
|
|
212
|
+
tail: new TailSamplingProcessor({
|
|
213
|
+
decision_wait: "5s",
|
|
214
|
+
policies: POLICIES.slice(0, 1),
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
217
|
+
expect(yaml).toBe(`processors:
|
|
218
|
+
tail_sampling:
|
|
219
|
+
decision_wait: 5s
|
|
220
|
+
policies:
|
|
221
|
+
- name: errors
|
|
222
|
+
type: status_code
|
|
223
|
+
status_code:
|
|
224
|
+
status_codes: [ERROR]
|
|
225
|
+
`);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("a valid gateway config has nothing to report", () => {
|
|
229
|
+
expect(validateCollectorEntities(entities(gateway()))).toEqual([]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("no decision_wait fails the per-component check", () => {
|
|
233
|
+
const tail = new TailSamplingProcessor({ policies: POLICIES } as never);
|
|
234
|
+
expect(problems(tail)).toEqual([expect.stringContaining("decision_wait is not set")]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("an empty policy list fails the per-component check", () => {
|
|
238
|
+
expect(problems(new TailSamplingProcessor({ decision_wait: "10s", policies: [] }))).toEqual([
|
|
239
|
+
expect.stringContaining("policies is empty"),
|
|
240
|
+
]);
|
|
241
|
+
expect(problems(new TailSamplingProcessor({ decision_wait: "10s" } as never))).toEqual([
|
|
242
|
+
expect.stringContaining("policies is empty"),
|
|
243
|
+
]);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("policy-level problems", () => {
|
|
247
|
+
const bad = new TailSamplingProcessor({
|
|
248
|
+
decision_wait: "10s",
|
|
249
|
+
policies: [
|
|
250
|
+
{
|
|
251
|
+
name: "a",
|
|
252
|
+
type: "status_code",
|
|
253
|
+
status_code: { status_codes: ["FAILED" as never] },
|
|
254
|
+
},
|
|
255
|
+
{ name: "a", type: "latency", latency: { threshold_ms: 0 } },
|
|
256
|
+
{
|
|
257
|
+
name: "",
|
|
258
|
+
type: "probabilistic",
|
|
259
|
+
probabilistic: { sampling_percentage: 150 },
|
|
260
|
+
},
|
|
261
|
+
{ name: "b", type: "string_attribute" } as never,
|
|
262
|
+
{ name: "c", type: "and", and: { and_sub_policy: [] } },
|
|
263
|
+
{
|
|
264
|
+
name: "d",
|
|
265
|
+
type: "composite",
|
|
266
|
+
composite: {
|
|
267
|
+
max_total_spans_per_second: 100,
|
|
268
|
+
policy_order: ["x"],
|
|
269
|
+
composite_sub_policy: [{ name: "y", type: "always_sample" }],
|
|
270
|
+
rate_allocation: [{ policy: "z", percent: 120 }],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
});
|
|
275
|
+
const msgs = problems(bad);
|
|
276
|
+
expect(msgs).toEqual([
|
|
277
|
+
expect.stringContaining('status code "FAILED" is not OK, ERROR or UNSET'),
|
|
278
|
+
expect.stringContaining('policy name "a" is repeated'),
|
|
279
|
+
expect.stringContaining("latency needs threshold_ms or upper_threshold_ms above 0"),
|
|
280
|
+
expect.stringContaining("policies[2] has no name"),
|
|
281
|
+
expect.stringContaining("sampling_percentage must be above 0 and at most 100"),
|
|
282
|
+
expect.stringContaining("type string_attribute needs a string_attribute block"),
|
|
283
|
+
expect.stringContaining("and.and_sub_policy is empty"),
|
|
284
|
+
expect.stringContaining('policy_order names "x"'),
|
|
285
|
+
expect.stringContaining('rate_allocation names "z"'),
|
|
286
|
+
expect.stringContaining("add up to 120"),
|
|
287
|
+
]);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("probabilistic_sampler", () => {
|
|
292
|
+
test("serializes as given", () => {
|
|
293
|
+
const parsed = load(
|
|
294
|
+
yamlOf({
|
|
295
|
+
p: new ProbabilisticSamplerProcessor({
|
|
296
|
+
sampling_percentage: 15.3,
|
|
297
|
+
mode: "hash_seed",
|
|
298
|
+
hash_seed: 22,
|
|
299
|
+
}),
|
|
300
|
+
}),
|
|
301
|
+
) as any;
|
|
302
|
+
expect(parsed.processors.probabilistic_sampler).toEqual({
|
|
303
|
+
sampling_percentage: 15.3,
|
|
304
|
+
mode: "hash_seed",
|
|
305
|
+
hash_seed: 22,
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("checks", () => {
|
|
310
|
+
expect(problems(new ProbabilisticSamplerProcessor({ sampling_percentage: 25 }))).toEqual([]);
|
|
311
|
+
expect(problems(new ProbabilisticSamplerProcessor({} as never))).toEqual([expect.stringContaining("keeps nothing")]);
|
|
312
|
+
expect(
|
|
313
|
+
problems(
|
|
314
|
+
new ProbabilisticSamplerProcessor({
|
|
315
|
+
sampling_percentage: 101,
|
|
316
|
+
sampling_precision: 15,
|
|
317
|
+
attribute_source: "record",
|
|
318
|
+
mode: "proportional",
|
|
319
|
+
hash_seed: 1,
|
|
320
|
+
}),
|
|
321
|
+
),
|
|
322
|
+
).toEqual([
|
|
323
|
+
expect.stringContaining("between 0 and 100"),
|
|
324
|
+
expect.stringContaining("sampling_precision (15)"),
|
|
325
|
+
expect.stringContaining("from_attribute is not set"),
|
|
326
|
+
expect.stringContaining("only read in hash_seed mode"),
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("loadbalancing", () => {
|
|
332
|
+
test("serializes the resolver, routing key and nested otlp protocol", () => {
|
|
333
|
+
const parsed = load(yamlOf(agent())) as any;
|
|
334
|
+
expect(parsed.exporters["loadbalancing/gateway"]).toEqual({
|
|
335
|
+
routing_key: "traceID",
|
|
336
|
+
protocol: { otlp: { tls: { insecure: true }, timeout: "5s" } },
|
|
337
|
+
resolver: {
|
|
338
|
+
k8s: { service: "otel-gateway-headless.observability", ports: [4317] },
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
expect(parsed.exporters["loadbalancing/fixed"]).toEqual({
|
|
342
|
+
resolver: { static: { hostnames: ["gw-0.gw:4317", "gw-1.gw:4317"] } },
|
|
343
|
+
});
|
|
344
|
+
expect(validateCollectorEntities(entities(agent()))).toEqual([]);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("topology reports the resolver's backends", () => {
|
|
348
|
+
const topo = collectorTopology(load(yamlOf(agent())) as never);
|
|
349
|
+
const byId = Object.fromEntries(topo.exporters.map((e) => [e.id, e]));
|
|
350
|
+
expect(byId["loadbalancing/gateway"].endpoints).toEqual(["otel-gateway-headless.observability:4317"]);
|
|
351
|
+
expect(byId["loadbalancing/gateway"].signals).toEqual(["traces"]);
|
|
352
|
+
expect(byId["loadbalancing/fixed"].endpoints).toEqual(["gw-0.gw:4317", "gw-1.gw:4317"]);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("loadBalancingEndpoints covers every resolver", () => {
|
|
356
|
+
expect(loadBalancingEndpoints({ k8s: { service: "gw.obs" } })).toEqual(["gw.obs:4317"]);
|
|
357
|
+
expect(loadBalancingEndpoints({ k8s: { service: "gw", ports: [4317, 55690] } })).toEqual(["gw:4317", "gw:55690"]);
|
|
358
|
+
expect(loadBalancingEndpoints({ dns: { hostname: "gw-headless" } })).toEqual(["gw-headless:4317"]);
|
|
359
|
+
expect(
|
|
360
|
+
loadBalancingEndpoints({
|
|
361
|
+
aws_cloud_map: { namespace: "ns", service_name: "gw" },
|
|
362
|
+
}),
|
|
363
|
+
).toEqual(["aws-cloud-map://ns/gw"]);
|
|
364
|
+
expect(loadBalancingEndpoints(undefined)).toEqual([]);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("checks", () => {
|
|
368
|
+
expect(problems(new LoadBalancingExporter({ resolver: {} as never }))).toEqual([
|
|
369
|
+
expect.stringContaining("resolver has none of"),
|
|
370
|
+
]);
|
|
371
|
+
expect(
|
|
372
|
+
problems(
|
|
373
|
+
new LoadBalancingExporter({
|
|
374
|
+
resolver: {
|
|
375
|
+
static: { hostnames: [] },
|
|
376
|
+
k8s: { service: "" },
|
|
377
|
+
} as never,
|
|
378
|
+
routing_key: "attributes",
|
|
379
|
+
protocol: { otlp: { endpoint: "x:4317" } as never },
|
|
380
|
+
}),
|
|
381
|
+
),
|
|
382
|
+
).toEqual([
|
|
383
|
+
expect.stringContaining("sets static and k8s"),
|
|
384
|
+
expect.stringContaining("hostnames is empty"),
|
|
385
|
+
expect.stringContaining("k8s.service is empty"),
|
|
386
|
+
expect.stringContaining("routing_attributes is empty"),
|
|
387
|
+
expect.stringContaining("protocol.otlp.endpoint is ignored"),
|
|
388
|
+
]);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// ── otelcol validate ─────────────────────────────────────────────────
|
|
393
|
+
//
|
|
394
|
+
// The acceptance test is the collector itself: write the rendered config and
|
|
395
|
+
// run `otelcol-contrib validate --config` on it. The binary must be a contrib
|
|
396
|
+
// build (the core `otelcol` distribution ships none of these components), so
|
|
397
|
+
// the test looks for OTELCOL_BIN, then otelcol-contrib, then an otelcol that
|
|
398
|
+
// lists tail_sampling among its components, and skips with the reason when
|
|
399
|
+
// none is on PATH. CI does not install one.
|
|
400
|
+
|
|
401
|
+
function findCollector(): string | undefined {
|
|
402
|
+
const candidates = [process.env.OTELCOL_BIN, "otelcol-contrib", "otelcol"].filter((c): c is string => !!c);
|
|
403
|
+
for (const bin of candidates) {
|
|
404
|
+
try {
|
|
405
|
+
const components = execFileSync(bin, ["components"], {
|
|
406
|
+
encoding: "utf8",
|
|
407
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
408
|
+
});
|
|
409
|
+
if (components.includes("tail_sampling") && components.includes("loadbalancing")) return bin;
|
|
410
|
+
} catch {
|
|
411
|
+
// not installed, or not a contrib build
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const collector = findCollector();
|
|
418
|
+
|
|
419
|
+
describe.skipIf(!collector)(
|
|
420
|
+
`otelcol validate accepts the rendered config${collector ? "" : " (skipped: no otelcol-contrib on PATH)"}`,
|
|
421
|
+
() => {
|
|
422
|
+
let dir = "";
|
|
423
|
+
let kubeconfig = "";
|
|
424
|
+
|
|
425
|
+
// `validate` builds every component, and the k8s resolver builds a
|
|
426
|
+
// Kubernetes client while it does, so it needs some kubeconfig. A stub one
|
|
427
|
+
// is enough: nothing connects until the collector starts.
|
|
428
|
+
beforeAll(() => {
|
|
429
|
+
dir = mkdtempSync(join(tmpdir(), "chant-otel-sampling-"));
|
|
430
|
+
kubeconfig = join(dir, "kubeconfig");
|
|
431
|
+
writeFileSync(
|
|
432
|
+
kubeconfig,
|
|
433
|
+
[
|
|
434
|
+
"apiVersion: v1",
|
|
435
|
+
"kind: Config",
|
|
436
|
+
"clusters: [{ name: stub, cluster: { server: 'https://127.0.0.1:1' } }]",
|
|
437
|
+
"users: [{ name: stub, user: { token: stub } }]",
|
|
438
|
+
"contexts: [{ name: stub, context: { cluster: stub, user: stub } }]",
|
|
439
|
+
"current-context: stub",
|
|
440
|
+
"",
|
|
441
|
+
].join("\n"),
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
afterAll(() => rmSync(dir, { recursive: true, force: true }));
|
|
445
|
+
|
|
446
|
+
function validate(name: string, yaml: string): void {
|
|
447
|
+
const file = join(dir, `${name}.yaml`);
|
|
448
|
+
writeFileSync(file, yaml);
|
|
449
|
+
execFileSync(collector!, ["validate", `--config=${file}`], {
|
|
450
|
+
encoding: "utf8",
|
|
451
|
+
stdio: "pipe",
|
|
452
|
+
env: { ...process.env, KUBECONFIG: kubeconfig },
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
test("gateway: probabilistic_sampler and tail_sampling with every policy kind", () => {
|
|
457
|
+
expect(() => validate("gateway", yamlOf(gateway()))).not.toThrow();
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("agent: loadbalancing with the k8s and static resolvers", () => {
|
|
461
|
+
expect(() => validate("agent", yamlOf(agent()))).not.toThrow();
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
);
|