mirror of
https://github.com/home-assistant/core.git
synced 2025-08-12 09:05:15 +02:00
Include backup agent error in response to WS command backup/info (#130884)
This commit is contained in:
@@ -8,10 +8,21 @@ from pathlib import Path
|
||||
from typing import Any, Protocol
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .models import BackupUploadMetadata, BaseBackup
|
||||
|
||||
|
||||
class BackupAgentError(HomeAssistantError):
|
||||
"""Base class for backup agent errors."""
|
||||
|
||||
|
||||
class BackupAgentUnreachableError(BackupAgentError):
|
||||
"""Raised when the agent can't reach it's API."""
|
||||
|
||||
_message = "The backup agent is unreachable."
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class UploadedBackup(BaseBackup):
|
||||
"""Uploaded backup class."""
|
||||
|
@@ -29,7 +29,12 @@ from homeassistant.helpers import integration_platform
|
||||
from homeassistant.helpers.json import json_bytes
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .agent import BackupAgent, BackupAgentPlatformProtocol, LocalBackupAgent
|
||||
from .agent import (
|
||||
BackupAgent,
|
||||
BackupAgentError,
|
||||
BackupAgentPlatformProtocol,
|
||||
LocalBackupAgent,
|
||||
)
|
||||
from .const import (
|
||||
BUF_SIZE,
|
||||
DOMAIN,
|
||||
@@ -207,7 +212,9 @@ class BaseBackupManager(abc.ABC, Generic[_BackupT]):
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_get_backups(self, **kwargs: Any) -> dict[str, _BackupT]:
|
||||
async def async_get_backups(
|
||||
self, **kwargs: Any
|
||||
) -> tuple[dict[str, Backup], dict[str, Exception]]:
|
||||
"""Get backups.
|
||||
|
||||
Return a dictionary of Backup instances keyed by their slug.
|
||||
@@ -275,12 +282,25 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
finally:
|
||||
self.syncing = False
|
||||
|
||||
async def async_get_backups(self, **kwargs: Any) -> dict[str, Backup]:
|
||||
async def async_get_backups(
|
||||
self, **kwargs: Any
|
||||
) -> tuple[dict[str, Backup], dict[str, Exception]]:
|
||||
"""Return backups."""
|
||||
backups: dict[str, Backup] = {}
|
||||
for agent_id, agent in self.backup_agents.items():
|
||||
agent_backups = await agent.async_list_backups()
|
||||
for agent_backup in agent_backups:
|
||||
agent_errors: dict[str, Exception] = {}
|
||||
agent_ids = list(self.backup_agents.keys())
|
||||
|
||||
list_backups_results = await asyncio.gather(
|
||||
*(agent.async_list_backups() for agent in self.backup_agents.values()),
|
||||
return_exceptions=True,
|
||||
)
|
||||
for idx, result in enumerate(list_backups_results):
|
||||
if isinstance(result, BackupAgentError):
|
||||
agent_errors[agent_ids[idx]] = result
|
||||
continue
|
||||
if isinstance(result, BaseException):
|
||||
raise result
|
||||
for agent_backup in result:
|
||||
if agent_backup.slug not in backups:
|
||||
backups[agent_backup.slug] = Backup(
|
||||
slug=agent_backup.slug,
|
||||
@@ -290,9 +310,9 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
size=agent_backup.size,
|
||||
protected=agent_backup.protected,
|
||||
)
|
||||
backups[agent_backup.slug].agent_ids.append(agent_id)
|
||||
backups[agent_backup.slug].agent_ids.append(agent_ids[idx])
|
||||
|
||||
return backups
|
||||
return (backups, agent_errors)
|
||||
|
||||
async def async_get_backup(self, *, slug: str, **kwargs: Any) -> Backup | None:
|
||||
"""Return a backup."""
|
||||
|
@@ -40,10 +40,13 @@ async def handle_info(
|
||||
) -> None:
|
||||
"""List all stored backups."""
|
||||
manager = hass.data[DATA_MANAGER]
|
||||
backups = await manager.async_get_backups()
|
||||
backups, agent_errors = await manager.async_get_backups()
|
||||
connection.send_result(
|
||||
msg["id"],
|
||||
{
|
||||
"agent_errors": {
|
||||
agent_id: str(err) for agent_id, err in agent_errors.items()
|
||||
},
|
||||
"backups": [b.as_dict() for b in backups.values()],
|
||||
"backing_up": manager.backup_task is not None,
|
||||
},
|
||||
|
@@ -439,6 +439,8 @@
|
||||
dict({
|
||||
'id': 1,
|
||||
'result': dict({
|
||||
'agent_errors': dict({
|
||||
}),
|
||||
'backing_up': False,
|
||||
'backups': list([
|
||||
dict({
|
||||
@@ -457,6 +459,42 @@
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_info_with_errors[BackupAgentUnreachableError]
|
||||
dict({
|
||||
'id': 1,
|
||||
'result': dict({
|
||||
'agent_errors': dict({
|
||||
'domain.test': 'The backup agent is unreachable.',
|
||||
}),
|
||||
'backing_up': False,
|
||||
'backups': list([
|
||||
dict({
|
||||
'agent_ids': list([
|
||||
'backup.local',
|
||||
]),
|
||||
'date': '1970-01-01T00:00:00.000Z',
|
||||
'name': 'Test',
|
||||
'protected': False,
|
||||
'size': 0.0,
|
||||
'slug': 'abc123',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_info_with_errors[side_effect0]
|
||||
dict({
|
||||
'error': dict({
|
||||
'code': 'home_assistant_error',
|
||||
'message': 'Boom!',
|
||||
}),
|
||||
'id': 1,
|
||||
'success': False,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_remove[with_hassio]
|
||||
dict({
|
||||
'error': dict({
|
||||
|
@@ -143,8 +143,9 @@ async def test_load_backups(hass: HomeAssistant) -> None:
|
||||
),
|
||||
):
|
||||
await manager.backup_agents[LOCAL_AGENT_ID].load_backups()
|
||||
backups = await manager.async_get_backups()
|
||||
backups, agent_errors = await manager.async_get_backups()
|
||||
assert backups == {TEST_BACKUP.slug: TEST_BACKUP}
|
||||
assert agent_errors == {}
|
||||
|
||||
|
||||
async def test_load_backups_with_exception(
|
||||
@@ -162,9 +163,10 @@ async def test_load_backups_with_exception(
|
||||
patch("tarfile.open", side_effect=OSError("Test exception")),
|
||||
):
|
||||
await manager.backup_agents[LOCAL_AGENT_ID].load_backups()
|
||||
backups = await manager.async_get_backups()
|
||||
backups, agent_errors = await manager.async_get_backups()
|
||||
assert f"Unable to read backup {TEST_BACKUP_PATH}: Test exception" in caplog.text
|
||||
assert backups == {}
|
||||
assert agent_errors == {}
|
||||
|
||||
|
||||
async def test_removing_backup(
|
||||
|
@@ -9,6 +9,7 @@ import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.backup import BaseBackup
|
||||
from homeassistant.components.backup.agent import BackupAgentUnreachableError
|
||||
from homeassistant.components.backup.const import DATA_MANAGER
|
||||
from homeassistant.components.backup.manager import NewBackup
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -56,6 +57,27 @@ async def test_info(
|
||||
assert await client.receive_json() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"side_effect", [HomeAssistantError("Boom!"), BackupAgentUnreachableError]
|
||||
)
|
||||
async def test_info_with_errors(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
side_effect: Exception,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test getting backup info with one unavailable agent."""
|
||||
await setup_backup_integration(hass, with_hassio=False, backups=[TEST_LOCAL_BACKUP])
|
||||
hass.data[DATA_MANAGER].backup_agents["domain.test"] = BackupAgentTest("test")
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with patch.object(BackupAgentTest, "async_list_backups", side_effect=side_effect):
|
||||
await client.send_json_auto_id({"type": "backup/info"})
|
||||
assert await client.receive_json() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"backup_content",
|
||||
[
|
||||
|
Reference in New Issue
Block a user