From d46b5685c4bf4f63a678687fb4cde8e499f6c9cd Mon Sep 17 00:00:00 2001 From: purhan Date: Sat, 5 Jun 2021 19:29:44 +0530 Subject: [PATCH] [change] NetJSON DeviceMonitoring compliance #2 Closes #2 --- netengine/backends/schema.py | 152 +++++++++++++++++++++++++++++ netengine/backends/snmp/airos.py | 30 ++---- netengine/backends/snmp/openwrt.py | 26 +---- requirements-test.txt | 1 + tests/static/test-airos-snmp.json | 8 +- tests/test_snmp/test_airos.py | 10 +- tests/test_snmp/test_openwrt.py | 14 ++- tests/utils.py | 7 +- 8 files changed, 195 insertions(+), 53 deletions(-) create mode 100644 netengine/backends/schema.py diff --git a/netengine/backends/schema.py b/netengine/backends/schema.py new file mode 100644 index 0000000..af707f0 --- /dev/null +++ b/netengine/backends/schema.py @@ -0,0 +1,152 @@ +# NetJSON DeviceMonitoring schema, +# https://github.com/netjson/netjson/blob/master/schema/device-monitoring.json +schema = { + '$schema': 'http://json-schema.org/draft-07/schema#', + '$id': 'https://raw.githubusercontent.com/netjson/netjson/master/schema/device-monitoring.json', + 'title': 'NetJSON Device Monitoring', + 'description': 'Monitoring information sent by a device.', + 'type': 'object', + 'additionalProperties': True, + 'required': ['type'], + 'properties': { + 'type': {'type': 'string', 'enum': ['DeviceMonitoring']}, + 'general': { + 'type': 'object', + 'title': 'General', + 'additionalProperties': True, + 'properties': { + 'local_time': {'type': 'integer'}, + 'uptime': {'type': 'integer'}, + }, + }, + 'resources': { + 'type': 'object', + 'title': 'Resources', + 'additionalProperties': True, + 'properties': { + 'load': { + 'type': 'array', + 'items': {'type': 'number', 'minItems': 3, 'maxItems': 3}, + }, + 'memory': { + 'id': 'memory', + 'type': 'object', + 'properties': { + 'total': {'type': 'integer'}, + 'free': {'type': 'integer'}, + 'buffered': {'type': 'integer'}, + 'cache': {'type': 'integer'}, + }, + }, + 'swap': { + 'type': 'object', + 'properties': { + 'total': {'type': 'integer'}, + 'free': {'type': 'integer'}, + }, + }, + 'connections': { + 'type': 'object', + 'properties': { + 'ipv4': { + 'type': 'object', + 'properties': { + 'tcp': {'type': 'integer'}, + 'udp': {'type': 'integer'}, + }, + }, + 'ipv6': { + 'type': 'object', + 'properties': { + 'tcp': {'type': 'integer'}, + 'udp': {'type': 'integer'}, + }, + }, + }, + }, + 'processes': { + 'type': 'object', + 'properties': { + 'running': {'type': 'integer'}, + 'sleeping': {'type': 'integer'}, + 'blocked': {'type': 'integer'}, + 'zombie': {'type': 'integer'}, + 'stopped': {'type': 'integer'}, + 'paging': {'type': 'integer'}, + }, + }, + 'cpu': { + 'type': 'object', + 'properties': { + 'frequency': {'type': 'integer'}, + 'user': {'type': 'integer'}, + 'system': {'type': 'integer'}, + 'nice': {'type': 'integer'}, + 'idle': {'type': 'integer'}, + 'iowait': {'type': 'integer'}, + 'irq': {'type': 'integer'}, + 'softirq': {'type': 'integer'}, + }, + }, + 'flash': { + 'type': 'object', + 'properties': { + 'total': {'type': 'integer'}, + 'free': {'type': 'integer'}, + }, + }, + 'storage': { + 'type': 'object', + 'properties': { + 'total': {'type': 'integer'}, + 'free': {'type': 'integer'}, + }, + }, + }, + }, + 'interfaces': { + 'type': 'array', + 'title': 'Interfaces', + 'uniqueItems': True, + 'additionalItems': True, + 'items': { + 'type': 'object', + 'title': 'Interface', + 'additionalProperties': True, + 'required': ['name'], + 'properties': { + 'name': {'type': 'string'}, + 'uptime': {'type': 'integer'}, + 'statistics': { + 'type': 'object', + 'properties': { + 'collisions': {'type': 'integer'}, + 'rx_frame_errors': {'type': 'integer'}, + 'tx_compressed': {'type': 'integer'}, + 'multicast': {'type': 'integer'}, + 'rx_length_errors': {'type': 'integer'}, + 'tx_dropped': {'type': 'integer'}, + 'rx_bytes': {'type': 'integer'}, + 'rx_missed_errors': {'type': 'integer'}, + 'tx_errors': {'type': 'integer'}, + 'rx_compressed': {'type': 'integer'}, + 'rx_over_errors': {'type': 'integer'}, + 'tx_fifo_errors': {'type': 'integer'}, + 'rx_crc_errors': {'type': 'integer'}, + 'rx_packets': {'type': 'integer'}, + 'tx_heartbeat_errors': {'type': 'integer'}, + 'rx_dropped': {'type': 'integer'}, + 'tx_aborted_errors': {'type': 'integer'}, + 'tx_packets': {'type': 'integer'}, + 'rx_errors': {'type': 'integer'}, + 'tx_bytes': {'type': 'integer'}, + 'tx_window_errors': {'type': 'integer'}, + 'rx_fifo_errors': {'type': 'integer'}, + 'tx_carrier_errors': {'type': 'integer'}, + }, + }, + }, + }, + }, + }, +} diff --git a/netengine/backends/snmp/airos.py b/netengine/backends/snmp/airos.py index 24c7fd1..fce0844 100644 --- a/netengine/backends/snmp/airos.py +++ b/netengine/backends/snmp/airos.py @@ -317,13 +317,10 @@ def interfaces_to_dict(self): result = self._dict( { 'name': self.interfaces_MAC[i]['name'], - 'type': self.interfaces_type[i]['type'], - 'mac_address': self.interfaces_MAC[i]['mac_address'], - 'rx_bytes': int(self.interfaces_bytes[i]['rx']), - 'tx_bytes': int(self.interfaces_bytes[i]['tx']), - 'state': self.interfaces_state[i]['state'], - 'mtu': int(self.interfaces_mtu[i]['mtu']), - 'speed': int(self.interfaces_speed[i]['speed']), + 'statistics': { + 'rx_bytes': int(self.interfaces_bytes[i]['rx']), + 'tx_bytes': int(self.interfaces_bytes[i]['tx']), + }, } ) results.append(result) @@ -403,20 +400,11 @@ def RAM_free(self): def to_dict(self): return self._dict( { - 'name': self.name, - 'type': 'radio', - 'os': self.os[0], - 'os_version': self.os[1], - 'manufacturer': self.manufacturer, - 'model': self.model, - 'RAM_total': self.RAM_total, - 'RAM_free': self.RAM_free, - 'uptime': self.uptime, - 'uptime_tuple': self.uptime_tuple, + 'type': 'DeviceMonitoring', + 'general': {'uptime': self.uptime,}, + 'resources': { + 'memory': {'total': self.RAM_total, 'free': self.RAM_free,}, + }, 'interfaces': self.interfaces_to_dict, - 'antennas': [], - 'wireless_dbm': self.wireless_dbm, - 'wireless_links': self.wireless_links, - 'routing_protocols': None, } ) diff --git a/netengine/backends/snmp/openwrt.py b/netengine/backends/snmp/openwrt.py index 2e21adb..ceb57b2 100644 --- a/netengine/backends/snmp/openwrt.py +++ b/netengine/backends/snmp/openwrt.py @@ -368,15 +368,7 @@ def interfaces_to_dict(self): result = self._dict( { 'name': name, - 'type': if_type, - 'mac_address': mac_address, - 'ip_address': ip_address, - 'netmask': netmask, - 'rx_bytes': rx_bytes, - 'tx_bytes': tx_bytes, - 'state': state, - 'mtu': mtu, - 'speed': speed, + 'statistics': {'rx_bytes': rx_bytes, 'tx_bytes': tx_bytes,}, } ) results.append(result) @@ -392,17 +384,9 @@ def RAM_total(self): def to_dict(self): return self._dict( { - 'name': self.name, - 'type': 'radio', - 'os': self.os[0], - 'os_version': self.os[1], - 'manufacturer': self.manufacturer, - 'model': None, - 'RAM_total': self.RAM_total, - 'uptime': self.uptime, - 'uptime_tuple': self.uptime_tuple, - 'interfaces': self.get_interfaces(), - 'antennas': [], - 'routing_protocols': None, + 'type': 'DeviceMonitoring', + 'general': {'uptime': self.uptime,}, + 'resources': {'memory': {'total': self.RAM_total,}}, + 'interfaces': self.interfaces_to_dict, } ) diff --git a/requirements-test.txt b/requirements-test.txt index fed2167..136ff05 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -3,3 +3,4 @@ coverage~=5.5 sphinx~=4.0.2 openwisp-utils[qa]~=0.7.4 pylinkvalidator~=0.3.0 +jsonschema~=3.2.0 diff --git a/tests/static/test-airos-snmp.json b/tests/static/test-airos-snmp.json index e470631..6890337 100644 --- a/tests/static/test-airos-snmp.json +++ b/tests/static/test-airos-snmp.json @@ -8,10 +8,10 @@ "1.2.840.10036.3.1.2.1.4.5": "XM.ar7240.v5.5.12536.120406.1455", "1.2.840.10036.3.1.2.1.3.5": "NanoStation Loco M2", "1.3.6.1.2.1.2.2.1.2.1": "\\xd4\\xa0*", - "1.3.6.1.2.1.2.2.1.2.2": "\\xd4\\xa0*", - "1.3.6.1.2.1.2.2.1.2.3": "\\xd4\\xa0*", - "1.3.6.1.2.1.2.2.1.2.4": "\\xd4\\xa0*", - "1.3.6.1.2.1.2.2.1.2.5": "\\xd4\\xa0*", + "1.3.6.1.2.1.2.2.1.2.2": "\\xd4\\xb0*", + "1.3.6.1.2.1.2.2.1.2.3": "\\xd4\\xc0*", + "1.3.6.1.2.1.2.2.1.2.4": "\\xd4\\xd0*", + "1.3.6.1.2.1.2.2.1.2.5": "\\xd4\\xe0*", "1.3.6.1.2.1.2.2.1.6.1": "\b\u0000''�\u0010\u0014", "1.3.6.1.2.1.2.2.1.6.2": "\b\u0000''�\u0010\u0000", "1.3.6.1.2.1.2.2.1.6.3": "\b\u0000''�\u0010\u0015", diff --git a/tests/test_snmp/test_airos.py b/tests/test_snmp/test_airos.py index 4ea3227..ab19a75 100644 --- a/tests/test_snmp/test_airos.py +++ b/tests/test_snmp/test_airos.py @@ -1,9 +1,12 @@ +import json import unittest from unittest.mock import patch +from jsonschema import validate from pysnmp.entity.rfc3413.oneliner import cmdgen from pysnmp.smi.error import NoSuchObjectError +from netengine.backends.schema import schema from netengine.backends.snmp import AirOS from netengine.exceptions import NetEngineError @@ -142,7 +145,7 @@ def test_to_dict(self): ) self.assertTrue(isinstance(self.device.to_dict(), dict)) - def test_manufacturer_to_dict(self): + def test_netjson_compliance(self): with self.nextcmd_patcher as np: SpyMock._update_patch( np, @@ -150,7 +153,10 @@ def test_manufacturer_to_dict(self): data=args ), ) - self.assertIsNotNone(self.device.to_dict()['manufacturer']) + device_dict = self.device.to_dict() + device_json = self.device.to_json() + validate(instance=device_dict, schema=schema) + validate(instance=json.loads(device_json), schema=schema) def test_manufacturer(self): with self.nextcmd_patcher: diff --git a/tests/test_snmp/test_openwrt.py b/tests/test_snmp/test_openwrt.py index f12588e..e73f108 100644 --- a/tests/test_snmp/test_openwrt.py +++ b/tests/test_snmp/test_openwrt.py @@ -1,8 +1,11 @@ +import json import unittest from unittest.mock import patch +from jsonschema import validate from pysnmp.entity.rfc3413.oneliner import cmdgen +from netengine.backends.schema import schema from netengine.backends.snmp import OpenWRT from ..settings import settings @@ -94,17 +97,20 @@ def test_RAM_total(self): def test_to_dict(self): with self.nextcmd_patcher as p: - SpyMock._update_patch(p, _mock_return_value=[0, 0, 0, [[[0, 1]]] * 5]) + SpyMock._update_patch(p, _mock_return_value=[0, 0, 0, []]) device_dict = self.device.to_dict() self.assertTrue(isinstance(device_dict, dict)) self.assertEqual( len(device_dict['interfaces']), len(self.device.get_interfaces()), ) - def test_manufacturer_to_dict(self): + def test_netjson_compliance(self): with self.nextcmd_patcher as p: - SpyMock._update_patch(p, _mock_return_value=[0, 0, 0, [[[0, 1]]] * 5]) - self.assertIsNotNone(self.device.to_dict()['manufacturer']) + SpyMock._update_patch(p, _mock_return_value=[0, 0, 0, []]) + device_dict = self.device.to_dict() + device_json = self.device.to_json() + validate(instance=device_dict, schema=schema) + validate(instance=json.loads(device_json), schema=schema) def tearDown(self): patch.stopall() diff --git a/tests/utils.py b/tests/utils.py index 6c3b548..aca6018 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -43,6 +43,11 @@ def _get_mocked_wireless_links(data): '1.3.6.1.4.1.14988.1.1.1.2.1': [0, 0, 0, [[[0, 0], 0]] * 28], '1.3.6.1.4.1.14988.1.1.1.2.1.3': [0, 0, 0, [0, 0]], '1.3.6.1.4.1.14988.1.1.1.2.1.3.0': [None, 0, 0, []], - '1.3.6.1.2.1.1.9.1.1': [0, 0, 0, [[[0, 1]]] * 5], + '1.3.6.1.2.1.1.9.1.1': [ + 0, + 0, + 0, + [[[0, 1]], [[0, 2]], [[0, 3]], [[0, 4]], [[0, 5]]], + ], } return return_data[oid]