Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a plugin to get information from ipmitool #48

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,91 @@ $ xe host-call-plugin host-uuid=<uuid> plugin=hyperthreading.py fn=get_hyperthre
true
```

## Ipmitool

A xapi plugin that uses `ipmitool` to get information about sensors and the IPMI server. Before
running the commands you need to ensure that your system have support for IPMI.

### `is_ipmi_device_available`

Returns `true` if IPMI device is found and `ipmitool` can be used. If it could not open device at `/dev/ipmi*`
it returns `false`. In that case you need to ensure the IPMI module is loaded and that your system
supports IPMI. Others unexpected errors raise a XenAPIPlugin error.

```
$ xe host-call-plugin host-uuid=<uuid> plugin=ipmitool.py fn=is_ipmi_device_available
false
```

If `true` is returned you should be able to run `get_all_sensors`, `get_sensor`
or `get_ipmi_lan` without raising a XenAPIPlugin error.

### `get_all_sensors`

Returns a JSON containing all sensor data repository entries and readings or raise a XenAPIPlugin error.
```
$ xe host-call-plugin host-uuid=<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`

Returns a JSON containing detailed information about the sensors passed as paramaters
or raise an XenAPIPlugin error. The names of the sensors can be found by running `get_all_sensors`
function. If a wrong sensor name is passed an error is logged in `/var/log/ipmitool-xapi-plugin-plugin.log`
and the sensor is skipped.

```
$ xe host-call-plugin host-uuid=<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_ipmi_lan`

Returns JSON that contains information about the configuration of the network related to the IPMI server
or raise a XenAPIPlugin error.

```
$ xe host-call-plugin host-uuid=<uuid> plugin=ipmitool.py fn=get_ipmi_lan
[
{
"name": "IP Address Source",
"value": "Static Address"
},
{
"name": "IP Address",
"value": "1.2.3.4"
},
{
"name": "Subnet Mask",
"value": "255.255.255.0"
},
{
"name": "MAC Address",
"value": "a8:ac:a2:a5:a0:ae"
},
...
]

```

## Tests

To run the plugins' unit tests you'll need to install `pytest`, `pyfakefs` and `mock`.
Expand Down
135 changes: 135 additions & 0 deletions SOURCES/etc/xapi.d/plugins/ipmitool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/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,
ProcessException,
gthvn1 marked this conversation as resolved.
Show resolved Hide resolved
raise_plugin_error,
)


@error_wrapped
def _is_ipmi_available():
# Try to run a simple command to check if ipmitool works
# If the command raise an error saying that we cannot open the IPMI device it means
# that IPMI is not available on the system. So we return False. If we don't know the
# error we raise it again and it will need to be debugged...
try:
gthvn1 marked this conversation as resolved.
Show resolved Hide resolved
_ = run_command(["ipmitool", "chassis", "status"])
except ProcessException as e:
if "Could not open device" in e.stderr:
return False
raise e

return True


def is_ipmi_device_available(_session, _args):
return json.dumps(_is_ipmi_available())


@error_wrapped
def check_ipmi_availability(func):
def wrapper(*args, **kwargs):
if not _is_ipmi_available():
raise_plugin_error(1, "IPMI not available")
return func(*args, **kwargs)

return wrapper


@check_ipmi_availability
def sensor_data(_session, _args):
sensor_data = []
output = run_command(["ipmitool", "sdr", "list"])

for line in output["stdout"].splitlines():
if not line:
continue
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)


@check_ipmi_availability
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])

# If there is an error while getting info about the sensor skip it
# and report the error.
if output["stderr"]:
_LOGGER.error("{}".format(output["stderr"].rstrip()))
continue

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)


@check_ipmi_availability
def ipmi_lan(_session, _args):
lan_info = []
wanted = [
"IP Address",
"Subnet Mask",
"MAC Address",
"BMC ARP Control",
"Default Gateway IP",
"802.1q VLAN",
"RMCP+ Cipher Suites",
]

output = run_command(["ipmitool", "lan", "print"])

for line in output["stdout"].splitlines():
if any(word in line for word in wanted):
name, value = line.split(":", 1)
lan_info.append({
"name": name.strip(),
"value": value.strip(),
})

return json.dumps(lan_info)


_LOGGER = configure_logging("ipmitool-xapi-plugin")
if __name__ == "__main__":
XenAPIPlugin.dispatch({
"is_ipmi_device_available": is_ipmi_device_available,
"get_all_sensors": sensor_data,
"get_sensor": sensor_info,
"get_ipmi_lan": ipmi_lan,
})
Loading
Loading