From 5faae63d1ec4d36c12d3ea981b5daf34f8727dad Mon Sep 17 00:00:00 2001 From: Juan Matthys Uys Date: Sat, 19 Mar 2016 12:17:34 +0000 Subject: [PATCH 1/4] correlation-id plugin, fixes #1086 --- kong-0.7.0-0.rockspec | 3 + kong/constants.lua | 2 +- kong/plugins/correlation-id/handler.lua | 56 ++++++++++++++ kong/plugins/correlation-id/schema.lua | 13 ++++ spec/plugins/correlation-id/access_spec.lua | 82 +++++++++++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 kong/plugins/correlation-id/handler.lua create mode 100644 kong/plugins/correlation-id/schema.lua create mode 100644 spec/plugins/correlation-id/access_spec.lua diff --git a/kong-0.7.0-0.rockspec b/kong-0.7.0-0.rockspec index 1cc3c6ca9aed..cc64bfe6a946 100644 --- a/kong-0.7.0-0.rockspec +++ b/kong-0.7.0-0.rockspec @@ -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", diff --git a/kong/constants.lua b/kong/constants.lua index 4ff64180bd38..fbb2ff6d3712 100644 --- a/kong/constants.lua +++ b/kong/constants.lua @@ -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", diff --git a/kong/plugins/correlation-id/handler.lua b/kong/plugins/correlation-id/handler.lua new file mode 100644 index 000000000000..4ff072bc7907 --- /dev/null +++ b/kong/plugins/correlation-id/handler.lua @@ -0,0 +1,56 @@ +-- 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 + + -- For later use, to echo it back downstream + ngx.ctx.correlationid_header_value = header_value +end + +function CorrelationIdHandler:header_filter(conf) + CorrelationIdHandler.super.header_filter(self) + ngx.header[conf.header_name] = ngx.ctx.correlationid_header_value +end + +return CorrelationIdHandler diff --git a/kong/plugins/correlation-id/schema.lua b/kong/plugins/correlation-id/schema.lua new file mode 100644 index 000000000000..838cd78cbb35 --- /dev/null +++ b/kong/plugins/correlation-id/schema.lua @@ -0,0 +1,13 @@ +return { + fields = { + header_name = { + type = "string", + default = "Kong-Request-ID" + }, + generator = { + type = "string", + default = "uuid#counter", + enum = {"uuid", "uuid#counter"} + } + } +} diff --git a/spec/plugins/correlation-id/access_spec.lua b/spec/plugins/correlation-id/access_spec.lua new file mode 100644 index 000000000000..21061659a844 --- /dev/null +++ b/spec/plugins/correlation-id/access_spec.lua @@ -0,0 +1,82 @@ +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"} + }, + plugin = { + {name = "correlation-id", config = {}, __api = 1}, + {name = "correlation-id", config = {header_name = "Foo-Bar-Id"}, __api = 2}, + {name = "correlation-id", config = {generator = "uuid"}, __api = 3} + } + } + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + local function test_with(host, header, pattern) + local response1, status1 = http_client.get(STUB_GET_URL, nil, {host = host}) + assert.equal(200, status1) + local correlation_id1 = json.decode(response1).headers[header:lower()] + assert.truthy(correlation_id1:match(pattern)) + + local response2, status2 = http_client.get(STUB_GET_URL, nil, {host = host}) + assert.equal(200, status2) + local correlation_id2 = json.decode(response2).headers[header:lower()] + assert.truthy(correlation_id2:match(pattern)) + + assert.are_not_equals(correlation_id1, correlation_id2) + + -- TODO kong_TEST.yml's worker_processes has to be 1 for the below to work. + --[[ + 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) +end) From e90ee34b0f89961803c49d8eda8d10e4eab46b3a Mon Sep 17 00:00:00 2001 From: "Juan M. Uys" Date: Tue, 22 Mar 2016 19:58:27 +0000 Subject: [PATCH 2/4] use more idiomatic spec matcher --- spec/plugins/correlation-id/access_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/plugins/correlation-id/access_spec.lua b/spec/plugins/correlation-id/access_spec.lua index 21061659a844..943012cf4a74 100644 --- a/spec/plugins/correlation-id/access_spec.lua +++ b/spec/plugins/correlation-id/access_spec.lua @@ -34,12 +34,12 @@ describe("Correlation ID Plugin", function() local response1, status1 = http_client.get(STUB_GET_URL, nil, {host = host}) assert.equal(200, status1) local correlation_id1 = json.decode(response1).headers[header:lower()] - assert.truthy(correlation_id1:match(pattern)) + assert.match(correlation_id1, pattern) local response2, status2 = http_client.get(STUB_GET_URL, nil, {host = host}) assert.equal(200, status2) local correlation_id2 = json.decode(response2).headers[header:lower()] - assert.truthy(correlation_id2:match(pattern)) + assert.match(correlation_id2, pattern) assert.are_not_equals(correlation_id1, correlation_id2) From 4e12036b71b006122753ec7e16f3ec17ca27a206 Mon Sep 17 00:00:00 2001 From: "Juan M. Uys" Date: Wed, 23 Mar 2016 07:12:51 +0000 Subject: [PATCH 3/4] make 'echo downstream' configurable and false default - also revert busted:match, since it's not for regex --- kong/plugins/correlation-id/handler.lua | 10 ++++--- kong/plugins/correlation-id/schema.lua | 4 +++ spec/plugins/correlation-id/access_spec.lua | 29 ++++++++++++++------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/kong/plugins/correlation-id/handler.lua b/kong/plugins/correlation-id/handler.lua index 4ff072bc7907..e4ac667ae893 100644 --- a/kong/plugins/correlation-id/handler.lua +++ b/kong/plugins/correlation-id/handler.lua @@ -44,13 +44,17 @@ function CorrelationIdHandler:access(conf) req_set_header(conf.header_name, header_value) end - -- For later use, to echo it back downstream - ngx.ctx.correlationid_header_value = header_value + 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) - ngx.header[conf.header_name] = ngx.ctx.correlationid_header_value + if conf.echo_downstream then + ngx.header[conf.header_name] = ngx.ctx.correlationid_header_value + end end return CorrelationIdHandler diff --git a/kong/plugins/correlation-id/schema.lua b/kong/plugins/correlation-id/schema.lua index 838cd78cbb35..6f9782f6a753 100644 --- a/kong/plugins/correlation-id/schema.lua +++ b/kong/plugins/correlation-id/schema.lua @@ -8,6 +8,10 @@ return { type = "string", default = "uuid#counter", enum = {"uuid", "uuid#counter"} + }, + echo_downstream = { + type = "boolean", + default = false } } } diff --git a/spec/plugins/correlation-id/access_spec.lua b/spec/plugins/correlation-id/access_spec.lua index 943012cf4a74..99e411ce4a08 100644 --- a/spec/plugins/correlation-id/access_spec.lua +++ b/spec/plugins/correlation-id/access_spec.lua @@ -15,12 +15,14 @@ describe("Correlation ID Plugin", function() 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 = "correlation3.com", upstream_url = "http://mockbin.com"}, + {request_host = "correlation4.com", upstream_url = "http://mockbin.com"} }, plugin = { - {name = "correlation-id", config = {}, __api = 1}, - {name = "correlation-id", config = {header_name = "Foo-Bar-Id"}, __api = 2}, - {name = "correlation-id", config = {generator = "uuid"}, __api = 3} + {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() @@ -31,15 +33,15 @@ describe("Correlation ID Plugin", function() end) local function test_with(host, header, pattern) - local response1, status1 = http_client.get(STUB_GET_URL, nil, {host = host}) + local _, status1, headers1 = http_client.get(STUB_GET_URL, nil, {host = host}) assert.equal(200, status1) - local correlation_id1 = json.decode(response1).headers[header:lower()] - assert.match(correlation_id1, pattern) + local correlation_id1 = headers1[header:lower()] + assert.truthy(correlation_id1:match(pattern)) - local response2, status2 = http_client.get(STUB_GET_URL, nil, {host = host}) + local _, status2, headers2 = http_client.get(STUB_GET_URL, nil, {host = host}) assert.equal(200, status2) - local correlation_id2 = json.decode(response2).headers[header:lower()] - assert.match(correlation_id2, pattern) + local correlation_id2 = headers2[header:lower()] + assert.truthy(correlation_id2:match(pattern)) assert.are_not_equals(correlation_id1, correlation_id2) @@ -79,4 +81,11 @@ describe("Correlation ID Plugin", function() 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) From 52756e9cceed4a7aec813fb97d2d5ab679d31483 Mon Sep 17 00:00:00 2001 From: Juan Matthys Uys Date: Wed, 23 Mar 2016 16:31:49 +0000 Subject: [PATCH 4/4] use luassert regex matcher --- spec/plugins/correlation-id/access_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/plugins/correlation-id/access_spec.lua b/spec/plugins/correlation-id/access_spec.lua index 99e411ce4a08..906e572d9f01 100644 --- a/spec/plugins/correlation-id/access_spec.lua +++ b/spec/plugins/correlation-id/access_spec.lua @@ -36,12 +36,12 @@ describe("Correlation ID Plugin", function() local _, status1, headers1 = http_client.get(STUB_GET_URL, nil, {host = host}) assert.equal(200, status1) local correlation_id1 = headers1[header:lower()] - assert.truthy(correlation_id1:match(pattern)) + 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.truthy(correlation_id2:match(pattern)) + assert.are.matches(pattern, correlation_id2) assert.are_not_equals(correlation_id1, correlation_id2)