| 1 |
-- Copyright (c) 2008, OmniTI Computer Consulting, Inc. |
|---|
| 2 |
-- All rights reserved. |
|---|
| 3 |
-- |
|---|
| 4 |
-- Redistribution and use in source and binary forms, with or without |
|---|
| 5 |
-- modification, are permitted provided that the following conditions are |
|---|
| 6 |
-- met: |
|---|
| 7 |
-- |
|---|
| 8 |
-- * Redistributions of source code must retain the above copyright |
|---|
| 9 |
-- notice, this list of conditions and the following disclaimer. |
|---|
| 10 |
-- * Redistributions in binary form must reproduce the above |
|---|
| 11 |
-- copyright notice, this list of conditions and the following |
|---|
| 12 |
-- disclaimer in the documentation and/or other materials provided |
|---|
| 13 |
-- with the distribution. |
|---|
| 14 |
-- * Neither the name OmniTI Computer Consulting, Inc. nor the names |
|---|
| 15 |
-- of its contributors may be used to endorse or promote products |
|---|
| 16 |
-- derived from this software without specific prior written |
|---|
| 17 |
-- permission. |
|---|
| 18 |
-- |
|---|
| 19 |
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|---|
| 20 |
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|---|
| 21 |
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|---|
| 22 |
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|---|
| 23 |
-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|---|
| 24 |
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|---|
| 25 |
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|---|
| 26 |
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|---|
| 27 |
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|---|
| 28 |
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|---|
| 29 |
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 30 |
|
|---|
| 31 |
module(..., package.seeall) |
|---|
| 32 |
|
|---|
| 33 |
function onload(image) |
|---|
| 34 |
image.xml_description([=[ |
|---|
| 35 |
<module> |
|---|
| 36 |
<name>resmon</name> |
|---|
| 37 |
<description><para>The resmon module performs services checks against an HTTP server serving with Resmon XML or JSON.</para> |
|---|
| 38 |
<para><ulink url="https://labs.omniti.com/trac/resmon"><citetitle>Resmon</citetitle></ulink> is a light-weight resource monitor that exposes health of services over HTTP in XML.</para> |
|---|
| 39 |
<para>This module rides on the http module and provides a secondary phase of XML parsing on the contents that extracts Resmon status messages into metrics that can be trended.</para> |
|---|
| 40 |
</description> |
|---|
| 41 |
<loader>lua</loader> |
|---|
| 42 |
<object>noit.module.resmon</object> |
|---|
| 43 |
<checkconfig> |
|---|
| 44 |
<parameter name="url" |
|---|
| 45 |
required="required" |
|---|
| 46 |
allowed=".+">The URL including schema and hostname (as you would type into a browser's location bar).</parameter> |
|---|
| 47 |
<parameter name="port" |
|---|
| 48 |
required="optional" |
|---|
| 49 |
allowed="\d+">The TCP port can be specified to overide the default of 81.</parameter> |
|---|
| 50 |
</checkconfig> |
|---|
| 51 |
<examples> |
|---|
| 52 |
<example> |
|---|
| 53 |
<title>Checking resmon services on OmniTI Labs.</title> |
|---|
| 54 |
<para>This example checks the Resmon service on OmniTI Labs.</para> |
|---|
| 55 |
<programlisting><![CDATA[ |
|---|
| 56 |
<noit> |
|---|
| 57 |
<modules> |
|---|
| 58 |
<loader image="lua" name="lua"> |
|---|
| 59 |
<config><directory>/opt/reconnoiter/libexec/modules-lua/?.lua</directory></config> |
|---|
| 60 |
</loader> |
|---|
| 61 |
<module loader="lua" name="resmon" object="noit.module.resmon"/> |
|---|
| 62 |
</modules> |
|---|
| 63 |
<checks> |
|---|
| 64 |
<labs target="8.8.38.5" module="resmon"> |
|---|
| 65 |
<check uuid="36b8ba72-7968-11dd-a67f-d39a2cc3f9de"> |
|---|
| 66 |
<config> |
|---|
| 67 |
<auth_user>foo</auth_user> |
|---|
| 68 |
<auth_password>bar</auth_password> |
|---|
| 69 |
</config> |
|---|
| 70 |
</check> |
|---|
| 71 |
</labs> |
|---|
| 72 |
</checks> |
|---|
| 73 |
</noit> |
|---|
| 74 |
]]></programlisting> |
|---|
| 75 |
</example> |
|---|
| 76 |
</examples> |
|---|
| 77 |
</module> |
|---|
| 78 |
]=]); |
|---|
| 79 |
return 0 |
|---|
| 80 |
end |
|---|
| 81 |
|
|---|
| 82 |
function init(module) |
|---|
| 83 |
return 0 |
|---|
| 84 |
end |
|---|
| 85 |
|
|---|
| 86 |
function config(module, options) |
|---|
| 87 |
return 0 |
|---|
| 88 |
end |
|---|
| 89 |
|
|---|
| 90 |
local HttpClient = require 'noit.HttpClient' |
|---|
| 91 |
|
|---|
| 92 |
function json_metric(check, prefix, o) |
|---|
| 93 |
local cnt = 1 |
|---|
| 94 |
if type(o) == "table" then |
|---|
| 95 |
cnt = 0 |
|---|
| 96 |
for k, v in pairs(o) do |
|---|
| 97 |
cnt = cnt + json_metric(check, prefix and (prefix .. '`' .. k) or k, v) |
|---|
| 98 |
end |
|---|
| 99 |
elseif type(o) == "string" then |
|---|
| 100 |
check.metric(prefix, o) |
|---|
| 101 |
elseif type(o) == "number" then |
|---|
| 102 |
check.metric_double(prefix, o) |
|---|
| 103 |
elseif type(o) == "boolean" then |
|---|
| 104 |
check.metric_int32(prefix, o and 1 or 0) |
|---|
| 105 |
end |
|---|
| 106 |
return cnt |
|---|
| 107 |
end |
|---|
| 108 |
|
|---|
| 109 |
function json_to_metrics(check, doc) |
|---|
| 110 |
local services = 0 |
|---|
| 111 |
check.available() |
|---|
| 112 |
local data = doc:document() |
|---|
| 113 |
services = json_metric(check, nil, data) |
|---|
| 114 |
check.metric_uint32("services", services) |
|---|
| 115 |
if services > 0 then check.good() else check.bad() end |
|---|
| 116 |
check.status("services=" .. services) |
|---|
| 117 |
end |
|---|
| 118 |
|
|---|
| 119 |
function xml_to_metrics(check, doc) |
|---|
| 120 |
check.available() |
|---|
| 121 |
|
|---|
| 122 |
local services = 0 |
|---|
| 123 |
local result |
|---|
| 124 |
for result in doc:xpath("/ResmonResults/ResmonResult") do |
|---|
| 125 |
services = services + 1 |
|---|
| 126 |
local module = result:attr("module") or "DUMMY" |
|---|
| 127 |
local service = result:attr("service") or "DUMMY" |
|---|
| 128 |
local prefix = module .. '`' .. service .. '`' |
|---|
| 129 |
local obj |
|---|
| 130 |
obj = (doc:xpath("last_runtime_seconds", result))() |
|---|
| 131 |
local ds = tonumber(obj and obj:contents()) |
|---|
| 132 |
if ds ~= nil then |
|---|
| 133 |
ds = math.floor(ds * 1000) |
|---|
| 134 |
check.metric_uint32(prefix .. "duration", ds) |
|---|
| 135 |
end |
|---|
| 136 |
obj = (doc:xpath("state", result))() |
|---|
| 137 |
if obj ~= nil then |
|---|
| 138 |
check.metric_string(prefix .. "state", obj and obj:contents()) |
|---|
| 139 |
end |
|---|
| 140 |
local metrics = 0 |
|---|
| 141 |
for metric in doc:xpath("metric", result) do |
|---|
| 142 |
metrics = metrics + 1 |
|---|
| 143 |
local name = metric:attr("name") or "DUMMY" |
|---|
| 144 |
local type = metric:attr("type") or "DUMMY" |
|---|
| 145 |
if type == 'i' then |
|---|
| 146 |
check.metric_int32(prefix .. name, metric and metric:contents()) |
|---|
| 147 |
elseif type == 'I' then |
|---|
| 148 |
check.metric_uint32(prefix .. name, metric and metric:contents()) |
|---|
| 149 |
elseif type == 'l' then |
|---|
| 150 |
check.metric_int64(prefix .. name, metric and metric:contents()) |
|---|
| 151 |
elseif type == 'L' then |
|---|
| 152 |
check.metric_uint64(prefix .. name, metric and metric:contents()) |
|---|
| 153 |
elseif type == 'n' then |
|---|
| 154 |
check.metric_double(prefix .. name, metric and metric:contents()) |
|---|
| 155 |
elseif type == 's' then |
|---|
| 156 |
check.metric_string(prefix .. name, metric and metric:contents()) |
|---|
| 157 |
else |
|---|
| 158 |
check.metric(prefix .. name, metric and metric:contents()) |
|---|
| 159 |
end |
|---|
| 160 |
end |
|---|
| 161 |
if metrics == 0 then |
|---|
| 162 |
local message = (doc:xpath("message", result))() |
|---|
| 163 |
check.metric_string(prefix .. "message", message and message:contents()) |
|---|
| 164 |
end |
|---|
| 165 |
end |
|---|
| 166 |
check.metric_uint32("services", services) |
|---|
| 167 |
local status = '' |
|---|
| 168 |
if services > 0 then check.good() else check.bad() end |
|---|
| 169 |
check.status("services=" .. services) |
|---|
| 170 |
end |
|---|
| 171 |
|
|---|
| 172 |
function initiate(module, check) |
|---|
| 173 |
local url = check.config.url or 'http:///' |
|---|
| 174 |
local schema, host, uri = string.match(url, "^(https?)://([^/]*)(.+)$"); |
|---|
| 175 |
local port |
|---|
| 176 |
local use_ssl = false |
|---|
| 177 |
local codere = noit.pcre(check.config.code or '^200$') |
|---|
| 178 |
local good = false |
|---|
| 179 |
local starttime = noit.timeval.now() |
|---|
| 180 |
|
|---|
| 181 |
local user = check.config.auth_user or nil |
|---|
| 182 |
local pass = check.config.auth_password or nil |
|---|
| 183 |
local encoded = nil |
|---|
| 184 |
if (user ~= nil and pass ~= nil) then |
|---|
| 185 |
encoded = noit.base64_encode(user .. ':' .. pass) |
|---|
| 186 |
end |
|---|
| 187 |
|
|---|
| 188 |
-- assume the worst. |
|---|
| 189 |
check.bad() |
|---|
| 190 |
check.unavailable() |
|---|
| 191 |
|
|---|
| 192 |
if host == nil then host = check.target end |
|---|
| 193 |
if schema == nil then |
|---|
| 194 |
schema = 'http' |
|---|
| 195 |
uri = '/' |
|---|
| 196 |
end |
|---|
| 197 |
if schema == 'http' then |
|---|
| 198 |
port = check.config.port or 81 |
|---|
| 199 |
elseif schema == 'https' then |
|---|
| 200 |
port = check.config.port or 443 |
|---|
| 201 |
use_ssl = true |
|---|
| 202 |
else |
|---|
| 203 |
error(schema .. " not supported") |
|---|
| 204 |
end |
|---|
| 205 |
|
|---|
| 206 |
local output = '' |
|---|
| 207 |
|
|---|
| 208 |
-- callbacks from the HttpClient |
|---|
| 209 |
local callbacks = { } |
|---|
| 210 |
local hdrs_in = { } |
|---|
| 211 |
callbacks.consume = function (str) output = output .. str end |
|---|
| 212 |
callbacks.headers = function (t) hdrs_in = t end |
|---|
| 213 |
local client = HttpClient:new(callbacks) |
|---|
| 214 |
local rv, err = client:connect(check.target, port, use_ssl) |
|---|
| 215 |
|
|---|
| 216 |
if rv ~= 0 then |
|---|
| 217 |
check.status(err or "unknown error") |
|---|
| 218 |
return |
|---|
| 219 |
end |
|---|
| 220 |
|
|---|
| 221 |
-- perform the request |
|---|
| 222 |
local headers = {} |
|---|
| 223 |
headers.Host = host |
|---|
| 224 |
if encoded ~= nil then |
|---|
| 225 |
headers["Authorization"] = "Basic " .. encoded |
|---|
| 226 |
end |
|---|
| 227 |
client:do_request("GET", uri, headers) |
|---|
| 228 |
client:get_response() |
|---|
| 229 |
|
|---|
| 230 |
local jsondoc = nil |
|---|
| 231 |
if string.find(hdrs_in["content-type"] or '', 'json') ~= nil or |
|---|
| 232 |
string.find(hdrs_in["content-type"] or '', 'javascript') ~= nil then |
|---|
| 233 |
jsondoc = noit.parsejson(output) |
|---|
| 234 |
if jsondoc == nil then |
|---|
| 235 |
noit.log("error", "bad json: %s", output) |
|---|
| 236 |
check.status("json parse error") |
|---|
| 237 |
return |
|---|
| 238 |
end |
|---|
| 239 |
end |
|---|
| 240 |
|
|---|
| 241 |
if jsondoc ~= nil then |
|---|
| 242 |
json_to_metrics(check, jsondoc) |
|---|
| 243 |
return |
|---|
| 244 |
end |
|---|
| 245 |
|
|---|
| 246 |
-- try xml by "default" (assuming no json-specific content header) |
|---|
| 247 |
|
|---|
| 248 |
-- parse the xml doc |
|---|
| 249 |
local doc = noit.parsexml(output) |
|---|
| 250 |
if doc == nil then |
|---|
| 251 |
jsondoc = noit.parsejson(output) |
|---|
| 252 |
if jsondoc == nil then |
|---|
| 253 |
noit.log("error", "bad xml: %s", output) |
|---|
| 254 |
check.status("xml parse error") |
|---|
| 255 |
return |
|---|
| 256 |
end |
|---|
| 257 |
json_to_metrics(check, jsondoc) |
|---|
| 258 |
return |
|---|
| 259 |
end |
|---|
| 260 |
|
|---|
| 261 |
xml_to_metrics(check, doc) |
|---|
| 262 |
end |
|---|
| 263 |
|
|---|