mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Don't allow creating backups if Home Assistant is not running (#139499)
* Don't allow creating backups if hass is not running * Revert "Don't allow creating backups if hass is not running" This reverts commit 1bf545eb25f20fc27fe161691a94531cba7e005c. * Set backup manager to idle only after Home Assistant has started * Update according to discussion, add tests * Add more test
This commit is contained in:
parent
76e76a417c
commit
219b441be0
@ -120,6 +120,7 @@ class BackupManagerState(StrEnum):
|
||||
|
||||
IDLE = "idle"
|
||||
CREATE_BACKUP = "create_backup"
|
||||
BLOCKED = "blocked"
|
||||
RECEIVE_BACKUP = "receive_backup"
|
||||
RESTORE_BACKUP = "restore_backup"
|
||||
|
||||
@ -228,6 +229,13 @@ class RestoreBackupEvent(ManagerStateEvent):
|
||||
state: RestoreBackupState
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True, slots=True)
|
||||
class BlockedEvent(ManagerStateEvent):
|
||||
"""Backup manager blocked, Home Assistant is starting."""
|
||||
|
||||
manager_state: BackupManagerState = BackupManagerState.BLOCKED
|
||||
|
||||
|
||||
class BackupPlatformProtocol(Protocol):
|
||||
"""Define the format that backup platforms can have."""
|
||||
|
||||
@ -342,7 +350,7 @@ class BackupManager:
|
||||
self.remove_next_delete_event: Callable[[], None] | None = None
|
||||
|
||||
# Latest backup event and backup event subscribers
|
||||
self.last_event: ManagerStateEvent = IdleEvent()
|
||||
self.last_event: ManagerStateEvent = BlockedEvent()
|
||||
self.last_non_idle_event: ManagerStateEvent | None = None
|
||||
self._backup_event_subscriptions = hass.data[
|
||||
DATA_BACKUP
|
||||
@ -356,10 +364,19 @@ class BackupManager:
|
||||
self.known_backups.load(stored["backups"])
|
||||
|
||||
await self._reader_writer.async_validate_config(config=self.config)
|
||||
|
||||
await self._reader_writer.async_resume_restore_progress_after_restart(
|
||||
on_progress=self.async_on_backup_event
|
||||
)
|
||||
|
||||
async def set_manager_idle_after_start(hass: HomeAssistant) -> None:
|
||||
"""Set manager to idle after start."""
|
||||
self.async_on_backup_event(IdleEvent())
|
||||
|
||||
if self.state == BackupManagerState.BLOCKED:
|
||||
# If we're not finishing a restore job, set the manager to idle after start
|
||||
start.async_at_started(self.hass, set_manager_idle_after_start)
|
||||
|
||||
await self.load_platforms()
|
||||
|
||||
@property
|
||||
@ -1319,7 +1336,7 @@ class BackupManager:
|
||||
if (current_state := self.state) != (new_state := event.manager_state):
|
||||
LOGGER.debug("Backup state: %s -> %s", current_state, new_state)
|
||||
self.last_event = event
|
||||
if not isinstance(event, IdleEvent):
|
||||
if not isinstance(event, (BlockedEvent, IdleEvent)):
|
||||
self.last_non_idle_event = event
|
||||
for subscription in self._backup_event_subscriptions:
|
||||
subscription(event)
|
||||
|
@ -47,7 +47,8 @@ from homeassistant.components.backup.manager import (
|
||||
WrittenBackup,
|
||||
)
|
||||
from homeassistant.components.backup.util import password_to_key
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STARTED
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
@ -3469,3 +3470,66 @@ async def test_restore_progress_after_restart_fail_to_remove(
|
||||
"Unexpected error deleting backup restore result file: <class 'OSError'> Boom!"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
async def test_manager_blocked_until_home_assistant_started(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test backup manager's state is blocked until Home Assistant has started."""
|
||||
|
||||
hass.set_state(CoreState.not_running)
|
||||
|
||||
await setup_backup_integration(hass)
|
||||
manager = hass.data[DATA_MANAGER]
|
||||
|
||||
assert manager.state == BackupManagerState.BLOCKED
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
# Fired when Home Assistant changes to starting state
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
assert manager.state == BackupManagerState.BLOCKED
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
# Fired when Home Assistant changes to running state
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
assert manager.state == BackupManagerState.IDLE
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
|
||||
async def test_manager_not_blocked_after_restore(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test restore backup progress after restart."""
|
||||
restore_result = {"error": None, "error_type": None, "success": True}
|
||||
|
||||
hass.set_state(CoreState.not_running)
|
||||
with patch(
|
||||
"pathlib.Path.read_bytes", return_value=json.dumps(restore_result).encode()
|
||||
):
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
await ws_client.send_json_auto_id({"type": "backup/info"})
|
||||
result = await ws_client.receive_json()
|
||||
assert result["success"] is True
|
||||
assert result["result"] == {
|
||||
"agent_errors": {},
|
||||
"backups": [],
|
||||
"last_attempted_automatic_backup": None,
|
||||
"last_completed_automatic_backup": None,
|
||||
"last_non_idle_event": {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": None,
|
||||
"state": "completed",
|
||||
},
|
||||
"next_automatic_backup": None,
|
||||
"next_automatic_backup_additional": False,
|
||||
"state": "idle",
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import pytest
|
||||
|
||||
from homeassistant.auth.models import RefreshToken
|
||||
from homeassistant.components.hassio.handler import HassIO, HassioAPIError
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
@ -75,7 +75,6 @@ def hassio_stubs(
|
||||
"homeassistant.components.hassio.issues.SupervisorIssues.setup",
|
||||
),
|
||||
):
|
||||
hass.set_state(CoreState.starting)
|
||||
hass.loop.run_until_complete(async_setup_component(hass, "hassio", {}))
|
||||
|
||||
return hass_api.call_args[0][1]
|
||||
|
Loading…
x
Reference in New Issue
Block a user