Skip to content

Commit

Permalink
Better strip path handling and collision checking. Now also supports …
Browse files Browse the repository at this point in the history
…stripping so much it ends up on a pool-target.

Fixes #102, #117
  • Loading branch information
psy0rz committed Feb 23, 2022
1 parent 07cb7cf commit cab2f98
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 5 deletions.
7 changes: 7 additions & 0 deletions tests/test_zfsautobackup.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,13 @@ def test_strippath(self):
test_target1/fs2/sub@test-20101111000000
""")

def test_strippath_collision(self):
with self.assertRaisesRegexp(Exception,"collision"):
ZfsAutobackup("test test_target1 --verbose --strip-path=2 --no-progress --debug".split(" ")).run()

def test_strippath_toomuch(self):
with self.assertRaisesRegexp(Exception,"too much"):
ZfsAutobackup("test test_target1 --verbose --strip-path=3 --no-progress --debug".split(" ")).run()

def test_clearrefres(self):

Expand Down
33 changes: 30 additions & 3 deletions zfs_autobackup/ZfsAutobackup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
from .LogConsole import LogConsole
from .ZfsNode import ZfsNode
from .ThinnerRule import ThinnerRule

import os.path

class ZfsAutobackup:
"""main class"""

VERSION = "3.1.1"
VERSION = "3.1.2-rc1"
HEADER = "zfs-autobackup v{} - (c)2021 E.H.Eefting ([email protected])".format(VERSION)

def __init__(self, argv, print_arguments=True):
Expand Down Expand Up @@ -364,6 +364,29 @@ def get_recv_pipes(self, logger):

return ret

def make_target_name(self, source_dataset):
"""make target_name from a source_dataset"""
stripped=source_dataset.lstrip_path(self.args.strip_path)
if stripped!="":
return self.args.target_path + "/" + stripped
else:
return self.args.target_path

def check_target_names(self, source_node, source_datasets, target_node):
"""check all target names for collesions etc due to strip-options"""

self.debug("Checking target names:")
target_datasets={}
for source_dataset in source_datasets:

target_name = self.make_target_name(source_dataset)
source_dataset.debug("-> {}".format(target_name))

if target_name in target_datasets:
raise Exception("Target collision: Target path {} encountered twice, due to: {} and {}".format(target_name, source_dataset, target_datasets[target_name]))

target_datasets[target_name]=source_dataset

# NOTE: this method also uses self.args. args that need extra processing are passed as function parameters:
def sync_datasets(self, source_node, source_datasets, target_node):
"""Sync datasets, or thin-only on both sides
Expand All @@ -387,13 +410,14 @@ def sync_datasets(self, source_node, source_datasets, target_node):

try:
# determine corresponding target_dataset
target_name = self.args.target_path + "/" + source_dataset.lstrip_path(self.args.strip_path)
target_name = self.make_target_name(source_dataset)
target_dataset = ZfsDataset(target_node, target_name)
target_datasets.append(target_dataset)

# ensure parents exists
# TODO: this isnt perfect yet, in some cases it can create parents when it shouldn't.
if not self.args.no_send \
and target_dataset.parent \
and target_dataset.parent not in target_datasets \
and not target_dataset.parent.exists:
target_dataset.parent.create_filesystem(parents=True)
Expand Down Expand Up @@ -560,6 +584,9 @@ def run(self):
raise (Exception(
"Target path '{}' does not exist. Please create this dataset first.".format(target_dataset)))

# check for collisions due to strip-path
self.check_target_names(source_node, source_datasets, target_node)

# do the actual sync
# NOTE: even with no_send, no_thinning and no_snapshot it does a usefull thing because it checks if the common snapshots and shows incompatible snapshots
fail_count = self.sync_datasets(
Expand Down
14 changes: 12 additions & 2 deletions zfs_autobackup/ZfsDataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ def lstrip_path(self, count):
Args:
:type count: int
"""
return "/".join(self.split_path()[count:])
components=self.split_path()
if count>len(components):
raise Exception("Trying to strip too much from path ({} items from {})".format(count, self.name))

return "/".join(components[count:])

def rstrip_path(self, count):
"""return name with last count components stripped
Expand Down Expand Up @@ -188,7 +192,11 @@ def parent(self):
if self.is_snapshot:
return ZfsDataset(self.zfs_node, self.filesystem_name)
else:
return ZfsDataset(self.zfs_node, self.rstrip_path(1))
stripped=self.rstrip_path(1)
if stripped:
return ZfsDataset(self.zfs_node, stripped)
else:
return None

# NOTE: unused for now
# def find_prev_snapshot(self, snapshot, also_other_snapshots=False):
Expand Down Expand Up @@ -1007,6 +1015,8 @@ def sync_snapshots(self, target_dataset, features, show_progress, filter_propert
:type destroy_incompatible: bool
"""

self.verbose("sending to {}".format(target_dataset))

(common_snapshot, start_snapshot, source_obsoletes, target_obsoletes, target_keeps,
incompatible_target_snapshots) = \
self._plan_sync(target_dataset=target_dataset, also_other_snapshots=also_other_snapshots)
Expand Down

0 comments on commit cab2f98

Please sign in to comment.