Skip to content

Commit 0523f1e

Browse files
committed
Add nvme status action
1 parent f6aebe6 commit 0523f1e

File tree

7 files changed

+256
-100
lines changed

7 files changed

+256
-100
lines changed

aiohasupervisor/client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,11 @@ async def _request(
8686
json: dict[str, Any] | None = None,
8787
data: Any = None,
8888
timeout: ClientTimeout | None = DEFAULT_TIMEOUT,
89+
uri_encoded: bool = False,
8990
) -> Response:
9091
"""Handle a request to Supervisor."""
9192
try:
92-
url = URL(self.api_host).joinpath(uri)
93+
url = URL(self.api_host).joinpath(uri, encoded=uri_encoded)
9394
except ValueError as err:
9495
raise SupervisorError from err
9596

@@ -158,6 +159,7 @@ async def get(
158159
params: dict[str, str] | MultiDict[str] | None = None,
159160
response_type: ResponseType = ResponseType.JSON,
160161
timeout: ClientTimeout | None = DEFAULT_TIMEOUT,
162+
uri_encoded: bool = False,
161163
) -> Response:
162164
"""Handle a GET request to Supervisor."""
163165
return await self._request(
@@ -166,6 +168,7 @@ async def get(
166168
params=params,
167169
response_type=response_type,
168170
timeout=timeout,
171+
uri_encoded=uri_encoded,
169172
)
170173

171174
async def post(
@@ -177,6 +180,7 @@ async def post(
177180
json: dict[str, Any] | None = None,
178181
data: Any = None,
179182
timeout: ClientTimeout | None = DEFAULT_TIMEOUT,
183+
uri_encoded: bool = False,
180184
) -> Response:
181185
"""Handle a POST request to Supervisor."""
182186
return await self._request(
@@ -187,6 +191,7 @@ async def post(
187191
json=json,
188192
data=data,
189193
timeout=timeout,
194+
uri_encoded=uri_encoded,
190195
)
191196

192197
async def put(
@@ -196,6 +201,7 @@ async def put(
196201
params: dict[str, str] | MultiDict[str] | None = None,
197202
json: dict[str, Any] | None = None,
198203
timeout: ClientTimeout | None = DEFAULT_TIMEOUT,
204+
uri_encoded: bool = False,
199205
) -> Response:
200206
"""Handle a PUT request to Supervisor."""
201207
return await self._request(
@@ -205,6 +211,7 @@ async def put(
205211
response_type=ResponseType.NONE,
206212
json=json,
207213
timeout=timeout,
214+
uri_encoded=uri_encoded,
208215
)
209216

210217
async def delete(
@@ -214,6 +221,7 @@ async def delete(
214221
params: dict[str, str] | MultiDict[str] | None = None,
215222
json: dict[str, Any] | None = None,
216223
timeout: ClientTimeout | None = DEFAULT_TIMEOUT,
224+
uri_encoded: bool = False,
217225
) -> Response:
218226
"""Handle a DELETE request to Supervisor."""
219227
return await self._request(
@@ -223,6 +231,7 @@ async def delete(
223231
response_type=ResponseType.NONE,
224232
json=json,
225233
timeout=timeout,
234+
uri_encoded=uri_encoded,
226235
)
227236

228237
async def close(self) -> None:

aiohasupervisor/host.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
"""Host client for supervisor."""
22

3+
import re
4+
from urllib.parse import quote
5+
36
from .client import _SupervisorComponentClient
47
from .const import TIMEOUT_60_SECONDS
8+
from .exceptions import SupervisorError
59
from .models.host import (
610
HostInfo,
711
HostOptions,
12+
NVMeStatus,
813
RebootOptions,
914
Service,
1015
ServiceList,
1116
ShutdownOptions,
1217
)
1318

19+
RE_NVME_DEVICE = re.compile(r"^(?:[-A-Fa-f0-9]+|\/dev\/[-_a-z0-9]+)$")
20+
1421

1522
class HostClient(_SupervisorComponentClient):
1623
"""Handles host access in supervisor."""
@@ -47,4 +54,27 @@ async def services(self) -> list[Service]:
4754
result = await self._client.get("host/services")
4855
return ServiceList.from_dict(result.data).services
4956

57+
async def nvme_status(self, device: str | None = None) -> NVMeStatus:
58+
"""Get NVMe status for a device.
59+
60+
Device can be the Host ID or device path (e.g. /dev/nvme0n1).
61+
If omitted, returns status of datadisk if it is an nvme device.
62+
"""
63+
if device is not None:
64+
# Encoding must be done here because something like /dev/nvme0n1 is
65+
# valid and that won't work in the resource path. But that means we
66+
# bypass part of the safety check that would normally raise on any
67+
# encoded chars. So strict validation needs to be done here rather
68+
# then letting Supervisor handle it like normal.
69+
if not RE_NVME_DEVICE.match(device):
70+
raise SupervisorError(f"Invalid device: {device}")
71+
72+
encoded = quote(device, safe="")
73+
result = await self._client.get(
74+
f"host/nvme/{encoded}/status", uri_encoded=True
75+
)
76+
else:
77+
result = await self._client.get("host/nvme/status")
78+
return NVMeStatus.from_dict(result.data)
79+
5080
# Omitted for now - Log endpoints

aiohasupervisor/models/__init__.py

Lines changed: 92 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
from aiohasupervisor.models.host import (
6666
HostInfo,
6767
HostOptions,
68+
NVMeStatus,
6869
RebootOptions,
6970
Service,
7071
ServiceState,
@@ -152,130 +153,131 @@
152153
)
153154

154155
__all__ = [
155-
"HostFeature",
156-
"SupervisorState",
157-
"UpdateChannel",
158-
"LogLevel",
159-
"UpdateType",
160-
"RootInfo",
161-
"AvailableUpdate",
162-
"AddonStage",
163-
"AddonStartup",
156+
"LOCATION_CLOUD_BACKUP",
157+
"LOCATION_LOCAL_STORAGE",
158+
"AccessPoint",
164159
"AddonBoot",
165160
"AddonBootConfig",
166-
"CpuArch",
167-
"Capability",
168-
"AppArmor",
169-
"SupervisorRole",
161+
"AddonSet",
162+
"AddonStage",
163+
"AddonStartup",
170164
"AddonState",
171-
"StoreAddon",
172-
"StoreAddonComplete",
173-
"InstalledAddon",
174-
"InstalledAddonComplete",
175-
"AddonsOptions",
176165
"AddonsConfigValidate",
166+
"AddonsOptions",
177167
"AddonsRebuild",
178168
"AddonsSecurityOptions",
179169
"AddonsStats",
180170
"AddonsUninstall",
181-
"Repository",
182-
"StoreInfo",
183-
"StoreAddonUpdate",
184-
"StoreAddRepository",
185-
"Check",
186-
"CheckOptions",
187-
"CheckType",
188-
"ContextType",
189-
"Issue",
190-
"IssueType",
191-
"ResolutionInfo",
192-
"Suggestion",
193-
"SuggestionType",
194-
"UnhealthyReason",
195-
"UnsupportedReason",
196-
"SupervisorInfo",
197-
"SupervisorOptions",
198-
"SupervisorStats",
199-
"SupervisorUpdateOptions",
200-
"HomeAssistantInfo",
201-
"HomeAssistantOptions",
202-
"HomeAssistantRebuildOptions",
203-
"HomeAssistantRestartOptions",
204-
"HomeAssistantStats",
205-
"HomeAssistantStopOptions",
206-
"HomeAssistantUpdateOptions",
207-
"RaucState",
208-
"BootSlotName",
209-
"BootSlot",
210-
"OSInfo",
211-
"OSUpdate",
212-
"MigrateDataOptions",
213-
"DataDisk",
214-
"SetBootSlotOptions",
215-
"GreenInfo",
216-
"GreenOptions",
217-
"YellowInfo",
218-
"YellowOptions",
219-
"LOCATION_CLOUD_BACKUP",
220-
"LOCATION_LOCAL_STORAGE",
221-
"AddonSet",
171+
"AppArmor",
172+
"AuthMethod",
173+
"AvailableUpdate",
222174
"Backup",
223175
"BackupAddon",
224176
"BackupComplete",
225177
"BackupContent",
226178
"BackupJob",
227179
"BackupLocationAttributes",
180+
"BackupType",
228181
"BackupsInfo",
229182
"BackupsOptions",
230-
"BackupType",
183+
"BootSlot",
184+
"BootSlotName",
185+
"CIFSMountRequest",
186+
"CIFSMountResponse",
187+
"Capability",
188+
"Check",
189+
"CheckOptions",
190+
"CheckType",
191+
"ContextType",
192+
"CpuArch",
193+
"DataDisk",
194+
"Discovery",
195+
"DiscoveryConfig",
196+
"DockerNetwork",
231197
"DownloadBackupOptions",
232198
"Folder",
233199
"FreezeOptions",
234200
"FullBackupOptions",
235201
"FullRestoreOptions",
236-
"NewBackup",
237-
"PartialBackupOptions",
238-
"PartialRestoreOptions",
239-
"RemoveBackupOptions",
240-
"UploadBackupOptions",
241-
"Discovery",
242-
"DiscoveryConfig",
243-
"AccessPoint",
244-
"AuthMethod",
245-
"DockerNetwork",
246-
"InterfaceMethod",
247-
"InterfaceType",
202+
"GreenInfo",
203+
"GreenOptions",
204+
"HomeAssistantInfo",
205+
"HomeAssistantOptions",
206+
"HomeAssistantRebuildOptions",
207+
"HomeAssistantRestartOptions",
208+
"HomeAssistantStats",
209+
"HomeAssistantStopOptions",
210+
"HomeAssistantUpdateOptions",
211+
"HostFeature",
212+
"HostInfo",
213+
"HostOptions",
248214
"IPv4",
249215
"IPv4Config",
250216
"IPv6",
251217
"IPv6Config",
252-
"NetworkInfo",
253-
"NetworkInterface",
254-
"NetworkInterfaceConfig",
255-
"Vlan",
256-
"VlanConfig",
257-
"Wifi",
258-
"WifiConfig",
259-
"WifiMode",
260-
"HostInfo",
261-
"HostOptions",
262-
"RebootOptions",
263-
"Service",
264-
"ServiceState",
265-
"ShutdownOptions",
218+
"InstalledAddon",
219+
"InstalledAddonComplete",
220+
"InterfaceMethod",
221+
"InterfaceType",
222+
"Issue",
223+
"IssueType",
266224
"Job",
267225
"JobCondition",
268226
"JobError",
269227
"JobsInfo",
270228
"JobsOptions",
271-
"CIFSMountRequest",
272-
"CIFSMountResponse",
229+
"LogLevel",
230+
"MigrateDataOptions",
273231
"MountCifsVersion",
274-
"MountsInfo",
275-
"MountsOptions",
276232
"MountState",
277233
"MountType",
278234
"MountUsage",
235+
"MountsInfo",
236+
"MountsOptions",
279237
"NFSMountRequest",
280238
"NFSMountResponse",
239+
"NVMeStatus",
240+
"NetworkInfo",
241+
"NetworkInterface",
242+
"NetworkInterfaceConfig",
243+
"NewBackup",
244+
"OSInfo",
245+
"OSUpdate",
246+
"PartialBackupOptions",
247+
"PartialRestoreOptions",
248+
"RaucState",
249+
"RebootOptions",
250+
"RemoveBackupOptions",
251+
"Repository",
252+
"ResolutionInfo",
253+
"RootInfo",
254+
"Service",
255+
"ServiceState",
256+
"SetBootSlotOptions",
257+
"ShutdownOptions",
258+
"StoreAddRepository",
259+
"StoreAddon",
260+
"StoreAddonComplete",
261+
"StoreAddonUpdate",
262+
"StoreInfo",
263+
"Suggestion",
264+
"SuggestionType",
265+
"SupervisorInfo",
266+
"SupervisorOptions",
267+
"SupervisorRole",
268+
"SupervisorState",
269+
"SupervisorStats",
270+
"SupervisorUpdateOptions",
271+
"UnhealthyReason",
272+
"UnsupportedReason",
273+
"UpdateChannel",
274+
"UpdateType",
275+
"UploadBackupOptions",
276+
"Vlan",
277+
"VlanConfig",
278+
"Wifi",
279+
"WifiConfig",
280+
"WifiMode",
281+
"YellowInfo",
282+
"YellowOptions",
281283
]

aiohasupervisor/models/host.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,25 @@ class ServiceList(ResponseData):
9696
"""ServiceList model."""
9797

9898
services: list[Service]
99+
100+
101+
@dataclass(frozen=True, slots=True)
102+
class NVMeStatus(ResponseData):
103+
"""NVMeStatus model."""
104+
105+
available_spare: int
106+
critical_warning: int
107+
data_units_read: int
108+
data_units_written: int
109+
percent_used: int
110+
temperature_kelvin: int
111+
host_read_commands: int
112+
host_write_commands: int
113+
controller_busy_minutes: int
114+
power_cycles: int
115+
power_on_hours: int
116+
unsafe_shutdowns: int
117+
media_errors: int
118+
number_error_log_entries: int
119+
warning_temp_minutes: int
120+
critical_composite_temp_minutes: int

0 commit comments

Comments
 (0)