Skip to content

Commit

Permalink
Unmount mounts before backup restore (#4557)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdegat01 authored Sep 12, 2023
1 parent 2bb10a3 commit 5ae585c
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 1 deletion.
21 changes: 20 additions & 1 deletion supervisor/backups/backup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Representation of a backup file."""
import asyncio
from base64 import b64decode, b64encode
from collections.abc import Awaitable
from datetime import timedelta
Expand Down Expand Up @@ -490,6 +491,18 @@ async def _folder_restore(name: str) -> None:
_LOGGER.warning("Can't find restore folder %s", name)
return

# Unmount any mounts within folder
bind_mounts = [
bound.bind_mount
for bound in self.sys_mounts.bound_mounts
if bound.bind_mount.local_where
and bound.bind_mount.local_where.is_relative_to(origin_dir)
]
if bind_mounts:
await asyncio.gather(
*[bind_mount.unmount() for bind_mount in bind_mounts]
)

# Clean old stuff
if origin_dir.is_dir():
await remove_folder(origin_dir, content_only=True)
Expand All @@ -510,7 +523,13 @@ def _restore() -> None:
except (tarfile.TarError, OSError) as err:
_LOGGER.warning("Can't restore folder %s: %s", name, err)

await self.sys_run_in_executor(_restore)
try:
await self.sys_run_in_executor(_restore)
finally:
if bind_mounts:
await asyncio.gather(
*[bind_mount.mount() for bind_mount in bind_mounts]
)

# Restore folder sequential
# avoid issue on slow IO
Expand Down
51 changes: 51 additions & 0 deletions tests/backups/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,57 @@ async def test_backup_media_with_mounts(
assert not mount_dir.exists()


async def test_backup_media_with_mounts_retains_files(
coresys: CoreSys,
all_dbus_services: dict[str, DBusServiceMock],
tmp_supervisor_data,
path_extern,
mount_propagation,
):
"""Test backing up media folder with mounts retains mount files."""
systemd_service: SystemdService = all_dbus_services["systemd"]
systemd_service.response_get_unit = [
DBusError("org.freedesktop.systemd1.NoSuchUnit", "error"),
"/org/freedesktop/systemd1/unit/tmp_2dyellow_2emount",
DBusError("org.freedesktop.systemd1.NoSuchUnit", "error"),
"/org/freedesktop/systemd1/unit/tmp_2dyellow_2emount",
"/org/freedesktop/systemd1/unit/tmp_2dyellow_2emount",
"/org/freedesktop/systemd1/unit/tmp_2dyellow_2emount",
]

# Add a media mount
await coresys.mounts.load()
await coresys.mounts.create_mount(
Mount.from_dict(
coresys,
{
"name": "media_test",
"usage": "media",
"type": "cifs",
"server": "test.local",
"share": "test",
},
)
)

# Make a partial backup
coresys.core.state = CoreState.RUNNING
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
backup: Backup = await coresys.backups.do_backup_partial("test", folders=["media"])

systemd_service.StopUnit.calls.clear()
systemd_service.StartTransientUnit.calls.clear()
with patch.object(DockerHomeAssistant, "is_running", return_value=True):
await coresys.backups.do_restore_partial(backup, folders=["media"])

assert systemd_service.StopUnit.calls == [
("mnt-data-supervisor-media-media_test.mount", "fail")
]
assert systemd_service.StartTransientUnit.calls == [
("mnt-data-supervisor-media-media_test.mount", "fail", ANY, [])
]


async def test_backup_share_with_mounts(
coresys: CoreSys,
all_dbus_services: dict[str, DBusServiceMock],
Expand Down

0 comments on commit 5ae585c

Please sign in to comment.