mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
fix: Use memcached to lock daily processes on Distributed Pollers (#7735)
* fix: use memcached to lock daily processes on Distributed Pollers * All the locks!
This commit is contained in:
committed by
Neil Lathwood
parent
9c0a74debb
commit
2e73b75297
31
LibreNMS/Exceptions/LockException.php
Normal file
31
LibreNMS/Exceptions/LockException.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* LockException.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2017 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
namespace LibreNMS\Exceptions;
|
||||
|
||||
class LockException extends \Exception
|
||||
{
|
||||
}
|
@@ -1,105 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* LibreNMS
|
||||
*
|
||||
* This file is part of LibreNMS.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @subpackage FileLock
|
||||
* @copyright (C) 2017
|
||||
*
|
||||
*/
|
||||
|
||||
namespace LibreNMS;
|
||||
|
||||
class FileLock
|
||||
{
|
||||
private $name;
|
||||
private $file;
|
||||
/**
|
||||
* @var resource | false
|
||||
*/
|
||||
private $handle;
|
||||
|
||||
private $acquired = false;
|
||||
|
||||
private function __construct($lock_name)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$this->name = $lock_name;
|
||||
$this->file = "$config[install_dir]/.$lock_name.lock";
|
||||
$this->handle = fopen($this->file, "w+");
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock.
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
if (!$this->acquired) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->handle !== false) {
|
||||
flock($this->handle, LOCK_UN);
|
||||
fclose($this->handle);
|
||||
}
|
||||
if (file_exists($this->file)) {
|
||||
unlink($this->file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a lock name, try to acquire the lock.
|
||||
* On success return a FileLock object, or on failure return false.
|
||||
* @param string $lock_name Name of lock
|
||||
* @param int $timeout Try for this many seconds to see if we can acquire the lock. Default is no wait. A negative timeout will wait forever.
|
||||
* @return self|false
|
||||
*/
|
||||
public static function lock($lock_name, $timeout = 0)
|
||||
{
|
||||
$lock = new self($lock_name);
|
||||
if ($lock->handle === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to acquire the lock each second until we reach the timeout, once if timeout is 0, forever if timeout < 0
|
||||
for ($i = 0; $i <= $timeout || $timeout < 0; $i++) {
|
||||
if (flock($lock->handle, $timeout < 0 ? LOCK_EX : LOCK_EX | LOCK_NB)) {
|
||||
$lock->acquired = true;
|
||||
return $lock;
|
||||
}
|
||||
|
||||
if ($timeout) {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a lock name, try to acquire the lock, exiting on failure.
|
||||
* On success return a FileLock object.
|
||||
* @param string $lock_name Name of lock
|
||||
* @param int $timeout Try for this many seconds to see if we can acquire the lock. Default is no wait. A negative timeout will wait forever.
|
||||
* @return self
|
||||
*/
|
||||
public static function lockOrDie($lock_name, $timeout = 0)
|
||||
{
|
||||
$lock = self::lock($lock_name, $timeout);
|
||||
|
||||
if ($lock === false) {
|
||||
echo "Failed to acquire lock $lock_name, exiting\n";
|
||||
exit(1);
|
||||
}
|
||||
return $lock;
|
||||
}
|
||||
}
|
59
LibreNMS/Interfaces/Lock.php
Normal file
59
LibreNMS/Interfaces/Lock.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* Lock.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2017 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Interfaces;
|
||||
|
||||
interface Lock
|
||||
{
|
||||
/**
|
||||
* Release the lock.
|
||||
*/
|
||||
public function release();
|
||||
|
||||
/**
|
||||
* Given a lock name, try to acquire the lock.
|
||||
* On success return a Lock object, or on failure return false.
|
||||
* @param string $lock_name Name of lock
|
||||
* @param int $wait Try for this many seconds to see if we can acquire the lock. Default is no wait. A negative timeout will wait forever.
|
||||
* @return \LibreNMS\Interfaces\Lock|false
|
||||
*/
|
||||
public static function lock($lock_name, $wait = 0);
|
||||
|
||||
/**
|
||||
* Renew an expiring lock
|
||||
*
|
||||
* @param int $expiration number of seconds to hold lock for (null to cancel expiration)
|
||||
*/
|
||||
public function renew($expiration);
|
||||
|
||||
/**
|
||||
* Given a lock name, try to acquire the lock, exiting on failure.
|
||||
* On success return a Lock object.
|
||||
* @param string $lock_name Name of lock
|
||||
* @param int $timeout Try for this many seconds to see if we can acquire the lock. Default is no wait. A negative timeout will wait forever.
|
||||
* @return \LibreNMS\Interfaces\Lock
|
||||
*/
|
||||
public static function lockOrDie($lock_name, $timeout = 0);
|
||||
}
|
@@ -43,6 +43,11 @@ class Proc
|
||||
*/
|
||||
private $_synchronous;
|
||||
|
||||
/**
|
||||
* @var int|null hold the exit code, we can only get this on the first process_status after exit
|
||||
*/
|
||||
private $_exitcode = null;
|
||||
|
||||
/**
|
||||
* Create and run a new process
|
||||
* Most arguments match proc_open()
|
||||
@@ -217,7 +222,13 @@ class Proc
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return proc_get_status($this->_process);
|
||||
$status = proc_get_status($this->_process);
|
||||
|
||||
if ($status['running'] === false && is_null($this->_exitcode)) {
|
||||
$this->_exitcode = $status['exitcode'];
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +242,18 @@ class Proc
|
||||
return false;
|
||||
}
|
||||
$st = $this->getStatus();
|
||||
return isset($st['running']);
|
||||
return isset($st['running']) && $st['running'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the exit code from the process.
|
||||
* Will return null unless isRunning() or getStatus() has been run and returns false.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getExitCode()
|
||||
{
|
||||
return $this->_exitcode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
133
LibreNMS/Util/FileLock.php
Normal file
133
LibreNMS/Util/FileLock.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* FileLock.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2017 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Util;
|
||||
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Interfaces\Lock;
|
||||
|
||||
class FileLock implements Lock
|
||||
{
|
||||
private $name;
|
||||
private $file;
|
||||
/**
|
||||
* @var resource | false
|
||||
*/
|
||||
private $handle;
|
||||
|
||||
private $acquired = false;
|
||||
|
||||
private function __construct($lock_name)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$this->name = $lock_name;
|
||||
$this->file = "$config[install_dir]/.$lock_name.lock";
|
||||
$this->handle = fopen($this->file, "w+");
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock.
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
if (!$this->acquired) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->handle !== false) {
|
||||
flock($this->handle, LOCK_UN);
|
||||
fclose($this->handle);
|
||||
}
|
||||
if (file_exists($this->file)) {
|
||||
unlink($this->file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a lock name, try to acquire the lock.
|
||||
* On success return a Lock object, or on failure return false.
|
||||
* @param string $lock_name Name of lock
|
||||
* @param int $wait Try for this many seconds to see if we can acquire the lock. Default is no wait. A negative timeout will wait forever.
|
||||
* @param int $expire Expire is unsupported for file lock at this time.
|
||||
* @return Lock
|
||||
* @throws LockException
|
||||
*/
|
||||
public static function lock($lock_name, $wait = 0, $expire = 0)
|
||||
{
|
||||
$lock = new self($lock_name);
|
||||
if ($lock->handle === false) {
|
||||
throw new LockException("Failed to acquire lock $lock_name");
|
||||
}
|
||||
|
||||
// try to acquire the lock each second until we reach the timeout, once if timeout is 0, forever if timeout < 0
|
||||
for ($i = 0; $i <= $wait || $wait < 0; $i++) {
|
||||
if (flock($lock->handle, $wait < 0 ? LOCK_EX : LOCK_EX | LOCK_NB)) {
|
||||
$lock->acquired = true;
|
||||
return $lock;
|
||||
}
|
||||
|
||||
if ($wait) {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
throw new LockException("Failed to acquire lock $lock_name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a lock name, try to acquire the lock, exiting on failure.
|
||||
* On success return a Lock object.
|
||||
* @param string $lock_name Name of lock
|
||||
* @param int $timeout Try for this many seconds to see if we can acquire the lock. Default is no wait. A negative timeout will wait forever.
|
||||
* @return \LibreNMS\Interfaces\Lock|false
|
||||
*/
|
||||
public static function lockOrDie($lock_name, $timeout = 0)
|
||||
{
|
||||
try {
|
||||
return self::lock($lock_name, $timeout);
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renew an expiring lock
|
||||
*
|
||||
* @param int $expiration number of seconds to hold lock for (null to cancel expiration)
|
||||
*/
|
||||
public function renew($expiration)
|
||||
{
|
||||
echo "Unsupported";
|
||||
// TODO: Implement renew() method.
|
||||
}
|
||||
}
|
146
LibreNMS/Util/MemcacheLock.php
Normal file
146
LibreNMS/Util/MemcacheLock.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* MemcacheLock.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2017 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Util;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Interfaces\Lock;
|
||||
|
||||
class MemcacheLock implements Lock
|
||||
{
|
||||
private $namespace = 'lock';
|
||||
private $lock_name;
|
||||
private $poller_name;
|
||||
private $memcached;
|
||||
private $host;
|
||||
private $port;
|
||||
|
||||
private function __construct($lock_name)
|
||||
{
|
||||
if (!class_exists('Memcached')) {
|
||||
throw new LockException("Missing PHP Memcached extension, this is required for distributed polling.");
|
||||
}
|
||||
|
||||
// check all config vars or fallback
|
||||
$this->host = Config::get('distributed_poller_memcached_host', Config::get('memcached.host', 'localhost'));
|
||||
$this->port = Config::get('distributed_poller_memcached_port', Config::get('memcached.port', 11211));
|
||||
|
||||
$this->lock_name = "$this->namespace.$lock_name";
|
||||
$this->poller_name = Config::get('distributed_poller_name');
|
||||
$this->memcached = new \Memcached();
|
||||
$this->memcached->addServer($this->host, $this->port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a lock name, try to acquire the lock, exiting on failure.
|
||||
* On success return a Lock object.
|
||||
* @param string $lock_name Name of lock
|
||||
* @param int $timeout Try for this many seconds to see if we can acquire the lock. Default is no wait. A negative timeout will wait forever.
|
||||
* @return Lock
|
||||
*/
|
||||
public static function lockOrDie($lock_name, $timeout = 0)
|
||||
{
|
||||
try {
|
||||
return self::lock($lock_name, $timeout);
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a lock name, try to acquire the lock.
|
||||
* On success return a Lock object, or on failure return false.
|
||||
* @param string $lock_name Name of lock
|
||||
* @param int $wait Try for this many seconds to see if we can acquire the lock. Default is no wait. A negative timeout will wait forever.
|
||||
* @param int $expiration number of seconds to hold lock for, default is forever
|
||||
* @return Lock
|
||||
* @throws LockException
|
||||
*/
|
||||
public static function lock($lock_name, $wait = 0, $expiration = null)
|
||||
{
|
||||
$lock = new self($lock_name);
|
||||
|
||||
if (!$lock->isConnected()) {
|
||||
throw new LockException("Could not connect to memcached ($lock->host:$lock->port)");
|
||||
}
|
||||
|
||||
$owner = true;
|
||||
for ($i = 0; $i <= $wait || $wait < 0; $i++) {
|
||||
$owner = $lock->memcached->get($lock->lock_name);
|
||||
if ($owner == false) {
|
||||
break; // try to acquire the lock
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
if ($owner) {
|
||||
if ($owner == $lock->poller_name) {
|
||||
throw new LockException("This poller ($owner) already owns the lock: $lock->lock_name");
|
||||
}
|
||||
throw new LockException("Lock $lock->lock_name already acquired by $owner");
|
||||
}
|
||||
|
||||
$lock->memcached->set($lock->lock_name, $lock->poller_name, $expiration);
|
||||
$owner = $lock->memcached->get($lock->lock_name);
|
||||
if ($owner != $lock->poller_name) {
|
||||
throw new LockException("Another poller ($owner) has lock: $lock->lock_name");
|
||||
}
|
||||
|
||||
return $lock;
|
||||
}
|
||||
|
||||
private function isConnected()
|
||||
{
|
||||
return $this->memcached->getVersion() != false;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock.
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
$this->memcached->delete($this->lock_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renew an expiring lock
|
||||
*
|
||||
* @param int $expiration number of seconds to hold lock for (null to cancel expiration)
|
||||
*/
|
||||
public function renew($expiration)
|
||||
{
|
||||
$owner = $this->memcached->get($this->lock_name);
|
||||
if ($owner == $this->poller_name) {
|
||||
$this->memcached->set($this->lock_name, $this->poller_name, $expiration);
|
||||
}
|
||||
}
|
||||
}
|
@@ -25,12 +25,14 @@
|
||||
* @subpackage Alerts
|
||||
*/
|
||||
|
||||
use LibreNMS\Util\FileLock;
|
||||
|
||||
$init_modules = array('alerts');
|
||||
require __DIR__ . '/includes/init.php';
|
||||
|
||||
$options = getopt('d::');
|
||||
|
||||
$alerts_lock = \LibreNMS\FileLock::lockOrDie('alerts');
|
||||
$alerts_lock = FileLock::lockOrDie('alerts');
|
||||
|
||||
if (isset($options['d'])) {
|
||||
echo "DEBUG!\n";
|
||||
|
226
daily.php
226
daily.php
@@ -7,12 +7,14 @@
|
||||
*/
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Util\MemcacheLock;
|
||||
|
||||
$init_modules = array('alerts');
|
||||
require __DIR__ . '/includes/init.php';
|
||||
include_once __DIR__ . '/includes/notifications.php';
|
||||
|
||||
$options = getopt('f:d:o:t:r:');
|
||||
$options = getopt('df:o:t:r:');
|
||||
|
||||
if (isset($options['d'])) {
|
||||
echo "DEBUG\n";
|
||||
@@ -52,59 +54,72 @@ if ($options['f'] === 'check_php_ver') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'rrd_purge') {
|
||||
if (is_numeric($config['rrd_purge']) && $config['rrd_purge'] > 0) {
|
||||
$cmd = "find ".$config['rrd_dir']." -type f -mtime +".$config['rrd_purge']." -print -exec rm -f {} +";
|
||||
$purge = `$cmd`;
|
||||
if (!empty($purge)) {
|
||||
echo "Purged the following RRD files due to old age (over ".$config['rrd_purge']." days old):\n";
|
||||
echo $purge;
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('rrd_purge', 0, 86000);
|
||||
}
|
||||
|
||||
$rrd_purge = Config::get('rrd_purge');
|
||||
$rrd_dir = Config::get('rrd_dir');
|
||||
|
||||
if (is_numeric($rrd_purge) && $rrd_purge > 0) {
|
||||
$cmd = "find $rrd_dir -type f -mtime +$rrd_purge -print -exec rm -f {} +";
|
||||
$purge = `$cmd`;
|
||||
if (!empty($purge)) {
|
||||
echo "Purged the following RRD files due to old age (over $rrd_purge days old):\n";
|
||||
echo $purge;
|
||||
}
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'syslog') {
|
||||
if (is_numeric($config['syslog_purge'])) {
|
||||
$rows = (int)dbFetchCell('SELECT MIN(seq) FROM syslog');
|
||||
while (true) {
|
||||
$limit = dbFetchRow('SELECT seq FROM syslog WHERE seq >= ? ORDER BY seq LIMIT 1000,1', array($rows));
|
||||
if (empty($limit)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (dbDelete('syslog', 'seq >= ? AND seq < ? AND timestamp < DATE_SUB(NOW(), INTERVAL ? DAY)', array($rows, $limit, $config['syslog_purge'])) > 0) {
|
||||
$rows = $limit;
|
||||
echo 'Syslog cleared for entries over '.$config['syslog_purge']." days 1000 limit\n";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('syslog_purge', 0, 86000);
|
||||
}
|
||||
$syslog_purge = Config::get('syslog_purge');
|
||||
|
||||
dbDelete('syslog', 'seq >= ? AND timestamp < DATE_SUB(NOW(), INTERVAL ? DAY)', array($rows, $config['syslog_purge']));
|
||||
if (is_numeric($syslog_purge)) {
|
||||
$rows = (int)dbFetchCell('SELECT MIN(seq) FROM syslog');
|
||||
while (true) {
|
||||
$limit = dbFetchRow('SELECT seq FROM syslog WHERE seq >= ? ORDER BY seq LIMIT 1000,1', array($rows));
|
||||
if (empty($limit)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (dbDelete('syslog', 'seq >= ? AND seq < ? AND timestamp < DATE_SUB(NOW(), INTERVAL ? DAY)', array($rows, $limit, $syslog_purge)) > 0) {
|
||||
$rows = $limit;
|
||||
echo "Syslog cleared for entries over $syslog_purge days 1000 limit\n";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dbDelete('syslog', 'seq >= ? AND timestamp < DATE_SUB(NOW(), INTERVAL ? DAY)', array($rows, $syslog_purge));
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'eventlog') {
|
||||
if (is_numeric($config['eventlog_purge'])) {
|
||||
if (dbDelete('eventlog', 'datetime < DATE_SUB(NOW(), INTERVAL ? DAY)', array($config['eventlog_purge']))) {
|
||||
echo 'Eventlog cleared for entries over '.$config['eventlog_purge']." days\n";
|
||||
}
|
||||
}
|
||||
$ret = lock_and_purge('eventlog', 'datetime < DATE_SUB(NOW(), INTERVAL ? DAY)');
|
||||
exit($ret);
|
||||
}
|
||||
|
||||
if ($options['f'] === 'authlog') {
|
||||
if (is_numeric($config['authlog_purge'])) {
|
||||
if (dbDelete('authlog', 'datetime < DATE_SUB(NOW(), INTERVAL ? DAY)', array($config['authlog_purge']))) {
|
||||
echo 'Authlog cleared for entries over '.$config['authlog_purge']." days\n";
|
||||
}
|
||||
}
|
||||
$ret = lock_and_purge('authlog', 'datetime < DATE_SUB(NOW(), INTERVAL ? DAY)');
|
||||
exit($ret);
|
||||
}
|
||||
|
||||
if ($options['f'] === 'perf_times') {
|
||||
if (is_numeric($config['perf_times_purge'])) {
|
||||
if (dbDelete('perf_times', 'start < UNIX_TIMESTAMP(DATE_SUB(NOW(),INTERVAL ? DAY))', array($config['perf_times_purge']))) {
|
||||
echo 'Performance poller times cleared for entries over '.$config['perf_times_purge']." days\n";
|
||||
}
|
||||
}
|
||||
$ret = lock_and_purge('perf_times', 'start < UNIX_TIMESTAMP(DATE_SUB(NOW(),INTERVAL ? DAY))');
|
||||
exit($ret);
|
||||
}
|
||||
|
||||
if ($options['f'] === 'callback') {
|
||||
@@ -112,16 +127,14 @@ if ($options['f'] === 'callback') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'device_perf') {
|
||||
if (is_numeric($config['device_perf_purge'])) {
|
||||
if (dbDelete('device_perf', 'timestamp < DATE_SUB(NOW(),INTERVAL ? DAY)', array($config['device_perf_purge']))) {
|
||||
echo 'Device performance times cleared for entries over '.$config['device_perf_purge']." days\n";
|
||||
}
|
||||
}
|
||||
$ret = lock_and_purge('device_perf', 'timestamp < DATE_SUB(NOW(),INTERVAL ? DAY)');
|
||||
exit($ret);
|
||||
}
|
||||
|
||||
if ($options['f'] === 'handle_notifiable') {
|
||||
if ($options['t'] === 'update') {
|
||||
$title = 'Error: Daily update failed';
|
||||
$poller_name = Config::get('distributed_poller_name');
|
||||
|
||||
if ($options['r']) {
|
||||
// result was a success (1), remove the notification
|
||||
@@ -130,7 +143,8 @@ if ($options['f'] === 'handle_notifiable') {
|
||||
// result was a failure (0), create the notification
|
||||
new_notification(
|
||||
$title,
|
||||
'The daily update script (daily.sh) has failed. Please check output by hand. If you need assistance, '
|
||||
"The daily update script (daily.sh) has failed on $poller_name."
|
||||
. 'Please check output by hand. If you need assistance, '
|
||||
. 'visit the <a href="https://www.librenms.org/#support">LibreNMS Website</a> to find out how.',
|
||||
2,
|
||||
'daily.sh'
|
||||
@@ -140,66 +154,98 @@ if ($options['f'] === 'handle_notifiable') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'notifications') {
|
||||
post_notifications();
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('notifications', 0, 86000);
|
||||
}
|
||||
|
||||
post_notifications();
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'bill_data') {
|
||||
if (is_numeric($config['billing_data_purge']) && $config['billing_data_purge'] > 0) {
|
||||
# Deletes data older than XX months before the start of the last complete billing period
|
||||
$months = $config['billing_data_purge'];
|
||||
echo "Deleting billing data more than $months month before the last completed billing cycle\n";
|
||||
$sql = "DELETE bill_data
|
||||
FROM bill_data
|
||||
INNER JOIN (SELECT bill_id,
|
||||
SUBDATE(
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('syslog_purge', 0, 86000);
|
||||
}
|
||||
$billing_data_purge = Config::get('billing_data_purge');
|
||||
if (is_numeric($billing_data_purge) && $billing_data_purge > 0) {
|
||||
# Deletes data older than XX months before the start of the last complete billing period
|
||||
echo "Deleting billing data more than $billing_data_purge month before the last completed billing cycle\n";
|
||||
$sql = "DELETE bill_data
|
||||
FROM bill_data
|
||||
INNER JOIN (SELECT bill_id,
|
||||
SUBDATE(
|
||||
ADDDATE(
|
||||
subdate(curdate(), (day(curdate())-1)), # Start of this month
|
||||
bill_day - 1), # Billing anniversary
|
||||
INTERVAL IF(bill_day > DAY(curdate()), 1, 0) MONTH), # Deal with anniversary not yet happened this month
|
||||
INTERVAL ? MONTH) AS threshold # Adjust based on config threshold
|
||||
FROM bills) q
|
||||
ON bill_data.bill_id = q.bill_id AND bill_data.timestamp < q.threshold;";
|
||||
dbQuery($sql, array($months));
|
||||
SUBDATE(
|
||||
ADDDATE(
|
||||
subdate(curdate(), (day(curdate())-1)), # Start of this month
|
||||
bill_day - 1), # Billing anniversary
|
||||
INTERVAL IF(bill_day > DAY(curdate()), 1, 0) MONTH), # Deal with anniversary not yet happened this month
|
||||
INTERVAL ? MONTH) AS threshold # Adjust based on config threshold
|
||||
FROM bills) q
|
||||
ON bill_data.bill_id = q.bill_id AND bill_data.timestamp < q.threshold;";
|
||||
dbQuery($sql, array($billing_data_purge));
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'alert_log') {
|
||||
if (is_numeric($config['alert_log_purge']) && $config['alert_log_purge'] > 0) {
|
||||
if (dbDelete('alert_log', 'time_logged < DATE_SUB(NOW(),INTERVAL ? DAY)', array($config['alert_log_purge']))) {
|
||||
echo 'Alert log data cleared for entries over '.$config['alert_log_purge']." days\n";
|
||||
}
|
||||
}
|
||||
$ret = lock_and_purge('alert_log', 'time_logged < DATE_SUB(NOW(),INTERVAL ? DAY)');
|
||||
exit($ret);
|
||||
}
|
||||
|
||||
if ($options['f'] === 'purgeusers') {
|
||||
$purge = 0;
|
||||
if (is_numeric($config['radius']['users_purge']) && $config['auth_mechanism'] === 'radius') {
|
||||
$purge = $config['radius']['users_purge'];
|
||||
}
|
||||
if (is_numeric($config['active_directory']['users_purge']) && $config['auth_mechanism'] === 'active_directory') {
|
||||
$purge = $config['active_directory']['users_purge'];
|
||||
}
|
||||
if ($purge > 0) {
|
||||
foreach (dbFetchRows("SELECT DISTINCT(`user`) FROM `authlog` WHERE `datetime` >= DATE_SUB(NOW(), INTERVAL ? DAY)", array($purge)) as $user) {
|
||||
$users[] = $user['user'];
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('purgeusers', 0, 86000);
|
||||
}
|
||||
$del_users = '"'.implode('","', $users).'"';
|
||||
if (dbDelete('users', "username NOT IN ($del_users)", array($del_users))) {
|
||||
echo "Removed users that haven't logged in for $purge days";
|
||||
|
||||
$purge = 0;
|
||||
if (is_numeric($config['radius']['users_purge']) && $config['auth_mechanism'] === 'radius') {
|
||||
$purge = $config['radius']['users_purge'];
|
||||
}
|
||||
if (is_numeric($config['active_directory']['users_purge']) && $config['auth_mechanism'] === 'active_directory') {
|
||||
$purge = $config['active_directory']['users_purge'];
|
||||
}
|
||||
if ($purge > 0) {
|
||||
foreach (dbFetchRows("SELECT DISTINCT(`user`) FROM `authlog` WHERE `datetime` >= DATE_SUB(NOW(), INTERVAL ? DAY)", array($purge)) as $user) {
|
||||
$users[] = $user['user'];
|
||||
}
|
||||
$del_users = '"'.implode('","', $users).'"';
|
||||
if (dbDelete('users', "username NOT IN ($del_users)", array($del_users))) {
|
||||
echo "Removed users that haven't logged in for $purge days";
|
||||
}
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'refresh_alert_rules') {
|
||||
echo 'Refreshing alert rules queries' . PHP_EOL;
|
||||
$rules = dbFetchRows('SELECT `id`, `rule` FROM `alert_rules`');
|
||||
foreach ($rules as $rule) {
|
||||
$data['query'] = GenSQL($rule['rule']);
|
||||
if (!empty($data['query'])) {
|
||||
dbUpdate($data, 'alert_rules', 'id=?', array($rule['id']));
|
||||
unset($data);
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('refresh_alert_rules', 0, 86000);
|
||||
}
|
||||
|
||||
echo 'Refreshing alert rules queries' . PHP_EOL;
|
||||
$rules = dbFetchRows('SELECT `id`, `rule` FROM `alert_rules`');
|
||||
foreach ($rules as $rule) {
|
||||
$data['query'] = GenSQL($rule['rule']);
|
||||
if (!empty($data['query'])) {
|
||||
dbUpdate($data, 'alert_rules', 'id=?', array($rule['id']));
|
||||
unset($data);
|
||||
}
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +260,15 @@ if ($options['f'] === 'notify') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'peeringdb') {
|
||||
cache_peeringdb();
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('peeringdb', 0, 86000);
|
||||
}
|
||||
cache_peeringdb();
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'refresh_os_cache') {
|
||||
|
13
daily.sh
13
daily.sh
@@ -196,17 +196,8 @@ main () {
|
||||
fi
|
||||
fi
|
||||
|
||||
cnf=$(echo $(grep '\[.distributed_poller.\]' config.php | egrep -v -e '^//' -e '^#' | cut -d = -f 2 | sed 's/;//g'))
|
||||
if ((${BASH_VERSINFO[0]} < 4)); then
|
||||
cnf=`echo $cnf|tr [:upper:] [:lower:]`
|
||||
else
|
||||
cnf=${cnf,,}
|
||||
fi
|
||||
|
||||
if [[ -z "$cnf" ]] || [[ "$cnf" == "0" ]] || [[ "$cnf" == "false" ]]; then
|
||||
# Call ourself again in case above pull changed or added something to daily.sh
|
||||
${DAILY_SCRIPT} post-pull
|
||||
fi
|
||||
# Call ourself again in case above pull changed or added something to daily.sh
|
||||
${DAILY_SCRIPT} post-pull
|
||||
else
|
||||
case $arg in
|
||||
no-code-update)
|
||||
|
@@ -11,6 +11,8 @@
|
||||
* @copyright (C) 2006 - 2012 Adam Armstrong
|
||||
*/
|
||||
|
||||
use LibreNMS\Util\FileLock;
|
||||
|
||||
$init_modules = array('discovery');
|
||||
require __DIR__ . '/includes/init.php';
|
||||
|
||||
@@ -33,7 +35,7 @@ if (isset($options['h'])) {
|
||||
$where = ' ';
|
||||
$doing = 'all';
|
||||
} elseif ($options['h'] == 'new') {
|
||||
$new_discovery_lock = \LibreNMS\FileLock::lockOrDie('new-discovery');
|
||||
$new_discovery_lock = FileLock::lockOrDie('new-discovery');
|
||||
$where = 'AND `last_discovered` IS NULL';
|
||||
$doing = 'new';
|
||||
} elseif ($options['h']) {
|
||||
|
@@ -18,8 +18,10 @@ use LibreNMS\Exceptions\HostUnreachableException;
|
||||
use LibreNMS\Exceptions\HostUnreachablePingException;
|
||||
use LibreNMS\Exceptions\InvalidIpException;
|
||||
use LibreNMS\Exceptions\InvalidPortAssocModeException;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Exceptions\SnmpVersionUnsupportedException;
|
||||
use LibreNMS\Util\IP;
|
||||
use LibreNMS\Util\MemcacheLock;
|
||||
|
||||
function set_debug($debug)
|
||||
{
|
||||
@@ -2455,3 +2457,33 @@ function locate_binary($binary)
|
||||
|
||||
return $binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* If Distributed, create a lock, then purge the mysql table
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $sql
|
||||
* @return int exit code
|
||||
*/
|
||||
function lock_and_purge($table, $sql)
|
||||
{
|
||||
try {
|
||||
$purge_name = $table . '_purge';
|
||||
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock($purge_name, 0, 86000);
|
||||
}
|
||||
$purge_days = Config::get($purge_name);
|
||||
|
||||
$name = str_replace('_', ' ', ucfirst($table));
|
||||
if (is_numeric($purge_days)) {
|
||||
if (dbDelete($table, $sql, array($purge_days))) {
|
||||
echo "$name cleared for entries over $purge_days days\n";
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@@ -12,32 +12,38 @@
|
||||
* See COPYING for more details.
|
||||
*/
|
||||
|
||||
if (!isset($init_modules) && php_sapi_name() == 'cli') {
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Util\FileLock;
|
||||
use LibreNMS\Util\MemcacheLock;
|
||||
|
||||
global $database_link;
|
||||
|
||||
if (!isset($init_modules) && php_sapi_name() == 'cli') {
|
||||
// Not called from within discovery, let's load up the necessary stuff.
|
||||
$init_modules = array();
|
||||
require realpath(__DIR__ . '/../..') . '/includes/init.php';
|
||||
}
|
||||
|
||||
if (isset($skip_schema_lock) && $skip_schema_lock) {
|
||||
$schemaLock = true;
|
||||
} else {
|
||||
$schemaLock = \LibreNMS\FileLock::lock('schema', 30);
|
||||
}
|
||||
$return = 0;
|
||||
|
||||
if ($schemaLock === false) {
|
||||
echo "Failed to acquire lock, skipping schema update\n";
|
||||
$return = 1;
|
||||
} else {
|
||||
$return = 0;
|
||||
try {
|
||||
if (isset($skip_schema_lock) && !$skip_schema_lock) {
|
||||
if (Config::get('distributed_poller')) {
|
||||
$schemaLock = MemcacheLock::lock('schema', 30, 86000);
|
||||
} else {
|
||||
$schemaLock = FileLock::lock('schema', 30);
|
||||
}
|
||||
}
|
||||
|
||||
// only import build.sql to an empty database
|
||||
$tables = dbFetchRows("SHOW TABLES FROM {$config['db_name']}");
|
||||
$tables = dbFetchRows("SHOW TABLES FROM " . Config::get('db_name'));
|
||||
if (empty($tables)) {
|
||||
echo "-- Creating base database structure\n";
|
||||
$step = 0;
|
||||
$sql_fh = fopen('build.sql', 'r');
|
||||
if ($sql_fh === false) {
|
||||
echo 'ERROR: Cannot open SQL build script ' . $sql_file . PHP_EOL;
|
||||
echo 'ERROR: Cannot open SQL build script build.sql' . PHP_EOL;
|
||||
$return = 1;
|
||||
}
|
||||
|
||||
@@ -64,7 +70,7 @@ if ($schemaLock === false) {
|
||||
d_echo("DB Schema already up to date.\n");
|
||||
} else {
|
||||
// Set Database Character set and Collation
|
||||
dbQuery('ALTER DATABASE ? CHARACTER SET utf8 COLLATE utf8_unicode_ci;', array(array($config['db_name'])));
|
||||
dbQuery('ALTER DATABASE ? CHARACTER SET utf8 COLLATE utf8_unicode_ci;', array(array(Config::get('db_name'))));
|
||||
|
||||
$db_rev = get_db_schema();
|
||||
$insert = ($db_rev == 0); // if $db_rev == 0, insert the first update
|
||||
@@ -86,6 +92,7 @@ if ($schemaLock === false) {
|
||||
|
||||
if ($line[0] != '#') {
|
||||
if (!mysqli_query($database_link, $line)) {
|
||||
$return = 2;
|
||||
$err++;
|
||||
d_echo(mysqli_error($database_link) . PHP_EOL);
|
||||
}
|
||||
@@ -115,7 +122,10 @@ if ($schemaLock === false) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is_a($schemaLock, '\LibreNMS\FileLock')) {
|
||||
if (isset($schemaLock)) {
|
||||
$schemaLock->release();
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
$return = 1;
|
||||
}
|
||||
|
@@ -25,7 +25,8 @@
|
||||
|
||||
namespace LibreNMS\Tests;
|
||||
|
||||
use LibreNMS\FileLock;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Util\FileLock;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class LockTest extends TestCase
|
||||
@@ -33,40 +34,35 @@ class LockTest extends TestCase
|
||||
public function testFileLock()
|
||||
{
|
||||
$lock = FileLock::lock('tests');
|
||||
$this->assertNotFalse($lock, 'Failed to acquire initial lock!');
|
||||
$lock->release();
|
||||
|
||||
$new_lock = FileLock::lock('tests');
|
||||
$this->assertNotFalse($new_lock, 'Failed to release the lock with release()');
|
||||
unset($new_lock);
|
||||
|
||||
$this->assertNotFalse(FileLock::lock('tests'), 'Failed to remove lock when the lock object was destroyed');
|
||||
FileLock::lock('tests');
|
||||
}
|
||||
|
||||
public function testFileLockFail()
|
||||
{
|
||||
$lock = FileLock::lock('tests');
|
||||
$this->assertNotFalse($lock, 'Failed to acquire initial lock!');
|
||||
|
||||
$this->setExpectedException('LibreNMS\Exceptions\LockException');
|
||||
$failed_lock = FileLock::lock('tests');
|
||||
$this->assertFalse($failed_lock, 'Additional lock attempt did not fail');
|
||||
}
|
||||
|
||||
public function testFileLockWait()
|
||||
{
|
||||
$lock = FileLock::lock('tests');
|
||||
$this->assertNotFalse($lock, 'Failed to acquire initial lock!');
|
||||
|
||||
$start = microtime(true);
|
||||
$this->setExpectedException('LibreNMS\Exceptions\LockException');
|
||||
$wait_lock = FileLock::lock('tests', 1);
|
||||
$this->assertGreaterThan(1, microtime(true) - $start, 'Lock did not wait.');
|
||||
$this->assertFalse($wait_lock, 'Waiting lock attempt did not fail');
|
||||
|
||||
$lock->release();
|
||||
|
||||
$start = microtime(true);
|
||||
$wait_lock = FileLock::lock('tests', 5);
|
||||
$this->assertLessThan(1, microtime(true) - $start, 'Lock waited when it should not have');
|
||||
$this->assertNotFalse($wait_lock, 'Second wait lock did not succeed');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user