feature: Added support for sensors to be discovered from yaml (#6859)

* feature: Added support for sensors to be discovered from yaml

* added discovery definitions

* Updated docs, more code updates + tests

* removed the oid_name use

* pre-set variable from scrut output

* small updates as per comments in pr

* Allow index in descr

* Added ability to skip values

* Check for numeric values

* small update of oid name
This commit is contained in:
Neil Lathwood
2017-06-26 23:27:57 +01:00
committed by GitHub
parent 5d5d3816ef
commit 5e32474d26
11 changed files with 314 additions and 236 deletions

View File

@ -48,6 +48,66 @@ We also map these values to the actual state sensor(state_index) where these val
*Is as you might have guessed, where the sensor_id is mapped to a state_index_id.*
### Example
For YAML based state discovery:
```yaml
mib: NETBOTZV2-MIB
modules:
sensors:
state:
-
oid: dryContactSensorTable
value: dryContactSensorValue
num_oid: .1.3.6.1.4.1.5528.100.4.2.1.1.2.
descr: dryContactSensorLabel
index: 'dryContactSensor.{{ $index }}'
state_name: dryContactSensor
states:
- { descr: 'null', graph: 0, value: -1, generic: 3 }
- { descr: open, graph: 0, value: 0, generic: 0 }
- { descr: closed, graph: 0, value: 1, generic: 2 }
-
oid: doorSwitchSensorTable
value: doorSwitchSensorValue
num_oid: .1.3.6.1.4.1.5528.100.4.2.2.1.2.
descr: doorSwitchSensorLabel
index: 'doorSwitchSensor.{{ $index }}'
state_name: doorSwitchSensor
states:
- { descr: 'null', graph: 0, value: -1, generic: 3 }
- { descr: open, graph: 0, value: 0, generic: 0 }
- { descr: closed, graph: 0, value: 1, generic: 2 }
-
oid: cameraMotionSensorTable
value: cameraMotionSensorValue
num_oid: .1.3.6.1.4.1.5528.100.4.2.3.1.2.
descr: cameraMotionSensorLabel
index: 'cameraMotionSensor.{{ $index }}'
state_name: cameraMotionSensor
states:
- { descr: 'null', graph: 0, value: -1, generic: 3 }
- { descr: noMotion, graph: 0, value: 0, generic: 0 }
- { descr: motionDetected, graph: 0, value: 1, generic: 2 }
-
oid: otherStateSensorTable
value: otherStateSensorErrorStatus
num_oid: .1.3.6.1.4.1.5528.100.4.2.10.1.3.
descr: otherStateSensorLabel
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 }
```
For advanced state discovery:
This example will be based on a Cisco power supply sensor and is all it takes to have sensor state support for Cisco power supplys in Cisco switches.
The file should be located in /includes/discovery/sensors/state/cisco.inc.php.

View File

@ -1,5 +1,7 @@
source: Developing/os/Health-Information.md
#### Sensors
This document will guide you through adding health / sensor information for your new device.
Currently we have support for the following health metrics along with the values we expect to see the data in:
@ -17,10 +19,62 @@ Currently we have support for the following health metrics along with the values
| power | W |
| runtime | Min |
| signal | dBm |
| snr | SNR |
| state | # |
| temperature | C |
| voltage | V |
#### Simple health discovery
We have support for defining health / sensor discovery using YAML files so that you don't need to know how to write PHP.
All yaml files are located in `includes/definitions/discovery/$os.yaml`. Defining the information hear is not always
possible and is heavily reliant on vendors being sensible with the MIBs they generate. Only snmp walks are supported
and you must provide a sane table that can be traversed and contains all of the data you need. We will use netbotz as
an example here.
`includes/definitions/discovery/netbotz.yaml`
```yaml
mib: NETBOTZV2-MIB
modules:
sensors:
airflow:
-
oid_name: netbotz_airflow
oid: airFlowSensorTable
value: airFlowSensorValue
divisor: 10
num_oid: .1.3.6.1.4.1.5528.100.4.1.5.1.2.
descr: airFlowSensorLabel
index: 'airFlowSensorValue.{{ $index }}'
```
At the top you can define one or more mibs to be used in the lookup of data:
`mib: NETBOTZV2-MIB`
The only sensor we have defined here is airflow. The available options are as follows:
- `oid` (required): This is the name of the table you want to do the snmp walk on.
- `value` (required): This is the key within the table that contains the value.
- `num_oid` (required): This is the numerical OID that contains `value`. This should always be without the appended `index`.
- `divisor` (optional): This is the divisor to use against the returned `value`.
- `multiplier` (optional): This is the multiplier to use against the returned `value`.
- `low_limit` (optional): This is the critical low threshold that `value` should be (used in alerting).
- `low_warn_limit` (optional): This is the warning low threshold that `value` should be (used in alerting).
- `warn_limit` (optional): This is the warning high threshold that `value` should be (used in alerting).
- `high_limit` (optional): This is the critical high threshold that `value` should be (used in alerting).
- `descr` (required): This is the key within the table that contains the description of this sensor.
- `index` (optional): This is the index value we use to uniquely identify this sensor. `{{ $index }}` will be replaced by the `index` from the snmp walk.
- `skip_values` (optional): This is an array of values we should skip over.
If you aren't able to use yaml to perform the sensor discovery, you will most likely need to use Advanced health discovery.
#### Advanced health discovery
If you can't use the yaml files as above, then you will need to create the discovery code in php.
The directory structure for sensor information is `includes/discovery/sensors/$class/$os.inc.php`. The format of all
of the sensors follows the same code format which is to call the `discover_sensor()` function - with the
exception of state which requires additional code.

View File

@ -0,0 +1,80 @@
mib: NETBOTZV2-MIB
modules:
sensors:
airflow:
-
oid: airFlowSensorTable
value: airFlowSensorValue
divisor: 10
num_oid: .1.3.6.1.4.1.5528.100.4.1.5.1.2.
descr: airFlowSensorLabel
index: 'airFlowSensorValue.{{ $index }}'
temperature:
-
oid: dewPointSensorTable
value: dewPointSensorValue
divisor: 10
num_oid: .1.3.6.1.4.1.5528.100.4.1.3.1.2.
descr: dewPointSensorLabel
index: 'dewPointSensorValue.{{ $index }}'
-
oid: tempSensorTable
value: tempSensorValueInt
num_oid: .1.3.6.1.4.1.5528.100.4.1.1.1.8.
descr: tempSensorLabel
index: '{{ $index }}'
humidity:
-
oid: humiSensorTable
value: humiSensorValue
num_oid: .1.3.6.1.4.1.5528.100.4.1.2.1.8.
descr: humiSensorLabel
index: '{{ $index }}'
state:
-
oid: dryContactSensorTable
value: dryContactSensorValue
num_oid: .1.3.6.1.4.1.5528.100.4.2.1.1.2.
descr: dryContactSensorLabel
index: 'dryContactSensor.{{ $index }}'
state_name: dryContactSensor
states:
- { descr: 'null', graph: 0, value: -1, generic: 3 }
- { descr: open, graph: 0, value: 0, generic: 0 }
- { descr: closed, graph: 0, value: 1, generic: 2 }
-
oid: doorSwitchSensorTable
value: doorSwitchSensorValue
num_oid: .1.3.6.1.4.1.5528.100.4.2.2.1.2.
descr: doorSwitchSensorLabel
index: 'doorSwitchSensor.{{ $index }}'
state_name: doorSwitchSensor
states:
- { descr: 'null', graph: 0, value: -1, generic: 3 }
- { descr: open, graph: 0, value: 0, generic: 0 }
- { descr: closed, graph: 0, value: 1, generic: 2 }
-
oid: cameraMotionSensorTable
value: cameraMotionSensorValue
num_oid: .1.3.6.1.4.1.5528.100.4.2.3.1.2.
descr: cameraMotionSensorLabel
index: 'cameraMotionSensor.{{ $index }}'
state_name: cameraMotionSensor
states:
- { descr: 'null', graph: 0, value: -1, generic: 3 }
- { descr: noMotion, graph: 0, value: 0, generic: 0 }
- { descr: motionDetected, graph: 0, value: 1, generic: 2 }
-
oid: otherStateSensorTable
value: otherStateSensorErrorStatus
num_oid: .1.3.6.1.4.1.5528.100.4.2.10.1.3.
descr: otherStateSensorLabel
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 }

