mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
refactor: Improve yaml state discovery (#7221)
* feature: Improve yaml state discovery Handle state values that are returned as strings instead of int Synchronize state values for existing state translations so we can change them without creating a new translation and losing historical data More extensive/verbose yaml discovery phpunit tests dbBulkInsert, use the first entry instead of requiring the first entry to be at index 0 * Update sensor state documentation re-order values for better readability remove os check Use snmpwalk_group since it is more flexible * Add some more debug output in dynamic discovery
This commit is contained in:
committed by
Neil Lathwood
parent
5441bafc81
commit
7b262a6851
@@ -97,12 +97,12 @@ modules:
|
||||
index: '{{ $index }}'
|
||||
state_name: otherStateSensorErrorStatus
|
||||
states:
|
||||
- { descr: normal, graph: 0, value: 0, generic: 0 }
|
||||
- { descr: info, graph: 0, value: 1, generic: 1 }
|
||||
- { descr: warning, graph: 0, value: 2, generic: 1 }
|
||||
- { descr: error, graph: 0, value: 3, generic: 2 }
|
||||
- { descr: critical, graph: 0, value: 4, generic: 2 }
|
||||
- { descr: failure, graph: 0, value: 5, generic: 2 }
|
||||
- { value: 0, generic: 0, graph: 0, descr: normal }
|
||||
- { value: 1, generic: 1, graph: 0, descr: info }
|
||||
- { value: 2, generic: 1, graph: 0, descr: warning }
|
||||
- { value: 3, generic: 2, graph: 0, descr: error }
|
||||
- { value: 4, generic: 2, graph: 0, descr: critical }
|
||||
- { value: 5, generic: 2, graph: 0, descr: failure }
|
||||
|
||||
```
|
||||
|
||||
@@ -114,45 +114,28 @@ The file should be located in /includes/discovery/sensors/state/cisco.inc.php.
|
||||
```php
|
||||
<?php
|
||||
|
||||
if ($device['os_group'] == 'cisco') {
|
||||
$oids = snmpwalk_cache_multi_oid($device, 'ciscoEnvMonSupplyStatusTable', array(), 'CISCO-ENVMON-MIB');
|
||||
$cur_oid = '.1.3.6.1.4.1.9.9.13.1.5.1.3.';
|
||||
$oids = snmpwalk_group($device, 'ciscoEnvMonSupplyStatusTable', 'CISCO-ENVMON-MIB');
|
||||
|
||||
if (is_array($oids)) {
|
||||
if (!empty($oids)) {
|
||||
//Create State Index
|
||||
$state_name = 'ciscoEnvMonSupplyState';
|
||||
$states = array(
|
||||
array('value' => 1, 'generic' => 0, 'graph' => 0, 'descr' => 'normal'),
|
||||
array('value' => 2, 'generic' => 1, 'graph' => 0, 'descr' => 'warning'),
|
||||
array('value' => 3, 'generic' => 2, 'graph' => 0, 'descr' => 'critical'),
|
||||
array('value' => 4, 'generic' => 3, 'graph' => 0, 'descr' => 'shutdown'),
|
||||
array('value' => 5, 'generic' => 3, 'graph' => 0, 'descr' => 'notPresent'),
|
||||
array('value' => 6, 'generic' => 2, 'graph' => 0, 'descr' => 'notFunctioning'),
|
||||
);
|
||||
create_state_index($state_name, $states);
|
||||
|
||||
//Create State Index
|
||||
$state_name = 'ciscoEnvMonSupplyState';
|
||||
$state_index_id = create_state_index($state_name);
|
||||
$num_oid = '.1.3.6.1.4.1.9.9.13.1.5.1.3.';
|
||||
foreach ($oids as $index => $entry) {
|
||||
//Discover Sensors
|
||||
discover_sensor($valid['sensor'], 'state', $device, $num_oid.$index, $index, $state_name, $entry['ciscoEnvMonSupplyStatusDescr'], '1', '1', null, null, null, null, $entry['ciscoEnvMonSupplyState'], 'snmp', $index);
|
||||
|
||||
//Create State Translation
|
||||
if ($state_index_id) {
|
||||
$states = array(
|
||||
array($state_index_id,'normal',0,1,0) ,
|
||||
array($state_index_id,'warning',0,2,1) ,
|
||||
array($state_index_id,'critical',0,3,2) ,
|
||||
array($state_index_id,'shutdown',0,4,3) ,
|
||||
array($state_index_id,'notPresent',0,5,3) ,
|
||||
array($state_index_id,'notFunctioning',0,6,2)
|
||||
);
|
||||
foreach($states as $value){
|
||||
$insert = array(
|
||||
'state_index_id' => $value[0],
|
||||
'state_descr' => $value[1],
|
||||
'state_draw_graph' => $value[2],
|
||||
'state_value' => $value[3],
|
||||
'state_generic_value' => $value[4]
|
||||
);
|
||||
dbInsert($insert, 'state_translations');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($oids as $index => $entry) {
|
||||
//Discover Sensors
|
||||
discover_sensor($valid['sensor'], 'state', $device, $cur_oid.$index, $index, $state_name, $entry['ciscoEnvMonSupplyStatusDescr'], '1', '1', null, null, null, null, $entry['ciscoEnvMonSupplyState'], 'snmp', $index);
|
||||
|
||||
//Create Sensor To State Index
|
||||
create_sensor_to_state_index($device, $state_name, $index);
|
||||
}
|
||||
//Create Sensor To State Index
|
||||
create_sensor_to_state_index($device, $state_name, $index);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@@ -184,14 +184,17 @@ function dbBulkInsert($data, $table)
|
||||
$data = $table;
|
||||
$table = $tmp;
|
||||
}
|
||||
if (count($data) === 0) {
|
||||
// check that data isn't an empty array
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
}
|
||||
if (count($data[0]) === 0) {
|
||||
// make sure we have fields to insert
|
||||
$fields = array_keys(reset($data));
|
||||
if (empty($fields)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = 'INSERT INTO `'.$table.'` (`'.implode('`,`', array_keys($data[0])).'`) VALUES ';
|
||||
$sql = 'INSERT INTO `'.$table.'` (`'.implode('`,`', $fields).'`) VALUES ';
|
||||
$values ='';
|
||||
|
||||
foreach ($data as $row) {
|
||||
|
@@ -1081,12 +1081,33 @@ function discovery_process(&$valid, $device, $sensor_type, $pre_cache)
|
||||
if (isset($device['dynamic_discovery']['modules']['sensors'][$sensor_type]['options'])) {
|
||||
$sensor_options = $device['dynamic_discovery']['modules']['sensors'][$sensor_type]['options'];
|
||||
}
|
||||
|
||||
d_echo("Dynamic Discovery ($sensor_type): ");
|
||||
d_echo($device['dynamic_discovery']['modules']['sensors'][$sensor_type]);
|
||||
|
||||
foreach ($device['dynamic_discovery']['modules']['sensors'][$sensor_type]['data'] as $data) {
|
||||
$tmp_name = $data['oid'];
|
||||
$raw_data = $pre_cache[$tmp_name];
|
||||
$raw_data = (array)$pre_cache[$tmp_name];
|
||||
$cached_data = $pre_cache['__cached'] ?: array();
|
||||
|
||||
d_echo("Data $tmp_name: ");
|
||||
d_echo($raw_data);
|
||||
|
||||
foreach ($raw_data as $index => $snmp_data) {
|
||||
$value = is_numeric($snmp_data[$data['value']]) ? $snmp_data[$data['value']] : (is_numeric($snmp_data[$data['oid']]) ? $snmp_data[$data['oid']]: false);
|
||||
// get the value for this sensor, check 'value' and 'oid', if state string, translate to a number
|
||||
$data_name = isset($data['value']) ? $data['value'] : $data['oid']; // fallback to oid if value is not set
|
||||
if (is_numeric($snmp_data[$data_name])) {
|
||||
$value = $snmp_data[$data_name];
|
||||
} elseif ($sensor_type === 'state') {
|
||||
// translate string states to values (poller does this as well)
|
||||
$states = array_column($data['states'], 'value', 'descr');
|
||||
$value = isset($states[$snmp_data[$data_name]]) ? $states[$snmp_data[$data_name]] : false;
|
||||
} else {
|
||||
$value = false;
|
||||
}
|
||||
|
||||
d_echo("Final sensor value: $value\n");
|
||||
|
||||
if (can_skip_sensor($value, $data, $sensor_options) === false && is_numeric($value)) {
|
||||
$oid = $data['num_oid'] . $index;
|
||||
if (isset($snmp_data[$data['descr']])) {
|
||||
@@ -1111,37 +1132,25 @@ function discovery_process(&$valid, $device, $sensor_type, $pre_cache)
|
||||
$low_warn_limit = is_numeric($data['low_warn_limit']) ? $data['low_warn_limit'] : ($snmp_data[$data['low_warn_limit']] ?: 'null');
|
||||
$warn_limit = is_numeric($data['warn_limit']) ? $data['warn_limit'] : ($snmp_data[$data['warn_limit']] ?: 'null');
|
||||
$high_limit = is_numeric($data['high_limit']) ? $data['high_limit'] : ($snmp_data[$data['high_limit']] ?: 'null');
|
||||
$state_name = '';
|
||||
if ($sensor_type !== 'state') {
|
||||
|
||||
$sensor_name = $device['os'];
|
||||
if ($sensor_type === 'state') {
|
||||
$sensor_name = $data['state_name'] ?: $data['oid'];
|
||||
create_state_index($sensor_name, $data['states']);
|
||||
} else {
|
||||
if (is_numeric($divisor)) {
|
||||
$value = $value / $divisor;
|
||||
}
|
||||
if (is_numeric($multiplier)) {
|
||||
$value = $value * $multiplier;
|
||||
}
|
||||
} else {
|
||||
$state_name = $data['state_name'] ?: $data['oid'];
|
||||
$state_index_id = create_state_index($state_name);
|
||||
if ($state_index_id != null) {
|
||||
foreach ($data['states'] as $state) {
|
||||
$insert = array(
|
||||
'state_index_id' => $state_index_id,
|
||||
'state_descr' => $state['descr'],
|
||||
'state_draw_graph' => $state['graph'],
|
||||
'state_value' => $state['value'],
|
||||
'state_generic_value' => $state['generic']
|
||||
);
|
||||
dbInsert($insert, 'state_translations');
|
||||
}
|
||||
}
|
||||
}
|
||||
$tmp_index = $data['index'] ?: $index;
|
||||
$uindex = str_replace('{{ $index }}', $index, $tmp_index);
|
||||
|
||||
$uindex = str_replace('{{ $index }}', $index, $data['index'] ?: $index);
|
||||
discover_sensor($valid['sensor'], $sensor_type, $device, $oid, $uindex, $sensor_name, $descr, $divisor, $multiplier, $low_limit, $low_warn_limit, $warn_limit, $high_limit, $value);
|
||||
|
||||
if ($sensor_type === 'state') {
|
||||
discover_sensor($valid['sensor'], $sensor_type, $device, $oid, $uindex, $state_name, $descr, $divisor, $multiplier, $low_limit, $low_warn_limit, $warn_limit, $high_limit, $value);
|
||||
create_sensor_to_state_index($device, $state_name, $uindex);
|
||||
} else {
|
||||
discover_sensor($valid['sensor'], $sensor_type, $device, $oid, $uindex, $device['os'], $descr, $divisor, $multiplier, $low_limit, $low_warn_limit, $warn_limit, $high_limit, $value);
|
||||
create_sensor_to_state_index($device, $sensor_name, $uindex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1586,19 +1586,87 @@ function rrdtest($path, &$stdOutput, &$stdError)
|
||||
return $status['exitcode'];
|
||||
}
|
||||
|
||||
function create_state_index($state_name)
|
||||
/**
|
||||
* Create a new state index. Update translations if $states is given.
|
||||
*
|
||||
* For for backward compatibility:
|
||||
* Returns null if $states is empty, $state_name already exists, and contains state translations
|
||||
*
|
||||
* @param string $state_name the unique name for this state translation
|
||||
* @param array $states array of states, each must contain keys: descr, graph, value, generic
|
||||
* @return int|null
|
||||
*/
|
||||
function create_state_index($state_name, $states = array())
|
||||
{
|
||||
$state_index_id = dbFetchCell('SELECT `state_index_id` FROM state_indexes WHERE state_name = ? LIMIT 1', array($state_name));
|
||||
if (!is_numeric($state_index_id)) {
|
||||
$insert = array('state_name' => $state_name);
|
||||
return dbInsert($insert, 'state_indexes');
|
||||
} else {
|
||||
$state_index_id = dbInsert(array('state_name' => $state_name), 'state_indexes');
|
||||
|
||||
// legacy code, return index so states are created
|
||||
if (empty($states)) {
|
||||
return $state_index_id;
|
||||
}
|
||||
}
|
||||
|
||||
// check or synchronize states
|
||||
if (empty($states)) {
|
||||
$translations = dbFetchRows('SELECT * FROM `state_translations` WHERE `state_index_id` = ?', array($state_index_id));
|
||||
if (count($translations) == 0) {
|
||||
// If we don't have any translations something has gone wrong so return the state_index_id so they get created.
|
||||
return $state_index_id;
|
||||
}
|
||||
} else {
|
||||
sync_sensor_states($state_index_id, $states);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize the sensor state translations with the database
|
||||
*
|
||||
* @param int $state_index_id index of the state
|
||||
* @param array $states array of states, each must contain keys: descr, graph, value, generic
|
||||
*/
|
||||
function sync_sensor_states($state_index_id, $states)
|
||||
{
|
||||
$new_translations = array_reduce($states, function ($array, $state) use ($state_index_id) {
|
||||
$array[$state['value']] = array(
|
||||
'state_index_id' => $state_index_id,
|
||||
'state_descr' => $state['descr'],
|
||||
'state_draw_graph' => $state['graph'],
|
||||
'state_value' => $state['value'],
|
||||
'state_generic_value' => $state['generic']
|
||||
);
|
||||
return $array;
|
||||
}, array());
|
||||
|
||||
$existing_translations = dbFetchRows(
|
||||
'SELECT `state_index_id`,`state_descr`,`state_draw_graph`,`state_value`,`state_generic_value` FROM `state_translations` WHERE `state_index_id`=?',
|
||||
array($state_index_id)
|
||||
);
|
||||
|
||||
foreach ($existing_translations as $translation) {
|
||||
$value = $translation['state_value'];
|
||||
if (isset($new_translations[$value])) {
|
||||
if ($new_translations[$value] != $translation) {
|
||||
dbUpdate(
|
||||
$new_translations[$value],
|
||||
'state_translations',
|
||||
'`state_index_id`=? AND `state_value`=?',
|
||||
array($state_index_id, $value)
|
||||
);
|
||||
}
|
||||
|
||||
// this translation is synchronized, it doesn't need to be inserted
|
||||
unset($new_translations[$value]);
|
||||
} else {
|
||||
dbDelete('state_translations', '`state_index_id`=? AND `state_value`=?', array($state_index_id, $value));
|
||||
}
|
||||
}
|
||||
|
||||
// insert any new translations
|
||||
dbBulkInsert($new_translations, 'state_translations');
|
||||
}
|
||||
|
||||
function create_sensor_to_state_index($device, $state_name, $index)
|
||||
|
@@ -25,18 +25,17 @@
|
||||
|
||||
namespace LibreNMS\Tests;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use LibreNMS\Config;
|
||||
use PHPUnit_Framework_ExpectationFailedException as PHPUnitException;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class YamlTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
public function testOSYaml()
|
||||
{
|
||||
global $config;
|
||||
|
||||
$pattern = $config['install_dir'] . '/includes/definitions/*.yaml';
|
||||
$pattern = Config::get('install_dir') . '/includes/definitions/*.yaml';
|
||||
foreach (glob($pattern) as $file) {
|
||||
try {
|
||||
$data = Yaml::parse(file_get_contents($file));
|
||||
@@ -50,30 +49,52 @@ class YamlTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testDiscoveryYaml()
|
||||
/**
|
||||
* @dataProvider listDiscoveryFiles
|
||||
* @param $file
|
||||
*/
|
||||
public function testDiscoveryYaml($file)
|
||||
{
|
||||
global $config;
|
||||
try {
|
||||
$data = Yaml::parse(file_get_contents(Config::get('install_dir') . "/includes/definitions/discovery/$file"));
|
||||
} catch (ParseException $e) {
|
||||
throw new PHPUnitException("includes/definitions/discovery/$file Could not be parsed");
|
||||
}
|
||||
|
||||
$pattern = $config['install_dir'] . '/includes/definitions/discovery/*.yaml';
|
||||
foreach (glob($pattern) as $file) {
|
||||
try {
|
||||
$data = Yaml::parse(file_get_contents($file));
|
||||
} catch (ParseException $e) {
|
||||
throw new PHPUnitException("$file Could not be parsed");
|
||||
}
|
||||
foreach ($data['modules'] as $module => $sub_modules) {
|
||||
foreach ($sub_modules as $type => $sub_module) {
|
||||
$this->assertArrayHasKey('data', $sub_module, "$type is missing data key");
|
||||
foreach ($sub_module['data'] as $sensor_index => $sensor) {
|
||||
$this->assertArrayHasKey('oid', $sensor, "$type.data.$sensor_index is missing oid key");
|
||||
if ($type !== 'pre-cache') {
|
||||
$this->assertArrayHasKey('num_oid', $sensor, "$type.data.$sensor_index(${sensor['oid']}) is missing num_oid key");
|
||||
$this->assertArrayHasKey('descr', $sensor, "$type.data.$sensor_index(${sensor['oid']}) is missing descr key");
|
||||
}
|
||||
|
||||
foreach ($data['modules'] as $module => $sub_modules) {
|
||||
foreach ($sub_modules as $type => $sub_module) {
|
||||
foreach ($sub_module['data'] as $sensor) {
|
||||
$this->assertArrayHasKey('oid', $sensor, $file);
|
||||
if ($type !== 'pre-cache') {
|
||||
$this->assertArrayHasKey('oid', $sensor, $file);
|
||||
$this->assertArrayHasKey('num_oid', $sensor, $file);
|
||||
$this->assertArrayHasKey('descr', $sensor, $file);
|
||||
if ($type === 'state') {
|
||||
$this->assertArrayHasKey('states', $sensor, "$type.data(${sensor['oid']}) is missing states key");
|
||||
|
||||
foreach ($sensor['states'] as $state_index => $state) {
|
||||
$this->assertArrayHasKey('descr', $state, "$type.data.$sensor_index(${sensor['oid']}).states.$state_index is missing descr key");
|
||||
$this->assertNotEmpty($state['descr'], "$type.data.$sensor_index(${sensor['oid']}).states.$state_index(${state['descr']}) descr must not be empty");
|
||||
$this->assertArrayHasKey('graph', $state, "$type.data.$sensor_index(${sensor['oid']}).states.$state_index(${state['descr']}) is missing graph key");
|
||||
$this->assertTrue($state['graph'] === 0 || $state['graph'] === 1, "$type.data.$sensor_index(${sensor['oid']}).states.$state_index(${state['descr']}) invalid graph value must be 0 or 1");
|
||||
$this->assertArrayHasKey('value', $state, "$type.data.$sensor_index(${sensor['oid']}).states.$state_index(${state['descr']}) is missing value key");
|
||||
$this->assertInternalType('int', $state['value'], "$type.data.$sensor_index(${sensor['oid']}).states.$state_index(${state['descr']}) value must be an int");
|
||||
$this->assertArrayHasKey('generic', $state, "$type.data.$sensor_index(${sensor['oid']}).states.$state_index(${state['descr']}) is missing generic key");
|
||||
$this->assertInternalType('int', $state['generic'], "$type.data.$sensor_index(${sensor['oid']}).states.$state_index(${state['descr']}) generic must be an int");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function listDiscoveryFiles()
|
||||
{
|
||||
$pattern = Config::get('install_dir') . '/includes/definitions/discovery/*.yaml';
|
||||
return array_map(function ($file) {
|
||||
return array(basename($file));
|
||||
}, glob($pattern));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user