Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

correlation-id plugin, fixes #1086 #1094

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions kong-0.7.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ build = {
["kong.plugins.acl.api"] = "kong/plugins/acl/api.lua",
["kong.plugins.acl.daos"] = "kong/plugins/acl/daos.lua",

["kong.plugins.correlation-id.handler"] = "kong/plugins/correlation-id/handler.lua",
["kong.plugins.correlation-id.schema"] = "kong/plugins/correlation-id/schema.lua",

["kong.api.app"] = "kong/api/app.lua",
["kong.api.crud_helpers"] = "kong/api/crud_helpers.lua",
["kong.api.route_helpers"] = "kong/api/route_helpers.lua",
Expand Down
2 changes: 1 addition & 1 deletion kong/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ return {
NGINX_CONFIG = "nginx.conf"
},
PLUGINS_AVAILABLE = {
"ssl", "jwt", "acl", "cors", "oauth2", "tcp-log", "udp-log", "file-log",
"ssl", "jwt", "acl", "correlation-id", "cors", "oauth2", "tcp-log", "udp-log", "file-log",
"http-log", "key-auth", "hmac-auth", "basic-auth", "ip-restriction",
"mashape-analytics", "request-transformer", "response-transformer",
"request-size-limiting", "rate-limiting", "response-ratelimiting", "syslog",
Expand Down
60 changes: 60 additions & 0 deletions kong/plugins/correlation-id/handler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
-- Copyright (C) Mashape, Inc.

local BasePlugin = require "kong.plugins.base_plugin"
local uuid = require "lua_uuid"
local req_set_header = ngx.req.set_header
local req_get_headers = ngx.req.get_headers

local CorrelationIdHandler = BasePlugin:extend()

local worker_uuid
local worker_counter

local generators = setmetatable({
["uuid"] = function()
return uuid()
end,
["uuid#counter"] = function()
worker_counter = worker_counter + 1
return worker_uuid.."#"..worker_counter
end,
}, { __index = function(self, generator)
ngx.log(ngx.ERR, "Invalid generator: "..generator)
end
})

function CorrelationIdHandler:new()
CorrelationIdHandler.super.new(self, "correlation-id")
end

function CorrelationIdHandler:init_worker()
CorrelationIdHandler.super.init_worker(self)
worker_uuid = uuid()
worker_counter = 0
end

function CorrelationIdHandler:access(conf)
CorrelationIdHandler.super.access(self)

-- Set header for upstream
local header_value = req_get_headers()[conf.header_name]
if not header_value then
-- Generate the header value
header_value = generators[conf.generator]()
req_set_header(conf.header_name, header_value)
end

if conf.echo_downstream then
-- For later use, to echo it back downstream
ngx.ctx.correlationid_header_value = header_value
end
end

function CorrelationIdHandler:header_filter(conf)
CorrelationIdHandler.super.header_filter(self)
if conf.echo_downstream then
ngx.header[conf.header_name] = ngx.ctx.correlationid_header_value
end
end

return CorrelationIdHandler
17 changes: 17 additions & 0 deletions kong/plugins/correlation-id/schema.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
return {
fields = {
header_name = {
type = "string",
default = "Kong-Request-ID"
},
generator = {
type = "string",
default = "uuid#counter",
enum = {"uuid", "uuid#counter"}
},
echo_downstream = {
type = "boolean",
default = false
}
}
}
91 changes: 91 additions & 0 deletions spec/plugins/correlation-id/access_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
local spec_helper = require "spec.spec_helpers"
local http_client = require "kong.tools.http_client"
local json = require "cjson"

local STUB_GET_URL = spec_helper.STUB_GET_URL
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"
local UUID_COUNTER_PATTERN = UUID_PATTERN.."#%d"
local DEFAULT_HEADER_NAME = "Kong-Request-ID"

describe("Correlation ID Plugin", function()

setup(function()
spec_helper.prepare_db()
spec_helper.insert_fixtures {
api = {
{request_host = "correlation1.com", upstream_url = "http://mockbin.com"},
{request_host = "correlation2.com", upstream_url = "http://mockbin.com"},
{request_host = "correlation3.com", upstream_url = "http://mockbin.com"},
{request_host = "correlation4.com", upstream_url = "http://mockbin.com"}
},
plugin = {
{name = "correlation-id", config = {echo_downstream = true}, __api = 1},
{name = "correlation-id", config = {header_name = "Foo-Bar-Id", echo_downstream = true}, __api = 2},
{name = "correlation-id", config = {generator = "uuid", echo_downstream = true}, __api = 3},
{name = "correlation-id", config = {}, __api = 4},
}
}
spec_helper.start_kong()
end)

teardown(function()
spec_helper.stop_kong()
end)

local function test_with(host, header, pattern)
local _, status1, headers1 = http_client.get(STUB_GET_URL, nil, {host = host})
assert.equal(200, status1)
local correlation_id1 = headers1[header:lower()]
assert.are.matches(pattern, correlation_id1)

local _, status2, headers2 = http_client.get(STUB_GET_URL, nil, {host = host})
assert.equal(200, status2)
local correlation_id2 = headers2[header:lower()]
assert.are.matches(pattern, correlation_id2)

assert.are_not_equals(correlation_id1, correlation_id2)

-- TODO kong_TEST.yml's worker_processes has to be 1 for the below to work.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be in the future :)

--[[
if pattern == UUID_COUNTER_PATTERN then
local uuid1 = correlation_id1:sub(0, -3)
local uuid2 = correlation_id2:sub(0, -3)
assert.equals(uuid1, uuid2)

local counter1 = correlation_id1:sub(-1)
local counter2 = correlation_id2:sub(-1)
assert.True(counter1 + 1 == counter2)
end
--]]
end

it("should increment the counter", function()
test_with("correlation1.com", DEFAULT_HEADER_NAME, UUID_COUNTER_PATTERN)
end)

it("should use the header in the configuration", function()
test_with("correlation2.com", "Foo-Bar-Id", UUID_COUNTER_PATTERN)
end)

it("should generate a unique UUID for every request using default header", function()
test_with("correlation3.com", DEFAULT_HEADER_NAME, UUID_PATTERN)
end)

it("should honour the existing header", function()
local existing_correlation_id = "foo"
local response, status = http_client.get(
STUB_GET_URL,
nil,
{host = "correlation1.com", [DEFAULT_HEADER_NAME] = existing_correlation_id})
assert.equal(200, status)
local correlation_id = json.decode(response).headers[DEFAULT_HEADER_NAME:lower()]
assert.equals(existing_correlation_id, correlation_id)
end)

it("should not echo back the correlation header", function()
local _, status, headers = http_client.get(STUB_GET_URL, nil, {host = "correlation4.com"})
assert.equal(200, status)
local correlation_id = headers[DEFAULT_HEADER_NAME:lower()]
assert.falsy(correlation_id)
end)
end)