mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-12-24 12:29:08 +00:00
Add support for cloud backups in Core (#5438)
* Add support for cloud backups in Core * Test cases and small fixes identified * Add test for partial reload no file failure
This commit is contained in:
@@ -4,7 +4,7 @@ import asyncio
|
||||
import errno
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from shutil import copy, rmtree
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock, Mock, PropertyMock, patch
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
@@ -34,10 +34,12 @@ 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 import JobSchedulerOptions
|
||||
from supervisor.jobs.const import JobCondition
|
||||
from supervisor.mounts.mount import Mount
|
||||
from supervisor.utils.json import read_json_file, write_json_file
|
||||
|
||||
from tests.common import get_fixture_path
|
||||
from tests.const import TEST_ADDON_SLUG
|
||||
from tests.dbus_service_mocks.base import DBusServiceMock
|
||||
from tests.dbus_service_mocks.systemd import Systemd as SystemdService
|
||||
@@ -626,7 +628,8 @@ async def test_full_backup_to_mount(
|
||||
},
|
||||
)
|
||||
await coresys.mounts.create_mount(mount)
|
||||
assert mount_dir in coresys.backups.backup_locations
|
||||
assert "backup_test" in coresys.backups.backup_locations
|
||||
assert coresys.backups.backup_locations["backup_test"] == mount_dir
|
||||
|
||||
# Make a backup and add it to mounts. Confirm it exists in the right place
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
@@ -671,7 +674,8 @@ async def test_partial_backup_to_mount(
|
||||
},
|
||||
)
|
||||
await coresys.mounts.create_mount(mount)
|
||||
assert mount_dir in coresys.backups.backup_locations
|
||||
assert "backup_test" in coresys.backups.backup_locations
|
||||
assert coresys.backups.backup_locations["backup_test"] == mount_dir
|
||||
|
||||
# Make a backup and add it to mounts. Confirm it exists in the right place
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
@@ -723,7 +727,8 @@ async def test_backup_to_down_mount_error(
|
||||
},
|
||||
)
|
||||
await coresys.mounts.create_mount(mount)
|
||||
assert mount_dir in coresys.backups.backup_locations
|
||||
assert "backup_test" in coresys.backups.backup_locations
|
||||
assert coresys.backups.backup_locations["backup_test"] == mount_dir
|
||||
|
||||
# Attempt to make a backup which fails because is_mount on directory is false
|
||||
mock_is_mount.return_value = False
|
||||
@@ -1866,3 +1871,161 @@ async def test_core_pre_backup_actions_failed(
|
||||
f"Preparing backup of Home Assistant Core failed due to: {pre_backup_error['message']}"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mount_propagation", "mock_is_mount", "path_extern")
|
||||
async def test_reload_multiple_locations(coresys: CoreSys, tmp_supervisor_data: Path):
|
||||
"""Test reload with a backup that exists in multiple locations."""
|
||||
(mount_dir := coresys.config.path_mounts / "backup_test").mkdir()
|
||||
await coresys.mounts.load()
|
||||
mount = Mount.from_dict(
|
||||
coresys,
|
||||
{
|
||||
"name": "backup_test",
|
||||
"usage": "backup",
|
||||
"type": "cifs",
|
||||
"server": "test.local",
|
||||
"share": "test",
|
||||
},
|
||||
)
|
||||
await coresys.mounts.create_mount(mount)
|
||||
|
||||
assert not coresys.backups.list_backups
|
||||
|
||||
backup_file = get_fixture_path("backup_example.tar")
|
||||
copy(backup_file, tmp_supervisor_data / "core/backup")
|
||||
await coresys.backups.reload()
|
||||
|
||||
assert coresys.backups.list_backups
|
||||
assert (backup := coresys.backups.get("7fed74c8"))
|
||||
assert backup.location == ".cloud_backup"
|
||||
assert backup.locations == [".cloud_backup"]
|
||||
assert backup.all_locations == {".cloud_backup"}
|
||||
|
||||
copy(backup_file, tmp_supervisor_data / "backup")
|
||||
await coresys.backups.reload()
|
||||
|
||||
assert coresys.backups.list_backups
|
||||
assert (backup := coresys.backups.get("7fed74c8"))
|
||||
assert backup.location is None
|
||||
assert backup.locations == [None]
|
||||
assert backup.all_locations == {".cloud_backup", None}
|
||||
|
||||
copy(backup_file, mount_dir)
|
||||
await coresys.backups.reload()
|
||||
|
||||
assert coresys.backups.list_backups
|
||||
assert (backup := coresys.backups.get("7fed74c8"))
|
||||
assert backup.location in {None, "backup_test"}
|
||||
assert backup.locations == [None, "backup_test"]
|
||||
assert backup.all_locations == {".cloud_backup", None, "backup_test"}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mount_propagation", "mock_is_mount", "path_extern")
|
||||
async def test_partial_reload_multiple_locations(
|
||||
coresys: CoreSys, tmp_supervisor_data: Path
|
||||
):
|
||||
"""Test a partial reload with a backup that exists in multiple locations."""
|
||||
(mount_dir := coresys.config.path_mounts / "backup_test").mkdir()
|
||||
await coresys.mounts.load()
|
||||
mount = Mount.from_dict(
|
||||
coresys,
|
||||
{
|
||||
"name": "backup_test",
|
||||
"usage": "backup",
|
||||
"type": "cifs",
|
||||
"server": "test.local",
|
||||
"share": "test",
|
||||
},
|
||||
)
|
||||
await coresys.mounts.create_mount(mount)
|
||||
|
||||
assert not coresys.backups.list_backups
|
||||
|
||||
backup_file = get_fixture_path("backup_example.tar")
|
||||
copy(backup_file, tmp_supervisor_data / "core/backup")
|
||||
await coresys.backups.reload()
|
||||
|
||||
assert coresys.backups.list_backups
|
||||
assert (backup := coresys.backups.get("7fed74c8"))
|
||||
assert backup.location == ".cloud_backup"
|
||||
assert backup.locations == [".cloud_backup"]
|
||||
assert backup.all_locations == {".cloud_backup"}
|
||||
|
||||
copy(backup_file, tmp_supervisor_data / "backup")
|
||||
await coresys.backups.reload(location=None, filename="backup_example.tar")
|
||||
|
||||
assert coresys.backups.list_backups
|
||||
assert (backup := coresys.backups.get("7fed74c8"))
|
||||
assert backup.location is None
|
||||
assert backup.locations == [None]
|
||||
assert backup.all_locations == {".cloud_backup", None}
|
||||
|
||||
copy(backup_file, mount_dir)
|
||||
await coresys.backups.reload(location=mount, filename="backup_example.tar")
|
||||
|
||||
assert coresys.backups.list_backups
|
||||
assert (backup := coresys.backups.get("7fed74c8"))
|
||||
assert backup.location is None
|
||||
assert backup.locations == [None, "backup_test"]
|
||||
assert backup.all_locations == {".cloud_backup", None, "backup_test"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("location", "folder"), [(None, "backup"), (".cloud_backup", "cloud_backup")]
|
||||
)
|
||||
@pytest.mark.usefixtures("tmp_supervisor_data")
|
||||
async def test_partial_backup_complete_ws_message(
|
||||
coresys: CoreSys, ha_ws_client: AsyncMock, location: str | None, folder: str
|
||||
):
|
||||
"""Test WS message notifies core when a partial backup is complete."""
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||
ha_ws_client.ha_version = AwesomeVersion("2024.12.0")
|
||||
|
||||
# Test a partial backup
|
||||
job, backup_task = coresys.jobs.schedule_job(
|
||||
coresys.backups.do_backup_partial,
|
||||
JobSchedulerOptions(),
|
||||
"test",
|
||||
folders=["media"],
|
||||
location=location,
|
||||
)
|
||||
backup: Backup = await backup_task
|
||||
|
||||
assert ha_ws_client.async_send_command.call_args_list[-3].args[0] == {
|
||||
"type": "backup/supervisor/backup_complete",
|
||||
"data": {
|
||||
"job_id": job.uuid,
|
||||
"slug": backup.slug,
|
||||
"path": f"/{folder}/{backup.slug}.tar",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("location", "folder"), [(None, "backup"), (".cloud_backup", "cloud_backup")]
|
||||
)
|
||||
@pytest.mark.usefixtures("tmp_supervisor_data")
|
||||
async def test_full_backup_complete_ws_message(
|
||||
coresys: CoreSys, ha_ws_client: AsyncMock, location: str | None, folder: str
|
||||
):
|
||||
"""Test WS message notifies core when a full backup is complete."""
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||
ha_ws_client.ha_version = AwesomeVersion("2024.12.0")
|
||||
|
||||
# Test a full backup
|
||||
job, backup_task = coresys.jobs.schedule_job(
|
||||
coresys.backups.do_backup_full, JobSchedulerOptions(), "test", location=location
|
||||
)
|
||||
backup: Backup = await backup_task
|
||||
|
||||
assert ha_ws_client.async_send_command.call_args_list[-3].args[0] == {
|
||||
"type": "backup/supervisor/backup_complete",
|
||||
"data": {
|
||||
"job_id": job.uuid,
|
||||
"slug": backup.slug,
|
||||
"path": f"/{folder}/{backup.slug}.tar",
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user