diff --git a/backup.py b/backup.py index 30c9e01..68257a0 100755 --- a/backup.py +++ b/backup.py @@ -17,7 +17,7 @@ def main(): verify_running_as_root() local_pool = Pool(config.local_pool_to_backup) external_pool = ExternalPool.find_from_dict(config.backup_pools) - manager = Manager(local_pool, external_pool) + manager = Manager(local_pool, external_pool, config.recent_scrub_timedelta) manager.backup() diff --git a/zfs_backup/commands/__init__.py b/zfs_backup/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zfs_backup/commands.py b/zfs_backup/commands/_core.py similarity index 98% rename from zfs_backup/commands.py rename to zfs_backup/commands/_core.py index cd734f3..4f80c68 100644 --- a/zfs_backup/commands.py +++ b/zfs_backup/commands/_core.py @@ -3,7 +3,7 @@ from __future__ import annotations from pathlib import Path from subprocess import Popen, call, check_output, PIPE -from misc import assert_type +from zfs_backup.misc import assert_type class CommandInterface: diff --git a/zfs_backup/cryptsetup.py b/zfs_backup/commands/cryptsetup.py similarity index 94% rename from zfs_backup/cryptsetup.py rename to zfs_backup/commands/cryptsetup.py index 733074b..57ec893 100644 --- a/zfs_backup/cryptsetup.py +++ b/zfs_backup/commands/cryptsetup.py @@ -2,9 +2,9 @@ from __future__ import annotations from pathlib import Path -from .messages import MAPPER_ENTRY_ALREADY_EXISTS, CANNOT_FIND_DEVICE_PATH -from .commands import CommandInterface -from .misc import DISK_BY_UUID, MAPPER_PATH +from ._core import CommandInterface +from ..messages import MAPPER_ENTRY_ALREADY_EXISTS, CANNOT_FIND_DEVICE_PATH +from ..misc import MAPPER_PATH, DISK_BY_UUID class Cryptsetup(CommandInterface): diff --git a/zfs_backup/zfs.py b/zfs_backup/commands/zfs.py similarity index 92% rename from zfs_backup/zfs.py rename to zfs_backup/commands/zfs.py index 528ab81..c99a599 100644 --- a/zfs_backup/zfs.py +++ b/zfs_backup/commands/zfs.py @@ -3,14 +3,13 @@ from __future__ import annotations from pathlib import Path from subprocess import Popen -import config -from .messages import DELETE_IN_LOCAL_POOL, SAME_DATASET -from .misc import assert_type -from .commands import CommandInterface -from .snapshot import Snapshot -from .dataset import Dataset -from .pool import Pool -from .zfs_path import ZFSPath +from .. import Pool +from ._core import CommandInterface +from ..dataset import Dataset +from ..messages import SAME_DATASET +from ..misc import assert_type +from ..snapshot import Snapshot +from ..zfs_path import ZFSPath class ZFS(CommandInterface): @@ -65,8 +64,6 @@ class ZFS(CommandInterface): def destroy_snapshot(cls, snapshot: Snapshot) -> None: """Non-recursively destroy a snapshot.""" assert_type(snapshot, Snapshot, "Can only destroy snapshots.") - if config.local_pool_to_backup == snapshot.pool: - raise Exception(DELETE_IN_LOCAL_POOL) cls._run_command(*cls._Subcommands.destroy, str(snapshot)) @classmethod diff --git a/zfs_backup/zpool.py b/zfs_backup/commands/zpool.py similarity index 81% rename from zfs_backup/zpool.py rename to zfs_backup/commands/zpool.py index 45735e0..9bb1ba4 100644 --- a/zfs_backup/zpool.py +++ b/zfs_backup/commands/zpool.py @@ -1,12 +1,12 @@ from __future__ import annotations -from datetime import datetime, timedelta +from datetime import timedelta, datetime from pathlib import Path -from .misc import assert_type -from .commands import CommandInterface -from .pool import Pool -from .time import Time +from ._core import CommandInterface +from ..pool import Pool +from ..misc import assert_type +from ..time import Time class ZPool(CommandInterface): @@ -33,8 +33,9 @@ class ZPool(CommandInterface): assert_type(pool, Pool, "Can only export Pool objects") cls._run_command(*cls._Subcommands.export, pool.name) + @classmethod def scrub( - self, + cls, pool: Pool, *, skip_if_recently_scrubbed: bool, @@ -45,16 +46,15 @@ class ZPool(CommandInterface): Waits for the scrub to finish. Raise an IOError if the pool reports as not healthy after the scrub.""" assert_type(pool, Pool, "Can only scrub pools.") - self._scrub_if_necessary( - pool, skip_if_recently_scrubbed, recent_scrub_timedelta - ) + cls._scrub_if_necessary(pool, skip_if_recently_scrubbed, recent_scrub_timedelta) if wait_for_finish: - self._run_command(*self._Subcommands.wait, pool.name) - if not self._is_healthy(pool): + cls._run_command(*cls._Subcommands.wait, pool.name) + if not cls._is_healthy(pool): raise IOError(f"Pool {pool.name} is not healthy.") + @classmethod def _scrub_if_necessary( - self, + cls, pool: Pool, skip_if_recently_scrubbed: bool, recent_scrub_timedelta: timedelta, @@ -62,14 +62,14 @@ class ZPool(CommandInterface): """Scrub the pool. If""" def do_scrub(): - self._run_command(*self._Subcommands.scrub, pool.name) + cls._run_command(*cls._Subcommands.scrub, pool.name) - if self._scrub_in_progress(pool): + if cls._scrub_in_progress(pool): return if not skip_if_recently_scrubbed: do_scrub() else: - if not self._recently_scrubbed(pool, recent_scrub_timedelta): + if not cls._recently_scrubbed(pool, recent_scrub_timedelta): do_scrub() @classmethod diff --git a/zfs_backup/core.py b/zfs_backup/core.py index 48c72a8..299fe2f 100644 --- a/zfs_backup/core.py +++ b/zfs_backup/core.py @@ -1,18 +1,25 @@ from __future__ import annotations +from datetime import timedelta from subprocess import Popen import config from dataset import Dataset from pool import Pool, ExternalPool from snapshot import Snapshot -from zfs import ZFS +from zfs_backup.commands.zfs import ZFS class Manager: """Controls the overall backup process.""" - def __init__(self, local_pool: Pool, external_pool: ExternalPool): + def __init__( + self, + local_pool: Pool, + external_pool: ExternalPool, + recent_scrub_timedelta: timedelta, + ): + self._recent_scrub_timedelta = recent_scrub_timedelta self._local_pool = local_pool self._external_pool = external_pool @@ -20,10 +27,16 @@ class Manager: """Backup workflow.""" self._external_pool.import_() try: - self._external_pool.scrub(skip_if_recently_scrubbed=True) + self._external_pool.scrub( + skip_if_recently_scrubbed=True, + recent_scrub_timedelta=self._recent_scrub_timedelta, + ) self._backup_all_datasets() self._external_pool.clean_old_snapshots() - self._external_pool.scrub(skip_if_recently_scrubbed=False) + self._external_pool.scrub( + skip_if_recently_scrubbed=False, + recent_scrub_timedelta=self._recent_scrub_timedelta, + ) finally: self._external_pool.export() diff --git a/zfs_backup/dataset.py b/zfs_backup/dataset.py index f4084b1..2b1c2a2 100644 --- a/zfs_backup/dataset.py +++ b/zfs_backup/dataset.py @@ -1,7 +1,7 @@ from __future__ import annotations import config -from zfs import ZFS +from zfs_backup.commands.zfs import ZFS from zfs_backup.core import _get_regex_matching_snapshots_with_tags from snapshot import Snapshot from zfs_path import ZFSPath diff --git a/zfs_backup/disk.py b/zfs_backup/disk.py index 330a325..d235a2c 100644 --- a/zfs_backup/disk.py +++ b/zfs_backup/disk.py @@ -4,7 +4,7 @@ from pathlib import Path from uuid import UUID from .messages import ALREADY_DECRYPTED, NOT_DECRYPTED -from .cryptsetup import Cryptsetup +from .commands.cryptsetup import Cryptsetup from .misc import DISK_BY_UUID diff --git a/zfs_backup/pool.py b/zfs_backup/pool.py index fa787f4..b49cd24 100644 --- a/zfs_backup/pool.py +++ b/zfs_backup/pool.py @@ -1,14 +1,15 @@ from __future__ import annotations +from datetime import timedelta from uuid import UUID from .disk import Disk from .messages import CANNOT_FIND_BACKUP_DRIVE, ALREADY_IMPORTED, NOT_IMPORTED from .misc import MAPPER_PATH, DISK_BY_UUID -from .zfs import ZFS +from .commands.zfs import ZFS from .dataset import Dataset from .zfs_path import ZFSPath -from .zpool import ZPool +from .commands.zpool import ZPool class Pool: @@ -52,9 +53,15 @@ class ExternalPool(Pool): self._imported = False print(f"Exported {self.name}. Disk {self._disk.uuid} can be removed.") - def scrub(self, skip_if_recently_scrubbed: bool) -> None: + def scrub( + self, skip_if_recently_scrubbed: bool, recent_scrub_timedelta: timedelta + ) -> None: print(f"Scrubbing {self._name}") - ZPool.scrub(self, skip_if_recently_scrubbed=skip_if_recently_scrubbed) + ZPool.scrub( + self, + skip_if_recently_scrubbed=skip_if_recently_scrubbed, + recent_scrub_timedelta=recent_scrub_timedelta, + ) @classmethod def find_from_dict(cls, backup_pools: dict[str, UUID]) -> ExternalPool: diff --git a/zfs_backup/snapshot.py b/zfs_backup/snapshot.py index 797788e..8fafdf3 100644 --- a/zfs_backup/snapshot.py +++ b/zfs_backup/snapshot.py @@ -3,7 +3,7 @@ from __future__ import annotations from re import search from dataset import Dataset -from zfs import ZFS +from zfs_backup.commands.zfs import ZFS from zfs_path import ZFSPath