diff --git a/doc/General/Changelog.md b/doc/General/Changelog.md index 54a97dacc2..a60beb1aa1 100644 --- a/doc/General/Changelog.md +++ b/doc/General/Changelog.md @@ -1,3 +1,35 @@ +### February 2016 + +#### Bug fixes + - Discovery / Polling: + - Quote snmp v2c community (PR2927) + - For entity-sensor, changed variable name again (PR2948) + - Fix some issues with/introduced by port association mode configuration (PR2923) + - WebUI: + - Fix ceph graps (PR2909, PR2942) + - BGP Overlib (PR2915) + - Added `application/json` headers where json is returned (PR2936) + - Stop realtime graph page from auto refreshing (PR2939) + - Updated parsing of alert rules to allow `|` (PR2917) + - Fix IP Display (PR2951) + - Misc: + - Updated `device_by_id_cache()` to convert IP column (PR2940) + - Documentation: + - Removed devloping doc as none of the info is current (PR2911) + +#### Improvements + - Discovery / Polling: + - Added ability to ignore device sensors from entity mib (PR2862) + - Added `ifOperStatus_prev` and `ifAdminStatus_prev` values to db (PR2912) + - Added detection for: + - Dell Networking N2048 (PR2949) + - Misc: + - Added check for rrd vadility (PR2908) + - Add systemd unit file for the python poller service (PR2913) + - Documentation: + - Added description of AD configuration options (PR2910) + - Add description to mibbases polling (PR2919) + ### January 2016 #### Bug fixes diff --git a/doc/Support/Install Validation.md b/doc/Support/Install Validation.md index ba90879dea..2ed6a2ce03 100644 --- a/doc/Support/Install Validation.md +++ b/doc/Support/Install Validation.md @@ -17,7 +17,9 @@ So, to try and help with some of the general issues people come across we've put Optionally you can also pass -m and a module name for that to be tested. Current modules are: - - mail. This will validate your mail transport configuration. + - mail - This will validate your mail transport configuration. + - dist-poller - This will test your distributed poller configuration. + - rrdcheck - This will test your rrd files to see if they are unreadable or corrupted (source of broken graphs). Output, this is color coded to try and make things a little easier: diff --git a/html/ajax_dash.php b/html/ajax_dash.php index d2e2b0a622..0ffc580679 100644 --- a/html/ajax_dash.php +++ b/html/ajax_dash.php @@ -55,5 +55,5 @@ $response = array( 'html' => $output, 'title' => $title, ); - +header('Content-type: application/json'); echo _json_encode($response); diff --git a/html/ajax_form.php b/html/ajax_form.php index 999a43a4dc..3851d5b4f2 100644 --- a/html/ajax_form.php +++ b/html/ajax_form.php @@ -29,6 +29,7 @@ if (!$_SESSION['authenticated']) { if (preg_match('/^[a-zA-Z0-9\-]+$/', $_POST['type']) == 1) { if (file_exists('includes/forms/'.$_POST['type'].'.inc.php')) { + header('Content-type: application/json'); include_once 'includes/forms/'.$_POST['type'].'.inc.php'; } } diff --git a/html/ajax_rulesuggest.php b/html/ajax_rulesuggest.php index cee773a2fc..850658939a 100644 --- a/html/ajax_rulesuggest.php +++ b/html/ajax_rulesuggest.php @@ -62,7 +62,7 @@ function levsort($base, $obj) { } - +header('Content-type: application/json'); $obj = array(array('name' => 'Error: No suggestions found.')); $term = array(); $current = false; diff --git a/html/ajax_search.php b/html/ajax_search.php index e9ddad36f5..cb8d012a33 100644 --- a/html/ajax_search.php +++ b/html/ajax_search.php @@ -19,7 +19,7 @@ $bgp = array(); if (isset($_REQUEST['search'])) { $search = mres($_REQUEST['search']); - + header('Content-type: application/json'); if (strlen($search) > 0) { $found = 0; diff --git a/html/ajax_table.php b/html/ajax_table.php index f16cc2da97..c3513a68a8 100644 --- a/html/ajax_table.php +++ b/html/ajax_table.php @@ -36,6 +36,7 @@ $response = array(); if (isset($id)) { if (file_exists("includes/table/$id.inc.php")) { + header('Content-type: application/json'); include_once "includes/table/$id.inc.php"; } } diff --git a/html/includes/dev-overview-data.inc.php b/html/includes/dev-overview-data.inc.php index 2e7ed141ea..04c4de3ddb 100644 --- a/html/includes/dev-overview-data.inc.php +++ b/html/includes/dev-overview-data.inc.php @@ -30,10 +30,10 @@ echo ' '.$device['sysName'].' '; -if ($ip = inet6_ntop($device['ip'])) { +if (!empty($device['ip'])) { echo ' Resolved IP - '.$ip.' + '.$device['ip'].' '; } diff --git a/html/includes/forms/parse-alert-rule.inc.php b/html/includes/forms/parse-alert-rule.inc.php index aa3120424c..286fb35c8b 100644 --- a/html/includes/forms/parse-alert-rule.inc.php +++ b/html/includes/forms/parse-alert-rule.inc.php @@ -20,7 +20,7 @@ $alert_id = $_POST['alert_id']; if (is_numeric($alert_id) && $alert_id > 0) { $rule = dbFetchRow('SELECT * FROM `alert_rules` WHERE `id` = ? LIMIT 1', array($alert_id)); - $rule_split = preg_split('/([a-zA-Z0-9_\-\.\=\%\<\>\ \"\'\!\~\(\)\*\/\@]+[&&\|\|]+)/', $rule['rule'], -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)); + $rule_split = preg_split('/([a-zA-Z0-9_\-\.\=\%\<\>\ \"\'\!\~\(\)\*\/\@\|]+[&&|\|\|]{2})/', $rule['rule'], -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)); $count = (count($rule_split) - 1); $rule_split[$count] = $rule_split[$count].' &&'; $output = array( diff --git a/html/pages/device/apps/ceph.inc.php b/html/pages/device/apps/ceph.inc.php index 50dedc8c48..9ad7536516 100644 --- a/html/pages/device/apps/ceph.inc.php +++ b/html/pages/device/apps/ceph.inc.php @@ -17,9 +17,9 @@ foreach ($graphs as $key => $text) { if ($key == "ceph_poolstats") { foreach (glob($rrddir."/app-ceph-".$app['app_id']."-pool-*") as $rrd_filename) { - $graph_array['to'] = $config['time']['now']; - $graph_array['id'] = $app['app_id']; if (preg_match("/.*-pool-(.+)\.rrd$/", $rrd_filename, $pools)) { + $graph_array['to'] = $config['time']['now']; + $graph_array['id'] = $app['app_id']; $pool = $pools[1]; echo '

