Update set_fanspeed.py
This commit is contained in:
@@ -4,24 +4,55 @@ import re
|
||||
import sys
|
||||
import datetime
|
||||
import argparse
|
||||
import configparser
|
||||
import os
|
||||
|
||||
# IPMI settings
|
||||
IPMIUSER = "<username>"
|
||||
IPMIPW = "<password>"
|
||||
IPMIEK = "0000000000000000000000000000000000000000"
|
||||
DEFAULT_EK = "0000000000000000000000000000000000000000"
|
||||
|
||||
# IPMI settings (loaded at runtime from credentials file)
|
||||
IPMIUSER = None
|
||||
IPMIPW = None
|
||||
IPMIEK = DEFAULT_EK
|
||||
|
||||
# Fan / temperature settings
|
||||
FANSPEED = 20 # percent
|
||||
MAXTEMP = 37 # Celsius
|
||||
|
||||
def build_ipmi_base():
|
||||
base = ["ipmitool", "-I", IPMIINTERFACE, "-H", IPMIHOST, "-U", IPMIUSER, "-P", IPMIPW, "-y", IPMIEK]
|
||||
if IPMICIPHER is not None:
|
||||
base += ["-C", str(IPMICIPHER)]
|
||||
return base
|
||||
|
||||
def has_ipmi_session_error(stderr_text):
|
||||
s = (stderr_text or "").lower()
|
||||
markers = [
|
||||
"unable to establish ipmi v2 / rmcp+ session",
|
||||
"rakp",
|
||||
"session timeout",
|
||||
"authentication type",
|
||||
"invalid user name",
|
||||
]
|
||||
return any(marker in s for marker in markers)
|
||||
|
||||
def run_ipmi(args):
|
||||
base = ["ipmitool", "-I", "lanplus", "-H", IPMIHOST, "-U", IPMIUSER, "-P", IPMIPW, "-y", IPMIEK]
|
||||
base = build_ipmi_base()
|
||||
cmd = base + args
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError(f"ipmitool failed: {' '.join(cmd)}\n{p.stderr.strip()}")
|
||||
if p.returncode != 0 or has_ipmi_session_error(p.stderr):
|
||||
raise RuntimeError(
|
||||
f"ipmitool failed: {' '.join(cmd)}\n"
|
||||
f"stdout: {(p.stdout or '').strip() or '<empty>'}\n"
|
||||
f"stderr: {(p.stderr or '').strip() or '<empty>'}"
|
||||
)
|
||||
return p.stdout
|
||||
|
||||
def run_ipmi_raw(args):
|
||||
base = build_ipmi_base()
|
||||
cmd = base + args
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
return cmd, p.returncode, p.stdout, p.stderr
|
||||
|
||||
def log_systemd(tag, msg):
|
||||
try:
|
||||
subprocess.run(["systemd-cat", "-t", tag], input=msg + "\n", text=True, check=True)
|
||||
@@ -34,33 +65,108 @@ def ts_print(msg):
|
||||
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(f"{now} {msg}")
|
||||
|
||||
def get_ambient_temp():
|
||||
out = run_ipmi(["sdr", "type", "temperature"])
|
||||
# find lines like "Exhuat Temp | 29 degrees C"
|
||||
lines = [l for l in out.splitlines() if "Exhaust" in l and "degrees" in l]
|
||||
if not lines:
|
||||
raise RuntimeError("Exhaust temperature line not found in ipmitool output")
|
||||
# Extract last number found in the last matching line (like the original)
|
||||
field = lines[-1].split("|")[4]
|
||||
#ts_print("Debug: field =", field)
|
||||
m = re.search(r"(\d{1,3})", field)
|
||||
if not m:
|
||||
raise RuntimeError("Could not parse temperature from line: " + lines[-1])
|
||||
return int(m.group(1))
|
||||
def load_credentials(path):
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError(f"credentials file not found: {path}")
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(path)
|
||||
|
||||
try:
|
||||
username = config.get("credentials", "username", fallback="root")
|
||||
password = config.get("credentials", "password")
|
||||
encryption_key = config.get("credentials", "encryption_key", fallback=DEFAULT_EK)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError) as e:
|
||||
raise RuntimeError(f"error reading credentials file: {e}")
|
||||
|
||||
return username, password, encryption_key
|
||||
|
||||
def get_exhaust_temp():
|
||||
commands_to_try = [
|
||||
["sensor", "get", "Exhaust Temp"],
|
||||
["sdr", "type", "temperature"],
|
||||
["sensor", "list"],
|
||||
["sdr", "elist", "full"],
|
||||
]
|
||||
|
||||
debug_sections = []
|
||||
session_error_seen = False
|
||||
for args in commands_to_try:
|
||||
cmd, rc, out, err = run_ipmi_raw(args)
|
||||
if has_ipmi_session_error(err):
|
||||
session_error_seen = True
|
||||
cmd_text = " ".join(cmd)
|
||||
section = [
|
||||
f"Command: {cmd_text}",
|
||||
f"Return code: {rc}",
|
||||
"STDOUT:",
|
||||
out.strip() or "<empty>",
|
||||
"STDERR:",
|
||||
err.strip() or "<empty>",
|
||||
]
|
||||
debug_sections.append("\n".join(section))
|
||||
|
||||
# Parse explicit sensor-get output first.
|
||||
if args[:2] == ["sensor", "get"] and out:
|
||||
m = re.search(r"Sensor\s+Reading\s*:\s*(-?\d+(?:\.\d+)?)\b.*degrees\s*C", out, re.IGNORECASE)
|
||||
if m:
|
||||
return int(float(m.group(1)))
|
||||
|
||||
# Parse table outputs that have pipe-delimited sensor rows.
|
||||
for line in out.splitlines():
|
||||
parts = [p.strip() for p in line.split("|")]
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
|
||||
sensor_name = parts[0]
|
||||
normalized_name = " ".join(sensor_name.lower().split())
|
||||
if not (normalized_name == "exhaust temp" or normalized_name.startswith("exhaust temp ")):
|
||||
continue
|
||||
|
||||
for part in parts[1:]:
|
||||
m = re.search(r"(-?\d+(?:\.\d+)?)\s*degrees\s*C", part, re.IGNORECASE)
|
||||
if m:
|
||||
return int(float(m.group(1)))
|
||||
|
||||
if session_error_seen:
|
||||
raise RuntimeError(
|
||||
"Failed to establish an IPMI session. "
|
||||
"Check host/credentials, then try different options like "
|
||||
"--interface lan --cipher 3 or --interface lanplus --cipher 17.\n\n"
|
||||
+ "Debug output from attempted commands:\n\n"
|
||||
+ "\n\n---\n\n".join(debug_sections)
|
||||
)
|
||||
|
||||
raise RuntimeError(
|
||||
"Failed to read Exhaust Temp from ipmitool output.\n"
|
||||
+ "Debug output from attempted commands:\n\n"
|
||||
+ "\n\n---\n\n".join(debug_sections)
|
||||
)
|
||||
|
||||
def main():
|
||||
# parse CLI args (IPMI host can be overridden with -H/--host)
|
||||
parser = argparse.ArgumentParser(description="Set fan speed via IPMI")
|
||||
parser.add_argument("-H", "--host", default="127.0.0.1", help="IPMI host/IP (default: %(default)s)")
|
||||
parser.add_argument("-H", "--host", required=True, help="IPMI host/IP")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--credentials",
|
||||
default="/etc/credentials",
|
||||
help="Path to credentials file (default: %(default)s)",
|
||||
)
|
||||
parser.add_argument("--interface", default="lanplus", choices=["lanplus", "lan"], help="IPMI interface (default: %(default)s)")
|
||||
parser.add_argument("--cipher", type=int, default=None, help="Optional IPMI cipher suite ID (e.g. 3 or 17)")
|
||||
args = parser.parse_args()
|
||||
|
||||
global IPMIHOST
|
||||
# set global IPMIHOST so run_ipmi() uses the provided host
|
||||
global IPMIHOST, IPMIINTERFACE, IPMICIPHER, IPMIUSER, IPMIPW, IPMIEK
|
||||
# set globals so run_ipmi() uses provided connection settings and credentials
|
||||
IPMIHOST = args.host
|
||||
IPMIINTERFACE = args.interface
|
||||
IPMICIPHER = args.cipher
|
||||
IPMIUSER, IPMIPW, IPMIEK = load_credentials(args.credentials)
|
||||
|
||||
ts_print(f"{IPMIHOST}: Fan speed check for host {IPMIHOST}")
|
||||
try:
|
||||
temp = get_ambient_temp()
|
||||
temp = get_exhaust_temp()
|
||||
ts_print(f"{IPMIHOST}: Exhaust temperature: {temp} C")
|
||||
except Exception as e:
|
||||
ts_print(f"{IPMIHOST}: Error reading temperature: {e}")
|
||||
|
||||
Reference in New Issue
Block a user