Skip to content

Commit 4f4e1c1

Browse files
Storage management: Add SMART status to disk infos
- Also rewrite API using sdbus-python
1 parent ec4f5c1 commit 4f4e1c1

File tree

6 files changed

+896
-58
lines changed

6 files changed

+896
-58
lines changed

debian/control

+2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ Depends: python3-all (>= 3.11),
1717
, python3-ldap, python3-zeroconf (>= 0.47), python3-lexicon,
1818
, python3-cryptography, python3-jwt, python3-passlib, python3-magic
1919
, python-is-python3, python3-pydantic, python3-email-validator
20+
, python3-sortedcollections, python3-sdbus
2021
, udisks2, udisks2-bcache, udisks2-btrfs, udisks2-lvm2, udisks2-zram
22+
, smartmontools
2123
, python3-zmq
2224
, nginx, nginx-extras (>=1.22)
2325
, apt, apt-transport-https, apt-utils, aptitude, dirmngr

share/actionsmap.yml

+11-11
Original file line numberDiff line numberDiff line change
@@ -2117,27 +2117,27 @@ storage:
21172117
subcategories:
21182118
disk:
21192119
subcategory_help: Manage et get infos about hard-drives
2120+
storage_disks_common_args: &storage_disks_common_args
2121+
-H:
2122+
full: --human-readable
2123+
help: Print informations in a human-readable format
2124+
action: store_true
2125+
--human-readable-size:
2126+
help: Print sizes in a human-readable format
2127+
action: store_true
21202128
actions:
21212129
# storage_disks_list
21222130
list:
21232131
action_help: List hard-drives currently attached to this system optionnaly with infos
21242132
api: GET /storage/disk/list
21252133
arguments:
2134+
<<: *storage_disks_common_args
21262135
-i:
21272136
full: --with-info
21282137
help: Get all informations for each archive
21292138
action: store_true
2130-
-H:
2131-
full: --human-readable
2132-
help: Print sizes in human readable format
2133-
action: store_true
2139+
# storage_disks_info
21342140
info:
21352141
action_help: Get hard-drive information
21362142
api: GET /storage/disk/info/<name>
2137-
arguments:
2138-
name:
2139-
help: Name of the hard drive as returned by 'list' command
2140-
-H:
2141-
full: --human-readable
2142-
help: Print sizes in human readable format
2143-
action: store_true
2143+
arguments: *storage_disks_common_args

src/disk.py

+61-43
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,54 @@
1-
import dbus
1+
import enum
2+
3+
from sdbus import sd_bus_open_system
24
from yunohost.utils.system import binary_to_human
5+
from yunohost.utils.udisks2_interfaces import Udisks2Manager
36

47
UDISK_DRIVE_PATH = "/org/freedesktop/UDisks2/drives/"
58
UDISK_DRIVE_IFC = "org.freedesktop.UDisks2.Drive"
6-
7-
8-
def _query_udisks() -> list[tuple[str, dict]]:
9-
bus = dbus.SystemBus()
10-
manager = dbus.Interface(
11-
bus.get_object("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2"),
12-
"org.freedesktop.DBus.ObjectManager",
13-
)
14-
15-
return sorted(
16-
(
17-
(name.removeprefix(UDISK_DRIVE_PATH), dev[UDISK_DRIVE_IFC])
18-
for name, dev in manager.GetManagedObjects().items()
19-
if name.startswith(UDISK_DRIVE_PATH)
20-
),
21-
key=lambda item: item[1]["SortKey"],
22-
)
23-
24-
25-
def _disk_infos(name: str, drive: dict, human_readable=False):
9+
UDISK_DRIVE_ATA_IFC = "org.freedesktop.UDisks2.Drive.Ata"
10+
UDISK_DRIVE_NVME_IFC = "org.freedesktop.UDisks2.Manager.NVMe"
11+
12+
13+
class DiskState(enum.StrEnum):
14+
"""https://github.com/storaged-project/udisks/issues/1352#issuecomment-2678874537"""
15+
@staticmethod
16+
def _generate_next_value_(name, start, count, last_values):
17+
return name.upper()
18+
19+
SANE = enum.auto()
20+
CRITICAL = enum.auto()
21+
UNKNOWN = enum.auto()
22+
23+
@staticmethod
24+
def parse(drive: dict) -> "DiskState":
25+
match drive:
26+
case {"smart_failing": failing}:
27+
# ATA disk, see https://storaged.org/doc/udisks2-api/latest/gdbus-org.freedesktop.UDisks2.Drive.Ata.html#gdbus-property-org-freedesktop-UDisks2-Drive-Ata.SmartFailing
28+
return DiskState.SANE if not failing else DiskState.CRITICAL
29+
case {"smart_critical_warning": failing}:
30+
# NVME; see https://storaged.org/doc/udisks2-api/latest/gdbus-org.freedesktop.UDisks2.NVMe.Controller.html#gdbus-property-org-freedesktop-UDisks2-NVMe-Controller.SmartCriticalWarning
31+
return DiskState.SANE if not failing else DiskState.CRITICAL
32+
case _:
33+
return DiskState.UNKNOWN
34+
35+
36+
def _disk_infos(name: str, drive: dict, **kwargs):
37+
human_readable = kwargs.get("human_readable", False)
38+
human_readable_size = kwargs.get("human_readable_size", human_readable)
2639
result = {
2740
"name": name,
28-
"model": drive["Model"],
29-
"serial": drive["Serial"],
30-
"removable": bool(drive["MediaRemovable"]),
31-
"size": binary_to_human(drive["Size"]) if human_readable else drive["Size"],
41+
"model": drive["model"],
42+
"serial": drive["serial"],
43+
"removable": bool(drive["media_removable"]),
44+
"size": binary_to_human(drive["size"]) if human_readable_size else drive["size"],
45+
"smartStatus": DiskState.parse(drive),
3246
}
3347

