Skip to content

Commit 6df9f51

Browse files
author
Carl Chang
committed
add option to disable colored output;
add more information to the output;
1 parent 50bc092 commit 6df9f51

File tree

1 file changed

+208
-53
lines changed

1 file changed

+208
-53
lines changed

check_snmp.py

Lines changed: 208 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77
from collections import OrderedDict
88
from typing import NamedTuple
99

10-
1110
_parser = argparse.ArgumentParser()
1211
_parser.add_argument('-v', dest='version', help='The SNMP version to use.', default='2c')
1312
_parser.add_argument('-c', dest='community', help='The community string to use.', default='public')
1413
_parser.add_argument('-r', dest='respect', help='Respect properties marked as important when other results contain errors.', action='store_true')
14+
_parser.add_argument('-f', dest='more_format', help='Include additional format like colors in the output.', action='store_true')
1515
_parser.add_argument('host', help='The host to connect to.')
1616
_args = _parser.parse_args()
1717
args_Host = _args.host
1818
args_Version = _args.version
1919
args_Community = _args.community
2020
args_RespectImp = _args.respect
21-
21+
args_MoreFormat = _args.more_format
2222

2323
# get base folder
2424
os.chdir(os.path.dirname(os.path.realpath(__file__)))
@@ -28,48 +28,158 @@
2828
'dell': {
2929
'mib_dir': 'mibs/default:mibs/iana:mibs/ietf:mibs/dell',
3030
'mib': 'IDRAC-MIB-SMIv2',
31-
'oids': OrderedDict([
32-
('globalSystemStatus', {'description': 'Overall system status', 'important': True}),
33-
('systemStateProcessorDeviceStatusCombined', {'description': 'Processor status'}),
34-
('memoryDeviceStatus', {'description': 'Memory status'}),
35-
('physicalDiskState', {'description': 'Physical disk status'}),
36-
('virtualDiskState', {'description': 'Virtual disk status'}),
37-
('controllerComponentStatus', {'description': 'Storage controller status'}),
38-
('coolingUnitStatus', {'description': 'Cooling status'}),
39-
('temperatureProbeStatus', {'description': 'Temperature status'}),
40-
('powerSupplyStatus', {'description': 'Power supply status'}),
41-
('systemStateBatteryStatusCombined', {'description': 'Battery status'}),
31+
'categories': OrderedDict([
32+
('global', {
33+
'description': 'Overall System Status',
34+
'oids': [
35+
{'oid': 'systemModelName', 'type': 'text'},
36+
{'oid': 'systemServiceTag', 'type': 'text'},
37+
{'oid': 'systemOSName', 'type': 'text'},
38+
{'oid': 'globalSystemStatus', 'type': 'status'},
39+
],
40+
'important': True
41+
}),
42+
('processor', {
43+
'description': 'Processor Status',
44+
'oids': [
45+
{'oid': 'processorDeviceBrandName', 'type': 'text'},
46+
{'oid': 'processorDeviceStatus', 'type': 'status'},
47+
],
48+
}),
49+
('memory', {
50+
'description': 'Memory Status',
51+
'oids': [
52+
{'oid': 'memoryDeviceLocationName', 'type': 'text'},
53+
{'oid': 'memoryDeviceStatus', 'type': 'status'},
54+
],
55+
}),
56+
('physicalDisk', {
57+
'description': 'Physical Disk Status',
58+
'oids': [
59+
{'oid': 'physicalDiskDisplayName', 'type': 'text'},
60+
{'oid': 'physicalDiskState', 'type': 'status'},
61+
],
62+
}),
63+
('virtualDisk', {
64+
'description': 'Virtual Disk Status',
65+
'oids': [
66+
{'oid': 'virtualDiskDisplayName', 'type': 'text'},
67+
{'oid': 'virtualDiskState', 'type': 'status'},
68+
],
69+
}),
70+
('storageController', {
71+
'description': 'Storage Controller Status',
72+
'oids': [
73+
{'oid': 'controllerName', 'type': 'text'},
74+
{'oid': 'controllerComponentStatus', 'type': 'status'},
75+
],
76+
}),
77+
('cooling', {
78+
'description': 'Cooling Status',
79+
'oids': [
80+
{'oid': 'coolingUnitName', 'type': 'text'},
81+
{'oid': 'coolingUnitStatus', 'type': 'status'},
82+
],
83+
}),
84+
('temperature', {
85+
'description': 'Temperature Status',
86+
'oids': [
87+
{'oid': 'temperatureProbeLocationName', 'type': 'text'},
88+
{'oid': 'temperatureProbeStatus', 'type': 'status'},
89+
],
90+
}),
91+
('powerSupply', {
92+
'description': 'Power supply Status',
93+
'oids': [
94+
{'oid': 'powerSupplyLocationName', 'type': 'text'},
95+
{'oid': 'powerSupplyStatus', 'type': 'status'},
96+
],
97+
}),
98+
('battery', {
99+
'description': 'Battery Status',
100+
'oids': [
101+
{'oid': 'systemBatteryLocationName', 'type': 'text'},
102+
{'oid': 'systemBatteryStatus', 'type': 'status'},
103+
],
104+
}),
42105
])
43106
},
44107
'hpe': {
45108
'mib_dir': 'mibs/default:mibs/iana:mibs/ietf:mibs/hpe',
46109
'mib': 'CPQSINFO-MIB:CPQHLTH-MIB:CPQIDA-MIB',
47-
'oids': OrderedDict([
48-
('cpqHeMibCondition', {'description': 'Overall system status', 'important': True}),
49-
('cpqHeResilientMemCondition', {'description': 'Memory status'}),
50-
('cpqDaPhyDrvCondition', {'description': 'Physical disk status'}),
51-
('cpqDaMibCondition', {'description': 'Virtual disk status'}),
52-
('cpqDaCntlrCondition', {'description': 'Storage controller status'}),
53-
('cpqHeThermalSystemFanStatus', {'description': 'Cooling status'}),
54-
('cpqHeThermalTempStatus', {'description': 'Temperature status'}),
55-
('cpqHeFltTolPowerSupplyCondition', {'description': 'Power supply status'}),
56-
('cpqHeEventLogCondition', {'description': 'Integrated Management Log status'}),
57-
])
110+
'categories': OrderedDict([
111+
('global', {
112+
'description': 'Overall System Status',
113+
'oids': [
114+
{'oid': 'cpqHeMibCondition', 'type': 'status'},
115+
],
116+
'important': True
117+
}),
118+
('memory', {
119+
'description': 'Memory Status',
120+
'oids': [
121+
{'oid': 'cpqHeResilientMemCondition', 'type': 'status'},
122+
],
123+
}),
124+
('physicalDisk', {
125+
'description': 'Physical Disk Status',
126+
'oids': [
127+
{'oid': 'cpqDaPhyDrvLocationString', 'type': 'text'},
128+
{'oid': 'cpqDaPhyDrvCondition', 'type': 'status'},
129+
],
130+
}),
131+
('virtualDisk', {
132+
'description': 'Virtual Disk Status',
133+
'oids': [
134+
{'oid': 'cpqDaMibCondition', 'type': 'status'},
135+
],
136+
}),
137+
('storageController', {
138+
'description': 'Storage Controller Status',
139+
'oids': [
140+
{'oid': 'cpqDaCntlrCondition', 'type': 'status'},
141+
],
142+
}),
143+
('cooling', {
144+
'description': 'Cooling Status',
145+
'oids': [
146+
{'oid': 'cpqHeThermalSystemFanStatus', 'type': 'status'},
147+
],
148+
}),
149+
('temperature', {
150+
'description': 'Temperature Status',
151+
'oids': [
152+
{'oid': 'cpqHeThermalTempStatus', 'type': 'status'},
153+
],
154+
}),
155+
('powerSupply', {
156+
'description': 'Power supply Status',
157+
'oids': [
158+
{'oid': 'cpqHeFltTolPowerSupplyCondition', 'type': 'status'},
159+
],
160+
}),
161+
('battery', {
162+
'description': 'Integrated Management Log Status',
163+
'oids': [
164+
{'oid': 'cpqHeEventLogCondition', 'type': 'status'},
165+
],
166+
}),
167+
]),
58168
}
59169
}
60-
StatusOK = ('ok', 'true', 'yes', 'on', 'online', 'spunup', 'full', 'ready', 'enabled', 'presence', 'non-raid', 'nonraid', 0)
170+
StatusOK = ('ok', 'true', 'yes', 'on', 'online', 'spunup', 'full', 'ready', 'enabled', 'presence', 'non-raid', 'nonraid', '0')
61171
StatusWarning = ('noncritical', 'removed', 'foreign', 'offline')
62172
StatusCritical = ('fail', 'failed', 'critical', 'nonrecoverable', 'notredundant', 'lost', 'degraded', 'redundancyoffline')
63173
StatusMap = {
64-
0: {'status': 'ok', 'color': '\033[32m{}\033[00m', 'severity': 0}, # green
65-
1: {'status': 'warning', 'color': '\033[33m{}\033[00m', 'severity': 2}, # orange
66-
2: {'status': 'critical', 'color': '\033[31m{}\033[00m', 'severity': 3}, # red
67-
3: {'status': 'unknown', 'color': '\033[37m{}\033[00m', 'severity': 1}, # lightgrey
174+
0: {'status': 'OK', 'color': '\033[32m{}\033[00m', 'severity': 0}, # green
175+
1: {'status': 'WARNING', 'color': '\033[33m{}\033[00m', 'severity': 2}, # orange
176+
2: {'status': 'CRITICAL', 'color': '\033[31m{}\033[00m', 'severity': 3}, # red
177+
3: {'status': 'UNKNOWN', 'color': '\033[37m{}\033[00m', 'severity': 1}, # lightgrey
68178
}
69179

70180
SnmpCommand = NamedTuple('SnmpCommand', [('command', str), ('host', str), ('oid', str), ('mib_dir', str), ('mib', str), ('value_only', bool)])
71181
SnmpResult = NamedTuple('SnmpResult', [('stdout', str), ('stderr', str)])
72-
CombinedStatus = NamedTuple('CombinedStatus', [('code', int), ('formatted', str)])
182+
CombinedStatus = NamedTuple('CombinedStatus', [('combined', int), ('separated', list), ('raw', list)])
73183

74184

75185
def run(cmd: SnmpCommand) -> SnmpResult:
@@ -80,7 +190,7 @@ def run(cmd: SnmpCommand) -> SnmpResult:
80190
cmdlst.append(cmd.host); cmdlst.append(cmd.oid)
81191

82192
_proc = subprocess.run(cmdlst, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
83-
_result = SnmpResult(str(_proc.stdout, 'utf-8').strip().lower(), str(_proc.stderr, 'utf-8').strip().lower())
193+
_result = SnmpResult(str(_proc.stdout, 'utf-8').strip(), str(_proc.stderr, 'utf-8').strip())
84194
return _result
85195

86196

@@ -90,6 +200,7 @@ def print_and_exit(msg: str, code: int):
90200

91201

92202
def status_converter(status: any) -> int:
203+
if type(status) is str: status = status.lower()
93204
if status in StatusOK:
94205
return 0 # ok
95206
elif status in StatusWarning:
@@ -100,14 +211,18 @@ def status_converter(status: any) -> int:
100211
return 3 # unknown
101212

102213

103-
def multi_status_converter(status_str: str) -> CombinedStatus:
104-
combined, formatted = 0, ''
214+
def multi_status_converter(status_str: str, bypass: bool = False) -> CombinedStatus:
215+
combined = -1
216+
separated = []
217+
raw = []
105218
for s in status_str.splitlines():
106-
formatted += s + '|'
107-
combined = update_status_code(combined, status_converter(s))
219+
raw.append(s)
220+
if not bypass:
221+
converted = status_converter(s)
222+
separated.append(converted)
223+
combined = update_status_code(combined, converted)
108224

109-
if len(formatted) > 1: formatted = formatted[:-1]
110-
return CombinedStatus(combined, formatted)
225+
return CombinedStatus(combined, separated, raw)
111226

112227

113228
# def status_merger(ok: int, warning: int, critical: int, unknown: int) -> int:
@@ -133,6 +248,23 @@ def update_status_code(old_status_code: int, status_code: int) -> int:
133248
return old_status_code
134249

135250

251+
def status_formatter(status: int, alt_text: str = None, with_color: bool = True) -> str:
252+
result = StatusMap[status]['status'] if alt_text is None else alt_text
253+
if with_color:
254+
result = StatusMap[status]['color'].format(result)
255+
return result
256+
257+
258+
def get_row_output(col_val_raw: str, col_type: str) -> str:
259+
if col_type == 'status':
260+
col_val = status_converter(col_val_raw)
261+
global category_code
262+
category_code = update_status_code(category_code, col_val)
263+
return status_formatter(col_val, StatusMap[col_val]['status'] + ' (' + col_val_raw + ')', args_MoreFormat)
264+
else:
265+
return col_val_raw
266+
267+
136268
# execution
137269
# get vendor
138270
VendorResult = run(SnmpCommand('snmpgetnext', args_Host, '1.3.6.1.4.1', '', '', False))
@@ -148,22 +280,45 @@ def update_status_code(old_status_code: int, status_code: int) -> int:
148280

149281

150282
exitCode, exitCodeImp = -1, -1
151-
for oid in Config[Vendor]['oids']:
152-
vendor = Config[Vendor]
153-
mib_dir = vendor['mib_dir']
154-
mib = vendor['mib']
155-
desc = vendor['oids'][oid]['description']
156-
imp = vendor['oids'][oid].get('important') is True
157-
158-
result = run(SnmpCommand('snmpwalk', args_Host, oid, mib_dir, mib, True))
159-
cs = multi_status_converter(result.stdout)
160-
if imp: exitCodeImp = update_status_code(exitCodeImp, cs.code)
161-
exitCode = update_status_code(exitCode, cs.code)
162-
163-
result_status = StatusMap[cs.code]
164-
print('{desc}: {status} ({formatted})'.format(desc=desc,
165-
status=result_status['color'].format(result_status['status']),
166-
formatted=cs.formatted))
283+
vendor = Config[Vendor]
284+
mib_dir = vendor['mib_dir']
285+
mib = vendor['mib']
286+
for category_key in vendor['categories']:
287+
category = vendor['categories'][category_key]
288+
description = category['description']
289+
imp = category.get('important') is True
290+
291+
oids = category['oids']
292+
if len(oids) == 0: continue
293+
294+
# get and reshape data
295+
category_output = description + ': {combined_status}\n'
296+
category_code = -1
297+
category_result_raw = []
298+
for oid in oids:
299+
oid_result_raw = run(SnmpCommand('snmpwalk', args_Host, oid['oid'], mib_dir, mib, True))
300+
if len(oid_result_raw.stderr) > 0: print_and_exit(oid_result_raw.stderr, 3)
301+
category_result_raw.append([(l.strip('" \n'), oid['type']) for l in oid_result_raw.stdout.splitlines()])
302+
category_result_raw = [i for i in zip(*category_result_raw)] # swap axis
303+
304+
if len(category_result_raw) == 1 and len(category_result_raw[0]) == 1: # there is only one status item without anything else
305+
col = category_result_raw[0][0]
306+
category_output = category_output.format(combined_status=get_row_output(col[0], col[1]))
307+
308+
else:
309+
for row in category_result_raw: # row is like (('DIMM.Socket.A1', 'text'), ('failed', 'status'))
310+
cols = []
311+
for col in row: # col is like ('DIMM.Socket.A1', 'text')
312+
cols.append(get_row_output(col[0], col[1]))
313+
314+
category_output += ' - ' + ', '.join(cols) + '\n'
315+
category_output = category_output.format(combined_status=status_formatter(category_code, with_color=args_MoreFormat))
316+
317+
print(category_output)
318+
319+
if imp: exitCodeImp = update_status_code(exitCodeImp, category_code)
320+
exitCode = update_status_code(exitCode, category_code)
321+
167322

168323
if args_RespectImp and exitCodeImp > -1:
169324
sys.exit(exitCodeImp)

0 commit comments

Comments
 (0)