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/details (#130892)
This commit is contained in:
@@ -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."""
|
||||||
|
@@ -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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@@ -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({
|
||||||
|
@@ -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 "
|
||||||
|
@@ -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",
|
||||||
[
|
[
|
||||||
|
Reference in New Issue
Block a user