Files
kong--kong/spec/01-unit/26-observability/03-propagation_module_spec.lua
2026-07-13 12:32:21 +08:00

464 lines
14 KiB
Lua

local propagation_utils = require "kong.observability.tracing.propagation.utils"
local tablex = require "pl.tablex"
local shallow_copy = require "kong.tools.table".shallow_copy
local to_hex = require "resty.string".to_hex
local from_hex = propagation_utils.from_hex
local fmt = string.format
-- W3C Ids
local trace_id_16_w3c = "0af7651916cd43dd8448eb211c80319c"
local trace_id_8_w3c_dec = "9532127138774266268" -- 8448eb211c80319c to decimal
local span_id_8_w3c = "b7ad6b7169203331"
local span_id_8_w3c_dec = "13235353014750950193" -- b7ad6b7169203331 to decimal
-- B3 Ids
local trace_id_16_b3 = "dc9d1b0ccedf0ecaf4f26ffab84d4f5e"
local trace_id_8_b3 = "f4f26ffab84d4f5e"
local span_id_8_b3 = "b7ad6b7169203332"
local span_id_8_b3p = "f4f26ffab84d4f5f"
-- Jaeger Ids
local trace_id_16_jae = "f744b23fe9aa64f08255043ba51848db"
local span_id_8_jae = "f4f26ffab84d4f60"
local span_id_8_jaep = "f4f26ffab84d4f61"
local padding_prefix = string.rep("0", 16)
-- apply some transformation to a hex id (affects last byte)
local function transform_hex_id(id)
local max = string.byte("f")
local min = string.byte("0")
local bytes = { id:byte(1, -1) }
local last_byte = bytes[#bytes]
last_byte = last_byte + 1 < max and last_byte + 1 or min
bytes[#bytes] = last_byte
return string.char(unpack(bytes))
end
-- apply some transformation (same as transform_hex_id above) to a binary id
local function transform_bin_id(bid)
return from_hex(transform_hex_id(to_hex(bid)))
end
local request_headers_1 = {
traceparent = fmt("00-%s-%s-01", trace_id_16_w3c, span_id_8_w3c),
["x-b3-traceid"] = trace_id_16_b3,
["x-b3-spanid"] = span_id_8_b3,
["x-b3-sampled"] = "1",
["x-b3-parentspanid"] = span_id_8_b3p,
["uber-trace-id"] = fmt("%s:%s:%s:%s", trace_id_16_jae, span_id_8_jae, span_id_8_jaep, "01"),
}
local request_headers_2 = {
["x-b3-traceid"] = trace_id_8_b3,
["x-b3-spanid"] = span_id_8_b3,
["x-b3-sampled"] = "1",
["x-b3-parentspanid"] = span_id_8_b3p,
}
local test_data = { {
description = "extract empty, inject empty (propagation disabled)",
req_headers = request_headers_1,
conf = {
propagation = {
extract = {},
inject = {},
}
},
expected = request_headers_1
}, {
description = "extract = ignore, inject single heder",
req_headers = request_headers_1,
conf = {
propagation = {
extract = {},
inject = { "w3c" },
}
},
cb = function()
-- no extraction, set some values using the callback
-- (same as what tracing plugins would do)
return {
trace_id = from_hex("0af7651916cd43dd8448eb211c80319d"),
span_id = from_hex("8448eb211c80319e"),
should_sample = true,
}
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", "0af7651916cd43dd8448eb211c80319d", "8448eb211c80319e"),
}, true)
}, {
description = "extract = ignore, inject multiple heders",
req_headers = request_headers_1,
conf = {
propagation = {
extract = {},
inject = { "w3c", "b3-single" },
}
},
cb = function()
-- no extraction, set some values using the callback
-- (same as what tracing plugins would do)
return {
trace_id = from_hex("0af7651916cd43dd8448eb211c80319d"),
span_id = from_hex("8448eb211c80319e"),
should_sample = true,
}
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", "0af7651916cd43dd8448eb211c80319d", "8448eb211c80319e"),
b3 = fmt("%s-%s-1", "0af7651916cd43dd8448eb211c80319d", "8448eb211c80319e"),
}, true)
}, {
description = "extract = ignore, inject = preserve, no default setting",
req_headers = request_headers_1,
conf = {
propagation = {
extract = {},
inject = { "preserve" },
}
},
cb = function()
-- no extraction, set some values using the callback
-- (same as what tracing plugins would do)
return {
trace_id = from_hex("0af7651916cd43dd8448eb211c80319d"),
span_id = from_hex("8448eb211c80319e"),
should_sample = true,
}
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", "0af7651916cd43dd8448eb211c80319d", "8448eb211c80319e"),
}, true)
}, {
description = "extract = ignore, inject = preserve, uses default format",
req_headers = request_headers_1,
conf = {
propagation = {
extract = {},
inject = { "preserve" },
default_format = "datadog",
}
},
cb = function()
-- no extraction, set some values using the callback
-- (same as what tracing plugins would do)
return {
trace_id = from_hex("0af7651916cd43dd8448eb211c80319d"),
span_id = from_hex("8448eb211c80319e"),
should_sample = true,
}
end,
expected = tablex.merge(request_headers_1, {
["x-datadog-trace-id"] = "9532127138774266269", -- 8448eb211c80319d to dec
["x-datadog-parent-id"] = "9532127138774266270", -- 8448eb211c80319e to dec
["x-datadog-sampling-priority"] = "1"
}, true)
}, {
description = "extract configured with header not found in request, inject = preserve, uses default format",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "datadog" },
inject = { "preserve" },
default_format = "ot"
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
assert.same(ctx, {})
ctx.trace_id = from_hex("0af7651916cd43dd8448eb211c80319d")
ctx.span_id = from_hex("8448eb211c80319e")
ctx.should_sample = true
return ctx
end,
expected = tablex.merge(request_headers_1, {
["ot-tracer-sampled"] = '1',
["ot-tracer-spanid"] = '8448eb211c80319e',
["ot-tracer-traceid"] = '8448eb211c80319d',
}, true)
}, {
description = "extract configured with header found in request, inject = preserve + other formats",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "b3", "w3c", "jaeger" },
inject = { "w3c", "preserve", "b3-single" },
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
ctx.parent_id = transform_bin_id(ctx.parent_id)
return ctx
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", transform_hex_id(trace_id_16_b3), transform_hex_id(span_id_8_b3)),
["x-b3-traceid"] = transform_hex_id(trace_id_16_b3),
["x-b3-spanid"] = transform_hex_id(span_id_8_b3),
["x-b3-sampled"] = "1",
["x-b3-parentspanid"] = transform_hex_id(span_id_8_b3p),
b3 = fmt("%s-%s-1-%s", transform_hex_id(trace_id_16_b3), transform_hex_id(span_id_8_b3),
transform_hex_id(span_id_8_b3p)),
}, true)
}, {
description = "extract configured with header formats, injection disabled",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "gcp", "aws", "ot", "datadog", "b3", "w3c", "jaeger" },
inject = {},
}
},
cb = function()
return {
trace_id = from_hex("abcdef"),
span_id = from_hex("123fff"),
should_sample = true,
}
end,
expected = request_headers_1
}, {
description = "extract configured with header formats, b3 first",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "b3", "w3c", "jaeger" },
inject = { "w3c" },
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
return ctx
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", transform_hex_id(trace_id_16_b3), transform_hex_id(span_id_8_b3)),
}, true)
}, {
description = "extract configured with header formats, w3c first",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "w3c", "b3", "jaeger" },
inject = { "w3c" },
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
return ctx
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", transform_hex_id(trace_id_16_w3c), transform_hex_id(span_id_8_w3c)),
}, true)
}, {
description = "extract configured with header formats, missing first header",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "datadog", "jaeger", "b3" },
inject = { "w3c" },
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
return ctx
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", transform_hex_id(trace_id_16_jae), transform_hex_id(span_id_8_jae)),
}, true)
}, {
description = "extract configured with header formats, multiple injection",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "w3c", "b3", "jaeger" },
inject = { "datadog", "w3c" },
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
return ctx
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", transform_hex_id(trace_id_16_w3c), transform_hex_id(span_id_8_w3c)),
["x-datadog-trace-id"] = transform_hex_id(trace_id_8_w3c_dec),
["x-datadog-parent-id"] = transform_hex_id(span_id_8_w3c_dec),
["x-datadog-sampling-priority"] = "1"
}, true)
}, {
description = "extract = b3, 64b id, inject = b3 and w3c",
req_headers = request_headers_2,
conf = {
propagation = {
extract = { "b3", },
inject = { "w3c", "b3" },
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
ctx.parent_id = transform_bin_id(ctx.parent_id)
return ctx
end,
expected = tablex.merge(request_headers_2, {
traceparent = fmt("00-%s-%s-01", transform_hex_id(padding_prefix .. trace_id_8_b3), transform_hex_id(span_id_8_b3)),
["x-b3-traceid"] = transform_hex_id(trace_id_8_b3), -- 64b (same as incoming)
["x-b3-spanid"] = transform_hex_id(span_id_8_b3),
["x-b3-sampled"] = "1",
["x-b3-parentspanid"] = transform_hex_id(span_id_8_b3p),
}, true)
}, {
description = "extract = b3, 128b id, inject = b3 and w3c",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "b3", },
inject = { "w3c", "b3" },
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
ctx.parent_id = transform_bin_id(ctx.parent_id)
return ctx
end,
expected = tablex.merge(request_headers_1, {
traceparent = fmt("00-%s-%s-01", transform_hex_id(trace_id_16_b3), transform_hex_id(span_id_8_b3)),
["x-b3-traceid"] = transform_hex_id(trace_id_16_b3), -- 128b (same as incoming)
["x-b3-spanid"] = transform_hex_id(span_id_8_b3),
["x-b3-sampled"] = "1",
["x-b3-parentspanid"] = transform_hex_id(span_id_8_b3p),
}, true)
}, {
description = "extract configured with header formats, inject = preserve (matches jaeger)",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "datadog", "jaeger", "b3" },
inject = { "preserve" },
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
ctx.parent_id = transform_bin_id(ctx.parent_id)
return ctx
end,
expected = tablex.merge(request_headers_1, {
["uber-trace-id"] = fmt("%s:%s:%s:%s", transform_hex_id(trace_id_16_jae), transform_hex_id(span_id_8_jae),
transform_hex_id(span_id_8_jaep), "01"),
}, true)
}, {
description = "clear = b3 and w3c",
req_headers = request_headers_1,
conf = {
propagation = {
extract = { "datadog", "jaeger", "b3", "w3c" },
inject = { "preserve" },
clear = {
"x-b3-traceid",
"x-b3-spanid",
"x-b3-sampled",
"x-b3-parentspanid",
"traceparent"
}
}
},
-- apply some updates to the extracted ctx
cb = function(ctx)
ctx.trace_id = transform_bin_id(ctx.trace_id)
ctx.span_id = transform_bin_id(ctx.span_id)
ctx.parent_id = transform_bin_id(ctx.parent_id)
return ctx
end,
expected = {
["uber-trace-id"] = fmt("%s:%s:%s:%s", transform_hex_id(trace_id_16_jae), transform_hex_id(span_id_8_jae),
transform_hex_id(span_id_8_jaep), "01"),
}
} }
describe("Tracing Headers Propagation Module", function()
local warn, err, set_serialize_value, req_headers
local old_get_headers = _G.ngx.req.get_headers
local old_kong = _G.kong
_G.ngx.req.get_headers = function()
return req_headers
end
_G.kong = {
ctx = {
plugin = {},
},
log = {},
service = {
request = {
set_header = function(name, value)
req_headers[name] = value
end,
clear_header = function(name)
req_headers[name] = nil
end,
}
}
}
local propagation = require "kong.observability.tracing.propagation"
lazy_setup(function()
err = spy.on(kong.log, "err")
warn = spy.on(kong.log, "warn")
set_serialize_value = spy.on(kong.log, "set_serialize_value")
end)
lazy_teardown(function()
_G.kong = old_kong
_G.ngx.req.get_headers = old_get_headers
end)
describe("propagate() function with", function()
before_each(function()
warn:clear()
err:clear()
set_serialize_value:clear()
end)
for _, t in ipairs(test_data) do
it(t.description .. " updates headers correctly", function()
local conf = t.conf
local expected = t.expected
req_headers = shallow_copy(t.req_headers)
propagation.propagate(
propagation.get_plugin_params(conf),
t.cb or function(c) return c end
)
assert.spy(err).was_not_called()
assert.spy(warn).was_not_called()
assert.same(expected, req_headers)
end)
end
end)
end)