mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-08 09:46:29 +00:00
Improve handling of NFS mounts and backup manager errors (#4323)
This commit is contained in:
parent
e449205863
commit
73d795e05e
@ -2,6 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from collections.abc import Iterable
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -29,6 +30,15 @@ from .validate import ALL_FOLDERS, SCHEMA_BACKUPS_CONFIG
|
|||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _list_backup_files(path: Path) -> Iterable[Path]:
|
||||||
|
"""Return iterable of backup files, suppress and log OSError for network mounts."""
|
||||||
|
try:
|
||||||
|
return path.glob("*.tar")
|
||||||
|
except OSError as err:
|
||||||
|
_LOGGER.error("Could not list backups from %s: %s", path.as_posix(), err)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class BackupManager(FileConfiguration, CoreSysAttributes):
|
class BackupManager(FileConfiguration, CoreSysAttributes):
|
||||||
"""Manage backups."""
|
"""Manage backups."""
|
||||||
|
|
||||||
@ -119,7 +129,7 @@ class BackupManager(FileConfiguration, CoreSysAttributes):
|
|||||||
tasks = [
|
tasks = [
|
||||||
self.sys_create_task(_load_backup(tar_file))
|
self.sys_create_task(_load_backup(tar_file))
|
||||||
for path in self.backup_locations
|
for path in self.backup_locations
|
||||||
for tar_file in path.glob("*.tar")
|
for tar_file in _list_backup_files(path)
|
||||||
]
|
]
|
||||||
|
|
||||||
_LOGGER.info("Found %d backup files", len(tasks))
|
_LOGGER.info("Found %d backup files", len(tasks))
|
||||||
|
@ -381,6 +381,11 @@ class NFSMount(NetworkMount):
|
|||||||
"""What to mount."""
|
"""What to mount."""
|
||||||
return f"{self.server}:{self.path.as_posix()}"
|
return f"{self.server}:{self.path.as_posix()}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self) -> list[str]:
|
||||||
|
"""Options to use to mount."""
|
||||||
|
return super().options + ["soft", "timeo=200"]
|
||||||
|
|
||||||
|
|
||||||
class BindMount(Mount):
|
class BindMount(Mount):
|
||||||
"""A bind type mount."""
|
"""A bind type mount."""
|
||||||
|
@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, PropertyMock, patch
|
|||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
from dbus_fast import DBusError
|
from dbus_fast import DBusError
|
||||||
|
import pytest
|
||||||
|
|
||||||
from supervisor.addons.addon import Addon
|
from supervisor.addons.addon import Addon
|
||||||
from supervisor.backups.backup import Backup
|
from supervisor.backups.backup import Backup
|
||||||
@ -662,3 +663,36 @@ async def test_backup_to_default(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert (mount_dir / f"{backup.slug}.tar").exists()
|
assert (mount_dir / f"{backup.slug}.tar").exists()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_load_network_error(
|
||||||
|
coresys: CoreSys,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
tmp_supervisor_data,
|
||||||
|
path_extern,
|
||||||
|
mount_propagation,
|
||||||
|
):
|
||||||
|
"""Test load of backup manager when there is a network error."""
|
||||||
|
(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)
|
||||||
|
caplog.clear()
|
||||||
|
|
||||||
|
# This should not raise, manager should just ignore backup locations with errors
|
||||||
|
mock_path = MagicMock()
|
||||||
|
mock_path.glob.side_effect = OSError("Host is down")
|
||||||
|
mock_path.as_posix.return_value = "/data/backup_test"
|
||||||
|
with patch.object(Mount, "local_where", new=PropertyMock(return_value=mock_path)):
|
||||||
|
await coresys.backups.load()
|
||||||
|
|
||||||
|
assert "Could not list backups from /data/backup_test" in caplog.text
|
||||||
|
@ -126,6 +126,7 @@ async def test_load(
|
|||||||
"mnt-data-supervisor-mounts-media_test.mount",
|
"mnt-data-supervisor-mounts-media_test.mount",
|
||||||
"fail",
|
"fail",
|
||||||
[
|
[
|
||||||
|
["Options", Variant("s", "soft,timeo=200")],
|
||||||
["Type", Variant("s", "nfs")],
|
["Type", Variant("s", "nfs")],
|
||||||
["Description", Variant("s", "Supervisor nfs mount: media_test")],
|
["Description", Variant("s", "Supervisor nfs mount: media_test")],
|
||||||
["What", Variant("s", "media.local:/media")],
|
["What", Variant("s", "media.local:/media")],
|
||||||
@ -189,6 +190,7 @@ async def test_load_share_mount(
|
|||||||
"mnt-data-supervisor-mounts-share_test.mount",
|
"mnt-data-supervisor-mounts-share_test.mount",
|
||||||
"fail",
|
"fail",
|
||||||
[
|
[
|
||||||
|
["Options", Variant("s", "soft,timeo=200")],
|
||||||
["Type", Variant("s", "nfs")],
|
["Type", Variant("s", "nfs")],
|
||||||
["Description", Variant("s", "Supervisor nfs mount: share_test")],
|
["Description", Variant("s", "Supervisor nfs mount: share_test")],
|
||||||
["What", Variant("s", "share.local:/share")],
|
["What", Variant("s", "share.local:/share")],
|
||||||
|
@ -114,7 +114,7 @@ async def test_nfs_mount(
|
|||||||
assert mount.what == "test.local:/media/camera"
|
assert mount.what == "test.local:/media/camera"
|
||||||
assert mount.where == Path("/mnt/data/supervisor/mounts/test")
|
assert mount.where == Path("/mnt/data/supervisor/mounts/test")
|
||||||
assert mount.local_where == tmp_supervisor_data / "mounts" / "test"
|
assert mount.local_where == tmp_supervisor_data / "mounts" / "test"
|
||||||
assert mount.options == ["port=1234"]
|
assert mount.options == ["port=1234", "soft", "timeo=200"]
|
||||||
|
|
||||||
assert not mount.local_where.exists()
|
assert not mount.local_where.exists()
|
||||||
assert mount.to_dict() == mount_data
|
assert mount.to_dict() == mount_data
|
||||||
@ -130,7 +130,7 @@ async def test_nfs_mount(
|
|||||||
"mnt-data-supervisor-mounts-test.mount",
|
"mnt-data-supervisor-mounts-test.mount",
|
||||||
"fail",
|
"fail",
|
||||||
[
|
[
|
||||||
["Options", Variant("s", "port=1234")],
|
["Options", Variant("s", "port=1234,soft,timeo=200")],
|
||||||
["Type", Variant("s", "nfs")],
|
["Type", Variant("s", "nfs")],
|
||||||
["Description", Variant("s", "Supervisor nfs mount: test")],
|
["Description", Variant("s", "Supervisor nfs mount: test")],
|
||||||
["What", Variant("s", "test.local:/media/camera")],
|
["What", Variant("s", "test.local:/media/camera")],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user