From c0e1d9cbc558ff158134a4841506763f0bf8535c Mon Sep 17 00:00:00 2001 From: f0o Date: Mon, 15 Dec 2014 11:10:26 +0000 Subject: [PATCH] Added Alerting --- alerts.php | 231 ++++++++++++++++++++++--- doc/Alerting.md | 165 ++++++++++++++++++ includes/alerts.inc.php | 243 ++++++++++++++++----------- includes/alerts/transport.api.php | 48 ++++++ includes/alerts/transport.dummy.php | 25 +++ includes/alerts/transport.irc.php | 24 +++ includes/alerts/transport.mail.php | 24 +++ includes/alerts/transport.nagios.php | 48 ++++++ irc.php | 16 +- librenms.cron | 3 +- poller.php | 3 + sql-schema/036.sql | 10 ++ 12 files changed, 710 insertions(+), 130 deletions(-) create mode 100644 doc/Alerting.md create mode 100644 includes/alerts/transport.api.php create mode 100644 includes/alerts/transport.dummy.php create mode 100644 includes/alerts/transport.irc.php create mode 100644 includes/alerts/transport.mail.php create mode 100644 includes/alerts/transport.nagios.php create mode 100644 sql-schema/036.sql diff --git a/alerts.php b/alerts.php index 999ae76737..bfe4e60edb 100755 --- a/alerts.php +++ b/alerts.php @@ -1,16 +1,26 @@ #!/usr/bin/env php + * 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 . */ /** - * Observium - * - * This file is part of Observium. - * - * @package observium - * @subpackage alerts - * @author Adam Armstrong - * @copyright (C) 2006 - 2012 Adam Armstrong - * + * Alerts Cronjob + * @author f0o + * @copyright 2014 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts */ include("includes/defaults.inc.php"); @@ -18,22 +28,199 @@ include("config.php"); include("includes/definitions.inc.php"); include("includes/functions.php"); -foreach (dbFetchRows("SELECT *, A.id AS id FROM `alerts` AS A, `devices` AS D WHERE A.device_id = D.device_id AND alerted = '0'") as $alert) -{ - $id = $alert['id']; - $host = $alert['hostname']; - $date = $alert['time_logged']; - $msg = $alert['message']; - $alert_text .= "$date $host $msg"; - - dbUpdate(array('alerted' => '1'), 'alerts', '`id` = ?', array($id)); +RunAlerts(); +/** + * Run all alerts + * @return void + */ +function RunAlerts() { + global $config; + $default_tpl = "%title\r\nSeverity: %severity\r\n{if %state == 0}Time elapsed: %elapsed\r\n{/if}Timestamp: %timestamp\r\nUnique-ID: %uid\r\nRule: %rule\r\n{if %faults}Faults:\r\n{foreach %faults} #%key: %value\r\n{/foreach}{/if}Alert sent to: {foreach %contacts}%value <%key> {/foreach}"; //FIXME: Put somewhere else? + foreach( dbFetchRows("SELECT alerts.device_id, alerts.rule_id, alerts.state FROM alerts WHERE alerts.state != 2 && alerts.open = 1") as $alert ) { + $alert = dbFetchRow("SELECT alert_log.id,alert_log.rule_id,alert_log.device_id,alert_log.state,alert_log.details,alert_log.time_logged,alert_rules.rule,alert_rules.severity,alert_rules.extra FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? ORDER BY alert_log.id DESC LIMIT 1",array($alert['device_id'],$alert['rule_id'])); + $alert['details'] = json_decode(gzuncompress($alert['details']),true); + $noiss = false; + $noacc = false; + $updet = false; + $rextra = json_decode($alert['extra'],true); + $chk = dbFetchRow('SELECT alerted FROM alerts WHERE device_id = ? && rule_id = ?',array($alert['device_id'],$alert['rule_id'])); + if( $chk['alerted'] == $alert['state'] ) { + $noiss = true; + } + if( !empty($rextra['delay']) ) { + if( (time()-strtotime($alert['time_logged'])) < $rextra['delay'] || (!empty($alert['details']['delay']) && (time()-$alert['details']['delay']) < $rextra['delay']) ) { + continue; + } else { + $alert['details']['delay'] = time(); + $updet = true; + } + } + if( $alert['state'] == 1 && !empty($rextra['count']) && ($rextra['count'] == -1 || $alert['details']['count']++ < $rextra['count']) ) { + if( $alert['details']['count'] < $rextra['count'] ) { + $noacc = true; + } + $updet = true; + $noiss = false; + } + if( $updet ) { + dbUpdate(array('details' => gzcompress(json_encode($alert['details']),9)),'alert_log','id = ?',array($alert['id'])); + } + if( !empty($rextra['muted']) ) { + echo "Muted Alert-UID #".$alert['id']."\r\n"; + $noiss = true; + } + if( !$noiss ) { + $obj = DescribeAlert($alert); + if( is_array($obj) ) { + $tpl = dbFetchRow('SELECT template FROM alert_templates WHERE rule_id LIKE "%,'.$alert['rule_id'].',%"'); + if( isset($tpl['template']) ) { + $tpl = $tpl['template']; + } else { + $tpl = $default_tpl; + } + echo "Issuing Alert-UID #".$alert['id'].": "; + $msg = FormatAlertTpl($tpl,$obj); + $obj['msg'] = $msg; + if( !empty($config['alert']['transports']) ) { + ExtTransports($obj); + } + echo "\r\n"; + dbUpdate(array('alerted' => $alert['state']),'alerts','rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id'])); + } + } + if( !$noacc ) { + dbUpdate(array('open' => 0),'alerts','rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id'])); + } + } } -if ($alert_text) -{ - echo("$alert_text"); - # `echo '$alert_text' | gnokii --sendsms `; +/** + * Run external transports + * @param array $obj Alert-Array + * @return void + */ +function ExtTransports($obj) { + global $config; + foreach( $config['alert']['transports'] as $transport=>$opts ) { + if( file_exists($config['install_dir']."/includes/alerts/transport.".$transport.".php") ) { + echo $transport." => "; + eval('$tmp = function($obj,$opts) { global $config; '.file_get_contents($config['install_dir']."/includes/alerts/transport.".$transport.".php").' };'); + $tmp = $tmp($obj,$opts); + echo ($tmp ? "OK" : "ERROR")."; "; + } + } } +/** + * Format Alert + * @param string $tpl Template + * @param array $obj Alert-Array + * @return string + */ +function FormatAlertTpl($tpl,$obj) { + $tpl = addslashes($tpl); + + /** + * {if ..}..{else}..{/if} + */ + preg_match_all('/\\{if (.+)\\}(.+)\\{\\/if\\}/Uims',$tpl,$m); + foreach( $m[1] as $k=>$if ) { + $if = preg_replace('/%(\w+)/i','\$obj["$1"]', $if); + $ret = ""; + $cond = "if( $if ) {\r\n".'$ret = "'.str_replace("{else}",'";'."\r\n} else {\r\n".'$ret = "',$m[2][$k]).'";'."\r\n}\r\n"; + eval($cond); //FIXME: Eval is Evil + $tpl = str_replace($m[0][$k],$ret,$tpl); + } + + /** + * {foreach %var}..{/foreach} + */ + preg_match_all('/\\{foreach (.+)\\}(.+)\\{\\/foreach\\}/Uims',$tpl,$m); + foreach( $m[1] as $k=>$for ) { + $for = preg_replace('/%(\w+)/i','\$obj["$1"]', $for); + $ret = ""; + $loop = 'foreach( '.$for.' as $key=>$value ) { $ret .= "'.str_replace(array("%key","%value"),array('$key','$value'),$m[2][$k]).'"; }'; + eval($loop); //FIXME: Eval is Evil + $tpl = str_replace($m[0][$k],$ret,$tpl); + } + + /** + * Populate variables with data + */ + foreach( $obj as $k=>$v ) { + $tpl = str_replace("%".$k, $v, $tpl); + } + return $tpl; +} + +/** + * Describe Alert + * @param array $alert Alert-Result from DB + * @return array + */ +function DescribeAlert($alert) { + $tmp = array(); + $obj = array(); + $device = dbFetchRow("SELECT hostname FROM devices WHERE device_id = ?",array($alert['device_id'])); + $obj['hostname'] = $device['hostname']; + $extra = $alert['details']; + $s = (sizeof($extra['rule']) > 1); + if( $alert['state'] == 1 ) { + $obj['title'] = 'Alert for device '.$device['hostname'].' Alert-ID #'.$alert['id']; + foreach( $extra['rule'] as $incident ) { + $i++; + foreach( $incident as $k=>$v ) { + if( !empty($v) && $k != 'device_id' && (stristr($k,'id') || stristr($k,'desc')) && substr_count($k,'_') <= 1 ) { + $obj['faults'][$i] .= $k.' => '.$v."; "; + } + } + } + } elseif( $alert['state'] == 0 ) { + $id = dbFetchRow("SELECT alert_log.id,alert_log.time_logged,alert_log.details FROM alert_log WHERE alert_log.state = 1 && alert_log.rule_id = ? && alert_log.device_id = ? && alert_log.id < ? ORDER BY id DESC LIMIT 1", array($alert['rule_id'],$alert['device_id'],$alert['id'])); + if( empty($id['id']) ) { + return false; + } + $extra = json_decode(gzuncompress($id['details']),true); + $s = (sizeof($extra['rule']) > 1); + $obj['title'] = 'Device '.$device['hostname'].' recovered from Alert-ID #'.$id['id']; + $obj['elapsed'] = TimeFormat(strtotime($alert['time_logged'])-strtotime($id['time_logged'])); + $obj['id'] = $id['id']; + $obj['faults'] = false; + } else { + return "Unknown State"; + } + $obj['uid'] = $alert['id']; + $obj['severity'] = $alert['severity']; + $obj['rule'] = $alert['rule']; + $obj['timestamp'] = $alert['time_logged']; + $obj['contacts'] = $extra['contacts']; + $obj['state'] = $alert['state']; + return $obj; +} + +/** + * Format Elapsed Time + * @param int $secs Seconds elapsed + * @return string + */ +function TimeFormat($secs){ + $bit = array( + 'y' => $secs / 31556926 % 12, + 'w' => $secs / 604800 % 52, + 'd' => $secs / 86400 % 7, + 'h' => $secs / 3600 % 24, + 'm' => $secs / 60 % 60, + 's' => $secs % 60 + ); + foreach($bit as $k => $v){ + if($v > 0) { + $ret[] = $v . $k; + } + } + if( empty($ret) ) { + return "none"; + } + return join(' ', $ret); +} ?> diff --git a/doc/Alerting.md b/doc/Alerting.md new file mode 100644 index 0000000000..0a721db18a --- /dev/null +++ b/doc/Alerting.md @@ -0,0 +1,165 @@ +Table of Content: +- [About](#about) +- [Rules](#rules) + - [Syntax](#rules-syntax) + - [Examples](#rules-examples) +- [Templates](#templates) + - [Syntax](#templates-syntax) + - [Examples](#templates-examples) +- [Transports](#transports) + - [E-Mail](#transports-email) + - [API](#transports-api) + - [Nagios-Compatible](#transports-nagios) + - [IRC](#transports-irc) + +# About + +LibreNMS includes a highly customizable alerting system. +The system requires a set of user-defined rules to evaluate the situation of each device, port, service or any other entity. + +This document only covers the usage of it. See the [DEVELOPMENT.md](https://github.com/f0o/glowing-tyrion/blob/master/DEVELOPMENT.md) for code-documentation. + +# Rules + +Rules are defined using a logical language. +The GUI provides a simple way of creating basic as well as complex Rules in a self-describing manner. +More complex rules can be written manually. + +## Syntax + +Rules must consist of at least 3 elements: An __Entity__, a __Condition__ and a __Value__. +Rules can contain braces and __Glues__. +__Entities__ are provided as `%`-Noted pair of Table and Field. For Example: `%ports.ifOperStatus`. +__Conditions__ can be any of: +- Equals `=` +- Not Equals `!=` +- Matches `~` +- Not Matches `!~` +- Greater `>` +- Greater or Equal `>=` +- Smaller `<` +- Smaller or Equal `<=` + +__Values__ can be Entities or any single-quoted data. +__Glues__ can be either `&&` for `AND` or `||` for `OR`. + +__Note__: The difference between `Equals` and `Matches` (and it's negation) is that `Equals` does a strict comparison and `Matches` allows the usage of the placeholder `*`. The placeholder `*` is comparable with `.*` in RegExp. + +## Examples + +Alert when: +- Device goes down: `%devices.status != '1'` +- Any port changes: `%ports.ifOperStatus != 'up'` +- Root-directory gets too full: `%storage.storage_descr = '/' && %storage.storage_perc >= '75'` +- Any storage gets fuller than the 'warning': `%storage.storage_perc >= %storage_perc_warn` + +# Templates + +Templates can be assigned to a single or a group of rules. +They can contain any kind of text. +The template-parser understands `if` and `foreach` controls and replaces certain placeholders with information gathered about the alert. + +## Syntax + +Controls: +- if-else (Else can be omitted): +`{if %placeholder == 'value'}Some Text{else}Other Text{/if}` +- foreach-loop: +`{foreach %placeholder}Key: %key
Value: %value{/foreach}` + +__Limitations__: Currently it is not possible to have nested `if` controls, so `if` inside an `if`. It is also not possible to have an `if` inside a `foreach` control. These limitations are going to be resolved in (near?) future. + +Placeholders: +- Hostname of the Device: `%hostname` +- Title for the Alert: `%title` +- Time Elapsed, Only available on recovery (`%state == 0`): `%elapsed` +- Alert-ID: `%id` +- Unique-ID: `%uid` +- Faults, Only available on alert (`%state == 1`), must be iterated in a foreach: `%faults` +- State: `%state` +- Severity: `%severity` +- Rule: `%rule` +- Timestamp: `%timestamp` +- Contacts, must be iterated in a foreach, `%key` holds email and `%value` holds name: `%contacts` + +## Examples + +Default Template: +```text +%title\r\n +Severity: %severity\r\n +{if %state == 0}Time elapsed: %elapsed\r\n{/if} +Timestamp: %timestamp\r\n +Unique-ID: %uid\r\n +Rule: %rule\r\n +{if %faults}Faults:\r\n +{foreach %faults} #%key: %value\r\n{/foreach}{/if} +Alert sent to: {foreach %contacts}%value <%key> {/foreach} +``` + +# Transports + +Transports are located within `$config['install_dir']/includes/alerts/transports.*.php` and defined as well as configured via `$config['alert']['transports']['Example'] = 'Some Options'`. + +Contacts will be gathered automatically and passed to the configured transports. +The contacts will always include the `SysContact` defined in the Device's SNMP configuration and also every LibreNMS-User that has at least `read`-permissions on the entity that is to be alerted. +At the moment LibreNMS only supports Port or Device permissions. +To include users that have `Global-Read` or `Administrator` permissions it is required to add these additions to the `config.php` respectively: +```php +$config['alert']['globals'] = true; //Include Global-Read into alert-contacts +$config['alert']['admins'] = true; //Include Adminstrators into alert-contacts +``` + +## E-Mail + +E-Mail transport is enabled with adding the following to your `config.php`: +```php +$config['alert']['transports']['mail'] = true; +``` + +The E-Mail transports uses the same email-configuration like the rest of LibreNMS. +As a small reminder, here is it's configuration directives including defaults: +```php +$config['email_backend'] = 'mail'; // Mail backend. Allowed: "mail" (PHP's built-in), "sendmail", "smtp". +$config['email_from'] = NULL; // Mail from. Default: "ProjectName" +$config['email_user'] = $config['project_id']; +$config['email_sendmail_path'] = '/usr/sbin/sendmail'; // The location of the sendmail program. +$config['email_smtp_host'] = 'localhost'; // Outgoing SMTP server name. +$config['email_smtp_port'] = 25; // The port to connect. +$config['email_smtp_timeout'] = 10; // SMTP connection timeout in seconds. +$config['email_smtp_secure'] = NULL; // Enable encryption. Use 'tls' or 'ssl' +$config['email_smtp_auth'] = FALSE; // Whether or not to use SMTP authentication. +$config['email_smtp_username'] = NULL; // SMTP username. +$config['email_smtp_password'] = NULL; // Password for SMTP authentication. +``` + +## API + +API transports definitions are a bit more complex than the E-Mail configuration. +The basis for configuration is `$config['alert']['transports']['api'][METHOD]` where `METHOD` can be `get`,`post` or `put`. +This basis has to contain an array with URLs of each API to call. +The URL can have the same placeholders as defined in the [Template-Syntax](#templates-syntax). +If the `METHOD` is `get`, all placeholders will be URL-Encoded. +The API transport uses cURL to call the APIs, therefore you might need to install `php5-curl` or similar in order to make it work. +__Note__: it is highly recommended to define own [Templates](#templates) when you want to use the API transport. The default template might exceed URL-length for GET requests and therefore cause all sorts of errors. + +Example: +```php +$config['alert']['transports']['api']['get'][] = "https://api.thirdparti.es/issue?apikey=abcdefg&subject=%title"; +``` + +## Nagios Compatible + +The nagios transport will feed a FIFO at the defined location with the same format that nagios would. +This allows you to use other Alerting-Systems to work with LibreNMS, for example [Flapjack](http://flapjack.io). +```php +$config['alert']['transports']['nagios'] = "/path/to/my.fifo"; //Flapjack expects it to be at '/var/cache/nagios3/event_stream.fifo' +``` + +## IRC + +The IRC transports only works together with the LibreNMS IRC-Bot. +Configuration of the LibreNMS IRC-Bot is described [here](https://github.com/librenms/librenms/blob/master/doc/IRC-Bot.md). +```php +$config['alert']['transports']['irc'] = true; +``` diff --git a/includes/alerts.inc.php b/includes/alerts.inc.php index 45a3373fed..565bb4cebf 100755 --- a/includes/alerts.inc.php +++ b/includes/alerts.inc.php @@ -1,112 +1,151 @@ + * 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 . */ /** - * Observium Network Management and Monitoring System - * Copyright (C) 2006-2012, Adam Armstrong - http://www.observium.org - * - * @package alerting - * @author Adam Armstrong - * + * Alerts Tracking + * @author Daniel Preussker + * @copyright 2014 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts */ +/** + * Generate SQL from Rule + * @param string $rule Rule to generate SQL for + * @return string + */ +function GenSQL($rule) { + $tmp = explode(" ",$rule); + $tables = array(); + foreach( $tmp as $opt ) { + if( strstr($opt,'%') && strstr($opt,'.') ) { + $tmpp = explode(".",$opt,2); + $tmpp[0] = str_replace("%","",$tmpp[0]); + $tables[] = mres($tmpp[0]); + $rule = str_replace($opt,$tmpp[0].'.'.$tmpp[1],$rule); + } + } + $tables = array_unique($tables); + $x = sizeof($tables); + $i = 0; + $join = ""; + while( $i < $x ) { + if( isset($tables[$i+1]) ) { + $join .= $tables[$i].".device_id = ".$tables[$i+1].".device_id && "; + } + $i++; + } + $sql = "SELECT * FROM ".implode(",",$tables)." WHERE (".$join."".$tables[0].".device_id = ?) && (".str_replace(array("*","!~","~"),array("%","NOT LIKE","LIKE"),$rule).")"; + return $sql; +} + /** - * Build a cache of default alert conditions - * + * Run all rules for a device + * @param int $device Device-ID + * @return void + */ +function RunRules($device) { + global $debug; + $chk = dbFetchRow("SELECT id FROM alert_schedule WHERE alert_schedule.device_id = ? AND NOW() BETWEEN alert_schedule.start AND alert_schedule.end", array($device)); + if( $chk['id'] > 0 ) { + return false; + } + foreach( dbFetchRows("SELECT * FROM alert_rules WHERE alert_rules.disabled = 0 && ( alert_rules.device_id = -1 || alert_rules.device_id = ? ) ORDER BY device_id,id",array($device)) as $rule ) { + echo " #".$rule['id'].":"; + $chk = dbFetchRow("SELECT state FROM alerts WHERE rule_id = ? && device_id = ? ORDER BY id DESC LIMIT 1", array($rule['id'], $device)); + $sql = GenSQL($rule['rule']); + $qry = dbFetchRows($sql,array($device)); + if( sizeof($qry) > 0 ) { + if( $chk['state'] === "2" ) { + echo " SKIP "; + } elseif( $chk['state'] === "1" ) { + echo " NOCHG "; + } else { + $extra = gzcompress(json_encode(array('contacts' => GetContacts($qry), 'rule'=>$qry)),9); + if( dbInsert(array('state' => 1, 'device_id' => $device, 'rule_id' => $rule['id'], 'details' => $extra),'alert_log') ) { + if( !dbUpdate(array('state' => 1, 'open' => 1),'alerts','device_id = ? && rule_id = ?', array($device,$rule['id'])) ) { + dbInsert(array('state' => 1, 'device_id' => $device, 'rule_id' => $rule['id'], 'open' => 1),'alerts'); + } + echo " ALERT "; + } + } + } else { + if( $chk['state'] === "0" ) { + echo " NOCHG "; + } else { + if( dbInsert(array('state' => 0, 'device_id' => $device, 'rule_id' => $rule['id']),'alert_log') ){ + if( !dbUpdate(array('state' => 0, 'open' => 1),'alerts','device_id = ? && rule_id = ?', array($device,$rule['id'])) ) { + dbInsert(array('state' => 0, 'device_id' => $device, 'rule_id' => $rule['id'], 'open' => 1),'alerts'); + } + echo " OK "; + } + } + } + } +} + +/** + * Find contacts for alert + * @param array $results Rule-Result * @return array -*/ - -function cache_conditions_global() { - $cache = array(); - foreach (dbFetchRows("SELECT * FROM `alert_conditions_global`") as $entry) - { - $cache[$entry['type']][$entry['subtype']][$entry['metric']][] = array('operator' => $entry['operator'], 'value' => $entry['value'], - 'severity' => $entry['severity'], 'alerter' => $entry['alerter'], 'enable' => $entry['enable']); - } - return $cache; + */ +function GetContacts($results) { + global $config; + if( sizeof($results) == 0 ) { + return array(); + } + $contacts = array(); + $uids = array(); + foreach( $results as $result ) { + $tmpa = array(); + $tmp = NULL; + if( is_numeric($result["port_id"]) ) { + $tmpa = dbFetchRows("SELECT user_id FROM ports_perms WHERE access_level >= 0 AND port_id = ?",array($result["port_id"])); + foreach( $tmpa as $tmp ) { + $uids[$tmp['user_id']] = $tmp['user_id']; + } + } + if( is_numeric($result["device_id"]) ) { + $tmpa = dbFetchRow("SELECT sysContact FROM devices WHERE device_id = ?",array($result["device_id"])); + $contacts[$tmpa["sysContact"]] = "NOC"; + $tmpa = dbFetchRows("SELECT user_id FROM devices_perms WHERE access_level >= 0 AND device_id = ?", array($result["device_id"])); + foreach( $tmpa as $tmp ) { + $uids[$tmp['user_id']] = $tmp['user_id']; + } + } + } + if( $config["alert"]["globals"] ) { + $tmpa = dbFetchRows("SELECT realname,email FROM users WHERE level >= 5 AND level < 10"); + foreach( $tmpa as $tmp ) { + $contacts[$tmp['email']] = $tmp['realname']; + } + } + if( $config["alert"]["admins"] ) { + $tmpa = dbFetchRows("SELECT realname,email FROM users WHERE level = 10"); + foreach( $tmpa as $tmp ) { + $contacts[$tmp['email']] = $tmp['realname']; + } + } + if( is_array($uids) ) { + foreach( $uids as $uid ) { + $tmp = dbFetchRow("SELECT realname,email FROM users WHERE user_id = ?", array($uid)); + $contacts[$tmp['email']] = $tmp['realname']; + } + } + return $contacts; } - -/** - * Build a cache of device-specific alert conditions - * - * @return array - * @param device_id -*/ - -function cache_conditions_device($device_id) { - - $cache = array(); - foreach (dbFetchRows("SELECT * FROM `alert_conditions` WHERE `device_id` = ?", array($device_id)) as $entry) - { - $cache[$entry['type']][$entry['subtype']][$entry['entity']][$entry['metric']][] = array('condition' => $entry['operator'], 'value' => $entry['value'], - 'severity' => $entry['severity'], 'alerter' => $entry['alerter'], - 'enable' => $entry['enable']); - } - return $cache; -} - -/** - * Compare two values - * - * @return integer - * @param value_a - * @param condition - * @param value_b -*/ - -function test_condition($value_a, $condition, $value_b) -{ - switch($condition) - { - case ">": - if($value_a > $value_b) { $alert = 1; } else { $alert = 0; } - break; - case "<": - if($value_a < $value_b) { $alert = 1; } else { $alert = 0; } - break; - case "!=": - if($value_a != $value_b) { $alert = 1; } else { $alert = 0; } - break; - case "=": - if($value_a = $value_b) { $alert = 1; } else { $alert = 0; } - break; - default: - $alert = -1; - break; - } - return $alert; -} - -/** - * Check entity data against alert conditions - * - * @param value_a - * @param condition - * @param value_b -*/ - -function check_entity($device, $entity_type, $entity_id, $data) -{ - global $glo_conditions; - global $dev_conditions; - - if(!empty($entity_id)) { echo(" $entity_id"); } - - foreach($data as $name => $value) - { - foreach($dev_conditions[$entity_type][$entity_id][$name] as $condition) - { - $alert = test_condition($value, $condition['condition'], $condition['value']); - if($alert == 1) - { - echo("ALERT "); - } else { - echo("OK "); - } - } - } -} - - - ?> diff --git a/includes/alerts/transport.api.php b/includes/alerts/transport.api.php new file mode 100644 index 0000000000..c601ca6f69 --- /dev/null +++ b/includes/alerts/transport.api.php @@ -0,0 +1,48 @@ +/* Copyright (C) 2014 Daniel Preussker + * 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 . */ + +/** + * API Transport + * @author f0o + * @copyright 2014 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts + */ + +foreach( $opts as $method=>$apis ) { +// var_dump($method); //FIXME: propper debuging + foreach( $apis as $api ) { +// var_dump($api); //FIXME: propper debuging + list($host, $api) = explode("?",$api,2); + foreach( $obj as $k=>$v ) { + $api = str_replace("%".$k,$method == "get" ? urlencode($v) : $v, $api); + } +// var_dump($api); //FIXME: propper debuging + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, ($method == "get" ? $host."?".$api : $host) ); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method)); + curl_setopt($curl, CURLOPT_POSTFIELDS, $api); + $ret = curl_exec($curl); + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + if( $code != 200 ) { + var_dump("API '$host' returnd Error"); //FIXME: propper debuging + var_dump("Params: ".$api); //FIXME: propper debuging + var_dump("Return: ".$ret); //FIXME: propper debuging + return false; + } + } +} +return true; diff --git a/includes/alerts/transport.dummy.php b/includes/alerts/transport.dummy.php new file mode 100644 index 0000000000..04ac01e9b1 --- /dev/null +++ b/includes/alerts/transport.dummy.php @@ -0,0 +1,25 @@ +/* Copyright (C) 2014 Daniel Preussker + * 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 . */ + +/** + * Dummy Transport + * @author f0o + * @copyright 2014 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts + */ + +var_dump($obj); +return true; diff --git a/includes/alerts/transport.irc.php b/includes/alerts/transport.irc.php new file mode 100644 index 0000000000..71e0591c07 --- /dev/null +++ b/includes/alerts/transport.irc.php @@ -0,0 +1,24 @@ +/* Copyright (C) 2014 Daniel Preussker + * 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 . */ + +/** + * IRC Transport + * @author f0o + * @copyright 2014 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts + */ + +return file_put_contents($config['install_dir']."/.ircbot.alert", json_encode($obj)."\n", FILE_APPEND); diff --git a/includes/alerts/transport.mail.php b/includes/alerts/transport.mail.php new file mode 100644 index 0000000000..5a09f2493b --- /dev/null +++ b/includes/alerts/transport.mail.php @@ -0,0 +1,24 @@ +/* Copyright (C) 2014 Daniel Preussker + * 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 . */ + +/** + * Mail Transport + * @author f0o + * @copyright 2014 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts + */ + +return send_mail($obj['contacts'], $obj['title'], $obj['msg']); diff --git a/includes/alerts/transport.nagios.php b/includes/alerts/transport.nagios.php new file mode 100644 index 0000000000..201598674e --- /dev/null +++ b/includes/alerts/transport.nagios.php @@ -0,0 +1,48 @@ +/* Copyright (C) 2014 Daniel Preussker + * 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 . */ + +/** + * Nagios Transport + * @author f0o + * @copyright 2014 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts + */ + +/* +host_perfdata_file_template= +[HOSTPERFDATA]\t +$TIMET$\t +$HOSTNAME$\t +HOST\t +$HOSTSTATE$\t +$HOSTEXECUTIONTIME$\t +$HOSTLATENCY$\t +$HOSTOUTPUT$\t +$HOSTPERFDATA$ +*/ + +$format = ''; +$format .= "[HOSTPERFDATA]\t"; +$format .= $obj['timestamp']."\t"; +$format .= $obj['hostname']."\t"; +$format .= md5($obj['rule'])."\t"; //FIXME: Better entity +$format .= ($obj['state'] ? $obj['severity'] : "ok")."\t"; +$format .= 0."\t"; +$format .= 0."\t"; +$format .= str_replace("\n","",nl2br($obj['msg']))."\t"; +$format .= "NULL"; //FIXME: What's the HOSTPERFDATA equivalent for LibreNMS? Oo +$format .= "\n"; +return file_put_contents($opts, $format); diff --git a/irc.php b/irc.php index 8bb29c4abe..87c3d38363 100755 --- a/irc.php +++ b/irc.php @@ -151,11 +151,17 @@ class ircbot { } private function alertData() { - if( ($tmp = $this->read("alert")) !== false ) { - foreach( $tmp as $data ) { - $this->data = $data; - echo $this->data; - //dostuff + if( ($alert = $this->read("alert")) !== false ) { + $alert = json_decode($alert,true); + foreach( $this->authd as $nick=>$data ) { + if( $data['expire'] >= time() ) { + $this->irc_raw("PRIVMSG ".$nick." :".trim($alert['title'])." - Rule: ".trim($alert['rule'])." - Faults".(sizeof($alert['faults']) > 3 ? " (showing first 3 out of ".sizeof($alert['faults'])." )" : "" ).":"); + foreach( $alert['faults'] as $k=>$v ) { + $this->irc_raw("PRIVMSG ".$nick." :#".$k." ".$v); + if( $k >= 3 ) + break; + } + } } } } diff --git a/librenms.cron b/librenms.cron index 5d346503c8..f2171210ff 100644 --- a/librenms.cron +++ b/librenms.cron @@ -1,4 +1,5 @@ 33 */6 * * * root /opt/librenms/discovery.php -h all >> /dev/null 2>&1 */5 * * * * root /opt/librenms/discovery.php -h new >> /dev/null 2>&1 */5 * * * * root /opt/librenms/poller-wrapper.py 16 >> /dev/null 2>&1 -15 0 * * * root sh /opt/librenms/daily.sh > /dev/null 2>&1 +15 0 * * * root sh /opt/librenms/daily.sh >> /dev/null 2>&1 +*/2 * * * * root /opt/librenms/alerts.php >> /dev/null 2>&1 diff --git a/poller.php b/poller.php index fd91f3d727..2a1f348e18 100755 --- a/poller.php +++ b/poller.php @@ -20,6 +20,7 @@ include("config.php"); include("includes/definitions.inc.php"); include("includes/functions.php"); include("includes/polling/functions.inc.php"); +include("includes/alerts.inc.php"); $poller_start = utime(); echo($config['project_name_version']." Poller\n\n"); @@ -108,6 +109,8 @@ foreach (dbFetch($query) as $device) { $device = dbFetchRow("SELECT * FROM `devices` WHERE `device_id` = '".$device['device_id']."'"); poll_device($device, $options); + RunRules($device['device_id']); + echo "\r\n"; $polled_devices++; } diff --git a/sql-schema/036.sql b/sql-schema/036.sql new file mode 100644 index 0000000000..3a02fd9604 --- /dev/null +++ b/sql-schema/036.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS `alerts`; +CREATE TABLE IF NOT EXISTS `alerts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `device_id` int(11) NOT NULL, `rule_id` int(11) NOT NULL, `state` int(11) NOT NULL, `alerted` int(11) NOT NULL, `open` int(11) NOT NULL, `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `alert_log`; +CREATE TABLE IF NOT EXISTS `alert_log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rule_id` int(11) NOT NULL, `device_id` int(11) NOT NULL, `state` int(11) NOT NULL, `details` longblob NOT NULL, `time_logged` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY `id` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `alert_rules`; +CREATE TABLE IF NOT EXISTS `alert_rules` ( `id` int(11) NOT NULL AUTO_INCREMENT, `device_id` int(11) NOT NULL, `rule` text CHARACTER SET utf8 NOT NULL, `severity` enum('ok','warning','critical') CHARACTER SET utf8 NOT NULL, `extra` varchar(255) CHARACTER SET utf8 NOT NULL, `disabled` tinyint(1) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `alert_schedule`; +CREATE TABLE IF NOT EXISTS `alert_schedule` ( `id` int(11) NOT NULL AUTO_INCREMENT, `device_id` int(11) NOT NULL, `start` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', `end` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `alert_templates`; +CREATE TABLE IF NOT EXISTS `alert_templates` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rule_id` varchar(255) NOT NULL DEFAULT ',', `template` longtext NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;