From 4f8c99dfd87e056554ab1090e86b447d12361257 Mon Sep 17 00:00:00 2001 From: Shane Utt Date: Thu, 10 Feb 2022 14:26:33 -0500 Subject: [PATCH] feat: add configuration_hash in /status in dbless (#8214) * feat: add configuration_hash in dbless /status * Update spec/02-integration/08-status_api/01-core_routes_spec.lua Co-authored-by: Harry * Update spec/02-integration/08-status_api/01-core_routes_spec.lua Co-authored-by: Harry * Update spec/02-integration/08-status_api/01-core_routes_spec.lua Co-authored-by: Harry * test(kong_routes) some syntax fixes Co-authored-by: Harry Co-authored-by: Vinicius Mignot --- kong/api/routes/health.lua | 12 ++++++ kong/db/declarative/init.lua | 11 ++++- .../04-admin_api/02-kong_routes_spec.lua | 42 +++++++++++++++++++ .../08-status_api/01-core_routes_spec.lua | 42 ++++++++++++++++++- 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/kong/api/routes/health.lua b/kong/api/routes/health.lua index 486c3f2e25d6..ff9d22d273d7 100644 --- a/kong/api/routes/health.lua +++ b/kong/api/routes/health.lua @@ -1,4 +1,5 @@ local utils = require "kong.tools.utils" +local declarative = require "kong.db.declarative" local find = string.find local select = select @@ -11,6 +12,8 @@ local knode = (kong and kong.node) and kong.node or local select = select local tonumber = tonumber local kong = kong +local dbless = kong.configuration.database == "off" +local data_plane_role = kong.configuration.role == "data_plane" return { @@ -64,6 +67,15 @@ return { }, } + -- if dbless mode is enabled we provide the current hash of the + -- data-plane in the status response as this enables control planes + -- to make decisions when something changes in the data-plane (e.g. + -- if the gateway gets unexpectedly restarted and its configuration + -- has been reset to empty). + if dbless or data_plane_role then + status_response.configuration_hash = declarative.get_current_hash() + end + -- TODO: no way to bypass connection pool local ok, err = kong.db:connect() if not ok then diff --git a/kong/db/declarative/init.lua b/kong/db/declarative/init.lua index 455c5fae0d67..dcf3d06ef50c 100644 --- a/kong/db/declarative/init.lua +++ b/kong/db/declarative/init.lua @@ -44,6 +44,8 @@ local DECLARATIVE_LOCK_KEY = "declarative:lock" local DECLARATIVE_LOCK_TTL = 60 local GLOBAL_QUERY_OPTS = { nulls = true, workspace = null } +local EMPTY_CONFIGURATION_HASH = string.rep("0", 32) + local declarative = {} @@ -845,7 +847,14 @@ function declarative.load_into_cache(entities, meta, hash, shadow) return nil, err end - local ok, err = ngx.shared.kong:safe_set(DECLARATIVE_HASH_KEY, hash or true) + -- mask any default hash to indicate no hash is available. + if hash == EMPTY_CONFIGURATION_HASH or hash == "" then + hash = null + end + + -- set the value of the configuration hash. The value can be nil, which + -- indicates that no configuration has been applied yet to the Gateway. + local ok, err = ngx.shared.kong:safe_set(DECLARATIVE_HASH_KEY, hash) if not ok then return nil, "failed to set " .. DECLARATIVE_HASH_KEY .. " in shm: " .. err end diff --git a/spec/02-integration/04-admin_api/02-kong_routes_spec.lua b/spec/02-integration/04-admin_api/02-kong_routes_spec.lua index e66159b99a93..a7db6e2fd077 100644 --- a/spec/02-integration/04-admin_api/02-kong_routes_spec.lua +++ b/spec/02-integration/04-admin_api/02-kong_routes_spec.lua @@ -16,6 +16,7 @@ describe("Admin API - Kong routes with strategy #" .. strategy, function() lazy_setup(function() helpers.get_db_utils(nil, {}) -- runs migrations assert(helpers.start_kong { + database = strategy, plugins = "bundled,reports-api", pg_password = "hide_me" }) @@ -206,6 +207,47 @@ describe("Admin API - Kong routes with strategy #" .. strategy, function() assert.is_number(json.server.connections_writing) assert.is_number(json.server.connections_waiting) assert.is_number(json.server.total_requests) + assert.is_nil(json.server.configuration_hash) -- not present in DB mode, or in DBLESS mode until configuration is applied + end) + + it("returns status info including a configuration_hash in DBLESS mode if an initial configuration has been provided #off", function() + -- push an initial configuration so that a configuration_hash will be present + local postres = assert(client:send { + method = "POST", + path = "/config", + body = { + config = [[ + _format_version: "1.1" + services: + - host: "konghq.com" + ]], + }, + headers = { + ["Content-Type"] = "application/json" + } + }) + assert.res_status(201, postres) + + -- verify the status endpoint now includes a value (other than the default) for the configuration_hash + local res = assert(client:send { + method = "GET", + path = "/status" + }) + local body = assert.res_status(200, res) + local json = cjson.decode(body) + assert.is_table(json.database) + assert.is_table(json.server) + assert.is_boolean(json.database.reachable) + assert.is_number(json.server.connections_accepted) + assert.is_number(json.server.connections_active) + assert.is_number(json.server.connections_handled) + assert.is_number(json.server.connections_reading) + assert.is_number(json.server.connections_writing) + assert.is_number(json.server.connections_waiting) + assert.is_number(json.server.total_requests) + assert.is_string(json.configuration_hash) + assert.equal(32, #json.configuration_hash) + end) it("database.reachable is `true` when DB connection is healthy", function() diff --git a/spec/02-integration/08-status_api/01-core_routes_spec.lua b/spec/02-integration/08-status_api/01-core_routes_spec.lua index d313dfc0a2d3..cd28b4f7362c 100644 --- a/spec/02-integration/08-status_api/01-core_routes_spec.lua +++ b/spec/02-integration/08-status_api/01-core_routes_spec.lua @@ -21,7 +21,7 @@ describe("Status API - with strategy #" .. strategy, function() end) describe("core", function() - it("/status returns status info", function() + it("/status returns status info without configuration_hash", function() local res = assert(client:send { method = "GET", path = "/status" @@ -40,7 +40,47 @@ describe("Status API - with strategy #" .. strategy, function() assert.is_number(json.server.connections_writing) assert.is_number(json.server.connections_waiting) assert.is_number(json.server.total_requests) + assert.is_nil(json.server.configuration_hash) -- no hash in DB mode, or in DBLESS mode until configuration has been applied end) + + it("/status starts providing a config_hash once an initial configuration has been pushed in dbless mode #off", function() + -- push an initial configuration so that a configuration_hash will be present + local postres = assert(client:send { + method = "POST", + path = "/config", + body = { + config = [[ + _format_version: "1.1" + services: + - host = "konghq.com" + ]], + }, + headers = { + ["Content-Type"] = "application/json" + } + }) + assert.res_status(201, postres) + + local res = assert(client:send { + method = "GET", + path = "/status" + }) + local body = assert.res_status(200, res) + local json = cjson.decode(body) + assert.is_table(json.database) + assert.is_table(json.server) + assert.is_boolean(json.database.reachable) + assert.is_number(json.server.connections_accepted) + assert.is_number(json.server.connections_active) + assert.is_number(json.server.connections_handled) + assert.is_number(json.server.connections_reading) + assert.is_number(json.server.connections_writing) + assert.is_number(json.server.connections_waiting) + assert.is_number(json.server.total_requests) + assert.is_string(json.server.configuration_hash) + assert.equal(32, #json.server.configuration_hash) + end) + end) describe("plugins", function()