#!/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', 'Hot Spare', 'Ready', 'Global Hot-Spare', 'Dedicated 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, 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)