Files
2026-07-13 12:32:21 +08:00

547 lines
18 KiB
Lua

local constants = require "kong.constants"
local helpers = require "spec.helpers"
local websocket_client = require "resty.websocket.client"
local cjson = require "cjson"
local MESSAGE = "echo, ping, pong. echo, ping, pong. echo, ping, pong.\n"
local function websocket_send_text_and_get_echo(uri)
local payload = { message = "hello websocket" }
local wc = assert(websocket_client:new())
assert(wc:connect(uri))
assert(wc:send_text(cjson.encode(payload)))
local frame, typ, err = wc:recv_frame()
assert.is_nil(wc.fatal)
assert(frame, err)
assert.equal("text", typ)
assert.same(payload, cjson.decode(frame))
assert(wc:send_close())
end
for _, strategy in helpers.each_strategy() do
-- Might need to be marked as flaky because it may require an arbitrary high port?
describe("anonymous reports in Admin API #" .. strategy, function()
local dns_hostsfile
local reports_server
local reports_send_ping = function(opts)
ngx.sleep(0.01) -- hand over the CPU so other threads can do work (processing the sent data)
local admin_client = helpers.admin_client()
local opts = opts or {}
local port = opts.port or nil
local res = admin_client:post("/reports/send-ping" .. (port and "?port=" .. port or ""))
assert.response(res).has_status(200)
admin_client:close()
end
local reports_send_stream_ping = function()
ngx.sleep(0.01) -- hand over the CPU so other threads can do work (processing the sent data)
local tcp = ngx.socket.tcp()
assert(tcp:connect(helpers.get_proxy_ip(false), 19001))
local body = assert(tcp:receive("*a"))
assert.equal("ok", body)
tcp:close()
end
lazy_setup(function()
dns_hostsfile = assert(os.tmpname() .. ".hosts")
local fd = assert(io.open(dns_hostsfile, "wb"))
assert(fd:write("127.0.0.1 " .. constants.REPORTS.ADDRESS))
assert(fd:close())
local bp = assert(helpers.get_db_utils(strategy, {
"services",
"routes",
"plugins",
"certificates",
"snis",
}, { "reports-api" }))
local http_srv = assert(bp.services:insert {
name = "mock-service",
host = helpers.mock_upstream_host,
port = helpers.mock_upstream_port,
})
bp.routes:insert({ service = http_srv,
protocols = { "http" },
hosts = { "http-service.test" } })
bp.routes:insert({ service = http_srv,
protocols = { "https" },
hosts = { "https-service.test" } })
local grpc_srv = bp.services:insert({
name = "grpc",
url = helpers.grpcbin_url,
})
bp.routes:insert({
service = grpc_srv,
protocols = { "grpc" },
hosts = { "grpc" },
})
local grpcs_srv = bp.services:insert({
name = "grpcs",
url = helpers.grpcbin_ssl_url,
})
bp.routes:insert({
service = grpcs_srv,
protocols = { "grpcs" },
hosts = { "grpcs" },
})
local ws_srv = bp.services:insert({
name = "ws",
path = "/ws",
})
bp.routes:insert({
service = ws_srv,
protocols = { "http" },
paths = { "/up-ws" },
strip_path = true,
})
local tcp_srv = bp.services:insert({
name = "tcp",
host = helpers.mock_upstream_host,
port = helpers.mock_upstream_stream_port,
protocol = "tcp"
})
bp.routes:insert {
destinations = {
{ port = 19000, },
},
protocols = {
"tcp",
},
service = tcp_srv,
}
local tls_srv = bp.services:insert({
name = "tls",
host = helpers.mock_upstream_host,
port = helpers.mock_upstream_stream_ssl_port,
protocol = "tls"
})
bp.routes:insert {
destinations = {
{ port = 19443, },
},
protocols = {
"tls",
},
service = tls_srv,
}
local reports_srv = bp.services:insert({
name = "reports-srv",
host = helpers.mock_upstream_host,
port = helpers.mock_upstream_stream_port,
protocol = "tcp"
})
bp.routes:insert {
destinations = {
{ port = 19001, },
},
protocols = {
"tcp",
},
service = reports_srv,
}
bp.plugins:insert({
name = "reports-api",
service = { id = reports_srv.id },
protocols = { "tcp" },
config = {}
})
assert(helpers.start_kong({
nginx_conf = "spec/fixtures/custom_nginx.template",
database = strategy,
dns_hostsfile = dns_hostsfile,
resolver_hosts_file = dns_hostsfile,
anonymous_reports = true,
plugins = "reports-api",
stream_listen = helpers.get_proxy_ip(false) .. ":19000," ..
helpers.get_proxy_ip(false) .. ":19001," ..
helpers.get_proxy_ip(true) .. ":19443 ssl",
}))
end)
lazy_teardown(function()
helpers.stop_kong()
os.remove(dns_hostsfile)
end)
before_each(function()
reports_server = helpers.tcp_server(constants.REPORTS.STATS_TLS_PORT, {tls=true})
end)
it("reports http requests", function()
local proxy_client = assert(helpers.proxy_client())
local res = proxy_client:get("/", {
headers = { host = "http-service.test" }
})
assert.response(res).has_status(200)
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match("requests=1", reports_data)
assert.match("http_reqs=1", reports_data)
assert.match("https_reqs=0", reports_data)
assert.match("h2c_reqs=0", reports_data)
assert.match("h2_reqs=0", reports_data)
assert.match("grpc_reqs=0", reports_data)
assert.match("grpcs_reqs=0", reports_data)
assert.match("ws_reqs=0", reports_data)
assert.match("wss_reqs=0", reports_data)
proxy_client:close()
end)
it("reports https requests", function()
local proxy_ssl_client = assert(helpers.proxy_ssl_client())
local res = proxy_ssl_client:get("/", {
headers = { host = "https-service.test" }
})
assert.response(res).has_status(200)
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match("requests=1", reports_data)
assert.match("http_reqs=0", reports_data)
assert.match("https_reqs=1", reports_data)
assert.match("h2c_reqs=0", reports_data)
assert.match("h2_reqs=0", reports_data)
assert.match("grpc_reqs=0", reports_data)
assert.match("grpcs_reqs=0", reports_data)
assert.match("ws_reqs=0", reports_data)
assert.match("wss_reqs=0", reports_data)
proxy_ssl_client:close()
end)
it("when send http request to https port, no other error in error.log", function()
local https_port = assert(helpers.get_proxy_port(true))
local proxy_client = assert(helpers.proxy_client(nil, https_port))
local res = proxy_client:get("/", {
headers = { host = "http-service.test" }
})
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
assert.response(res).has_status(400)
assert.logfile().has.no.line("using uninitialized")
assert.logfile().has.no.line("could not determine log suffix (scheme=http, proxy_mode=)")
proxy_client:close()
end)
it("reports h2c requests", function()
local h2c_client = assert(helpers.proxy_client_h2c())
local body, headers = h2c_client({
headers = { [":authority"] = "http-service.test" }
})
assert.equal(200, tonumber(headers:get(":status")))
assert.is_not_nil(body)
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match("requests=1", reports_data)
assert.match("http_reqs=0", reports_data)
assert.match("https_reqs=0", reports_data)
assert.match("h2c_reqs=1", reports_data)
assert.match("h2_reqs=0", reports_data)
assert.match("grpc_reqs=0", reports_data)
assert.match("grpcs_reqs=0", reports_data)
assert.match("ws_reqs=0", reports_data)
assert.match("wss_reqs=0", reports_data)
end)
it("reports h2 requests", function()
local h2_client = assert(helpers.proxy_client_h2())
local body, headers = h2_client({
headers = { [":authority"] = "https-service.test" }
})
assert.equal(200, tonumber(headers:get(":status")))
assert.is_not_nil(body)
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match("requests=1", reports_data)
assert.match("http_reqs=0", reports_data)
assert.match("https_reqs=0", reports_data)
assert.match("h2c_reqs=0", reports_data)
assert.match("h2_reqs=1", reports_data)
assert.match("grpc_reqs=0", reports_data)
assert.match("grpcs_reqs=0", reports_data)
assert.match("ws_reqs=0", reports_data)
assert.match("wss_reqs=0", reports_data)
end)
it("reports grpc requests", function()
local grpc_client = helpers.proxy_client_grpc()
assert(grpc_client({
service = "hello.HelloService.SayHello",
opts = {
["-authority"] = "grpc",
},
}))
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match("requests=1", reports_data)
assert.match("http_reqs=0", reports_data)
assert.match("https_reqs=0", reports_data)
assert.match("h2c_reqs=0", reports_data)
assert.match("h2_reqs=0", reports_data)
assert.match("grpc_reqs=1", reports_data)
assert.match("grpcs_reqs=0", reports_data)
assert.match("ws_reqs=0", reports_data)
assert.match("wss_reqs=0", reports_data)
end)
it("reports grpcs requests", function()
local grpcs_client = assert(helpers.proxy_client_grpcs())
grpcs_client({
service = "hello.HelloService.SayHello",
opts = {
["-authority"] = "grpcs",
},
})
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match("requests=1", reports_data)
assert.match("http_reqs=0", reports_data)
assert.match("https_reqs=0", reports_data)
assert.match("h2c_reqs=0", reports_data)
assert.match("h2_reqs=0", reports_data)
assert.match("grpc_reqs=0", reports_data)
assert.match("grpcs_reqs=1", reports_data)
assert.match("ws_reqs=0", reports_data)
assert.match("wss_reqs=0", reports_data)
end)
it("reports ws requests", function()
websocket_send_text_and_get_echo("ws://" .. helpers.get_proxy_ip(false) ..
":" .. helpers.get_proxy_port(false) .. "/up-ws")
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match("requests=1", reports_data)
assert.match("http_reqs=0", reports_data)
assert.match("https_reqs=0", reports_data)
assert.match("h2c_reqs=0", reports_data)
assert.match("h2_reqs=0", reports_data)
assert.match("grpc_reqs=0", reports_data)
assert.match("grpcs_reqs=0", reports_data)
assert.match("ws_reqs=1", reports_data)
assert.match("wss_reqs=0", reports_data)
end)
it("reports wss requests", function()
websocket_send_text_and_get_echo("wss://" .. helpers.get_proxy_ip(true) ..
":" .. helpers.get_proxy_port(true) .. "/up-ws")
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match("requests=1", reports_data)
assert.match("http_reqs=0", reports_data)
assert.match("https_reqs=0", reports_data)
assert.match("h2c_reqs=0", reports_data)
assert.match("h2_reqs=0", reports_data)
assert.match("grpc_reqs=0", reports_data)
assert.match("grpcs_reqs=0", reports_data)
assert.match("ws_reqs=0", reports_data)
assert.match("wss_reqs=1", reports_data)
end)
pending("#stream reports tcp streams", function()
local tcp = ngx.socket.tcp()
assert(tcp:connect(helpers.get_proxy_ip(false), 19000))
assert(tcp:send(MESSAGE))
local body = assert(tcp:receive("*a"))
assert.equal(MESSAGE, body)
tcp:close()
reports_send_stream_ping()
local _, reports_data = assert(reports_server:join())
assert.match("streams=1", reports_data)
assert.match("tcp_streams=1", reports_data)
assert.match("tls_streams=0", reports_data)
end)
pending("#stream reports tls streams", function()
local tcp = ngx.socket.tcp()
assert(tcp:connect(helpers.get_proxy_ip(true), 19443))
assert(tcp:sslhandshake(nil, "this-is-needed.test", false))
assert(tcp:send(MESSAGE))
local body = assert(tcp:receive("*a"))
assert.equal(MESSAGE, body)
tcp:close()
reports_send_stream_ping()
local _, reports_data = assert(reports_server:join())
assert.match("streams=2", reports_data)
assert.match("tcp_streams=1", reports_data) -- it counts the stream request for the ping
assert.match("tls_streams=1", reports_data)
end)
it("does not log NGINX-produced errors", function()
local proxy_client = assert(helpers.proxy_client())
local res = assert(proxy_client:send {
method = "GET",
path = "/",
headers = {
["X-Large"] = string.rep("a", 2^10 * 10), -- default large_client_header_buffers is 8k
}
})
-- send a ping so the tcp server shutdown cleanly and not with a timeout.
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
assert.res_status(400, res)
proxy_client:close()
assert.errlog()
.has.no.line([[could not determine log suffix]], true)
end)
it("reports route statistics", function()
local proxy_client = assert(helpers.proxy_client())
local res = proxy_client:get("/", {
headers = { host = "http-service.test" }
})
assert.response(res).has_status(200)
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match([["headers":0]], reports_data)
assert.match([["routes":5]], reports_data)
assert.match([["http":3]], reports_data)
assert.match([["grpc":2]], reports_data)
assert.match([["stream":0]], reports_data)
assert.match([["tls_passthrough":0]], reports_data)
assert.match([["flavor":"traditional_compatible"]], reports_data)
assert.match([["paths":1]], reports_data)
assert.match([["regex_routes":0]], reports_data)
assert.match([["v1":0]], reports_data)
assert.match([["v0":5]], reports_data)
proxy_client:close()
end)
if strategy ~= "off" then
it("reports route statistics after #change", function()
local admin = helpers.admin_client()
-- any other route will fail because we are ... routing all traffic to localhost
assert.res_status(201, admin:send({
method = "POST",
path = "/services",
body = {
name = "test",
url = "http://localhost:9001/services/",
path = "/",
},
headers = { ["Content-Type"] = "application/json" },
}))
assert.res_status(201, admin:send({
method = "POST",
path = "/services/test/routes",
body = {
protocols = { "http" },
headers = { ["x-test"] = { "test" } },
paths = { "~/test", "/normal" },
preserve_host = false,
path_handling = "v1",
},
headers = { ["Content-Type"] = "application/json" },
}))
local proxy_client = assert(helpers.proxy_client())
helpers.pwait_until(function()
local res = proxy_client:get("/test", {
headers = { ["x-test"] = "test", host = "http-service2.test" }
})
assert.response(res).has_status(200)
end, 1000)
for _ = 1, 2 do
local res = proxy_client:get("/test", {
headers = { ["x-test"] = "test", host = "http-service2.test" }
})
assert.response(res).has_status(200)
end
for _ = 1, 5 do
local res = proxy_client:get("/foo", {
headers = { ["x-test"] = "test", host = "http-service2.test" }
})
assert.response(res).has_status(404)
end
reports_send_ping({port=constants.REPORTS.STATS_TLS_PORT})
local _, reports_data = assert(reports_server:join())
assert.match([["headers":1]], reports_data)
assert.match([["routes":6]], reports_data)
assert.match([["http":4]], reports_data)
assert.match([["grpc":2]], reports_data)
assert.match([["stream":0]], reports_data)
assert.match([["tls_passthrough":0]], reports_data)
assert.match([["flavor":"traditional_compatible"]], reports_data)
assert.match([["paths":3]], reports_data)
assert.match([["regex_routes":1]], reports_data)
assert.match([["v1":1]], reports_data)
assert.match([["v0":5]], reports_data)
assert.match([[request_route_cache_hit_pos=2]], reports_data)
-- the wait_util may trigger cache misses, so we can't assert on accurate values
assert.match([[request_route_cache_hit_neg=]], reports_data)
proxy_client:close()
end)
end
end)
end