Skip to content

Commit 22afa60

Browse files
mdegat01agnersCopilot
authored
Get lifetime info for NVMe devices (#6056)
* Get lifetime info for NVMe devices * Fix lint and test issues * Update tests/dbus_service_mocks/udisks2_manager.py Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Stefan Agner <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 9f2fda5 commit 22afa60

File tree

13 files changed

+739
-52
lines changed

13 files changed

+739
-52
lines changed

supervisor/dbus/const.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
DBUS_IFACE_IP4CONFIG = "org.freedesktop.NetworkManager.IP4Config"
3333
DBUS_IFACE_IP6CONFIG = "org.freedesktop.NetworkManager.IP6Config"
3434
DBUS_IFACE_NM = "org.freedesktop.NetworkManager"
35+
DBUS_IFACE_NVME_CONTROLLER = "org.freedesktop.UDisks2.NVMe.Controller"
3536
DBUS_IFACE_PARTITION = "org.freedesktop.UDisks2.Partition"
3637
DBUS_IFACE_PARTITION_TABLE = "org.freedesktop.UDisks2.PartitionTable"
3738
DBUS_IFACE_RAUC_INSTALLER = "de.pengutronix.rauc.Installer"
@@ -87,6 +88,7 @@
8788
DBUS_ATTR_CURRENT_DEVICE = "CurrentDevice"
8889
DBUS_ATTR_CURRENT_DNS_SERVER = "CurrentDNSServer"
8990
DBUS_ATTR_CURRENT_DNS_SERVER_EX = "CurrentDNSServerEx"
91+
DBUS_ATTR_CONTROLLER_ID = "ControllerID"
9092
DBUS_ATTR_DEFAULT = "Default"
9193
DBUS_ATTR_DEPLOYMENT = "Deployment"
9294
DBUS_ATTR_DESCRIPTION = "Description"
@@ -111,6 +113,7 @@
111113
DBUS_ATTR_EJECTABLE = "Ejectable"
112114
DBUS_ATTR_FALLBACK_DNS = "FallbackDNS"
113115
DBUS_ATTR_FALLBACK_DNS_EX = "FallbackDNSEx"
116+
DBUS_ATTR_FGUID = "FGUID"
114117
DBUS_ATTR_FINISH_TIMESTAMP = "FinishTimestamp"
115118
DBUS_ATTR_FIRMWARE_TIMESTAMP_MONOTONIC = "FirmwareTimestampMonotonic"
116119
DBUS_ATTR_FREQUENCY = "Frequency"
@@ -147,6 +150,7 @@
147150
DBUS_ATTR_NTP = "NTP"
148151
DBUS_ATTR_NTPSYNCHRONIZED = "NTPSynchronized"
149152
DBUS_ATTR_NUMBER = "Number"
153+
DBUS_ATTR_NVME_REVISION = "NVMeRevision"
150154
DBUS_ATTR_OFFSET = "Offset"
151155
DBUS_ATTR_OPERATING_SYSTEM_PRETTY_NAME = "OperatingSystemPrettyName"
152156
DBUS_ATTR_OPERATION = "Operation"
@@ -161,15 +165,24 @@
161165
DBUS_ATTR_RESOLV_CONF_MODE = "ResolvConfMode"
162166
DBUS_ATTR_REVISION = "Revision"
163167
DBUS_ATTR_RCMANAGER = "RcManager"
168+
DBUS_ATTR_SANITIZE_PERCENT_REMAINING = "SanitizePercentRemaining"
169+
DBUS_ATTR_SANITIZE_STATUS = "SanitizeStatus"
164170
DBUS_ATTR_SEAT = "Seat"
165171
DBUS_ATTR_SERIAL = "Serial"
166172
DBUS_ATTR_SIZE = "Size"
173+
DBUS_ATTR_SMART_CRITICAL_WARNING = "SmartCriticalWarning"
174+
DBUS_ATTR_SMART_POWER_ON_HOURS = "SmartPowerOnHours"
175+
DBUS_ATTR_SMART_SELFTEST_PERCENT_REMAINING = "SmartSelftestPercentRemaining"
176+
DBUS_ATTR_SMART_SELFTEST_STATUS = "SmartSelftestStatus"
177+
DBUS_ATTR_SMART_TEMPERATURE = "SmartTemperature"
178+
DBUS_ATTR_SMART_UPDATED = "SmartUpdated"
167179
DBUS_ATTR_SSID = "Ssid"
168180
DBUS_ATTR_STATE = "State"
169181
DBUS_ATTR_STATE_FLAGS = "StateFlags"
170182
DBUS_ATTR_STATIC_HOSTNAME = "StaticHostname"
171183
DBUS_ATTR_STATIC_OPERATING_SYSTEM_CPE_NAME = "OperatingSystemCPEName"
172184
DBUS_ATTR_STRENGTH = "Strength"
185+
DBUS_ATTR_SUBSYSTEM_NQN = "SubsystemNQN"
173186
DBUS_ATTR_SUPPORTED_FILESYSTEMS = "SupportedFilesystems"
174187
DBUS_ATTR_SYMLINKS = "Symlinks"
175188
DBUS_ATTR_SWAP_SIZE = "SwapSize"
@@ -180,6 +193,7 @@
180193
DBUS_ATTR_TIMEZONE = "Timezone"
181194
DBUS_ATTR_TRANSACTION_STATISTICS = "TransactionStatistics"
182195
DBUS_ATTR_TYPE = "Type"
196+
DBUS_ATTR_UNALLOCATED_CAPACITY = "UnallocatedCapacity"
183197
DBUS_ATTR_USER_LED = "UserLED"
184198
DBUS_ATTR_USERSPACE_TIMESTAMP_MONOTONIC = "UserspaceTimestampMonotonic"
185199
DBUS_ATTR_UUID_UPPERCASE = "UUID"

supervisor/dbus/udisks2/__init__.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import logging
5+
from pathlib import Path
56
from typing import Any
67

78
from awesomeversion import AwesomeVersion
@@ -132,7 +133,10 @@ async def update(self, changed: dict[str, Any] | None = None) -> None:
132133
for drive in drives
133134
}
134135

