add: skip scrub if recently scrubbed
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
# Name des lokalen Pools
|
# Name des lokalen Pools
|
||||||
local_pool_to_backup = "tank"
|
local_pool_to_backup = "tank"
|
||||||
@@ -16,6 +17,8 @@ do_not_backup = [
|
|||||||
"tank/docker",
|
"tank/docker",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
recent_scrub_timedelta = timedelta(hours=24)
|
||||||
|
|
||||||
# überprüfe ob neue Snapshots mit diesen String-Teilen vorliegen
|
# überprüfe ob neue Snapshots mit diesen String-Teilen vorliegen
|
||||||
snapshot_tag = "znap"
|
snapshot_tag = "znap"
|
||||||
snapshot_interval = "monthly"
|
snapshot_interval = "monthly"
|
||||||
|
|||||||
+61
-10
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from functools import cache
|
from functools import cache
|
||||||
from os import getuid
|
from os import getuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -61,10 +62,10 @@ class Manager:
|
|||||||
def backup(self):
|
def backup(self):
|
||||||
self._external_pool.import_()
|
self._external_pool.import_()
|
||||||
try:
|
try:
|
||||||
self._external_pool.scrub()
|
self._external_pool.scrub(skip_if_recently_scrubbed=True)
|
||||||
self._backup_all_datasets()
|
self._backup_all_datasets()
|
||||||
self._external_pool.clean_old_snapshots()
|
self._external_pool.clean_old_snapshots()
|
||||||
self._external_pool.scrub()
|
self._external_pool.scrub(skip_if_recently_scrubbed=False)
|
||||||
finally:
|
finally:
|
||||||
self._external_pool.export()
|
self._external_pool.export()
|
||||||
|
|
||||||
@@ -280,28 +281,78 @@ class ZFS:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@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):
|
if not isinstance(pool, Pool):
|
||||||
raise TypeError("Can only scrub pools.")
|
raise TypeError("Can only scrub pools.")
|
||||||
if not cls._scrub_in_progress(pool):
|
cls._scrub_if_necessary(pool, skip_if_recently_scrubbed)
|
||||||
Command.run([config.ZPOOL, "scrub", pool.name])
|
|
||||||
if wait_for_finish:
|
if wait_for_finish:
|
||||||
Command.run([config.ZPOOL, "wait", "-t", "scrub", pool.name])
|
Command.run([config.ZPOOL, "wait", "-t", "scrub", pool.name])
|
||||||
if not cls._healthy(pool):
|
if not cls._healthy(pool):
|
||||||
raise IOError(f"Pool {pool.name} is not healthy.")
|
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
|
@classmethod
|
||||||
def _scrub_in_progress(cls, pool: Pool) -> bool:
|
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
|
@classmethod
|
||||||
def _healthy(cls, pool: Pool) -> bool:
|
def _healthy(cls, pool: Pool) -> bool:
|
||||||
return "ONLINE" in cls._pool_status_output(pool)
|
return "ONLINE" in cls._get_pool_status_output(pool)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _pool_status_output(pool):
|
def _get_pool_status_output(pool) -> str:
|
||||||
return Command.get_output([config.ZFS, "status", pool.name])
|
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:
|
class Disk:
|
||||||
def __init__(self, uuid: UUID, name: str):
|
def __init__(self, uuid: UUID, name: str):
|
||||||
@@ -380,9 +431,9 @@ class ExternalPool(Pool):
|
|||||||
# TODO cancel scrub if necessary
|
# TODO cancel scrub if necessary
|
||||||
ZFS.export_pool(self._name)
|
ZFS.export_pool(self._name)
|
||||||
|
|
||||||
def scrub(self) -> None:
|
def scrub(self, skip_if_recently_scrubbed: bool) -> None:
|
||||||
print(f"Scrubbing {self._name}")
|
print(f"Scrubbing {self._name}")
|
||||||
ZFS.scrub(self)
|
ZFS.scrub(self, skip_if_recently_scrubbed=skip_if_recently_scrubbed)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_from_dict(cls, backup_pools: dict[str, UUID]) -> ExternalPool:
|
def find_from_dict(cls, backup_pools: dict[str, UUID]) -> ExternalPool:
|
||||||
|
|||||||
Reference in New Issue
Block a user