Skip to content

Commit 9e6280e

Browse files
authored
Merge pull request #50 from xcp-ng/gtn-pr-46
Update smartctl.py
2 parents 82584b6 + 5dd7908 commit 9e6280e

File tree

9 files changed

+1838
-101
lines changed

9 files changed

+1838
-101
lines changed

README.md

+19-3
Original file line numberDiff line numberDiff line change
@@ -152,17 +152,33 @@ $ xe host-call-plugin host-uuid=<uuid> plugin=lsblk.py fn=list_block_devices
152152

153153
## Smartctl parser
154154

155-
A xapi plugin to get information and health of physical disks on the host
155+
This XAPI plugin provides information and health details for the physical disks on the host.
156+
157+
It uses the `smartctl --scan` command to retrieve the list of devices. For devices managed by
158+
MegaRAID, the device names may be identical. To handle this, the plugin returns information
159+
for each unique "name:type" pair.
160+
161+
The plugin parses the JSON output from the `smartctl` command to gather information and health
162+
data. As a result, it requires a version of `smartctl` capable of producing JSON output.
163+
This functionality is available in **XCP-ng 8.3**, but not in **XCP-ng 8.2**.
164+
156165
### `information`:
166+
167+
This function returns information about all detected devices. The JSON can be quite big.
168+
157169
```
158170
xe host-call-plugin host-uuid=<uuid> plugin=smartctl.py fn=information
159-
{"/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}, [...] }
171+
{"/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",
172+
"/dev/nvme1"], "version": [7, 0], "svn_revision": "4883", "platform_info": "x86_64-linux-4.19.0+1"}, "temperature": {"current": 32}, ...
160173
```
161174

162175
### `health`:
176+
177+
This function returns health status per detected devices.
178+
163179
```
164180
xe host-call-plugin host-uuid=<uuid> plugin=smartctl.py fn=health
165-
{"/dev/sdf": "PASSED", "/dev/sdg": "PASSED", "/dev/sdd": "PASSED", "/dev/sde": "PASSED", "/dev/sdb": "PASSED", "/dev/sdc": "PASSED", "/dev/sda": "PASSED"}
181+
{"/dev/nvme1:nvme": "PASSED", "/dev/sda:scsi": "PASSED", "/dev/nvme0:nvme": "PASSED", "/dev/bus/0:megaraid,1": "PASSED", "/dev/bus/0:megaraid,0": "PASSED"}
166182
```
167183

168184
## Netdata

SOURCES/etc/xapi.d/plugins/smartctl.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,37 @@
1010
from xcpngutils.operationlocker import OperationLocker
1111

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

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

3132
@error_wrapped
3233
def get_health(session, args):
3334
results = {}
3435
with OperationLocker():
35-
disks = _list_disks()
36-
for disk in disks:
37-
cmd = run_command(["smartctl", "-j", "-H", disk])
36+
devices = _list_devices()
37+
for device in devices:
38+
cmd = run_command(["smartctl", "-j", "-H", "-d", device['type'], device['name']], check=False)
3839
json_output = json.loads(cmd['stdout'])
3940
if json_output['smart_status']['passed']:
40-
results[disk] = "PASSED"
41+
results[device['name'] + ":" + device['type']] = "PASSED"
4142
else:
42-
results[disk] = "FAILED"
43+
results[device['name'] + ":" + device['type']] = "FAILED"
4344
return json.dumps(results)
4445

4546

tests/smartctl_outputs/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import json
2+
from smartctl_outputs.smartctl_sda import INFO_SDA
3+
from smartctl_outputs.smartctl_nvme1 import INFO_NVME1
4+
from smartctl_outputs.smartctl_megaraid0 import INFO_MEGARAID0
5+
from smartctl_outputs.smartctl_megaraid1 import INFO_MEGARAID1
6+
7+
# Parse the INFO JSON string for each devices
8+
info_sda_dict = json.loads(INFO_SDA)
9+
info_nvme1_dict = json.loads(INFO_NVME1)
10+
info_megaraid0_dict = json.loads(INFO_MEGARAID0)
11+
info_megaraid1_dict = json.loads(INFO_MEGARAID1)
12+
13+
expected_info_dict = {
14+
"/dev/sda:sat": info_sda_dict,
15+
"/dev/nvme1:nvme": info_nvme1_dict,
16+
"/dev/bus/0:megaraid,0": info_megaraid0_dict,
17+
"/dev/bus/0:megaraid,1": info_megaraid1_dict,
18+
}
19+
20+
# Convert the result back to a JSON string
21+
EXPECTED_INFO = json.dumps(expected_info_dict, indent=2)
22+
23+
expected_health_dict = {
24+
"/dev/sda:sat": "PASSED",
25+
"/dev/nvme1:nvme": "PASSED",
26+
"/dev/bus/0:megaraid,0": "PASSED",
27+
"/dev/bus/0:megaraid,1": "PASSED",
28+
}
29+
30+
EXPECTED_HEALTH = json.dumps(expected_health_dict, indent=2)

0 commit comments

Comments
 (0)