Changeset

2219:5fcf9d558250

Three new modules: mod_host_status_check, mod_host_status_heartbeat and mod_http_host_status_check
author Matthew Wild <mwild1@gmail.com>
date Tue, 28 Jun 2016 22:33:13 +0100
parents 2218:0ca0fdad3b2c
children 2220:7f955f92bbbb
files mod_host_status_check/README.markdown mod_host_status_check/mod_host_status_check.lua mod_host_status_heartbeat/README.markdown mod_host_status_heartbeat/mod_host_status_heartbeat.lua mod_http_host_status_check/README.markdown mod_http_host_status_check/mod_http_host_status_check.lua
diffstat 6 files changed, 259 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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.
--- /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
--- /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.
--- /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);
--- /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.
--- /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;
+	};
+})