135-
# Update existing drives
136+
# For existing drives, need to check their type and call update
137+
await asyncio.gather(
138+
*[self._drives[path].check_type() for path in unchanged_drives]
139+
)
136140
await asyncio.gather(
137141
*[self._drives[path].update() for path in unchanged_drives]
138142
)
@@ -160,20 +164,33 @@ def drives(self) -> list[UDisks2Drive]:
160164
return list(self._drives.values())
161165

162166
@dbus_connected
163-
def get_drive(self, drive_path: str) -> UDisks2Drive:
167+
def get_drive(self, object_path: str) -> UDisks2Drive:
164168
"""Get additional info on drive from object path."""
165-
if drive_path not in self._drives:
166-
raise DBusObjectError(f"Drive {drive_path} not found")
169+
if object_path not in self._drives:
170+
raise DBusObjectError(f"Drive {object_path} not found")
167171

168-
return self._drives[drive_path]
172+
return self._drives[object_path]
169173

170174
@dbus_connected
171-
def get_block_device(self, device_path: str) -> UDisks2Block:
175+
def get_block_device(self, object_path: str) -> UDisks2Block:
172176
"""Get additional info on block device from object path."""
173-
if device_path not in self._block_devices:
174-
raise DBusObjectError(f"Block device {device_path} not found")
177+
if object_path not in self._block_devices:
178+
raise DBusObjectError(f"Block device {object_path} not found")
175179

176-
return self._block_devices[device_path]
180+
return self._block_devices[object_path]
181+
182+
@dbus_connected
183+
def get_block_device_by_path(self, device_path: Path) -> UDisks2Block:
184+
"""Get additional info on block device from device path.
185+
186+
Uses cache only. Use `resolve_device` to force a call for fresh data.
187+
"""
188+
for device in self._block_devices.values():
189+
if device.device == device_path:
190+
return device
191+
raise DBusObjectError(
192+
f"Block device not found with device path {device_path.as_posix()}"
193+
)
177194

