add: skip scrub if recently scrubbed

This commit is contained in:
timeshifter
2021-12-02 16:20:55 +01:00
parent 359a6a99a8
commit a4e9b12c4c
2 changed files with 64 additions and 10 deletions
+61 -10
View File
@@ -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: