2019-07-18 08:36:02 -05:00
< ? php
/*
* RunAlerts . php
2019-09-04 21:13:32 -05:00
*
2019-07-18 08:36:02 -05:00
* Copyright ( C ) 2014 Daniel Preussker < f0o @ devilcode . org >
* 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 />.
*
* Original Code :
* @ author Daniel Preussker < f0o @ devilcode . org >
* @ copyright 2014 f0o , LibreNMS
* @ license GPL
* @ package LibreNMS
* @ subpackage Alerts
*
* Modified by :
* @ author Heath Barnhart < barnhart @ kanren . net >
2019-09-04 21:13:32 -05:00
*
2019-07-18 08:36:02 -05:00
*/
namespace LibreNMS\Alert ;
use App\Models\DevicePerf ;
use LibreNMS\Config ;
2020-06-08 10:56:45 +02:00
use LibreNMS\Enum\Alert ;
2020-05-24 04:14:36 +02:00
use LibreNMS\Enum\AlertState ;
2020-09-21 14:54:51 +02:00
use LibreNMS\Util\Time ;
2019-07-18 08:36:02 -05:00
use Log ;
class RunAlerts
{
/**
* Populate variables
* @ param string $txt Text with variables
2020-09-21 14:54:51 +02:00
* @ param bool $wrap Wrap variable for text - usage ( default : true )
2019-07-18 08:36:02 -05:00
* @ return string
*/
public function populate ( $txt , $wrap = true )
{
preg_match_all ( '/%([\w\.]+)/' , $txt , $m );
foreach ( $m [ 1 ] as $tmp ) {
$orig = $tmp ;
2020-09-21 14:54:51 +02:00
$rep = false ;
2019-07-18 08:36:02 -05:00
if ( $tmp == 'key' || $tmp == 'value' ) {
2020-09-21 14:54:51 +02:00
$rep = '$' . $tmp ;
2019-07-18 08:36:02 -05:00
} else {
if ( strstr ( $tmp , '.' )) {
$tmp = explode ( '.' , $tmp , 2 );
2020-09-21 14:54:51 +02:00
$pre = '$' . $tmp [ 0 ];
2019-07-18 08:36:02 -05:00
$tmp = $tmp [ 1 ];
} else {
$pre = '$obj' ;
}
2020-09-21 14:54:51 +02:00
$rep = $pre . " [' " . str_replace ( '.' , " '][' " , $tmp ) . " '] " ;
2019-07-18 08:36:02 -05:00
if ( $wrap ) {
2020-09-21 14:54:51 +02:00
$rep = '{' . $rep . '}' ;
2019-07-18 08:36:02 -05:00
}
}
2020-09-21 14:54:51 +02:00
$txt = str_replace ( '%' . $orig , $rep , $txt );
2019-07-18 08:36:02 -05:00
}
2020-09-21 14:54:51 +02:00
2019-07-18 08:36:02 -05:00
return $txt ;
}
/**
* Describe Alert
* @ param array $alert Alert - Result from DB
2020-09-21 14:54:51 +02:00
* @ return array | bool
2019-07-18 08:36:02 -05:00
*/
public function describeAlert ( $alert )
{
2020-09-21 14:54:51 +02:00
$obj = [];
$i = 0 ;
$device = dbFetchRow ( 'SELECT hostname, sysName, sysDescr, sysContact, os, type, ip, hardware, version, purpose, notes, uptime, status, status_reason, locations.location FROM devices LEFT JOIN locations ON locations.id = devices.location_id WHERE device_id = ?' , [ $alert [ 'device_id' ]]);
$attribs = get_dev_attribs ( $alert [ 'device_id' ]);
$obj [ 'hostname' ] = $device [ 'hostname' ];
$obj [ 'sysName' ] = $device [ 'sysName' ];
$obj [ 'sysDescr' ] = $device [ 'sysDescr' ];
$obj [ 'sysContact' ] = $device [ 'sysContact' ];
$obj [ 'os' ] = $device [ 'os' ];
$obj [ 'type' ] = $device [ 'type' ];
$obj [ 'ip' ] = inet6_ntop ( $device [ 'ip' ]);
$obj [ 'hardware' ] = $device [ 'hardware' ];
$obj [ 'version' ] = $device [ 'version' ];
$obj [ 'serial' ] = $device [ 'serial' ];
$obj [ 'features' ] = $device [ 'features' ];
$obj [ 'location' ] = $device [ 'location' ];
$obj [ 'uptime' ] = $device [ 'uptime' ];
$obj [ 'uptime_short' ] = Time :: formatInterval ( $device [ 'uptime' ], 'short' );
$obj [ 'uptime_long' ] = Time :: formatInterval ( $device [ 'uptime' ]);
$obj [ 'description' ] = $device [ 'purpose' ];
$obj [ 'notes' ] = $device [ 'notes' ];
$obj [ 'alert_notes' ] = $alert [ 'note' ];
$obj [ 'device_id' ] = $alert [ 'device_id' ];
$obj [ 'rule_id' ] = $alert [ 'rule_id' ];
$obj [ 'id' ] = $alert [ 'id' ];
$obj [ 'proc' ] = $alert [ 'proc' ];
$obj [ 'status' ] = $device [ 'status' ];
2019-07-18 08:36:02 -05:00
$obj [ 'status_reason' ] = $device [ 'status_reason' ];
if ( can_ping_device ( $attribs )) {
$ping_stats = DevicePerf :: where ( 'device_id' , $alert [ 'device_id' ]) -> latest ( 'timestamp' ) -> first ();
$obj [ 'ping_timestamp' ] = $ping_stats -> template ;
2020-09-21 14:54:51 +02:00
$obj [ 'ping_loss' ] = $ping_stats -> loss ;
$obj [ 'ping_min' ] = $ping_stats -> min ;
$obj [ 'ping_max' ] = $ping_stats -> max ;
$obj [ 'ping_avg' ] = $ping_stats -> avg ;
$obj [ 'debug' ] = json_decode ( $ping_stats -> debug , true );
2019-07-18 08:36:02 -05:00
}
2020-09-21 14:54:51 +02:00
$extra = $alert [ 'details' ];
2019-07-18 08:36:02 -05:00
2020-09-21 14:54:51 +02:00
$tpl = new Template ;
$template = $tpl -> getTemplate ( $obj );
2019-07-18 08:36:02 -05:00
2020-05-24 04:14:36 +02:00
if ( $alert [ 'state' ] >= AlertState :: ACTIVE ) {
2020-09-21 14:54:51 +02:00
$obj [ 'title' ] = $template -> title ? : 'Alert for device ' . $device [ 'hostname' ] . ' - ' . ( $alert [ 'name' ] ? $alert [ 'name' ] : $alert [ 'rule' ]);
2020-05-24 04:14:36 +02:00
if ( $alert [ 'state' ] == AlertState :: ACKNOWLEDGED ) {
2019-07-18 08:36:02 -05:00
$obj [ 'title' ] .= ' got acknowledged' ;
2020-05-24 04:14:36 +02:00
} elseif ( $alert [ 'state' ] == AlertState :: WORSE ) {
2019-07-18 08:36:02 -05:00
$obj [ 'title' ] .= ' got worse' ;
2020-05-24 04:14:36 +02:00
} elseif ( $alert [ 'state' ] == AlertState :: BETTER ) {
2019-07-18 08:36:02 -05:00
$obj [ 'title' ] .= ' got better' ;
}
foreach ( $extra [ 'rule' ] as $incident ) {
$i ++ ;
$obj [ 'faults' ][ $i ] = $incident ;
$obj [ 'faults' ][ $i ][ 'string' ] = null ;
foreach ( $incident as $k => $v ) {
2020-09-21 14:54:51 +02:00
if ( ! empty ( $v ) && $k != 'device_id' && ( stristr ( $k , 'id' ) || stristr ( $k , 'desc' ) || stristr ( $k , 'msg' )) && substr_count ( $k , '_' ) <= 1 ) {
$obj [ 'faults' ][ $i ][ 'string' ] .= $k . ' = ' . $v . '; ' ;
2019-07-18 08:36:02 -05:00
}
}
}
$obj [ 'elapsed' ] = $this -> timeFormat ( time () - strtotime ( $alert [ 'time_logged' ]));
2020-09-21 14:54:51 +02:00
if ( ! empty ( $extra [ 'diff' ])) {
2019-07-18 08:36:02 -05:00
$obj [ 'diff' ] = $extra [ 'diff' ];
}
2020-05-24 04:14:36 +02:00
} elseif ( $alert [ 'state' ] == AlertState :: RECOVERED ) {
2019-07-18 08:36:02 -05:00
// Alert is now cleared
2020-09-21 14:54:51 +02:00
$id = dbFetchRow ( 'SELECT alert_log.id,alert_log.time_logged,alert_log.details FROM alert_log WHERE alert_log.state != ? && alert_log.state != ? && alert_log.rule_id = ? && alert_log.device_id = ? && alert_log.id < ? ORDER BY id DESC LIMIT 1' , [ AlertState :: ACKNOWLEDGED , AlertState :: RECOVERED , $alert [ 'rule_id' ], $alert [ 'device_id' ], $alert [ 'id' ]]);
2019-07-18 08:36:02 -05:00
if ( empty ( $id [ 'id' ])) {
return false ;
}
$extra = [];
2020-09-21 14:54:51 +02:00
if ( ! empty ( $id [ 'details' ])) {
2019-07-18 08:36:02 -05:00
$extra = json_decode ( gzuncompress ( $id [ 'details' ]), true );
}
// Reset count to 0 so alerts will continue
$extra [ 'count' ] = 0 ;
2020-09-21 14:54:51 +02:00
dbUpdate ([ 'details' => gzcompress ( json_encode ( $id [ 'details' ]), 9 )], 'alert_log' , 'id = ?' , [ $alert [ 'id' ]]);
2019-07-18 08:36:02 -05:00
2020-09-21 14:54:51 +02:00
$obj [ 'title' ] = $template -> title_rec ? : 'Device ' . $device [ 'hostname' ] . ' recovered from ' . ( $alert [ 'name' ] ? $alert [ 'name' ] : $alert [ 'rule' ]);
2019-07-18 08:36:02 -05:00
$obj [ 'elapsed' ] = $this -> timeFormat ( strtotime ( $alert [ 'time_logged' ]) - strtotime ( $id [ 'time_logged' ]));
2020-09-21 14:54:51 +02:00
$obj [ 'id' ] = $id [ 'id' ];
2019-07-18 08:36:02 -05:00
foreach ( $extra [ 'rule' ] as $incident ) {
$i ++ ;
$obj [ 'faults' ][ $i ] = $incident ;
foreach ( $incident as $k => $v ) {
2020-09-21 14:54:51 +02:00
if ( ! empty ( $v ) && $k != 'device_id' && ( stristr ( $k , 'id' ) || stristr ( $k , 'desc' ) || stristr ( $k , 'msg' )) && substr_count ( $k , '_' ) <= 1 ) {
$obj [ 'faults' ][ $i ][ 'string' ] .= $k . ' => ' . $v . '; ' ;
2019-07-18 08:36:02 -05:00
}
}
}
} else {
return 'Unknown State' ;
} //end if
2020-09-21 14:54:51 +02:00
$obj [ 'builder' ] = $alert [ 'builder' ];
$obj [ 'uid' ] = $alert [ 'id' ];
$obj [ 'alert_id' ] = $alert [ 'alert_id' ];
$obj [ 'severity' ] = $alert [ 'severity' ];
$obj [ 'rule' ] = $alert [ 'rule' ];
$obj [ 'name' ] = $alert [ 'name' ];
2019-07-18 08:36:02 -05:00
$obj [ 'timestamp' ] = $alert [ 'time_logged' ];
2020-09-21 14:54:51 +02:00
$obj [ 'contacts' ] = $extra [ 'contacts' ];
$obj [ 'state' ] = $alert [ 'state' ];
$obj [ 'alerted' ] = $alert [ 'alerted' ];
$obj [ 'template' ] = $template ;
2019-07-18 08:36:02 -05:00
return $obj ;
}
/**
* Format Elapsed Time
2020-09-21 14:54:51 +02:00
* @ param int $secs Seconds elapsed
2019-07-18 08:36:02 -05:00
* @ return string
*/
public function timeFormat ( $secs )
{
2020-09-21 14:54:51 +02:00
$bit = [
2019-07-18 08:36:02 -05:00
'y' => $secs / 31556926 % 12 ,
'w' => $secs / 604800 % 52 ,
'd' => $secs / 86400 % 7 ,
'h' => $secs / 3600 % 24 ,
'm' => $secs / 60 % 60 ,
's' => $secs % 60 ,
2020-09-21 14:54:51 +02:00
];
$ret = [];
2019-07-18 08:36:02 -05:00
foreach ( $bit as $k => $v ) {
if ( $v > 0 ) {
2020-09-21 14:54:51 +02:00
$ret [] = $v . $k ;
2019-07-18 08:36:02 -05:00
}
}
if ( empty ( $ret )) {
return 'none' ;
}
return join ( ' ' , $ret );
}
public function clearStaleAlerts ()
{
2020-09-21 14:54:51 +02:00
$sql = " SELECT `alerts`.`id` AS `alert_id`, `devices`.`hostname` AS `hostname` FROM `alerts` LEFT JOIN `devices` ON `alerts`.`device_id`=`devices`.`device_id` RIGHT JOIN `alert_rules` ON `alerts`.`rule_id`=`alert_rules`.`id` WHERE `alerts`.`state`!= " . AlertState :: CLEAR . " AND `devices`.`hostname` IS NULL " ;
2019-07-18 08:36:02 -05:00
foreach ( dbFetchRows ( $sql ) as $alert ) {
if ( empty ( $alert [ 'hostname' ]) && isset ( $alert [ 'alert_id' ])) {
2020-09-21 14:54:51 +02:00
dbDelete ( 'alerts' , '`id` = ?' , [ $alert [ 'alert_id' ]]);
2019-07-18 08:36:02 -05:00
echo " Stale-alert: # { $alert [ 'alert_id' ] } " . PHP_EOL ;
}
}
}
/**
* Re - Validate Rule - Mappings
2020-09-21 14:54:51 +02:00
* @ param int $device_id Device - ID
* @ param int $rule Rule - ID
* @ return bool
2019-07-18 08:36:02 -05:00
*/
public function isRuleValid ( $device_id , $rule )
{
global $rulescache ;
2020-09-21 14:54:51 +02:00
if ( empty ( $rulescache [ $device_id ]) || ! isset ( $rulescache [ $device_id ])) {
2019-07-18 11:05:43 -05:00
foreach ( AlertUtil :: getRules ( $device_id ) as $chk ) {
2019-07-18 08:36:02 -05:00
$rulescache [ $device_id ][ $chk [ 'id' ]] = true ;
}
}
if ( $rulescache [ $device_id ][ $rule ] === true ) {
return true ;
}
return false ;
}
/**
* Issue Alert - Object
* @ param array $alert
2020-09-21 14:54:51 +02:00
* @ return bool
2019-07-18 08:36:02 -05:00
*/
public function issueAlert ( $alert )
{
if ( Config :: get ( 'alert.fixed-contacts' ) == false ) {
if ( empty ( $alert [ 'query' ])) {
$alert [ 'query' ] = AlertDB :: genSQL ( $alert [ 'rule' ], $alert [ 'builder' ]);
}
$sql = $alert [ 'query' ];
2020-09-21 14:54:51 +02:00
$qry = dbFetchRows ( $sql , [ $alert [ 'device_id' ]]);
2019-07-18 11:05:43 -05:00
$alert [ 'details' ][ 'contacts' ] = AlertUtil :: getContacts ( $qry );
2019-07-18 08:36:02 -05:00
}
$obj = $this -> describeAlert ( $alert );
if ( is_array ( $obj )) {
2020-09-21 14:54:51 +02:00
echo 'Issuing Alert-UID #' . $alert [ 'id' ] . '/' . $alert [ 'state' ] . ':' . PHP_EOL ;
2019-07-18 08:36:02 -05:00
$this -> extTransports ( $obj );
echo " \r \n " ;
}
return true ;
}
/**
* Issue ACK notification
* @ return void
*/
public function runAcks ()
{
2020-09-21 14:54:51 +02:00
foreach ( $this -> loadAlerts ( 'alerts.state = ' . AlertState :: ACKNOWLEDGED . ' && alerts.open = ' . AlertState :: ACTIVE ) as $alert ) {
2019-07-18 08:36:02 -05:00
$this -> issueAlert ( $alert );
2020-09-21 14:54:51 +02:00
dbUpdate ([ 'open' => AlertState :: CLEAR ], 'alerts' , 'rule_id = ? && device_id = ?' , [ $alert [ 'rule_id' ], $alert [ 'device_id' ]]);
2019-07-18 08:36:02 -05:00
}
}
/**
* Run Follow - Up alerts
* @ return void
*/
public function runFollowUp ()
{
2020-09-21 14:54:51 +02:00
foreach ( $this -> loadAlerts ( 'alerts.state > ' . AlertState :: CLEAR . ' && alerts.open = 0' ) as $alert ) {
2020-05-24 04:14:36 +02:00
if ( $alert [ 'state' ] != AlertState :: ACKNOWLEDGED || ( $alert [ 'info' ][ 'until_clear' ] === false )) {
2019-07-18 08:36:02 -05:00
$rextra = json_decode ( $alert [ 'extra' ], true );
if ( $rextra [ 'invert' ]) {
continue ;
}
if ( empty ( $alert [ 'query' ])) {
$alert [ 'query' ] = AlertDB :: genSQL ( $alert [ 'rule' ], $alert [ 'builder' ]);
}
2020-09-21 14:54:51 +02:00
$chk = dbFetchRows ( $alert [ 'query' ], [ $alert [ 'device_id' ]]);
2019-07-18 08:36:02 -05:00
//make sure we can json_encode all the datas later
$cnt = count ( $chk );
for ( $i = 0 ; $i < $cnt ; $i ++ ) {
if ( isset ( $chk [ $i ][ 'ip' ])) {
$chk [ $i ][ 'ip' ] = inet6_ntop ( $chk [ $i ][ 'ip' ]);
}
}
$o = sizeof ( $alert [ 'details' ][ 'rule' ]);
$n = sizeof ( $chk );
$ret = 'Alert #' . $alert [ 'id' ];
2020-05-24 04:14:36 +02:00
$state = AlertState :: CLEAR ;
2019-07-18 08:36:02 -05:00
if ( $n > $o ) {
$ret .= ' Worsens' ;
2020-05-24 04:14:36 +02:00
$state = AlertState :: WORSE ;
2019-07-18 08:36:02 -05:00
$alert [ 'details' ][ 'diff' ] = array_diff ( $chk , $alert [ 'details' ][ 'rule' ]);
} elseif ( $n < $o ) {
$ret .= ' Betters' ;
2020-05-24 04:14:36 +02:00
$state = AlertState :: BETTER ;
2019-07-18 08:36:02 -05:00
$alert [ 'details' ][ 'diff' ] = array_diff ( $alert [ 'details' ][ 'rule' ], $chk );
}
2020-05-24 04:14:36 +02:00
if ( $state > AlertState :: CLEAR && $n > 0 ) {
2019-07-18 08:36:02 -05:00
$alert [ 'details' ][ 'rule' ] = $chk ;
2020-09-21 14:54:51 +02:00
if ( dbInsert ([
2019-07-18 08:36:02 -05:00
'state' => $state ,
'device_id' => $alert [ 'device_id' ],
'rule_id' => $alert [ 'rule_id' ],
2020-09-21 14:54:51 +02:00
'details' => gzcompress ( json_encode ( $alert [ 'details' ]), 9 ),
], 'alert_log' )) {
dbUpdate ([ 'state' => $state , 'open' => 1 , 'alerted' => 1 ], 'alerts' , 'rule_id = ? && device_id = ?' , [ $alert [ 'rule_id' ], $alert [ 'device_id' ]]);
2019-07-18 08:36:02 -05:00
}
echo $ret . ' (' . $o . '/' . $n . " ) \r \n " ;
}
}
}
}
public function loadAlerts ( $where )
{
$alerts = [];
Add support for sending events to Sensu (#11383)
* Add support for sending events to Sensu
Sensu is an alerting and monitoring service (and much more) that has a
nagios compatible check API.
This transport translates LibreNMS alerts into Sensu events, and sends
them to the agent API running on the same host as the poller.
The transport has a few options, but none of them are required - if the
Sensu agent is correctly configured, alerts will be sent as soon as the
transport is enabled.
There's a fair amount of code, as I've tried to translate as much data as
possible between LibreNMS and Sensu.
* Update Transports.md
* If alerted is 0, send an "ok" alert dated rrd.step / 2 seconds ago
This makes Sensu aware of the last time the check ran successfully (ish).
If we don't send the initial "ok", Sensu will either display 'unknown',
or an incorrectly high duration for the incident.
Alerted gets set to 1 after the first alert is sent.
We choose rrd.step / 2 as:
* rrd.step is the maximum time ago the check could have succeeded
* we halve it, so that if a check is flapping, it is not masked
Basically, we guess that the check fails around halfway through the time
since the poller last ran.
* Add additional metadata
* Improve codeclimate slightly
* Consider names that are 2 or 3 components long
2020-04-11 20:29:13 +01:00
foreach ( dbFetchRows ( " SELECT alerts.id, alerts.alerted, alerts.device_id, alerts.rule_id, alerts.state, alerts.note, alerts.info FROM alerts WHERE $where " ) as $alert_status ) {
2019-07-18 08:36:02 -05:00
$alert = dbFetchRow (
2019-09-24 18:08:22 +07:00
'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,alert_rules.name,alert_rules.query,alert_rules.builder,alert_rules.proc FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? && alert_rules.disabled = 0 ORDER BY alert_log.id DESC LIMIT 1' ,
2020-09-21 14:54:51 +02:00
[ $alert_status [ 'device_id' ], $alert_status [ 'rule_id' ]]
2019-07-18 08:36:02 -05:00
);
2020-09-21 14:54:51 +02:00
if ( empty ( $alert [ 'rule_id' ]) || ! $this -> isRuleValid ( $alert_status [ 'device_id' ], $alert_status [ 'rule_id' ])) {
2019-07-18 08:36:02 -05:00
echo 'Stale-Rule: #' . $alert_status [ 'rule_id' ] . '/' . $alert_status [ 'device_id' ] . " \r \n " ;
// Alert-Rule does not exist anymore, let's remove the alert-state.
dbDelete ( 'alerts' , 'rule_id = ? && device_id = ?' , [ $alert_status [ 'rule_id' ], $alert_status [ 'device_id' ]]);
} else {
$alert [ 'alert_id' ] = $alert_status [ 'id' ];
$alert [ 'state' ] = $alert_status [ 'state' ];
Add support for sending events to Sensu (#11383)
* Add support for sending events to Sensu
Sensu is an alerting and monitoring service (and much more) that has a
nagios compatible check API.
This transport translates LibreNMS alerts into Sensu events, and sends
them to the agent API running on the same host as the poller.
The transport has a few options, but none of them are required - if the
Sensu agent is correctly configured, alerts will be sent as soon as the
transport is enabled.
There's a fair amount of code, as I've tried to translate as much data as
possible between LibreNMS and Sensu.
* Update Transports.md
* If alerted is 0, send an "ok" alert dated rrd.step / 2 seconds ago
This makes Sensu aware of the last time the check ran successfully (ish).
If we don't send the initial "ok", Sensu will either display 'unknown',
or an incorrectly high duration for the incident.
Alerted gets set to 1 after the first alert is sent.
We choose rrd.step / 2 as:
* rrd.step is the maximum time ago the check could have succeeded
* we halve it, so that if a check is flapping, it is not masked
Basically, we guess that the check fails around halfway through the time
since the poller last ran.
* Add additional metadata
* Improve codeclimate slightly
* Consider names that are 2 or 3 components long
2020-04-11 20:29:13 +01:00
$alert [ 'alerted' ] = $alert_status [ 'alerted' ];
2019-07-18 08:36:02 -05:00
$alert [ 'note' ] = $alert_status [ 'note' ];
2020-09-21 14:54:51 +02:00
if ( ! empty ( $alert [ 'details' ])) {
2019-07-18 08:36:02 -05:00
$alert [ 'details' ] = json_decode ( gzuncompress ( $alert [ 'details' ]), true );
}
$alert [ 'info' ] = json_decode ( $alert_status [ 'info' ], true );
$alerts [] = $alert ;
}
}
return $alerts ;
}
/**
* Run all alerts
* @ return void
*/
public function runAlerts ()
{
2020-09-21 14:54:51 +02:00
foreach ( $this -> loadAlerts ( 'alerts.state != ' . AlertState :: ACKNOWLEDGED . ' && alerts.open = 1' ) as $alert ) {
$noiss = false ;
$noacc = false ;
$updet = false ;
$rextra = json_decode ( $alert [ 'extra' ], true );
if ( ! isset ( $rextra [ 'recovery' ])) {
2019-07-18 08:36:02 -05:00
// backwards compatibility check
$rextra [ 'recovery' ] = true ;
}
2020-09-21 14:54:51 +02:00
$chk = dbFetchRow ( 'SELECT alerts.alerted,devices.ignore,devices.disabled FROM alerts,devices WHERE alerts.device_id = ? && devices.device_id = alerts.device_id && alerts.rule_id = ?' , [ $alert [ 'device_id' ], $alert [ 'rule_id' ]]);
2019-07-18 08:36:02 -05:00
if ( $chk [ 'alerted' ] == $alert [ 'state' ]) {
$noiss = true ;
}
$tolerence_window = Config :: get ( 'alert.tolerance_window' );
2020-09-21 14:54:51 +02:00
if ( ! empty ( $rextra [ 'count' ]) && empty ( $rextra [ 'interval' ])) {
2019-07-18 08:36:02 -05:00
// This check below is for compat-reasons
2020-09-21 14:54:51 +02:00
if ( ! empty ( $rextra [ 'delay' ]) && $alert [ 'state' ] != AlertState :: RECOVERED ) {
if (( time () - strtotime ( $alert [ 'time_logged' ]) + $tolerence_window ) < $rextra [ 'delay' ] || ( ! empty ( $alert [ 'details' ][ 'delay' ]) && ( time () - $alert [ 'details' ][ 'delay' ] + $tolerence_window ) < $rextra [ 'delay' ])) {
2019-07-18 08:36:02 -05:00
continue ;
} else {
$alert [ 'details' ][ 'delay' ] = time ();
$updet = true ;
}
}
2020-09-21 14:54:51 +02:00
if ( $alert [ 'state' ] == AlertState :: ACTIVE && ! empty ( $rextra [ 'count' ]) && ( $rextra [ 'count' ] == - 1 || $alert [ 'details' ][ 'count' ] ++ < $rextra [ 'count' ])) {
2019-07-18 08:36:02 -05:00
if ( $alert [ 'details' ][ 'count' ] < $rextra [ 'count' ]) {
$noacc = true ;
}
$updet = true ;
$noiss = false ;
}
} else {
// This is the new way
2020-09-21 14:54:51 +02:00
if ( ! empty ( $rextra [ 'delay' ]) && ( time () - strtotime ( $alert [ 'time_logged' ]) + $tolerence_window ) < $rextra [ 'delay' ] && $alert [ 'state' ] != AlertState :: RECOVERED ) {
2019-07-18 08:36:02 -05:00
continue ;
}
2020-09-21 14:54:51 +02:00
if ( ! empty ( $rextra [ 'interval' ])) {
if ( ! empty ( $alert [ 'details' ][ 'interval' ]) && ( time () - $alert [ 'details' ][ 'interval' ] + $tolerence_window ) < $rextra [ 'interval' ]) {
2019-07-18 08:36:02 -05:00
continue ;
} else {
$alert [ 'details' ][ 'interval' ] = time ();
$updet = true ;
}
}
2020-09-21 14:54:51 +02:00
if ( in_array ( $alert [ 'state' ], [ AlertState :: ACTIVE , AlertState :: WORSE , AlertState :: BETTER ]) && ! empty ( $rextra [ 'count' ]) && ( $rextra [ 'count' ] == - 1 || $alert [ 'details' ][ 'count' ] ++ < $rextra [ 'count' ])) {
2019-07-18 08:36:02 -05:00
if ( $alert [ 'details' ][ 'count' ] < $rextra [ 'count' ]) {
$noacc = true ;
}
$updet = true ;
$noiss = false ;
}
}
if ( $chk [ 'ignore' ] == 1 || $chk [ 'disabled' ] == 1 ) {
$noiss = true ;
$updet = false ;
$noacc = false ;
}
2020-06-14 12:39:10 -05:00
if ( AlertUtil :: isMaintenance ( $alert [ 'device_id' ])) {
2019-07-18 08:36:02 -05:00
$noiss = true ;
$noacc = true ;
}
if ( $updet ) {
2020-09-21 14:54:51 +02:00
dbUpdate ([ 'details' => gzcompress ( json_encode ( $alert [ 'details' ]), 9 )], 'alert_log' , 'id = ?' , [ $alert [ 'id' ]]);
2019-07-18 08:36:02 -05:00
}
2020-09-21 14:54:51 +02:00
if ( ! empty ( $rextra [ 'mute' ])) {
echo 'Muted Alert-UID #' . $alert [ 'id' ] . " \r \n " ;
2019-07-18 08:36:02 -05:00
$noiss = true ;
}
if ( $this -> isParentDown ( $alert [ 'device_id' ])) {
$noiss = true ;
Log :: event ( 'Skipped alerts because all parent devices are down' , $alert [ 'device_id' ], 'alert' , 1 );
}
2020-05-24 04:14:36 +02:00
if ( $alert [ 'state' ] == AlertState :: RECOVERED && $rextra [ 'recovery' ] == false ) {
2019-07-18 08:36:02 -05:00
// Rule is set to not send a recovery alert
$noiss = true ;
}
2020-09-21 14:54:51 +02:00
if ( ! $noiss ) {
2019-07-18 08:36:02 -05:00
$this -> issueAlert ( $alert );
2020-09-21 14:54:51 +02:00
dbUpdate ([ 'alerted' => $alert [ 'state' ]], 'alerts' , 'rule_id = ? && device_id = ?' , [ $alert [ 'rule_id' ], $alert [ 'device_id' ]]);
2019-07-18 08:36:02 -05:00
}
2020-09-21 14:54:51 +02:00
if ( ! $noacc ) {
dbUpdate ([ 'open' => 0 ], 'alerts' , 'rule_id = ? && device_id = ?' , [ $alert [ 'rule_id' ], $alert [ 'device_id' ]]);
2019-07-18 08:36:02 -05:00
}
}
}
/**
* Run external transports
* @ param array $obj Alert - Array
* @ return void
*/
public function extTransports ( $obj )
{
2020-09-21 14:54:51 +02:00
$type = new Template ;
2019-07-18 08:36:02 -05:00
// If alert transport mapping exists, override the default transports
$transport_maps = AlertUtil :: getAlertTransports ( $obj [ 'alert_id' ]);
2020-09-21 14:54:51 +02:00
if ( ! $transport_maps ) {
2019-07-18 08:36:02 -05:00
$transport_maps = AlertUtil :: getDefaultAlertTransports ();
}
// alerting for default contacts, etc
2020-09-21 14:54:51 +02:00
if ( Config :: get ( 'alert.transports.mail' ) === true && ! empty ( $obj [ 'contacts' ])) {
2019-07-18 08:36:02 -05:00
$transport_maps [] = [
'transport_id' => null ,
'transport_type' => 'mail' ,
'opts' => $obj ,
];
}
foreach ( $transport_maps as $item ) {
2020-09-21 14:54:51 +02:00
$class = 'LibreNMS\\Alert\\Transport\\' . ucfirst ( $item [ 'transport_type' ]);
2019-07-18 08:36:02 -05:00
if ( class_exists ( $class )) {
//FIXME remove Deprecated transport
$transport_title = " Transport { $item [ 'transport_type' ] } " ;
$obj [ 'transport' ] = $item [ 'transport_type' ];
$obj [ 'transport_name' ] = $item [ 'transport_name' ];
2020-09-21 14:54:51 +02:00
$obj [ 'alert' ] = new AlertData ( $obj );
$obj [ 'title' ] = $type -> getTitle ( $obj );
2019-07-18 08:36:02 -05:00
$obj [ 'alert' ][ 'title' ] = $obj [ 'title' ];
2020-09-21 14:54:51 +02:00
$obj [ 'msg' ] = $type -> getBody ( $obj );
2019-07-18 08:36:02 -05:00
c_echo ( " :: $transport_title => " );
2019-09-04 21:13:32 -05:00
try {
$instance = new $class ( $item [ 'transport_id' ]);
$tmp = $instance -> deliverAlert ( $obj , $item [ 'opts' ]);
$this -> alertLog ( $tmp , $obj , $obj [ 'transport' ]);
} catch ( \Exception $e ) {
$this -> alertLog ( $e , $obj , $obj [ 'transport' ]);
}
2019-07-18 08:36:02 -05:00
unset ( $instance );
echo PHP_EOL ;
}
}
if ( count ( $transport_maps ) === 0 ) {
echo 'No configured transports' ;
}
}
// Log alert event
public function alertLog ( $result , $obj , $transport )
{
$prefix = [
2020-05-24 04:14:36 +02:00
AlertState :: RECOVERED => " recovery " ,
2020-09-21 14:54:51 +02:00
AlertState :: ACTIVE => $obj [ 'severity' ] . " alert " ,
AlertState :: ACKNOWLEDGED => " acknowledgment " ,
2019-07-18 08:36:02 -05:00
];
$prefix [ 3 ] = & $prefix [ 0 ];
$prefix [ 4 ] = & $prefix [ 0 ];
2020-06-08 10:56:45 +02:00
if ( $obj [ 'state' ] == AlertState :: RECOVERED ) {
$severity = Alert :: OK ;
} elseif ( $obj [ 'state' ] == AlertState :: ACTIVE ) {
2020-09-21 14:54:51 +02:00
$severity = Alert :: SEVERITIES [ $obj [ 'severity' ]] ? ? Alert :: UNKNOWN ;
2020-06-08 10:56:45 +02:00
} elseif ( $obj [ 'state' ] == AlertState :: ACKNOWLEDGED ) {
$severity = Alert :: NOTICE ;
} else {
$severity = Alert :: UNKNOWN ;
}
2019-07-18 08:36:02 -05:00
if ( $result === true ) {
echo 'OK' ;
2020-06-08 10:56:45 +02:00
Log :: event ( 'Issued ' . $prefix [ $obj [ 'state' ]] . " for rule ' " . $obj [ 'name' ] . " ' to transport ' " . $transport . " ' " , $obj [ 'device_id' ], 'alert' , $severity );
2019-07-18 08:36:02 -05:00
} elseif ( $result === false ) {
echo 'ERROR' ;
2020-06-08 10:56:45 +02:00
Log :: event ( 'Could not issue ' . $prefix [ $obj [ 'state' ]] . " for rule ' " . $obj [ 'name' ] . " ' to transport ' " . $transport . " ' " , $obj [ 'device_id' ], null , Alert :: ERROR );
2019-07-18 08:36:02 -05:00
} else {
echo " ERROR: $result\r\n " ;
2020-06-08 10:56:45 +02:00
Log :: event ( 'Could not issue ' . $prefix [ $obj [ 'state' ]] . " for rule ' " . $obj [ 'name' ] . " ' to transport ' " . $transport . " ' Error: " . $result , $obj [ 'device_id' ], 'error' , Alert :: ERROR );
2019-07-18 08:36:02 -05:00
}
}
/**
* Check if a device ' s all parent are down
* Returns true if all parents are down
* @ param int $device Device - ID
* @ return bool
*/
public function isParentDown ( $device )
{
2020-09-21 14:54:51 +02:00
$parent_count = dbFetchCell ( " SELECT count(*) from `device_relationships` WHERE `child_device_id` = ? " , [ $device ]);
if ( ! $parent_count ) {
2019-07-18 08:36:02 -05:00
return false ;
}
2020-09-21 14:54:51 +02:00
$down_parent_count = dbFetchCell ( " SELECT count(*) from devices as d LEFT JOIN devices_attribs as a ON d.device_id=a.device_id LEFT JOIN device_relationships as r ON d.device_id=r.parent_device_id WHERE d.status=0 AND d.ignore=0 AND d.disabled=0 AND r.child_device_id=? AND (d.status_reason='icmp' OR (a.attrib_type='override_icmp_disable' AND a.attrib_value=true)) " , [ $device ]);
2019-07-18 08:36:02 -05:00
if ( $down_parent_count == $parent_count ) {
return true ;
}
return false ;
}
}