Skip to content

Commit 751dca8

Browse files
committed
Fix Proxmox VE sensor crashes due to missing API fields
Update all sensor definitions (Node, VM, Container, and Storage) to safely handle missing keys in the Proxmox API response by using dict.get(). This prevents KeyError crashes for entities where certain fields (like used_fraction, cpu, or memory) may be omitted. Added tests/components/proxmoxve/test_sensor_missing_data.py to verify robust handling of incomplete API data.
1 parent eb64589 commit 751dca8

File tree

2 files changed

+229
-37
lines changed

2 files changed

+229
-37
lines changed

homeassistant/components/proxmoxve/sensor.py

Lines changed: 63 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
6363
ProxmoxNodeSensorEntityDescription(
6464
key="node_cpu",
6565
translation_key="node_cpu",
66-
value_fn=lambda data: data.node["cpu"] * 100,
66+
value_fn=lambda data: (
67+
value * 100 if (value := data.node.get("cpu")) is not None else None
68+
),
6769
native_unit_of_measurement=PERCENTAGE,
6870
entity_category=EntityCategory.DIAGNOSTIC,
6971
suggested_display_precision=2,
@@ -72,12 +74,12 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
7274
ProxmoxNodeSensorEntityDescription(
7375
key="node_max_cpu",
7476
translation_key="node_max_cpu",
75-
value_fn=lambda data: data.node["maxcpu"],
77+
value_fn=lambda data: data.node.get("maxcpu"),
7678
),
7779
ProxmoxNodeSensorEntityDescription(
7880
key="node_disk",
7981
translation_key="node_disk",
80-
value_fn=lambda data: data.node["disk"],
82+
value_fn=lambda data: data.node.get("disk"),
8183
device_class=SensorDeviceClass.DATA_SIZE,
8284
native_unit_of_measurement=UnitOfInformation.BYTES,
8385
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -88,7 +90,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
8890
ProxmoxNodeSensorEntityDescription(
8991
key="node_max_disk",
9092
translation_key="node_max_disk",
91-
value_fn=lambda data: data.node["maxdisk"],
93+
value_fn=lambda data: data.node.get("maxdisk"),
9294
device_class=SensorDeviceClass.DATA_SIZE,
9395
native_unit_of_measurement=UnitOfInformation.BYTES,
9496
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -99,7 +101,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
99101
ProxmoxNodeSensorEntityDescription(
100102
key="node_memory",
101103
translation_key="node_memory",
102-
value_fn=lambda data: data.node["mem"],
104+
value_fn=lambda data: data.node.get("mem"),
103105
device_class=SensorDeviceClass.DATA_SIZE,
104106
native_unit_of_measurement=UnitOfInformation.BYTES,
105107
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -110,7 +112,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
110112
ProxmoxNodeSensorEntityDescription(
111113
key="node_max_memory",
112114
translation_key="node_max_memory",
113-
value_fn=lambda data: data.node["maxmem"],
115+
value_fn=lambda data: data.node.get("maxmem"),
114116
device_class=SensorDeviceClass.DATA_SIZE,
115117
native_unit_of_measurement=UnitOfInformation.BYTES,
116118
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -121,7 +123,13 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
121123
ProxmoxNodeSensorEntityDescription(
122124
key="node_memory_percentage",
123125
translation_key="node_memory_percentage",
124-
value_fn=lambda data: int(data.node["mem"]) / int(data.node["maxmem"]) * 100,
126+
value_fn=lambda data: (
127+
int(mem) / int(maxmem) * 100
128+
if (mem := data.node.get("mem")) is not None
129+
and (maxmem := data.node.get("maxmem"))
130+
and int(maxmem) > 0
131+
else None
132+
),
125133
native_unit_of_measurement=PERCENTAGE,
126134
entity_category=EntityCategory.DIAGNOSTIC,
127135
suggested_display_precision=2,
@@ -130,7 +138,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
130138
ProxmoxNodeSensorEntityDescription(
131139
key="node_uptime",
132140
translation_key="node_uptime",
133-
value_fn=lambda data: data.node["uptime"],
141+
value_fn=lambda data: data.node.get("uptime"),
134142
device_class=SensorDeviceClass.DURATION,
135143
native_unit_of_measurement=UnitOfTime.SECONDS,
136144
suggested_unit_of_measurement=UnitOfTime.HOURS,
@@ -140,16 +148,16 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
140148
ProxmoxNodeSensorEntityDescription(
141149
key="node_status",
142150
translation_key="node_status",
143-
value_fn=lambda data: data.node["status"],
151+
value_fn=lambda data: data.node.get("status"),
144152
device_class=SensorDeviceClass.ENUM,
145153
options=["online", "offline"],
146154
),
147155
ProxmoxNodeSensorEntityDescription(
148156
key="node_backup_last_backup",
149157
translation_key="node_backup_last_backup",
150158
value_fn=lambda data: (
151-
dt_util.utc_from_timestamp(data.backups[0]["endtime"])
152-
if data.backups
159+
dt_util.utc_from_timestamp(endtime)
160+
if data.backups and (endtime := data.backups[0].get("endtime")) is not None
153161
else None
154162
),
155163
device_class=SensorDeviceClass.TIMESTAMP,
@@ -159,8 +167,10 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
159167
key="node_backup_duration",
160168
translation_key="node_backup_duration",
161169
value_fn=lambda data: (
162-
data.backups[0]["endtime"] - data.backups[0]["starttime"]
170+
endtime - starttime
163171
if data.backups
172+
and (endtime := data.backups[0].get("endtime")) is not None
173+
and (starttime := data.backups[0].get("starttime")) is not None
164174
else None
165175
),
166176
device_class=SensorDeviceClass.DURATION,
@@ -175,12 +185,14 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
175185
ProxmoxVMSensorEntityDescription(
176186
key="vm_max_cpu",
177187
translation_key="vm_max_cpu",
178-
value_fn=lambda data: data["cpus"],
188+
value_fn=lambda data: data.get("cpus"),
179189
),
180190
ProxmoxVMSensorEntityDescription(
181191
key="vm_cpu",
182192
translation_key="vm_cpu",
183-
value_fn=lambda data: data["cpu"] * 100,
193+
value_fn=lambda data: (
194+
value * 100 if (value := data.get("cpu")) is not None else None
195+
),
184196
native_unit_of_measurement=PERCENTAGE,
185197
entity_category=EntityCategory.DIAGNOSTIC,
186198
suggested_display_precision=2,
@@ -189,7 +201,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
189201
ProxmoxVMSensorEntityDescription(
190202
key="vm_memory",
191203
translation_key="vm_memory",
192-
value_fn=lambda data: data["mem"],
204+
value_fn=lambda data: data.get("mem"),
193205
device_class=SensorDeviceClass.DATA_SIZE,
194206
native_unit_of_measurement=UnitOfInformation.BYTES,
195207
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -200,7 +212,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
200212
ProxmoxVMSensorEntityDescription(
201213
key="vm_max_memory",
202214
translation_key="vm_max_memory",
203-
value_fn=lambda data: data["maxmem"],
215+
value_fn=lambda data: data.get("maxmem"),
204216
device_class=SensorDeviceClass.DATA_SIZE,
205217
native_unit_of_measurement=UnitOfInformation.BYTES,
206218
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -211,7 +223,13 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
211223
ProxmoxVMSensorEntityDescription(
212224
key="vm_memory_percentage",
213225
translation_key="vm_memory_percentage",
214-
value_fn=lambda data: int(data["mem"]) / int(data["maxmem"]) * 100,
226+
value_fn=lambda data: (
227+
int(mem) / int(maxmem) * 100
228+
if (mem := data.get("mem")) is not None
229+
and (maxmem := data.get("maxmem"))
230+
and int(maxmem) > 0
231+
else None
232+
),
215233
native_unit_of_measurement=PERCENTAGE,
216234
entity_category=EntityCategory.DIAGNOSTIC,
217235
suggested_display_precision=2,
@@ -220,7 +238,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
220238
ProxmoxVMSensorEntityDescription(
221239
key="vm_uptime",
222240
translation_key="vm_uptime",
223-
value_fn=lambda data: data["uptime"],
241+
value_fn=lambda data: data.get("uptime"),
224242
device_class=SensorDeviceClass.DURATION,
225243
native_unit_of_measurement=UnitOfTime.SECONDS,
226244
suggested_unit_of_measurement=UnitOfTime.HOURS,
@@ -230,7 +248,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
230248
ProxmoxVMSensorEntityDescription(
231249
key="vm_disk",
232250
translation_key="vm_disk",
233-
value_fn=lambda data: data["disk"],
251+
value_fn=lambda data: data.get("disk"),
234252
device_class=SensorDeviceClass.DATA_SIZE,
235253
native_unit_of_measurement=UnitOfInformation.BYTES,
236254
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -241,7 +259,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
241259
ProxmoxVMSensorEntityDescription(
242260
key="vm_max_disk",
243261
translation_key="vm_max_disk",
244-
value_fn=lambda data: data["maxdisk"],
262+
value_fn=lambda data: data.get("maxdisk"),
245263
device_class=SensorDeviceClass.DATA_SIZE,
246264
native_unit_of_measurement=UnitOfInformation.BYTES,
247265
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -252,14 +270,14 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
252270
ProxmoxVMSensorEntityDescription(
253271
key="vm_status",
254272
translation_key="vm_status",
255-
value_fn=lambda data: data["status"],
273+
value_fn=lambda data: data.get("status"),
256274
device_class=SensorDeviceClass.ENUM,
257275
options=["running", "stopped", "suspended"],
258276
),
259277
ProxmoxVMSensorEntityDescription(
260278
key="vm_netin",
261279
translation_key="vm_netin",
262-
value_fn=lambda data: data["netin"],
280+
value_fn=lambda data: data.get("netin"),
263281
device_class=SensorDeviceClass.DATA_SIZE,
264282
native_unit_of_measurement=UnitOfInformation.BYTES,
265283
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -270,7 +288,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
270288
ProxmoxVMSensorEntityDescription(
271289
key="vm_netout",
272290
translation_key="vm_netout",
273-
value_fn=lambda data: data["netout"],
291+
value_fn=lambda data: data.get("netout"),
274292
device_class=SensorDeviceClass.DATA_SIZE,
275293
native_unit_of_measurement=UnitOfInformation.BYTES,
276294
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -284,12 +302,14 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
284302
ProxmoxContainerSensorEntityDescription(
285303
key="container_max_cpu",
286304
translation_key="container_max_cpu",
287-
value_fn=lambda data: data["cpus"],
305+
value_fn=lambda data: data.get("cpus"),
288306
),
289307
ProxmoxContainerSensorEntityDescription(
290308
key="container_cpu",
291309
translation_key="container_cpu",
292-
value_fn=lambda data: data["cpu"] * 100,
310+
value_fn=lambda data: (
311+
value * 100 if (value := data.get("cpu")) is not None else None
312+
),
293313
native_unit_of_measurement=PERCENTAGE,
294314
entity_category=EntityCategory.DIAGNOSTIC,
295315
suggested_display_precision=2,
@@ -298,7 +318,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
298318
ProxmoxContainerSensorEntityDescription(
299319
key="container_memory",
300320
translation_key="container_memory",
301-
value_fn=lambda data: data["mem"],
321+
value_fn=lambda data: data.get("mem"),
302322
device_class=SensorDeviceClass.DATA_SIZE,
303323
native_unit_of_measurement=UnitOfInformation.BYTES,
304324
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -309,7 +329,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
309329
ProxmoxContainerSensorEntityDescription(
310330
key="container_max_memory",
311331
translation_key="container_max_memory",
312-
value_fn=lambda data: data["maxmem"],
332+
value_fn=lambda data: data.get("maxmem"),
313333
device_class=SensorDeviceClass.DATA_SIZE,
314334
native_unit_of_measurement=UnitOfInformation.BYTES,
315335
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -320,7 +340,13 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
320340
ProxmoxContainerSensorEntityDescription(
321341
key="container_memory_percentage",
322342
translation_key="container_memory_percentage",
323-
value_fn=lambda data: int(data["mem"]) / int(data["maxmem"]) * 100,
343+
value_fn=lambda data: (
344+
int(mem) / int(maxmem) * 100
345+
if (mem := data.get("mem")) is not None
346+
and (maxmem := data.get("maxmem"))
347+
and int(maxmem) > 0
348+
else None
349+
),
324350
native_unit_of_measurement=PERCENTAGE,
325351
entity_category=EntityCategory.DIAGNOSTIC,
326352
suggested_display_precision=2,
@@ -329,7 +355,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
329355
ProxmoxContainerSensorEntityDescription(
330356
key="container_uptime",
331357
translation_key="container_uptime",
332-
value_fn=lambda data: data["uptime"],
358+
value_fn=lambda data: data.get("uptime"),
333359
device_class=SensorDeviceClass.DURATION,
334360
native_unit_of_measurement=UnitOfTime.SECONDS,
335361
suggested_unit_of_measurement=UnitOfTime.HOURS,
@@ -339,7 +365,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
339365
ProxmoxContainerSensorEntityDescription(
340366
key="container_disk",
341367
translation_key="container_disk",
342-
value_fn=lambda data: data["disk"],
368+
value_fn=lambda data: data.get("disk"),
343369
device_class=SensorDeviceClass.DATA_SIZE,
344370
native_unit_of_measurement=UnitOfInformation.BYTES,
345371
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -350,7 +376,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
350376
ProxmoxContainerSensorEntityDescription(
351377
key="container_max_disk",
352378
translation_key="container_max_disk",
353-
value_fn=lambda data: data["maxdisk"],
379+
value_fn=lambda data: data.get("maxdisk"),
354380
device_class=SensorDeviceClass.DATA_SIZE,
355381
native_unit_of_measurement=UnitOfInformation.BYTES,
356382
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -361,14 +387,14 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
361387
ProxmoxContainerSensorEntityDescription(
362388
key="container_status",
363389
translation_key="container_status",
364-
value_fn=lambda data: data["status"],
390+
value_fn=lambda data: data.get("status"),
365391
device_class=SensorDeviceClass.ENUM,
366392
options=["running", "stopped", "suspended"],
367393
),
368394
ProxmoxContainerSensorEntityDescription(
369395
key="container_netin",
370396
translation_key="container_netin",
371-
value_fn=lambda data: data["netin"],
397+
value_fn=lambda data: data.get("netin"),
372398
device_class=SensorDeviceClass.DATA_SIZE,
373399
native_unit_of_measurement=UnitOfInformation.BYTES,
374400
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -379,7 +405,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
379405
ProxmoxContainerSensorEntityDescription(
380406
key="container_netout",
381407
translation_key="container_netout",
382-
value_fn=lambda data: data["netout"],
408+
value_fn=lambda data: data.get("netout"),
383409
device_class=SensorDeviceClass.DATA_SIZE,
384410
native_unit_of_measurement=UnitOfInformation.BYTES,
385411
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -393,7 +419,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
393419
ProxmoxStorageSensorEntityDescription(
394420
key="storage_used",
395421
translation_key="storage_used",
396-
value_fn=lambda data: data["used"],
422+
value_fn=lambda data: data.get("used"),
397423
device_class=SensorDeviceClass.DATA_SIZE,
398424
native_unit_of_measurement=UnitOfInformation.BYTES,
399425
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -404,7 +430,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
404430
ProxmoxStorageSensorEntityDescription(
405431
key="storage_total",
406432
translation_key="storage_total",
407-
value_fn=lambda data: data["total"],
433+
value_fn=lambda data: data.get("total"),
408434
device_class=SensorDeviceClass.DATA_SIZE,
409435
native_unit_of_measurement=UnitOfInformation.BYTES,
410436
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
@@ -415,7 +441,7 @@ class ProxmoxStorageSensorEntityDescription(SensorEntityDescription):
415441
ProxmoxStorageSensorEntityDescription(
416442
key="storage_available",
417443
translation_key="storage_available",
418-
value_fn=lambda data: data["avail"],
444+
value_fn=lambda data: data.get("avail"),
419445
device_class=SensorDeviceClass.DATA_SIZE,
420446
native_unit_of_measurement=UnitOfInformation.BYTES,
421447
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,

0 commit comments

Comments
 (0)