add: docstrings in Manager
This commit is contained in:
+39
-10
@@ -48,6 +48,8 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
class Manager:
|
class Manager:
|
||||||
|
"""Controls the overall backup process."""
|
||||||
|
|
||||||
_map_type: dict[Dataset, Dataset | None]
|
_map_type: dict[Dataset, Dataset | None]
|
||||||
|
|
||||||
def __init__(self, local_pool: Pool, external_pool: ExternalPool):
|
def __init__(self, local_pool: Pool, external_pool: ExternalPool):
|
||||||
@@ -55,6 +57,7 @@ class Manager:
|
|||||||
self._external_pool = external_pool
|
self._external_pool = external_pool
|
||||||
|
|
||||||
def backup(self):
|
def backup(self):
|
||||||
|
"""Backup workflow."""
|
||||||
self._external_pool.import_()
|
self._external_pool.import_()
|
||||||
try:
|
try:
|
||||||
self._external_pool.scrub(skip_if_recently_scrubbed=True)
|
self._external_pool.scrub(skip_if_recently_scrubbed=True)
|
||||||
@@ -65,18 +68,26 @@ class Manager:
|
|||||||
self._external_pool.export()
|
self._external_pool.export()
|
||||||
|
|
||||||
def _backup_all_datasets(self):
|
def _backup_all_datasets(self):
|
||||||
|
"""Send snapshots of local datasets to the backup pool."""
|
||||||
print("Backing up datasets")
|
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:
|
if remote_dataset is None:
|
||||||
stream = self._send_absolute(local_dataset)
|
stream = self._send_absolute(local_dataset)
|
||||||
else:
|
else:
|
||||||
stream = self._send_incremental(local_dataset, remote_dataset)
|
stream = self._send_incremental(local_dataset, remote_dataset)
|
||||||
ZFS.receive(stream, self._external_pool)
|
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())
|
return self._user_confirm_new_datasets(self._get_raw_dataset_map())
|
||||||
|
|
||||||
def _user_confirm_new_datasets(self, dataset_map: _map_type) -> _map_type:
|
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 {
|
return {
|
||||||
local_dataset: remote_dataset
|
local_dataset: remote_dataset
|
||||||
for local_dataset, remote_dataset in dataset_map.items()
|
for local_dataset, remote_dataset in dataset_map.items()
|
||||||
@@ -84,6 +95,10 @@ class Manager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _get_raw_dataset_map(self) -> _map_type:
|
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 {
|
return {
|
||||||
local_dataset: self._search_matching_dataset_in_remote_pool(
|
local_dataset: self._search_matching_dataset_in_remote_pool(
|
||||||
local_dataset, self._external_pool
|
local_dataset, self._external_pool
|
||||||
@@ -94,23 +109,29 @@ class Manager:
|
|||||||
def _send_incremental(
|
def _send_incremental(
|
||||||
self, local_dataset: Dataset, remote_dataset: Dataset
|
self, local_dataset: Dataset, remote_dataset: Dataset
|
||||||
) -> Popen:
|
) -> Popen:
|
||||||
start_snapshot = self._get_last_common_snapshot(local_dataset, remote_dataset)
|
"""Send incremental stream between two datasets to remote."""
|
||||||
end_snapshot = self._get_last_snapshot_with_backup_tag(local_dataset)
|
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)
|
stream = ZFS.send_incremental(start_snapshot, end_snapshot)
|
||||||
return stream
|
return stream
|
||||||
|
|
||||||
def _send_absolute(self, local_dataset: Dataset) -> Popen:
|
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)
|
return ZFS.send_absolute(snapshot)
|
||||||
|
|
||||||
def _get_last_snapshot_with_backup_tag(self, local_dataset):
|
def _get_newest_snapshot_with_backup_tag(self, dataset: Dataset) -> Snapshot:
|
||||||
snapshots = self._find_snapshots_with_backup_tag(local_dataset)
|
"""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()
|
snapshots.sort()
|
||||||
end_snapshot = snapshots[-1]
|
end_snapshot = snapshots[-1]
|
||||||
return end_snapshot
|
return end_snapshot
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _datasets_to_backup(self) -> list[Dataset]:
|
def _datasets_to_backup(self) -> list[Dataset]:
|
||||||
|
"""Return local datasets to consider for a backup."""
|
||||||
return [
|
return [
|
||||||
dataset
|
dataset
|
||||||
for dataset in self._local_pool.datasets
|
for dataset in self._local_pool.datasets
|
||||||
@@ -119,6 +140,7 @@ class Manager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _find_snapshots_with_backup_tag(dataset: Dataset) -> list[Snapshot]:
|
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(
|
regex = _get_regex_matching_snapshots_with_tags(
|
||||||
[config.snapshot_tag, config.snapshot_interval]
|
[config.snapshot_tag, config.snapshot_interval]
|
||||||
)
|
)
|
||||||
@@ -130,15 +152,22 @@ class Manager:
|
|||||||
def _search_matching_dataset_in_remote_pool(
|
def _search_matching_dataset_in_remote_pool(
|
||||||
dataset: Dataset, pool: Pool
|
dataset: Dataset, pool: Pool
|
||||||
) -> Dataset | None:
|
) -> 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)
|
dataset_to_search = dataset.replace_pool(pool.name)
|
||||||
for dataset in pool.datasets:
|
for dataset in pool.datasets:
|
||||||
if dataset == dataset_to_search:
|
if dataset == dataset_to_search:
|
||||||
return dataset
|
return dataset
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_last_common_snapshot(
|
def _get_newest_common_snapshot_with_backup_tags(
|
||||||
cls, local_dataset: Dataset, remote_dataset: Dataset
|
cls, local_dataset: Dataset, remote_dataset: Dataset
|
||||||
) -> Snapshot:
|
) -> 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 = [
|
common_snapshots = [
|
||||||
local_snap
|
local_snap
|
||||||
for local_snap in list(cls._find_snapshots_with_backup_tag(local_dataset))
|
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)
|
cls._scrub_if_necessary(pool, skip_if_recently_scrubbed)
|
||||||
if wait_for_finish:
|
if wait_for_finish:
|
||||||
cls._run_command(*cls._Subcommands.wait, pool.name)
|
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.")
|
raise IOError(f"Pool {pool.name} is not healthy.")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -312,7 +341,7 @@ class ZPool(CommandInterface):
|
|||||||
return "scrub in progress" in cls._get_pool_status_output(pool)
|
return "scrub in progress" in cls._get_pool_status_output(pool)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _healthy(cls, pool: Pool) -> bool:
|
def _is_healthy(cls, pool: Pool) -> bool:
|
||||||
return "ONLINE" in cls._get_pool_status_output(pool)
|
return "ONLINE" in cls._get_pool_status_output(pool)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user