Add dsa-check-raid-megaraid
[mirror/dsa-nagios.git] / dsa-nagios-checks / checks / dsa-check-raid-megaraid
1 #!/usr/bin/env python
2 #
3 #   Copyright Hari Sekhon 2007
4 #
5 #   This program is free software; you can redistribute it and/or modify
6 #   it under the terms of the GNU General Public License as published by
7 #   the Free Software Foundation; either version 2 of the License, or
8 #   (at your option) any later version.
9 #
10 #   This program is distributed in the hope that it will be useful,
11 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #   GNU General Public License for more details.
14 #
15 #   You should have received a copy of the GNU General Public License
16 #   along with this program; if not, write to the Free Software
17 #   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19
20 """Nagios plugin to test the status of all arrays on all Lsi MegaRaid
21 controllers on the local machine. Uses the megarc.bin program written by Lsi to
22 get the status of all arrays on all local Lsi MegaRaid controllers. Expects the
23 megarc.bin program to be in the same directory as this plugin"""
24
25 __version__ = 0.8
26
27 import os
28 import sys
29 import commands
30 from optparse import OptionParser
31
32 # Standard Nagios return codes
33 OK       = 0
34 WARNING  = 1
35 CRITICAL = 2
36 UNKNOWN  = 3
37
38 SRCDIR   = os.path.dirname(sys.argv[0])
39 BIN      = SRCDIR + "/megarc.bin"
40 MEGADEV  = "/dev/megadev0"
41
42
43 def end(status, message):
44     """exits the plugin with first arg as the return code and the second
45     arg as the message to output"""
46         
47     if status == OK:
48         print "RAID OK: %s" % message
49         sys.exit(OK)
50     elif status == WARNING:
51         print "RAID WARNING: %s" % message
52         sys.exit(WARNING)
53     elif status == CRITICAL:
54         print "RAID CRITICAL: %s" % message
55         sys.exit(CRITICAL)
56     else:
57         print "UNKNOWN: %s" % message
58         sys.exit(UNKNOWN)
59
60
61 def make_megadev(devicenode):
62     """Creates the device node needed for the Lsi utility to work
63     (usually /dev/megadev0)"""
64
65     try:
66         devices = open("/proc/devices", "r")
67         lines   = devices.read()
68         devices.close()
69     except IOError, error:
70         end(UNKNOWN, "Error reading /proc/devices while trying to create " \
71                    + "device node '%s' - %s" % (devicenode, error))
72     device = ""
73     for line in lines.split("\n"):
74         line = line.split()
75         if len(line) > 1:
76             major_number = line[0]
77             device       = line[1]
78             if device == "megadev":
79                 break
80
81     if device != "megadev":
82         end(UNKNOWN, "Unable to create device node /dev/megadev0. Megadev " \
83                    + "not found in /proc/devices. Please make sure you have " \
84                    + "an Lsi MegaRaid card detected by your kernel first")
85     cmd = "mknod /dev/megadev0 c %s 2" % major_number
86     print >> sys.stderr, "running in shell: %s" % cmd
87     try:
88         result, output = commands.getstatusoutput(cmd) 
89         if result != 0:
90             end(UNKNOWN, "Error making device node '%s' - %s" \
91                                                         % (devicenode, output))
92         print >> sys.stderr, "%s" % output
93         print >> sys.stderr, "now continuing with raid checks..."
94     except OSError, error:
95         end(UNKNOWN, "Error making '%s' device node - %s" % (devicenode, error))
96
97
98 if os.geteuid() != 0:
99     end(UNKNOWN, "You must be root to run this plugin")
100
101 if not os.path.exists(BIN):
102     end(UNKNOWN, "Lsi MegaRaid utility '%s' was not found" % BIN)
103
104 if not os.access(BIN, os.X_OK):
105     end(UNKNOWN, "Lsi MegaRaid utility '%s' is not executable" % BIN)
106
107 if not os.path.exists(MEGADEV):
108     print >> sys.stderr, "Megaraid device node not found (possible first " \
109                        + "run?), creating it now..." 
110     make_megadev(MEGADEV)
111
112
113 def run(args):
114     """run megarc.bin util with passed in args and return output"""
115     if args == "" or args == None:
116         print "UNKNOWN: internal python error",
117         print "- no cmd supplied for Lsi MegaRaid utility"
118         sys.exit(UNKNOWN)
119     cmd = "%s %s -nolog" % (BIN, args)
120     result, output = commands.getstatusoutput(cmd)
121     lines = output.split("\n")
122     if result != 0:
123         if lines[0][-25:] == "No such file or directory":
124             end(UNKNOWN, "Cannot find Lsi MegaRaid utility '%s'" % BIN)
125         elif len(lines) == 0:
126             end(UNKNOWN, "No output from Lsi MegaRaid utility")
127         elif len(lines) < 13:
128             print >> sys.stderr, "Error running '%s':" % cmd
129             print >> sys.stderr, "%s" % output
130             end(UNKNOWN, "Output from Lsi MegaRaid utility is too short, "
131                        + "please inspect code")
132         else:
133             end(UNKNOWN, "Error using MegaRaid utility - %s" \
134                                                     % output.replace("\n", "|"))
135     
136     return lines
137
138
139 def get_controllers(verbosity):
140     """finds and returns a list of all controllers on the local machine"""
141
142     lines = run("-AllAdpInfo")
143
144     if lines[11].strip() == "No Adapters Found":
145         end(WARNING, "No LSI adapters were found on this machine")
146
147     controllers = []
148     controller_lines = lines[12:]
149     for line in controller_lines:
150         try:
151             controller = int(line.split("\t")[1])
152         except OSError,error:
153             end(UNKNOWN, "Exception occurred in code - %s" % str(error))
154         controllers.append(controller)
155
156     if len(controllers) == 0:
157         end(WARNING, "No LSI controllers were found on this machine")
158
159     if verbosity >= 2:
160         print "Found %s controller(s)" % len(controllers)
161    
162     return controllers
163
164
165 def test_raid(verbosity, no_summary=False):
166     """tests all raid arrays on all Lsi controllers found on local machine
167     and returns status code"""
168
169     status = OK 
170     message = ""
171     number_arrays = 0
172     non_optimal_arrays = 0
173     controllers = get_controllers(verbosity)
174     number_controllers = len(controllers)
175     for controller in controllers:
176         detailed_output = run("-dispCfg -a%s" % controller )
177         if verbosity >= 3:
178             for line in detailed_output:
179                 print "%s" % line
180             print
181         array_details = {}
182         for line in detailed_output:
183             if "Status:" in line:
184                 state = line.split(":")[-1][1:-1]
185                 logical_drive = line.split()[3][:-1]
186                 array_details[logical_drive] = [state]  
187             if "RaidLevel:" in line:
188                 raid_level = line.split()[3]
189                 array_details[logical_drive].append(raid_level)
190        
191         if len(array_details) == 0:
192             message += "No arrays found on controller %s. " % controller
193             if status == OK:
194                 status = WARNING
195             continue
196
197         array_keys = array_details.keys()
198         array_keys.sort()
199         number_arrays += len(array_keys)
200
201         for drive in array_keys:
202             state = array_details[drive][0]
203             if state != "OPTIMAL":
204                 non_optimal_arrays += 1
205                 raid_level = array_details[drive][1]
206                 # The Array number here is incremented by one because of the
207                 # inconsistent way that the LSI tools count arrays. 
208                 # This brings it back in line with the view in the bios 
209                 # and from megamgr.bin where the array counting starts at 
210                 # 1 instead of 0
211                 message += 'Array %s status is "%s"' % (int(drive)+1, state)
212                 message += '(Raid-%s on adapter %s), ' \
213                                                   % (raid_level, controller)
214                 status = CRITICAL
215
216
217     message = add_status_summary(status, \
218                           message, \
219                           non_optimal_arrays)
220
221     message = message.rstrip(" ")
222     message = message.rstrip(",")
223
224     if not no_summary:
225         message = add_checked_summary(message, \
226                               number_arrays, \
227                               number_controllers)
228     return status, message
229
230
231 def add_status_summary(status, message, non_optimal_arrays):
232     """Add initial summary information on the overall state of the arrays"""
233
234     if status == OK:
235         message += "All arrays OK"
236     else:
237         if non_optimal_arrays == 1:
238             message = "%s array not OK - " % non_optimal_arrays \
239                     + message
240         else:
241             message = "%s arrays not OK - " % non_optimal_arrays \
242                     + message
243        
244     return message
245
246
247 def add_checked_summary(message, number_arrays, number_controllers):
248     """ Adds ending summary information on how many arrays were checked"""
249
250     message += " [%s array" % number_arrays
251     if number_arrays != 1:
252         message += "s"
253     message += " checked on %s controller" % number_controllers
254
255     if number_controllers == 1:
256         message += "]"
257     else:
258         message += "s]"
259     
260     return message
261     
262
263 def main():
264     """parses args and calls func to test raid arrays"""
265
266     parser = OptionParser()
267     parser.add_option( "-n",
268                        "--no-summary",
269                        action="store_true",
270                        dest="no_summary",
271                        help="Do not display the number of arrays " \
272                           + "checked. By default the number of arrays " \
273                           + "checked are printed at the end of the " \
274                           + "line. This is useful information and helps to " \
275                           + "know that they are detected properly")
276
277     parser.add_option(  "-v",
278                         "--verbose",
279                         action="count",
280                         dest="verbosity",
281                         help="Verbose mode. Good for testing plugin. By \
282 default only one result line is printed as per Nagios standards")
283
284     parser.add_option( "-V",
285                         "--version",
286                         action = "store_true",
287                         dest = "version",
288                         help = "Print version number and exit" )
289
290     (options, args) = parser.parse_args()
291
292     no_summary = options.no_summary
293     verbosity  = options.verbosity
294     version    = options.version
295
296     if args:
297         parser.print_help()
298         sys.exit(UNKNOWN)
299
300     if version:
301         print __version__
302         sys.exit(OK)
303
304     result, message = test_raid(verbosity, no_summary)
305
306     end(result, message)
307
308
309 if __name__ == "__main__":
310     try:
311         main()
312     except KeyboardInterrupt:
313         print "Caught Control-C..."
314         sys.exit(CRITICAL)