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:
Tony Murray
2020-10-07 07:36:35 -05:00
committed by GitHub
parent e52531fba4
commit 1e4702fa4f
17 changed files with 235 additions and 560 deletions

View File

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

View File

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

View File

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

View File

@@ -28,32 +28,30 @@
*/
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') {
echo 'Start: ' . date('r') . "\r\n";
echo 'ClearStaleAlerts():' . PHP_EOL;
$alerts->clearStaleAlerts();
echo "RunFollowUp():\r\n";
$alerts->runFollowUp();
echo "RunAlerts():\r\n";
$alerts->runAlerts();
echo "RunAcks():\r\n";
$alerts->runAcks();
echo 'End : ' . date('r') . "\r\n";
$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();
echo "RunFollowUp():\r\n";
$alerts->runFollowUp();
echo "RunAlerts():\r\n";
$alerts->runAlerts();
echo "RunAcks():\r\n";
$alerts->runAcks();
echo 'End : ' . date('r') . "\r\n";
}
$alerts_lock->release();
}
$alerts_lock->release();

View File

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

@@ -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",

View File

@@ -26,7 +26,7 @@ return [
|
*/
'default' => env('CACHE_DRIVER', 'array'),
'default' => env('CACHE_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------

View File

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

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

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

View File

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

View File

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

View File

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

View File

@@ -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);
}
$purge_name = $table . '_purge';
$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;
}
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;
$lock->release();
return -1;
return 0;
}
return 0;
return -1;
}
/**

View File

@@ -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;
$schemaLock->release();
}

View File

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

View File

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