1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2025-12-24 12:29:08 +00:00

Add freeze/thaw apis for external snapshots (#4538)

* Add freeze/thaw apis for external backups

* Error when thaw called before freeze

* Timeout must be > 0
This commit is contained in:
Mike Degatano
2023-09-09 04:54:19 -04:00
committed by GitHub
parent 0aafda1477
commit 44daffc65b
14 changed files with 577 additions and 213 deletions

View File

@@ -20,7 +20,7 @@ from supervisor.docker.addon import DockerAddon
from supervisor.docker.const import ContainerState
from supervisor.docker.homeassistant import DockerHomeAssistant
from supervisor.docker.monitor import DockerContainerStateEvent
from supervisor.exceptions import AddonsError, DockerError
from supervisor.exceptions import AddonsError, BackupError, DockerError
from supervisor.homeassistant.core import HomeAssistantCore
from supervisor.homeassistant.module import HomeAssistant
from supervisor.mounts.mount import Mount
@@ -832,8 +832,7 @@ async def test_restore_with_healthcheck(
def _make_backup_message_for_assert(
*,
full: bool = True,
restore: bool = False,
action: str = "full_backup",
reference: str,
stage: str | None,
done: bool = False,
@@ -844,7 +843,7 @@ def _make_backup_message_for_assert(
"data": {
"event": "job",
"data": {
"name": f"backup_manager_{'full' if full else 'partial'}_{'restore' if restore else 'backup'}",
"name": f"backup_manager_{action}",
"reference": reference,
"uuid": ANY,
"progress": 0,
@@ -920,27 +919,35 @@ async def test_backup_progress(
== "backup_manager_partial_backup"
]
assert messages == [
_make_backup_message_for_assert(full=False, reference=None, stage=None),
_make_backup_message_for_assert(
full=False, reference=partial_backup.slug, stage=None
action="partial_backup", reference=None, stage=None
),
_make_backup_message_for_assert(
full=False, reference=partial_backup.slug, stage="addon_repositories"
action="partial_backup", reference=partial_backup.slug, stage=None
),
_make_backup_message_for_assert(
full=False, reference=partial_backup.slug, stage="docker_config"
action="partial_backup",
reference=partial_backup.slug,
stage="addon_repositories",
),
_make_backup_message_for_assert(
full=False, reference=partial_backup.slug, stage="addons"
action="partial_backup",
reference=partial_backup.slug,
stage="docker_config",
),
_make_backup_message_for_assert(
full=False, reference=partial_backup.slug, stage="folders"
action="partial_backup", reference=partial_backup.slug, stage="addons"
),
_make_backup_message_for_assert(
full=False, reference=partial_backup.slug, stage="finishing_file"
action="partial_backup", reference=partial_backup.slug, stage="folders"
),
_make_backup_message_for_assert(
full=False,
action="partial_backup",
reference=partial_backup.slug,
stage="finishing_file",
),
_make_backup_message_for_assert(
action="partial_backup",
reference=partial_backup.slug,
stage="finishing_file",
done=True,
@@ -987,51 +994,53 @@ async def test_restore_progress(
== "backup_manager_full_restore"
]
assert messages == [
_make_backup_message_for_assert(restore=True, reference=None, stage=None),
_make_backup_message_for_assert(
restore=True, reference=full_backup.slug, stage=None
action="full_restore", reference=None, stage=None
),
_make_backup_message_for_assert(
restore=True, reference=full_backup.slug, stage="docker_config"
action="full_restore", reference=full_backup.slug, stage=None
),
_make_backup_message_for_assert(
restore=True, reference=full_backup.slug, stage="folders"
action="full_restore", reference=full_backup.slug, stage="docker_config"
),
_make_backup_message_for_assert(
restore=True,
action="full_restore", reference=full_backup.slug, stage="folders"
),
_make_backup_message_for_assert(
action="full_restore",
reference=full_backup.slug,
stage="home_assistant",
),
_make_backup_message_for_assert(
restore=True,
action="full_restore",
reference=full_backup.slug,
stage="remove_delta_addons",
),
_make_backup_message_for_assert(
restore=True,
action="full_restore",
reference=full_backup.slug,
stage="addon_repositories",
),
_make_backup_message_for_assert(
restore=True, reference=full_backup.slug, stage="addons"
action="full_restore", reference=full_backup.slug, stage="addons"
),
_make_backup_message_for_assert(
restore=True,
action="full_restore",
reference=full_backup.slug,
stage="await_home_assistant_restart",
),
_make_backup_message_for_assert(
restore=True,
action="full_restore",
reference=full_backup.slug,
stage="await_addon_restarts",
),
_make_backup_message_for_assert(
restore=True,
action="full_restore",
reference=full_backup.slug,
stage="check_home_assistant",
),
_make_backup_message_for_assert(
restore=True,
action="full_restore",
reference=full_backup.slug,
stage="check_home_assistant",
done=True,
@@ -1055,29 +1064,25 @@ async def test_restore_progress(
]
assert messages == [
_make_backup_message_for_assert(
full=False, restore=True, reference=None, stage=None
action="partial_restore", reference=None, stage=None
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=folders_backup.slug,
stage=None,
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=folders_backup.slug,
stage="docker_config",
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=folders_backup.slug,
stage="folders",
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=folders_backup.slug,
stage="folders",
done=True,
@@ -1103,37 +1108,151 @@ async def test_restore_progress(
]
assert messages == [
_make_backup_message_for_assert(
full=False, restore=True, reference=None, stage=None
action="partial_restore", reference=None, stage=None
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=addon_backup.slug,
stage=None,
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=addon_backup.slug,
stage="docker_config",
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=addon_backup.slug,
stage="addon_repositories",
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=addon_backup.slug,
stage="addons",
),
_make_backup_message_for_assert(
full=False,
restore=True,
action="partial_restore",
reference=addon_backup.slug,
stage="addons",
done=True,
),
]
async def test_freeze_thaw(
coresys: CoreSys,
install_addon_ssh: Addon,
container: MagicMock,
ha_ws_client: AsyncMock,
tmp_supervisor_data,
path_extern,
):
"""Test manual freeze and thaw for external snapshots."""
container.status = "running"
install_addon_ssh.path_data.mkdir()
coresys.core.state = CoreState.RUNNING
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
container.exec_run.return_value = (0, None)
ha_ws_client.ha_version = AwesomeVersion("2022.1.0")
with patch.object(
AddonModel, "backup_pre", new=PropertyMock(return_value="pre_backup")
), patch.object(
AddonModel, "backup_post", new=PropertyMock(return_value="post_backup")
):
# Run the freeze
await coresys.backups.freeze_all()
container.exec_run.assert_called_once_with("pre_backup")
assert coresys.core.state == CoreState.FREEZE
await asyncio.sleep(0)
messages = [
call.args[0]
for call in ha_ws_client.async_send_command.call_args_list
if call.args[0]["type"] in ["backup/start", "backup/end"]
or call.args[0]["data"].get("data", {}).get("name")
in ["backup_manager_freeze_all", "backup_manager_thaw_all"]
]
assert messages == [
_make_backup_message_for_assert(
action="freeze_all", reference=None, stage=None
),
{"type": "backup/start"},
_make_backup_message_for_assert(
action="freeze_all", reference=None, stage="home_assistant"
),
_make_backup_message_for_assert(
action="freeze_all", reference=None, stage="addons"
),
_make_backup_message_for_assert(
action="thaw_all", reference=None, stage=None
),
_make_backup_message_for_assert(
action="freeze_all", reference=None, stage="addons", done=True
),
]
# Release the thaw task
container.exec_run.reset_mock()
ha_ws_client.async_send_command.reset_mock()
await coresys.backups.thaw_all()
container.exec_run.assert_called_once_with("post_backup")
assert coresys.core.state == CoreState.RUNNING
await asyncio.sleep(0)
messages = [
call.args[0]
for call in ha_ws_client.async_send_command.call_args_list
if call.args[0]["type"] in ["backup/start", "backup/end"]
or call.args[0]["data"].get("data", {}).get("name")
in ["backup_manager_freeze_all", "backup_manager_thaw_all"]
]
assert messages == [
{"type": "backup/end"},
_make_backup_message_for_assert(
action="thaw_all", reference=None, stage="home_assistant"
),
_make_backup_message_for_assert(
action="thaw_all", reference=None, stage="addons"
),
_make_backup_message_for_assert(
action="thaw_all", reference=None, stage="addons", done=True
),
]
async def test_freeze_thaw_timeout(
coresys: CoreSys,
ha_ws_client: AsyncMock,
caplog: pytest.LogCaptureFixture,
tmp_supervisor_data,
path_extern,
):
"""Test manual freeze ends due to timeout expiration."""
coresys.core.state = CoreState.RUNNING
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
ha_ws_client.ha_version = AwesomeVersion("2022.1.0")
await coresys.backups.freeze_all(timeout=0.01)
assert coresys.core.state == CoreState.FREEZE
await asyncio.sleep(0)
assert any(
call.args[0] == {"type": "backup/start"}
for call in ha_ws_client.async_send_command.call_args_list
)
ha_ws_client.async_send_command.reset_mock()
await asyncio.sleep(0.02)
assert coresys.core.state == CoreState.RUNNING
assert any(
call.args[0] == {"type": "backup/end"}
for call in ha_ws_client.async_send_command.call_args_list
)
assert "Timeout waiting for signal to thaw after manual freeze" in caplog.text
async def test_cannot_manually_thaw_normal_freeze(coresys: CoreSys):
"""Test thaw_all cannot be used unless freeze was started by freeze_all method."""
coresys.core.state = CoreState.FREEZE
with pytest.raises(BackupError):
await coresys.backups.thaw_all()