mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Support multiple daily process locking backends with distributed polling (#11896)
* Implement locks in the file cache * Replace custom locks * implement restore lock Used when re-hydrating * remove legacy use statements * Add class descriptions * Fix style * Default to database cache driver * missed cache_locks table prevent chicken-egg issue * style fixes * Remove custom file lock implementation * missed items from file cache * Update schema definition hmm, other schema noise must be from manual modification as this is generated from a freshly migrated db. * require predis, it is pure python, so no harm in adding * and set predis as the default client
This commit is contained in:
@@ -1,58 +0,0 @@
|
||||
<?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/>.
|
||||
*
|
||||
* @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);
|
||||
}
|
@@ -1,134 +0,0 @@
|
||||
<?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/>.
|
||||
*
|
||||
* @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 FileLock implements Lock
|
||||
{
|
||||
private $name;
|
||||
private $file;
|
||||
/**
|
||||
* @var resource | false
|
||||
*/
|
||||
private $handle;
|
||||
|
||||
private $acquired = false;
|
||||
|
||||
private function __construct($lock_name)
|
||||
{
|
||||
$install_dir = Config::get('install_dir');
|
||||
|
||||
$this->name = $lock_name;
|
||||
$this->file = "$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 (is_resource($this->handle)) {
|
||||
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.
|
||||
}
|
||||
}
|
@@ -1,145 +0,0 @@
|
||||
<?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/>.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
14
alerts.php
14
alerts.php
@@ -28,22 +28,20 @@
|
||||
*/
|
||||
|
||||
use LibreNMS\Alert\RunAlerts;
|
||||
use LibreNMS\Util\FileLock;
|
||||
|
||||
$init_modules = ['alerts', 'laravel'];
|
||||
require __DIR__ . '/includes/init.php';
|
||||
|
||||
$options = getopt('d::');
|
||||
|
||||
$alerts_lock = FileLock::lockOrDie('alerts');
|
||||
|
||||
$alerts = new RunAlerts();
|
||||
|
||||
if (set_debug(isset($options['d']))) {
|
||||
echo "DEBUG!\n";
|
||||
}
|
||||
|
||||
if (! defined('TEST') && \LibreNMS\Config::get('alert.disable') != 'true') {
|
||||
$alerts_lock = Cache::lock('alerts');
|
||||
if ($alerts_lock->get()) {
|
||||
$alerts = new RunAlerts();
|
||||
if (! defined('TEST') && \LibreNMS\Config::get('alert.disable') != 'true') {
|
||||
echo 'Start: ' . date('r') . "\r\n";
|
||||
echo 'ClearStaleAlerts():' . PHP_EOL;
|
||||
$alerts->clearStaleAlerts();
|
||||
@@ -54,6 +52,6 @@ if (! defined('TEST') && \LibreNMS\Config::get('alert.disable') != 'true') {
|
||||
echo "RunAcks():\r\n";
|
||||
$alerts->runAcks();
|
||||
echo 'End : ' . date('r') . "\r\n";
|
||||
}
|
||||
$alerts_lock->release();
|
||||
}
|
||||
|
||||
$alerts_lock->release();
|
||||
|
@@ -45,24 +45,25 @@
|
||||
"ezyang/htmlpurifier": "^4.8",
|
||||
"fico7489/laravel-pivot": "^3.0",
|
||||
"fideloper/proxy": "^4.2",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^6.3",
|
||||
"influxdb/influxdb-php": "^1.14",
|
||||
"laravel/framework": "^7.11",
|
||||
"laravel/tinker": "^2.0",
|
||||
"laravel/ui": "^2.0",
|
||||
"librenms/laravel-vue-i18n-generator": "^0.1.46",
|
||||
"oriceon/toastr-5-laravel": "dev-master",
|
||||
"pear/console_color2": "^0.1",
|
||||
"pear/console_table": "^1.3",
|
||||
"php-amqplib/php-amqplib": "^2.0",
|
||||
"phpmailer/phpmailer": "~6.0",
|
||||
"predis/predis": "^1.1",
|
||||
"rmccue/requests": "^1.7",
|
||||
"symfony/yaml": "^4.0",
|
||||
"tecnickcom/tcpdf": "~6.2.0",
|
||||
"tightenco/ziggy": "^0.8.0",
|
||||
"wpb/string-blade-compiler": "dev-laravel-7-and-autoload-blade-custom-directives",
|
||||
"xjtuwangke/passwordhash": "dev-master",
|
||||
"laravel/ui": "^2.0",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^6.3"
|
||||
"xjtuwangke/passwordhash": "dev-master"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.2",
|
||||
|
77
composer.lock
generated
77
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a8236087c5d8af2cdaaaa6201cd402e1",
|
||||
"content-hash": "4389f49396c63fa97f39a7fb849f9312",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amenadiel/jpgraph",
|
||||
@@ -2866,6 +2866,81 @@
|
||||
],
|
||||
"time": "2020-09-08T04:24:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"version": "v1.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/predis/predis.git",
|
||||
"reference": "9930e933c67446962997b05201c69c2319bf26de"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/predis/predis/zipball/9930e933c67446962997b05201c69c2319bf26de",
|
||||
"reference": "9930e933c67446962997b05201c69c2319bf26de",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"cweagans/composer-patches": "^1.6",
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Allows access to Webdis when paired with phpiredis",
|
||||
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"composer-exit-on-patch-failure": true,
|
||||
"patches": {
|
||||
"phpunit/phpunit-mock-objects": {
|
||||
"Fix PHP 7 and 8 compatibility": "./tests/phpunit_mock_objects.patch"
|
||||
},
|
||||
"phpunit/phpunit": {
|
||||
"Fix PHP 7 compatibility": "./tests/phpunit_php7.patch",
|
||||
"Fix PHP 8 compatibility": "./tests/phpunit_php8.patch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Predis\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniele Alessandri",
|
||||
"email": "suppakilla@gmail.com",
|
||||
"homepage": "http://clorophilla.net",
|
||||
"role": "Creator & Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Till Krüss",
|
||||
"homepage": "https://till.im",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "Flexible and feature-complete Redis client for PHP and HHVM",
|
||||
"homepage": "http://github.com/predis/predis",
|
||||
"keywords": [
|
||||
"nosql",
|
||||
"predis",
|
||||
"redis"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/tillkruss",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-09-11T19:18:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
"version": "1.0.0",
|
||||
|
@@ -26,7 +26,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_DRIVER', 'array'),
|
||||
'default' => env('CACHE_DRIVER', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@@ -155,7 +155,7 @@ return [
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
'client' => env('REDIS_CLIENT', 'predis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
|
101
daily.php
101
daily.php
@@ -11,8 +11,6 @@ use App\Models\DeviceGroup;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use LibreNMS\Alert\AlertDB;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Util\MemcacheLock;
|
||||
use LibreNMS\Validations\Php;
|
||||
|
||||
$init_modules = ['alerts'];
|
||||
@@ -40,11 +38,8 @@ if ($options['f'] === 'update') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'rrd_purge') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('rrd_purge', 0, 86000);
|
||||
}
|
||||
|
||||
$lock = Cache::lock('rrd_purge', 86000);
|
||||
if ($lock->get()) {
|
||||
$rrd_purge = Config::get('rrd_purge');
|
||||
$rrd_dir = Config::get('rrd_dir');
|
||||
|
||||
@@ -56,17 +51,13 @@ if ($options['f'] === 'rrd_purge') {
|
||||
echo $purge;
|
||||
}
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'syslog') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('syslog_purge', 0, 86000);
|
||||
}
|
||||
$lock = Cache::lock('syslog_purge', 86000);
|
||||
if ($lock->get()) {
|
||||
$syslog_purge = Config::get('syslog_purge');
|
||||
|
||||
if (is_numeric($syslog_purge)) {
|
||||
@@ -90,9 +81,7 @@ if ($options['f'] === 'syslog') {
|
||||
$final_rows = $rows - $initial_rows;
|
||||
echo "Syslog cleared for entries over $syslog_purge days (about $final_rows rows)\n";
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,13 +118,9 @@ if ($options['f'] === 'device_perf') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'ports_purge') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('ports_purge', 0, 86000);
|
||||
}
|
||||
$ports_purge = Config::get('ports_purge');
|
||||
|
||||
if ($ports_purge) {
|
||||
if (Config::get('ports_purge')) {
|
||||
$lock = Cache::lock('syslog_purge', 86000);
|
||||
if ($lock->get()) {
|
||||
\App\Models\Port::query()->with(['device' => function ($query) {
|
||||
$query->select('device_id', 'hostname');
|
||||
}])->isDeleted()->chunk(100, function ($ports) {
|
||||
@@ -144,10 +129,8 @@ if ($options['f'] === 'ports_purge') {
|
||||
}
|
||||
});
|
||||
echo "All deleted ports now purged\n";
|
||||
$lock->release();
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,15 +208,10 @@ if ($options['f'] === 'handle_notifiable') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'notifications') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('notifications', 0, 86000);
|
||||
}
|
||||
|
||||
$lock = Cache::lock('notifications', 86000);
|
||||
if ($lock->get()) {
|
||||
post_notifications();
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,11 +275,8 @@ if ($options['f'] === 'alert_log') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'purgeusers') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('purgeusers', 0, 86000);
|
||||
}
|
||||
|
||||
$lock = Cache::lock('purgeusers', 86000);
|
||||
if ($lock->get()) {
|
||||
$purge = 0;
|
||||
if (is_numeric(\LibreNMS\Config::get('radius.users_purge')) && Config::get('auth_mechanism') === 'radius') {
|
||||
$purge = \LibreNMS\Config::get('radius.users_purge');
|
||||
@@ -319,18 +294,13 @@ if ($options['f'] === 'purgeusers') {
|
||||
echo "Removed users that haven't logged in for $purge days\n";
|
||||
}
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'refresh_alert_rules') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('refresh_alert_rules', 0, 86000);
|
||||
}
|
||||
|
||||
$lock = Cache::lock('refresh_alert_rules', 86000);
|
||||
if ($lock->get()) {
|
||||
echo 'Refreshing alert rules queries' . PHP_EOL;
|
||||
$rules = dbFetchRows('SELECT `id`, `rule`, `builder`, `extra` FROM `alert_rules`');
|
||||
foreach ($rules as $rule) {
|
||||
@@ -343,18 +313,13 @@ if ($options['f'] === 'refresh_alert_rules') {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'refresh_device_groups') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('refresh_device_groups', 0, 86000);
|
||||
}
|
||||
|
||||
$lock = Cache::lock('refresh_device_groups', 86000);
|
||||
if ($lock->get()) {
|
||||
echo 'Refreshing device group table relationships' . PHP_EOL;
|
||||
DeviceGroup::all()->each(function ($deviceGroup) {
|
||||
if ($deviceGroup->type == 'dynamic') {
|
||||
@@ -363,9 +328,7 @@ if ($options['f'] === 'refresh_device_groups') {
|
||||
$deviceGroup->save();
|
||||
}
|
||||
});
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,14 +343,10 @@ if ($options['f'] === 'notify') {
|
||||
}
|
||||
|
||||
if ($options['f'] === 'peeringdb') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('peeringdb', 0, 86000);
|
||||
}
|
||||
$lock = Cache::lock('peeringdb', 86000);
|
||||
if ($lock->get()) {
|
||||
cache_peeringdb();
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,10 +358,8 @@ if ($options['f'] === 'refresh_os_cache') {
|
||||
if ($options['f'] === 'recalculate_device_dependencies') {
|
||||
// fix broken dependency max_depth calculation in case things weren't done though eloquent
|
||||
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('recalculate_device_dependencies', 0, 86000);
|
||||
}
|
||||
$lock = Cache::lock('recalculate_device_dependencies', 86000);
|
||||
if ($lock->get()) {
|
||||
\LibreNMS\DB\Eloquent::boot();
|
||||
|
||||
// update all root nodes and recurse, chunk so we don't blow up
|
||||
@@ -416,8 +373,6 @@ if ($options['f'] === 'recalculate_device_dependencies') {
|
||||
|
||||
$devices->each($recurse);
|
||||
});
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
31
database/migrations/2020_09_18_223431_create_cache_table.php
Normal file
31
database/migrations/2020_09_18_223431_create_cache_table.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateCacheTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('cache', function ($table) {
|
||||
$table->string('key')->unique();
|
||||
$table->text('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateCacheLocksTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('cache_locks', function ($table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
}
|
@@ -8,9 +8,6 @@
|
||||
*
|
||||
* @copyright (C) 2006 - 2012 Adam Armstrong
|
||||
*/
|
||||
|
||||
use LibreNMS\Util\FileLock;
|
||||
|
||||
$init_modules = ['discovery'];
|
||||
require __DIR__ . '/includes/init.php';
|
||||
|
||||
@@ -33,7 +30,7 @@ if (isset($options['h'])) {
|
||||
$where = ' ';
|
||||
$doing = 'all';
|
||||
} elseif ($options['h'] == 'new') {
|
||||
$new_discovery_lock = FileLock::lockOrDie('new-discovery');
|
||||
$new_discovery_lock = Cache::lock('new-discovery', 300);
|
||||
$where = 'AND `last_discovered` IS NULL';
|
||||
$doing = 'new';
|
||||
} elseif ($options['h']) {
|
||||
@@ -138,7 +135,7 @@ if ($discovered_devices) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($doing === 'new') {
|
||||
if (isset($new_discovery_lock)) {
|
||||
$new_discovery_lock->release();
|
||||
}
|
||||
|
||||
|
@@ -74,7 +74,7 @@ DB_PASSWORD=
|
||||
|
||||
## Distributed Polling Configuration
|
||||
|
||||
Once you have your Redis database set up, configure it in the .env file on each node.
|
||||
Once you have your Redis database set up, configure it in the .env file on each node. Configure the redis cache driver for distributed locking.
|
||||
|
||||
```dotenv
|
||||
REDIS_HOST=127.0.0.1
|
||||
@@ -86,6 +86,8 @@ REDIS_SENTINEL_SERVICE=myservice
|
||||
REDIS_DB=0
|
||||
#REDIS_PASSWORD=
|
||||
#REDIS_TIMEOUT=60
|
||||
|
||||
CACHE_DRIVER=redis
|
||||
```
|
||||
|
||||
## Basic Configuration
|
||||
|
@@ -16,13 +16,11 @@ use LibreNMS\Exceptions\HostIpExistsException;
|
||||
use LibreNMS\Exceptions\HostUnreachableException;
|
||||
use LibreNMS\Exceptions\HostUnreachablePingException;
|
||||
use LibreNMS\Exceptions\InvalidPortAssocModeException;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Exceptions\SnmpVersionUnsupportedException;
|
||||
use LibreNMS\Fping;
|
||||
use LibreNMS\Modules\Core;
|
||||
use LibreNMS\Util\IPv4;
|
||||
use LibreNMS\Util\IPv6;
|
||||
use LibreNMS\Util\MemcacheLock;
|
||||
use LibreNMS\Util\Time;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Symfony\Component\Process\Process;
|
||||
@@ -2334,12 +2332,9 @@ function get_device_oid_limit($device)
|
||||
*/
|
||||
function lock_and_purge($table, $sql)
|
||||
{
|
||||
try {
|
||||
$purge_name = $table . '_purge';
|
||||
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock($purge_name, 0, 86000);
|
||||
}
|
||||
$lock = Cache::lock($purge_name, 86000);
|
||||
if ($lock->get()) {
|
||||
$purge_days = Config::get($purge_name);
|
||||
|
||||
$name = str_replace('_', ' ', ucfirst($table));
|
||||
@@ -2348,13 +2343,12 @@ function lock_and_purge($table, $sql)
|
||||
echo "$name cleared for entries over $purge_days days\n";
|
||||
}
|
||||
}
|
||||
$lock->release();
|
||||
|
||||
return 0;
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2369,24 +2363,21 @@ function lock_and_purge_query($table, $sql, $msg)
|
||||
{
|
||||
$purge_name = $table . '_purge';
|
||||
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock($purge_name, 0, 86000);
|
||||
}
|
||||
$purge_duration = Config::get($purge_name);
|
||||
if (! (is_numeric($purge_duration) && $purge_duration > 0)) {
|
||||
return -2;
|
||||
}
|
||||
try {
|
||||
$lock = Cache::lock($purge_name, 86000);
|
||||
if ($lock->get()) {
|
||||
if (dbQuery($sql, [$purge_duration])) {
|
||||
printf($msg, $purge_duration);
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
|
||||
return -1;
|
||||
}
|
||||
$lock->release();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -23,12 +23,6 @@
|
||||
* @copyright 2017-2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
use LibreNMS\Util\FileLock;
|
||||
use LibreNMS\Util\MemcacheLock;
|
||||
|
||||
if (! isset($init_modules) && php_sapi_name() == 'cli') {
|
||||
// Not called from within discovery, let's load up the necessary stuff.
|
||||
$init_modules = [];
|
||||
@@ -37,15 +31,13 @@ if (! isset($init_modules) && php_sapi_name() == 'cli') {
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
// make sure the cache_locks table exists before attempting to use a db lock
|
||||
if (config('cache.default') == 'database' && ! \Schema::hasTable('cache_locks')) {
|
||||
$skip_schema_lock = true;
|
||||
}
|
||||
|
||||
$schemaLock = Cache::lock('schema', 86000);
|
||||
if (! empty($skip_schema_lock) || $schemaLock->get()) {
|
||||
$db_rev = get_db_schema();
|
||||
|
||||
$migrate_opts = ['--force' => true, '--ansi' => true];
|
||||
@@ -107,10 +99,5 @@ try {
|
||||
echo Artisan::output();
|
||||
}
|
||||
|
||||
if (isset($schemaLock)) {
|
||||
$schemaLock->release();
|
||||
}
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
$return = 1;
|
||||
}
|
||||
|
@@ -199,9 +199,8 @@ availability:
|
||||
- { Field: availability_perc, Type: 'decimal(9,6)', 'Null': false, Extra: '', Default: '0.000000' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [availability_id], Unique: true, Type: BTREE }
|
||||
availability_device_id_index: { Name: availability_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
|
||||
availability_device_id_duration_unique: { Name: availability_device_id_duration_unique, Columns: [device_id, duration], Unique: true, Type: BTREE }
|
||||
|
||||
availability_device_id_index: { Name: availability_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
|
||||
bgpPeers:
|
||||
Columns:
|
||||
- { Field: bgpPeer_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||
@@ -339,6 +338,20 @@ bill_port_counters:
|
||||
- { Field: bill_id, Type: 'int unsigned', 'Null': false, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [port_id, bill_id], Unique: true, Type: BTREE }
|
||||
cache:
|
||||
Columns:
|
||||
- { Field: key, Type: varchar(255), 'Null': false, Extra: '' }
|
||||
- { Field: value, Type: text, 'Null': false, Extra: '' }
|
||||
- { Field: expiration, Type: int, 'Null': false, Extra: '' }
|
||||
Indexes:
|
||||
cache_key_unique: { Name: cache_key_unique, Columns: [key], Unique: true, Type: BTREE }
|
||||
cache_locks:
|
||||
Columns:
|
||||
- { Field: key, Type: varchar(255), 'Null': false, Extra: '' }
|
||||
- { Field: owner, Type: varchar(255), 'Null': false, Extra: '' }
|
||||
- { Field: expiration, Type: int, 'Null': false, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [key], Unique: true, Type: BTREE }
|
||||
callback:
|
||||
Columns:
|
||||
- { Field: callback_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||
@@ -572,14 +585,6 @@ device_groups:
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
device_groups_name_unique: { Name: device_groups_name_unique, Columns: [name], Unique: true, Type: BTREE }
|
||||
device_outages:
|
||||
Columns:
|
||||
- { Field: device_id, Type: 'int unsigned', 'Null': false, Extra: '' }
|
||||
- { Field: going_down, Type: bigint, 'Null': false, Extra: '' }
|
||||
- { Field: up_again, Type: bigint, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
device_outages_device_id_index: { Name: device_outages_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
|
||||
device_outages_device_id_going_down_unique: { Name: device_outages_device_id_going_down_unique, Columns: [device_id, going_down], Unique: true, Type: BTREE }
|
||||
device_group_device:
|
||||
Columns:
|
||||
- { Field: device_group_id, Type: 'int unsigned', 'Null': false, Extra: '' }
|
||||
@@ -591,6 +596,14 @@ device_group_device:
|
||||
Constraints:
|
||||
device_group_device_device_group_id_foreign: { name: device_group_device_device_group_id_foreign, foreign_key: device_group_id, table: device_groups, key: id, extra: 'ON DELETE CASCADE' }
|
||||
device_group_device_device_id_foreign: { name: device_group_device_device_id_foreign, foreign_key: device_id, table: devices, key: device_id, extra: 'ON DELETE CASCADE' }
|
||||
device_outages:
|
||||
Columns:
|
||||
- { Field: device_id, Type: 'int unsigned', 'Null': false, Extra: '' }
|
||||
- { Field: going_down, Type: bigint, 'Null': false, Extra: '' }
|
||||
- { Field: up_again, Type: bigint, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
device_outages_device_id_going_down_unique: { Name: device_outages_device_id_going_down_unique, Columns: [device_id, going_down], Unique: true, Type: BTREE }
|
||||
device_outages_device_id_index: { Name: device_outages_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
|
||||
device_perf:
|
||||
Columns:
|
||||
- { Field: id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||
|
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* LockTest.php
|
||||
*
|
||||
* Test Locking functionality.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* @link http://librenms.org
|
||||
* @copyright 2017 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Tests;
|
||||
|
||||
use LibreNMS\Util\FileLock;
|
||||
|
||||
class LockTest extends TestCase
|
||||
{
|
||||
public function testFileLock()
|
||||
{
|
||||
$lock = FileLock::lock('tests');
|
||||
$lock->release();
|
||||
|
||||
$new_lock = FileLock::lock('tests');
|
||||
unset($new_lock);
|
||||
|
||||
FileLock::lock('tests');
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
public function testFileLockFail()
|
||||
{
|
||||
$lock = FileLock::lock('tests');
|
||||
|
||||
$this->expectException('LibreNMS\Exceptions\LockException');
|
||||
$failed_lock = FileLock::lock('tests');
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
public function testFileLockWait()
|
||||
{
|
||||
$lock = FileLock::lock('tests');
|
||||
|
||||
$start = microtime(true);
|
||||
$this->expectException('LibreNMS\Exceptions\LockException');
|
||||
$wait_lock = FileLock::lock('tests', 1);
|
||||
$this->assertGreaterThan(1, microtime(true) - $start, 'Lock did not wait.');
|
||||
|
||||
$lock->release();
|
||||
|
||||
$start = microtime(true);
|
||||
$wait_lock = FileLock::lock('tests', 5);
|
||||
$this->assertLessThan(1, microtime(true) - $start, 'Lock waited when it should not have');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user