Files
zeekayandHanzo Dev f337a4b21e one /v1/o11y surface: kill /api and every version segment, unbreak main, migrate consumers to the event plane
MAIN HAD NOT COMPILED SINCE 2026-07-30. Commit 5e3210f759 rewrote tables.go in
three telemetry packages and the sentrytypes Event struct without updating their
consumers, while its message claimed the read paths 'build clean' — proven false
with a detached worktree at clean HEAD. That is why the deployed backend is
SHA-pinned and stale: CI could not build main at all.

THE RENAME: /api/v1|v2|v3|v4/<rest> -> /v1/o11y/<rest> (290 replacements; the
host already says api, so the path never does, and one live generation carries
no version segments). 21 duplicate v1 registrations DELETED with their v2 twins
kept — gorilla/mux is first-match, so after the collapse the v1 line would have
shadowed v2 silently, with no error, forever: rules (6), dashboards (6),
services/top_operations/entry_point_operations/ttl (5), features, query_range
v3+v4 -> v5, metrics/dashboards. 16 orphaned handler funcs (430 lines) deleted.

CONSUMERS MIGRATED to the one-way table names, never the reverse: tables.go's
deletion of the versioned constants was deliberate (stated three times), and the
new blocks are positional 1:1 rewrites of the old, so every resolution is a
constant the same commit declared — SpanIndexV3->span, LogsV2->log,
SamplesV4->metric, TimeseriesV4->series, UpdatedMetadata->descriptor, ambiguity
on operation-vs-trace settled by tables.go's own inline comments.

sentrytypes.Frame/Event.Frames/Event.Handled RECONSTRUCTED, not guessed: pinned
independently by eventsql.go's projection, eventstore.go's scan targets and
module.go's clampLine/InApp, and asserted literally by the pre-existing
TestFramesRoundTrip. TraceSummary.Sample -> Message (event.error has no sample
column). Event.Sample deleted: a field that always read "" while claiming to
hold the occurrence JSON.

PATH MANGLERS KILLED, both deployment shapes: mount.go rewriteExternalPath+
setPath deleted (it mapped /v1/o11y/services -> /api/services => 404 on the
cloud-binary path — proven with a real test before deletion); server.go
StripPrefix removed (it reduced /v1/o11y/services -> /services => the web
catch-all answered index.html on the standalone path). versionless.go +
versionless_test.go + both call sites deleted: its regex matched 0 of the
registered routes and walked the router at every startup to register nothing —
the second spelling of one path, gone with the mechanism that needed it.

FRONTEND: prependBase '/api/v1/' -> '/v1/o11y/' (both baseURL branches), 17
/api/v1/assistant literals renamed (note: no assistant route exists in pkg/ —
client-only surface, a real product gap, not papered over), 21 MSW mocks moved
so they intercept the paths the app now requests.

GATE, all executed: go build ./... green; go test ./... green (exit 0);
0 /api/v[0-9] references in pkg code (frontend's 5 are query-builder escaping
fixtures); 0 duplicate (method,path) registrations; /v1/o11y/services survives
both mount.go and server.go untouched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-31 14:07:44 -07:00

149 lines
7.8 KiB
Go

