Include backup agent error in response to WS command backup/details (#130892)

This commit is contained in:
Erik Montnemery
2024-11-18 20:33:59 +01:00
committed by GitHub
parent 3bc45fec61
commit 9f0a06a718
5 changed files with 100 additions and 13 deletions

View File

@@ -221,7 +221,9 @@ class BaseBackupManager(abc.ABC, Generic[_BackupT]):
""" """
@abc.abstractmethod @abc.abstractmethod
async def async_get_backup(self, *, slug: str, **kwargs: Any) -> _BackupT | None: async def async_get_backup(
self, *, slug: str, **kwargs: Any
) -> tuple[_BackupT | None, dict[str, Exception]]:
"""Get a backup.""" """Get a backup."""
@abc.abstractmethod @abc.abstractmethod
@@ -314,25 +316,41 @@ class BackupManager(BaseBackupManager[Backup]):
return (backups, agent_errors) return (backups, agent_errors)
async def async_get_backup(self, *, slug: str, **kwargs: Any) -> Backup | None: async def async_get_backup(
self, *, slug: str, **kwargs: Any
) -> tuple[Backup | None, dict[str, Exception]]:
"""Return a backup.""" """Return a backup."""
backup: Backup | None = None backup: Backup | None = None
agent_errors: dict[str, Exception] = {}
agent_ids = list(self.backup_agents.keys())
for agent_id, agent in self.backup_agents.items(): get_backup_results = await asyncio.gather(
if not (agent_backup := await agent.async_get_backup(slug=slug)): *(
agent.async_get_backup(slug=slug)
for agent in self.backup_agents.values()
),
return_exceptions=True,
)
for idx, result in enumerate(get_backup_results):
if isinstance(result, BackupAgentError):
agent_errors[agent_ids[idx]] = result
continue
if isinstance(result, BaseException):
raise result
if not result:
continue continue
if backup is None: if backup is None:
backup = Backup( backup = Backup(
slug=agent_backup.slug, slug=result.slug,
name=agent_backup.name, name=result.name,
date=agent_backup.date, date=result.date,
agent_ids=[], agent_ids=[],
size=agent_backup.size, size=result.size,
protected=agent_backup.protected, protected=result.protected,
) )
backup.agent_ids.append(agent_id) backup.agent_ids.append(agent_ids[idx])
return backup return (backup, agent_errors)
async def async_remove_backup(self, *, slug: str, **kwargs: Any) -> None: async def async_remove_backup(self, *, slug: str, **kwargs: Any) -> None:
"""Remove a backup.""" """Remove a backup."""

View File

@@ -67,10 +67,15 @@ async def handle_details(
msg: dict[str, Any], msg: dict[str, Any],
) -> None: ) -> None:
"""Get backup details for a specific slug.""" """Get backup details for a specific slug."""
backup = await hass.data[DATA_MANAGER].async_get_backup(slug=msg["slug"]) backup, agent_errors = await hass.data[DATA_MANAGER].async_get_backup(
slug=msg["slug"]
)
connection.send_result( connection.send_result(
msg["id"], msg["id"],
{ {
"agent_errors": {
agent_id: str(err) for agent_id, err in agent_errors.items()
},
"backup": backup, "backup": backup,
}, },
) )

View File

@@ -283,6 +283,8 @@
dict({ dict({
'id': 1, 'id': 1,
'result': dict({ 'result': dict({
'agent_errors': dict({
}),
'backup': dict({ 'backup': dict({
'agent_ids': list([ 'agent_ids': list([
'backup.local', 'backup.local',
@@ -302,12 +304,47 @@
dict({ dict({
'id': 1, 'id': 1,
'result': dict({ 'result': dict({
'agent_errors': dict({
}),
'backup': None, 'backup': None,
}), }),
'success': True, 'success': True,
'type': 'result', 'type': 'result',
}) })
# --- # ---
# name: test_details_with_errors[BackupAgentUnreachableError]
dict({
'id': 1,
'result': dict({
'agent_errors': dict({
'domain.test': 'The backup agent is unreachable.',
}),
'backup': 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_details_with_errors[side_effect0]
dict({
'error': dict({
'code': 'home_assistant_error',
'message': 'Boom!',
}),
'id': 1,
'success': False,
'type': 'result',
})
# ---
# name: test_generate[with_hassio-None] # name: test_generate[with_hassio-None]
dict({ dict({
'error': dict({ 'error': dict({

View File

@@ -217,8 +217,11 @@ async def test_getting_backup_that_does_not_exist(
local_agent._loaded_backups = True local_agent._loaded_backups = True
with patch("pathlib.Path.exists", return_value=False): with patch("pathlib.Path.exists", return_value=False):
backup = await manager.async_get_backup(slug=TEST_LOCAL_BACKUP.slug) backup, agent_errors = await manager.async_get_backup(
slug=TEST_LOCAL_BACKUP.slug
)
assert backup is None assert backup is None
assert agent_errors == {}
assert ( assert (
f"Removing tracked backup ({TEST_LOCAL_BACKUP.slug}) that " f"Removing tracked backup ({TEST_LOCAL_BACKUP.slug}) that "

View File

@@ -112,6 +112,30 @@ async def test_details(
assert await client.receive_json() == snapshot assert await client.receive_json() == snapshot
@pytest.mark.parametrize(
"side_effect", [HomeAssistantError("Boom!"), BackupAgentUnreachableError]
)
async def test_details_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("pathlib.Path.exists", return_value=True),
patch.object(BackupAgentTest, "async_get_backup", side_effect=side_effect),
):
await client.send_json_auto_id({"type": "backup/details", "slug": "abc123"})
assert await client.receive_json() == snapshot
@pytest.mark.parametrize( @pytest.mark.parametrize(
"with_hassio", "with_hassio",
[ [