Report progress while restoring supervisor backup (#137313)
parent
3dc075f287
commit
d8179dacc6
|
@ -33,6 +33,7 @@ from .manager import (
|
|||
ManagerBackup,
|
||||
NewBackup,
|
||||
RestoreBackupEvent,
|
||||
RestoreBackupStage,
|
||||
RestoreBackupState,
|
||||
WrittenBackup,
|
||||
)
|
||||
|
@ -61,6 +62,7 @@ __all__ = [
|
|||
"ManagerBackup",
|
||||
"NewBackup",
|
||||
"RestoreBackupEvent",
|
||||
"RestoreBackupStage",
|
||||
"RestoreBackupState",
|
||||
"WrittenBackup",
|
||||
"async_get_manager",
|
||||
|
|
|
@ -39,6 +39,7 @@ from homeassistant.components.backup import (
|
|||
ManagerBackup,
|
||||
NewBackup,
|
||||
RestoreBackupEvent,
|
||||
RestoreBackupStage,
|
||||
RestoreBackupState,
|
||||
WrittenBackup,
|
||||
async_get_manager as async_get_backup_manager,
|
||||
|
@ -548,6 +549,14 @@ class SupervisorBackupReaderWriter(BackupReaderWriter):
|
|||
@callback
|
||||
def on_job_progress(data: Mapping[str, Any]) -> None:
|
||||
"""Handle backup restore progress."""
|
||||
if not (stage := try_parse_enum(RestoreBackupStage, data.get("stage"))):
|
||||
_LOGGER.debug("Unknown restore stage: %s", data.get("stage"))
|
||||
else:
|
||||
on_progress(
|
||||
RestoreBackupEvent(
|
||||
reason=None, stage=stage, state=RestoreBackupState.IN_PROGRESS
|
||||
)
|
||||
)
|
||||
if data.get("done") is True:
|
||||
restore_complete.set()
|
||||
restore_errors.extend(data.get("errors", []))
|
||||
|
@ -574,15 +583,26 @@ class SupervisorBackupReaderWriter(BackupReaderWriter):
|
|||
|
||||
_LOGGER.debug("Found restore job ID %s in environment", restore_job_id)
|
||||
|
||||
sent_event = False
|
||||
|
||||
@callback
|
||||
def on_job_progress(data: Mapping[str, Any]) -> None:
|
||||
"""Handle backup restore progress."""
|
||||
nonlocal sent_event
|
||||
|
||||
if not (stage := try_parse_enum(RestoreBackupStage, data.get("stage"))):
|
||||
_LOGGER.debug("Unknown restore stage: %s", data.get("stage"))
|
||||
|
||||
if data.get("done") is not True:
|
||||
on_progress(
|
||||
RestoreBackupEvent(
|
||||
reason="", stage=None, state=RestoreBackupState.IN_PROGRESS
|
||||
if stage or not sent_event:
|
||||
sent_event = True
|
||||
on_progress(
|
||||
RestoreBackupEvent(
|
||||
reason=None,
|
||||
stage=stage,
|
||||
state=RestoreBackupState.IN_PROGRESS,
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
restore_errors = data.get("errors", [])
|
||||
|
@ -592,14 +612,14 @@ class SupervisorBackupReaderWriter(BackupReaderWriter):
|
|||
on_progress(
|
||||
RestoreBackupEvent(
|
||||
reason="unknown_error",
|
||||
stage=None,
|
||||
stage=stage,
|
||||
state=RestoreBackupState.FAILED,
|
||||
)
|
||||
)
|
||||
else:
|
||||
on_progress(
|
||||
RestoreBackupEvent(
|
||||
reason="", stage=None, state=RestoreBackupState.COMPLETED
|
||||
reason=None, stage=stage, state=RestoreBackupState.COMPLETED
|
||||
)
|
||||
)
|
||||
on_progress(IdleEvent())
|
||||
|
|
|
@ -2032,6 +2032,109 @@ async def test_reader_writer_restore(
|
|||
assert response["result"] is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("hassio_client", "setup_integration")
|
||||
async def test_reader_writer_restore_report_progress(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
supervisor_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test restoring a backup."""
|
||||
client = await hass_ws_client(hass)
|
||||
supervisor_client.backups.partial_restore.return_value.job_id = TEST_JOB_ID
|
||||
supervisor_client.backups.list.return_value = [TEST_BACKUP]
|
||||
supervisor_client.backups.backup_info.return_value = TEST_BACKUP_DETAILS
|
||||
supervisor_client.jobs.get_job.return_value = TEST_JOB_NOT_DONE
|
||||
|
||||
await client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||
response = await client.receive_json()
|
||||
assert response["event"] == {
|
||||
"manager_state": "idle",
|
||||
}
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
|
||||
await client.send_json_auto_id(
|
||||
{"type": "backup/restore", "agent_id": "hassio.local", "backup_id": "abc123"}
|
||||
)
|
||||
response = await client.receive_json()
|
||||
assert response["event"] == {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": None,
|
||||
"state": "in_progress",
|
||||
}
|
||||
|
||||
supervisor_client.backups.partial_restore.assert_called_once_with(
|
||||
"abc123",
|
||||
supervisor_backups.PartialRestoreOptions(
|
||||
addons=None,
|
||||
background=True,
|
||||
folders=None,
|
||||
homeassistant=True,
|
||||
location=None,
|
||||
password=None,
|
||||
),
|
||||
)
|
||||
|
||||
supervisor_event_base = {"uuid": TEST_JOB_ID, "reference": "test_slug"}
|
||||
supervisor_events = [
|
||||
supervisor_event_base | {"done": False, "stage": "addon_repositories"},
|
||||
supervisor_event_base | {"done": False, "stage": None}, # Will be skipped
|
||||
supervisor_event_base | {"done": False, "stage": "unknown"}, # Will be skipped
|
||||
supervisor_event_base | {"done": False, "stage": "home_assistant"},
|
||||
supervisor_event_base | {"done": True, "stage": "addons"},
|
||||
]
|
||||
expected_manager_events = [
|
||||
"addon_repositories",
|
||||
"home_assistant",
|
||||
"addons",
|
||||
]
|
||||
|
||||
for supervisor_event in supervisor_events:
|
||||
await client.send_json_auto_id(
|
||||
{
|
||||
"type": "supervisor/event",
|
||||
"data": {"event": "job", "data": supervisor_event},
|
||||
}
|
||||
)
|
||||
|
||||
acks = 0
|
||||
events = []
|
||||
for _ in range(len(supervisor_events) + len(expected_manager_events)):
|
||||
response = await client.receive_json()
|
||||
if "event" in response:
|
||||
events.append(response)
|
||||
continue
|
||||
assert response["success"]
|
||||
acks += 1
|
||||
|
||||
assert acks == len(supervisor_events)
|
||||
assert len(events) == len(expected_manager_events)
|
||||
|
||||
for i, event in enumerate(events):
|
||||
assert event["event"] == {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": expected_manager_events[i],
|
||||
"state": "in_progress",
|
||||
}
|
||||
|
||||
response = await client.receive_json()
|
||||
assert response["event"] == {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": None,
|
||||
"state": "completed",
|
||||
}
|
||||
|
||||
response = await client.receive_json()
|
||||
assert response["event"] == {"manager_state": "idle"}
|
||||
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("supervisor_error", "expected_error_code", "expected_reason"),
|
||||
[
|
||||
|
@ -2261,7 +2364,7 @@ async def test_reader_writer_restore_wrong_parameters(
|
|||
TEST_JOB_DONE,
|
||||
{
|
||||
"manager_state": "restore_backup",
|
||||
"reason": "",
|
||||
"reason": None,
|
||||
"stage": None,
|
||||
"state": "completed",
|
||||
},
|
||||
|
@ -2302,6 +2405,88 @@ async def test_restore_progress_after_restart(
|
|||
assert response["result"]["state"] == "idle"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("hassio_client")
|
||||
async def test_restore_progress_after_restart_report_progress(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
supervisor_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test restore backup progress after restart."""
|
||||
|
||||
supervisor_client.jobs.get_job.return_value = TEST_JOB_NOT_DONE
|
||||
|
||||
with patch.dict(os.environ, MOCK_ENVIRON | {RESTORE_JOB_ID_ENV: TEST_JOB_ID}):
|
||||
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json_auto_id({"type": "backup/subscribe_events"})
|
||||
response = await client.receive_json()
|
||||
assert response["event"] == {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": None,
|
||||
"state": "in_progress",
|
||||
}
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
|
||||
supervisor_event_base = {"uuid": TEST_JOB_ID, "reference": "test_slug"}
|
||||
supervisor_events = [
|
||||
supervisor_event_base | {"done": False, "stage": "addon_repositories"},
|
||||
supervisor_event_base | {"done": False, "stage": None}, # Will be skipped
|
||||
supervisor_event_base | {"done": False, "stage": "unknown"}, # Will be skipped
|
||||
supervisor_event_base | {"done": False, "stage": "home_assistant"},
|
||||
supervisor_event_base | {"done": True, "stage": "addons"},
|
||||
]
|
||||
expected_manager_events = ["addon_repositories", "home_assistant", "addons"]
|
||||
expected_manager_states = ["in_progress", "in_progress", "completed"]
|
||||
|
||||
for supervisor_event in supervisor_events:
|
||||
await client.send_json_auto_id(
|
||||
{
|
||||
"type": "supervisor/event",
|
||||
"data": {"event": "job", "data": supervisor_event},
|
||||
}
|
||||
)
|
||||
|
||||
acks = 0
|
||||
events = []
|
||||
for _ in range(len(supervisor_events) + len(expected_manager_events)):
|
||||
response = await client.receive_json()
|
||||
if "event" in response:
|
||||
events.append(response)
|
||||
continue
|
||||
assert response["success"]
|
||||
acks += 1
|
||||
|
||||
assert acks == len(supervisor_events)
|
||||
assert len(events) == len(expected_manager_events)
|
||||
|
||||
for i, event in enumerate(events):
|
||||
assert event["event"] == {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": expected_manager_events[i],
|
||||
"state": expected_manager_states[i],
|
||||
}
|
||||
|
||||
response = await client.receive_json()
|
||||
assert response["event"] == {"manager_state": "idle"}
|
||||
|
||||
await client.send_json_auto_id({"type": "backup/info"})
|
||||
response = await client.receive_json()
|
||||
|
||||
assert response["success"]
|
||||
assert response["result"]["last_non_idle_event"] == {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": "addons",
|
||||
"state": "completed",
|
||||
}
|
||||
assert response["result"]["state"] == "idle"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("hassio_client")
|
||||
async def test_restore_progress_after_restart_unknown_job(
|
||||
hass: HomeAssistant,
|
||||
|
|
Loading…
Reference in New Issue