diff --git a/python/nav/ipdevpoll/plugins/sensors.py b/python/nav/ipdevpoll/plugins/sensors.py index 7defdb939e..650a2c06c0 100644 --- a/python/nav/ipdevpoll/plugins/sensors.py +++ b/python/nav/ipdevpoll/plugins/sensors.py @@ -126,6 +126,9 @@ def _store_sensors(self, result): sensor.on_message_sys = row.get('on_message') sensor.off_message_sys = row.get('off_message') sensor.on_state_sys = row.get('on_state') + sensor.threshold_type = row.get('threshold_type') + sensor.threshold_alert_type = row.get('threshold_alert_type') + sensor.threshold_for_oid = row.get('threshold_for_oid') if ifindex: iface = self.containers.factory(ifindex, shadows.Interface) iface.netbox = self.netbox diff --git a/python/nav/mibs/juniper_dom_mib.py b/python/nav/mibs/juniper_dom_mib.py index b8c71550de..cd58910f96 100644 --- a/python/nav/mibs/juniper_dom_mib.py +++ b/python/nav/mibs/juniper_dom_mib.py @@ -23,7 +23,7 @@ from nav.mibs.mibretriever import MibRetriever from nav.models.manage import Sensor -COLUMNS = { +SENSOR_COLUMNS = { "jnxDomCurrentRxLaserPower": { "unit_of_measurement": Sensor.UNIT_DBM, "precision": 2, @@ -51,6 +51,97 @@ }, } +THRESHOLD_COLUMNS = { + "jnxDomCurrentRxLaserPower": { + "jnxDomCurrentRxLaserPowerHighAlarmThreshold": { + "name": "{ifc} RX Laser Power High Alarm Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_HIGH, + "threshold_alert_type": Sensor.ALERT_TYPE_ALERT, + }, + "jnxDomCurrentRxLaserPowerLowAlarmThreshold": { + "name": "{ifc} RX Laser Power Low Alarm Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_LOW, + "threshold_alert_type": Sensor.ALERT_TYPE_ALERT, + }, + "jnxDomCurrentRxLaserPowerHighWarningThreshold": { + "name": "{ifc} RX Laser Power High Warning Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_HIGH, + "threshold_alert_type": Sensor.ALERT_TYPE_WARNING, + }, + "jnxDomCurrentRxLaserPowerLowWarningThreshold": { + "name": "{ifc} RX Laser Power Low Warning Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_LOW, + "threshold_alert_type": Sensor.ALERT_TYPE_WARNING, + }, + }, + "jnxDomCurrentTxLaserBiasCurrent": { + "jnxDomCurrentTxLaserBiasCurrentHighAlarmThreshold": { + "name": "{ifc} TX Laser Bias Current High Alarm Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_HIGH, + "threshold_alert_type": Sensor.ALERT_TYPE_ALERT, + }, + "jnxDomCurrentTxLaserBiasCurrentLowAlarmThreshold": { + "name": "{ifc} TX Laser Bias Current Low Alarm Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_LOW, + "threshold_alert_type": Sensor.ALERT_TYPE_ALERT, + }, + "jnxDomCurrentTxLaserBiasCurrentHighWarningThreshold": { + "name": "{ifc} TX Laser Bias Current High Warning Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_HIGH, + "threshold_alert_type": Sensor.ALERT_TYPE_WARNING, + }, + "jnxDomCurrentTxLaserBiasCurrentLowWarningThreshold": { + "name": "{ifc} TX Laser Bias Current Low Warning Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_LOW, + "threshold_alert_type": Sensor.ALERT_TYPE_WARNING, + }, + }, + "jnxDomCurrentTxLaserOutputPower": { + "jnxDomCurrentTxLaserOutputPowerHighAlarmThreshold": { + "name": "{ifc} TX Laser Output Power High Alarm Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_HIGH, + "threshold_alert_type": Sensor.ALERT_TYPE_ALERT, + }, + "jnxDomCurrentTxLaserOutputPowerLowAlarmThreshold": { + "name": "{ifc} TX Laser Output Power Low Alarm Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_LOW, + "threshold_alert_type": Sensor.ALERT_TYPE_ALERT, + }, + "jnxDomCurrentTxLaserOutputPowerHighWarningThreshold": { + "name": "{ifc} TX Laser Output Power High Warning Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_HIGH, + "threshold_alert_type": Sensor.ALERT_TYPE_WARNING, + }, + "jnxDomCurrentTxLaserOutputPowerLowWarningThreshold": { + "name": "{ifc} TX Laser Output Power Low Warning Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_LOW, + "threshold_alert_type": Sensor.ALERT_TYPE_WARNING, + }, + }, + "jnxDomCurrentModuleTemperature": { + "jnxDomCurrentModuleTemperatureHighAlarmThreshold": { + "name": "{ifc} Module Temperature High Alarm Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_HIGH, + "threshold_alert_type": Sensor.ALERT_TYPE_ALERT, + }, + "jnxDomCurrentModuleTemperatureLowAlarmThreshold": { + "name": "{ifc} Module Temperature Low Alarm Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_LOW, + "threshold_alert_type": Sensor.ALERT_TYPE_ALERT, + }, + "jnxDomCurrentModuleTemperatureHighWarningThreshold": { + "name": "{ifc} Module Temperature High Warning Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_HIGH, + "threshold_alert_type": Sensor.ALERT_TYPE_WARNING, + }, + "jnxDomCurrentModuleTemperatureLowWarningThreshold": { + "name": "{ifc} Module Temperature Low Warning Threshold", + "threshold_type": Sensor.THRESHOLD_TYPE_LOW, + "threshold_alert_type": Sensor.ALERT_TYPE_WARNING, + }, + }, +} + class JuniperDomMib(MibRetriever): """MibRetriever for Juniper DOM Sensors""" @@ -63,12 +154,21 @@ def get_all_sensors(self): device. """ sensors = [] - for column, config in COLUMNS.items(): - sensors += yield self.handle_column(column, config) + for sensor_column, sensor_config in SENSOR_COLUMNS.items(): + sensors += yield self.handle_sensor_column(sensor_column, sensor_config) + for threshold_column, threshold_config in THRESHOLD_COLUMNS[ + sensor_column + ].items(): + sensors += yield self.handle_threshold_column( + threshold_column, + threshold_config, + sensor_column, + sensor_config, + ) returnValue(sensors) @defer.inlineCallbacks - def handle_column(self, column, config): + def handle_sensor_column(self, column, config): """Returns the sensors of the given type""" result = [] value_oid = self.nodes[column].oid @@ -84,3 +184,28 @@ def handle_column(self, column, config): sensor.update(config) result.append(sensor) returnValue(result) + + @defer.inlineCallbacks + def handle_threshold_column( + self, column, config, related_sensor_column, related_sensor_config + ): + """Returns the sensor thresholds of the given type""" + result = [] + value_oid = self.nodes[column].oid + related_sensor_oid = self.nodes[related_sensor_column].oid + rows = yield self.retrieve_column(column) + for row in rows: + threshold_sensor = dict( + oid=str(value_oid + row), + scale=None, + mib=self.get_module_name(), + description=config['name'], + internal_name="{ifc}." + column, + ifindex=row[-1], + unit_of_measurement=related_sensor_config['unit_of_measurement'], + precision=related_sensor_config['precision'], + threshold_for_oid=str(related_sensor_oid + row), + ) + threshold_sensor.update(config) + result.append(threshold_sensor) + returnValue(result) diff --git a/python/nav/models/manage.py b/python/nav/models/manage.py index fce4d1c26a..a0c5350156 100644 --- a/python/nav/models/manage.py +++ b/python/nav/models/manage.py @@ -2331,6 +2331,13 @@ class Sensor(models.Model): (ALERT_TYPE_WARNING, 'An orange warning'), ) + THRESHOLD_TYPE_HIGH = 1 + THRESHOLD_TYPE_LOW = 2 + THRESHOLD_TYPE_CHOICES = ( + (THRESHOLD_TYPE_HIGH, 'Threshold for high values'), + (THRESHOLD_TYPE_LOW, 'Threshold for low values'), + ) + id = models.AutoField(db_column='sensorid', primary_key=True) netbox = models.ForeignKey(Netbox, on_delete=models.CASCADE, db_column='netboxid') interface = models.ForeignKey( @@ -2365,6 +2372,13 @@ class Sensor(models.Model): alert_type = models.IntegerField( db_column='alert_type', choices=ALERT_TYPE_CHOICES, null=True ) + threshold_type = models.IntegerField( + db_column='threshold_type', choices=THRESHOLD_TYPE_CHOICES, null=True + ) + threshold_alert_type = models.IntegerField( + db_column='threshold_alert_type', choices=ALERT_TYPE_CHOICES, null=True + ) + threshold_for_oid = VarcharField(db_column='threshold_for_oid', null=True) class Meta(object): db_table = 'sensor' @@ -2484,6 +2498,29 @@ def get_display_configuration(self): } return {} + @property + def threshold_for(self): + """Returns the sensor this is a threshold for. + Returns None if no such sensor exists. + """ + if not self.threshold_for_oid: + return None + try: + return self.__class__.objects.get( + netbox=self.netbox, oid=self.threshold_for_oid, mib=self.mib + ) + except self.DoesNotExist: + _logger.error("Could not find sensor with oid %s", self.threshold_for_oid) + return None + + @property + def thresholds(self): + """Returns list of all threshold-sensors for this sensor""" + thresholds = self.__class__.objects.filter( + netbox=self.netbox, threshold_for_oid=self.oid, mib=self.mib + ) + return list(thresholds) + class PowerSupplyOrFan(models.Model): STATE_UP = u'y' diff --git a/python/nav/models/sql/changes/sc.05.05.0002.sql b/python/nav/models/sql/changes/sc.05.05.0002.sql new file mode 100644 index 0000000000..fd7dbf3b57 --- /dev/null +++ b/python/nav/models/sql/changes/sc.05.05.0002.sql @@ -0,0 +1,5 @@ +ALTER TABLE sensor + ADD threshold_type INT, + ADD threshold_alert_type INT, + ADD threshold_for_oid VARCHAR +; diff --git a/tests/integration/models/sensor_test.py b/tests/integration/models/sensor_test.py new file mode 100644 index 0000000000..10f634c614 --- /dev/null +++ b/tests/integration/models/sensor_test.py @@ -0,0 +1,78 @@ +from nav.models.manage import Sensor +import pytest + + +class TestSensor: + def test_threshold_for_property_returns_the_sensor_the_current_sensor_is_a_threshold_for( + self, db, sensor, threshold_sensor1 + ): + threshold_for_sensor = threshold_sensor1.threshold_for + assert threshold_for_sensor == sensor + + def test_thresholds_property_returns_all_sensors_that_are_thresholds_for_current_sensor( + self, db, sensor, threshold_sensor1, threshold_sensor2 + ): + threshold_sensors = sensor.thresholds + expected_threshold_sensors = Sensor.objects.filter( + threshold_for_oid=sensor.oid + ).all() + assert set(threshold_sensors) == set(expected_threshold_sensors) + + +@pytest.fixture +def threshold_sensor1(db, localhost): + sensor = Sensor( + netbox=localhost, + oid="1.2.4", + unit_of_measurement=Sensor.UNIT_OTHER, + data_scale=Sensor.SCALE_MILLI, + precision=1, + human_readable="threshold_sensor1", + name="threshold_sensor1", + internal_name="threshold_sensor1", + mib="testmib", + threshold_for_oid="1.2.3", + ) + sensor.save() + yield sensor + if sensor.pk: + sensor.delete() + + +@pytest.fixture +def threshold_sensor2(db, localhost): + sensor = Sensor( + netbox=localhost, + oid="1.2.5", + unit_of_measurement=Sensor.UNIT_OTHER, + data_scale=Sensor.SCALE_MILLI, + precision=1, + human_readable="threshold_sensor2", + name="threshold_sensor2", + internal_name="threshold_sensor2", + mib="testmib", + threshold_for_oid="1.2.3", + ) + sensor.save() + yield sensor + if sensor.pk: + sensor.delete() + + +@pytest.fixture +def sensor(db, localhost): + sensor = Sensor( + netbox=localhost, + oid="1.2.3", + unit_of_measurement=Sensor.UNIT_OTHER, + data_scale=Sensor.SCALE_MILLI, + precision=1, + human_readable="value", + name="sensor", + internal_name="sensor", + mib="testmib", + ) + sensor.save() + yield sensor + if sensor.pk: + sensor.delete() diff --git a/tests/unittests/mibs/juniper_dom_mib_test.py b/tests/unittests/mibs/juniper_dom_mib_test.py new file mode 100644 index 0000000000..7c0c888bd1 --- /dev/null +++ b/tests/unittests/mibs/juniper_dom_mib_test.py @@ -0,0 +1,47 @@ +from unittest import TestCase +from mock import patch, Mock + +from twisted.internet import defer +from twisted.internet.defer import succeed + +from nav.mibs.juniper_dom_mib import JuniperDomMib, SENSOR_COLUMNS, THRESHOLD_COLUMNS + + +class TestJuniperDomMIB(TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_handle_sensor_column_returns_correct_amount_of_sensor_dicts(self): + rows_for_column = {"0": "interface 0", "1": "interface 1"} + + with patch( + 'nav.mibs.juniper_dom_mib.JuniperDomMib.retrieve_column' + ) as retrieve: + retrieve.return_value = succeed(rows_for_column) + mib = JuniperDomMib(Mock()) + column = "jnxDomCurrentRxLaserPower" + deferred_result = mib.handle_sensor_column(column, SENSOR_COLUMNS[column]) + result = deferred_result.result + self.assertEqual(len(result), len(rows_for_column)) + + def test_handle_threshold_column_returns_correct_amount_of_sensor_dicts(self): + rows_for_column = {"0": "interface 0", "1": "interface 1"} + + with patch( + 'nav.mibs.juniper_dom_mib.JuniperDomMib.retrieve_column' + ) as retrieve: + retrieve.return_value = succeed(rows_for_column) + mib = JuniperDomMib(Mock()) + threshold_column = "jnxDomCurrentRxLaserPowerHighAlarmThreshold" + sensor_column = "jnxDomCurrentRxLaserPower" + deferred_result = mib.handle_threshold_column( + threshold_column, + THRESHOLD_COLUMNS[sensor_column][threshold_column], + sensor_column, + SENSOR_COLUMNS[sensor_column], + ) + result = deferred_result.result + self.assertEqual(len(result), len(rows_for_column), result)