kong/spec/03-plugins/26-prometheus/02-access_spec.lua (503 lines of code) (raw):
local helpers = require "spec.helpers"
local tcp_service_port = helpers.get_available_port()
local tcp_proxy_port = helpers.get_available_port()
local UUID_PATTERN = "%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x"
describe("Plugin: prometheus (access)", function()
local proxy_client
local admin_client
local proxy_client_grpc
local proxy_client_grpcs
setup(function()
local bp = helpers.get_db_utils()
local service = bp.services:insert {
name = "mock-service",
host = helpers.mock_upstream_host,
port = helpers.mock_upstream_port,
protocol = helpers.mock_upstream_protocol,
}
bp.routes:insert {
protocols = { "http" },
name = "http-route",
paths = { "/" },
methods = { "GET" },
service = service,
}
local grpc_service = bp.services:insert {
name = "mock-grpc-service",
url = helpers.grpcbin_url,
}
bp.routes:insert {
protocols = { "grpc" },
name = "grpc-route",
hosts = { "grpc" },
service = grpc_service,
}
local grpcs_service = bp.services:insert {
name = "mock-grpcs-service",
url = helpers.grpcbin_ssl_url,
}
bp.routes:insert {
protocols = { "grpcs" },
name = "grpcs-route",
hosts = { "grpcs" },
service = grpcs_service,
}
local tcp_service = bp.services:insert {
name = "tcp-service",
url = "tcp://127.0.0.1:" .. tcp_service_port,
}
bp.routes:insert {
protocols = { "tcp" },
name = "tcp-route",
service = tcp_service,
destinations = { { port = tcp_proxy_port } },
}
bp.plugins:insert {
protocols = { "http", "https", "grpc", "grpcs", "tcp", "tls" },
name = "prometheus",
config = {
status_code_metrics = true,
latency_metrics = true,
bandwidth_metrics = true,
upstream_health_metrics = true,
},
}
assert(helpers.start_kong {
nginx_conf = "spec/fixtures/custom_nginx.template",
plugins = "bundled",
stream_listen = "127.0.0.1:" .. tcp_proxy_port,
})
proxy_client = helpers.proxy_client()
admin_client = helpers.admin_client()
proxy_client_grpc = helpers.proxy_client_grpc()
proxy_client_grpcs = helpers.proxy_client_grpcs()
end)
teardown(function()
if proxy_client then
proxy_client:close()
end
if admin_client then
admin_client:close()
end
helpers.stop_kong()
end)
it("increments the count for proxied requests", function()
local res = assert(proxy_client:send {
method = "GET",
path = "/status/200",
headers = {
host = helpers.mock_upstream_host,
}
})
assert.res_status(200, res)
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
return body:find('http_requests_total{service="mock-service",route="http-route",code="200",source="service",consumer=""} 1', nil, true)
end)
res = assert(proxy_client:send {
method = "GET",
path = "/status/400",
headers = {
host = helpers.mock_upstream_host,
}
})
assert.res_status(400, res)
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
return body:find('http_requests_total{service="mock-service",route="http-route",code="400",source="service",consumer=""} 1', nil, true)
end)
end)
it("increments the count for proxied grpc requests", function()
local ok, resp = proxy_client_grpc({
service = "hello.HelloService.SayHello",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpc",
}
})
assert(ok, resp)
assert.truthy(resp)
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
return body:find('http_requests_total{service="mock-grpc-service",route="grpc-route",code="200",source="service",consumer=""} 1', nil, true)
end)
ok, resp = proxy_client_grpcs({
service = "hello.HelloService.SayHello",
body = {
greeting = "world!"
},
opts = {
["-authority"] = "grpcs",
}
})
assert(ok, resp)
assert.truthy(resp)
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
return body:find('http_requests_total{service="mock-grpcs-service",route="grpcs-route",code="200",source="service",consumer=""} 1', nil, true)
end)
end)
it("increments the count for proxied TCP streams", function()
local thread = helpers.tcp_server(tcp_service_port, { requests = 1 })
local conn = assert(ngx.socket.connect("127.0.0.1", tcp_proxy_port))
assert(conn:send("hi there!\n"))
local gotback = assert(conn:receive("*a"))
assert.equal("hi there!\n", gotback)
conn:close()
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
assert.matches('kong_stream_sessions_total{service="tcp-service",route="tcp-route",code="200",source="service"} 1', body, nil, true)
assert.matches('kong_session_duration_ms_bucket{service="tcp%-service",route="tcp%-route",le="%+Inf"} %d+', body)
return body:find('kong_stream_sessions_total{service="tcp-service",route="tcp-route",code="200",source="service"} 1', nil, true)
end)
thread:join()
end)
it("does not log error if no service was matched", function()
-- cleanup logs
os.execute(":> " .. helpers.test_conf.nginx_err_logs)
local res = assert(proxy_client:send {
method = "POST",
path = "/no-route-match-in-kong",
})
assert.res_status(404, res)
-- make sure no errors
assert.logfile().has.no.line("[error]", true, 10)
end)
it("does not log error during a scrape", function()
-- cleanup logs
os.execute(":> " .. helpers.test_conf.nginx_err_logs)
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
-- make sure no errors
assert.logfile().has.no.line("[error]", true, 10)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
it("scrape response has metrics and comments only", function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
for line in body:gmatch("[^\r\n]+") do
assert.matches("^[#|kong]", line)
end
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
it("exposes db reachability metrics", function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_datastore_reachable 1', body, nil, true)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
it("exposes Lua worker VM stats", function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_memory_workers_lua_vms_bytes{node_id="' .. UUID_PATTERN .. '",pid="%d+",kong_subsystem="http"} %d+', body)
assert.matches('kong_memory_workers_lua_vms_bytes{node_id="' .. UUID_PATTERN .. '",pid="%d+",kong_subsystem="stream"} %d+', body)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
it("exposes lua_shared_dict metrics", function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_memory_lua_shared_dict_total_bytes' ..
'{node_id="' .. UUID_PATTERN .. '",shared_dict="prometheus_metrics",kong_subsystem="http"} %d+', body)
-- TODO: uncomment below once the ngx.shared iterrator in stream is fixed
-- if stream_available then
-- assert.matches('kong_memory_lua_shared_dict_total_bytes' ..
-- '{shared_dict="stream_prometheus_metrics",kong_subsystem="stream"} %d+', body)
-- end
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
it("does not expose per consumer metrics by default", function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.not_match('http_consumer_status', body, nil, true)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
end)
describe("Plugin: prometheus (access) no stream listeners", function()
local admin_client
setup(function()
local bp = helpers.get_db_utils()
bp.plugins:insert {
protocols = { "http", "https", "grpc", "grpcs", "tcp", "tls" },
name = "prometheus",
config = {
status_code_metrics = true,
latency_metrics = true,
bandwidth_metrics = true,
upstream_health_metrics = true,
},
}
assert(helpers.start_kong {
plugins = "bundled, prometheus",
stream_listen = "off",
})
admin_client = helpers.admin_client()
end)
teardown(function()
if admin_client then
admin_client:close()
end
helpers.stop_kong()
end)
it("exposes Lua worker VM stats only for http subsystem", function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_memory_workers_lua_vms_bytes{node_id="' .. UUID_PATTERN .. '",pid="%d+",kong_subsystem="http"}', body)
assert.not_matches('kong_memory_workers_lua_vms_bytes{node_id="' .. UUID_PATTERN .. '",pid="%d+",kong_subsystem="stream"}', body)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
it("exposes lua_shared_dict metrics only for http subsystem", function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_memory_lua_shared_dict_total_bytes' ..
'{node_id="' .. UUID_PATTERN .. '",shared_dict="prometheus_metrics",kong_subsystem="http"} %d+', body)
assert.not_matches('kong_memory_lua_shared_dict_bytes' ..
'{node_id="' .. UUID_PATTERN .. '",shared_dict="stream_prometheus_metric",kong_subsystem="stream"} %d+', body)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
end)
describe("Plugin: prometheus (access) per-consumer metrics", function()
local proxy_client
local admin_client
setup(function()
local bp = helpers.get_db_utils()
local service = bp.services:insert {
name = "mock-service",
host = helpers.mock_upstream_host,
port = helpers.mock_upstream_port,
protocol = helpers.mock_upstream_protocol,
}
local route = bp.routes:insert {
protocols = { "http" },
name = "http-route",
paths = { "/" },
methods = { "GET" },
service = service,
}
bp.plugins:insert {
protocols = { "http", "https", "grpc", "grpcs", "tcp", "tls" },
name = "prometheus",
config = {
per_consumer = true,
status_code_metrics = true,
latency_metrics = true,
bandwidth_metrics = true,
upstream_health_metrics = true,
},
}
bp.plugins:insert {
name = "key-auth",
route = route,
}
local consumer = bp.consumers:insert {
username = "alice",
}
bp.keyauth_credentials:insert {
key = "alice-key",
consumer = consumer,
}
assert(helpers.start_kong {
nginx_conf = "spec/fixtures/custom_nginx.template",
plugins = "bundled, prometheus",
})
proxy_client = helpers.proxy_client()
admin_client = helpers.admin_client()
end)
teardown(function()
if proxy_client then
proxy_client:close()
end
if admin_client then
admin_client:close()
end
helpers.stop_kong()
end)
it("increments the count for proxied requests", function()
local res = assert(proxy_client:send {
method = "GET",
path = "/status/200",
headers = {
host = helpers.mock_upstream_host,
apikey = 'alice-key',
}
})
assert.res_status(200, res)
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
return body:find('http_requests_total{service="mock-service",route="http-route",code="200",source="service",consumer="alice"} 1', nil, true)
end)
res = assert(proxy_client:send {
method = "GET",
path = "/status/400",
headers = {
host = helpers.mock_upstream_host,
apikey = 'alice-key',
}
})
assert.res_status(400, res)
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
return body:find('http_requests_total{service="mock-service",route="http-route",code="400",source="service",consumer="alice"} 1', nil, true)
end)
end)
it("behave correctly if consumer is not found", function()
local res = assert(proxy_client:send {
method = "GET",
path = "/status/200",
headers = {
host = helpers.mock_upstream_host,
}
})
assert.res_status(401, res)
local body
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
body = assert.res_status(200, res)
return body:find('http_requests_total{service="mock-service",route="http-route",code="200",source="service",consumer="alice"} 1', nil, true)
end)
assert.matches('http_requests_total{service="mock-service",route="http-route",code="401",source="kong",consumer=""} 1', body, nil, true)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
end)
end)
local granular_metrics_set = {
status_code_metrics = "http_requests_total",
latency_metrics = "kong_latency_ms",
bandwidth_metrics = "bandwidth_bytes",
upstream_health_metrics = "upstream_target_health",
}
for switch, expected_pattern in pairs(granular_metrics_set) do
describe("Plugin: prometheus (access) granular metrics switch", function()
local proxy_client
local admin_client
local success_scrape = ""
setup(function()
local bp = helpers.get_db_utils()
local service = bp.services:insert {
name = "mock-service",
host = helpers.mock_upstream_host,
port = helpers.mock_upstream_port,
protocol = helpers.mock_upstream_protocol,
}
bp.routes:insert {
protocols = { "http" },
name = "http-route",
paths = { "/" },
methods = { "GET" },
service = service,
}
local upstream_hc_off = bp.upstreams:insert({
name = "mock-upstream-healthchecksoff",
})
bp.targets:insert {
target = helpers.mock_upstream_host .. ':' .. helpers.mock_upstream_port,
weight = 1000,
upstream = { id = upstream_hc_off.id },
}
bp.plugins:insert {
protocols = { "http", "https", "grpc", "grpcs", "tcp", "tls" },
name = "prometheus",
config = {
[switch] = true,
},
}
assert(helpers.start_kong {
nginx_conf = "spec/fixtures/custom_nginx.template",
plugins = "bundled, prometheus",
nginx_worker_processes = 1, -- due to healthcheck state flakyness and local switch of healthcheck export or not
})
proxy_client = helpers.proxy_client()
admin_client = helpers.admin_client()
end)
teardown(function()
if proxy_client then
proxy_client:close()
end
if admin_client then
admin_client:close()
end
helpers.stop_kong()
end)
it("expected metrics " .. expected_pattern .. " is found", function()
local res = assert(proxy_client:send {
method = "GET",
path = "/status/200",
headers = {
host = helpers.mock_upstream_host,
apikey = 'alice-key',
}
})
assert.res_status(200, res)
helpers.wait_until(function()
local res = assert(admin_client:send {
method = "GET",
path = "/metrics",
})
local body = assert.res_status(200, res)
assert.matches('kong_nginx_metric_errors_total 0', body, nil, true)
success_scrape = body
return body:find(expected_pattern, nil, true)
end)
end)
it("unexpected metrics is not found", function()
for test_switch, test_expected_pattern in pairs(granular_metrics_set) do
if test_switch ~= switch then
assert.not_match(test_expected_pattern, success_scrape, nil, true)
end
end
end)
end)
end