#!/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 device number [0-9]+$',line.strip()): ids.append(line.strip('Logical device number').strip()) return ids def returnArrayInfo(output): 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 logical device\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 [0-9]+\s+: .*$',line.strip()): splitter = re.compile('(\(.*\))') # The line can be either # Segment 0 : Present (Controller:1,Enclosure:0,Slot:0) JPW9J0N00RWMUV # Or # Segment 0 : Present (Controller:1,Channel:0,Device:0) S13PJ1CQ719255 # Or # Segment 0 : Present (Controller:1,Connector:1,Device:2) 9QJ7D0MJ line = re.sub('Controller:','',line) line = re.sub('(Channel|Enclosure|Connector):','',line) line = re.sub('(Device|Slot):','',line) line = line.split(':')[1] if re.match(r'^ Missing',line): members.append('?,?') else: members.append(splitter.split(line)[1].strip('(').strip(')')) 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) # 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) 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] != 'Online' and disk[1] != 'Hot Spare' and disk[1] != 'Ready' and disk[1] != 'Global Hot-Spare': 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) # 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)