176 lines
6.5 KiB
Python
176 lines
6.5 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# Copyright 2024 Spearhead Systems SRL
|
|
#
|
|
# Docs for this system:
|
|
# https://cdn10.servertech.com/assets/documents/documents/135/original/manual_CDU_Y-30932L.pdf
|
|
# https://cdn10.servertech.com/assets/documents/documents/793/original/Sentry3.mib
|
|
|
|
from itertools import chain
|
|
|
|
|
|
# A note about MAX_SOCKETS:
|
|
#
|
|
# According to the MIB, MAX_SOCKETS should be 64. However, since CheckMK 1.6
|
|
# doesn't appear to have something like 2.*'s SNMPTree functionality, we
|
|
# brute-force by explicitly querying every possible socket. Unfortunately,
|
|
# this means querying ~3K OIDs. In practice, the data received from the customer
|
|
# shows that they only have two or four sockets per infeed, so I've set the
|
|
# MAX_SOCKETS here to 16, which cuts the queried OIDs to 800. Using MAX_SOCKETS
|
|
# of 4 would still satisfy the customer's current needs, but leaves no leeway
|
|
# if the get more sockets in an infeed.
|
|
MAX_TOWERS = 4
|
|
MAX_INFEEDS = 4
|
|
MAX_SOCKETS = 16 # XXX
|
|
|
|
TOWER_ARR_SIZE = MAX_TOWERS
|
|
INFEED_ARR_SIZE = MAX_TOWERS * MAX_INFEEDS
|
|
SOCKET_ARR_SIZE = MAX_TOWERS * MAX_INFEEDS * MAX_SOCKETS
|
|
|
|
TOWER_NUM_OFFSET = 0
|
|
INFEED_NUM_OFFSET = TOWER_NUM_OFFSET + 1
|
|
INFEED_ID_OFFSET = INFEED_NUM_OFFSET + TOWER_ARR_SIZE
|
|
INFEED_VOLT_OFFSET = INFEED_ID_OFFSET + INFEED_ARR_SIZE
|
|
SOCKET_NUM_OFFSET = INFEED_VOLT_OFFSET + INFEED_ARR_SIZE
|
|
SOCKET_ID_OFFSET = SOCKET_NUM_OFFSET + INFEED_ARR_SIZE
|
|
SOCKET_NAME_OFFSET = SOCKET_ID_OFFSET + SOCKET_ARR_SIZE
|
|
SOCKET_LOAD_OFFSET = SOCKET_NAME_OFFSET + SOCKET_ARR_SIZE
|
|
|
|
|
|
def parse_sentry_pdu(info):
|
|
outlets_info = {}
|
|
results = info[0]
|
|
num_towers = int(results[TOWER_NUM_OFFSET])
|
|
|
|
for tower_id in range(num_towers):
|
|
num_infeeds = int(results[INFEED_NUM_OFFSET + tower_id])
|
|
|
|
for infeed_id in range(num_infeeds):
|
|
infeed_idx = tower_id * MAX_TOWERS + infeed_id
|
|
|
|
infeed_sid = results[INFEED_ID_OFFSET + infeed_idx]
|
|
infeed_voltage = float(results[INFEED_VOLT_OFFSET + infeed_idx]) / 10
|
|
num_outlets = int(results[SOCKET_NUM_OFFSET + infeed_idx])
|
|
|
|
for outlet_id in range(num_outlets):
|
|
outlet_idx = MAX_INFEEDS * MAX_SOCKETS * tower_id + MAX_SOCKETS * infeed_id + outlet_id
|
|
outlet_sid = results[SOCKET_ID_OFFSET + outlet_idx]
|
|
outlet_name = results[SOCKET_NAME_OFFSET + outlet_idx]
|
|
outlet_load = float(results[SOCKET_LOAD_OFFSET + outlet_idx]) / 100
|
|
|
|
outlet_key = '%s.%s.%s' % (tower_id, infeed_id, outlet_id)
|
|
outlets_info[outlet_key] = {
|
|
'infeed_id': infeed_sid,
|
|
'infeed_voltage': infeed_voltage,
|
|
'outlet_id': outlet_sid,
|
|
'outlet_name': outlet_name,
|
|
'outlet_load': outlet_load
|
|
}
|
|
|
|
return outlets_info
|
|
|
|
|
|
def check_sentry_pdu(item, params, parsed):
|
|
id = params['id']
|
|
outlet = parsed.get(id)
|
|
|
|
if outlet is None:
|
|
return (3, 'item not found in snmp data')
|
|
|
|
voltage = outlet['infeed_voltage']
|
|
amps = outlet['outlet_load']
|
|
|
|
if voltage < 0:
|
|
return (1, 'Infeed voltage unavailable')
|
|
elif amps < 0:
|
|
return (1, 'Outlet load unavailable')
|
|
|
|
power = voltage * amps
|
|
state = 0
|
|
|
|
crit_watts_above = params.get('crit_watts_above')
|
|
warn_watts_above = params.get('warn_watts_above')
|
|
warn_watts_below = params.get('warn_watts_below')
|
|
crit_watts_below = params.get('crit_watts_below')
|
|
|
|
if crit_watts_above and crit_watts_above < power:
|
|
state = 2
|
|
elif crit_watts_below and crit_watts_below > power:
|
|
state = 2
|
|
elif warn_watts_above and warn_watts_above < power:
|
|
state = 1
|
|
elif warn_watts_below and warn_watts_below > power:
|
|
state = 1
|
|
|
|
return (state, '%.1f watts' % power)
|
|
|
|
|
|
|
|
def inventory_sentry_pdu(parsed):
|
|
items = []
|
|
|
|
for id, outlet in parsed.items():
|
|
name = '%s %s [infeed %s] power' % (
|
|
outlet['outlet_id'],
|
|
outlet['outlet_name'],
|
|
outlet['infeed_id'])
|
|
items.append((name, { 'id': id }))
|
|
|
|
return items
|
|
|
|
|
|
check_info['sentry_pdu_outlets_power'] = {
|
|
'parse_function': parse_sentry_pdu,
|
|
'check_function': check_sentry_pdu,
|
|
'inventory_function': inventory_sentry_pdu,
|
|
'service_description': 'Outlet %s',
|
|
'group': 'sentry_pdu_outlets_power',
|
|
'snmp_info': (
|
|
'.1.3.6.1.4.1.1718.3',
|
|
# In CheckMK 2.* there is SMPTree, but 1.6 doesn't seem to have that.
|
|
# Therefore we resort to this sledge-hammer approach to get the
|
|
# information we need. It's... not ideal.
|
|
list(chain(
|
|
# Number of towers
|
|
['1.4.0'],
|
|
|
|
# Number of infeeds
|
|
# .1.3.6.1.4.1.1718.3.2.1.1.5.<tower #>
|
|
['2.1.1.5.%s' % (x) for x in range(1, MAX_TOWERS+1)],
|
|
|
|
# Infeed IDs:
|
|
# .1.3.6.1.4.1.1718.3.2.2.1.2.<tower #>.<infeed #>
|
|
['2.2.1.2.%s.%s' % (x, y) for x in range(1, MAX_TOWERS+1)
|
|
for y in range(1, MAX_INFEEDS+1)],
|
|
|
|
# Infeed voltage:
|
|
# .1.3.6.1.4.1.1718.3.2.2.1.11.<tower #>.<infeed #>
|
|
['2.2.1.11.%s.%s' % (x, y) for x in range(1, MAX_TOWERS+1)
|
|
for y in range(1, MAX_INFEEDS+1)],
|
|
|
|
# Number of outlets
|
|
# .1.3.6.1.4.1.1718.3.2.2.1.9.<tower #>.<infeed #>
|
|
['2.2.1.9.%s.%s' % (x, y) for x in range(1, MAX_TOWERS+1)
|
|
for y in range(1, MAX_INFEEDS+1)],
|
|
|
|
# Outlet IDs:
|
|
# .1.3.6.1.4.1.1718.3.2.3.1.2.<tower #>.<infeed #>.<outlet #>
|
|
['2.3.1.2.%s.%s.%s' % (x, y, z) for x in range(1, MAX_TOWERS+1)
|
|
for y in range(1, MAX_INFEEDS+1)
|
|
for z in range(1, MAX_SOCKETS+1)],
|
|
|
|
# Outlet names:
|
|
# .1.3.6.1.4.1.1718.3.2.3.1.3.<tower #>.<infeed #>.<outlet #>
|
|
['2.3.1.3.%s.%s.%s' % (x, y, z) for x in range(1, MAX_TOWERS+1)
|
|
for y in range(1, MAX_INFEEDS+1)
|
|
for z in range(1, MAX_SOCKETS+1)],
|
|
|
|
# Outlet load:
|
|
# .1.3.6.1.4.1.1718.3.2.3.1.7.<tower #>.<infeed #>.<outlet #>
|
|
['2.3.1.7.%s.%s.%s' % (x, y, z) for x in range(1, MAX_TOWERS+1)
|
|
for y in range(1, MAX_INFEEDS+1)
|
|
for z in range(1, MAX_SOCKETS+1)],
|
|
))),
|
|
'snmp_scan_function': lambda oid: 'Sentry Switched -48 VDC' in oid('.1.3.6.1.2.1.1.1.0')
|
|
}
|