add: skip scrub if recently scrubbed
This commit is contained in:
+61
-10
@@ -3,6 +3,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from functools import cache
|
||||
from os import getuid
|
||||
from pathlib import Path
|
||||
@@ -61,10 +62,10 @@ class Manager:
|
||||
def backup(self):
|
||||
self._external_pool.import_()
|
||||
try:
|
||||
self._external_pool.scrub()
|
||||
self._external_pool.scrub(skip_if_recently_scrubbed=True)
|
||||
self._backup_all_datasets()
|
||||
self._external_pool.clean_old_snapshots()
|
||||
self._external_pool.scrub()
|
||||
self._external_pool.scrub(skip_if_recently_scrubbed=False)
|
||||
finally:
|
||||
self._external_pool.export()
|
||||
|
||||
@@ -280,28 +281,78 @@ class ZFS:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def scrub(cls, pool: Pool, *, wait_for_finish: bool = True) -> None:
|
||||
def scrub(cls, pool: Pool, *, skip_if_recently_scrubbed: bool, wait_for_finish: bool = True) -> None:
|
||||
if not isinstance(pool, Pool):
|
||||
raise TypeError("Can only scrub pools.")
|
||||
if not cls._scrub_in_progress(pool):
|
||||
Command.run([config.ZPOOL, "scrub", pool.name])
|
||||
cls._scrub_if_necessary(pool, skip_if_recently_scrubbed)
|
||||
if wait_for_finish:
|
||||
Command.run([config.ZPOOL, "wait", "-t", "scrub", pool.name])
|
||||
if not cls._healthy(pool):
|
||||
raise IOError(f"Pool {pool.name} is not healthy.")
|
||||
|
||||
@classmethod
|
||||
def _scrub_if_necessary(cls, pool: Pool, skip_if_recently_scrubbed: bool) -> None:
|
||||
if cls._scrub_in_progress(pool):
|
||||
return
|
||||
if not skip_if_recently_scrubbed:
|
||||
Command.run([config.ZPOOL, "scrub", pool.name])
|
||||
else:
|
||||
if not cls._recently_scrubbed(pool):
|
||||
Command.run([config.ZPOOL, "scrub", pool.name])
|
||||
|
||||
@classmethod
|
||||
def _recently_scrubbed(cls, pool):
|
||||
return datetime.now() - cls._last_scrub(pool) < config.recent_scrub_timedelta
|
||||
|
||||
@classmethod
|
||||
def _scrub_in_progress(cls, pool: Pool) -> bool:
|
||||
return "scrub in progress" in cls._pool_status_output(pool)
|
||||
return "scrub in progress" in cls._get_pool_status_output(pool)
|
||||
|
||||
@classmethod
|
||||
def _healthy(cls, pool: Pool) -> bool:
|
||||
return "ONLINE" in cls._pool_status_output(pool)
|
||||
return "ONLINE" in cls._get_pool_status_output(pool)
|
||||
|
||||
@staticmethod
|
||||
def _pool_status_output(pool):
|
||||
def _get_pool_status_output(pool) -> str:
|
||||
return Command.get_output([config.ZFS, "status", pool.name])
|
||||
|
||||
@classmethod
|
||||
def _last_scrub(cls, pool: Pool) -> datetime:
|
||||
return Time.get_last_scrub_from_status_output(cls._get_pool_status_output(pool))
|
||||
|
||||
|
||||
class Time:
|
||||
@staticmethod
|
||||
def _get_date_format() -> str:
|
||||
"""date format as used by zpool status
|
||||
|
||||
%a: day of week, short version
|
||||
%b: month, short version
|
||||
%e: day of month, space-padded
|
||||
%H, %M, %S: hour, minute, second, zero-padded
|
||||
%Y: year
|
||||
"""
|
||||
return "%a %b %e %H:%M:%S %Y"
|
||||
|
||||
@classmethod
|
||||
def _extract_datetime_segment(cls, line_with_datetime: str) -> str:
|
||||
return line_with_datetime.split(" errors on ")[-1]
|
||||
|
||||
@classmethod
|
||||
def _get_line_with_datetime_from_status_output(cls, output: str) -> str:
|
||||
lines = output.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith(" scan: scrub repaired"):
|
||||
return line.strip()
|
||||
else:
|
||||
raise ValueError("Could not find the correct line")
|
||||
|
||||
@classmethod
|
||||
def get_last_scrub_from_status_output(cls, output: str) -> datetime:
|
||||
line_with_datetime = cls._get_line_with_datetime_from_status_output(output)
|
||||
date_str = cls._extract_datetime_segment(line_with_datetime)
|
||||
return datetime.strptime(date_str, cls._get_date_format())
|
||||
|
||||
|
||||
class Disk:
|
||||
def __init__(self, uuid: UUID, name: str):
|
||||
@@ -380,9 +431,9 @@ class ExternalPool(Pool):
|
||||
# TODO cancel scrub if necessary
|
||||
ZFS.export_pool(self._name)
|
||||
|
||||
def scrub(self) -> None:
|
||||
def scrub(self, skip_if_recently_scrubbed: bool) -> None:
|
||||
print(f"Scrubbing {self._name}")
|
||||
ZFS.scrub(self)
|
||||
ZFS.scrub(self, skip_if_recently_scrubbed=skip_if_recently_scrubbed)
|
||||
|
||||
@classmethod
|
||||
def find_from_dict(cls, backup_pools: dict[str, UUID]) -> ExternalPool:
|
||||
|
||||
Reference in New Issue
Block a user