Proper handling of unavailable Synology DSM nas during backup (#140721)

* raise BackupAgentUnreachableError when NAS is unavailable

* also raise BackupAgentUnreachableError during upload when nas unavailable

* Revert "also raise BackupAgentUnreachableError during upload when nas unavailable"

This reverts commit 38877d8540aa3c61c366069dc063bb9b4d866c48.

* Revert "raise BackupAgentUnreachableError when NAS is unavailable"

This reverts commit 4d8cfae396ea3be3409ed8f4784b9e2448954a04.

* check last_update_success of  coordinator_central to get backup agents

* consider last_update_success before notify backup listeners

* add test

* use walrus :=  :)
This commit is contained in:
Michael 2025-03-26 10:22:43 +01:00 committed by GitHub
parent f0c774a4bd
commit b5117eb071
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 63 additions and 1 deletions

View File

@ -123,6 +123,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SynologyDSMConfigEntry)
entry.runtime_data = SynologyDSMData( entry.runtime_data = SynologyDSMData(
api=api, api=api,
coordinator_central=coordinator_central, coordinator_central=coordinator_central,
coordinator_central_old_update_success=True,
coordinator_cameras=coordinator_cameras, coordinator_cameras=coordinator_cameras,
coordinator_switches=coordinator_switches, coordinator_switches=coordinator_switches,
) )
@ -139,6 +140,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: SynologyDSMConfigEntry)
entry.async_on_state_change(async_notify_backup_listeners) entry.async_on_state_change(async_notify_backup_listeners)
) )
def async_check_last_update_success() -> None:
if (
last := coordinator_central.last_update_success
) is not entry.runtime_data.coordinator_central_old_update_success:
entry.runtime_data.coordinator_central_old_update_success = last
async_notify_backup_listeners()
entry.runtime_data.coordinator_central.async_add_listener(
async_check_last_update_success
)
return True return True

View File

@ -58,6 +58,7 @@ async def async_get_backup_agents(
if entry.unique_id is not None if entry.unique_id is not None
and entry.runtime_data.api.file_station and entry.runtime_data.api.file_station
and entry.options.get(CONF_BACKUP_PATH) and entry.options.get(CONF_BACKUP_PATH)
and entry.runtime_data.coordinator_central.last_update_success
] ]

View File

@ -35,6 +35,7 @@ class SynologyDSMData:
api: SynoApi api: SynoApi
coordinator_central: SynologyDSMCentralUpdateCoordinator coordinator_central: SynologyDSMCentralUpdateCoordinator
coordinator_central_old_update_success: bool
coordinator_cameras: SynologyDSMCameraUpdateCoordinator | None coordinator_cameras: SynologyDSMCameraUpdateCoordinator | None
coordinator_switches: SynologyDSMSwitchUpdateCoordinator | None coordinator_switches: SynologyDSMSwitchUpdateCoordinator | None

View File

@ -4,9 +4,13 @@ from io import StringIO
from typing import Any from typing import Any
from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from synology_dsm.api.file_station.models import SynoFileFile, SynoFileSharedFolder from synology_dsm.api.file_station.models import SynoFileFile, SynoFileSharedFolder
from synology_dsm.exceptions import SynologyDSMAPIErrorException from synology_dsm.exceptions import (
SynologyDSMAPIErrorException,
SynologyDSMRequestException,
)
from homeassistant.components.backup import ( from homeassistant.components.backup import (
DOMAIN as BACKUP_DOMAIN, DOMAIN as BACKUP_DOMAIN,
@ -279,6 +283,50 @@ async def test_agents_on_unload(
} }
async def test_agents_on_changed_update_success(
hass: HomeAssistant,
setup_dsm_with_filestation: MagicMock,
hass_ws_client: WebSocketGenerator,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test backup agent on changed update success of coordintaor."""
client = await hass_ws_client(hass)
# config entry is loaded
await client.send_json_auto_id({"type": "backup/agents/info"})
response = await client.receive_json()
assert response["success"]
assert len(response["result"]["agents"]) == 2
# coordinator update was successful
freezer.tick(910) # 15 min interval + 10s
await hass.async_block_till_done(wait_background_tasks=True)
await client.send_json_auto_id({"type": "backup/agents/info"})
response = await client.receive_json()
assert response["success"]
assert len(response["result"]["agents"]) == 2
# coordinator update was un-successful
setup_dsm_with_filestation.update.side_effect = SynologyDSMRequestException(
OSError()
)
freezer.tick(910)
await hass.async_block_till_done(wait_background_tasks=True)
await client.send_json_auto_id({"type": "backup/agents/info"})
response = await client.receive_json()
assert response["success"]
assert len(response["result"]["agents"]) == 1
# coordinator update was successful again
setup_dsm_with_filestation.update.side_effect = None
freezer.tick(910)
await hass.async_block_till_done(wait_background_tasks=True)
await client.send_json_auto_id({"type": "backup/agents/info"})
response = await client.receive_json()
assert response["success"]
assert len(response["result"]["agents"]) == 2
async def test_agents_list_backups( async def test_agents_list_backups(
hass: HomeAssistant, hass: HomeAssistant,
setup_dsm_with_filestation: MagicMock, setup_dsm_with_filestation: MagicMock,