Oxidized update and Device remove (#13730)

* Oxidized + Device remove
Was just working on oxidized, but then to properly update nodes after delete, updated delete_device()

* revert dumb style changes

* baseline update and no DI there...

* Fix OS first load and device deletion missing tables
This commit is contained in:
Tony Murray 2022-01-29 21:09:05 -06:00 committed by GitHub
parent 29ff20d7eb
commit b6a8b602b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 315 additions and 184 deletions

View File

@ -125,7 +125,9 @@ class Core implements Module
];
// check yaml files
\LibreNMS\Util\OS::loadAllDefinitions();
$os_defs = Config::get('os');
foreach ($os_defs as $os => $def) {
if (isset($def['discovery']) && ! in_array($os, $generic_os)) {
if (self::discoveryIsSlow($def)) {

View File

@ -25,20 +25,21 @@
namespace App\ApiClients;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Http;
use LibreNMS\Util\Proxy;
class BaseApi
{
protected $base_uri;
private $client;
protected function getClient()
protected function getClient(): \Illuminate\Http\Client\PendingRequest
{
if (is_null($this->client)) {
$this->client = new Client([
'base_uri' => $this->base_uri,
'timeout' => 2,
]);
$this->client = Http::withOptions([
'proxy' => Proxy::forGuzzle($this->base_uri),
])->baseUrl($this->base_uri)
->timeout(3);
}
return $this->client;

View File

@ -26,6 +26,7 @@
namespace App\ApiClients;
use Exception;
use Illuminate\Http\Client\Response;
use LibreNMS\Config;
use LibreNMS\Interfaces\Geocoder;
@ -75,13 +76,9 @@ class BingApi extends BaseApi implements Geocoder
/**
* Checks if the request was a success
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param array $data decoded response data
* @return bool
*/
protected function checkResponse($response, $data)
protected function checkResponse(Response $response, array $data): bool
{
return $response->getStatusCode() == 200 && ! empty($data['resourceSets'][0]['resources']);
return $response->successful() && ! empty($data['resourceSets'][0]['resources']);
}
}

View File

@ -25,17 +25,14 @@
namespace App\ApiClients;
use Exception;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use LibreNMS\Config;
use Log;
trait GeocodingHelper
{
/**
* From BaseApi...
*
* @return \GuzzleHttp\Client
*/
abstract protected function getClient();
abstract protected function getClient(): PendingRequest;
/**
* Try to get the coordinates of a given address.
@ -56,7 +53,7 @@ trait GeocodingHelper
$options = $this->buildGeocodingOptions($address);
$response = $this->getClient()->get($this->geocoding_uri, $options);
$response_data = json_decode($response->getBody(), true);
$response_data = $response->json();
if ($this->checkResponse($response, $response_data)) {
return $this->parseLatLng($response_data);
} else {
@ -71,14 +68,10 @@ trait GeocodingHelper
/**
* Checks if the request was a success
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param array $data decoded response data
* @return bool
*/
protected function checkResponse($response, $data)
protected function checkResponse(Response $response, array $data): bool
{
return $response->getStatusCode() == 200;
return $response->successful();
}
/**

View File

@ -26,6 +26,7 @@
namespace App\ApiClients;
use Exception;
use Illuminate\Http\Client\Response;
use LibreNMS\Config;
use LibreNMS\Interfaces\Geocoder;
@ -89,15 +90,9 @@ class GoogleMapsApi extends BaseApi implements Geocoder
/**
* Checks if the request was a success
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param array $data decoded response data
* @return bool
*
* @throws Exception you may throw an Exception if validation fails
*/
protected function checkResponse($response, $data)
protected function checkResponse(Response $response, array $data): bool
{
return $response->getStatusCode() == 200 && $data['status'] == 'OK';
return $response->successful() && $data['status'] == 'OK';
}
}

View File

@ -26,6 +26,7 @@
namespace App\ApiClients;
use Exception;
use Illuminate\Http\Client\Response;
use LibreNMS\Config;
use LibreNMS\Interfaces\Geocoder;
@ -76,13 +77,9 @@ class MapquestApi extends BaseApi implements Geocoder
/**
* Checks if the request was a success
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param array $data decoded response data
* @return bool
*/
protected function checkResponse($response, $data)
protected function checkResponse(Response $response, array $data): bool
{
return $response->getStatusCode() == 200 && $data['info']['statuscode'] == 0;
return $response->successful() && $data['info']['statuscode'] == 0;
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* Oxidized.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 <https://www.gnu.org/licenses/>.
*
* @link https://www.librenms.org
*
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\ApiClients;
use LibreNMS\Config;
class Oxidized extends BaseApi
{
/**
* @var bool if Oxidized is enabled
*/
private $enabled;
public function __construct()
{
$this->base_uri = Config::get('oxidized.url');
$this->enabled = Config::get('oxidized.enabled') === true && $this->base_uri;
}
/**
* Ask oxidized to refresh the node list for the source (likely the LibreNMS API).
*/
public function reloadNodes(): void
{
if ($this->enabled && Config::get('oxidized.reload_nodes') === true) {
$this->getClient()->get('/reload.json');
}
}
/**
* Queues a hostname to be refreshed by Oxidized
*/
public function updateNode(string $hostname, string $msg, string $username = 'not_provided'): bool
{
if ($this->enabled) {
// Work around https://github.com/rack/rack/issues/337
$msg = str_replace('%', '', $msg);
return $this->getClient()
->put("/node/next/$hostname", ['user' => $username, 'msg' => $msg])
->successful();
}
return false;
}
}

View File

@ -77,8 +77,7 @@ class RipeApi extends BaseApi
private function makeApiCall(string $uri, array $options)
{
try {
$response = $this->getClient()->get($uri, $options);
$response_data = json_decode($response->getBody(), true);
$response_data = $this->getClient()->get($uri, $options)->json();
if (isset($response_data['status']) && $response_data['status'] == 'ok') {
return $response_data;
} else {

14
app/Models/AlertLog.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class AlertLog extends DeviceRelatedModel
{
use HasFactory;
public const UPDATED_AT = null;
public const CREATED_AT = 'time_logged';
protected $table = 'alert_log';
}

View File

@ -612,9 +612,9 @@ class Device extends BaseModel
return $this->hasMany(\App\Models\Alert::class, 'device_id');
}
public function attribs(): HasMany
public function alertLogs(): HasMany
{
return $this->hasMany(\App\Models\DeviceAttrib::class, 'device_id');
return $this->hasMany(\App\Models\AlertLog::class, 'device_id');
}
public function alertSchedules(): MorphToMany
@ -627,6 +627,16 @@ class Device extends BaseModel
return $this->hasMany(\App\Models\Application::class, 'device_id');
}
public function attribs(): HasMany
{
return $this->hasMany(\App\Models\DeviceAttrib::class, 'device_id');
}
public function availability(): HasMany
{
return $this->hasMany(\App\Models\Availability::class, 'device_id');
}
public function bgppeers(): HasMany
{
return $this->hasMany(\App\Models\BgpPeer::class, 'device_id');
@ -647,6 +657,11 @@ class Device extends BaseModel
return $this->hasMany(\App\Models\Component::class, 'device_id');
}
public function diskIo(): HasMany
{
return $this->hasMany(\App\Models\DiskIo::class, 'device_id');
}
public function hostResources(): HasMany
{
return $this->hasMany(HrDevice::class, 'device_id');
@ -662,6 +677,11 @@ class Device extends BaseModel
return $this->hasMany(EntPhysical::class, 'device_id');
}
public function entityState(): HasMany
{
return $this->hasMany(EntityState::class, 'device_id');
}
public function eventlogs(): HasMany
{
return $this->hasMany(\App\Models\Eventlog::class, 'device_id', 'device_id');
@ -692,11 +712,21 @@ class Device extends BaseModel
return $this->hasManyThrough(\App\Models\Ipv6Address::class, \App\Models\Port::class, 'device_id', 'port_id', 'device_id', 'port_id');
}
public function isisAdjacencies(): HasMany
{
return $this->hasMany(\App\Models\IsisAdjacency::class, 'device_id', 'device_id');
}
public function location(): BelongsTo
{
return $this->belongsTo(\App\Models\Location::class, 'location_id', 'id');
}
public function macs(): HasMany
{
return $this->hasMany(Ipv4Mac::class, 'device_id');
}
public function mefInfo(): HasMany
{
return $this->hasMany(MefInfo::class, 'device_id');
@ -707,6 +737,11 @@ class Device extends BaseModel
return $this->hasMany(\App\Models\MuninPlugin::class, 'device_id');
}
public function netscalerVservers(): HasMany
{
return $this->hasMany(NetscalerVserver::class, 'device_id');
}
public function ospfAreas(): HasMany
{
return $this->hasMany(\App\Models\OspfArea::class, 'device_id');
@ -727,16 +762,6 @@ class Device extends BaseModel
return $this->hasMany(\App\Models\OspfPort::class, 'device_id');
}
public function isisAdjacencies(): HasMany
{
return $this->hasMany(\App\Models\IsisAdjacency::class, 'device_id', 'device_id');
}
public function netscalerVservers(): HasMany
{
return $this->hasMany(NetscalerVserver::class, 'device_id');
}
public function packages(): HasMany
{
return $this->hasMany(\App\Models\Package::class, 'device_id', 'device_id');
@ -767,6 +792,21 @@ class Device extends BaseModel
return $this->hasMany(\App\Models\PortsNac::class, 'device_id', 'device_id');
}
public function portsStp(): HasMany
{
return $this->hasMany(\App\Models\PortStp::class, 'device_id', 'device_id');
}
public function portsVlan(): HasMany
{
return $this->hasMany(\App\Models\PortVlan::class, 'device_id', 'device_id');
}
public function processes(): HasMany
{
return $this->hasMany(\App\Models\Process::class, 'device_id');
}
public function processors(): HasMany
{
return $this->hasMany(\App\Models\Processor::class, 'device_id');
@ -882,6 +922,11 @@ class Device extends BaseModel
return $this->hasMany(\App\Models\Syslog::class, 'device_id', 'device_id');
}
public function tnmsNeInfo(): HasMany
{
return $this->hasMany(TnmsneInfo::class, 'device_id');
}
public function users(): BelongsToMany
{
// FIXME does not include global read

View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class EntityState extends DeviceRelatedModel
{
use HasFactory;
protected $table = 'entityState';
protected $primaryKey = 'entity_state_id';
public $timestamps = false;
}

10
app/Models/Process.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Process extends DeviceRelatedModel
{
use HasFactory;
}

View File

@ -2,7 +2,10 @@
namespace App\Observers;
use App;
use App\ApiClients\Oxidized;
use App\Models\Device;
use Illuminate\Support\Str;
use Log;
class DeviceObserver
@ -13,9 +16,10 @@ class DeviceObserver
* @param \App\Models\Device $device
* @return void
*/
public function created(Device $device)
public function created(Device $device): void
{
Log::event("Device $device->hostname has been created", $device, 'system', 3);
(new Oxidized)->reloadNodes();
}
/**
@ -24,7 +28,7 @@ class DeviceObserver
* @param \App\Models\Device $device
* @return void
*/
public function updated(Device $device)
public function updated(Device $device): void
{
// handle device dependency updates
if ($device->isDirty('max_depth')) {
@ -49,19 +53,118 @@ class DeviceObserver
}
}
/**
* Handle the device "deleted" event.
*/
public function deleted(Device $device): void
{
// delete rrd files
$host_dir = addcslashes(escapeshellarg(\Rrd::dirFromHost($device->hostname)), '\'');
$result = trim(shell_exec("bash -c '( [ ! -d $host_dir ] || rm -vrf $host_dir 2>&1 ) && echo -n OK'"));
if (! Str::endsWith($result, 'OK')) {
Log::debug("Could not $device->hostname files: $result");
}
Log::event("Device $device->hostname has been removed", 0, 'system', 3);
(new Oxidized)->reloadNodes();
}
/**
* Handle the device "deleting" event.
*
* @param \App\Models\Device $device
* @return void
*/
public function deleting(Device $device)
public function deleting(Device $device): void
{
// prevent abort from the webserver
if (App::runningInConsole() === false) {
ignore_user_abort(true);
set_time_limit(0);
}
// delete related data
$device->ports()->delete();
$device->syslogs()->delete();
$device->eventlogs()->delete();
$device->accessPoints()->delete();
$device->alerts()->delete();
\DB::table('alert_device_map')->where('device_id', $device->device_id)->delete();
$device->alertLogs()->delete();
$device->applications()->delete();
$device->attribs()->delete();
$device->availability()->delete();
$device->bgppeers()->delete();
\DB::table('bgpPeers_cbgp')->where('device_id', $device->device_id)->delete();
$device->cefSwitching()->delete();
\DB::table('ciscoASA')->where('device_id', $device->device_id)->delete();
$device->components()->delete();
\DB::table('customoids')->where('device_id', $device->device_id)->delete();
\DB::table('devices_perms')->where('device_id', $device->device_id)->delete();
$device->graphs()->delete();
\DB::table('device_group_device')->where('device_id', $device->device_id)->delete();
$device->diskIo()->delete();
$device->entityState()->delete();
$device->entityPhysical()->delete();
\DB::table('entPhysical_state')->where('device_id', $device->device_id)->delete();
$device->eventlogs()->delete();
$device->hostResources()->delete();
$device->hostResourceValues()->delete();
$device->ipsecTunnels()->delete();
$device->ipv4()->delete();
$device->ipv6()->delete();
$device->isisAdjacencies()->delete();
$device->isisAdjacencies()->delete();
$device->macs()->delete();
$device->mefInfo()->delete();
$device->mempools()->delete();
$device->mplsLsps()->delete();
$device->mplsLspPaths()->delete();
$device->mplsSaps()->delete();
$device->mplsSdps()->delete();
$device->mplsSdpBinds()->delete();
$device->mplsServices()->delete();
$device->mplsTunnelArHops()->delete();
$device->mplsTunnelCHops()->delete();
$device->muninPlugins()->delete();
\DB::table('netscaler_vservers')->where('device_id', $device->device_id)->delete();
$device->ospfAreas()->delete();
$device->ospfInstances()->delete();
$device->ospfNbrs()->delete();
$device->ospfPorts()->delete();
$device->outages()->delete();
$device->packages()->delete();
$device->perf()->delete();
$device->portsFdb()->delete();
$device->portsNac()->delete();
\DB::table('ports_stack')->where('device_id', $device->device_id)->delete();
$device->portsStp()->delete();
$device->portsVlan()->delete();
$device->printerSupplies()->delete();
$device->processes()->delete();
$device->processors()->delete();
$device->pseudowires()->delete();
$device->routes()->delete();
$device->rServers()->delete();
$device->sensors()->delete(); // delete sensor state indexes first?
$device->services()->delete();
\DB::table('service_templates_device')->where('device_id', $device->device_id)->delete();
$device->storage()->delete();
$device->stpInstances()->delete();
$device->syslogs()->delete();
$device->tnmsNeInfo()->delete();
$device->vlans()->delete();
$device->vminfo()->delete();
$device->vrfs()->delete();
$device->vrfLites()->delete();
$device->vServers()->delete();
$device->wirelessSensors()->delete();
$device->ports()
->select(['port_id', 'device_id', 'ifIndex', 'ifName', 'ifAlias', 'ifDescr'])
->chunk(100, function ($ports) {
foreach ($ports as $port) {
$port->delete();
}
});
// handle device dependency updates
$device->children->each->updateMaxDepth($device->device_id);

View File

@ -122,13 +122,6 @@ $end = microtime(true);
$run = ($end - $start);
$proctime = substr($run, 0, 5);
if ($discovered_devices) {
if ($doing === 'new') {
// We have added a new device by this point so we might want to do some other work
oxidized_reload_nodes();
}
}
if (isset($new_discovery_lock)) {
$new_discovery_lock->release();
}

View File

@ -19,7 +19,6 @@ use LibreNMS\Exceptions\HostUnreachablePingException;
use LibreNMS\Exceptions\InvalidPortAssocModeException;
use LibreNMS\Exceptions\SnmpVersionUnsupportedException;
use LibreNMS\Modules\Core;
use LibreNMS\Util\Debug;
use LibreNMS\Util\IPv4;
use LibreNMS\Util\IPv6;
use LibreNMS\Util\Proxy;
@ -295,68 +294,16 @@ function device_discovery_trigger($id)
function delete_device($id)
{
if (App::runningInConsole() === false) {
ignore_user_abort(true);
set_time_limit(0);
$device = DeviceCache::get($id);
if (! $device->exists) {
return 'No such device.';
}
$ret = '';
$host = dbFetchCell('SELECT hostname FROM devices WHERE device_id = ?', [$id]);
if (empty($host)) {
return 'No such host.';
if ($device->delete()) {
return "Removed device $device->hostname\n";
}
// Remove IPv4/IPv6 addresses before removing ports as they depend on port_id
dbQuery('DELETE `ipv4_addresses` FROM `ipv4_addresses` INNER JOIN `ports` ON `ports`.`port_id`=`ipv4_addresses`.`port_id` WHERE `device_id`=?', [$id]);
dbQuery('DELETE `ipv6_addresses` FROM `ipv6_addresses` INNER JOIN `ports` ON `ports`.`port_id`=`ipv6_addresses`.`port_id` WHERE `device_id`=?', [$id]);
//Remove IsisAdjacencies
\App\Models\IsisAdjacency::where('device_id', $id)->delete();
//Remove Outages
\App\Models\Availability::where('device_id', $id)->delete();
\App\Models\DeviceOutage::where('device_id', $id)->delete();
\App\Models\Port::where('device_id', $id)
->with('device')
->select(['port_id', 'device_id', 'ifIndex', 'ifName', 'ifAlias', 'ifDescr'])
->chunk(100, function ($ports) use (&$ret) {
foreach ($ports as $port) {
$port->delete();
$ret .= "Removed interface $port->port_id (" . $port->getLabel() . ")\n";
}
});
// Remove sensors manually due to constraints
foreach (dbFetchRows('SELECT * FROM `sensors` WHERE `device_id` = ?', [$id]) as $sensor) {
$sensor_id = $sensor['sensor_id'];
dbDelete('sensors_to_state_indexes', '`sensor_id` = ?', [$sensor_id]);
}
$fields = ['device_id', 'host'];
$db_name = dbFetchCell('SELECT DATABASE()');
foreach ($fields as $field) {
foreach (dbFetch('SELECT TABLE_NAME FROM information_schema.columns WHERE table_schema = ? AND column_name = ?', [$db_name, $field]) as $table) {
$table = $table['TABLE_NAME'];
$entries = (int) dbDelete($table, "`$field` = ?", [$id]);
if ($entries > 0 && Debug::isEnabled()) {
$ret .= "$field@$table = #$entries\n";
}
}
}
$ex = shell_exec("bash -c '( [ ! -d " . trim(Rrd::dirFromHost($host)) . ' ] || rm -vrf ' . trim(Rrd::dirFromHost($host)) . " 2>&1 ) && echo -n OK'");
$tmp = explode("\n", $ex);
if ($tmp[sizeof($tmp) - 1] != 'OK') {
$ret .= "Could not remove files:\n$ex\n";
}
$ret .= "Removed device $host\n";
log_event("Device $host has been removed", 0, 'system', 3);
oxidized_reload_nodes();
return $ret;
return "Failed to remove device $device->hostname";
}
/**
@ -1019,23 +966,6 @@ function host_exists($hostname, $sysName = null)
return dbFetchCell($query, $params) > 0;
}
function oxidized_reload_nodes()
{
if (Config::get('oxidized.enabled') === true && Config::get('oxidized.reload_nodes') === true && Config::has('oxidized.url')) {
$oxidized_reload_url = Config::get('oxidized.url') . '/reload.json';
$ch = curl_init($oxidized_reload_url);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_exec($ch);
curl_close($ch);
}
}
/**
* Perform DNS lookup
*
@ -1671,32 +1601,6 @@ function is_disk_valid($disk, $device)
return true;
}
/**
* Queues a hostname to be refreshed by Oxidized
* Settings: oxidized.url
*
* @param string $hostname
* @param string $msg
* @param string $username
* @return bool
*/
function oxidized_node_update($hostname, $msg, $username = 'not_provided')
{
// Work around https://github.com/rack/rack/issues/337
$msg = str_replace('%', '', $msg);
$postdata = ['user' => $username, 'msg' => $msg];
$oxidized_url = Config::get('oxidized.url');
if (! empty($oxidized_url)) {
$response = Http::withOptions(['proxy' => Proxy::forGuzzle($oxidized_url)])->put("$oxidized_url/node/next/$hostname", $postdata);
if ($response->successful()) {
return true;
}
}
return false;
}//end oxidized_node_update()
/**
* Take a BGP error code and subcode to return a string representation of it
*

View File

@ -14,7 +14,7 @@ header('Content-type: application/json');
$device_hostname = strip_tags($_POST['device_hostname']);
if (Auth::user()->hasGlobalAdmin() && isset($device_hostname)) {
if (oxidized_node_update($device_hostname, 'LibreNMS GUI refresh', Auth::user()->username)) {
if ((new \App\ApiClients\Oxidized())->updateNode($device_hostname, 'LibreNMS GUI refresh', Auth::user()->username)) {
$status = 'ok';
$message = 'Queued refresh in oxidized for device ' . $device_hostname;
} else {

View File

@ -14,7 +14,7 @@ if (! Auth::user()->hasGlobalAdmin()) {
$status = 'error';
$message = 'ERROR: You need to be admin to reload Oxidized node list';
} else {
oxidized_reload_nodes();
(new \App\ApiClients\Oxidized())->reloadNodes();
$status = 'ok';
$message = 'Oxidized node list was reloaded';
}

View File

@ -7200,11 +7200,6 @@ parameters:
count: 1
path: LibreNMS/Validator.php
-
message: "#^Method App\\\\ApiClients\\\\BaseApi\\:\\:getClient\\(\\) has no return type specified\\.$#"
count: 1
path: app/ApiClients/BaseApi.php
-
message: "#^Property App\\\\ApiClients\\\\BaseApi\\:\\:\\$base_uri has no type specified\\.$#"
count: 1

View File

@ -8,22 +8,23 @@ $hostname = $argv[1];
$os = $argv[2];
$msg = $argv[3];
$oxidized_api = new \App\ApiClients\Oxidized();
if (preg_match('/(SYS-(SW[0-9]+-)?5-CONFIG_I|VSHD-5-VSHD_SYSLOG_CONFIG_I): Configured from .+ by (?P<user>.+) on .*/', $msg, $matches)) {
oxidized_node_update($hostname, $msg, $matches['user']);
$oxidized_api->updateNode($hostname, $msg, $matches['user']);
} elseif (preg_match('/GBL-CONFIG-6-DB_COMMIT : Configuration committed by user \\\\\'(?P<user>.+?)\\\\\'..*/', $msg, $matches)) {
oxidized_node_update($hostname, $msg, $matches['user']);
$oxidized_api->updateNode($hostname, $msg, $matches['user']);
} elseif (preg_match('/ASA-(config-)?5-111005: (?P<user>.+) end configuration: OK/', $msg, $matches)) {
oxidized_node_update($hostname, $msg, $matches['user']);
$oxidized_api->updateNode($hostname, $msg, $matches['user']);
} elseif (preg_match('/startup-config was changed by (?P<user>.+) from telnet client .*/', $msg, $matches)) {
oxidized_node_update($hostname, $msg, $matches['user']);
$oxidized_api->updateNode($hostname, $msg, $matches['user']);
} elseif (preg_match('/HWCM\/4\/CFGCHANGE/', $msg, $matches)) { //Huawei VRP devices CFGCHANGE syslog
oxidized_node_update($hostname, $msg);
$oxidized_api->updateNode($hostname, $msg);
} elseif (preg_match('/UI_COMMIT: User \\\\\'(?P<user>.+?)\\\\\' .*/', $msg, $matches)) {
oxidized_node_update($hostname, $msg, $matches['user']);
$oxidized_api->updateNode($hostname, $msg, $matches['user']);
} elseif (preg_match('/IMI.+.Startup-config saved on .+ by (?P<user>.+) via .*/', $msg, $matches)) {
oxidized_node_update($hostname, $msg, $matches['user']); //Alliedware Plus devices. Requires at least V5.4.8-2.1
$oxidized_api->updateNode($hostname, $msg, $matches['user']); //Alliedware Plus devices. Requires at least V5.4.8-2.1
} elseif (preg_match('/System configuration saved/', $msg, $matches)) {
oxidized_node_update($hostname, $msg); //ScreenOS
$oxidized_api->updateNode($hostname, $msg); //ScreenOS
} elseif (preg_match('/Running Config Change/', $msg, $matches)) {
oxidized_node_update($hostname, $msg); //HPE and Aruba Procurve devices
$oxidized_api->updateNode($hostname, $msg); //HPE and Aruba Procurve devices
}