'.$pool.' Reads/Writes

'; $graph_array['type'] = 'application_ceph_pool_io'; @@ -29,6 +29,8 @@ foreach ($graphs as $key => $text) { include 'includes/print-graphrow.inc.php'; echo ''; + $graph_array['to'] = $config['time']['now']; + $graph_array['id'] = $app['app_id']; echo '

'.$pool.' IOPS

'; $graph_array['type'] = 'application_ceph_pool_iops'; $graph_array['pool'] = $pool; diff --git a/html/pages/device/port/realtime.inc.php b/html/pages/device/port/realtime.inc.php index 41311917fd..7b282066e4 100644 --- a/html/pages/device/port/realtime.inc.php +++ b/html/pages/device/port/realtime.inc.php @@ -1,5 +1,7 @@ '200') { - $valid = false; + $valid_sensor = false; } $descr = preg_replace('/[T|t]emperature[|s]/', '', $descr); } // echo($descr . "|" . $index . "|" .$current . "|" . $multiplier . "|" . $divisor ."|" . $entry['entPhySensorScale'] . "|" . $entry['entPhySensorPrecision'] . "\n"); if ($current == '-127') { - $valid = false; + $valid_sensor = false; } - if ($valid && dbFetchCell("SELECT COUNT(*) FROM `sensors` WHERE device_id = ? AND `sensor_class` = ? AND `sensor_type` = 'cisco-entity-sensor' AND `sensor_index` = ?", array($device['device_id'], $type, $index)) == '0') { + if ($valid_sensor && dbFetchCell("SELECT COUNT(*) FROM `sensors` WHERE device_id = ? AND `sensor_class` = ? AND `sensor_type` = 'cisco-entity-sensor' AND `sensor_index` = ?", array($device['device_id'], $type, $index)) == '0') { // Check to make sure we've not already seen this sensor via cisco's entity sensor mib discover_sensor($valid['sensor'], $type, $device, $oid, $index, 'entity-sensor', $descr, $divisor, $multiplier, null, null, null, null, $current); } diff --git a/includes/functions.php b/includes/functions.php index f5949aae3e..de63f9a0ab 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1366,3 +1366,70 @@ function dnslookup($device,$type=false,$return=false) { $record = dns_get_record($device['hostname'],$type); return $record[0][$return]; }//end dnslookup + + +/** + * Reursive Filter Iterator to iterate directories and locate .rrd files. + * + * @method boolean isDir() + * +**/ + +class RRDRecursiveFilterIterator extends \RecursiveFilterIterator { + + public function accept() { + $filename = $this->current()->getFilename(); + if ($filename[0] === '.') { + // Ignore hidden files and directories + return false; + } + if ($this->isDir()) { + // We want to search into directories + return true; + } + // Matches files with .rrd in the filename. + // We are only searching rrd folder, but there could be other files and we don't want to cause a stink. + return strpos($filename, '.rrd') !== false; + } +} + +/** + * Run rrdtool info on a file path + * + * @param string $path Path to pass to rrdtool info + * @param string $stdOutput Variable to recieve the output of STDOUT + * @param string $stdError Variable to recieve the output of STDERR + * + * @return int exit code + * +**/ + +function rrdtest($path, &$stdOutput, &$stdError) { + global $config; + //rrdtool info + $command = $config['rrdtool'].' info '.escapeshellarg($path); + $process = proc_open( + $command, + array ( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w'), + ), + $pipes + ); + + if (!is_resource($process)) { + throw new \RuntimeException('Could not create a valid process'); + } + + $status = proc_get_status($process); + while($status['running']) { + usleep(2000); // Sleep 2000 microseconds or 2 milliseconds + $status = proc_get_status($process); + } + + $stdOutput = stream_get_contents($pipes[1]); + $stdError = stream_get_contents($pipes[2]); + proc_close($process); + return $status['exitcode']; +} diff --git a/includes/polling/port-adsl.inc.php b/includes/polling/port-adsl.inc.php index e3567e26b5..42b9864288 100644 --- a/includes/polling/port-adsl.inc.php +++ b/includes/polling/port-adsl.inc.php @@ -39,10 +39,7 @@ // adslAturPerfESs.1 = 0 seconds // adslAturPerfValidIntervals.1 = 0 // adslAturPerfInvalidIntervals.1 = 0 -if (isset($port_stats[$port_id]['adslLineCoding'])) { - // Check to make sure Port data is cached. - $this_port = &$port_stats[$port_id]; - +if (isset($this_port['adslLineCoding'])) { $rrdfile = get_port_rrdfile_path ($device['hostname'], $port_id, 'adsl'); $rrd_create = ' --step 300'; diff --git a/includes/polling/port-etherlike.inc.php b/includes/polling/port-etherlike.inc.php index bf947b92db..7299901620 100644 --- a/includes/polling/port-etherlike.inc.php +++ b/includes/polling/port-etherlike.inc.php @@ -1,11 +1,6 @@ $port_id) { } +$ports_found = array (); // New interface detection foreach ($port_stats as $ifIndex => $port) { // Store ifIndex in port entry and prefetch ifName as we'll need it multiple times @@ -238,6 +239,31 @@ foreach ($port_stats as $ifIndex => $port) { // Port newly discovered? if (! $ports[$port_id]) { + /** + * When using the ifName or ifDescr as means to map discovered ports to + * known ports in the DB (think of port association mode) it's possible + * that we're facing the problem that the ifName or ifDescr polled from + * the device is unset or an empty string (like when querying some ubnt + * devices...). If this happends we have no way to map this port to any + * port found in the database. As reported this situation may occur for + * the time of one poll and might resolve automagically before the next + * poller run happens. Without this special case this would lead to new + * ports added to the database each time this situation occurs. To give + * the user the choice between »a lot of new ports« and »some poll runs + * are missed but ports stay stable« the 'ignore_unmapable_port' option + * has been added to configure this behaviour. To skip the port in this + * loop is sufficient as the next loop is looping only over ports found + * in the database and "maps back". As we did not add a new port to the + * DB here, there's no port to be mapped to. + * + * I'm using the in_array() check here, as I'm not sure if an "ifIndex" + * can be legally set to 0, which would yield True when checking if the + * value is empty(). + */ + if ($config['ignore_unmapable_port'] === True and in_array ($port[$port_association_mode], array ('', Null))) { + continue; + } + $port_id = dbInsert(array('device_id' => $device['device_id'], 'ifIndex' => $ifIndex, 'ifName' => $ifName), 'ports'); dbInsert(array('port_id' => $port_id), 'ports_statistics'); $ports[$port_id] = dbFetchRow('SELECT * FROM `ports` WHERE `port_id` = ?', array($port_id)); @@ -255,9 +281,18 @@ foreach ($port_stats as $ifIndex => $port) { dbInsert(array('port_id' => $port_id), 'ports_statistics'); } - // Assure stable mapping - $port_stats[$ifIndex]['port_id'] = $port_id; + /** Assure stable bidirectional port mapping between DB and polled data + * + * Store the *current* ifIndex in the port info array containing all port information + * fetched from the database, as this is the only means we have to map ports_stats we + * just polled from the device to a port in $ports. All code below an includeed below + * will and has to map a port using it's ifIndex. + */ $ports[$port_id]['ifIndex'] = $ifIndex; + $port_stats[$ifIndex]['port_id'] = $port_id; + + /* Build a list of all ports, identified by their port_id, found within this poller run. */ + $ports_found[] = $port_id; } // Port vanished (mark as deleted) @@ -274,11 +309,32 @@ echo "\n"; // Loop ports in the DB and update where necessary foreach ($ports as $port) { $port_id = $port['port_id']; + $ifIndex = $port['ifIndex']; - echo 'Port ' . $port['ifName'] . ': ' . $port['ifDescr'] . '(' . $port['ifIndex'] . ') '; - if ($port_stats[$port['ifIndex']] && $port['disabled'] != '1') { + $port_info_string = 'Port ' . $port['ifName'] . ': ' . $port['ifDescr'] . " ($ifIndex / #$port_id) "; + + /* We don't care for disabled ports, go on */ + if ($port['disabled'] == 1) { + echo "$port_info_string disabled.\n"; + continue; + } + + /** + * If this port did not show up in $port_stats before it has been deleted + * since the last poller run. Mark it deleted in the database and go on. + */ + if (! in_array ($port_id, $ports_found)) { + if ($port['deleted'] != '1') { + dbUpdate(array('deleted' => '1'), 'ports', '`device_id` = ? AND `port_id` = ?', array($device['device_id'], $port_id)); + echo "$port_info_string deleted.\n"; + } + continue; + } + + echo $port_info_string; + if ($port_stats[$ifIndex]) { // Check to make sure Port data is cached. - $this_port = &$port_stats[$port['ifIndex']]; + $this_port = &$port_stats[$ifIndex]; if ($device['os'] == 'vmware' && preg_match('/Device ([a-z0-9]+) at .*/', $this_port['ifDescr'], $matches)) { $this_port['ifDescr'] = $matches[1]; @@ -410,6 +466,9 @@ foreach ($ports as $port) { } } $port['update'][$oid] = $this_port[$oid]; + if ($oid == 'ifOperStatus' || $oid == 'ifAdminStatus') { + $port['update'][$oid.'_prev'] = $port[$oid]; + } log_event($oid.': '.$port[$oid].' -> '.$this_port[$oid], $device, 'interface', $port['port_id']); if ($debug) { d_echo($oid.': '.$port[$oid].' -> '.$this_port[$oid].' '); @@ -622,19 +681,8 @@ foreach ($ports as $port) { $updated += dbUpdate($port['update_extended'], 'ports_statistics', '`port_id` = ?', array($port_id)); d_echo("$updated updated"); } - // End Update Database } - else if ($port['disabled'] != '1') { - echo 'Port Deleted'; - // Port missing from SNMP cache. - if ($port['deleted'] != '1') { - dbUpdate(array('deleted' => '1'), 'ports', '`device_id` = ? AND `port_id` = ?', array($device['device_id'], $port_id)); - } - } - else { - echo 'Port Disabled.'; - }//end if echo "\n"; @@ -644,3 +692,4 @@ foreach ($ports as $port) { // Clear Variables Here unset($port_stats); +unset($ports_found); diff --git a/includes/snmp.inc.php b/includes/snmp.inc.php index 7f806d8677..d2d485998e 100644 --- a/includes/snmp.inc.php +++ b/includes/snmp.inc.php @@ -776,8 +776,8 @@ function snmp_gen_auth(&$device) { } } else if ($device['snmpver'] === 'v2c' or $device['snmpver'] === 'v1') { - $cmd = ' -'.$device['snmpver']; - $cmd .= ' -c '.$device['community']; + $cmd = " -".$device['snmpver']; + $cmd .= " -c '".$device['community']."'"; } else { if ($debug) { diff --git a/sql-schema/099.sql b/sql-schema/099.sql new file mode 100644 index 0000000000..814d2abc91 --- /dev/null +++ b/sql-schema/099.sql @@ -0,0 +1,2 @@ +ALTER TABLE `ports` ADD `ifOperStatus_prev` VARCHAR( 16 ) NULL AFTER `ifOperStatus` ; +ALTER TABLE `ports` ADD `ifAdminStatus_prev` VARCHAR( 16 ) NULL AFTER `ifAdminStatus` ; diff --git a/sql-schema/100.sql b/sql-schema/100.sql new file mode 100644 index 0000000000..646e4c4e1a --- /dev/null +++ b/sql-schema/100.sql @@ -0,0 +1 @@ +INSERT INTO port_association_mode (name) values ('ifAlias'); diff --git a/validate.php b/validate.php index 13db33efd6..f2fe619e32 100755 --- a/validate.php +++ b/validate.php @@ -24,6 +24,7 @@ if (isset($options['h'])) { -m Any sub modules you want to run, comma separated: - mail: this will test your email settings (uses default_mail option even if default_only is not set). - dist-poller: this will test for the install running as a distributed poller. + - rrdcheck: this will check to see if your rrd files are corrupt Example: ./validate.php -m mail. @@ -267,6 +268,46 @@ foreach ($modules as $module) { } } } + break; + case 'rrdcheck': + + // Loop through the rrd_dir + $rrd_directory = new RecursiveDirectoryIterator($config['rrd_dir']); + // Filter out any non rrd files + $rrd_directory_filter = new RRDRecursiveFilterIterator($rrd_directory); + $rrd_iterator = new RecursiveIteratorIterator($rrd_directory_filter); + $rrd_total = iterator_count($rrd_iterator); + $rrd_iterator->rewind(); // Rewind iterator in case iterator_count left iterator in unknown state + + echo "\nScanning ".$rrd_total." rrd files in ".$config['rrd_dir']."...\n"; + + // Count loops so we can push status to the user + $loopcount = 0; + $screenpad = 0; + + foreach ($rrd_iterator as $filename => $file) { + + $rrd_test_result = rrdtest($filename, $output, $error); + + $loopcount++; + if (($loopcount % 50) == 0 ) { + //This lets us update the previous status update without spamming in most consoles + echo "\033[".$screenpad."D"; + $test_status = 'Status: '.$loopcount.'/'.$rrd_total; + echo $test_status; + $screenpad = strlen($test_status); + } + + // A non zero result means there was some kind of error + if ($rrd_test_result > 0) { + echo "\033[".$screenpad."D"; + print_fail('Error parsing "'.$filename.'" RRD '.trim($error)); + $screenpad = 0; + } + } + echo "\033[".$screenpad."D"; + echo "Status: ".$loopcount."/".$rrd_total." - Complete\n"; + break; }//end switch }//end foreach