178195
@dbus_connected
179196
async def resolve_device(self, devspec: DeviceSpecification) -> list[UDisks2Block]:

supervisor/dbus/udisks2/drive.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Interface to UDisks2 Drive over D-Bus."""
22

33
from datetime import UTC, datetime
4+
from typing import Any
45

56
from dbus_fast.aio import MessageBus
67

@@ -18,11 +19,13 @@
1819
DBUS_ATTR_VENDOR,
1920
DBUS_ATTR_WWN,
2021
DBUS_IFACE_DRIVE,
22+
DBUS_IFACE_NVME_CONTROLLER,
2123
DBUS_NAME_UDISKS2,
2224
)
2325
from ..interface import DBusInterfaceProxy, dbus_property
2426
from ..utils import dbus_connected
2527
from .const import UDISKS2_DEFAULT_OPTIONS
28+
from .nvme_controller import UDisks2NVMeController
2629

2730

2831
class UDisks2Drive(DBusInterfaceProxy):
@@ -35,11 +38,18 @@ class UDisks2Drive(DBusInterfaceProxy):
3538
bus_name: str = DBUS_NAME_UDISKS2
3639
properties_interface: str = DBUS_IFACE_DRIVE
3740

41+
_nvme_controller: UDisks2NVMeController | None = None
42+
3843
def __init__(self, object_path: str) -> None:
3944
"""Initialize object."""
4045
self._object_path = object_path
4146
super().__init__()
4247

48+
async def connect(self, bus: MessageBus) -> None:
49+
"""Connect to bus."""
50+
await super().connect(bus)
51+
await self._reload_interfaces()
52+
4353
@staticmethod
4454
async def new(object_path: str, bus: MessageBus) -> "UDisks2Drive":
4555
"""Create and connect object."""
@@ -52,6 +62,11 @@ def object_path(self) -> str:
5262
"""Object path for dbus object."""
5363
return self._object_path
5464

65+
@property
66+
def nvme_controller(self) -> UDisks2NVMeController | None:
67+
"""NVMe controller interface if drive is one."""
68+
return self._nvme_controller
69+
5570
@property
5671
@dbus_property
5772
def vendor(self) -> str:
@@ -130,3 +145,40 @@ def ejectable(self) -> bool:
130145
async def eject(self) -> None:
131146
"""Eject media from drive."""
132147
await self.connected_dbus.Drive.call("eject", UDISKS2_DEFAULT_OPTIONS)
148+
149+
@dbus_connected
150+
async def update(self, changed: dict[str, Any] | None = None) -> None:
151+
"""Update properties via D-Bus."""
152+
await super().update(changed)
153+
154+
if not changed and self.nvme_controller:
155+
await self.nvme_controller.update()
156+
157+
@dbus_connected
158+
async def check_type(self) -> None:
159+
"""Check if type of drive has changed and adjust interfaces if so."""
160+
introspection = await self.connected_dbus.introspect()
161+
interfaces = {intr.name for intr in introspection.interfaces}
162+
163+
# If interfaces changed, update the proxy from introspection and reload interfaces
164+
if interfaces != set(self.connected_dbus.proxies.keys()):
165+
await self.connected_dbus.init_proxy(introspection=introspection)
166+
await self._reload_interfaces()
167+
168+
@dbus_connected
169+
async def _reload_interfaces(self) -> None:
170+
"""Reload interfaces from introspection as necessary."""
171+
# Check if drive is an nvme controller
172+
if (
173+
not self.nvme_controller
174+
and DBUS_IFACE_NVME_CONTROLLER in self.connected_dbus.proxies
175+
):
176+
self._nvme_controller = UDisks2NVMeController(self.object_path)
177+
await self._nvme_controller.initialize(self.connected_dbus)
178+
179+
elif (
180+
self.nvme_controller
181+
and DBUS_IFACE_NVME_CONTROLLER not in self.connected_dbus.proxies
182+
):
183+
self.nvme_controller.stop_sync_property_changes()
184+
self._nvme_controller = None

0 commit comments

Comments
 (0)