View File

@ -85,9 +85,23 @@ function discover_new_device($hostname, $device = '', $method = '', $interface =
d_echo("$ip not in a matched network - skipping\n");
}//end if
}
//end discover_new_device()
/**
* @param $device
*/
function load_discovery(&$device)
{
global $config;
$yaml_discovery = $config['install_dir'] . '/includes/definitions/discovery/' . $device['os'] . '.yaml';
if (file_exists($yaml_discovery)) {
$device['dynamic_discovery'] = Symfony\Component\Yaml\Yaml::parse(
file_get_contents($yaml_discovery)
);
}
unset($yaml_discovery);
}
function discover_device(&$device, $options = null)
{
global $config, $valid;
@ -119,6 +133,7 @@ function discover_device(&$device, $options = null)
}
load_os($device);
load_discovery($device);
if (is_array($config['os'][$device['os']]['register_mibs'])) {
register_mibs($device, $config['os'][$device['os']]['register_mibs'], 'includes/discovery/os/' . $device['os'] . '.inc.php');
}
@ -182,14 +197,11 @@ function discover_device(&$device, $options = null)
echo "\n";
$discovered_devices++;
}
//end discover_device()
// Discover sensors
function discover_sensor(&$valid, $class, $device, $oid, $index, $type, $descr, $divisor = 1, $multiplier = 1, $low_limit = null, $low_warn_limit = null, $warn_limit = null, $high_limit = null, $current = null, $poller_type = 'snmp', $entPhysicalIndex = null, $entPhysicalIndex_measured = null, $user_func = null)
{
$low_limit = set_null($low_limit);
$low_warn_limit = set_null($low_warn_limit);
$warn_limit = set_null($warn_limit);
@ -1032,6 +1044,70 @@ function ignore_storage($descr)
return $deny;
}
function discovery_process(&$valid, $device, $sensor_type, $pre_cache)
{
if ($device['dynamic_discovery']['modules']['sensors'][$sensor_type]) {
foreach ($device['dynamic_discovery']['modules']['sensors'][$sensor_type] as $data) {
$tmp_name = $data['oid'];
$raw_data = $pre_cache[$tmp_name];
foreach ($raw_data as $index => $snmp_data) {
$skip = false;
$value = $snmp_data[$data['value']];
foreach ((array)$data['skip_values'] as $skip_value) {
echo "Here $value and $skip_value END\n";
if ($value == $skip_value) {
$skip = true;
}
}
if ($skip === false && is_numeric($value)) {
$oid = $data['num_oid'] . $index;
if (isset($snmp_data[$data['descr']])) {
$descr = $snmp_data[$data['descr']];
} else {
$descr = str_replace('{{ $index }}', $index, $data['descr']);
}
$divisor = $data['divisor'] ?: 1;
$multiplier = $data['multiplier'] ?: 1;
$low_limit = $data['low_limit'] ?: 'null';
$low_warn_limit = $data['low_warn_limit'] ?: 'null';
$warn_limit = $data['warn_limit'] ?: 'null';
$high_limit = $data['high_limit'] ?: 'null';
$state_name = '';
if ($sensor_type !== 'state') {
if (is_numeric($divisor)) {
$value = $value / $divisor;
}
if (is_numeric($multiplier)) {
$value = $value * $multiplier;
}
} else {
$state_name = $data['descr'];
$state_index_id = create_state_index($state_name);
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);
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);
}
}
}
}
}
}
/**
* @param $types
* @param $device
@ -1055,6 +1131,7 @@ function sensors($types, $device, $valid, $pre_cache = array())
include $dir . '/rfc1628.inc.php';
}
}
discovery_process($valid, $device, $sensor_type, $pre_cache);
d_echo($valid['sensor'][$sensor_type]);
check_valid_sensors($device, $sensor_type, $valid['sensor']);
echo "\n";

View File

@ -12,6 +12,19 @@ if (is_file($pre_cache_file)) {
d_echo($pre_cache);
}
if (isset($device['dynamic_discovery']['modules']['sensors'])) {
foreach ($device['dynamic_discovery']['modules']['sensors'] as $key => $data_array) {
foreach ($data_array as $data) {
foreach ((array)$data['oid'] as $oid) {
$tmp_name = $oid;
if (!isset($pre_cache[$tmp_name])) {
$pre_cache[$tmp_name] = snmpwalk_cache_oid($device, $oid, array(), $device['dynamic_discovery']['mib'], null, '-OeQUs');
}
}
}
}
}
// Run custom sensors
require 'includes/discovery/sensors/cisco-entity-sensor.inc.php';
require 'includes/discovery/sensors/entity-sensor.inc.php';

View File

@ -1,43 +0,0 @@
<?php
/**
* netbotz.inc.php
*
* LibreNMS airflow discovery module for Netbotz
*
* 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/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2017 Neil Lathwood
* @author Neil Lathwood <neil@lathwood.co.uk>
*/
d_echo($pre_cache['netbotz_airflow']);
if (is_array($pre_cache['netbotz_airflow'])) {
echo 'NetBotz ';
foreach ($pre_cache['netbotz_airflow'] as $index => $data) {
if ($data['airFlowSensorValue']) {
$divisor = 10;
$multiplier = 1;
$value = $data['airFlowSensorValue'] / $divisor;
$oid = '.1.3.6.1.4.1.5528.100.4.1.5.1.2.' . $index;
$index = 'airFlowSensorValue.' . $index;
$descr = $data['airFlowSensorLabel'];
if (is_numeric($value)) {
discover_sensor($valid['sensor'], 'airflow', $device, $oid, $index, 'netbotz', $descr, $divisor, $multiplier, null, null, null, null, $value);
}
}
}
}

