Skip to content

Instantly share code, notes, and snippets.

@freewood
Forked from nijave/sas_devices.py
Last active December 7, 2021 11:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save freewood/7ef61826e2e4f1b490c4eb894704360d to your computer and use it in GitHub Desktop.
Save freewood/7ef61826e2e4f1b490c4eb894704360d to your computer and use it in GitHub Desktop.
Convert sas2ircu output to json for easier use in other tools
#!/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