diff --git a/check-services.php b/check-services.php index c76493b420..3484bf3dd0 100755 --- a/check-services.php +++ b/check-services.php @@ -16,7 +16,7 @@ $init_modules = array(); require __DIR__ . '/includes/init.php'; -$options = getopt('d::'); +$options = getopt('d::h:f:;'); if (isset($options['d'])) { echo "DEBUG!\n"; $debug = true; @@ -44,7 +44,22 @@ if ($config['noinfluxdb'] !== true && $config['influxdb']['enable'] === true) { rrdtool_initialize(); -foreach (dbFetchRows('SELECT * FROM `devices` AS D, `services` AS S WHERE S.device_id = D.device_id ORDER by D.device_id DESC') as $service) { +$where = ''; +if ($options['h']) { + if (is_numeric($options['h'])) { + $where = "AND `S`.`device_id` = ".$options['h']; + } else { + if (preg_match('/\*/', $options['h'])) { + $where = "AND `hostname` LIKE '".str_replace('*', '%', mres($options['h']))."'"; + } else { + $where = "AND `hostname` = '".mres($options['h'])."'"; + } + } +} + +$sql = 'SELECT * FROM `devices` AS D, `services` AS S WHERE S.device_id = D.device_id ' . $where . ' ORDER by D.device_id DESC'; + +foreach (dbFetchRows($sql) as $service) { // Run the polling function poll_service($service); } //end foreach diff --git a/doc/Extensions/Services.md b/doc/Extensions/Services.md index 87cd1643ef..fb6d4acd42 100644 --- a/doc/Extensions/Services.md +++ b/doc/Extensions/Services.md @@ -8,6 +8,9 @@ to LibreNMS - localhost is a good one. ## Setup +> Service checks is now distributed aware. If you run a distributed setup then you can now run +`services-wrapper.py` in cron instead of `check-services.php` across all polling nodes. + Firstly, install Nagios plugins however you would like, this could be via yum, apt-get or direct from source. Next, you need to enable the services within config.php with the following: diff --git a/services-wrapper.py b/services-wrapper.py new file mode 100755 index 0000000000..88558676e7 --- /dev/null +++ b/services-wrapper.py @@ -0,0 +1,381 @@ +#! /usr/bin/env python2 +""" + services-wrapper A small tool which wraps around check-services.php and tries to + guide the services process with a more modern approach with a + Queue and workers. + + Based on the original version of poller-wrapper.py by Job Snijders + + Author: Neil Lathwood + Date: Oct 2016 + + Usage: This program accepts one command line argument: the number of threads + that should run simultaneously. If no argument is given it will assume + a default of 1 thread. + + Ubuntu Linux: apt-get install python-mysqldb + FreeBSD: cd /usr/ports/*/py-MySQLdb && make install clean + + License: This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see http://www.gnu.org/licenses/. + + LICENSE.txt contains a copy of the full GPLv3 licensing conditions. +""" +try: + + import json + import os + import Queue + import subprocess + import sys + import threading + import time + +except: + print "ERROR: missing one or more of the following python modules:" + print "threading, Queue, sys, subprocess, time, os, json" + sys.exit(2) + +try: + import MySQLdb +except: + print "ERROR: missing the mysql python module:" + print "On ubuntu: apt-get install python-mysqldb" + print "On FreeBSD: cd /usr/ports/*/py-MySQLdb && make install clean" + sys.exit(2) + +""" + Fetch configuration details from the config_to_json.php script +""" + +install_dir = os.path.dirname(os.path.realpath(__file__)) +config_file = install_dir + '/config.php' + + +def get_config_data(): + config_cmd = ['/usr/bin/env', 'php', '%s/config_to_json.php' % install_dir] + try: + proc = subprocess.Popen(config_cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + except: + print "ERROR: Could not execute: %s" % config_cmd + sys.exit(2) + return proc.communicate()[0] + +try: + with open(config_file) as f: + pass +except IOError as e: + print "ERROR: Oh dear... %s does not seem readable" % config_file + sys.exit(2) + +try: + config = json.loads(get_config_data()) +except: + print "ERROR: Could not load or parse configuration, are PATHs correct?" + sys.exit(2) + +service_path = config['install_dir'] + '/check-services.php' +db_username = config['db_user'] +db_password = config['db_pass'] + +if config['db_host'][:5].lower() == 'unix:': + db_server = config['db_host'] + db_port = 0 +elif ':' in config['db_host']: + db_server = config['db_host'].rsplit(':')[0] + db_port = int(config['db_host'].rsplit(':')[1]) +else: + db_server = config['db_host'] + db_port = 0 + +db_dbname = config['db_name'] + + +def db_open(): + try: + if db_port == 0: + db = MySQLdb.connect(host=db_server, user=db_username, passwd=db_password, db=db_dbname) + else: + db = MySQLdb.connect(host=db_server, port=db_port, user=db_username, passwd=db_password, db=db_dbname) + return db + except: + print "ERROR: Could not connect to MySQL database!" + sys.exit(2) + +# (c) 2015, GPLv3, Daniel Preussker << << << << << << 0 and nodes is not None: + try: + time.sleep(1) + nodes = memc.get("service.nodes") + except: + pass + print "Clearing Locks" + x = minlocks + while x <= maxlocks: + memc.delete('service.device.' + str(x)) + x = x + 1 + print "%s Locks Cleared" % x + print "Clearing Nodes" + memc.delete("service.master") + memc.delete("service.nodes") + else: + memc.decr("service.nodes") + print "Finished %s." % time.time() +# EOC6 + +show_stopper = False + +if total_time > 300: + print "WARNING: the process took more than 5 minutes to finish, you need faster hardware or more threads" + print "INFO: in sequential style service checks the elapsed time would have been: %s seconds" % real_duration + for device in per_device_duration: + if per_device_duration[device] > 300: + print "WARNING: device %s is taking too long: %s seconds" % (device, per_device_duration[device]) + show_stopper = True + if show_stopper: + print "ERROR: Some devices are taking more than 300 seconds, the script cannot recommend you what to do." + else: + recommend = int(total_time / 300.0 * amount_of_workers + 1) + print "WARNING: Consider setting a minimum of %d threads. (This does not constitute professional advice!)" % recommend + + sys.exit(2)