Improve handling of NFS mounts and backup manager errors (#4323)

This commit is contained in:
Mike Degatano 2023-05-30 17:29:51 -04:00 committed by GitHub
parent e449205863
commit 73d795e05e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 3 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from collections.abc import Iterable
import logging
from pathlib import Path
@ -29,6 +30,15 @@ from .validate import ALL_FOLDERS, SCHEMA_BACKUPS_CONFIG
_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):
"""Manage backups."""
@ -119,7 +129,7 @@ class BackupManager(FileConfiguration, CoreSysAttributes):
tasks = [
self.sys_create_task(_load_backup(tar_file))
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))

View File

@ -381,6 +381,11 @@ class NFSMount(NetworkMount):
"""What to mount."""
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):
"""A bind type mount."""

View File

@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, PropertyMock, patch
from awesomeversion import AwesomeVersion
from dbus_fast import DBusError
import pytest
from supervisor.addons.addon import Addon
from supervisor.backups.backup import Backup
@ -662,3 +663,36 @@ async def test_backup_to_default(
)
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

View File

@ -126,6 +126,7 @@ async def test_load(
"mnt-data-supervisor-mounts-media_test.mount",
"fail",
[
["Options", Variant("s", "soft,timeo=200")],
["Type", Variant("s", "nfs")],
["Description", Variant("s", "Supervisor nfs mount: media_test")],
["What", Variant("s", "media.local:/media")],
@ -189,6 +190,7 @@ async def test_load_share_mount(
"mnt-data-supervisor-mounts-share_test.mount",
"fail",
[
["Options", Variant("s", "soft,timeo=200")],
["Type", Variant("s", "nfs")],
["Description", Variant("s", "Supervisor nfs mount: share_test")],
["What", Variant("s", "share.local:/share")],

View File

@ -114,7 +114,7 @@ async def test_nfs_mount(
assert mount.what == "test.local:/media/camera"
assert mount.where == Path("/mnt/data/supervisor/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 mount.to_dict() == mount_data
@ -130,7 +130,7 @@ async def test_nfs_mount(
"mnt-data-supervisor-mounts-test.mount",
"fail",
[
["Options", Variant("s", "port=1234")],
["Options", Variant("s", "port=1234,soft,timeo=200")],
["Type", Variant("s", "nfs")],
["Description", Variant("s", "Supervisor nfs mount: test")],
["What", Variant("s", "test.local:/media/camera")],