34-
if connection_bus := drive["ConnectionBus"]:
35-
result["connection_bus"] = connection_bus
48+
if "connection_bus" in drive:
49+
result["connectionBus"] = drive["connection_bus"]
3650

37-
if (rotation_rate := drive["RotationRate"]) == -1:
51+
if (rotation_rate := drive["rotation_rate"]) == -1:
3852
result.update(
3953
{
4054
"type": "HDD",
@@ -54,25 +68,29 @@ def _disk_infos(name: str, drive: dict, human_readable=False):
5468
return result
5569

5670

57-
def disk_list(with_info=False, human_readable=False):
58-
if not with_info:
59-
return [name for name, _ in _query_udisks()]
71+
def disk_list(**kwargs):
72+
bus = sd_bus_open_system()
73+
disks = Udisks2Manager(bus).get_disks()
74+
75+
with_info = kwargs.get("with_info", False)
6076

61-
result = []
77+
if not with_info:
78+
return list(disks.keys())
6279

63-
for name, drive in _query_udisks():
64-
result.append(_disk_infos(name, drive, human_readable))
80+
result = [
81+
_disk_infos(name, disk.props, **kwargs) for name, disk in disks.items()
82+
]
6583

6684
return {"disks": result}
6785

6886

69-
def disk_info(name, human_readable=False):
70-
bus = dbus.SystemBus()
71-
drive = dbus.Interface(
72-
bus.get_object(
73-
"org.freedesktop.UDisks2", f"/org/freedesktop/UDisks2/drives/{name}"
74-
),
75-
dbus.PROPERTIES_IFACE,
76-
).GetAll("org.freedesktop.UDisks2.Drive")
87+
def disk_info(name, **kwargs):
88+
bus = sd_bus_open_system()
89+
disk = Udisks2Manager(bus).get_disks().get(name)
90+
91+
human_readable = kwargs.get("human_readable", False)
92+
93+
if not disk:
94+
return f"Unknown disk with name {name}" if human_readable else None
7795

78-
return _disk_infos(name, drive, human_readable)
96+
return _disk_infos(name, disk.props, **kwargs)

src/smart.py

Whitespace-only changes.

src/storage.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
def storage_disk_list(with_info=False, human_readable=False):
1+
def storage_disk_list(**kargs):
22
from yunohost.disk import disk_list
33

4-
return disk_list(with_info=with_info, human_readable=human_readable)
4+
return disk_list(**kargs)
55

66

7-
def storage_disk_info(name, human_readable=False):
7+
def storage_disk_info(name, **kargs):
88
from yunohost.disk import disk_info
99

10-
return disk_info(name, human_readable=human_readable)
10+
return disk_info(name, **kargs)

0 commit comments

Comments
 (0)