#!/usr/bin/python import os import re import sys from argparse import ArgumentParser # Argument parser # My own ArgumentParser with single-line stdout output and unknown state Nagios retcode class NagiosArgumentParser(ArgumentParser): def error(self, message): sys.stdout.write("UNKNOWN: Bad arguments (see --help): %s\n" % message) sys.exit(3) def parse_args(): parser = NagiosArgumentParser(description="Adaptec AACRAID status script") parser.add_argument("-d", "--disks-only", action="store_true", help="Only disply disk statuses") parser.add_argument("-n", "--nagios", action="store_true", help="Use Nagios-like output and return code") return parser.parse_args() def which(program): fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: # Add some defaults os.environ["PATH"] += os.pathsep + "/usr/StorMan/arcconf" os.environ["PATH"] += os.pathsep + os.path.dirname(os.path.realpath(sys.argv[0])) for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) # Get command output def getOutput(cmd): output = os.popen("%s 2>%s" % (cmd, os.devnull)) lines = [] for line in output: if not re.match(r"^$", line.strip()): lines.append(line.strip()) return lines def returnControllerNumber(output): for line in output: if re.match(r"^Controllers found: [0-9]+$", line.strip()): return int(line.split(":")[1].strip().strip(".")) def returnControllerModel(output): for line in output: if re.match(r"^Controller Model.*$", line.strip()): return line.split(":")[1].strip() def returnControllerStatus(output): for line in output: if re.match(r"^Controller Status.*$", line.strip()): return line.split(":")[1].strip() def returnArrayIds(output): ids = [] for line in output: if re.match(r"^Logical [Dd]evice number [0-9]+$", line.strip()): ids.append(re.sub(r"^Logical [Dd]evice number", "", line).strip()) return ids def returnArrayInfo(output, ctrl_id="?"): # For testing purpose # with open('/tmp/output') as f: # output = f.read().splitlines() members = [] for line in output: # RAID level may be either N or Simple_Volume # (a disk connected to the card, not hotspare, not part of any array) if re.match(r"^RAID level\s+: .+$", line.strip()): type = line.split(":")[1].strip() if re.match(r"^Status of [Ll]ogical [Dd]evice\s+: .*$", line.strip()): status = line.split(":")[1].strip() if re.match(r"^Size\s+: [0-9]+ MB$", line.strip()): size = str(int(line.strip("MB").split(":")[1].strip()) / 1000) if re.match(r"^(Group\s[0-9]+,\s)?Segment\s[0-9]+\s+: .*$", line.strip()): # The line can be either (arcconf 1.x) # Group 0, Segment 0 : Present (Controller:1,Enclosure:0,Slot:0) JPW9J0N00RWMUV # Group 0, Segment 0 : Present (Controller:1,Channel:0,Device:0) S13PJ1CQ719255 # Group 0; Segment 0 : Present (Controller:1,Connector:1,Device:2) 9QJ7D0MJ # # Or (arcconf 2.x) # Group 0, Segment 0 : Present (953869MB, SATA, HDD, Connector:1, Device:1) JPW9K0HZ216NJL' # Cut on : and re-join everything except first part line = ":".join(line.split(":")[1:]).strip() # Extract everything between () device_state = re.search("\s+\((.*)\)\s+", line).group(1) # Coma separated device_attrs = device_state.split(",") device_attrs = [x.strip() for x in device_attrs] # Some magic here... # Strip out from the list element not matching Xyz:12 # Split on column: now we have and array of arrays like [['Controller', '1'], ['Connector', '1'], ['Device', '0']] # Casting to dict will turn Controller as key and 1 as value device_attrs = dict([x.split(":") for x in device_attrs if re.match("\w+:\d+", x)]) # Extract id for this device if "Controller" in device_attrs: controller_id = device_attrs["Controller"] else: controller_id = ctrl_id if "Channel" in device_attrs: channel_id = device_attrs["Channel"] elif "Enclosure" in device_attrs: channel_id = device_attrs["Enclosure"] elif "Connector" in device_attrs: channel_id = device_attrs["Connector"] else: channel_id = "?" if "Device" in device_attrs: device_id = device_attrs["Device"] elif "Slot" in device_attrs: device_id = device_attrs["Slot"] else: device_id = "?" members.append("%s,%s,%s" % (controller_id, channel_id, device_id)) return [type, status, size, members] def returnControllerTasks(output): arrayid = False type = False state = False tasks = [] for line in output: if re.match(r"^Logical device\s+: [0-9]+$", line.strip()): arrayid = line.split(":")[1].strip() if re.match(r"^Current operation\s+: .*$", line.strip()): type = line.split(":")[1].strip() if re.match(r"^Percentage complete\s+: [0-9]+$", line.strip()): state = line.split(":")[1].strip() if arrayid != False and type != False and state != False: tasks.append([arrayid, type, state]) arrayid = False type = False state = False return tasks def returnDisksInfo(output, controllerid): diskid = False vendor = False model = False state = False serial = False disks = [] for line in output: if re.match(r"^Reported Channel,Device(\(T:L\))?\s+: [0-9]+,[0-9]+(\([0-9]+:[0-9]+\))?$", line.strip()): diskid = re.split("\s:\s", line)[1].strip() diskid = re.sub("\(.*\)", "", diskid) diskid = str(controllerid) + "," + diskid if re.match(r"^State\s+:.*$", line.strip()): state = line.split(":")[1].strip() if re.match(r"^Vendor\s+:.*$", line.strip()): vendor = line.split(":")[1].strip() if re.match(r"^Model\s+:.*$", line.strip()): model = line.split(":")[1].strip() if re.match(r"^Serial number\s+:.*$", line.strip()): serial = line.split(":")[1].strip() if diskid != False and vendor != False and model != False and state != False and serial != False: disks.append([diskid, state, vendor, model, serial]) diskid = False vendor = False model = False state = False serial = False return disks config = parse_args() if config.disks_only: printarray = False printcontroller = False else: printarray = True printcontroller = True nagiosoutput = "" nagiosgoodctrl = 0 nagiosbadctrl = 0 nagiosctrlbadarray = 0 nagiosgoodarray = 0 nagiosbadarray = 0 nagiosgooddisk = 0 nagiosbaddisk = 0 bad = False # Find arcconf for arcconfbin in "arcconf", "arcconf.exe": arcconfpath = which(arcconfbin) if arcconfpath != None: break # Check binary exists (and +x), if not print an error message if arcconfpath != None: if is_exe(arcconfpath): pass else: if config.nagios: print("UNKNOWN - Cannot find " + arcconfpath) else: print("Cannot find " + arcconfpath + "in your PATH. Please install it.") sys.exit(3) else: print('Cannot find "arcconf, "arcconf.exe" in your PATH. Please install it.') sys.exit(3) cmd = '"%s" GETVERSION' % arcconfpath output = getOutput(cmd) controllernumber = returnControllerNumber(output) if not controllernumber: print('"arcconf" returned no controller, are you sure to have an Adaptec adapter?') sys.exit(3) # List controllers if printcontroller: if not config.nagios: print("-- Controller informations --") print("-- ID | Model | Status") controllerid = 1 while controllerid <= controllernumber: cmd = '"%s" GETCONFIG %d' % (arcconfpath, controllerid) output = getOutput(cmd) controllermodel = returnControllerModel(output) controllerstatus = returnControllerStatus(output) if controllerstatus != "Optimal": bad = True nagiosbadctrl += 1 else: nagiosgoodctrl += 1 if not config.nagios: print("c" + str(controllerid - 1) + " | " + controllermodel + " | " + controllerstatus) controllerid += 1 if not config.nagios: print("") # List arrays if printarray: controllerid = 1 if not config.nagios: print("-- Arrays informations --") print("-- ID | Type | Size | Status | Task | Progress") while controllerid <= controllernumber: arrayid = 0 cmd = '"%s" GETCONFIG %s' % (arcconfpath, controllerid) output = getOutput(cmd) arrayids = returnArrayIds(output) for arrayid in arrayids: cmd = '"%s" GETCONFIG %s LD %s' % (arcconfpath, controllerid, arrayid) output = getOutput(cmd) arrayinfo = returnArrayInfo(output, ctrl_id=controllerid) if arrayinfo[1] != "Optimal": nagiosbadarray += 1 bad = True else: nagiosgoodarray += 1 cmd = '"%s" GETSTATUS %s' % (arcconfpath, controllerid) output = getOutput(cmd) tasksinfo = returnControllerTasks(output) done = False # Usually it should return either [0-9] or Simple_Volume but... # It can also return "6 Reed-Solomon" so we need to handle this too... # So let's match [0-9] followed by a space or EOL. if re.match("^[0-9]+(\s|$)", arrayinfo[0]): raidtype = re.sub("^", "RAID", arrayinfo[0]) else: raidtype = arrayinfo[0] for tasks in tasksinfo: if int(tasks[0]) == int(arrayid): if not config.nagios: print( "c" + str(controllerid - 1) + "u" + str(arrayid) + " | " + raidtype + " | " + arrayinfo[2] + "G | " + arrayinfo[1] + " | " + tasks[1] + " | " + tasks[2] + "%" ) done = True break if done == False: if not config.nagios: print("c" + str(controllerid - 1) + "u" + str(arrayid) + " | " + raidtype + " | " + arrayinfo[2] + "G | " + arrayinfo[1]) controllerid += 1 if not config.nagios: print("") # List disks controllerid = 1 if not config.nagios: print("-- Disks informations") print("-- ID | Model | Status") while controllerid <= controllernumber: arrayid = 0 cmd = '"%s" GETCONFIG %s' % (arcconfpath, controllerid) output = getOutput(cmd) arrayids = returnArrayIds(output) cmd = '"%s" GETCONFIG %d PD' % (arcconfpath, controllerid) output = getOutput(cmd) diskinfo = returnDisksInfo(output, controllerid) no_array_disk_id = 0 for disk in diskinfo: # Generic verification no matter is the disk is member of an array or not if disk[1] not in ["Online", "Online (JBOD)", "Hot Spare", "Ready", "Global Hot-Spare", "Dedicated Hot-Spare", "Raw (Pass Through)"]: bad = True nagiosbaddisk += 1 else: nagiosgooddisk += 1 array_member = False for arrayid in arrayids: cmd = '"%s" GETCONFIG %s LD %s' % (arcconfpath, controllerid, arrayid) output = getOutput(cmd) arrayinfo = returnArrayInfo(output, ctrl_id=controllerid) # Try to attach current disk to array (loop) memberid = 0 for member in arrayinfo[3]: # Matched in members of this array if disk[0] == member: if not config.nagios: print( "c" + str(controllerid - 1) + "u" + str(arrayid) + "d" + str(memberid) + " | " + disk[2] + " " + disk[3] + " " + disk[4] + " | " + disk[1] ) array_member = True memberid += 1 # Some disks may not be attached to any array (ie: global hot spare) if not array_member: if not config.nagios: print("c" + str(controllerid - 1) + "uX" + "d" + str(no_array_disk_id) + " | " + disk[2] + " " + disk[3] + " " + disk[4] + " | " + disk[1]) no_array_disk_id += 1 controllerid += 1 if config.nagios: if bad: print( ( "RAID ERROR - Controllers OK:%d Bad:%d - Arrays OK:%d Bad:%d - Disks OK:%d Bad:%d" % (nagiosgoodctrl, nagiosbadctrl, nagiosgoodarray, nagiosbadarray, nagiosgooddisk, nagiosbaddisk) ) ) sys.exit(2) else: print( ( "RAID OK - Controllers OK:%d Bad:%d - Arrays OK:%d Bad:%d - Disks OK:%d Bad:%d" % (nagiosgoodctrl, nagiosbadctrl, nagiosgoodarray, nagiosbadarray, nagiosgooddisk, nagiosbaddisk) ) ) else: if bad: print("\nThere is at least one disk/array in a NOT OPTIMAL state.") print('\nUse "arcconf GETCONFIG [1-9]" to get details.') sys.exit(1)