-
-
Save freewood/7ef61826e2e4f1b490c4eb894704360d to your computer and use it in GitHub Desktop.
Convert sas2ircu output to json for easier use in other tools
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Usage: | |
./sas_devices.py - json output | |
./sas_devices.py --summary - human output with locations and serial | |
Warning: sas2ircu invocations have occassionally caused kernel | |
panics on previous FreeBSD installations | |
Related https://redmine.ixsystems.com/issues/14859 | |
Note: This was only tested on a setup with a single controller | |
which had a single enclosure attached. Parsing may not | |
be sufficient for other configurations | |
Field naming conventions were copied directly from command output | |
in places where field names have been hardcoded | |
sas2ircu: Version 20.00.00.00 (2014.09.18) | |
os: FreeBSD nas 12.2-RELEASE-p3 FreeBSD 12.2-RELEASE-p3 7851f4a452d(HEAD) TRUENAS amd64 | |
By: Nick Venenga <nick@venenga.com> | |
""" | |
import json | |
import re | |
import shutil | |
import subprocess | |
import sys | |
if not shutil.which("sas2ircu"): | |
sys.stderr.write(f"sas2ircu: Command not found.\n") | |
sys.exit(1) | |
cmd_output = subprocess.check_output(["sas2ircu", "list"], text=True).splitlines() | |
lines = iter(cmd_output) | |
# skip lines til hyphen/space divider | |
while not re.match(r"^[- ]+$", next(lines)): | |
pass | |
controllers = [ | |
dict( | |
zip( | |
[ | |
"Index", | |
"Adapter Type", | |
"Vendor ID", | |
"Device ID", | |
"Pci Address", | |
"SubSys Ven ID", | |
"SubSys Dev ID", | |
], | |
line.split(), | |
) | |
) | |
for line in lines | |
if line.strip() and line.strip()[0].isnumeric() | |
] | |
def parse_controller_display(index): | |
cmd_output = subprocess.check_output( | |
["sas2ircu", index, "display"], | |
text=True, | |
) | |
sections = "|".join( | |
re.escape(s) | |
for s in [ | |
"Controller information", | |
"IR Volume information", | |
"Physical device information", | |
"Enclosure information", | |
] | |
) | |
section_finder = re.compile( | |
"(" + sections + ")\n[-]{8,}\n(.*?)[-]{8,}\n", flags=re.DOTALL | re.MULTILINE | |
) | |
results = {} | |
for header, data in section_finder.findall(cmd_output): | |
# This section is "special" in that it has "sub-sections" denoted by non-indented fields | |
# Each subsection is a device | |
if header == "Physical device information": | |
results.update( | |
{ | |
header: [ | |
{ | |
line.split(":")[0].strip(): line.split(":", 1)[1].strip() | |
for line in device.strip().splitlines() | |
} | |
for device in re.split(r"^Device is .+\n", data, flags=re.M) | |
if re.match("\s+[^\s]+", device) | |
] | |
} | |
) | |
elif header == "IR Volume information": | |
results.update( | |
{ | |
header: [ | |
{ | |
line.split(":")[0].strip(): line.split(":", 1)[1].strip() | |
for line in device.strip().splitlines() | |
} | |
for device in re.split(r"^IR volume .+\n", data, flags=re.M) | |
if re.match("\s+[^\s]+", device) | |
] | |
} | |
) | |
else: | |
results.update( | |
{ | |
header: { | |
line.split(":")[0].strip(): line.split(":", 1)[1].strip() | |
for line in data.splitlines() | |
} | |
} | |
) | |
return results | |
devices = [parse_controller_display(controller["Index"]) for controller in controllers] | |
if len(sys.argv) == 2 and sys.argv[1] == "--summary": | |
print( | |
"\n".join( | |
[ | |
f'Controller: {c["Controller information"]["Device"]} Enclosure: {device["Enclosure #"]} Slot: {device["Slot #"]} Serial: {device["Serial No"]}' | |
for c in devices | |
for device in c["Physical device information"] | |
if "Drive Type" in device | |
# if any(typ in device.get("Drive Type") for typ in ("SAS", "SATA")) | |
] | |
) | |
) | |
else: | |
print(json.dumps(devices, indent=2)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment