mirror of
https://github.com/eLvErDe/hwraid.git
synced 2025-05-28 02:20:07 +02:00
411 lines
14 KiB
Python
411 lines
14 KiB
Python
#!/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)
|