Giuseppe Lavagetto has uploaded a new change for review.
https://gerrit.wikimedia.org/r/223328
Change subject: service::node: auto-monitoring of local endpoints [WiP]
......................................................................
service::node: auto-monitoring of local endpoints [WiP]
Bug: T94821
Change-Id: I96ddc75be235d6efeb2e8d50f4af5bf0f8b2537a
---
A modules/service/files/checker.py
A modules/service/manifests/monitoring.pp
M modules/service/manifests/node.pp
3 files changed, 221 insertions(+), 1 deletion(-)
git pull ssh://gerrit.wikimedia.org:29418/operations/puppet
refs/changes/28/223328/1
diff --git a/modules/service/files/checker.py b/modules/service/files/checker.py
new file mode 100644
index 0000000..6e98fd3
--- /dev/null
+++ b/modules/service/files/checker.py
@@ -0,0 +1,192 @@
+try:
+ import urlparse
+except ImportError:
+ import urllib.parse as urlparse
+import yaml
+import urllib3
+import sys
+import argparse
+
+
+class CheckServiceError(Exception):
+ pass
+
+
+def fetch_url(client, url, **kw):
+ if 'method' in kw:
+ method = kw['method']
+ else:
+ method = 'GET'
+
+ try:
+ return client.request(
+ method,
+ url,
+ **kw
+ )
+ except urllib3.exceptions.SSLError:
+ raise CheckServiceError("Invalid certificate")
+ except (urllib3.exceptions.ConnectTimeoutError,
+ urllib3.exceptions.TimeoutError,
+ urllib3.exceptions.ConnectionError,
+ urllib3.exceptions.ReadTimeoutError):
+ raise CheckServiceError("Timeout on connection while "
+ "downloading {}".format(url))
+ except Exception as e:
+ raise CheckServiceError("Generic connection error: {}".format(e))
+
+
+class CheckService(object):
+ nagios_codes = ['OK', 'WARNING', 'CRITICAL']
+ spec_url = '/?spec'
+
+ def __init__(self, host_ip, base_url, timeout=5):
+ self.host_ip = host_ip
+ self.base_url = urlparse.urlsplit(base_url)
+ http_host_port = self.base_url.netloc.split(':')
+ if len(http_host_port) < 2:
+ http_host_port.append('80')
+ self.http_host, self.port = http_host_port
+ self.endpoints = {}
+
+ def _spawn_downloader(self):
+ kw = {
+ 'retries': 1,
+ 'timeout': self._timeout
+ }
+ kw['ca_certs'] = "/etc/ssl/certs/ca-certificates.crt"
+ kw['cert_reqs'] = 'CERT_REQUIRED'
+ return urllib3.PoolMananger(**kw)
+
+ def get_endpoints(self):
+ http = self._spawn_downloader()
+ # TODO: cache all this.
+ response = fetch_url(
+ http,
+ self._url(self.spec_url),
+ timeout=self.timeout,
+ headers={'Host': self.http_host}
+ )
+
+ resp = response.data.decode('utf-8')
+
+ try:
+ r = yaml.load(resp)
+ except ValueError:
+ raise ValueError("No valid spec found")
+ for endpoint, data in r['paths'].items():
+ try:
+ for x in data['x-amples']:
+ yield endpoint, x
+ except KeyError:
+ # No example
+ pass
+
+ def run(self):
+ res = []
+ status = 'OK'
+ idx = self.nagios_codes.index(status)
+ try:
+ for endpoint, data in self.get_endpoints():
+ ep_status, msg = self._check_endpoint(endpoint, data)
+ res.append("{} is {}: {}".format(endpoint, status, msg))
+ if ep_status != 'OK':
+ ep_idx = self.nagios_codes.index(ep_status)
+ if ep_idx >= idx:
+ status = ep_status
+ idx = ep_idx
+ message = u"; ".join(res)
+ except Exception as e:
+ message = "Generic error: {}".format(e)
+ status = 'CRITICAL'
+ print message
+ sys.exit(self.nagios_codes.index(status))
+
+ def _check_endpoint(self, url, data):
+ data['request']['http_host'] - self.http_host
+ er = EndpointRequest(
+ data['title'],
+ self._url(url),
+ data['request'],
+ data['response']
+ )
+ er.run()
+ return (er.status, er.msg)
+
+ def _url(self, url):
+ return "{}://{}:{}{}".format(self.base_url.scheme,
+ self.host_ip,
+ self.port,
+ url)
+
+
+class EndpointRequest(object):
+
+ def __init__(self, title, url, request, response):
+ self.status = 'OK'
+ self.msg = 'Endpoint {} healthy'.format(url)
+ self.title = title
+ self._request(request)
+ self._response(response)
+ self.url = url
+
+ def _request(self, data):
+ raise NotImplementedError
+
+ def _response(self, data):
+ raise NotImplementedError
+
+ def run(self, client):
+ try:
+
+ r = fetch_url(
+ client,
+ self.url,
+ headers=self.request_headers,
+ redirect=False
+ )
+ except CheckServiceError as e:
+ return ('CRITICAL', "Could not fetch url {}: {}".format(
+ self.url, e))
+
+ # Response status
+ if r.status != self.expected.status:
+ self.status = "CRITICAL"
+ self.msg = "Endpoint {} returned "
+ "the unexpected status {} (expecting: {})".format(
+ self.url, r.status, self.expected.status)
+ return
+
+ # Headers
+ for k, v in self.expected.headers:
+ h = r.getheader(k)
+ if h != v:
+ self.status = "CRITICAL"
+ self.msg = "Endpoint {} had an unexpected value "
+ "for header {}: {}".format(self.url, k, h)
+ return
+ # Body
+ if self.expected.body is not None:
+ body = r.data.decode('utf-8')
+ if self.expected.body != body:
+ self.status = "CRITICAL"
+ self.msg = "Endpoint {} responds with "
+ "unexpected body: {}".format(self.url, body)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Checks the availability and response of one WMF service')
+ parser.add_argument('host_ip', help="The IP address of the host to check")
+ parser.add_argument('service_url',
+ help="The base url for the service, including port")
+ parser.add_argument('-t', dest="timeout", default=5, type=int,
+ help="Timeout (in seconds) for each "
+ "request. Default: 5")
+ args = parser.parse_args()
+ checker = CheckService(args.host_ip, args.service_url, args.timeout)
+ checker.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/modules/service/manifests/monitoring.pp
b/modules/service/manifests/monitoring.pp
new file mode 100644
index 0000000..4b3e7c5
--- /dev/null
+++ b/modules/service/manifests/monitoring.pp
@@ -0,0 +1,16 @@
+# === Class service::monitoring
+#
+# this is intended to include all shared resources used for monitoring
+# services defined via service::node
+
+class service::monitoring {
+ require_packages 'python-yaml', 'python-urllib3'
+
+ file { '/usr/local/lib/nagions/plugins/service_checker':
+ ensure => present,
+ owner => root,
+ group => root,
+ mode => '0555',
+ source => 'puppet:///modules/service/checker.py',
+ }
+}
diff --git a/modules/service/manifests/node.pp
b/modules/service/manifests/node.pp
index c348e9d..1eb35ba 100644
--- a/modules/service/manifests/node.pp
+++ b/modules/service/manifests/node.pp
@@ -50,6 +50,7 @@
$no_file = 10000,
$healthcheck_url='/_info',
$firejail = false,
+ $has_spec = false,
) {
# Import all common configuration
include service::configuration
@@ -164,5 +165,16 @@
description => $title,
check_command => "check_http_port_url!${port}!${healthcheck_url}",
}
-}
+ # Advanced monitoring
+ if $has_spec {
+ include service::monitoring
+
+ $monitor_url = "http://localhost:${port}"
+ nrpe::monitor_service{ "endpoints_${title}":
+ description => "${title} endpoints health",
+ nrpe_command => "/usr/local/lib/nagios/plugins/service_checker -t
5 ${::ipaddress} ${monitor_url}",
+ subscribe =>
File['/usr/local/lib/nagions/plugins/service_checker'],
+ }
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/223328
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I96ddc75be235d6efeb2e8d50f4af5bf0f8b2537a
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Giuseppe Lavagetto <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits