more cleanup

This commit is contained in:
timeshifter
2021-11-26 19:16:30 +01:00
parent cf6fc81c96
commit b57e2c66b4
+90 -58
View File
@@ -48,13 +48,60 @@ class Manager:
self._external_pool.export()
def _backup_all_datasets(self):
for local_dataset in self._local_pool.datasets_to_backup:
remote_dataset = self._external_pool.search_dataset_like(local_dataset)
last_common_snapshot = get_last_common_snapshot(remote_dataset, local_dataset)
last_local_snapshot: Snapshot = local_dataset.last_snapshot
sender = ZFS.send(last_common_snapshot, last_local_snapshot)
for local_dataset in self._datasets_to_backup:
remote_dataset = self._search_matching_dataset_in_remote_pool(
local_dataset, self._external_pool
)
start_snapshot = self._get_last_common_snapshot(
remote_dataset, local_dataset
)
end_snapshot = self._get_last_snapshot_with_backup_tag(local_dataset)
sender = ZFS.send_incremental(start_snapshot, end_snapshot)
ZFS.receive(sender, self._external_pool.name)
def _get_last_snapshot_with_backup_tag(self, local_dataset):
snapshots = self._find_snapshots_with_backup_tag(local_dataset)
snapshots.sort()
end_snapshot = snapshots[-1]
return end_snapshot
@property
def _datasets_to_backup(self) -> list[Dataset]:
return [
dataset
for dataset in self._local_pool.datasets
if dataset.qualified_name not in config.do_not_backup
]
@staticmethod
def _find_snapshots_with_backup_tag(dataset: Dataset) -> list[Snapshot]:
regex = Manager._get_regex_matching_backup_tags()
return [
snapshot for snapshot in dataset.snapshots if snapshot.matches_regex(regex)
]
@staticmethod
def _get_regex_matching_backup_tags():
return "@" + ".*".join([config.snapshot_tag, config.snapshot_interval])
@staticmethod
def _search_matching_dataset_in_remote_pool(dataset: Dataset, pool: Pool):
dataset_to_search = dataset.replace_pool(pool.name)
for dataset in pool.datasets:
if dataset == dataset_to_search:
return dataset
@classmethod
def _get_last_common_snapshot(cls, ds1: Dataset, ds2: Dataset) -> Snapshot:
common_snapshots = [
local_snap
for local_snap in list(cls._find_snapshots_with_backup_tag(ds1))
for remote_snap in list(cls._find_snapshots_with_backup_tag(ds2))
if local_snap.snapname == remote_snap.snapname
]
common_snapshots.sort()
return common_snapshots[-1]
class ZFSPath:
def __init__(self, elements: list[str], snapname: Optional[str] = None):
@@ -74,6 +121,13 @@ class ZFSPath:
def __getitem__(self, item):
return self._qualified_name_as_list[item]
def __eq__(self, other: ZFSPath):
return (
self._elements == other._elements
and self._is_snapshot == other._is_snapshot
and self._snapname == other._snapname
)
@property
def _qualified_name_as_list(self) -> list[str]:
if self.is_snapshot:
@@ -96,8 +150,16 @@ class ZFSPath:
def is_snapshot(self) -> bool:
return self._is_snapshot
@property
def pool(self) -> str:
return self._elements[0]
@property
def dataset(self) -> ZFSPath:
return ZFSPath(self._elements)
def replace_pool(self, pool: str) -> ZFSPath:
return ZFSPath([pool] + self._elements[1:], self.snapname)
return ZFSPath([pool] + self._elements[1:].copy(), self.snapname)
@property
def name_without_pool(self) -> str:
@@ -176,7 +238,11 @@ class ZFS:
run_command(cmdline)
@staticmethod
def send(old_snap: Snapshot, new_snap: Snapshot) -> Popen[str]:
def send_incremental(old_snap: Snapshot, new_snap: Snapshot) -> Popen[str]:
if old_snap.dataset != new_snap.dataset:
raise ValueError(
"Cannot send incremental snapshots if start and end snapshot are not based on the same dataset"
)
sender = Popen(
[config.ZFS, "send", "-R", "-I", old_snap, new_snap], stdout=PIPE
)
@@ -236,17 +302,6 @@ class Disk:
self._decrypted = False
def get_last_common_snapshot(ds1: Dataset, ds2: Dataset) -> Snapshot:
common_snapshots = [
local_snap
for local_snap in ds1.snapshots_matching_backup_tags
for remote_snap in ds2.snapshots_matching_backup_tags
if local_snap.snapname == remote_snap.snapname
]
common_snapshots.sort()
return common_snapshots[-1]
class Pool:
def __init__(self, name: str):
self._name = name
@@ -261,12 +316,8 @@ class Pool:
def name(self) -> str:
return self._name
@property
@cache
def datasets_to_backup(self) -> list[Dataset]:
return [
d for d in self.datasets if d.qualified_name not in config.do_not_backup
]
# def datasets_to_backup(self, do_not_backup: list[str]) -> list[Dataset]:
# return [d for d in self.datasets if d.qualified_name not in do_not_backup]
class ExternalPool(Pool):
@@ -310,25 +361,17 @@ class ExternalPool(Pool):
print("Could not find a backup drive")
exit(1)
@property
@cache
def datasets_to_backup(self) -> list[Dataset]:
def replace_pool_name(old_name: str):
"/".join([self._name, old_name.split("/", maxsplit=1)[1]])
do_not_backup = [replace_pool_name(item) for item in config.do_not_backup]
return [d for d in self.datasets if d.qualified_name not in do_not_backup]
# def datasets_to_backup(self, do_not_backup: list[str]) -> list[Dataset]:
# def replace_pool_name(old_name: str):
# "/".join([self._name, old_name.split("/", maxsplit=1)[1]])
#
# do_not_backup_correct_name = [replace_pool_name(item) for item in do_not_backup]
# return [d for d in self.datasets if d.qualified_name not in do_not_backup_correct_name]
def clean_old_snapshots(self):
for dataset in self.datasets:
dataset.clean_old_snapshots()
def search_dataset_like(self, remote_dataset: Dataset) -> Dataset:
dataset_to_search = remote_dataset.replace_pool(self.name)
for dataset in self.datasets:
if dataset == dataset_to_search:
return dataset
class Dataset:
def __init__(self, zfs_path: ZFSPath):
@@ -338,7 +381,7 @@ class Dataset:
return self.qualified_name
def __eq__(self, other: Dataset):
return str(self) == str(other)
return self._path == other._path
@classmethod
def from_string(cls, qualified_name: str) -> Dataset:
@@ -357,20 +400,6 @@ class Dataset:
"""get qualified snapshot names that are direct children of the dataset"""
return ZFS.get_snapshots(self.qualified_name)
@property
def snapshots_matching_backup_tags(self) -> list[Snapshot]:
snapshots = [
snapshot for snapshot in self.snapshots if snapshot.matches_backup_tags
]
snapshots.sort()
return snapshots
@property
def last_snapshot(self) -> Snapshot:
for snapshot in self.snapshots[::-1]:
if snapshot.matches_backup_tags:
return snapshot
def replace_pool(self, name: str) -> Dataset:
return Dataset(self._path.replace_pool(name))
@@ -404,17 +433,20 @@ class Snapshot:
def snapname(self) -> str:
return self._path.snapname
@property
def matches_backup_tags(self) -> bool:
regex = "@" + ".*".join([config.snapshot_tag, config.snapshot_interval])
return self.matches_regex(regex)
def matches_regex(self, regex: str) -> bool:
return bool(re.search(regex, str(self._path)))
def destroy(self):
ZFS.destroy(str(self))
@property
def pool(self) -> str:
return self._path.pool
@property
def dataset(self) -> Dataset:
return Dataset(self._path.dataset)
def run_command(cmdline: list[str]):
subprocess.call(cmdline)