mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-12-24 20:35:55 +00:00
Stop backup if pre backup failed in Core (#5203)
* Stop backup if pre backup failed in Core * Fix API tests * Partial backup in ci since there's no Home assistant * Add ssl folder to partial backup * Allow backups when Home Assistant is not running * Undo change to skip db test
This commit is contained in:
@@ -31,6 +31,7 @@ from supervisor.exceptions import (
|
||||
DockerError,
|
||||
)
|
||||
from supervisor.homeassistant.api import HomeAssistantAPI
|
||||
from supervisor.homeassistant.const import WSType
|
||||
from supervisor.homeassistant.core import HomeAssistantCore
|
||||
from supervisor.homeassistant.module import HomeAssistant
|
||||
from supervisor.jobs.const import JobCondition
|
||||
@@ -335,9 +336,14 @@ async def test_fail_invalid_full_backup(
|
||||
|
||||
backup_instance.protected = False
|
||||
backup_instance.supervisor_version = "2022.08.4"
|
||||
with patch.object(
|
||||
type(coresys.supervisor), "version", new=PropertyMock(return_value="2022.08.3")
|
||||
), pytest.raises(BackupInvalidError):
|
||||
with (
|
||||
patch.object(
|
||||
type(coresys.supervisor),
|
||||
"version",
|
||||
new=PropertyMock(return_value="2022.08.3"),
|
||||
),
|
||||
pytest.raises(BackupInvalidError),
|
||||
):
|
||||
await manager.do_restore_full(backup_instance)
|
||||
|
||||
|
||||
@@ -364,9 +370,14 @@ async def test_fail_invalid_partial_backup(
|
||||
await manager.do_restore_partial(backup_instance, homeassistant=True)
|
||||
|
||||
backup_instance.supervisor_version = "2022.08.4"
|
||||
with patch.object(
|
||||
type(coresys.supervisor), "version", new=PropertyMock(return_value="2022.08.3")
|
||||
), pytest.raises(BackupInvalidError):
|
||||
with (
|
||||
patch.object(
|
||||
type(coresys.supervisor),
|
||||
"version",
|
||||
new=PropertyMock(return_value="2022.08.3"),
|
||||
),
|
||||
pytest.raises(BackupInvalidError),
|
||||
):
|
||||
await manager.do_restore_partial(backup_instance)
|
||||
|
||||
|
||||
@@ -766,7 +777,11 @@ async def test_backup_to_local_with_default(
|
||||
|
||||
|
||||
async def test_backup_to_default(
|
||||
coresys: CoreSys, tmp_supervisor_data, path_extern, mount_propagation, mock_is_mount
|
||||
coresys: CoreSys,
|
||||
tmp_supervisor_data,
|
||||
path_extern,
|
||||
mount_propagation,
|
||||
mock_is_mount,
|
||||
):
|
||||
"""Test making backup to default mount."""
|
||||
# Add a default backup mount
|
||||
@@ -926,9 +941,15 @@ async def test_backup_with_healthcheck(
|
||||
nonlocal _container_events_task
|
||||
_container_events_task = asyncio.create_task(container_events())
|
||||
|
||||
with patch.object(DockerAddon, "run", new=container_events_task), patch.object(
|
||||
AddonModel, "backup_mode", new=PropertyMock(return_value=AddonBackupMode.COLD)
|
||||
), patch.object(DockerAddon, "is_running", side_effect=[True, False, False]):
|
||||
with (
|
||||
patch.object(DockerAddon, "run", new=container_events_task),
|
||||
patch.object(
|
||||
AddonModel,
|
||||
"backup_mode",
|
||||
new=PropertyMock(return_value=AddonBackupMode.COLD),
|
||||
),
|
||||
patch.object(DockerAddon, "is_running", side_effect=[True, False, False]),
|
||||
):
|
||||
backup = await coresys.backups.do_backup_partial(
|
||||
homeassistant=False, addons=["local_ssh"]
|
||||
)
|
||||
@@ -1000,10 +1021,11 @@ async def test_restore_with_healthcheck(
|
||||
nonlocal _container_events_task
|
||||
_container_events_task = asyncio.create_task(container_events())
|
||||
|
||||
with patch.object(DockerAddon, "run", new=container_events_task), patch.object(
|
||||
DockerAddon, "is_running", return_value=False
|
||||
), patch.object(AddonModel, "_validate_availability"), patch.object(
|
||||
Addon, "with_ingress", new=PropertyMock(return_value=False)
|
||||
with (
|
||||
patch.object(DockerAddon, "run", new=container_events_task),
|
||||
patch.object(DockerAddon, "is_running", return_value=False),
|
||||
patch.object(AddonModel, "_validate_availability"),
|
||||
patch.object(Addon, "with_ingress", new=PropertyMock(return_value=False)),
|
||||
):
|
||||
await coresys.backups.do_restore_partial(backup, addons=["local_ssh"])
|
||||
|
||||
@@ -1054,16 +1076,22 @@ async def test_backup_progress(
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||
|
||||
with patch.object(
|
||||
AddonModel, "backup_mode", new=PropertyMock(return_value=AddonBackupMode.COLD)
|
||||
), patch("supervisor.addons.addon.asyncio.Event.wait"):
|
||||
with (
|
||||
patch.object(
|
||||
AddonModel,
|
||||
"backup_mode",
|
||||
new=PropertyMock(return_value=AddonBackupMode.COLD),
|
||||
),
|
||||
patch("supervisor.addons.addon.asyncio.Event.wait"),
|
||||
):
|
||||
full_backup: Backup = await coresys.backups.do_backup_full()
|
||||
await asyncio.sleep(0)
|
||||
|
||||
messages = [
|
||||
call.args[0]
|
||||
for call in ha_ws_client.async_send_command.call_args_list
|
||||
if call.args[0]["data"].get("data", {}).get("name")
|
||||
if call.args[0]["type"] == WSType.SUPERVISOR_EVENT
|
||||
and call.args[0]["data"].get("data", {}).get("name")
|
||||
== "backup_manager_full_backup"
|
||||
]
|
||||
assert messages == [
|
||||
@@ -1075,10 +1103,10 @@ async def test_backup_progress(
|
||||
_make_backup_message_for_assert(
|
||||
reference=full_backup.slug, stage="docker_config"
|
||||
),
|
||||
_make_backup_message_for_assert(reference=full_backup.slug, stage="addons"),
|
||||
_make_backup_message_for_assert(
|
||||
reference=full_backup.slug, stage="home_assistant"
|
||||
),
|
||||
_make_backup_message_for_assert(reference=full_backup.slug, stage="addons"),
|
||||
_make_backup_message_for_assert(reference=full_backup.slug, stage="folders"),
|
||||
_make_backup_message_for_assert(
|
||||
reference=full_backup.slug, stage="finishing_file"
|
||||
@@ -1100,7 +1128,8 @@ async def test_backup_progress(
|
||||
messages = [
|
||||
call.args[0]
|
||||
for call in ha_ws_client.async_send_command.call_args_list
|
||||
if call.args[0]["data"].get("data", {}).get("name")
|
||||
if call.args[0]["type"] == WSType.SUPERVISOR_EVENT
|
||||
and call.args[0]["data"].get("data", {}).get("name")
|
||||
== "backup_manager_partial_backup"
|
||||
]
|
||||
assert messages == [
|
||||
@@ -1162,18 +1191,21 @@ async def test_restore_progress(
|
||||
|
||||
# Install another addon to be uninstalled
|
||||
request.getfixturevalue("install_addon_example")
|
||||
with patch("supervisor.addons.addon.asyncio.Event.wait"), patch.object(
|
||||
HomeAssistant, "restore"
|
||||
), patch.object(HomeAssistantCore, "update"), patch.object(
|
||||
AddonModel, "_validate_availability"
|
||||
), patch.object(AddonModel, "with_ingress", new=PropertyMock(return_value=False)):
|
||||
with (
|
||||
patch("supervisor.addons.addon.asyncio.Event.wait"),
|
||||
patch.object(HomeAssistant, "restore"),
|
||||
patch.object(HomeAssistantCore, "update"),
|
||||
patch.object(AddonModel, "_validate_availability"),
|
||||
patch.object(AddonModel, "with_ingress", new=PropertyMock(return_value=False)),
|
||||
):
|
||||
await coresys.backups.do_restore_full(full_backup)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
messages = [
|
||||
call.args[0]
|
||||
for call in ha_ws_client.async_send_command.call_args_list
|
||||
if call.args[0]["data"].get("data", {}).get("name")
|
||||
if call.args[0]["type"] == WSType.SUPERVISOR_EVENT
|
||||
and call.args[0]["data"].get("data", {}).get("name")
|
||||
== "backup_manager_full_restore"
|
||||
]
|
||||
assert messages == [
|
||||
@@ -1242,7 +1274,8 @@ async def test_restore_progress(
|
||||
messages = [
|
||||
call.args[0]
|
||||
for call in ha_ws_client.async_send_command.call_args_list
|
||||
if call.args[0]["data"].get("data", {}).get("name")
|
||||
if call.args[0]["type"] == WSType.SUPERVISOR_EVENT
|
||||
and call.args[0]["data"].get("data", {}).get("name")
|
||||
== "backup_manager_partial_restore"
|
||||
]
|
||||
assert messages == [
|
||||
@@ -1277,8 +1310,9 @@ async def test_restore_progress(
|
||||
addon_backup: Backup = await coresys.backups.do_backup_partial(addons=["local_ssh"])
|
||||
|
||||
ha_ws_client.async_send_command.reset_mock()
|
||||
with patch.object(AddonModel, "_validate_availability"), patch.object(
|
||||
HomeAssistantCore, "start"
|
||||
with (
|
||||
patch.object(AddonModel, "_validate_availability"),
|
||||
patch.object(HomeAssistantCore, "start"),
|
||||
):
|
||||
await coresys.backups.do_restore_partial(addon_backup, addons=["local_ssh"])
|
||||
await asyncio.sleep(0)
|
||||
@@ -1286,7 +1320,8 @@ async def test_restore_progress(
|
||||
messages = [
|
||||
call.args[0]
|
||||
for call in ha_ws_client.async_send_command.call_args_list
|
||||
if call.args[0]["data"].get("data", {}).get("name")
|
||||
if call.args[0]["type"] == WSType.SUPERVISOR_EVENT
|
||||
and call.args[0]["data"].get("data", {}).get("name")
|
||||
== "backup_manager_partial_restore"
|
||||
]
|
||||
assert messages == [
|
||||
@@ -1338,10 +1373,13 @@ async def test_freeze_thaw(
|
||||
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")
|
||||
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()
|
||||
@@ -1465,11 +1503,12 @@ async def test_restore_only_reloads_ingress_on_change(
|
||||
async def mock_is_running(*_) -> bool:
|
||||
return True
|
||||
|
||||
with patch.object(
|
||||
HomeAssistantCore, "is_running", new=mock_is_running
|
||||
), patch.object(AddonModel, "_validate_availability"), patch.object(
|
||||
DockerAddon, "attach"
|
||||
), patch.object(HomeAssistantAPI, "make_request") as make_request:
|
||||
with (
|
||||
patch.object(HomeAssistantCore, "is_running", new=mock_is_running),
|
||||
patch.object(AddonModel, "_validate_availability"),
|
||||
patch.object(DockerAddon, "attach"),
|
||||
patch.object(HomeAssistantAPI, "make_request") as make_request,
|
||||
):
|
||||
make_request.return_value.__aenter__.return_value.status = 200
|
||||
|
||||
# Has ingress before and after - not called
|
||||
@@ -1518,8 +1557,9 @@ async def test_restore_new_addon(
|
||||
await coresys.addons.uninstall("local_example")
|
||||
assert "local_example" not in coresys.addons.local
|
||||
|
||||
with patch.object(AddonModel, "_validate_availability"), patch.object(
|
||||
DockerAddon, "attach"
|
||||
with (
|
||||
patch.object(AddonModel, "_validate_availability"),
|
||||
patch.object(DockerAddon, "attach"),
|
||||
):
|
||||
assert await coresys.backups.do_restore_partial(
|
||||
backup, addons=["local_example"]
|
||||
@@ -1554,8 +1594,9 @@ async def test_restore_preserves_data_config(
|
||||
assert install_addon_example.path_config.exists()
|
||||
assert test_config2.exists()
|
||||
|
||||
with patch.object(AddonModel, "_validate_availability"), patch.object(
|
||||
DockerAddon, "attach"
|
||||
with (
|
||||
patch.object(AddonModel, "_validate_availability"),
|
||||
patch.object(DockerAddon, "attach"),
|
||||
):
|
||||
assert await coresys.backups.do_restore_partial(
|
||||
backup, addons=["local_example"]
|
||||
@@ -1660,8 +1701,9 @@ async def test_skip_homeassistant_database(
|
||||
write_json_file(test_db, {"hello": "world"})
|
||||
write_json_file(test_db_wal, {"hello": "world"})
|
||||
|
||||
with patch.object(HomeAssistantCore, "update"), patch.object(
|
||||
HomeAssistantCore, "start"
|
||||
with (
|
||||
patch.object(HomeAssistantCore, "update"),
|
||||
patch.object(HomeAssistantCore, "start"),
|
||||
):
|
||||
await coresys.backups.do_restore_partial(backup, homeassistant=True)
|
||||
|
||||
@@ -1735,8 +1777,9 @@ async def test_reload_error(
|
||||
)
|
||||
|
||||
mock_is_mount.return_value = False
|
||||
with patch("supervisor.backups.manager.Path.is_dir", new=mock_is_dir), patch(
|
||||
"supervisor.backups.manager.Path.glob", return_value=[]
|
||||
with (
|
||||
patch("supervisor.backups.manager.Path.is_dir", new=mock_is_dir),
|
||||
patch("supervisor.backups.manager.Path.glob", return_value=[]),
|
||||
):
|
||||
err.errno = errno.EBUSY
|
||||
await coresys.backups.reload()
|
||||
@@ -1787,3 +1830,39 @@ async def test_monitoring_after_partial_restore(
|
||||
backup_instance.restore_addons.assert_called_once_with([TEST_ADDON_SLUG])
|
||||
assert coresys.core.state == CoreState.RUNNING
|
||||
coresys.docker.unload.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pre_backup_error",
|
||||
[
|
||||
{
|
||||
"code": "pre_backup_actions_failed",
|
||||
"message": "Database migration in progress",
|
||||
},
|
||||
{"code": "unknown_command", "message": "Unknown command."},
|
||||
],
|
||||
)
|
||||
async def test_core_pre_backup_actions_failed(
|
||||
coresys: CoreSys,
|
||||
ha_ws_client: AsyncMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
pre_backup_error: dict[str, str],
|
||||
tmp_supervisor_data,
|
||||
path_extern,
|
||||
):
|
||||
"""Test pre-backup actions failed in HA core stops backup."""
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||
ha_ws_client.ha_version = AwesomeVersion("2024.7.0")
|
||||
ha_ws_client.async_send_command.return_value = {
|
||||
"error": pre_backup_error,
|
||||
"id": 1,
|
||||
"success": False,
|
||||
"type": "result",
|
||||
}
|
||||
|
||||
assert not await coresys.backups.do_backup_full()
|
||||
assert (
|
||||
f"Preparing backup of Home Assistant Core failed due to: {pre_backup_error['message']}"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user