package telemetrymetadata
import (
"slices"
"testing"
"github.com/hanzoai/o11y/pkg/types/telemetrytypes"
)
func TestExtractFieldKeysFromTblStatement(t *testing.T) {
var statement = `CREATE TABLE event.log
(
` + "`ts_bucket_start`" + ` UInt64 CODEC(DoubleDelta, LZ4),
` + "`resource_fingerprint`" + ` String CODEC(ZSTD(1)),
` + "`timestamp`" + ` UInt64 CODEC(DoubleDelta, LZ4),
` + "`observed_timestamp`" + ` UInt64 CODEC(DoubleDelta, LZ4),
` + "`id`" + ` String CODEC(ZSTD(1)),
` + "`trace_id`" + ` String CODEC(ZSTD(1)),
` + "`span_id`" + ` String CODEC(ZSTD(1)),
` + "`trace_flags`" + ` UInt32,
` + "`severity_text`" + ` LowCardinality(String) CODEC(ZSTD(1)),
` + "`severity_number`" + ` UInt8,
` + "`body`" + ` String CODEC(ZSTD(2)),
` + "`attributes_string`" + ` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
` + "`attributes_number`" + ` Map(LowCardinality(String), Float64) CODEC(ZSTD(1)),
` + "`attributes_bool`" + ` Map(LowCardinality(String), Bool) CODEC(ZSTD(1)),
` + "`resources_string`" + ` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
` + "`scope_name`" + ` String CODEC(ZSTD(1)),
` + "`scope_version`" + ` String CODEC(ZSTD(1)),
` + "`scope_string`" + ` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
` + "`attribute_number_input_size`" + ` Int64 DEFAULT attributes_number['input_size'] CODEC(ZSTD(1)),
` + "`attribute_number_input_size_exists`" + ` Bool DEFAULT if(mapContains(attributes_number, 'input_size') != 0, true, false) CODEC(ZSTD(1)),
` + "`attribute_string_log$$iostream`" + ` String DEFAULT attributes_string['log.iostream'] CODEC(ZSTD(1)),
` + "`attribute_string_log$$iostream_exists`" + ` Bool DEFAULT if(mapContains(attributes_string, 'log.iostream') != 0, true, false) CODEC(ZSTD(1)),
` + "`attribute_string_log$$file$$path`" + ` String DEFAULT attributes_string['log.file.path'] CODEC(ZSTD(1)),
` + "`attribute_string_log$$file$$path_exists`" + ` Bool DEFAULT if(mapContains(attributes_string, 'log.file.path') != 0, true, false) CODEC(ZSTD(1)),
` + "`resource_string_k8s$$cluster$$name`" + ` String DEFAULT resources_string['k8s.cluster.name'] CODEC(ZSTD(1)),
` + "`resource_string_k8s$$cluster$$name_exists`" + ` Bool DEFAULT if(mapContains(resources_string, 'k8s.cluster.name') != 0, true, false) CODEC(ZSTD(1)),
` + "`resource_string_k8s$$namespace$$name`" + ` String DEFAULT resources_string['k8s.namespace.name'] CODEC(ZSTD(1)),
` + "`resource_string_k8s$$namespace$$name_exists`" + ` Bool DEFAULT if(mapContains(resources_string, 'k8s.namespace.name') != 0, true, false) CODEC(ZSTD(1)),
` + "`resource_string_k8s$$pod$$name`" + ` String DEFAULT resources_string['k8s.pod.name'] CODEC(ZSTD(1)),
` + "`resource_string_k8s$$pod$$name_exists`" + ` Bool DEFAULT if(mapContains(resources_string, 'k8s.pod.name') != 0, true, false) CODEC(ZSTD(1)),
` + "`resource_string_k8s$$node$$name`" + ` String DEFAULT resources_string['k8s.node.name'] CODEC(ZSTD(1)),
` + "`resource_string_k8s$$node$$name_exists`" + ` Bool DEFAULT if(mapContains(resources_string, 'k8s.node.name') != 0, true, false) CODEC(ZSTD(1)),
` + "`resource_string_k8s$$container$$name`" + ` String DEFAULT resources_string['k8s.container.name'] CODEC(ZSTD(1)),
` + "`resource_string_k8s$$container$$name_exists`" + ` Bool DEFAULT if(mapContains(resources_string, 'k8s.container.name') != 0, true, false) CODEC(ZSTD(1)),
` + "`resource_string_k8s$$deployment$$name`" + ` String DEFAULT resources_string['k8s.deployment.name'] CODEC(ZSTD(1)),
` + "`resource_string_k8s$$deployment$$name_exists`" + ` Bool DEFAULT if(mapContains(resources_string, 'k8s.deployment.name') != 0, true, false) CODEC(ZSTD(1)),
` + "`attribute_string_processor`" + ` String DEFAULT attributes_string['processor'] CODEC(ZSTD(1)),
` + "`attribute_string_processor_exists`" + ` Bool DEFAULT if(mapContains(attributes_string, 'processor') != 0, true, false) CODEC(ZSTD(1)),
INDEX body_idx lower(body) TYPE ngrambf_v1(4, 60000, 5, 0) GRANULARITY 1,
INDEX id_minmax id TYPE minmax GRANULARITY 1,
INDEX severity_number_idx severity_number TYPE set(25) GRANULARITY 4,
INDEX severity_text_idx severity_text TYPE set(25) GRANULARITY 4,
INDEX trace_flags_idx trace_flags TYPE bloom_filter GRANULARITY 4,
INDEX scope_name_idx scope_name TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
INDEX ` + "`resource_string_k8s$$cluster$$name_idx`" + ` ` + "`resource_string_k8s$$cluster$$name`" + ` TYPE bloom_filter(0.01) GRANULARITY 64,
INDEX ` + "`resource_string_k8s$$namespace$$name_idx`" + ` ` + "`resource_string_k8s$$namespace$$name`" + ` TYPE bloom_filter(0.01) GRANULARITY 64,
INDEX ` + "`resource_string_k8s$$pod$$name_idx`" + ` ` + "`resource_string_k8s$$pod$$name`" + ` TYPE bloom_filter(0.01) GRANULARITY 64,
INDEX ` + "`resource_string_k8s$$node$$name_idx`" + ` ` + "`resource_string_k8s$$node$$name`" + ` TYPE bloom_filter(0.01) GRANULARITY 64,
INDEX ` + "`resource_string_k8s$$container$$name_idx`" + ` ` + "`resource_string_k8s$$container$$name`" + ` TYPE bloom_filter(0.01) GRANULARITY 64,
INDEX ` + "`resource_string_k8s$$deployment$$name_idx`" + ` ` + "`resource_string_k8s$$deployment$$name`" + ` TYPE bloom_filter(0.01) GRANULARITY 64,
INDEX attribute_string_processor_idx attribute_string_processor TYPE bloom_filter(0.01) GRANULARITY 64
)
ENGINE = ReplicatedMergeTree('/datastore/tables/{uuid}/{shard}', '{replica}')
PARTITION BY toDate(timestamp / 1000000000)
ORDER BY (ts_bucket_start, resource_fingerprint, severity_text, timestamp, id)
TTL toDateTime(timestamp / 1000000000) + toIntervalSecond(2592000)
SETTINGS ttl_only_drop_parts = 1, index_granularity = 8192`
keys, err := ExtractFieldKeysFromTblStatement(statement)
if err != nil {
t.Fatalf("failed to extract field keys from tbl statement: %v", err)
}
// some expected keys
expectedKeys := []*telemetrytypes.TelemetryFieldKey{
{
Name: "k8s.pod.name",
FieldContext: telemetrytypes.FieldContextResource,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
{
Name: "k8s.cluster.name",
FieldContext: telemetrytypes.FieldContextResource,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
{
Name: "k8s.namespace.name",
FieldContext: telemetrytypes.FieldContextResource,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
{
Name: "k8s.deployment.name",
FieldContext: telemetrytypes.FieldContextResource,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
{
Name: "k8s.node.name",
FieldContext: telemetrytypes.FieldContextResource,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
{
Name: "k8s.container.name",
FieldContext: telemetrytypes.FieldContextResource,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
{
Name: "processor",
FieldContext: telemetrytypes.FieldContextAttribute,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
{
Name: "input_size",
FieldContext: telemetrytypes.FieldContextAttribute,
FieldDataType: telemetrytypes.FieldDataTypeNumber,
Materialized: true,
},
{
Name: "log.iostream",
FieldContext: telemetrytypes.FieldContextAttribute,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
{
Name: "log.file.path",
FieldContext: telemetrytypes.FieldContextAttribute,
FieldDataType: telemetrytypes.FieldDataTypeString,
Materialized: true,
},
}
for _, key := range expectedKeys {
if !slices.ContainsFunc(keys, func(k *telemetrytypes.TelemetryFieldKey) bool {
return k.Name == key.Name && k.FieldContext == key.FieldContext && k.FieldDataType == key.FieldDataType && k.Materialized == key.Materialized
}) {
t.Errorf("expected key %v not found", key)
}
}
}