View File

@ -1,26 +0,0 @@
<?php
$oids = snmp_walk($device, '.1.3.6.1.4.1.5528.100.4.1.2.1.4', '-Osqn', '');
d_echo($oids."\n");
$oids = trim($oids);
if ($oids) {
echo 'Netbotz ';
foreach (explode("\n", $oids) as $data) {
list($oid,$descr) = explode(' ', $data, 2);
$split_oid = explode('.', $oid);
$humidity_id = $split_oid[(count($split_oid) - 1)];
// tempHumidSensorHumidValue
$humidity_oid = '.1.3.6.1.4.1.5528.100.4.1.2.1.8.'.$humidity_id;
$humidity = snmp_get($device, "$humidity_oid", '-Ovq', '');
$descr = str_replace('"', '', $descr);
$descr = trim($descr);
if ($humidity >= 0) {
discover_sensor($valid['sensor'], 'humidity', $device, $humidity_oid, $humidity_id, 'netbotz', $descr, '1', '1', null, null, null, null, $humidity);
}
}
unset($data);
}
unset($oids);

View File

@ -1,30 +0,0 @@
<?php
/**
* netbotz.inc.php
*
* LibreNMS pre-cache discovery module for Netbotz
*
* 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/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2017 Neil Lathwood
* @author Neil Lathwood <neil@lathwood.co.uk>
*/
$pre_cache['netbotz_airflow'] = snmpwalk_cache_oid($device, 'airFlowSensorTable', array(), 'NETBOTZV2-MIB');
$pre_cache['netbotz_temperature'] = snmpwalk_cache_oid($device, 'dewPointSensorTable', array(), 'NETBOTZV2-MIB');
$pre_cache['netbotz_state'] = snmpwalk_cache_oid($device, 'dryContactSensorTable', array(), 'NETBOTZV2-MIB', null, '-OeQUs');
$pre_cache['netbotz_state'] = snmpwalk_cache_oid($device, 'doorSwitchSensorTable', $pre_cache['netbotz_state'], 'NETBOTZV2-MIB', null, '-OeQUs');
$pre_cache['netbotz_state'] = snmpwalk_cache_oid($device, 'cameraMotionSensorTable', $pre_cache['netbotz_state'], 'NETBOTZV2-MIB', null, '-OeQUs');

