77from collections import OrderedDict
88from 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 ()
1717args_Host = _args .host
1818args_Version = _args .version
1919args_Community = _args .community
2020args_RespectImp = _args .respect
21-
21+ args_MoreFormat = _args . more_format
2222
2323# get base folder
2424os .chdir (os .path .dirname (os .path .realpath (__file__ )))
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' )
61171StatusWarning = ('noncritical' , 'removed' , 'foreign' , 'offline' )
62172StatusCritical = ('fail' , 'failed' , 'critical' , 'nonrecoverable' , 'notredundant' , 'lost' , 'degraded' , 'redundancyoffline' )
63173StatusMap = {
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
70180SnmpCommand = NamedTuple ('SnmpCommand' , [('command' , str ), ('host' , str ), ('oid' , str ), ('mib_dir' , str ), ('mib' , str ), ('value_only' , bool )])
71181SnmpResult = 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
75185def 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
92202def 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
138270VendorResult = 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
150282exitCode , 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
168323if args_RespectImp and exitCodeImp > - 1 :
169324 sys .exit (exitCodeImp )
0 commit comments