diff --git a/doc/Support/Discovery Support.md b/doc/Support/Discovery Support.md index 9d41002428..fb8caa538b 100644 --- a/doc/Support/Discovery Support.md +++ b/doc/Support/Discovery Support.md @@ -57,6 +57,7 @@ $config['discovery_modules']['os'] = 1; $config['discovery_modules']['ports'] = 1; $config['discovery_modules']['ports-stack'] = 1; $config['discovery_modules']['entity-physical'] = 1; +$config['discovery_modules']['entity-state'] = 0; $config['discovery_modules']['processors'] = 1; $config['discovery_modules']['mempools'] = 1; $config['discovery_modules']['cisco-vrf-lite'] = 1; diff --git a/doc/Support/Poller Support.md b/doc/Support/Poller Support.md index f28f2a80e0..3862b913ae 100644 --- a/doc/Support/Poller Support.md +++ b/doc/Support/Poller Support.md @@ -76,6 +76,7 @@ $config['poller_modules']['cisco-vpdn'] = 0; $config['poller_modules']['netscaler-vsvr'] = 0; $config['poller_modules']['aruba-controller'] = 0; $config['poller_modules']['entity-physical'] = 1; +$config['poller_modules']['entity-state'] = 0; $config['poller_modules']['applications'] = 1; $config['poller_modules']['mib'] = 0; $config['poller_modules']['stp'] = 1; diff --git a/html/pages/device/entphysical.inc.php b/html/pages/device/entphysical.inc.php index a3d485f7b7..a8a6138b35 100644 --- a/html/pages/device/entphysical.inc.php +++ b/html/pages/device/entphysical.inc.php @@ -6,51 +6,40 @@ function printEntPhysical($ent, $level, $class) global $device; $ents = dbFetchRows('SELECT * FROM `entPhysical` WHERE device_id = ? AND entPhysicalContainedIn = ? ORDER BY entPhysicalContainedIn,entPhysicalIndex', array($device['device_id'], $ent)); + foreach ($ents as $ent) { echo "
  • "; if ($ent['entPhysicalClass'] == 'chassis') { - echo ' '; - } - - if ($ent['entPhysicalClass'] == 'module') { - echo ' '; - } - - if ($ent['entPhysicalClass'] == 'port') { - echo ' '; - } - - if ($ent['entPhysicalClass'] == 'container') { - echo ' '; - } - - if ($ent['entPhysicalClass'] == 'sensor') { - echo ' '; + echo ' '; + } elseif ($ent['entPhysicalClass'] == 'module') { + echo ' '; + } elseif ($ent['entPhysicalClass'] == 'port') { + echo ' '; + } elseif ($ent['entPhysicalClass'] == 'container') { + echo ' '; + } elseif ($ent['entPhysicalClass'] == 'sensor') { + echo ' '; $sensor = dbFetchRow('SELECT * FROM `sensors` WHERE `device_id` = ? AND (`entPhysicalIndex` = ? OR `sensor_index` = ?)', array($device['device_id'], $ent['entPhysicalIndex'], $ent['entPhysicalIndex'])); if (count($sensor)) { - $link = " href='device/device=".$device['device_id'].'/tab=health/metric='.$sensor['sensor_class']."/' onmouseover=\"return overlib('', LEFT,FGCOLOR,'#e5e5e5', BGCOLOR, '#c0c0c0', BORDER, 5, CELLPAD, 4, CAPCOLOR, '#050505');\" onmouseout=\"return nd();\""; + $link = "', LEFT,FGCOLOR,'#e5e5e5', BGCOLOR, '#c0c0c0', BORDER, 5, CELLPAD, 4, CAPCOLOR, '#050505');\" onmouseout=\"return nd();\">"; } - } else { - unset($link); - } - - if ($ent['entPhysicalClass'] == 'backplane') { - echo ' '; + } elseif ($ent['entPhysicalClass'] == 'backplane') { + echo ' '; } if ($ent['entPhysicalParentRelPos'] > '-1') { echo ''.$ent['entPhysicalParentRelPos'].'. '; } - if ($link) { - echo ""; + if (isset($link)) { + echo $link; } if ($ent['ifIndex']) { - $interface = dbFetchRow('SELECT * FROM `ports` WHERE ifIndex = ? AND device_id = ?', array($ent['ifIndex'], $device['device_id'])); - $interface = cleanPort($interface); + $interface = get_port_by_ifIndex($device['device_id'], $ent['ifIndex']); + $interface = cleanPort($interface); $ent['entPhysicalName'] = generate_port_link($interface); } @@ -66,16 +55,50 @@ function printEntPhysical($ent, $level, $class) echo ''.$ent['entPhysicalDescr'].''; } - if ($ent['entPhysicalClass'] == 'sensor') { - echo ' ('.$ent['entSensorValue'].' '.$ent['entSensorType'].')'; + if ($ent['entPhysicalClass'] == 'sensor' && isset($sensor)) { + echo ' ('.trim($sensor['sensor_current'] . ' ' . get_units_from_sensor($sensor)) . ')'; + } + + if (isset($link)) { + echo ''; + unset($link); + } + + // display entity state + $entState = dbFetchRow( + 'SELECT * FROM `entityState` WHERE `device_id`=? && `entPhysical_id`=?', + array($device['device_id'], $ent['entPhysical_id']) + ); + + if (!empty($entState)) { + $display_states = array( +// 'entStateAdmin', + 'entStateOper', + 'entStateUsage', + 'entStateStandby' + ); + foreach ($display_states as $state_name) { + $value = $entState[$state_name]; + $display = parse_entity_state($state_name, $value); + echo " "; + echo $display['text']; + echo " "; + } + + // ignore none and unavailable alarms + if ($entState['entStateAlarm'] != '00' && $entState['entStateAlarm'] != '80') { + $alarms = parse_entity_state_alarm($entState['entStateAlarm']); + echo '
    '; + echo "Alarms: "; + foreach ($alarms as $alarm) { + echo " {$alarm['text']}"; + } + echo ''; + } } echo "
    ".$ent['entPhysicalDescr']; - if ($link) { - echo ''; - } - if ($ent['entPhysicalSerialNum']) { echo "
    Serial No. ".$ent['entPhysicalSerialNum'].' '; } diff --git a/includes/dbFacile.php b/includes/dbFacile.php index 850239b145..1d4e44d423 100644 --- a/includes/dbFacile.php +++ b/includes/dbFacile.php @@ -210,7 +210,11 @@ function dbBulkInsert($data, $table) if ($rowvalues != '') { $rowvalues .= ','; } - $rowvalues .= "'".mres($value)."'"; + if (is_null($value)) { + $rowvalues .= 'NULL'; + } else { + $rowvalues .= "'" . mres($value) . "'"; + } } $values .= "(".$rowvalues.")"; } diff --git a/includes/defaults.inc.php b/includes/defaults.inc.php index e284b0f33e..b8a15f3c42 100644 --- a/includes/defaults.inc.php +++ b/includes/defaults.inc.php @@ -765,6 +765,7 @@ $config['poller_modules']['cisco-vpdn'] = 0; $config['poller_modules']['netscaler-vsvr'] = 0; $config['poller_modules']['aruba-controller'] = 0; $config['poller_modules']['entity-physical'] = 1; +$config['poller_modules']['entity-state'] = 0; $config['poller_modules']['applications'] = 1; $config['poller_modules']['mib'] = 0; $config['poller_modules']['stp'] = 1; @@ -779,6 +780,7 @@ $config['discovery_modules']['os'] = 1; $config['discovery_modules']['ports'] = 1; $config['discovery_modules']['ports-stack'] = 1; $config['discovery_modules']['entity-physical'] = 1; +$config['discovery_modules']['entity-state'] = 0; $config['discovery_modules']['processors'] = 1; $config['discovery_modules']['mempools'] = 1; $config['discovery_modules']['cisco-vrf-lite'] = 1; diff --git a/includes/discovery/entity-state.inc.php b/includes/discovery/entity-state.inc.php new file mode 100644 index 0000000000..8034f560b5 --- /dev/null +++ b/includes/discovery/entity-state.inc.php @@ -0,0 +1,105 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2017 Tony Murray + * @author Tony Murray + */ + +$entPhysical = dbFetchRows( + 'SELECT entPhysical_id, entPhysicalIndex FROM entPhysical WHERE device_id=?', + array($device['device_id']) +); + +if (!empty($entPhysical)) { + echo "\nEntity States: "; + + $entPhysical = array_combine( + array_column($entPhysical, 'entPhysicalIndex'), + array_column($entPhysical, 'entPhysical_id') + ); + $state_data = snmpwalk_group($device, 'entStateTable', 'ENTITY-STATE-MIB'); + $db_states = dbFetchRows('SELECT * FROM entityState WHERE device_id=?', array($device['device_id'])); + $db_states = array_combine(array_column($db_states, 'entPhysical_id'), $db_states); + + foreach ($state_data as $index => $state) { + if (isset($entPhysical[$index])) { + $id = $entPhysical[$index]; + + // format datetime + if (empty($state['entStateLastChanged'])) { + $state['entStateLastChanged'] = null; + } else { + list($date, $time, $tz) = explode(',', $state['entStateLastChanged']); + try { + $lastChanged = new DateTime("$date $time", new DateTimeZone($tz)); + $state['entStateLastChanged'] = $lastChanged + ->setTimezone(new DateTimeZone(date_default_timezone_get())) + ->format('Y-m-d H:i:s'); + } catch (Exception $e) { + // no update + } + } + + if (isset($db_states[$id])) { // update the db + $db_state = $db_states[$id]; + $update = array_diff($state, $db_state); + + if (!empty($update)) { + if (array_key_exists('entStateLastChanged', $update) && is_null($update['entStateLastChanged'])) { + $update['entStateLastChanged'] = array('NULL'); + } + + dbUpdate($update, 'entityState', 'entity_state_id=?', array($db_state['entity_state_id'])); + d_echo("Updating entity state: ", 'U'); + d_echo($update); + } else { + echo '.'; + } + + unset($state_data[$index]); // remove so we don't insert later + unset($db_states[$id]); // remove so we don't delete later + } else { + // prep for insert later + $state_data[$index]['device_id'] = $device['device_id']; + $state_data[$index]['entPhysical_id'] = $id; + $state_data[$index]['entStateLastChanged'] = $state['entStateLastChanged']; + d_echo("Inserting entity state:: ", '+'); + d_echo($state); + } + } + } + + if (!empty($state_data)) { + dbBulkInsert($state_data, 'entityState'); + } + + if (!empty($db_states)) { + dbDelete( + 'entityState', + 'entity_state_id IN ' . dbGenPlaceholders(count($db_states)), + array_column($db_states, 'entity_state_id') + ); + } +} + +echo PHP_EOL; + +unset($entPhysical, $state_data, $db_states, $update); diff --git a/includes/discovery/sensors.inc.php b/includes/discovery/sensors.inc.php index 95095d4294..c12eb0f0d6 100644 --- a/includes/discovery/sensors.inc.php +++ b/includes/discovery/sensors.inc.php @@ -34,6 +34,7 @@ if (isset($device['dynamic_discovery']['modules']['sensors']) && $device['os'] ! // Run custom sensors require 'includes/discovery/sensors/cisco-entity-sensor.inc.php'; require 'includes/discovery/sensors/entity-sensor.inc.php'; +require 'includes/discovery/sensors/entity-state.inc.php'; require 'includes/discovery/sensors/ipmi.inc.php'; if ($device['os'] == 'netscaler') { diff --git a/includes/discovery/sensors/entity-sensor.inc.php b/includes/discovery/sensors/entity-sensor.inc.php index 4f5ddfcbfa..9b9d6854e6 100644 --- a/includes/discovery/sensors/entity-sensor.inc.php +++ b/includes/discovery/sensors/entity-sensor.inc.php @@ -1,7 +1,7 @@ . + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2017 Tony Murray + * @author Tony Murray + */ + +$entityStatesIndexes = dbFetchRows( + 'SELECT S.entity_state_id, S.entStateLastChanged, P.entPhysicalIndex FROM entityState AS S ' . + 'LEFT JOIN entPhysical AS P USING (entPhysical_id) WHERE S.device_id=?', + array($device['device_id']) +); + +if (!empty($entityStatesIndexes)) { + echo "\nEntity States: "; + + // index by entPhysicalIndex + $entityStatesIndexes = array_combine(array_column($entityStatesIndexes, 'entPhysicalIndex'), $entityStatesIndexes); + + $entLC = snmpwalk_group($device, 'entStateLastChanged', 'ENTITY-STATE-MIB', 0); + + foreach (current($entLC) as $index => $changed) { + if ($changed) { // skip empty entries + try { + list($date, $time, $tz) = explode(',', $changed); + $lastChanged = new DateTime("$date $time", new DateTimeZone($tz)); + $dbLastChanged = new DateTime($entityStatesIndexes[$index]['entStateLastChanged']); + if ($lastChanged != $dbLastChanged) { + // data has changed, fetch it + $new_states = snmp_get_multi( + $device, + array( + "entStateAdmin.$index", + "entStateOper.$index", + "entStateUsage.$index", + "entStateAlarm.$index", + "entStateStandby.$index" + ), + '-OQUse', + 'ENTITY-STATE-MIB' + ); + $new_states = $new_states[$index]; // just get values + + // add entStateLastChanged and update + $new_states['entStateLastChanged'] = $lastChanged + ->setTimezone(new DateTimeZone(date_default_timezone_get())) + ->format('Y-m-d H:i:s'); + + // check if anything has changed + $update = array_diff( + $new_states, + dbFetchRow( + 'SELECT * FROM entityState WHERE entity_state_id=?', + array($entityStatesIndexes[$index]['entity_state_id']) + ) + ); + + if (!empty($update)) { + dbUpdate( + $update, + 'entityState', + 'entity_state_id=?', + array($entityStatesIndexes[$index]['entity_state_id']) + ); + d_echo("Updating $index: ", 'U'); + d_echo($new_states[$index]); + continue; + } + } + } catch (Exception $e) { + // no update + d_echo("Error: " . $e->getMessage() . PHP_EOL); + } + } + echo '.'; + } + + echo PHP_EOL; +} + +unset($entityStatesIndexes, $entLC, $lastChanged, $dbLastChanged, $new_states, $update); diff --git a/includes/rewrites.php b/includes/rewrites.php index b1e7c29bd7..734808fd04 100644 --- a/includes/rewrites.php +++ b/includes/rewrites.php @@ -1432,3 +1432,95 @@ function return_number($value) } return $value; } + +function get_units_from_sensor($sensor) +{ + switch ($sensor['sensor_class']) { + case 'airflow': + return 'cfm'; + case 'current': + return 'A'; + case 'dbm': + case 'signal': + return 'dBm'; + case 'fanspeed': + return 'rpm'; + case 'frequency': + return 'Hz'; + case 'charge': + case 'humidity': + case 'load': + return '%'; + case 'cooling': + case 'power': + return 'W'; + case 'pressure': + return 'kPa'; + case 'runtime': + return 'Min'; + case 'snr': + return 'SNR'; + case 'state': + return '#'; + case 'temperature': + return 'C'; + case 'voltage': + return 'V'; + default: + return ''; + } +} + +function parse_entity_state($state, $value) +{ + $data = array( + 'entStateOper' => array( + 1 => array('text' => 'unavailable', 'color' => 'default'), + 2 => array('text' => 'disabled', 'color' => 'danger'), + 3 => array('text' => 'enabled', 'color' => 'success'), + 4 => array('text' => 'testing', 'color' => 'warning'), + ), + 'entStateUsage' => array( + 1 => array('text' => 'unavailable', 'color' => 'default'), + 2 => array('text' => 'idle', 'color' => 'info'), + 3 => array('text' => 'active', 'color' => 'success'), + 4 => array('text' => 'busy', 'color' => 'success'), + ), + 'entStateStandby' => array( + 1 => array('text' => 'unavailable', 'color' => 'default'), + 2 => array('text' => 'hotStandby', 'color' => 'info'), + 3 => array('text' => 'coldStandby', 'color' => 'info'), + 4 => array('text' => 'providingService', 'color' => 'success'), + ), + 'entStateAdmin' => array( + 1 => array('text' => 'unknown', 'color' => 'default'), + 2 => array('text' => 'locked', 'color' => 'info'), + 3 => array('text' => 'shuttingDown', 'color' => 'warning'), + 4 => array('text' => 'unlocked', 'color' => 'success'), + ), + ); + + if (isset($data[$state][$value])) { + return $data[$state][$value]; + } + + return array('text'=>'na', 'color'=>'default'); +} + +function parse_entity_state_alarm($bits) +{ + // not sure if this is correct + $data = array( + 0 => array('text' => 'unavailable', 'color' => 'default'), + 1 => array('text' => 'underRepair', 'color' => 'warning'), + 2 => array('text' => 'critical', 'color' => 'danger'), + 3 => array('text' => 'major', 'color' => 'danger'), + 4 => array('text' => 'minor', 'color' => 'info'), + 5 => array('text' => 'warning', 'color' => 'warning'), + 6 => array('text' => 'indeterminate', 'color' => 'default'), + ); + + $alarms = str_split(base_convert($bits, 16, 2)); + $active_alarms = array_filter($alarms); + return array_intersect_key($data, $active_alarms); +} diff --git a/misc/db_schema.yaml b/misc/db_schema.yaml index 8bb45adde9..2c9e98d9e7 100644 --- a/misc/db_schema.yaml +++ b/misc/db_schema.yaml @@ -525,6 +525,20 @@ device_perf: Indexes: id: { Name: id, Columns: [id], Unique: false, Type: BTREE } device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE } +entityState: + Columns: + - { Field: entity_state_id, Type: int(11), 'Null': false, Extra: auto_increment } + - { Field: device_id, Type: int(11), 'Null': true, Extra: '' } + - { Field: entPhysical_id, Type: int(11), 'Null': true, Extra: '' } + - { Field: entStateLastChanged, Type: datetime, 'Null': true, Extra: '' } + - { Field: entStateAdmin, Type: int(11), 'Null': true, Extra: '' } + - { Field: entStateOper, Type: int(11), 'Null': true, Extra: '' } + - { Field: entStateUsage, Type: int(11), 'Null': true, Extra: '' } + - { Field: entStateAlarm, Type: text, 'Null': true, Extra: '' } + - { Field: entStateStandby, Type: int(11), 'Null': true, Extra: '' } + Indexes: + PRIMARY: { Name: PRIMARY, Columns: [entity_state_id], Unique: true, Type: BTREE } + entityState_device_id_index: { Name: entityState_device_id_index, Columns: [device_id], Unique: false, Type: BTREE } entPhysical: Columns: - { Field: entPhysical_id, Type: int(11), 'Null': false, Extra: auto_increment } diff --git a/sql-schema/220.sql b/sql-schema/220.sql new file mode 100644 index 0000000000..3a0e506409 --- /dev/null +++ b/sql-schema/220.sql @@ -0,0 +1,2 @@ +CREATE TABLE entityState (entity_state_id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT, device_id INT(11), entPhysical_id INT(11), entStateLastChanged DATETIME, entStateAdmin INT(11), entStateOper INT(11), entStateUsage INT(11), entStateAlarm TEXT, entStateStandby INT(11)); +CREATE INDEX entityState_device_id_index ON entityState (device_id);