View File

@ -9,94 +9,3 @@
* option) any later version. Please see LICENSE.txt at the top level of
* the source code distribution for details.
*/
$temp = snmpwalk_cache_multi_oid($device, 'otherStateSensorTable', array(), 'NETBOTZ410-MIB');
$cur_oid = '.1.3.6.1.4.1.5528.100.4.2.10.1.3.';
if (is_array($temp)) {
//Create State Index
$state_name = 'otherStateSensorErrorStatus';
$state_index_id = create_state_index($state_name);
//Create State Translation
if ($state_index_id !== null) {
$states = array(
array($state_index_id, 'normal', 0, 0, 0),
array($state_index_id, 'info', 0, 1, 1),
array($state_index_id, 'warning', 0, 2, 1),
array($state_index_id, 'error', 0, 3, 2),
array($state_index_id, 'critical', 0, 4, 2),
array($state_index_id, 'failure', 0, 5, 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 ($temp as $index => $entry) {
$descr = $temp[$index]['otherStateSensorLabel'];
//Discover Sensors
discover_sensor($valid['sensor'], 'state', $device, $cur_oid . $index, $index, $state_name, $descr, '1', '1', null, null, null, null, $temp[$index][' otherStateSensorErrorStatus'], 'snmp', $index);
//Create Sensor To State Index
create_sensor_to_state_index($device, $state_name, $index);
}
}
foreach ($pre_cache['netbotz_state'] as $index => $data) {
if (is_array($data)) {
$tmp_keys = array_keys($data);
$state_name = str_replace('Id', '', $tmp_keys[0]);
$state_index_id = create_state_index($state_name);
if ($state_name === 'cameraMotionSensor') {
$states = array(
array($state_index_id, 'null', 0, -1, 3),
array($state_index_id, 'noMotion', 0, 0, 0),
array($state_index_id, 'motionDetected', 0, 1, 2),
);
$oid = '.1.3.6.1.4.1.5528.100.4.2.3.1.2.';
} elseif ($state_name === 'doorSwitchSensor') {
$states = array(
array($state_index_id, 'null', 0, -1, 3),
array($state_index_id, 'open', 0, 0, 0),
array($state_index_id, 'closed', 0, 1, 2),
);
$oid = '.1.3.6.1.4.1.5528.100.4.2.2.1.2.';
} elseif ($state_name === 'dryContactSensor') {
$states = array(
array($state_index_id, 'null', 0, -1, 3),
array($state_index_id, 'open', 0, 0, 0),
array($state_index_id, 'closed', 0, 1, 2),
);
$oid = '.1.3.6.1.4.1.5528.100.4.2.1.1.2.';
}
if ($state_index_id !== null) {
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');
}
}
$cur_oid = $oid . $index;
$index = $state_name . '.' . $index;
$descr = $data[$state_name . 'Label'];
$value = $data[$state_name . 'Value'];
if (isset($value)) {
discover_sensor($valid['sensor'], 'state', $device, $cur_oid, $index, $state_name, $descr, 1, 1, null, null, null, null, $value);
create_sensor_to_state_index($device, $state_name, $index);
}
}
}

View File

@ -1,40 +0,0 @@
<?php
$oids = snmp_walk($device, '.1.3.6.1.4.1.5528.100.4.1.1.1.4', '-Osqn', '');
d_echo($oids."\n");
$oids = trim($oids);
if ($oids) {
echo 'NetBotz ';
foreach (explode("\n", $oids) as $data) {
list($oid,$descr) = explode(' ', $data, 2);
$split_oid = explode('.', $oid);
$temperature_id = $split_oid[(count($split_oid) - 1)];
$temperature_oid = ".1.3.6.1.4.1.5528.100.4.1.1.1.8.$temperature_id";
$temperature = snmp_get($device, $temperature_oid, '-Ovq');
$descr = str_replace('"', '', $descr);
$descr = trim($descr);
if ($temperature != '0' && $temperature <= '1000') {
discover_sensor($valid['sensor'], 'temperature', $device, $temperature_oid, $temperature_id, 'netbotz', $descr, '1', '1', null, null, null, null, $temperature);
}
}
}
d_echo($pre_cache['netbotz_temperature']);
if (is_array($pre_cache['netbotz_temperature'])) {
echo 'NetBotz ';
foreach ($pre_cache['netbotz_temperature'] as $index => $data) {
if ($data['dewPointSensorValue']) {
$divisor = 10;
$multiplier = 1;
$value = $data['dewPointSensorValue'] / $divisor;
$oid = '.1.3.6.1.4.1.5528.100.4.1.3.1.2.' . $index;
$index = 'dewPointSensorValue.' . $index;
$descr = $data['dewPointSensorLabel'];
if (is_numeric($value)) {
discover_sensor($valid['sensor'], 'temperature', $device, $oid, $index, 'netbotz', $descr, $divisor, $multiplier, null, null, null, null, $value);
}
}
}
}

View File

@ -32,7 +32,7 @@ use PHPUnit_Framework_ExpectationFailedException as PHPUnitException;
class YamlTest extends \PHPUnit_Framework_TestCase
{
public function testYaml()
public function testOSYaml()
{
global $config;
@ -49,4 +49,28 @@ class YamlTest extends \PHPUnit_Framework_TestCase
$this->assertArrayHasKey('text', $data, $file);
}
}
public function testDiscoveryYaml()
{
global $config;
$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 $sub_module) {
foreach ($sub_module as $sensor) {
$this->assertArrayHasKey('oid', $sensor, $file);
$this->assertArrayHasKey('num_oid', $sensor, $file);
$this->assertArrayHasKey('value', $sensor, $file);
}
}
}
}
}
}