Skip to content

Commit

Permalink
[change] Add monitoring info to OpenWRT backend
Browse files Browse the repository at this point in the history
  • Loading branch information
purhan committed Jun 10, 2021
1 parent d46b568 commit 9168a27
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 49 deletions.
12 changes: 12 additions & 0 deletions netengine/backends/snmp/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
'pysnmp library is not installed, install it with "pip install pysnmp"'
)

import binascii
import logging

from netengine.backends import BaseBackend
Expand Down Expand Up @@ -45,6 +46,17 @@ def _command(self):
"""
return cmdgen.CommandGenerator()

def _octet_to_mac(self, octet_mac):
"""
returns a valid mac address for a given octetstring
"""
mac_address = binascii.b2a_hex(octet_mac.encode()).decode()
if mac_address is not '':
mac_address = ':'.join(
[mac_address[slice(i, i + 2)] for i in range(0, 12, 2) if i != '']
)
return mac_address

def _oid(self, oid):
"""
returns valid oid value to be passed to getCmd() or nextCmd()
Expand Down
229 changes: 183 additions & 46 deletions netengine/backends/snmp/openwrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
__all__ = ['OpenWRT']


import binascii
import datetime
import logging
import struct
from datetime import timedelta

import pytz

from netengine.backends.snmp import SNMP

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -107,16 +110,8 @@ def interfaces_MAC(self):
mac1.append(self.get_value('1.3.6.1.2.1.2.2.1.6.' + str(i)))
mac_trans = []
for i in mac1:
mac_string = binascii.b2a_hex(i.encode()).decode()
mac_trans.append(
':'.join(
[
mac_string[slice(i, i + 2)]
for i in range(0, 12, 2)
if i != ''
]
)
)
mac_string = self._octet_to_mac(i)
mac_trans.append(mac_string)
for i in range(0, len(self.get_interfaces())):
result = self._dict(
{'name': self.get_interfaces()[i], 'mac_address': mac_trans[i]}
Expand Down Expand Up @@ -205,41 +200,35 @@ def interfaces_speed(self):

return self._interfaces_speed

_interfaces_state = None
_interfaces_up = None

@property
def interfaces_state(self):
def interfaces_up(self):
"""
Returns an ordereed dict with the interfaces and their state (up, down)
Returns an ordereed dict with the interfaces and their state (up: true/false)
"""
if self._interfaces_state is None:
if self._interfaces_up is None:
results = []
starting = '1.3.6.1.2.1.2.2.1.2.'
operative = '1.3.6.1.2.1.2.2.1.8.'
tmp = list(starting)
tmp[18] = str(4)
for i in self._value_to_retrieve():
if self.get_value(starting + str(i)) != '':
if int(self.get_value(operative + str(i))) == 1:
result = self._dict(
{'name': self.get_value(starting + str(i)), 'state': 'up'}
)
results.append(result)
else:
result = self._dict(
{
'name': self.get_value(starting + str(i)),
'state': 'down',
}
)
results.append(result)
result = self._dict(
{
'name': self.get_value(starting + str(i)),
'up': int(self.get_value(operative + str(i))) == 1,
}
)
results.append(result)
elif self.get_value(starting + str(i)) == '':
result = self._dict({'name': '', 'state': ''})
results.append(result)

self._interfaces_state = results
self._interfaces_up = results

return self._interfaces_state
return self._interfaces_up

_interfaces_bytes = None

Expand Down Expand Up @@ -349,44 +338,192 @@ def interfaces_to_dict(self):
rx_bytes = int(self.interfaces_bytes[i]['rx'])
logger.info('... tx_bytes ...')
tx_bytes = int(self.interfaces_bytes[i]['tx'])
logger.info('... state ...')
state = self.interfaces_state[i]['state']
logger.info('... up ...')
up = self.interfaces_up[i]['up']
logger.info('... mtu ...')
mtu = int(self.interfaces_mtu[i]['mtu'])
logger.info('... speed ...')
speed = int(self.interfaces_speed[i]['speed'])
logger.info('... ip address & subnet ...')
ip_and_netmask = self.interface_addr_and_mask

if name in list(ip_and_netmask.keys()):
ip_address = ip_and_netmask[name]['address']
netmask = ip_and_netmask[name]['netmask']
else:
ip_address = None
netmask = None

result = self._dict(
{
'name': name,
'statistics': {'rx_bytes': rx_bytes, 'tx_bytes': tx_bytes,},
'statistics': {
"mac": mac_address,
"type": if_type,
"up": up,
'rx_bytes': rx_bytes,
'tx_bytes': tx_bytes,
"mtu": mtu,
},
}
)
results.append(result)
return results

@property
def local_time(self):
"""
returns the local time of the host device as a timestamp
"""
octetstr = bytes(self.get('1.3.6.1.2.1.25.1.2.0')[3][0][1])
size = len(octetstr)
# string may or may not contain timezone, so size can be 8 or 11
if size == 8:
(year, month, day, hour, minutes, seconds, deci_seconds,) = struct.unpack(
'>HBBBBBB', octetstr
)
return int(
datetime.datetime(
year,
month,
day,
hour,
minutes,
seconds,
deci_seconds * 100_000,
tzinfo=pytz.utc,
).timestamp()
)
elif size == 11:
(
year,
month,
day,
hour,
minutes,
seconds,
deci_seconds,
direction,
hours_from_utc,
minutes_from_utc,
) = struct.unpack('>HBBBBBBcBB', octetstr)
offset = datetime.timedelta(hours=hours_from_utc, minutes=minutes_from_utc)
if direction == b'-':
offset = -offset
return int(
datetime.datetime(
year,
month,
day,
hour,
minutes,
seconds,
deci_seconds * 100_000,
tzinfo=pytz.utc,
).timestamp()
)
logger.warning('Invalid timestring was supplied')

@property
def RAM_total(self):
"""
returns the total RAM of the device
"""
return int(self.get_value('1.3.6.1.2.1.25.2.3.1.5.1'))

@property
def RAM_shared(self):
"""
returns the shared RAM of the device
"""
return int(self.get_value('1.3.6.1.2.1.25.2.3.1.6.8'))

@property
def RAM_cached(self):
"""
returns the cached RAM of the device
"""
return int(self.get_value('1.3.6.1.2.1.25.2.3.1.5.7'))

@property
def RAM_free(self):
"""
returns the free RAM of the device
"""
RAM_used = int(self.get_value('1.3.6.1.2.1.25.2.3.1.6.1'))
RAM_free = self.RAM_total - (self.RAM_cached + RAM_used)
return RAM_free

@property
def SWAP_total(self):
"""
returns the total SWAP of the device
"""
return int(self.get_value('1.3.6.1.2.1.25.2.3.1.5.10'))

@property
def SWAP_free(self):
"""
returns the free SWAP of the device
"""
SWAP_used = int(self.get_value('1.3.6.1.2.1.25.2.3.1.6.10'))
SWAP_free = self.SWAP_total - SWAP_used
return SWAP_free

@property
def CPU_count(self):
"""
returns the count of CPUs of the device
"""
return len(self.next('1.3.6.1.2.1.25.3.3.1.2')[3])

@property
def resources_to_dict(self):
"""
returns an ordered dict with hardware resources information
"""
result = self._dict(
{
'cpus': self.CPU_count,
'memory': {
'total': self.RAM_total,
'shared': self.RAM_shared,
'free': self.RAM_free,
'cached': self.RAM_cached,
},
'swap': {'total': self.SWAP_total, 'free': self.SWAP_free,},
}
)
return result

@property
def neighbors(self):
"""
returns a dict with neighbors information
"""
states_map = {
'1': 'REACHABLE',
'2': 'STALE',
'3': 'DELAY',
'4': 'PROBE',
'5': 'INVALID',
'6': 'UNKNOWN',
'7': 'INCOMPLETE',
}

# TODO: find a way to extract IP address from the OID
neighbors = self.next('1.3.6.1.2.1.4.35.1.4')[3]
neighbor_states = self.next('1.3.6.1.2.1.4.35.1.7')[3]
result = []

for i in range(len(neighbors)):
interface_num = neighbors[i][0][0].getOid()[10]
interface = self.get(f'1.3.6.1.2.1.31.1.1.1.1.{interface_num}')[3][0][1]
state = states_map[str(neighbor_states[i][0][1])]
mac = self._octet_to_mac(str(neighbors[i][0][1]))
result.append(
self._dict(
{'mac': str(mac), 'state': str(state), 'interface': str(interface),}
)
)
return result

def to_dict(self):
return self._dict(
{
'type': 'DeviceMonitoring',
'general': {'uptime': self.uptime,},
'resources': {'memory': {'total': self.RAM_total,}},
'general': {'uptime': self.uptime, "local_time": self.local_time},
'resources': self.resources_to_dict,
'interfaces': self.interfaces_to_dict,
'neighbors': self.neighbors,
}
)
8 changes: 7 additions & 1 deletion tests/static/test-openwrt-snmp-oid.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,11 @@
"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\u0014",
"1.3.6.1.2.1.2.2.1.6.4": "\b\u0000''�\u0010\u0000",
"1.3.6.1.2.1.2.2.1.6.5": "\b\u0000''�\u0010\u0015"
"1.3.6.1.2.1.2.2.1.6.5": "\b\u0000''�\u0010\u0015",
"1.3.6.1.2.1.25.2.3.1.5.7": "6956",
"1.3.6.1.2.1.25.2.3.1.6.1": "25164",
"1.3.6.1.2.1.25.2.3.1.6.8": "88",
"1.3.6.1.2.1.25.2.3.1.6.10": "0",
"1.3.6.1.2.1.25.2.3.1.5.10": "0",
"1.3.6.1.2.1.25.1.2.0": "\u0007\u00e5\u0006\t14\ufffd+\ufffd\ufffd"
}
15 changes: 15 additions & 0 deletions tests/test_snmp/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def setUp(self):
self.host = settings['base-snmp']['host']
self.community = settings['base-snmp']['community']
self.port = settings['base-snmp'].get('port', 161)
self.device = SNMP(self.host, self.community, self.port)

def test_instantiation(self):
device = SNMP(self.host, self.community, self.port)
Expand Down Expand Up @@ -65,3 +66,17 @@ class RightSNMPBackend(SNMP):
# now we expect a different kind of error
with self.assertRaises(IndexError):
device._value_to_retrieve()

def test_octet_to_mac(self):
self.assertEqual(
self.device._octet_to_mac('\x04\x0e<\xcaU_'), '04:0e:3c:c3:8a:55'
)

def test_oid(self):
self.assertEqual(self.device._oid('1,3,6,1,2,1,1,5,0'), '1.3.6.1.2.1.1.5.0')
self.assertEqual(
self.device._oid('1, 3, 6, 1, 2, 1, 1, 5, 0'), '1.3.6.1.2.1.1.5.0',
)
self.assertEqual(
self.device._oid([1, 3, 6, 1, 2, 1, 1, 5, 0]), '1.3.6.1.2.1.1.5.0',
)
Loading

0 comments on commit 9168a27

Please sign in to comment.