From d22108d20af2a93fd806956ffbbd50d59d226217 Mon Sep 17 00:00:00 2001 From: timeshifter Date: Sat, 4 Dec 2021 23:07:09 +0100 Subject: [PATCH] add: docstrings in Manager --- zfs-backup.py | 49 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/zfs-backup.py b/zfs-backup.py index 6ed3584..02b594c 100755 --- a/zfs-backup.py +++ b/zfs-backup.py @@ -48,6 +48,8 @@ def main(): class Manager: + """Controls the overall backup process.""" + _map_type: dict[Dataset, Dataset | None] def __init__(self, local_pool: Pool, external_pool: ExternalPool): @@ -55,6 +57,7 @@ class Manager: self._external_pool = external_pool def backup(self): + """Backup workflow.""" self._external_pool.import_() try: self._external_pool.scrub(skip_if_recently_scrubbed=True) @@ -65,18 +68,26 @@ class Manager: self._external_pool.export() def _backup_all_datasets(self): + """Send snapshots of local datasets to the backup pool.""" print("Backing up datasets") - for local_dataset, remote_dataset in self._get_dataset_backup_map().items(): + for ( + local_dataset, + remote_dataset, + ) in self._get_user_filtered_dataset_backup_map().items(): if remote_dataset is None: stream = self._send_absolute(local_dataset) else: stream = self._send_incremental(local_dataset, remote_dataset) ZFS.receive(stream, self._external_pool) - def _get_dataset_backup_map(self) -> _map_type: + def _get_user_filtered_dataset_backup_map(self) -> _map_type: return self._user_confirm_new_datasets(self._get_raw_dataset_map()) def _user_confirm_new_datasets(self, dataset_map: _map_type) -> _map_type: + """For datasets that exist only locally, ask the user if he wants to back it up. + + If he doesn't, remove it from the dict. + """ return { local_dataset: remote_dataset for local_dataset, remote_dataset in dataset_map.items() @@ -84,6 +95,10 @@ class Manager: } def _get_raw_dataset_map(self) -> _map_type: + """Return map from local to remote datasets. + + If the local dataset has no remote match, set the remote dataset to None. + """ return { local_dataset: self._search_matching_dataset_in_remote_pool( local_dataset, self._external_pool @@ -94,23 +109,29 @@ class Manager: def _send_incremental( self, local_dataset: Dataset, remote_dataset: Dataset ) -> Popen: - start_snapshot = self._get_last_common_snapshot(local_dataset, remote_dataset) - end_snapshot = self._get_last_snapshot_with_backup_tag(local_dataset) + """Send incremental stream between two datasets to remote.""" + start_snapshot = self._get_newest_common_snapshot_with_backup_tags( + local_dataset, remote_dataset + ) + end_snapshot = self._get_newest_snapshot_with_backup_tag(local_dataset) stream = ZFS.send_incremental(start_snapshot, end_snapshot) return stream def _send_absolute(self, local_dataset: Dataset) -> Popen: - snapshot = self._get_last_snapshot_with_backup_tag(local_dataset) + """Send absolute stream to remote.""" + snapshot = self._get_newest_snapshot_with_backup_tag(local_dataset) return ZFS.send_absolute(snapshot) - def _get_last_snapshot_with_backup_tag(self, local_dataset): - snapshots = self._find_snapshots_with_backup_tag(local_dataset) + def _get_newest_snapshot_with_backup_tag(self, dataset: Dataset) -> Snapshot: + """From all snapshots in the dataset, get the newest one that matches the backup tags in the config.""" + snapshots = self._find_snapshots_with_backup_tag(dataset) snapshots.sort() end_snapshot = snapshots[-1] return end_snapshot @property def _datasets_to_backup(self) -> list[Dataset]: + """Return local datasets to consider for a backup.""" return [ dataset for dataset in self._local_pool.datasets @@ -119,6 +140,7 @@ class Manager: @staticmethod def _find_snapshots_with_backup_tag(dataset: Dataset) -> list[Snapshot]: + """Find snapshots of a dataset that carry the backup tags.""" regex = _get_regex_matching_snapshots_with_tags( [config.snapshot_tag, config.snapshot_interval] ) @@ -130,15 +152,22 @@ class Manager: def _search_matching_dataset_in_remote_pool( dataset: Dataset, pool: Pool ) -> Dataset | None: + """For a dataset in the local pool, get the matching dataset in the remote pool. + + If there is no match, return None. + """ 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( + def _get_newest_common_snapshot_with_backup_tags( cls, local_dataset: Dataset, remote_dataset: Dataset ) -> Snapshot: + """In a local and a remote dataset, find the newest snapshot with backup tags which exists in both datasets. + + Return the local snapshot.""" common_snapshots = [ local_snap for local_snap in list(cls._find_snapshots_with_backup_tag(local_dataset)) @@ -287,7 +316,7 @@ class ZPool(CommandInterface): cls._scrub_if_necessary(pool, skip_if_recently_scrubbed) if wait_for_finish: cls._run_command(*cls._Subcommands.wait, pool.name) - if not cls._healthy(pool): + if not cls._is_healthy(pool): raise IOError(f"Pool {pool.name} is not healthy.") @classmethod @@ -312,7 +341,7 @@ class ZPool(CommandInterface): return "scrub in progress" in cls._get_pool_status_output(pool) @classmethod - def _healthy(cls, pool: Pool) -> bool: + def _is_healthy(cls, pool: Pool) -> bool: return "ONLINE" in cls._get_pool_status_output(pool) @classmethod