From cd0834bfbef869462344961bc8a1645c1cbc513e Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Wed, 25 Mar 2026 16:11:41 +0100 Subject: [PATCH] Add storages to Proxmox (#166409) --- .../components/proxmoxve/binary_sensor.py | 76 ++- homeassistant/components/proxmoxve/const.py | 3 + .../components/proxmoxve/coordinator.py | 55 +- homeassistant/components/proxmoxve/entity.py | 53 ++ homeassistant/components/proxmoxve/icons.json | 32 ++ homeassistant/components/proxmoxve/sensor.py | 88 +++- .../components/proxmoxve/strings.json | 33 ++ tests/components/proxmoxve/conftest.py | 3 + .../proxmoxve/fixtures/nodes/storage.json | 26 + .../snapshots/test_binary_sensor.ambr | 300 +++++++++++ .../proxmoxve/snapshots/test_diagnostics.ambr | 26 + .../proxmoxve/snapshots/test_sensor.ambr | 474 ++++++++++++++++++ 12 files changed, 1158 insertions(+), 11 deletions(-) create mode 100644 tests/components/proxmoxve/fixtures/nodes/storage.json diff --git a/homeassistant/components/proxmoxve/binary_sensor.py b/homeassistant/components/proxmoxve/binary_sensor.py index b3bf0d625efa..f0064465e5de 100644 --- a/homeassistant/components/proxmoxve/binary_sensor.py +++ b/homeassistant/components/proxmoxve/binary_sensor.py @@ -15,9 +15,21 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from .const import NODE_ONLINE, STATUS_OK, VM_CONTAINER_RUNNING +from .const import ( + NODE_ONLINE, + STATUS_OK, + STORAGE_ACTIVE, + STORAGE_ENABLED, + STORAGE_SHARED, + VM_CONTAINER_RUNNING, +) from .coordinator import ProxmoxConfigEntry, ProxmoxNodeData -from .entity import ProxmoxContainerEntity, ProxmoxNodeEntity, ProxmoxVMEntity +from .entity import ( + ProxmoxContainerEntity, + ProxmoxNodeEntity, + ProxmoxStorageEntity, + ProxmoxVMEntity, +) PARALLEL_UPDATES = 0 @@ -43,6 +55,13 @@ class ProxmoxNodeBinarySensorEntityDescription(BinarySensorEntityDescription): state_fn: Callable[[ProxmoxNodeData], bool | None] +@dataclass(frozen=True, kw_only=True) +class ProxmoxStorageBinarySensorEntityDescription(BinarySensorEntityDescription): + """Class to hold Proxmox storage binary sensor description.""" + + state_fn: Callable[[dict[str, Any]], bool | None] + + NODE_SENSORS: tuple[ProxmoxNodeBinarySensorEntityDescription, ...] = ( ProxmoxNodeBinarySensorEntityDescription( key="status", @@ -82,6 +101,27 @@ VM_SENSORS: tuple[ProxmoxVMBinarySensorEntityDescription, ...] = ( ), ) +STORAGE_SENSORS: tuple[ProxmoxStorageBinarySensorEntityDescription, ...] = ( + ProxmoxStorageBinarySensorEntityDescription( + key="storage_active", + translation_key="storage_active", + state_fn=lambda data: data["active"] == STORAGE_ACTIVE, + entity_category=EntityCategory.DIAGNOSTIC, + ), + ProxmoxStorageBinarySensorEntityDescription( + key="storage_enabled", + translation_key="storage_enabled", + state_fn=lambda data: data["enabled"] == STORAGE_ENABLED, + entity_category=EntityCategory.DIAGNOSTIC, + ), + ProxmoxStorageBinarySensorEntityDescription( + key="storage_shared", + translation_key="storage_shared", + state_fn=lambda data: data["shared"] == STORAGE_SHARED, + entity_category=EntityCategory.DIAGNOSTIC, + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -121,9 +161,22 @@ async def async_setup_entry( for entity_description in CONTAINER_SENSORS ) + def _async_add_new_storages( + storages: list[tuple[ProxmoxNodeData, dict[str, Any]]], + ) -> None: + """Add new storage binary sensors.""" + async_add_entities( + ProxmoxStorageBinarySensor( + coordinator, entity_description, storage, node_data + ) + for (node_data, storage) in storages + for entity_description in STORAGE_SENSORS + ) + coordinator.new_nodes_callbacks.append(_async_add_new_nodes) coordinator.new_vms_callbacks.append(_async_add_new_vms) coordinator.new_containers_callbacks.append(_async_add_new_containers) + coordinator.new_storages_callbacks.append(_async_add_new_storages) _async_add_new_nodes( [ @@ -148,6 +201,14 @@ async def async_setup_entry( if (node_data.node["node"], vmid) in coordinator.known_containers ] ) + _async_add_new_storages( + [ + (node_data, storage_data) + for node_data in coordinator.data.values() + for storage_id, storage_data in node_data.storages.items() + if (node_data.node["node"], storage_id) in coordinator.known_storages + ] + ) class ProxmoxNodeBinarySensor(ProxmoxNodeEntity, BinarySensorEntity): @@ -181,3 +242,14 @@ class ProxmoxContainerBinarySensor(ProxmoxContainerEntity, BinarySensorEntity): def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" return self.entity_description.state_fn(self.container_data) + + +class ProxmoxStorageBinarySensor(ProxmoxStorageEntity, BinarySensorEntity): + """Representation of a Proxmox Storage binary sensor.""" + + entity_description: ProxmoxStorageBinarySensorEntityDescription + + @property + def is_on(self) -> bool | None: + """Return true if the binary sensor is on.""" + return self.entity_description.state_fn(self.storage_data) diff --git a/homeassistant/components/proxmoxve/const.py b/homeassistant/components/proxmoxve/const.py index 54f0784f40d4..ad0a0ebda69a 100644 --- a/homeassistant/components/proxmoxve/const.py +++ b/homeassistant/components/proxmoxve/const.py @@ -16,6 +16,9 @@ CONF_USER = "user" NODE_ONLINE = "online" VM_CONTAINER_RUNNING = "running" +STORAGE_ACTIVE = 1 +STORAGE_SHARED = 1 +STORAGE_ENABLED = 1 STATUS_OK = "ok" AUTH_PAM = "pam" diff --git a/homeassistant/components/proxmoxve/coordinator.py b/homeassistant/components/proxmoxve/coordinator.py index 493a9b91ab0a..6231a989a215 100644 --- a/homeassistant/components/proxmoxve/coordinator.py +++ b/homeassistant/components/proxmoxve/coordinator.py @@ -54,6 +54,7 @@ class ProxmoxNodeData: node: dict[str, Any] = field(default_factory=dict) vms: dict[int, dict[str, Any]] = field(default_factory=dict) containers: dict[int, dict[str, Any]] = field(default_factory=dict) + storages: dict[str, dict[str, Any]] = field(default_factory=dict) backups: list[dict[str, Any]] = field(default_factory=list) @@ -80,6 +81,7 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]): self.known_nodes: set[str] = set() self.known_vms: set[tuple[str, int]] = set() self.known_containers: set[tuple[str, int]] = set() + self.known_storages: set[tuple[str, str]] = set() self.permissions: dict[str, dict[str, int]] = {} self.new_nodes_callbacks: list[Callable[[list[ProxmoxNodeData]], None]] = [] @@ -89,6 +91,9 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]): self.new_containers_callbacks: list[ Callable[[list[tuple[ProxmoxNodeData, dict[str, Any]]]], None] ] = [] + self.new_storages_callbacks: list[ + Callable[[list[tuple[ProxmoxNodeData, dict[str, Any]]]], None] + ] = [] async def _async_setup(self) -> None: """Set up the coordinator.""" @@ -173,13 +178,16 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]): ) from err data: dict[str, ProxmoxNodeData] = {} - for node, (vms, containers, backups) in zip(nodes, vms_containers, strict=True): + for node, (vms, containers, storages, backups) in zip( + nodes, vms_containers, strict=True + ): data[node[CONF_NODE]] = ProxmoxNodeData( node=node, vms={int(vm["vmid"]): vm for vm in vms}, containers={ int(container["vmid"]): container for container in containers }, + storages={s["storage"]: s for s in storages}, backups=backups, ) @@ -229,9 +237,16 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]): self, ) -> tuple[ list[dict[str, Any]], - list[tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]], + list[ + tuple[ + list[dict[str, Any]], + list[dict[str, Any]], + list[dict[str, Any]], + list[dict[str, Any]], + ] + ], ]: - """Fetch all nodes, and then proceed to the VMs, containers, and backups.""" + """Fetch all nodes, and then proceed to the VMs, containers, storages, and backups.""" nodes = self.proxmox.nodes.get() or [] node_data = [self._get_node_data(node) for node in nodes] return nodes, node_data @@ -239,22 +254,29 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]): def _get_node_data( self, node: dict[str, Any], - ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]: - """Get vms, containers, and backups for a node.""" + ) -> tuple[ + list[dict[str, Any]], + list[dict[str, Any]], + list[dict[str, Any]], + list[dict[str, Any]], + ]: + """Get vms, containers, storages, and backups for a node.""" if node.get("status") != NODE_ONLINE: _LOGGER.debug( - "Node %s is offline, skipping VM/container fetch", + "Node %s is offline, skipping VM/container/storage fetch", node[CONF_NODE], ) - return [], [], [] + return [], [], [], [] vms = self.proxmox.nodes(node[CONF_NODE]).qemu.get() or [] containers = self.proxmox.nodes(node[CONF_NODE]).lxc.get() or [] + storages = self.proxmox.nodes(node[CONF_NODE]).storage.get() or [] backups = ( self.proxmox.nodes(node[CONF_NODE]).tasks.get(typefilter="vzdump", limit=1) or [] ) - return vms, containers, backups + + return vms, containers, storages, backups def _async_add_remove_nodes(self, data: dict[str, ProxmoxNodeData]) -> None: """Add new nodes/VMs/containers, track removals.""" @@ -303,6 +325,23 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]): for containers_callback in self.new_containers_callbacks: containers_callback(new_container_data) + current_storages = { + (node_name, storage_name) + for node_name, node_data in data.items() + for storage_name in node_data.storages + } + self.known_storages &= current_storages + new_storages = current_storages - self.known_storages + if new_storages: + _LOGGER.debug("New storages found: %s", new_storages) + self.known_storages.update(new_storages) + new_storage_data = [ + (data[node_name], data[node_name].storages[storage_name]) + for node_name, storage_name in new_storages + ] + for storages_callback in self.new_storages_callbacks: + storages_callback(new_storage_data) + class ProxmoxSetupError(Exception): """Base exception for Proxmox setup issues.""" diff --git a/homeassistant/components/proxmoxve/entity.py b/homeassistant/components/proxmoxve/entity.py index 5684845391a6..acb72a8179b7 100644 --- a/homeassistant/components/proxmoxve/entity.py +++ b/homeassistant/components/proxmoxve/entity.py @@ -65,6 +65,59 @@ class ProxmoxNodeEntity(ProxmoxCoordinatorEntity): return super().available and self.device_name in self.coordinator.data +class ProxmoxStorageEntity(ProxmoxCoordinatorEntity): + """Represents a Storage entity.""" + + def __init__( + self, + coordinator: ProxmoxCoordinator, + entity_description: EntityDescription, + storage_data: dict[str, Any], + node_data: ProxmoxNodeData, + ) -> None: + """Initialize the Proxmox Storage entity.""" + super().__init__(coordinator) + self.entity_description = entity_description + self._storage_data = storage_data + self._node_name = node_data.node["node"] + self.device_id = storage_data["storage"] + self.device_name = storage_data["storage"] + + self._attr_device_info = DeviceInfo( + identifiers={ + ( + DOMAIN, + f"{coordinator.config_entry.entry_id}_storage_{self.device_id}", + ) + }, + name=f"Storage ({self.device_name})", + model="Storage", + configuration_url=_proxmox_base_url(coordinator).with_fragment( + f"v1:0:=storage/{self._node_name}/{storage_data['storage']}" + ), + via_device=( + DOMAIN, + f"{coordinator.config_entry.entry_id}_node_{node_data.node['id']}", + ), + ) + + self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{self._node_name}_{self.device_id}_{entity_description.key}" + + @property + def available(self) -> bool: + """Return if the device is available.""" + return ( + super().available + and self._node_name in self.coordinator.data + and self.device_id in self.coordinator.data[self._node_name].storages + ) + + @property + def storage_data(self) -> dict[str, Any]: + """Return the Storage data.""" + return self.coordinator.data[self._node_name].storages[self.device_id] + + class ProxmoxVMEntity(ProxmoxCoordinatorEntity): """Represents a VM entity.""" diff --git a/homeassistant/components/proxmoxve/icons.json b/homeassistant/components/proxmoxve/icons.json index 27615411e445..9f1a83a98cc3 100644 --- a/homeassistant/components/proxmoxve/icons.json +++ b/homeassistant/components/proxmoxve/icons.json @@ -1,5 +1,25 @@ { "entity": { + "binary_sensor": { + "storage_active": { + "default": "mdi:play", + "state": { + "off": "mdi:stop" + } + }, + "storage_enabled": { + "default": "mdi:power", + "state": { + "off": "mdi:power-off" + } + }, + "storage_shared": { + "default": "mdi:lan-connect", + "state": { + "off": "mdi:lan-disconnect" + } + } + }, "button": { "hibernate": { "default": "mdi:power-sleep" @@ -84,6 +104,18 @@ "node_status": { "default": "mdi:server" }, + "storage_available": { + "default": "mdi:harddisk" + }, + "storage_total": { + "default": "mdi:harddisk" + }, + "storage_used": { + "default": "mdi:harddisk" + }, + "storage_used_percentage": { + "default": "mdi:harddisk" + }, "vm_cpu": { "default": "mdi:cpu-64-bit" }, diff --git a/homeassistant/components/proxmoxve/sensor.py b/homeassistant/components/proxmoxve/sensor.py index 7024eb128682..cb7977745cef 100644 --- a/homeassistant/components/proxmoxve/sensor.py +++ b/homeassistant/components/proxmoxve/sensor.py @@ -21,7 +21,12 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.util import dt as dt_util from .coordinator import ProxmoxConfigEntry, ProxmoxNodeData -from .entity import ProxmoxContainerEntity, ProxmoxNodeEntity, ProxmoxVMEntity +from .entity import ( + ProxmoxContainerEntity, + ProxmoxNodeEntity, + ProxmoxStorageEntity, + ProxmoxVMEntity, +) PARALLEL_UPDATES = 0 @@ -47,6 +52,13 @@ class ProxmoxContainerSensorEntityDescription(SensorEntityDescription): value_fn: Callable[[dict[str, Any]], StateType] +@dataclass(frozen=True, kw_only=True) +class ProxmoxStorageSensorEntityDescription(SensorEntityDescription): + """Class to hold Proxmox storage sensor description.""" + + value_fn: Callable[[dict[str, Any]], StateType] + + NODE_SENSORS: tuple[ProxmoxNodeSensorEntityDescription, ...] = ( ProxmoxNodeSensorEntityDescription( key="node_cpu", @@ -377,6 +389,50 @@ CONTAINER_SENSORS: tuple[ProxmoxContainerSensorEntityDescription, ...] = ( ), ) +STORAGE_SENSORS: tuple[ProxmoxStorageSensorEntityDescription, ...] = ( + ProxmoxStorageSensorEntityDescription( + key="storage_used", + translation_key="storage_used", + value_fn=lambda data: data["used"], + device_class=SensorDeviceClass.DATA_SIZE, + native_unit_of_measurement=UnitOfInformation.BYTES, + suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), + ProxmoxStorageSensorEntityDescription( + key="storage_total", + translation_key="storage_total", + value_fn=lambda data: data["total"], + device_class=SensorDeviceClass.DATA_SIZE, + native_unit_of_measurement=UnitOfInformation.BYTES, + suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), + ProxmoxStorageSensorEntityDescription( + key="storage_available", + translation_key="storage_available", + value_fn=lambda data: data["avail"], + device_class=SensorDeviceClass.DATA_SIZE, + native_unit_of_measurement=UnitOfInformation.BYTES, + suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), + ProxmoxStorageSensorEntityDescription( + key="storage_used_percentage", + translation_key="storage_used_percentage", + value_fn=lambda data: round(data["used_fraction"] * 100, 1), + native_unit_of_measurement=PERCENTAGE, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -416,9 +472,20 @@ async def async_setup_entry( for entity_description in CONTAINER_SENSORS ) + def _async_add_new_storages( + storages: list[tuple[ProxmoxNodeData, dict[str, Any]]], + ) -> None: + """Add new storage sensors.""" + async_add_entities( + ProxmoxStorageSensor(coordinator, entity_description, storage, node_data) + for (node_data, storage) in storages + for entity_description in STORAGE_SENSORS + ) + coordinator.new_nodes_callbacks.append(_async_add_new_nodes) coordinator.new_vms_callbacks.append(_async_add_new_vms) coordinator.new_containers_callbacks.append(_async_add_new_containers) + coordinator.new_storages_callbacks.append(_async_add_new_storages) _async_add_new_nodes( [ @@ -443,6 +510,14 @@ async def async_setup_entry( if (node_data.node["node"], vmid) in coordinator.known_containers ] ) + _async_add_new_storages( + [ + (node_data, storage_data) + for node_data in coordinator.data.values() + for storage_name, storage_data in node_data.storages.items() + if (node_data.node["node"], storage_name) in coordinator.known_storages + ] + ) class ProxmoxNodeSensor(ProxmoxNodeEntity, SensorEntity): @@ -476,3 +551,14 @@ class ProxmoxContainerSensor(ProxmoxContainerEntity, SensorEntity): def native_value(self) -> StateType: """Return the native value of the sensor.""" return self.entity_description.value_fn(self.container_data) + + +class ProxmoxStorageSensor(ProxmoxStorageEntity, SensorEntity): + """Represents a Proxmox VE storage sensor.""" + + entity_description: ProxmoxStorageSensorEntityDescription + + @property + def native_value(self) -> StateType: + """Return the native value of the sensor.""" + return self.entity_description.value_fn(self.storage_data) diff --git a/homeassistant/components/proxmoxve/strings.json b/homeassistant/components/proxmoxve/strings.json index 3bf7b7fc0fb3..fcb13b68a93c 100644 --- a/homeassistant/components/proxmoxve/strings.json +++ b/homeassistant/components/proxmoxve/strings.json @@ -110,6 +110,27 @@ }, "status": { "name": "Status" + }, + "storage_active": { + "name": "Storage active", + "state": { + "off": "Inactive", + "on": "Active" + } + }, + "storage_enabled": { + "name": "Storage enabled", + "state": { + "off": "Disabled", + "on": "Enabled" + } + }, + "storage_shared": { + "name": "Storage shared", + "state": { + "off": "Not shared", + "on": "Shared" + } } }, "button": { @@ -217,6 +238,18 @@ "node_uptime": { "name": "Uptime" }, + "storage_available": { + "name": "Available storage" + }, + "storage_total": { + "name": "Total storage" + }, + "storage_used": { + "name": "Used storage" + }, + "storage_used_percentage": { + "name": "Storage usage percentage" + }, "vm_cpu": { "name": "CPU usage" }, diff --git a/tests/components/proxmoxve/conftest.py b/tests/components/proxmoxve/conftest.py index 4ed8f7e88162..43a71cd0d91a 100644 --- a/tests/components/proxmoxve/conftest.py +++ b/tests/components/proxmoxve/conftest.py @@ -118,6 +118,9 @@ def mock_proxmox_client(): node_mock.qemu.get.return_value = qemu_list node_mock.lxc.get.return_value = lxc_list + node_mock.storage.get.return_value = load_json_array_fixture( + "nodes/storage.json", DOMAIN + ) node_mock.tasks.get.return_value = load_json_array_fixture( "nodes/tasks.json", DOMAIN ) diff --git a/tests/components/proxmoxve/fixtures/nodes/storage.json b/tests/components/proxmoxve/fixtures/nodes/storage.json new file mode 100644 index 000000000000..52a02d63a1e7 --- /dev/null +++ b/tests/components/proxmoxve/fixtures/nodes/storage.json @@ -0,0 +1,26 @@ +[ + { + "type": "dir", + "used_fraction": 0.85, + "storage": "local", + "content": "iso,backup,vztmpl", + "used": 85734563840, + "active": 1, + "enabled": 1, + "shared": 0, + "total": 100861726720, + "avail": 9956425728 + }, + { + "shared": 1, + "enabled": 1, + "avail": 523769479168, + "total": 1915211874304, + "used_fraction": 0.73, + "storage": "synology", + "type": "nfs", + "active": 1, + "content": "backup,images", + "used": 1391442395136 + } +] diff --git a/tests/components/proxmoxve/snapshots/test_binary_sensor.ambr b/tests/components/proxmoxve/snapshots/test_binary_sensor.ambr index 81ec82efd4b9..7c4a51cbdf50 100644 --- a/tests/components/proxmoxve/snapshots/test_binary_sensor.ambr +++ b/tests/components/proxmoxve/snapshots/test_binary_sensor.ambr @@ -203,6 +203,306 @@ 'state': 'on', }) # --- +# name: test_all_entities[binary_sensor.storage_local_storage_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.storage_local_storage_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Storage active', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Storage active', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_active', + 'unique_id': '1234_pve1_local_storage_active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.storage_local_storage_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Storage (local) Storage active', + }), + 'context': , + 'entity_id': 'binary_sensor.storage_local_storage_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.storage_local_storage_enabled-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.storage_local_storage_enabled', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Storage enabled', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Storage enabled', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_enabled', + 'unique_id': '1234_pve1_local_storage_enabled', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.storage_local_storage_enabled-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Storage (local) Storage enabled', + }), + 'context': , + 'entity_id': 'binary_sensor.storage_local_storage_enabled', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.storage_local_storage_shared-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.storage_local_storage_shared', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Storage shared', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Storage shared', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_shared', + 'unique_id': '1234_pve1_local_storage_shared', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.storage_local_storage_shared-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Storage (local) Storage shared', + }), + 'context': , + 'entity_id': 'binary_sensor.storage_local_storage_shared', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.storage_synology_storage_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.storage_synology_storage_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Storage active', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Storage active', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_active', + 'unique_id': '1234_pve1_synology_storage_active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.storage_synology_storage_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Storage (synology) Storage active', + }), + 'context': , + 'entity_id': 'binary_sensor.storage_synology_storage_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.storage_synology_storage_enabled-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.storage_synology_storage_enabled', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Storage enabled', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Storage enabled', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_enabled', + 'unique_id': '1234_pve1_synology_storage_enabled', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.storage_synology_storage_enabled-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Storage (synology) Storage enabled', + }), + 'context': , + 'entity_id': 'binary_sensor.storage_synology_storage_enabled', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.storage_synology_storage_shared-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.storage_synology_storage_shared', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Storage shared', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Storage shared', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_shared', + 'unique_id': '1234_pve1_synology_storage_shared', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.storage_synology_storage_shared-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Storage (synology) Storage shared', + }), + 'context': , + 'entity_id': 'binary_sensor.storage_synology_storage_shared', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- # name: test_all_entities[binary_sensor.vm_db_status-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ diff --git a/tests/components/proxmoxve/snapshots/test_diagnostics.ambr b/tests/components/proxmoxve/snapshots/test_diagnostics.ambr index 2ef62da759c6..188211cc2a33 100644 --- a/tests/components/proxmoxve/snapshots/test_diagnostics.ambr +++ b/tests/components/proxmoxve/snapshots/test_diagnostics.ambr @@ -103,6 +103,32 @@ 'type': 'node', 'uptime': 86400, }), + 'storages': dict({ + 'local': dict({ + 'active': 1, + 'avail': 9956425728, + 'content': 'iso,backup,vztmpl', + 'enabled': 1, + 'shared': 0, + 'storage': 'local', + 'total': 100861726720, + 'type': 'dir', + 'used': 85734563840, + 'used_fraction': 0.85, + }), + 'synology': dict({ + 'active': 1, + 'avail': 523769479168, + 'content': 'backup,images', + 'enabled': 1, + 'shared': 1, + 'storage': 'synology', + 'total': 1915211874304, + 'type': 'nfs', + 'used': 1391442395136, + 'used_fraction': 0.73, + }), + }), 'vms': dict({ '100': dict({ 'cpu': 0.15, diff --git a/tests/components/proxmoxve/snapshots/test_sensor.ambr b/tests/components/proxmoxve/snapshots/test_sensor.ambr index 32b826d5da86..fbb94bac9be8 100644 --- a/tests/components/proxmoxve/snapshots/test_sensor.ambr +++ b/tests/components/proxmoxve/snapshots/test_sensor.ambr @@ -1946,6 +1946,480 @@ 'state': '24.0', }) # --- +# name: test_all_entities[sensor.storage_local_available_storage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.storage_local_available_storage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Available storage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Available storage', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_available', + 'unique_id': '1234_pve1_local_storage_available', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.storage_local_available_storage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'Storage (local) Available storage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.storage_local_available_storage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '9.27264404296875', + }) +# --- +# name: test_all_entities[sensor.storage_local_storage_usage_percentage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.storage_local_storage_usage_percentage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Storage usage percentage', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Storage usage percentage', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_used_percentage', + 'unique_id': '1234_pve1_local_storage_used_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.storage_local_storage_usage_percentage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Storage (local) Storage usage percentage', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.storage_local_storage_usage_percentage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '85.0', + }) +# --- +# name: test_all_entities[sensor.storage_local_total_storage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.storage_local_total_storage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Total storage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total storage', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_total', + 'unique_id': '1234_pve1_local_storage_total', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.storage_local_total_storage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'Storage (local) Total storage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.storage_local_total_storage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '93.9348030090332', + }) +# --- +# name: test_all_entities[sensor.storage_local_used_storage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.storage_local_used_storage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Used storage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Used storage', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_used', + 'unique_id': '1234_pve1_local_storage_used', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.storage_local_used_storage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'Storage (local) Used storage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.storage_local_used_storage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '79.8465347290039', + }) +# --- +# name: test_all_entities[sensor.storage_synology_available_storage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.storage_synology_available_storage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Available storage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Available storage', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_available', + 'unique_id': '1234_pve1_synology_storage_available', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.storage_synology_available_storage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'Storage (synology) Available storage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.storage_synology_available_storage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '487.79833984375', + }) +# --- +# name: test_all_entities[sensor.storage_synology_storage_usage_percentage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.storage_synology_storage_usage_percentage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Storage usage percentage', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Storage usage percentage', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_used_percentage', + 'unique_id': '1234_pve1_synology_storage_used_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.storage_synology_storage_usage_percentage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Storage (synology) Storage usage percentage', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.storage_synology_storage_usage_percentage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '73.0', + }) +# --- +# name: test_all_entities[sensor.storage_synology_total_storage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.storage_synology_total_storage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Total storage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total storage', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_total', + 'unique_id': '1234_pve1_synology_storage_total', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.storage_synology_total_storage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'Storage (synology) Total storage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.storage_synology_total_storage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1783.68005371094', + }) +# --- +# name: test_all_entities[sensor.storage_synology_used_storage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.storage_synology_used_storage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Used storage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Used storage', + 'platform': 'proxmoxve', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'storage_used', + 'unique_id': '1234_pve1_synology_storage_used', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.storage_synology_used_storage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'Storage (synology) Used storage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.storage_synology_used_storage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1295.88171386719', + }) +# --- # name: test_all_entities[sensor.vm_db_cpu_usage-entry] EntityRegistryEntrySnapshot({ 'aliases': list([