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
2021-02-09 00:29:04 +01:00
* along with this program . If not , see < https :// www . gnu . org / licenses />.
2019-07-18 08:36:02 -05:00
*
* 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 ;
2022-01-04 17:40:42 -06:00
use App\Facades\DeviceCache ;
2024-04-18 09:57:01 -05:00
use App\Facades\Rrd ;
2023-05-24 08:24:05 -05:00
use App\Models\AlertTransport ;
2022-11-09 09:47:19 +01:00
use App\Models\Eventlog ;
2019-07-18 08:36:02 -05:00
use LibreNMS\Config ;
2020-05-24 04:14:36 +02:00
use LibreNMS\Enum\AlertState ;
2023-08-05 12:12:36 -05:00
use LibreNMS\Enum\Severity ;
2022-09-05 16:20:10 -05:00
use LibreNMS\Exceptions\AlertTransportDeliveryException ;
2022-01-04 17:40:42 -06:00
use LibreNMS\Polling\ConnectivityHelper ;
2024-04-18 09:57:01 -05:00
use LibreNMS\Util\Number ;
2019-07-18 08:36:02 -05:00
use LibreNMS\Util\Time ;
class RunAlerts
{
/**
* Populate variables
2021-09-10 20:09:53 +02:00
*
2021-09-08 23:35:56 +02:00
* @ param string $txt Text with variables
* @ 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 ;
$rep = false ;
if ( $tmp == 'key' || $tmp == 'value' ) {
$rep = '$' . $tmp ;
} else {
if ( strstr ( $tmp , '.' )) {
$tmp = explode ( '.' , $tmp , 2 );
$pre = '$' . $tmp [ 0 ];
$tmp = $tmp [ 1 ];
} else {
$pre = '$obj' ;
}
$rep = $pre . " [' " . str_replace ( '.' , " '][' " , $tmp ) . " '] " ;
if ( $wrap ) {
$rep = '{' . $rep . '}' ;
}
}
$txt = str_replace ( '%' . $orig , $rep , $txt );
}
2020-09-21 14:54:51 +02:00
2019-07-18 08:36:02 -05:00
return $txt ;
}
/**
* Describe Alert
2021-09-10 20:09:53 +02:00
*
2021-09-08 23:35:56 +02:00
* @ param array $alert Alert - Result from DB
2021-04-01 01:06:07 +02:00
* @ return array | bool | string
2019-07-18 08:36:02 -05:00
*/
public function describeAlert ( $alert )
{
$obj = [];
$i = 0 ;
2022-01-04 17:40:42 -06:00
$device = DeviceCache :: get ( $alert [ 'device_id' ]);
2020-09-21 14:54:51 +02:00
2022-01-04 17:40:42 -06:00
$obj [ 'hostname' ] = $device -> hostname ;
$obj [ 'sysName' ] = $device -> sysName ;
$obj [ 'display' ] = $device -> displayName ();
$obj [ 'sysDescr' ] = $device -> sysDescr ;
$obj [ 'sysContact' ] = $device -> sysContact ;
$obj [ 'os' ] = $device -> os ;
$obj [ 'type' ] = $device -> type ;
$obj [ 'ip' ] = $device -> ip ;
$obj [ 'hardware' ] = $device -> hardware ;
$obj [ 'version' ] = $device -> version ;
$obj [ 'serial' ] = $device -> serial ;
$obj [ 'features' ] = $device -> features ;
$obj [ 'location' ] = ( string ) $device -> location ;
$obj [ 'uptime' ] = $device -> uptime ;
2022-12-15 15:52:53 -06:00
$obj [ 'uptime_short' ] = Time :: formatInterval ( $device -> uptime , true );
2022-01-04 17:40:42 -06:00
$obj [ 'uptime_long' ] = Time :: formatInterval ( $device -> uptime );
$obj [ 'description' ] = $device -> purpose ;
$obj [ 'notes' ] = $device -> notes ;
2019-07-18 08:36:02 -05:00
$obj [ 'alert_notes' ] = $alert [ 'note' ];
2022-01-04 17:40:42 -06:00
$obj [ 'device_id' ] = $device -> device_id ;
2019-07-18 08:36:02 -05:00
$obj [ 'rule_id' ] = $alert [ 'rule_id' ];
2019-08-22 21:06:44 +02:00
$obj [ 'id' ] = $alert [ 'id' ];
2019-09-24 18:08:22 +07:00
$obj [ 'proc' ] = $alert [ 'proc' ];
2022-01-04 17:40:42 -06:00
$obj [ 'status' ] = $device -> status ;
$obj [ 'status_reason' ] = $device -> status_reason ;
if (( new ConnectivityHelper ( $device )) -> canPing ()) {
2024-04-18 09:57:01 -05:00
$last_ping = Rrd :: lastUpdate ( Rrd :: name ( $device -> hostname , 'icmp-perf' ));
if ( $last_ping ) {
$obj [ 'ping_timestamp' ] = $last_ping -> timestamp ;
$obj [ 'ping_loss' ] = Number :: calculatePercent ( $last_ping -> get ( 'xmt' ) - $last_ping -> get ( 'rcv' ), $last_ping -> get ( 'xmt' ));
$obj [ 'ping_min' ] = $last_ping -> get ( 'min' );
$obj [ 'ping_max' ] = $last_ping -> get ( 'max' );
$obj [ 'ping_avg' ] = $last_ping -> get ( 'avg' );
$obj [ 'debug' ] = 'unsupported' ;
}
2019-07-18 08:36:02 -05:00
}
$extra = $alert [ 'details' ];
$tpl = new Template ;
$template = $tpl -> getTemplate ( $obj );
2020-05-24 04:14:36 +02:00
if ( $alert [ 'state' ] >= AlertState :: ACTIVE ) {
2022-01-04 17:40:42 -06:00
$obj [ 'title' ] = $template -> title ? : 'Alert for device ' . $obj [ 'display' ] . ' - ' . ( $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 ) {
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 . '; ' ;
}
}
}
2022-12-15 15:52:53 -06:00
$obj [ 'elapsed' ] = Time :: formatInterval ( time () - strtotime ( $alert [ 'time_logged' ]), true ) ? : 'none' ;
2019-07-18 08:36:02 -05:00
if ( ! empty ( $extra [ 'diff' ])) {
$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-05-24 04:14:36 +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 = [];
if ( ! empty ( $id [ 'details' ])) {
$extra = json_decode ( gzuncompress ( $id [ 'details' ]), true );
}
// Reset count to 0 so alerts will continue
$extra [ 'count' ] = 0 ;
dbUpdate ([ 'details' => gzcompress ( json_encode ( $id [ 'details' ]), 9 )], 'alert_log' , 'id = ?' , [ $alert [ 'id' ]]);
2022-01-04 17:40:42 -06:00
$obj [ 'title' ] = $template -> title_rec ? : 'Device ' . $obj [ 'display' ] . ' recovered from ' . ( $alert [ 'name' ] ? : $alert [ 'rule' ]);
2022-12-15 15:52:53 -06:00
$obj [ 'elapsed' ] = Time :: formatInterval ( strtotime ( $alert [ 'time_logged' ]) - strtotime ( $id [ 'time_logged' ]), true ) ? : 'none' ;
2019-07-18 08:36:02 -05:00
$obj [ 'id' ] = $id [ 'id' ];
foreach ( $extra [ 'rule' ] as $incident ) {
$i ++ ;
$obj [ 'faults' ][ $i ] = $incident ;
2022-09-02 19:19:21 -05:00
$obj [ 'faults' ][ $i ][ 'string' ] = '' ;
2019-07-18 08:36:02 -05:00
foreach ( $incident as $k => $v ) {
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 . '; ' ;
}
}
}
} else {
return 'Unknown State' ;
} //end if
$obj [ 'builder' ] = $alert [ 'builder' ];
$obj [ 'uid' ] = $alert [ 'id' ];
$obj [ 'alert_id' ] = $alert [ 'alert_id' ];
$obj [ 'severity' ] = $alert [ 'severity' ];
2022-10-02 00:02:08 -05:00
$obj [ 'rule' ] = $alert [ 'rule' ] ? : json_encode ( $alert [ 'builder' ]);
2019-07-18 08:36:02 -05:00
$obj [ 'name' ] = $alert [ 'name' ];
$obj [ 'timestamp' ] = $alert [ 'time_logged' ];
$obj [ 'contacts' ] = $extra [ 'contacts' ];
$obj [ 'state' ] = $alert [ '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
$obj [ 'alerted' ] = $alert [ 'alerted' ];
2019-07-18 08:36:02 -05:00
$obj [ 'template' ] = $template ;
2020-09-21 14:54:51 +02:00
2019-07-18 08:36:02 -05:00
return $obj ;
}
public function clearStaleAlerts ()
{
2020-05-24 04:14:36 +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' ])) {
dbDelete ( 'alerts' , '`id` = ?' , [ $alert [ 'alert_id' ]]);
echo " Stale-alert: # { $alert [ 'alert_id' ] } " . PHP_EOL ;
}
}
}
/**
* Re - Validate Rule - Mappings
2021-09-10 20:09:53 +02:00
*
2021-09-08 23:35:56 +02:00
* @ param int $device_id Device - ID
* @ param int $rule Rule - ID
2019-07-18 08:36:02 -05:00
* @ return bool
*/
public function isRuleValid ( $device_id , $rule )
{
global $rulescache ;
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
2021-09-10 20:09:53 +02:00
*
2021-09-08 23:35:56 +02:00
* @ param array $alert
2019-07-18 08:36:02 -05:00
* @ return bool
*/
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' ];
$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 )) {
echo 'Issuing Alert-UID #' . $alert [ 'id' ] . '/' . $alert [ 'state' ] . ':' . PHP_EOL ;
2023-10-14 05:20:51 +02:00
if ( $alert [ 'state' ] != AlertState :: ACKNOWLEDGED || Config :: get ( 'alert.acknowledged' ) === true ) {
$this -> extTransports ( $obj );
}
2019-07-18 08:36:02 -05:00
echo " \r \n " ;
}
return true ;
}
/**
* Issue ACK notification
2021-09-10 20:09:53 +02:00
*
2019-07-18 08:36:02 -05:00
* @ return void
*/
public function runAcks ()
{
2020-05-24 04:14:36 +02:00
foreach ( $this -> loadAlerts ( 'alerts.state = ' . AlertState :: ACKNOWLEDGED . ' && alerts.open = ' . AlertState :: ACTIVE ) as $alert ) {
2023-11-14 04:56:06 +01:00
$rextra = json_decode ( $alert [ 'extra' ], true );
if ( ! isset ( $rextra [ 'acknowledgement' ])) {
// backwards compatibility check
$rextra [ 'acknowledgement' ] = true ;
}
if ( $rextra [ 'acknowledgement' ]) {
// Rule is set to send an acknowledgement alert
$this -> issueAlert ( $alert );
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
2021-09-10 20:09:53 +02:00
*
2019-07-18 08:36:02 -05:00
* @ return void
*/
public function runFollowUp ()
{
2020-05-24 04:14:36 +02:00
foreach ( $this -> loadAlerts ( 'alerts.state > ' . AlertState :: CLEAR . ' && alerts.open = 0' ) as $alert ) {
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' ]);
}
$chk = dbFetchRows ( $alert [ 'query' ], [ $alert [ 'device_id' ]]);
//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' ]);
}
}
2023-05-24 22:21:54 +02:00
$o = count ( $alert [ 'details' ][ 'rule' ]);
$n = count ( $chk );
2019-07-18 08:36:02 -05:00
$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 ;
if ( dbInsert ([
'state' => $state ,
'device_id' => $alert [ 'device_id' ],
'rule_id' => $alert [ 'rule_id' ],
'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' ]]);
}
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' ,
2019-07-18 08:36:02 -05:00
[ $alert_status [ 'device_id' ], $alert_status [ 'rule_id' ]]
);
if ( empty ( $alert [ 'rule_id' ]) || ! $this -> isRuleValid ( $alert_status [ 'device_id' ], $alert_status [ 'rule_id' ])) {
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' ];
if ( ! empty ( $alert [ 'details' ])) {
$alert [ 'details' ] = json_decode ( gzuncompress ( $alert [ 'details' ]), true );
}
$alert [ 'info' ] = json_decode ( $alert_status [ 'info' ], true );
$alerts [] = $alert ;
}
}
return $alerts ;
}
/**
* Run all alerts
2021-09-10 20:09:53 +02:00
*
2019-07-18 08:36:02 -05:00
* @ return void
*/
public function runAlerts ()
{
2020-05-24 04:14:36 +02:00
foreach ( $this -> loadAlerts ( 'alerts.state != ' . AlertState :: ACKNOWLEDGED . ' && alerts.open = 1' ) as $alert ) {
2019-07-18 08:36:02 -05:00
$noiss = false ;
$noacc = false ;
$updet = false ;
$rextra = json_decode ( $alert [ 'extra' ], true );
if ( ! isset ( $rextra [ 'recovery' ])) {
// backwards compatibility check
$rextra [ 'recovery' ] = true ;
}
$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' ]]);
if ( $chk [ 'alerted' ] == $alert [ 'state' ]) {
$noiss = true ;
}
$tolerence_window = Config :: get ( 'alert.tolerance_window' );
if ( ! empty ( $rextra [ 'count' ]) && empty ( $rextra [ 'interval' ])) {
// This check below is for compat-reasons
2020-05-24 04:14:36 +02:00
if ( ! empty ( $rextra [ 'delay' ]) && $alert [ 'state' ] != AlertState :: RECOVERED ) {
2019-07-18 08:36:02 -05:00
if (( time () - strtotime ( $alert [ 'time_logged' ]) + $tolerence_window ) < $rextra [ 'delay' ] || ( ! empty ( $alert [ 'details' ][ 'delay' ]) && ( time () - $alert [ 'details' ][ 'delay' ] + $tolerence_window ) < $rextra [ 'delay' ])) {
continue ;
} else {
$alert [ 'details' ][ 'delay' ] = time ();
$updet = true ;
}
}
2020-05-24 04:14:36 +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-05-24 04:14:36 +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 ;
}
if ( ! empty ( $rextra [ 'interval' ])) {
if ( ! empty ( $alert [ 'details' ][ 'interval' ]) && ( time () - $alert [ 'details' ][ 'interval' ] + $tolerence_window ) < $rextra [ 'interval' ]) {
continue ;
} else {
$alert [ 'details' ][ 'interval' ] = time ();
$updet = true ;
}
}
2020-05-24 04:14:36 +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 ) {
dbUpdate ([ 'details' => gzcompress ( json_encode ( $alert [ 'details' ]), 9 )], 'alert_log' , 'id = ?' , [ $alert [ 'id' ]]);
}
if ( ! empty ( $rextra [ 'mute' ])) {
echo 'Muted Alert-UID #' . $alert [ 'id' ] . " \r \n " ;
$noiss = true ;
}
if ( $this -> isParentDown ( $alert [ 'device_id' ])) {
$noiss = true ;
2023-08-05 12:12:36 -05:00
Eventlog :: log ( 'Skipped alerts because all parent devices are down' , $alert [ 'device_id' ], 'alert' , Severity :: Ok );
2019-07-18 08:36:02 -05:00
}
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 ;
}
if ( ! $noiss ) {
$this -> issueAlert ( $alert );
dbUpdate ([ 'alerted' => $alert [ 'state' ]], 'alerts' , 'rule_id = ? && device_id = ?' , [ $alert [ 'rule_id' ], $alert [ 'device_id' ]]);
}
if ( ! $noacc ) {
dbUpdate ([ 'open' => 0 ], 'alerts' , 'rule_id = ? && device_id = ?' , [ $alert [ 'rule_id' ], $alert [ 'device_id' ]]);
}
}
}
/**
* Run external transports
2021-09-10 20:09:53 +02:00
*
2021-09-08 23:35:56 +02:00
* @ param array $obj Alert - Array
2019-07-18 08:36:02 -05:00
* @ return void
*/
public function extTransports ( $obj )
{
$type = new Template ;
// If alert transport mapping exists, override the default transports
$transport_maps = AlertUtil :: getAlertTransports ( $obj [ 'alert_id' ]);
if ( ! $transport_maps ) {
$transport_maps = AlertUtil :: getDefaultAlertTransports ();
}
// alerting for default contacts, etc
if ( Config :: get ( 'alert.transports.mail' ) === true && ! empty ( $obj [ 'contacts' ])) {
$transport_maps [] = [
'transport_id' => null ,
'transport_type' => 'mail' ,
'opts' => $obj ,
];
}
foreach ( $transport_maps as $item ) {
2021-10-06 07:29:47 -05:00
$class = Transport :: getClass ( $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' ];
$obj [ 'alert' ] = new AlertData ( $obj );
$obj [ 'title' ] = $type -> getTitle ( $obj );
$obj [ 'alert' ][ 'title' ] = $obj [ 'title' ];
$obj [ 'msg' ] = $type -> getBody ( $obj );
c_echo ( " :: $transport_title => " );
2019-09-04 21:13:32 -05:00
try {
2023-05-24 08:24:05 -05:00
$instance = new $class ( AlertTransport :: find ( $item [ 'transport_id' ]));
2022-09-02 19:19:21 -05:00
$tmp = $instance -> deliverAlert ( $obj , $item [ 'opts' ] ? ? []);
2019-09-04 21:13:32 -05:00
$this -> alertLog ( $tmp , $obj , $obj [ 'transport' ]);
2022-09-05 16:20:10 -05:00
} catch ( AlertTransportDeliveryException $e ) {
2023-09-15 08:05:55 -05:00
Eventlog :: log ( $e -> getTraceAsString () . PHP_EOL . $e -> getMessage (), $obj [ 'device_id' ], 'alert' , Severity :: Error );
2022-09-05 16:20:10 -05:00
$this -> alertLog ( $e -> getMessage (), $obj , $obj [ 'transport' ]);
2019-09-04 21:13:32 -05:00
} 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' ,
AlertState :: ACTIVE => $obj [ 'severity' ] . ' alert' ,
AlertState :: ACKNOWLEDGED => 'acknowledgment' ,
2023-02-24 06:10:10 -06:00
AlertState :: WORSE => 'got worse' ,
AlertState :: BETTER => 'got better' ,
2019-07-18 08:36:02 -05:00
];
2020-06-08 10:56:45 +02:00
2023-08-05 12:12:36 -05:00
$severity = match ( $obj [ 'state' ]) {
AlertState :: RECOVERED => Severity :: Ok ,
AlertState :: ACTIVE => Severity :: tryFrom (( int ) $obj [ 'severity' ]) ? ? Severity :: Unknown ,
AlertState :: ACKNOWLEDGED => Severity :: Notice ,
default => Severity :: Unknown ,
};
2020-06-08 10:56:45 +02:00
2019-07-18 08:36:02 -05:00
if ( $result === true ) {
echo 'OK' ;
2022-11-09 09:47:19 +01:00
Eventlog :: log ( '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' ;
2023-08-05 12:12:36 -05:00
Eventlog :: log ( 'Could not issue ' . $prefix [ $obj [ 'state' ]] . " for rule ' " . $obj [ 'name' ] . " ' to transport ' " . $transport . " ' " , $obj [ 'device_id' ], null , Severity :: Error );
2019-07-18 08:36:02 -05:00
} else {
echo " ERROR: $result\r\n " ;
2023-08-05 12:12:36 -05:00
Eventlog :: log ( 'Could not issue ' . $prefix [ $obj [ 'state' ]] . " for rule ' " . $obj [ 'name' ] . " ' to transport ' " . $transport . " ' Error: " . $result , $obj [ 'device_id' ], 'error' , Severity :: 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
2021-09-10 20:09:53 +02:00
*
2021-09-08 23:35:56 +02:00
* @ param int $device Device - ID
2019-07-18 08:36:02 -05:00
* @ return bool
*/
public function isParentDown ( $device )
{
$parent_count = dbFetchCell ( 'SELECT count(*) from `device_relationships` WHERE `child_device_id` = ?' , [ $device ]);
if ( ! $parent_count ) {
return false ;
}
$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 ]);
if ( $down_parent_count == $parent_count ) {
return true ;
}
return false ;
}
}