#!/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. ['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.. ['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.. ['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.. ['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... ['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... ['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... ['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') }