# HG changeset patch # User Matthew Wild # Date 1467149593 -3600 # Node ID 5fcf9d5582502c843ae09785980e10530a0bd864 # Parent 0ca0fdad3b2cd0284b8705526ff4e659d26295ba Three new modules: mod_host_status_check, mod_host_status_heartbeat and mod_http_host_status_check diff -r 0ca0fdad3b2c -r 5fcf9d558250 mod_host_status_check/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_host_status_check/README.markdown Tue Jun 28 22:33:13 2016 +0100 @@ -0,0 +1,33 @@ +--- +labels: Stage-Beta +description: Host status check +... + +Introduction +============ + +This module allows you to monitor the state of hosts and components in your Prosody server. For example, +it will track whether components are connected and (if the component supports it) listen for heartbeats +sent by the component to indicate that it is functioning. + +This module does not expose any interface to access the status information itself. See mod\_http\_host\_status\_check +for a module that allows you to access host status information over HTTP(S). + +Configuration +============= + +There are no configuration options for this module. + +You should enable it on every host that you want to monitor, by adding it to modules\_enabled. Note +that to monitor components, adding to the global modules\_enabled list will not suffice - you will +need to add it to the component's own modules\_enabled, like this: + +``` {.lua} +Component "mycomponent.example.com" + modules_enabled = { "host_status_check" } +``` + +Compatibility +============= + +Works with Prosody 0.9.x and later. diff -r 0ca0fdad3b2c -r 5fcf9d558250 mod_host_status_check/mod_host_status_check.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_host_status_check/mod_host_status_check.lua Tue Jun 28 22:33:13 2016 +0100 @@ -0,0 +1,28 @@ +local time = require "socket".gettime; + +local heartbeats = module:shared("/*/host_status_check/heartbeats"); +local connection_events = module:shared("/*/host_status_check/connection_events"); + +if prosody.hosts[module.host].type == "component" and module:get_option_string("component_module") == "component" then + module:hook("component-authenticated", function () + connection_events[module.host] = { connected = true; timestamp = time() }; + end); + + -- Note: this event is not in 0.9, and requires a recent 0.10 or trunk build + module:hook("component-disconnected", function () + connection_events[module.host] = { connected = false; timestamp = time() }; + end); + + module:hook("stanza/xmpp:prosody.im/heartbeat:heartbeat", function () + heartbeats[module.host] = time(); + return true; + end); +else + connection_events[module.host] = { connected = true, timestamp = time() }; + module:log("debug", "BLAH") +end + +function module.unload() + connection_events[module.host] = { connected = false, timestamp = time() }; + heartbeats[module.host] = nil; +end diff -r 0ca0fdad3b2c -r 5fcf9d558250 mod_host_status_heartbeat/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_host_status_heartbeat/README.markdown Tue Jun 28 22:33:13 2016 +0100 @@ -0,0 +1,36 @@ +--- +labels: Stage-Beta +description: Host status heartbeat +... + +Introduction +============ + +This module integrates with mod\_host\_status\_check to provide heartbeats at regular intervals. + +The only time you will generally want this, is if you are using mod\_component\_client to run Prosody as +an external component of another Prosody server that has mod\_host\_status\_check loaded and waiting for +heartbeats. + +Alternatively you can run this on the same Prosody host as mod\_http\_status\_check and it will simply +update a variable periodically to indicate that Prosody and timers are functional. + +Configuration +============= + +The following configuration options are supported: + +```{.lua} +-- The number of seconds to wait between sending heartbeats +status_check_heartbeat_interval = 5 + +-- Set this to "remote" (the default) if you are using mod_component_client +-- and you want to send a heartbeat to a remote server. Otherwise +-- set it to "local" to send to mod_host_status_check on the same server. +status_check_heartbeat_mode = "remote" +``` + +Compatibility +============= + +Works with Prosody 0.9.x and later. diff -r 0ca0fdad3b2c -r 5fcf9d558250 mod_host_status_heartbeat/mod_host_status_heartbeat.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_host_status_heartbeat/mod_host_status_heartbeat.lua Tue Jun 28 22:33:13 2016 +0100 @@ -0,0 +1,28 @@ +local st = require "util.stanza"; +local time = require "socket".gettime; + +local heartbeat_interval = module:get_option_number("status_check_heartbeat_interval", 5); +local heartbeat_mode = module:get_option_string("status_check_heartbeat_mode", "remote"); + +local local_heartbeats = module:shared("/*/host_status_check/heartbeats"); + +local heartbeat_methods = { + ["local"] = function() + module:log("debug", "Local heartbeat"); + local_heartbeats[module.host] = time(); + return heartbeat_interval; + end; + + ["remote"] = function () + module:fire_event("route/remote", { + origin = prosody.hosts[module.host]; + stanza = st.stanza("heartbeat", { xmlns = "xmpp:prosody.im/heartbeat" }); + }); + return heartbeat_interval; + end; +} + +local send_heartbeat = assert(heartbeat_methods[heartbeat_mode], "Unknown heartbeat_mode: "..heartbeat_mode); + +--FIXME: Commented for testing!!! :) +module:add_timer(0, send_heartbeat); diff -r 0ca0fdad3b2c -r 5fcf9d558250 mod_http_host_status_check/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_host_status_check/README.markdown Tue Jun 28 22:33:13 2016 +0100 @@ -0,0 +1,33 @@ +--- +labels: Stage-Beta +description: HTTP Host Status Check +... + +Introduction +============ + +This module exposes serves over HTTP the information collected by mod\_host\_status\_check and +mod\_host\_status\_heartbeat in a convenient format for automated monitoring tools. + +Configuration +============= + +mod\_http\_status\_check relies on Prosodys HTTP server and mod\_http for +serving HTTP requests. See [Prosodys HTTP server +documentation](https://prosody.im/doc/http) for information about how to +configure ports, HTTP Host names etc. + +Simply add this module to modules\_enabled for the host you would like to serve it from. + +There is a single configuration option: + +``` {.lua} + -- The maximum number of seconds that a host can go without sending a heartbeat, + -- before we mark it as TIMEOUT (default: 5) + status_check_heartbeat_threshold = 5; +``` + +Compatibility +============= + +Works with Prosody 0.9.x and later. diff -r 0ca0fdad3b2c -r 5fcf9d558250 mod_http_host_status_check/mod_http_host_status_check.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_host_status_check/mod_http_host_status_check.lua Tue Jun 28 22:33:13 2016 +0100 @@ -0,0 +1,101 @@ +local heartbeats = module:shared("/*/host_status_check/heartbeats"); +local events = module:shared("/*/host_status_check/connection_events"); + +local time = require "socket".gettime; +local template = require "util.interpolation".new("%b{}", function (s) return s end) +local st = require "util.stanza"; + +module:depends "http" + +local threshold = module:get_option_number("status_check_heartbeat_threshold", 5); + +local function status_string(status, duration, comment) + local string_timestamp = ""; + if duration then + string_timestamp = ("(%0.2fs%s)"):format(duration, comment or ""); + elseif comment then + string_timestamp = ("(%s)"):format(comment); + else + return status and "UP" or "DOWN"; + end + return (status and "UP " or "DOWN ")..string_timestamp; +end + +local function string_pad(s, len) + return s..(" "):rep(len-#s); +end + +local status_page_template = [[ +STATUS {status} +{host_statuses%HOST {item} {idx} +}]]; + +function status_page() + local host_statuses = {}; + local current_time = time(); + + local all_ok = true; + + for host in pairs(hosts) do + local last_heartbeat_time = heartbeats[host]; + + local ok, status_text = true, "OK"; + + local is_component = hosts[host].type == "component" and hosts[host].modules.component; + + if is_component then + local current_status = hosts[host].modules.component.connected; + if events[host] then + local tracked_status = events[host].connected; + if tracked_status == current_status then + status_text = status_string(current_status, time() - events[host].timestamp); + else + status_text = status_string(current_status, nil, "!"); + end + else + status_text = status_string(current_status, nil, "?"); + end + if not current_status then + ok = false; + end + else + local event_info = events[host]; + local connected = true; + if event_info then + connected = event_info.connected; + end + status_text = status_string(connected, event_info and (time() - events[host].timestamp), not event_info and "?"); + end + + if last_heartbeat_time then + local time_since_heartbeat = current_time - last_heartbeat_time; + if ok then + if time_since_heartbeat > threshold then + status_text = ("TIMEOUT (%0.2fs)"):format(time_since_heartbeat); + ok = false; + else + status_text = status_text:gsub("^%S+", "GOOD"); + end + end + end + + if not ok then + all_ok = false; + end + + if not ok or is_component or last_heartbeat_time then + host_statuses[host] = string_pad(status_text, 20); + end + end + local page = template(status_page_template, { + status = all_ok and "OK" or "FAIL"; + host_statuses = host_statuses; + }); + return page; +end + +module:provides("http", { + route = { + GET = status_page; + }; +})