kong/spec/03-plugins/34-zipkin/zipkin_spec.lua (932 lines of code) (raw):

local helpers = require "spec.helpers" local cjson = require "cjson" local utils = require "kong.tools.utils" local to_hex = require "resty.string".to_hex local fmt = string.format local ZIPKIN_HOST = helpers.zipkin_host local ZIPKIN_PORT = helpers.zipkin_port -- Transform zipkin annotations into a hash of timestamps. It assumes no repeated values -- input: { { value = x, timestamp = y }, { value = x2, timestamp = y2 } } -- output: { x = y, x2 = y2 } local function annotations_to_hash(annotations) local hash = {} for _, a in ipairs(annotations) do assert(not hash[a.value], "duplicated annotation: " .. a.value) hash[a.value] = a.timestamp end return hash end local function assert_is_integer(number) assert.equals("number", type(number)) assert.equals(number, math.floor(number)) end local function gen_trace_id(traceid_byte_count) return to_hex(utils.get_rand_bytes(traceid_byte_count)) end local function gen_span_id() return to_hex(utils.get_rand_bytes(8)) end -- assumption: tests take less than this (usually they run in ~2 seconds) local MAX_TIMESTAMP_AGE = 5 * 60 -- 5 minutes local function assert_valid_timestamp(timestamp_mu, start_s) assert_is_integer(timestamp_mu) local age_s = timestamp_mu / 1000000 - start_s if age_s < 0 or age_s > MAX_TIMESTAMP_AGE then error("out of bounds timestamp: " .. timestamp_mu .. "mu (age: " .. age_s .. "s)") end end local function wait_for_spans(zipkin_client, number_of_spans, remoteServiceName, trace_id) local spans = {} helpers.wait_until(function() if trace_id then local res = assert(zipkin_client:get("/api/v2/trace/" .. trace_id)) spans = cjson.decode(assert.response(res).has.status(200)) return #spans == number_of_spans end local res = zipkin_client:get("/api/v2/traces", { query = { limit = 10, remoteServiceName = remoteServiceName, } }) local all_spans = cjson.decode(assert.response(res).has.status(200)) if #all_spans > 0 then spans = all_spans[1] return #spans == number_of_spans end end) return utils.unpack(spans) end -- the following assertions should be true on any span list, even in error mode local function assert_span_invariants(request_span, proxy_span, expected_name, traceid_len, start_s, service_name) -- request_span assert.same("table", type(request_span)) assert.same("string", type(request_span.id)) assert.same(expected_name, request_span.name) assert.same(request_span.id, proxy_span.parentId) assert.same("SERVER", request_span.kind) assert.same("string", type(request_span.traceId)) assert.equals(traceid_len, #request_span.traceId, request_span.traceId) assert_valid_timestamp(request_span.timestamp, start_s) if request_span.duration and proxy_span.duration then assert.truthy(request_span.duration >= proxy_span.duration) end if #request_span.annotations == 1 then error(require("inspect")(request_span)) end assert.equals(2, #request_span.annotations) local rann = annotations_to_hash(request_span.annotations) assert_valid_timestamp(rann["krs"], start_s) assert_valid_timestamp(rann["krf"], start_s) assert.truthy(rann["krs"] <= rann["krf"]) assert.same({ serviceName = service_name }, request_span.localEndpoint) -- proxy_span assert.same("table", type(proxy_span)) assert.same("string", type(proxy_span.id)) assert.same(request_span.name .. " (proxy)", proxy_span.name) assert.same(request_span.id, proxy_span.parentId) assert.same("CLIENT", proxy_span.kind) assert.same("string", type(proxy_span.traceId)) assert.equals(request_span.traceId, proxy_span.traceId) assert_valid_timestamp(proxy_span.timestamp, start_s) if request_span.duration and proxy_span.duration then assert.truthy(proxy_span.duration >= 0) end assert.equals(6, #proxy_span.annotations) local pann = annotations_to_hash(proxy_span.annotations) assert_valid_timestamp(pann["kas"], start_s) assert_valid_timestamp(pann["kaf"], start_s) assert_valid_timestamp(pann["khs"], start_s) assert_valid_timestamp(pann["khf"], start_s) assert_valid_timestamp(pann["kbs"], start_s) assert_valid_timestamp(pann["kbf"], start_s) assert.truthy(pann["kas"] <= pann["kaf"]) assert.truthy(pann["khs"] <= pann["khf"]) assert.truthy(pann["kbs"] <= pann["kbf"]) assert.truthy(pann["khs"] <= pann["kbs"]) end for _, strategy in helpers.each_strategy() do describe("plugin configuration", function() local proxy_client, zipkin_client, service setup(function() local bp = helpers.get_db_utils(strategy, { "services", "routes", "plugins" }) service = bp.services:insert { name = string.lower("http-" .. utils.random_string()), } -- kong (http) mock upstream bp.routes:insert({ name = string.lower("route-" .. utils.random_string()), service = service, hosts = { "http-route" }, preserve_host = true, }) -- enable zipkin plugin globally, with sample_ratio = 0 bp.plugins:insert({ name = "zipkin", config = { sample_ratio = 0, http_endpoint = fmt("http://%s:%d/api/v2/spans", ZIPKIN_HOST, ZIPKIN_PORT), default_header_type = "b3-single", } }) -- enable zipkin on the service, with sample_ratio = 1 -- this should generate traces, even if there is another plugin with sample_ratio = 0 bp.plugins:insert({ name = "zipkin", service = { id = service.id }, config = { sample_ratio = 1, http_endpoint = fmt("http://%s:%d/api/v2/spans", ZIPKIN_HOST, ZIPKIN_PORT), default_header_type = "b3-single", } }) helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", stream_listen = helpers.get_proxy_ip(false) .. ":19000", }) proxy_client = helpers.proxy_client() zipkin_client = helpers.http_client(ZIPKIN_HOST, ZIPKIN_PORT) end) teardown(function() helpers.stop_kong() end) it("#generates traces when several plugins exist and one of them has sample_ratio = 0 but not the other", function() local start_s = ngx.now() local r = proxy_client:get("/", { headers = { ["x-b3-sampled"] = "1", host = "http-route", ["zipkin-tags"] = "foo=bar; baz=qux" }, }) assert.response(r).has.status(200) local _, proxy_span, request_span = wait_for_spans(zipkin_client, 3, service.name) -- common assertions for request_span and proxy_span assert_span_invariants(request_span, proxy_span, "get", 16 * 2, start_s, "kong") end) end) end for _, strategy in helpers.each_strategy() do describe("serviceName configuration", function() local proxy_client, zipkin_client, service setup(function() local bp = helpers.get_db_utils(strategy, { "services", "routes", "plugins" }) service = bp.services:insert { name = string.lower("http-" .. utils.random_string()), } -- kong (http) mock upstream bp.routes:insert({ name = string.lower("route-" .. utils.random_string()), service = service, hosts = { "http-route" }, preserve_host = true, }) -- enable zipkin plugin globally, with sample_ratio = 1 bp.plugins:insert({ name = "zipkin", config = { sample_ratio = 1, http_endpoint = fmt("http://%s:%d/api/v2/spans", ZIPKIN_HOST, ZIPKIN_PORT), default_header_type = "b3-single", local_service_name = "custom-service-name", } }) helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", stream_listen = helpers.get_proxy_ip(false) .. ":19000", }) proxy_client = helpers.proxy_client() zipkin_client = helpers.http_client(ZIPKIN_HOST, ZIPKIN_PORT) end) teardown(function() helpers.stop_kong() end) it("#generates traces with configured serviceName if set", function() local start_s = ngx.now() local r = proxy_client:get("/", { headers = { ["x-b3-sampled"] = "1", host = "http-route", ["zipkin-tags"] = "foo=bar; baz=qux" }, }) assert.response(r).has.status(200) local _, proxy_span, request_span = wait_for_spans(zipkin_client, 3, service.name) -- common assertions for request_span and proxy_span assert_span_invariants(request_span, proxy_span, "get", 16 * 2, start_s, "custom-service-name") end) end) end for _, strategy in helpers.each_strategy() do describe("upstream zipkin failures", function() local proxy_client, service before_each(function() helpers.clean_logfile() -- prevent log assertions from poisoning each other. end) setup(function() local bp = helpers.get_db_utils(strategy, { "services", "routes", "plugins" }) service = bp.services:insert { name = string.lower("http-" .. utils.random_string()), protocol = "http", host = helpers.mock_upstream_host, port = helpers.mock_upstream_port, } -- kong (http) mock upstream local route1 = bp.routes:insert({ name = string.lower("route-" .. utils.random_string()), service = service, hosts = { "zipkin-upstream-slow" }, preserve_host = true, }) -- plugin will respond slower than the send/recv timeout bp.plugins:insert { route = { id = route1.id }, name = "zipkin", config = { sample_ratio = 1, http_endpoint = "http://" .. helpers.mock_upstream_host .. ":" .. helpers.mock_upstream_port .. "/delay/1", default_header_type = "b3-single", connect_timeout = 0, send_timeout = 10, read_timeout = 10, } } local route2 = bp.routes:insert({ name = string.lower("route-" .. utils.random_string()), service = service, hosts = { "zipkin-upstream-connect-timeout" }, preserve_host = true, }) -- plugin will timeout (assumes port 1337 will have firewall) bp.plugins:insert { route = { id = route2.id }, name = "zipkin", config = { sample_ratio = 1, http_endpoint = "http://httpbin.org:1337/status/200", default_header_type = "b3-single", connect_timeout = 10, send_timeout = 0, read_timeout = 0, } } local route3 = bp.routes:insert({ name = string.lower("route-" .. utils.random_string()), service = service, hosts = { "zipkin-upstream-refused" }, preserve_host = true, }) -- plugin will get connection refused (service not listening on port) bp.plugins:insert { route = { id = route3.id }, name = "zipkin", config = { sample_ratio = 1, http_endpoint = "http://" .. helpers.mock_upstream_host .. ":22222" .. "/status/200", default_header_type = "b3-single", } } helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", }) proxy_client = helpers.proxy_client() end) teardown(function() helpers.stop_kong() end) it("times out if connection times out to upstream zipkin server", function() local res = assert(proxy_client:send({ method = "GET", path = "/status/200", headers = { ["Host"] = "zipkin-upstream-connect-timeout" } })) assert.res_status(200, res) -- wait for zero-delay timer helpers.wait_timer("zipkin", true, "any-finish") assert.logfile().has.line("reporter flush failed to request: timeout", false, 2) end) it("times out if upstream zipkin server takes too long to respond", function() local res = assert(proxy_client:send({ method = "GET", path = "/status/200", headers = { ["Host"] = "zipkin-upstream-slow" } })) assert.res_status(200, res) -- wait for zero-delay timer helpers.wait_timer("zipkin", true, "any-finish") assert.logfile().has.line("reporter flush failed to request: timeout", false, 2) end) it("connection refused if upstream zipkin server is not listening", function() local res = assert(proxy_client:send({ method = "GET", path = "/status/200", headers = { ["Host"] = "zipkin-upstream-refused" } })) assert.res_status(200, res) -- wait for zero-delay timer helpers.wait_timer("zipkin", true, "any-finish") assert.logfile().has.line("reporter flush failed to request: connection refused", false, 2) end) end) end for _, strategy in helpers.each_strategy() do describe("http_span_name configuration", function() local proxy_client, zipkin_client, service setup(function() local bp = helpers.get_db_utils(strategy, { "services", "routes", "plugins" }) service = bp.services:insert { name = string.lower("http-" .. utils.random_string()), } -- kong (http) mock upstream bp.routes:insert({ name = string.lower("route-" .. utils.random_string()), service = service, hosts = { "http-route" }, preserve_host = true, }) -- enable zipkin plugin globally, with sample_ratio = 1 bp.plugins:insert({ name = "zipkin", config = { sample_ratio = 1, http_endpoint = fmt("http://%s:%d/api/v2/spans", ZIPKIN_HOST, ZIPKIN_PORT), default_header_type = "b3-single", http_span_name = "method_path", } }) helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", stream_listen = helpers.get_proxy_ip(false) .. ":19000", }) proxy_client = helpers.proxy_client() zipkin_client = helpers.http_client(ZIPKIN_HOST, ZIPKIN_PORT) end) teardown(function() helpers.stop_kong() end) it("http_span_name = 'method_path' includes path to span name", function() local start_s = ngx.now() local r = proxy_client:get("/", { headers = { ["x-b3-sampled"] = "1", host = "http-route", ["zipkin-tags"] = "foo=bar; baz=qux" }, }) assert.response(r).has.status(200) local _, proxy_span, request_span = wait_for_spans(zipkin_client, 3, service.name) -- common assertions for request_span and proxy_span assert_span_invariants(request_span, proxy_span, "get /", 16 * 2, start_s, "kong") end) end) end for _, strategy in helpers.each_strategy() do for _, traceid_byte_count in ipairs({ 8, 16 }) do describe("http integration tests with zipkin server [#" .. strategy .. "] traceid_byte_count: " .. traceid_byte_count, function() local proxy_client_grpc local service, grpc_service, tcp_service local route, grpc_route, tcp_route local zipkin_client local proxy_client lazy_setup(function() local bp = helpers.get_db_utils(strategy, { "services", "routes", "plugins" }) -- enable zipkin plugin globally pointing to mock server bp.plugins:insert({ name = "zipkin", -- enable on TCP as well (by default it is only enabled on http, https, grpc, grpcs) protocols = { "http", "https", "tcp", "tls", "grpc", "grpcs" }, config = { sample_ratio = 1, http_endpoint = fmt("http://%s:%d/api/v2/spans", ZIPKIN_HOST, ZIPKIN_PORT), traceid_byte_count = traceid_byte_count, static_tags = { { name = "static", value = "ok" }, }, default_header_type = "b3-single", } }) service = bp.services:insert { name = string.lower("http-" .. utils.random_string()), } -- kong (http) mock upstream route = bp.routes:insert({ name = string.lower("route-" .. utils.random_string()), service = service, hosts = { "http-route" }, preserve_host = true, }) -- grpc upstream grpc_service = bp.services:insert { name = string.lower("grpc-" .. utils.random_string()), url = helpers.grpcbin_url, } grpc_route = bp.routes:insert { name = string.lower("grpc-route-" .. utils.random_string()), service = grpc_service, protocols = { "grpc" }, hosts = { "grpc-route" }, } -- tcp upstream tcp_service = bp.services:insert({ name = string.lower("tcp-" .. utils.random_string()), protocol = "tcp", host = helpers.mock_upstream_host, port = helpers.mock_upstream_stream_port, }) tcp_route = bp.routes:insert { name = string.lower("tcp-route-" .. utils.random_string()), destinations = { { port = 19000 } }, protocols = { "tcp" }, service = tcp_service, } helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", stream_listen = helpers.get_proxy_ip(false) .. ":19000", }) proxy_client = helpers.proxy_client() proxy_client_grpc = helpers.proxy_client_grpc() zipkin_client = helpers.http_client(ZIPKIN_HOST, ZIPKIN_PORT) end) teardown(function() helpers.stop_kong() end) it("generates spans, tags and annotations for regular requests", function() local start_s = ngx.now() local r = proxy_client:get("/", { headers = { ["x-b3-sampled"] = "1", host = "http-route", ["zipkin-tags"] = "foo=bar; baz=qux" }, }) assert.response(r).has.status(200) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, service.name) -- common assertions for request_span and proxy_span assert_span_invariants(request_span, proxy_span, "get", traceid_byte_count * 2, start_s, "kong") -- specific assertions for request_span local request_tags = request_span.tags assert.truthy(request_tags["kong.node.id"]:match("^[%x-]+$")) request_tags["kong.node.id"] = nil assert.same({ ["http.method"] = "GET", ["http.path"] = "/", ["http.status_code"] = "200", -- found (matches server status) ["http.protocol"] = "HTTP/1.1", ["http.host"] = "http-route", lc = "kong", static = "ok", foo = "bar", baz = "qux" }, request_tags) local consumer_port = request_span.remoteEndpoint.port assert_is_integer(consumer_port) assert.same({ ipv4 = "127.0.0.1", port = consumer_port, }, request_span.remoteEndpoint) -- specific assertions for proxy_span assert.same(proxy_span.tags["kong.route"], route.id) assert.same(proxy_span.tags["kong.route_name"], route.name) assert.same(proxy_span.tags["peer.hostname"], "127.0.0.1") assert.same({ ipv4 = helpers.mock_upstream_host, port = helpers.mock_upstream_port, serviceName = service.name, }, proxy_span.remoteEndpoint) -- specific assertions for balancer_span assert.equals(balancer_span.parentId, request_span.id) assert.equals(request_span.name .. " (balancer try 1)", balancer_span.name) assert.equals("number", type(balancer_span.timestamp)) if balancer_span.duration then assert.equals("number", type(balancer_span.duration)) end assert.same({ ipv4 = helpers.mock_upstream_host, port = helpers.mock_upstream_port, serviceName = service.name, }, balancer_span.remoteEndpoint) assert.same({ serviceName = "kong" }, balancer_span.localEndpoint) assert.same({ ["kong.balancer.try"] = "1", ["kong.route"] = route.id, ["kong.route_name"] = route.name, ["kong.service"] = service.id, ["kong.service_name"] = service.name, }, balancer_span.tags) end) it("generates spans, tags and annotations for regular requests (#grpc)", function() local start_s = ngx.now() local ok, resp = proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" }, opts = { ["-H"] = "'x-b3-sampled: 1'", ["-authority"] = "grpc-route", } }) assert(ok, resp) assert.truthy(resp) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, grpc_service.name) -- common assertions for request_span and proxy_span assert_span_invariants(request_span, proxy_span, "post", traceid_byte_count * 2, start_s, "kong") -- specific assertions for request_span local request_tags = request_span.tags assert.truthy(request_tags["kong.node.id"]:match("^[%x-]+$")) request_tags["kong.node.id"] = nil assert.same({ ["http.method"] = "POST", ["http.path"] = "/hello.HelloService/SayHello", ["http.status_code"] = "200", -- found (matches server status) ["http.protocol"] = "HTTP/2", ["http.host"] = "grpc-route", lc = "kong", static = "ok", }, request_tags) local consumer_port = request_span.remoteEndpoint.port assert_is_integer(consumer_port) assert.same({ ipv4 = '127.0.0.1', port = consumer_port, }, request_span.remoteEndpoint) -- specific assertions for proxy_span assert.same(proxy_span.tags["kong.route"], grpc_route.id) assert.same(proxy_span.tags["kong.route_name"], grpc_route.name) assert.same(proxy_span.tags["peer.hostname"], helpers.grpcbin_host) -- random ip assigned by Docker to the grpcbin container local grpcbin_ip = proxy_span.remoteEndpoint.ipv4 assert.same({ ipv4 = grpcbin_ip, port = helpers.grpcbin_port, serviceName = grpc_service.name, }, proxy_span.remoteEndpoint) -- specific assertions for balancer_span assert.equals(balancer_span.parentId, request_span.id) assert.equals(request_span.name .. " (balancer try 1)", balancer_span.name) assert_valid_timestamp(balancer_span.timestamp, start_s) if balancer_span.duration then assert_is_integer(balancer_span.duration) end assert.same({ ipv4 = grpcbin_ip, port = helpers.grpcbin_port, serviceName = grpc_service.name, }, balancer_span.remoteEndpoint) assert.same({ serviceName = "kong" }, balancer_span.localEndpoint) assert.same({ ["kong.balancer.try"] = "1", ["kong.service"] = grpc_route.service.id, ["kong.service_name"] = grpc_service.name, ["kong.route"] = grpc_route.id, ["kong.route_name"] = grpc_route.name, }, balancer_span.tags) end) it("generates spans, tags and annotations for regular #stream requests", function() local start_s = ngx.now() local tcp = ngx.socket.tcp() assert(tcp:connect(helpers.get_proxy_ip(false), 19000)) assert(tcp:send("hello\n")) local body = assert(tcp:receive("*a")) assert.equal("hello\n", body) assert(tcp:close()) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, tcp_service.name) -- request span assert.same("table", type(request_span)) assert.same("string", type(request_span.id)) assert.same("stream", request_span.name) assert.same(request_span.id, proxy_span.parentId) assert.same("SERVER", request_span.kind) assert.same("string", type(request_span.traceId)) assert_valid_timestamp(request_span.timestamp, start_s) if request_span.duration and proxy_span.duration then assert.truthy(request_span.duration >= proxy_span.duration) end assert.is_nil(request_span.annotations) assert.same({ serviceName = "kong" }, request_span.localEndpoint) local request_tags = request_span.tags assert.truthy(request_tags["kong.node.id"]:match("^[%x-]+$")) request_tags["kong.node.id"] = nil assert.same({ lc = "kong", static = "ok", }, request_tags) local consumer_port = request_span.remoteEndpoint.port assert_is_integer(consumer_port) assert.same({ ipv4 = "127.0.0.1", port = consumer_port, }, request_span.remoteEndpoint) -- proxy span assert.same("table", type(proxy_span)) assert.same("string", type(proxy_span.id)) assert.same(request_span.name .. " (proxy)", proxy_span.name) assert.same(request_span.id, proxy_span.parentId) assert.same("CLIENT", proxy_span.kind) assert.same("string", type(proxy_span.traceId)) assert_valid_timestamp(proxy_span.timestamp, start_s) if proxy_span.duration then assert.truthy(proxy_span.duration >= 0) end assert.equals(2, #proxy_span.annotations) local pann = annotations_to_hash(proxy_span.annotations) assert_valid_timestamp(pann["kps"], start_s) assert_valid_timestamp(pann["kpf"], start_s) assert.truthy(pann["kps"] <= pann["kpf"]) assert.same({ ["kong.route"] = tcp_route.id, ["kong.route_name"] = tcp_route.name, ["kong.service"] = tcp_service.id, ["kong.service_name"] = tcp_service.name, ["peer.hostname"] = "127.0.0.1", }, proxy_span.tags) assert.same({ ipv4 = helpers.mock_upstream_host, port = helpers.mock_upstream_stream_port, serviceName = tcp_service.name, }, proxy_span.remoteEndpoint) -- specific assertions for balancer_span assert.equals(balancer_span.parentId, request_span.id) assert.equals(request_span.name .. " (balancer try 1)", balancer_span.name) assert.equals("number", type(balancer_span.timestamp)) if balancer_span.duration then assert.equals("number", type(balancer_span.duration)) end assert.same({ ipv4 = helpers.mock_upstream_host, port = helpers.mock_upstream_stream_port, serviceName = tcp_service.name, }, balancer_span.remoteEndpoint) assert.same({ serviceName = "kong" }, balancer_span.localEndpoint) assert.same({ ["kong.balancer.try"] = "1", ["kong.route"] = tcp_route.id, ["kong.route_name"] = tcp_route.name, ["kong.service"] = tcp_service.id, ["kong.service_name"] = tcp_service.name, }, balancer_span.tags) end) it("generates spans, tags and annotations for non-matched requests", function() local trace_id = gen_trace_id(traceid_byte_count) local start_s = ngx.now() local r = assert(proxy_client:send { method = "GET", path = "/foobar", headers = { ["x-b3-traceid"] = trace_id, ["x-b3-sampled"] = "1", ["zipkin-tags"] = "error = true" }, }) assert.response(r).has.status(404) local proxy_span, request_span = wait_for_spans(zipkin_client, 2, nil, trace_id) -- common assertions for request_span and proxy_span assert_span_invariants(request_span, proxy_span, "get", #trace_id, start_s, "kong") -- specific assertions for request_span local request_tags = request_span.tags assert.truthy(request_tags["kong.node.id"]:match("^[%x-]+$")) request_tags["kong.node.id"] = nil assert.same({ ["http.method"] = "GET", ["http.path"] = "/foobar", ["http.status_code"] = "404", -- note that this was "not found" ["http.protocol"] = 'HTTP/1.1', ["http.host"] = '0.0.0.0', lc = "kong", static = "ok", error = "true", }, request_tags) local consumer_port = request_span.remoteEndpoint.port assert_is_integer(consumer_port) assert.same({ ipv4 = "127.0.0.1", port = consumer_port }, request_span.remoteEndpoint) -- specific assertions for proxy_span assert.is_nil(proxy_span.tags) assert.is_nil(proxy_span.remoteEndpoint) assert.same({ serviceName = "kong" }, proxy_span.localEndpoint) end) it("propagates b3 headers for non-matched requests", function() local trace_id = gen_trace_id(traceid_byte_count) local r = assert(proxy_client:send { method = "GET", path = "/foobar", headers = { ["x-b3-traceid"] = trace_id, ["x-b3-sampled"] = "1", }, }) assert.response(r).has.status(404) local proxy_span, request_span = wait_for_spans(zipkin_client, 2, nil, trace_id) assert.equals(trace_id, proxy_span.traceId) assert.equals(trace_id, request_span.traceId) end) describe("b3 single header propagation", function() it("works on regular calls", function() local trace_id = gen_trace_id(traceid_byte_count) local span_id = gen_span_id() local parent_id = gen_span_id() local r = proxy_client:get("/", { headers = { b3 = fmt("%s-%s-%s-%s", trace_id, span_id, "1", parent_id), host = "http-route", }, }) local body = assert.response(r).has.status(200) local json = cjson.decode(body) assert.matches(trace_id .. "%-%x+%-1%-%x+", json.headers.b3) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(span_id, request_span.id) assert.equals(parent_id, request_span.parentId) assert.equals(trace_id, proxy_span.traceId) assert.not_equals(span_id, proxy_span.id) assert.equals(span_id, proxy_span.parentId) assert.equals(trace_id, balancer_span.traceId) assert.not_equals(span_id, balancer_span.id) assert.equals(span_id, balancer_span.parentId) end) it("works without parent_id", function() local trace_id = gen_trace_id(traceid_byte_count) local span_id = gen_span_id() local r = proxy_client:get("/", { headers = { b3 = fmt("%s-%s-1", trace_id, span_id), host = "http-route", }, }) local body = assert.response(r).has.status(200) local json = cjson.decode(body) assert.matches(trace_id .. "%-%x+%-1%-%x+", json.headers.b3) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(span_id, request_span.id) assert.equals(trace_id, proxy_span.traceId) assert.not_equals(span_id, proxy_span.id) assert.equals(span_id, proxy_span.parentId) assert.equals(trace_id, balancer_span.traceId) assert.not_equals(span_id, balancer_span.id) assert.equals(span_id, balancer_span.parentId) end) it("works with only trace_id and span_id", function() local trace_id = gen_trace_id(traceid_byte_count) local span_id = gen_span_id() local r = proxy_client:get("/", { headers = { b3 = fmt("%s-%s", trace_id, span_id), ["x-b3-sampled"] = "1", host = "http-route", }, }) local body = assert.response(r).has.status(200) local json = cjson.decode(body) assert.matches(trace_id .. "%-%x+%-1%-%x+", json.headers.b3) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(span_id, request_span.id) assert.equals(trace_id, proxy_span.traceId) assert.not_equals(span_id, proxy_span.id) assert.equals(span_id, proxy_span.parentId) assert.equals(trace_id, balancer_span.traceId) assert.not_equals(span_id, balancer_span.id) assert.equals(span_id, balancer_span.parentId) end) it("works on non-matched requests", function() local trace_id = gen_trace_id(traceid_byte_count) local span_id = gen_span_id() local r = proxy_client:get("/foobar", { headers = { b3 = fmt("%s-%s-1", trace_id, span_id) }, }) assert.response(r).has.status(404) local proxy_span, request_span = wait_for_spans(zipkin_client, 2, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(span_id, request_span.id) assert.equals(trace_id, proxy_span.traceId) assert.not_equals(span_id, proxy_span.id) assert.equals(span_id, proxy_span.parentId) end) end) describe("w3c traceparent header propagation", function() it("works on regular calls", function() local trace_id = gen_trace_id(16) -- w3c only admits 16-byte trace_ids local parent_id = gen_span_id() local r = proxy_client:get("/", { headers = { traceparent = fmt("00-%s-%s-01", trace_id, parent_id), host = "http-route" }, }) local body = assert.response(r).has.status(200) local json = cjson.decode(body) assert.matches("00%-" .. trace_id .. "%-%x+-01", json.headers.traceparent) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(parent_id, request_span.parentId) assert.equals(trace_id, proxy_span.traceId) assert.equals(trace_id, balancer_span.traceId) end) it("works on non-matched requests", function() local trace_id = gen_trace_id(16) -- w3c only admits 16-bit trace_ids local parent_id = gen_span_id() local r = proxy_client:get("/foobar", { headers = { traceparent = fmt("00-%s-%s-01", trace_id, parent_id), }, }) assert.response(r).has.status(404) local proxy_span, request_span = wait_for_spans(zipkin_client, 2, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(parent_id, request_span.parentId) assert.equals(trace_id, proxy_span.traceId) end) end) describe("jaeger uber-trace-id header propagation", function() it("works on regular calls", function() local trace_id = gen_trace_id(traceid_byte_count) local span_id = gen_span_id() local parent_id = gen_span_id() local r = proxy_client:get("/", { headers = { ["uber-trace-id"] = fmt("%s:%s:%s:%s", trace_id, span_id, parent_id, "1"), host = "http-route" }, }) local body = assert.response(r).has.status(200) local json = cjson.decode(body) assert.matches(('0'):rep(32-#trace_id) .. trace_id .. ":%x+:" .. span_id .. ":01", json.headers["uber-trace-id"]) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(span_id, request_span.id) assert.equals(parent_id, request_span.parentId) assert.equals(trace_id, proxy_span.traceId) assert.not_equals(span_id, proxy_span.id) assert.equals(span_id, proxy_span.parentId) assert.equals(trace_id, balancer_span.traceId) assert.not_equals(span_id, balancer_span.id) assert.equals(span_id, balancer_span.parentId) end) it("works on non-matched requests", function() local trace_id = gen_trace_id(traceid_byte_count) local span_id = gen_span_id() local parent_id = gen_span_id() local r = proxy_client:get("/foobar", { headers = { ["uber-trace-id"] = fmt("%s:%s:%s:%s", trace_id, span_id, parent_id, "1"), }, }) assert.response(r).has.status(404) local proxy_span, request_span = wait_for_spans(zipkin_client, 2, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(span_id, request_span.id) assert.equals(parent_id, request_span.parentId) assert.equals(trace_id, proxy_span.traceId) assert.not_equals(span_id, proxy_span.id) assert.equals(span_id, proxy_span.parentId) end) end) describe("ot header propagation", function() it("works on regular calls", function() local trace_id = gen_trace_id(8) local span_id = gen_span_id() local r = proxy_client:get("/", { headers = { ["ot-tracer-traceid"] = trace_id, ["ot-tracer-spanid"] = span_id, ["ot-tracer-sampled"] = "1", host = "http-route", }, }) local body = assert.response(r).has.status(200) local json = cjson.decode(body) assert.equals(trace_id, json.headers["ot-tracer-traceid"]) local balancer_span, proxy_span, request_span = wait_for_spans(zipkin_client, 3, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(trace_id, proxy_span.traceId) assert.equals(trace_id, balancer_span.traceId) end) it("works on non-matched requests", function() local trace_id = gen_trace_id(8) local span_id = gen_span_id() local r = proxy_client:get("/foobar", { headers = { ["ot-tracer-traceid"] = trace_id, ["ot-tracer-spanid"] = span_id, ["ot-tracer-sampled"] = "1", }, }) assert.response(r).has.status(404) local proxy_span, request_span = wait_for_spans(zipkin_client, 2, nil, trace_id) assert.equals(trace_id, request_span.traceId) assert.equals(trace_id, proxy_span.traceId) end) end) describe("header type with 'preserve' config and no inbound headers", function() it("uses whatever is set in the plugin's config.default_header_type property", function() local r = proxy_client:get("/", { headers = { -- no tracing header host = "http-route" }, }) local body = assert.response(r).has.status(200) local json = cjson.decode(body) assert.not_nil(json.headers.b3) end) end) end) end end