Skip to content

Commit

Permalink
Merge pull request #50 from xcp-ng/gtn-pr-46
Browse files Browse the repository at this point in the history
Update smartctl.py
  • Loading branch information
gthvn1 authored Feb 4, 2025
2 parents 82584b6 + 5dd7908 commit 9e6280e
Show file tree
Hide file tree
Showing 9 changed files with 1,838 additions and 101 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,17 +152,33 @@ $ xe host-call-plugin host-uuid=<uuid> plugin=lsblk.py fn=list_block_devices

## Smartctl parser

A xapi plugin to get information and health of physical disks on the host
This XAPI plugin provides information and health details for the physical disks on the host.

It uses the `smartctl --scan` command to retrieve the list of devices. For devices managed by
MegaRAID, the device names may be identical. To handle this, the plugin returns information
for each unique "name:type" pair.

The plugin parses the JSON output from the `smartctl` command to gather information and health
data. As a result, it requires a version of `smartctl` capable of producing JSON output.
This functionality is available in **XCP-ng 8.3**, but not in **XCP-ng 8.2**.

### `information`:

This function returns information about all detected devices. The JSON can be quite big.

```
xe host-call-plugin host-uuid=<uuid> plugin=smartctl.py fn=information
{"/dev/sdf": {"power_on_time": {"hours": 9336}, "ata_version": {"minor_value": 94, "string": "ACS-4 T13/BSR INCITS 529 revision 5", "major_value": 2556}, "form_factor": {"ata_value": 3, "name": "2.5 inches"}, "firmware_version": "SVQ02B6Q", "wwn": {"oui": 9528, "naa": 5, "id": 65536604056}, "smart_status": {"passed": true}, "smartctl": {"build_info": "(local build)", "exit_status": 0, "argv": ["smartctl", "-j", "-a", "/dev/sdf"], "version": [7, 0], "svn_revision": "4883", "platform_info": "x86_64-linux-4.19.0+1"}, "temperature": {"current": 35}, "rotation_rate": 0, "interface_speed": {"current": {"sata_value": 3, "units_per_second": 60, "string": "6.0 Gb/s", "bits_per_unit": 100000000}, [...] }
{"/dev/nvme1:nvme": {"smart_status": {"nvme": {"value": 0}, "passed": true}, "nvme_controller_id": 0, "smartctl": {"build_info": "(local build)", "exit_status": 0, "argv": ["smartctl", "-j", "-a", "-d", "nvme",
"/dev/nvme1"], "version": [7, 0], "svn_revision": "4883", "platform_info": "x86_64-linux-4.19.0+1"}, "temperature": {"current": 32}, ...
```

### `health`:

This function returns health status per detected devices.

```
xe host-call-plugin host-uuid=<uuid> plugin=smartctl.py fn=health
{"/dev/sdf": "PASSED", "/dev/sdg": "PASSED", "/dev/sdd": "PASSED", "/dev/sde": "PASSED", "/dev/sdb": "PASSED", "/dev/sdc": "PASSED", "/dev/sda": "PASSED"}
{"/dev/nvme1:nvme": "PASSED", "/dev/sda:scsi": "PASSED", "/dev/nvme0:nvme": "PASSED", "/dev/bus/0:megaraid,1": "PASSED", "/dev/bus/0:megaraid,0": "PASSED"}
```

## Netdata
Expand Down
29 changes: 15 additions & 14 deletions SOURCES/etc/xapi.d/plugins/smartctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,37 @@
from xcpngutils.operationlocker import OperationLocker

@error_wrapped
def _list_disks():
disks = []
def _list_devices():
devices = []
result = run_command(['smartctl', '--scan'])
for line in result['stdout'].splitlines():
if line.startswith('/dev/') and not line.startswith('/dev/bus/'):
disks.append(line.split()[0])
return disks
devices.append({'name': line.split()[0], 'type': line.split()[2]})
return devices

@error_wrapped
def get_information(session, args):
results = {}
with OperationLocker():
disks = _list_disks()
for disk in disks:
cmd = run_command(["smartctl", "-j", "-a", disk], check=False)
results[disk] = json.loads(cmd['stdout'])
devices = _list_devices()
for device in devices:
cmd = run_command(["smartctl", "-j", "-a", "-d", device['type'], device['name']], check=False)
# We use the name + type as a key because we can have several megaraid with the same name.
# So we use the type to differenciate them.
results[device['name'] + ":" + device['type']] = json.loads(cmd['stdout'])
return json.dumps(results)

@error_wrapped
def get_health(session, args):
results = {}
with OperationLocker():
disks = _list_disks()
for disk in disks:
cmd = run_command(["smartctl", "-j", "-H", disk])
devices = _list_devices()
for device in devices:
cmd = run_command(["smartctl", "-j", "-H", "-d", device['type'], device['name']], check=False)
json_output = json.loads(cmd['stdout'])
if json_output['smart_status']['passed']:
results[disk] = "PASSED"
results[device['name'] + ":" + device['type']] = "PASSED"
else:
results[disk] = "FAILED"
results[device['name'] + ":" + device['type']] = "FAILED"
return json.dumps(results)


Expand Down
Empty file.
30 changes: 30 additions & 0 deletions tests/smartctl_outputs/smartctl_expected_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import json
from smartctl_outputs.smartctl_sda import INFO_SDA
from smartctl_outputs.smartctl_nvme1 import INFO_NVME1
from smartctl_outputs.smartctl_megaraid0 import INFO_MEGARAID0
from smartctl_outputs.smartctl_megaraid1 import INFO_MEGARAID1

# Parse the INFO JSON string for each devices
info_sda_dict = json.loads(INFO_SDA)
info_nvme1_dict = json.loads(INFO_NVME1)
info_megaraid0_dict = json.loads(INFO_MEGARAID0)
info_megaraid1_dict = json.loads(INFO_MEGARAID1)

expected_info_dict = {
"/dev/sda:sat": info_sda_dict,
"/dev/nvme1:nvme": info_nvme1_dict,
"/dev/bus/0:megaraid,0": info_megaraid0_dict,
"/dev/bus/0:megaraid,1": info_megaraid1_dict,
}

# Convert the result back to a JSON string
EXPECTED_INFO = json.dumps(expected_info_dict, indent=2)

expected_health_dict = {
"/dev/sda:sat": "PASSED",
"/dev/nvme1:nvme": "PASSED",
"/dev/bus/0:megaraid,0": "PASSED",
"/dev/bus/0:megaraid,1": "PASSED",
}

EXPECTED_HEALTH = json.dumps(expected_health_dict, indent=2)
Loading

0 comments on commit 9e6280e

Please sign in to comment.