diff --git a/check_maxscale b/check_maxscale index 0d2f856..f53753e 100755 --- a/check_maxscale +++ b/check_maxscale @@ -1,11 +1,11 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Author: Fabien Hochstrasser # Date: 2018-04-03 # Purpose: Nagios check to monitor the MaxScale SQL proxy. # -# This script uses the maxadmin CLI to read the status of MaxScale. +# This script uses the maxctrl CLI to read the status of MaxScale. # # Usage: see --help # @@ -13,7 +13,6 @@ import argparse import os import subprocess import sys - from collections import namedtuple Server = namedtuple('Server', 'name address port connections status') @@ -34,19 +33,19 @@ def main(args): problems = [] try: - servers = maxadmin_list_servers() - sessions = maxadmin_list_sessions() - monitors = maxadmin_list_monitors() + servers = maxctrl_list_servers() + sessions = maxctrl_list_sessions() + monitors = maxctrl_list_monitors() except subprocess.CalledProcessError: problems.append(Problem( - message='MaxScale doesn\'t seem to be running', + message="MaxScale doesn't seem to be running", severity=CRITICAL, )) problems.extend( - check_servers(servers, args.expected_number_of_servers, args.connections_threshold) + \ - check_sessions(sessions, args.sessions_threshold) + \ - check_monitors(monitors) + check_servers(servers, args.expected_number_of_servers, args.connections_threshold) + + check_sessions(sessions, args.sessions_threshold) + + check_monitors(monitors) ) if args.verbose: @@ -64,31 +63,33 @@ def main(args): print('') print('Problems') for problem in problems: - print problem + print(problem) print_message(problems) sys.exit( - CRITICAL if any([p.severity == CRITICAL for p in problems]) - else WARNING if any([p.severity == WARNING for p in problems]) + CRITICAL if any(p.severity == CRITICAL for p in problems) + else WARNING if any(p.severity == WARNING for p in problems) else OK ) -def maxadmin_list_servers(): - """Return the servers known to MaxScale. - - The command 'maxadmin list servers' is used. - """ - servers = [] - +def _run_maxctrl_list(what): devnull = open(os.devnull, 'w') - for line in subprocess.check_output( - ['maxadmin', 'list', 'servers'], + out = subprocess.check_output( + ['maxctrl', '--tsv', 'list', what], stderr=devnull, - close_fds=True, - ).split('\n'): + ) + if isinstance(out, bytes): + out = out.decode('utf-8', errors='replace') + return out.split('\n') + + +def maxctrl_list_servers(): + """Return the servers known to MaxScale.""" + servers = [] + for line in _run_maxctrl_list('servers'): try: - fields = [field.strip() for field in line.split('|')] + fields = [field.strip() for field in line.split('\t')] servers.append(Server( name=fields[0], address=fields[1], @@ -97,27 +98,16 @@ def maxadmin_list_servers(): status={s.strip() for s in fields[4].split(',')}, )) except (ValueError, IndexError): - # If the line cannot be parsed, just ignore it. It's a header line. pass - return servers -def maxadmin_list_sessions(): - """Return the sessions established to MaxScale. - - The command 'maxadmin list sessions' is used. - """ +def maxctrl_list_sessions(): + """Return the sessions established to MaxScale.""" sessions = [] - - devnull = open(os.devnull, 'w') - for line in subprocess.check_output( - ['maxadmin', 'list', 'sessions'], - stderr=devnull, - close_fds=True, - ).split('\n'): + for line in _run_maxctrl_list('sessions'): try: - fields = [field.strip() for field in line.split('|')] + fields = [field.strip() for field in line.split('\t')] sessions.append(Session( id=int(fields[0]), client=fields[1], @@ -125,37 +115,23 @@ def maxadmin_list_sessions(): state=fields[3], )) except (ValueError, IndexError): - # If the line cannot be parsed, just ignore it. It's a header line. pass - return sessions -def maxadmin_list_monitors(): - """Return the monitors configured in MaxScale. - - The command 'maxadmin list monitors' is used. - """ +def maxctrl_list_monitors(): + """Return the monitors configured in MaxScale.""" monitors = [] - - devnull = open(os.devnull, 'w') - for line in subprocess.check_output( - ['maxadmin', 'list', 'monitors'], - stderr=devnull, - close_fds=True, - ).split('\n'): + for line in _run_maxctrl_list('monitors'): try: - fields = [field.strip() for field in line.split('|')] - # Ignore the header line + fields = [field.strip() for field in line.split('\t')] if fields[1] != 'Status': monitors.append(Monitor( name=fields[0], status=fields[1], )) except (ValueError, IndexError): - # If the line cannot be parsed, just ignore it. It's a header line. pass - return monitors @@ -163,39 +139,36 @@ def check_servers(servers, expected_number_of_servers, connections_threshold): problems = [] problems.append(check_servers_count(len(servers), expected_number_of_servers)) problems.append(check_servers_no_master(servers)) - for server in servers: problems.append(check_server_status(server)) problems.append(check_server_connections(server, connections_threshold)) - return [p for p in problems if p is not None] def check_servers_count(real, expected): - """Return a problem if the number of backend servers is different than expected.""" - if real == 0: - return Problem( - message='Could not find any backend server', - severity=CRITICAL, - ) - if real != expected: - return Problem( - message='{real} backend servers detected (expected {expected})'.format( - real=real, - expected=expected, - ), - severity=WARNING, - ) + """Return a problem if the number of backend servers is different than expected.""" + if real == 0: + return Problem( + message='Could not find any backend server', + severity=CRITICAL, + ) + if real != expected: + return Problem( + message='{real} backend servers detected (expected {expected})'.format( + real=real, + expected=expected, + ), + severity=WARNING, + ) def check_servers_no_master(servers): """Return a problem if no backend server has the status "Master".""" - if not any(['Master' in s.status for s in servers]): + if not any('Master' in s.status for s in servers): return Problem( message='No backend server in Master mode', severity=CRITICAL, ) - return None @@ -206,19 +179,16 @@ def check_server_status(server): message='Server "{0}" in Maintenance mode'.format(server.name), severity=WARNING, ) - if 'Down' in server.status: return Problem( message='Server "{0}" down'.format(server.name), severity=WARNING, ) - if 'Synced' not in server.status: return Problem( message='Server "{0}" not Synced'.format(server.name), severity=CRITICAL, ) - return None @@ -232,56 +202,43 @@ def check_server_connections(server, connections_threshold): ), severity=WARNING, ) - return None def check_sessions(sessions, sessions_threshold): problems = [] - problems.append(check_sessions_count(len(sessions), sessions_threshold)) problems.append(check_sessions_unique_id(sessions)) - return [p for p in problems if p is not None] -def check_sessions_count(real, max): +def check_sessions_count(real, max_sessions): """Return a problem if the number of sessions is outside the "normal" range.""" - if real > max: + if real > max_sessions: return Problem( message='{real} sessions detected (max {max})'.format( real=real, - max=max, + max=max_sessions, ), severity=WARNING, ) - return None def check_sessions_unique_id(sessions): - """Alert if 2 sessions have the same ID. - - From MaxScale documentation: - The sessions are referenced using ID values, these are actually memory address, - however the important thing is that no two session have the same ID. - """ - # Compare list and set sizes. If they differ, there are duplicates + """Alert if 2 sessions have the same ID.""" if len([s.id for s in sessions]) != len({s.id for s in sessions}): return Problem( message='Some sessions have the same ID', severity=WARNING, ) - return None def check_monitors(monitors): problems = [] - for monitor in monitors: problems.append(check_monitor_status(monitor)) - return [p for p in problems if p is not None] @@ -292,7 +249,6 @@ def check_monitor_status(monitor): message='Status of monitor "{0}": {1}'.format(monitor.name, monitor.status), severity=WARNING, ) - return None @@ -308,7 +264,6 @@ def print_message(problems): print('OK - No problem detected on MaxScale') -# Those thresholds are just a guess, they are maybe way to low/high... DEFAULT_CONNECTIONS_THRESHOLD = 500 DEFAULT_SESSIONS_THRESHOLD = 1000 @@ -347,5 +302,4 @@ if __name__ == '__main__': help='Print detailed information', ) args = parser.parse_args() - main(args)