From 5c79109278bf3e9e47b7b91c15745cde15e2d8b8 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Thu, 5 Sep 2024 16:59:43 +0200 Subject: [PATCH] Add a plugin to get information from ipmitool - get_all_sensors: run `ipmitool sdr list` - returns a JSON string with all sensors - get_sensor: run `ipmitool sdr get ` - returns details about sensors passed as parameter - get_ipmi_ip: returns the IP of the IPMI server as a string Signed-off-by: Guillaume --- README.md | 46 ++++++++++++++++ SOURCES/etc/xapi.d/plugins/ipmitool.py | 72 ++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100755 SOURCES/etc/xapi.d/plugins/ipmitool.py diff --git a/README.md b/README.md index c1aba22..67f754f 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,52 @@ $ xe host-call-plugin host-uuid= plugin=hyperthreading.py fn=get_hyperthre true ``` +## Ipmitool + +A xapi plugin that uses `ipmitool` to get information about sensors and the ipmi server. + +### get sensor data + +Returns a JSON containing all sensor data repository entries and readings. +``` +$ xe host-call-plugin host-uuid= plugin=ipmitool.py fn=get_all_sensors +[ + {"name": "Fan1A", "value": "10920 RPM", "event": "ok"}, + {"name": "Fan2A", "value": "10800 RPM", "event": "ok"}, + {"name": "Inlet Temp", "value": "23 degrees C", "event": "ok"}, + {"name": "Exhaust Temp", "value": "28 degrees C", "event": "ok"}, + {"name": "Temp", "value": "38 degrees C", "event": "ok"} + {"name": "PFault Fail Safe", "value": "Not Readable", "event": "ns"} + ... +] +``` + +### get sensor details + +Returns a JSON containing detailed information about sensor passed as paramaters. The name of the sensor can be found +by running the `get_all_sensors` before. +``` +$ xe host-call-plugin host-uuid= plugin=ipmitool.py fn=get_sensor args:sensors="Fan7B,PFault Fail Safe" +[ + { + "name": "Fan7B", + "info": [{"name": "Sensor ID", "value": "Fan7B (0x3d)"}, {"name": "Entity ID", "value": "7.1 (System Board)"}, {"name": "Sensor Type (Threshold)", "value": "Fan (0x04)"}, {"name": "Sensor Reading", "value": "10320 (+/- 120) RPM"}, {"name": "Status", "value": "ok"}, {"name": "Nominal Reading", "value": "6720.000"}, {"name": "Normal Minimum", "value": "16680.000"}, {"name": "Normal Maximum", "value": "23640.000"}, {"name": "Lower critical", "value": "720.000"}, {"name": "Lower non-critical", "value": "840.000"}, {"name": "Positive Hysteresis", "value": "120.000"}, {"name": "Negative Hysteresis", "value": "120.000"}, {"name": "Minimum sensor range", "value": "Unspecified"}, {"name": "Maximum sensor range", "value": "Unspecified"}, {"name": "Event Message Control", "value": "Per-threshold"}, {"name": "Readable Thresholds", "value": "lcr lnc"}, {"name": "Settable Thresholds", "value": ""}, {"name": "Threshold Read Mask", "value": "lcr lnc"}, {"name": "Assertion Events", "value": ""}, {"name": "Assertions Enabled", "value": "lnc- lcr-"}, {"name": "Deassertions Enabled", "value": "lnc- lcr-"}] + }, + { + "name": "PFault Fail Safe", + "info": [{"name": "Sensor ID", "value": "PFault Fail Safe (0x66)"}, {"name": "Entity ID", "value": "7.1 (System Board)"}, {"name": "Sensor Type (Discrete)", "value": "Voltage (0x02)"}, {"name": "Sensor Reading", "value": "No Reading"}, {"name": "Event Message Control", "value": "Per-threshold"}, {"name": "OEM", "value": "0"}] + } +] +``` + +### get the IP address of the IPMI server + +Returns a string with the IP of the IPMI server or an empty string if it is not found. +``` +$ xe host-call-plugin host-uuid= plugin=ipmitool.py fn=get_ipmi_ip +1.2.3.4 +``` + ## Tests To run the plugins' unit tests you'll need to install `pytest`, `pyfakefs` and `mock`. diff --git a/SOURCES/etc/xapi.d/plugins/ipmitool.py b/SOURCES/etc/xapi.d/plugins/ipmitool.py new file mode 100755 index 0000000..28e16cd --- /dev/null +++ b/SOURCES/etc/xapi.d/plugins/ipmitool.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import json +import sys +import XenAPIPlugin + +sys.path.append('.') +from xcpngutils import configure_logging, run_command, error_wrapped + +@error_wrapped +def sensor_data(session, args): + sensor_data = [] + output = run_command(["ipmitool", "sdr", "list"]) + + for line in output['stdout'].splitlines(): + sensor_fields = line.split('|') + sensor_data.append({ + 'name': sensor_fields[0].strip(), + 'value': sensor_fields[1].strip(), + 'event': sensor_fields[2].strip(), + }) + + return json.dumps(sensor_data) + +@error_wrapped +def sensor_info(session, args): + sensors_info = [] + sensors = args.get('sensors') + + if not sensors: + return '{}' + + for sensor in sensors.split(','): + sensor = sensor.strip() + info = [] + output = run_command(["ipmitool", "sdr", "get", sensor]) + for line in output['stdout'].splitlines(): + if ":" not in line: + continue + name, value = line.split(":", 1) + info.append({ + "name": name.strip(), + "value": value.strip(), + }) + + sensors_info.append({ + "name": sensor, + "info": info, + }) + + return json.dumps(sensors_info) + +@error_wrapped +def ipmi_ip(session, args): + ip_addr = "" + output = run_command(["ipmitool", "lan", "print"]) + + for line in output['stdout'].splitlines(): + if "IP Address" in line and "Source" not in line: + ip_addr = line.split(":")[1].strip() + break + + return ip_addr + +_LOGGER = configure_logging('ipmitool-xapi-plugin') +if __name__ == "__main__": + XenAPIPlugin.dispatch({ + 'get_all_sensors': sensor_data, + 'get_sensor': sensor_info, + 'get_ipmi_ip': ipmi_ip, + })