Refresh HassOS coordinator when mount repair is received (#155969)

This commit is contained in:
Amit Finkelstein
2025-11-23 21:51:18 +02:00
committed by GitHub
parent 9aec7b12c2
commit ddb74c5af4
3 changed files with 147 additions and 7 deletions

View File

@@ -128,6 +128,8 @@ ISSUE_KEY_ADDON_PWNED = "issue_addon_pwned"
ISSUE_KEY_SYSTEM_FREE_SPACE = "issue_system_free_space"
ISSUE_KEY_ADDON_DEPRECATED = "issue_addon_deprecated_addon"
ISSUE_MOUNT_MOUNT_FAILED = "issue_mount_mount_failed"
CORE_CONTAINER = "homeassistant"
SUPERVISOR_CONTAINER = "hassio_supervisor"

View File

@@ -27,6 +27,7 @@ from homeassistant.helpers.issue_registry import (
)
from .const import (
ADDONS_COORDINATOR,
ATTR_DATA,
ATTR_HEALTHY,
ATTR_STARTUP,
@@ -49,6 +50,7 @@ from .const import (
ISSUE_KEY_ADDON_PWNED,
ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
ISSUE_KEY_SYSTEM_FREE_SPACE,
ISSUE_MOUNT_MOUNT_FAILED,
PLACEHOLDER_KEY_ADDON,
PLACEHOLDER_KEY_ADDON_URL,
PLACEHOLDER_KEY_FREE_SPACE,
@@ -57,7 +59,7 @@ from .const import (
STARTUP_COMPLETE,
UPDATE_KEY_SUPERVISOR,
)
from .coordinator import get_addons_info, get_host_info
from .coordinator import HassioDataUpdateCoordinator, get_addons_info, get_host_info
from .handler import HassIO, get_supervisor_client
ISSUE_KEY_UNHEALTHY = "unhealthy"
@@ -77,7 +79,7 @@ UNSUPPORTED_SKIP_REPAIR = {"privileged"}
# Keys (type + context) of issues that when found should be made into a repair
ISSUE_KEYS_FOR_REPAIRS = {
ISSUE_KEY_ADDON_BOOT_FAIL,
"issue_mount_mount_failed",
ISSUE_MOUNT_MOUNT_FAILED,
"issue_system_multiple_data_disks",
"issue_system_reboot_required",
ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
@@ -284,6 +286,9 @@ class SupervisorIssues:
else:
placeholders[PLACEHOLDER_KEY_FREE_SPACE] = "<2"
if issue.key == ISSUE_MOUNT_MOUNT_FAILED:
self._async_coordinator_refresh()
async_create_issue(
self._hass,
DOMAIN,
@@ -336,6 +341,9 @@ class SupervisorIssues:
if issue.key in ISSUE_KEYS_FOR_REPAIRS:
async_delete_issue(self._hass, DOMAIN, issue.uuid.hex)
if issue.key == ISSUE_MOUNT_MOUNT_FAILED:
self._async_coordinator_refresh()
del self._issues[issue.uuid]
def get_issue(self, issue_id: str) -> Issue | None:
@@ -406,3 +414,11 @@ class SupervisorIssues:
elif event[ATTR_WS_EVENT] == EVENT_ISSUE_REMOVED:
self.remove_issue(Issue.from_dict(event[ATTR_DATA]))
def _async_coordinator_refresh(self) -> None:
"""Refresh coordinator to update latest data in entities."""
coordinator: HassioDataUpdateCoordinator | None
if coordinator := self._hass.data.get(ADDONS_COORDINATOR):
coordinator.config_entry.async_create_task(
self._hass, coordinator.async_refresh()
)

View File

@@ -3,9 +3,18 @@
from dataclasses import replace
from datetime import timedelta
import os
from pathlib import PurePath
from unittest.mock import AsyncMock, patch
from uuid import uuid4
from aiohasupervisor.models.mounts import CIFSMountResponse, MountsInfo, MountState
from aiohasupervisor.models.mounts import (
CIFSMountResponse,
MountsInfo,
MountState,
MountType,
MountUsage,
NFSMountResponse,
)
import pytest
from homeassistant.components.hassio import DOMAIN
@@ -18,6 +27,7 @@ from .common import MOCK_REPOSITORIES, MOCK_STORE_ADDONS
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import WebSocketGenerator
MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"}
@@ -230,16 +240,16 @@ async def test_mount_binary_sensor(
assert hass.states.get(entity_id) is None
# Add a mount.
mock_mounts = [
mock_mounts: list[CIFSMountResponse | NFSMountResponse] = [
CIFSMountResponse(
share="files",
server="1.2.3.4",
name="NAS",
type="cifs",
usage="share",
type=MountType.CIFS,
usage=MountUsage.SHARE,
read_only=False,
state=MountState.ACTIVE,
user_path="/share/nas",
user_path=PurePath("/share/nas"),
)
]
supervisor_client.mounts.info = AsyncMock(
@@ -282,3 +292,115 @@ async def test_mount_binary_sensor(
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1000))
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(entity_id) is not None
async def test_mount_refresh_after_issue(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test hassio mount state is refreshed after an issue was send by the supervisor."""
# Add a mount.
mock_mounts: list[CIFSMountResponse | NFSMountResponse] = [
CIFSMountResponse(
share="files",
server="1.2.3.4",
name="NAS",
type=MountType.CIFS,
usage=MountUsage.SHARE,
read_only=False,
state=MountState.ACTIVE,
user_path=PurePath("/share/nas"),
)
]
supervisor_client.mounts.info = AsyncMock(
return_value=MountsInfo(default_backup_mount=None, mounts=mock_mounts)
)
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
config_entry.add_to_hass(hass)
with patch.dict(os.environ, MOCK_ENVIRON):
result = await async_setup_component(
hass,
"hassio",
{"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
)
assert result
await hass.async_block_till_done()
# Enable the entity.
entity_id = "binary_sensor.nas_connected"
entity_registry.async_update_entity(entity_id, disabled_by=None)
await hass.config_entries.async_reload(config_entry.entry_id)
await hass.async_block_till_done()
# Test new entity.
entity = hass.states.get(entity_id)
assert entity is not None
assert entity.state == "on"
# Change mount state to failed, issue a repair, and verify entity's state.
mock_mounts[0] = replace(mock_mounts[0], state=MountState.FAILED)
client = await hass_ws_client(hass)
issue_uuid = uuid4().hex
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "issue_changed",
"data": {
"uuid": issue_uuid,
"type": "mount_failed",
"context": "mount",
"reference": "nas",
"suggestions": [
{
"uuid": uuid4().hex,
"type": "execute_reload",
"context": "mount",
"reference": "nas",
},
{
"uuid": uuid4().hex,
"type": "execute_remove",
"context": "mount",
"reference": "nas",
},
],
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done(wait_background_tasks=True)
entity = hass.states.get(entity_id)
assert entity is not None
assert entity.state == "off"
# Change mount state to active, issue a repair, and verify entity's state.
mock_mounts[0] = replace(mock_mounts[0], state=MountState.ACTIVE)
await client.send_json(
{
"id": 2,
"type": "supervisor/event",
"data": {
"event": "issue_removed",
"data": {
"uuid": issue_uuid,
"type": "mount_failed",
"context": "mount",
"reference": "nas",
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done(wait_background_tasks=True)
entity = hass.states.get(entity_id)
assert entity is not None
assert entity.state == "on"