diff --git a/lib/resty/healthcheck.lua b/lib/resty/healthcheck.lua index 4b4e8633..555589a1 100644 --- a/lib/resty/healthcheck.lua +++ b/lib/resty/healthcheck.lua @@ -890,12 +890,14 @@ end -- Runs a single healthcheck probe function checker:run_single_check(ip, port, hostname, hostheader) if self.checks.active.type == "xrpc" then - local ok, status = self.checks.active.xrpc_handler({ + local handler = self.checks.active.xrpc_handler + local node = { host = ip, port = port, domain = hostname, - }, self.xrpc_conf) - + } + local conf = self.checks.active.xrpc_conf + local ok, status = handler(node, conf) if not ok then return self:report_failure(ip, port, hostname, "active") end @@ -1294,6 +1296,8 @@ local function fill_in_settings(opts, defaults, ctx) if type(v) == "table" then if default[1] then -- do not recurse on arrays obj[k] = v + elseif default == NO_DEFAULT then + obj[k] = deepcopy(v) else ctx[#ctx + 1] = k obj[k] = fill_in_settings(v, default, ctx) @@ -1326,8 +1330,9 @@ local defaults = { concurrency = 10, http_path = "/", https_verify_certificate = true, + xrpc_function_module = NO_DEFAULT, xrpc_handler = NO_DEFAULT, - xrpc_conf = {}, + xrpc_conf = NO_DEFAULT, healthy = { interval = 0, -- 0 = disabled by default http_statuses = { 200, 302 }, @@ -1455,6 +1460,14 @@ function _M.new(opts) end if self.checks.active.type == "xrpc" then + local module = self.checks.active.xrpc_function_module + if module then + local ok, handler = pcall(require, module) + if not ok then + return nil, "failed to load xrpc handler module: " .. handler + end + self.checks.active.xrpc_handler = handler + end assert(self.checks.active.xrpc_handler, "required option 'checks.active.xrpc_handler' is missing") assert(self.checks.active.unhealthy.failures < 255, "checks.active.unhealthy.tcp_failures must be at most 254") end diff --git a/t/report_xrpc_status.t b/t/report_xrpc_status.t index e3df50df..bac14d82 100644 --- a/t/report_xrpc_status.t +++ b/t/report_xrpc_status.t @@ -6,7 +6,7 @@ workers(1); my $pwd = cwd(); our $HttpConfig = qq{ - lua_package_path "$pwd/lib/?.lua;;"; + lua_package_path "$pwd/lib/?.lua;$pwd/t/?.lua;;"; lua_shared_dict test_shm 8m; lua_shared_dict my_worker_events 8m; }; @@ -499,3 +499,146 @@ event: target status '(127.0.0.1:2119)' from 'true' to 'false' healthy SUCCESS increment (1/2) healthy SUCCESS increment (2/2) event: target status '(127.0.0.1:2119)' from 'false' to 'true' + + + +=== TEST 8: start xrpc healthcheck with xrpc_conf +--- http_config eval +qq{ + $::HttpConfig + + server { + listen 2119; + location = /status { + return 200; + } + } +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + type = "xrpc", + checks = { + active = { + healthy = { + interval = 0.5, -- we don't want active checks + successes = 2, + }, + unhealthy = { + interval = 0.5, -- we don't want active checks + failures = 2, + }, + xrpc_conf = { + timeout = 1000, + }, + xrpc_handler = function(node, conf) + ngx.log(ngx.INFO, "node.host: ", node.host, ", node.port: ", node.port) + ngx.log(ngx.INFO, "conf.timeout: ", conf.timeout) + end + }, + }, + }) + ngx.sleep(0.1) -- wait for initial timers to run once + checker:add_target("127.0.0.1", 2119, nil, true) + ngx.sleep(1) + } + } +--- request +GET /t +--- timeout: 10 +--- error_log +node.host: 127.0.0.1, node.port: 2119 +conf.timeout: 1000 + + + +=== TEST 9: start xrpc healthcheck with xrpc_function_module +--- http_config eval +qq{ + $::HttpConfig + + lua_shared_dict request_counters 1m; + + server { + listen 2119; + + location /status { + content_by_lua_block { + local uri = ngx.var.request_uri + local counter = ngx.shared.request_counters + + counter:incr(uri, 1, 0) + + local current_count = counter:get(uri) or 0 + + if current_count < 4 or current_count > 8 then + ngx.status = 200 + ngx.say("OK") + else + ngx.status = 500 + ngx.say("ERROR") + end + } + } + } +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker, err = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + type = "xrpc", + checks = { + active = { + healthy = { + interval = 0.5, -- we don't want active checks + successes = 2, + }, + unhealthy = { + interval = 0.5, -- we don't want active checks + failures = 2, + }, + xrpc_function_module = "util.http_handler", + }, + }, + }) + if not checker then + ngx.log(ngx.ERR, "failed to create healthcheck: ", err) + return + end + ngx.sleep(0.1) -- wait for initial timers to run once + checker:add_target("127.0.0.1", 2119, nil, true) + ngx.sleep(0.5) + ngx.say(checker:get_target_status("127.0.0.1", 2119, nil)) -- true + ngx.sleep(3.1) + ngx.say(checker:get_target_status("127.0.0.1", 2119, nil)) -- false + ngx.sleep(4.2) + ngx.say(checker:get_target_status("127.0.0.1", 2119, nil)) -- true + } + } +--- request +GET /t +--- timeout: 10 +--- response_body +true +false +true +--- error_log +checking healthy targets: nothing to do +checking unhealthy targets: nothing to do +unhealthy XRPC increment (1/2) for '(127.0.0.1:2119)' +unhealthy XRPC increment (2/2) for '(127.0.0.1:2119)' +event: target status '(127.0.0.1:2119)' from 'true' to 'false' +healthy SUCCESS increment (1/2) +healthy SUCCESS increment (2/2) +event: target status '(127.0.0.1:2119)' from 'false' to 'true' diff --git a/t/util/http_handler.lua b/t/util/http_handler.lua new file mode 100644 index 00000000..39d89214 --- /dev/null +++ b/t/util/http_handler.lua @@ -0,0 +1,18 @@ +local http = require('resty.http') +local json = require('cjson') + +return function (node, conf) + ngx.log(ngx.INFO, "http_handler node: ", json.encode(node), " conf: ", json.encode(conf)) + conf = conf or {} + local httpc = http.new() + local uri = "http://" .. node.host .. ":" .. node.port + local res, err = httpc:request_uri(uri, { + method = conf.method or "GET", + path = conf.path or "/status", + }) + if not res then + ngx.log(ngx.ERR, "failed to request: ", err) + return false + end + return true, res.status +end \ No newline at end of file