from __future__ import annotations from datetime import timedelta, datetime from pathlib import Path from ._core import CommandInterface from ..pool import Pool from ..misc import assert_type from ..time import Time class ZPool(CommandInterface): _BINARY = Path("/usr/bin/zpool") class _Subcommands: # -N: no mount # -d: directory to search the pool in import_ = ["import", "-N", "-d"] export = ["export"] scrub = ["scrub"] status = ["status"] wait = ["wait", "-t", "scrub"] @classmethod def import_from_directory(cls, pool: Pool, directory: Path) -> None: """Search `directory` for `pool` and import it.""" assert_type(pool, Pool, "Can only import Pool objects") cls._run_command(*cls._Subcommands.import_, str(directory), pool.name) @classmethod def export(cls, pool: Pool) -> None: """Export the pool.""" assert_type(pool, Pool, "Can only export Pool objects") cls._run_command(*cls._Subcommands.export, pool.name) @classmethod def scrub( cls, pool: Pool, *, skip_if_recently_scrubbed: bool, recent_scrub_timedelta: timedelta, wait_for_finish: bool = True, ) -> None: """Scrub the pool. 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.") cls._scrub_if_necessary(pool, skip_if_recently_scrubbed, recent_scrub_timedelta) if wait_for_finish: 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( cls, pool: Pool, skip_if_recently_scrubbed: bool, recent_scrub_timedelta: timedelta, ) -> None: """Scrub the pool. If""" def do_scrub(): cls._run_command(*cls._Subcommands.scrub, pool.name) if cls._scrub_in_progress(pool): return if not skip_if_recently_scrubbed: do_scrub() else: if not cls._recently_scrubbed(pool, recent_scrub_timedelta): do_scrub() @classmethod def _recently_scrubbed(cls, pool: Pool, recent_scrub_timedelta: timedelta) -> bool: """Is the interval after the last scrub smaller than the minimum timedelta?""" return datetime.now() - cls._last_scrub(pool) < recent_scrub_timedelta @classmethod def _scrub_in_progress(cls, pool: Pool) -> bool: return "scrub in progress" in cls._get_pool_status_output(pool) @classmethod def _is_healthy(cls, pool: Pool) -> bool: return "ONLINE" in cls._get_pool_status_output(pool) @classmethod def _get_pool_status_output(cls, pool: Pool) -> str: """Return output of `zpool status [pool]`.""" return cls._get_output(*cls._Subcommands.status, pool.name) @classmethod def _last_scrub(cls, pool: Pool) -> datetime: """Get time of the last scrub.""" return Time.get_last_scrub_from_status_output(cls._get_pool_status_output(pool))