diff --git a/kong/plugins/circuit-breaker/handler.lua b/kong/plugins/circuit-breaker/handler.lua index 2804c7c..a79820c 100644 --- a/kong/plugins/circuit-breaker/handler.lua +++ b/kong/plugins/circuit-breaker/handler.lua @@ -6,9 +6,15 @@ local kong = kong local ngx = ngx local CircuitBreakerHandler = {} + CircuitBreakerHandler.VERSION = "1.0.0" CircuitBreakerHandler.PRIORITY = tonumber(os.getenv("PRIORITY_CIRCUIT_BREAKER")) or 930 -kong.log.info("Plugin priority set to " .. CircuitBreakerHandler.PRIORITY .. (os.getenv("PRIORITY_CIRCUIT_BREAKER") and " from env" or " by default")) + +kong.log.info( + "Plugin priority set to " + .. CircuitBreakerHandler.PRIORITY + .. (os.getenv("PRIORITY_CIRCUIT_BREAKER") and " from env" or " by default") +) local DEFAULT_VERSION = 1 @@ -16,127 +22,127 @@ local DEFAULT_VERSION = 1 local circuit_breakers = circuit_breaker_lib:new() local function is_successful(upstream_status_code) - return upstream_status_code and upstream_status_code < 500 + return upstream_status_code and upstream_status_code < 500 end -- Return circuit breaker instance for this API local function get_circuit_breaker(conf, api_identifier) - local cb_table_key = "global" - if conf.route_id ~= nil then - cb_table_key = conf.route_id - elseif conf.service_id ~= nil then - cb_table_key = conf.service_id - end - - return circuit_breakers:get_circuit_breaker(api_identifier, cb_table_key, conf) + local cb_table_key = "global" + if conf.route_id ~= nil then + cb_table_key = conf.route_id + elseif conf.service_id ~= nil then + cb_table_key = conf.service_id + end + + return circuit_breakers:get_circuit_breaker(api_identifier, cb_table_key, conf) end local function p_access(conf) - local excluded_apis = helpers.get_excluded_apis(conf) - local api_identifier = helpers.get_api_identifier() + local excluded_apis = helpers.get_excluded_apis(conf) + local api_identifier = helpers.get_api_identifier() - if excluded_apis[api_identifier] then - return - end + if excluded_apis[api_identifier] then + return + end - -- Set timeout for request after which it will be treated as a failure - ngx.ctx.balancer_data.read_timeout = conf["api_call_timeout_ms"] + -- Set timeout for request after which it will be treated as a failure + ngx.ctx.balancer_data.read_timeout = conf["api_call_timeout_ms"] - -- Start before proxy logic over here - conf.version = DEFAULT_VERSION - local cb = get_circuit_breaker(conf, api_identifier) + -- Start before proxy logic over here + conf.version = DEFAULT_VERSION + local cb = get_circuit_breaker(conf, api_identifier) - local _, err_cb = cb:_before() - if err_cb then - local headers = {["Content-Type"] = conf.response_header_override or "text/plain"} - return kong.response.exit(conf.error_status_code, conf.error_msg_override or err_cb, headers) - end + local _, err_cb = cb:_before() + if err_cb then + local headers = {["Content-Type"] = conf.response_header_override or "text/plain"} + return kong.response.exit(conf.error_status_code, conf.error_msg_override or err_cb, headers) + end - kong.ctx.plugin.cb = cb - kong.ctx.plugin.generation = cb._generation + kong.ctx.plugin.cb = cb + kong.ctx.plugin.generation = cb._generation end -- Run pre proxy checks function CircuitBreakerHandler:access(conf) - local success, err = pcall(p_access, conf) - if not success then - kong.log.err("Error in cb access phase " .. err) - return - end + local success, err = pcall(p_access, conf) + if not success then + kong.log.err("Error in cb access phase " .. err) + return + end end local function p_header_filter(conf) - if kong.response.get_status() and kong.response.get_status() ~= conf.error_status_code then - local cb = kong.ctx.plugin.cb - - if cb == nil then - return - end - local ok = is_successful(kong.response.get_status()) - cb:_after(kong.ctx.plugin.generation, ok) - end + if kong.response.get_status() and kong.response.get_status() ~= conf.error_status_code then + local cb = kong.ctx.plugin.cb + + if cb == nil then + return + end + local ok = is_successful(kong.response.get_status()) + cb:_after(kong.ctx.plugin.generation, ok) + end end -- Run post proxy checks function CircuitBreakerHandler:header_filter(conf) - local sucess, err = pcall(p_header_filter, conf) - if not sucess then - kong.log.err("Error in cb header_filter phase " .. err) - return - end + local sucess, err = pcall(p_header_filter, conf) + if not sucess then + kong.log.err("Error in cb header_filter phase " .. err) + return + end end function CircuitBreakerHandler:log(conf) - local api_identifier = helpers.get_api_identifier() - local cb = kong.ctx.plugin.cb - - if cb == nil then - return - end - if cb._last_state_notified == false then - cb._last_state_notified = true - -- Prepare latest state change and set it in context. - -- This data can be used later to do logging in a different plugin. - -- Example: Use this data to send events metrics to Datadog / New Relic. - if conf.set_logger_metrics_in_ctx == true then - helpers.set_logger_metrics(api_identifier, cb._state) - end - kong.log.notice("Circuit breaker state updated for route " .. api_identifier) - end + local api_identifier = helpers.get_api_identifier() + local cb = kong.ctx.plugin.cb + + if cb == nil then + return + end + if cb._last_state_notified == false then + cb._last_state_notified = true + -- Prepare latest state change and set it in context. + -- This data can be used later to do logging in a different plugin. + -- Example: Use this data to send events metrics to Datadog / New Relic. + if conf.set_logger_metrics_in_ctx == true then + helpers.set_logger_metrics(api_identifier, cb._state) + end + kong.log.notice("Circuit breaker state updated for route " .. api_identifier) + end end function CircuitBreakerHandler:init_worker() - kong.worker_events.register( - function (data) - if type(data) ~= "string" then - return - end - - local key_parts = pl_utils.split(data, ":") - if key_parts[1] ~= "plugins" or key_parts[2] ~= "circuit-breaker" then - return - end - - local service_id = key_parts[4] - local route_id = key_parts[3] - - if route_id ~= "" then - circuit_breakers:remove_breakers_by_group(route_id) -- Route level circuit breaker - elseif service_id ~= "" then - circuit_breakers:remove_breakers_by_group(service_id) -- Service level circuit breaker - else - circuit_breakers:remove_breakers_by_group("global") -- Global circuit breaker - end - - local cache_key = kong.db.plugins:cache_key("circuit_breaker_excluded_apis", service_id, route_id) - kong.core_cache:invalidate(cache_key, false) - end, - "mlcache", - "mlcache:invalidations:kong_core_db_cache" - ) + kong.worker_events.register( + function (data) + if type(data) ~= "string" then + return + end + + local key_parts = pl_utils.split(data, ":") + if key_parts[1] ~= "plugins" or key_parts[2] ~= "circuit-breaker" then + return + end + + local service_id = key_parts[4] + local route_id = key_parts[3] + + if route_id ~= "" then + circuit_breakers:remove_breakers_by_group(route_id) -- Route level circuit breaker + elseif service_id ~= "" then + circuit_breakers:remove_breakers_by_group(service_id) -- Service level circuit breaker + else + circuit_breakers:remove_breakers_by_group("global") -- Global circuit breaker + end + + local cache_key = kong.db.plugins:cache_key("circuit_breaker_excluded_apis", service_id, route_id) + kong.core_cache:invalidate(cache_key, false) + end, + "mlcache", + "mlcache:invalidations:kong_core_db_cache" + ) end return CircuitBreakerHandler diff --git a/kong/plugins/circuit-breaker/helpers.lua b/kong/plugins/circuit-breaker/helpers.lua index 4e5a611..8544391 100644 --- a/kong/plugins/circuit-breaker/helpers.lua +++ b/kong/plugins/circuit-breaker/helpers.lua @@ -3,30 +3,30 @@ local cjson = require "cjson" local kong = kong local function get_excluded_apis(conf) - local service_id = conf.service_id - local route_id = conf.route_id + local service_id = conf.service_id + local route_id = conf.route_id - local cache_key = kong.db.plugins:cache_key("circuit_breaker_excluded_apis", service_id, route_id) + local cache_key = kong.db.plugins:cache_key("circuit_breaker_excluded_apis", service_id, route_id) local excluded_apis, err = kong.core_cache:get(cache_key, nil, function(c) return cjson.decode(c["excluded_apis"]) end, conf) - if err then - error(err) - end - return excluded_apis + if err then + error(err) + end + return excluded_apis end local function get_api_identifier() - return kong.request.get_method() .. "_" .. kong.request.get_path() + return kong.request.get_method() .. "_" .. kong.request.get_path() end local function set_logger_metrics(api_identifier, new_state) - local upstream_host = kong.ctx.shared.upstream_host or '' - if kong.ctx.shared.logger_metrics == nil then - kong.ctx.shared.logger_metrics = {} - end - -- kong.ctx.shared object is specific to the lifecycle of a request and is used to share data between plugins + local upstream_host = kong.ctx.shared.upstream_host or '' + if kong.ctx.shared.logger_metrics == nil then + kong.ctx.shared.logger_metrics = {} + end + -- kong.ctx.shared object is specific to the lifecycle of a request and is used to share data between plugins table.insert(kong.ctx.shared.logger_metrics, { type = "circuit_breaker", tags = { diff --git a/kong/plugins/circuit-breaker/schema.lua b/kong/plugins/circuit-breaker/schema.lua index d9e77b2..8b03f1b 100644 --- a/kong/plugins/circuit-breaker/schema.lua +++ b/kong/plugins/circuit-breaker/schema.lua @@ -12,43 +12,119 @@ local function json_validator(config_string) end local function cb_schema_validator(conf) - -- Todo: Other checks can be added here - return json_validator(conf.excluded_apis) + return json_validator(conf.excluded_apis) end return { - name = "circuit-breaker", - fields = { - { - consumer = typedefs.no_consumer - }, - { - protocols = typedefs.protocols_http - }, - { - config = { - type = "record", - fields = { - {min_calls_in_window = {type = "number", gt = 1, required = true, default = 20}}, - {window_time = {type = "number", gt = 0, required = true, default = 10}}, - {api_call_timeout_ms = {type = "number", gt = 0, required = true, default = 2000}}, - {failure_percent_threshold = {type = "number", gt = 0, required = true, default = 51}}, - {wait_duration_in_open_state = {type = "number", gt = 0, required = true, default = 15}}, - {wait_duration_in_half_open_state = {type = "number", gt = 0, required = true, default = 120}}, - {half_open_max_calls_in_window = {type = "number", gt = 1, required = true, default = 10}}, - {half_open_min_calls_in_window = {type = "number", gt = 1, required = true, default = 5}}, - {error_status_code = {type = "number", required = true, default = 599}}, - {error_msg_override = {type = "string"}}, - {response_header_override = {type = "string"}}, - {excluded_apis = { - type = "string", - required = true, - default = "{\"GET_/kong-healthcheck\": true}", - }}, - {set_logger_metrics_in_ctx = {type = "boolean", default = true}}, - }, - custom_validator = cb_schema_validator - } - } - } + name = "circuit-breaker", + fields = { + { + consumer = typedefs.no_consumer, + }, + { + protocols = typedefs.protocols_http, + }, + { + config = { + type = "record", + fields = { + { + min_calls_in_window = { + type = "number", + gt = 1, + required = true, + default = 20, + }, + }, + { + window_time = { + type = "number", + gt = 0, + required = true, + default = 10, + }, + }, + { + api_call_timeout_ms = { + type = "number", + gt = 0, + required = true, + default = 2000, + }, + }, + { + failure_percent_threshold = { + type = "number", + gt = 0, + required = true, + default = 51, + }, + }, + { + wait_duration_in_open_state = { + type = "number", + gt = 0, + required = true, + default = 15, + }, + }, + { + wait_duration_in_half_open_state = { + type = "number", + gt = 0, + required = true, + default = 120, + }, + }, + { + half_open_max_calls_in_window = { + type = "number", + gt = 1, + required = true, + default = 10, + }, + }, + { + half_open_min_calls_in_window = { + type = "number", + gt = 1, + required = true, + default = 5, + }, + }, + { + error_status_code = { + type = "number", + required = true, + default = 599, + }, + }, + { + error_msg_override = { + type = "string", + }, + }, + { + response_header_override = { + type = "string", + }, + }, + { + excluded_apis = { + type = "string", + required = true, + default = "{\"GET_/kong-healthcheck\": true}", + }, + }, + { + set_logger_metrics_in_ctx = { + type = "boolean", + default = true, + }, + }, + }, + custom_validator = cb_schema_validator, + }, + }, + }, } diff --git a/spec/circuit-breaker/01-access_spec.lua b/spec/circuit-breaker/01-access_spec.lua index e478276..7ed487e 100644 --- a/spec/circuit-breaker/01-access_spec.lua +++ b/spec/circuit-breaker/01-access_spec.lua @@ -1,7 +1,7 @@ local helpers = require "spec.helpers" local fixtures = require "spec.circuit-breaker.fixtures" -strategies = {"postgres"} +local strategies = {"postgres"} for _, strategy in ipairs(strategies) do describe("circuit breaker plugin [#" .. strategy .. "]", function()