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:
Tony Murray
2017-11-24 03:37:52 -06:00
committed by Neil Lathwood
parent 9c0a74debb
commit 2e73b75297
13 changed files with 603 additions and 230 deletions

View 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
{
}

View File

@@ -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;
}
}

View 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);
}

View File

@@ -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
View 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.
}
}

View 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);
}
}
}

View File

@@ -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
View File

@@ -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') {

View File

@@ -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)

View File

@@ -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']) {

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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');
}
}