Compare commits
9 Commits
v3.2-alpha
...
v3.2-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
| 564daaa1f8 | |||
| 4d3aa6da22 | |||
| aedeb726d4 | |||
| 78d7dbab6d | |||
| 0b587b3800 | |||
| a331dab20f | |||
| 3f1696024e | |||
| 911db9b023 | |||
| 4873913fa8 |
@ -892,7 +892,7 @@ test_target1/test_source2/fs2/sub@test-20101111000003
|
|||||||
r = shelltest("zfs snapshot test_source1@test")
|
r = shelltest("zfs snapshot test_source1@test")
|
||||||
|
|
||||||
l=LogConsole(show_verbose=True, show_debug=False, color=False)
|
l=LogConsole(show_verbose=True, show_debug=False, color=False)
|
||||||
n=ZfsNode(snapshot_time_format="bla", hold_name="bla", logger=l)
|
n=ZfsNode(utc=False, snapshot_time_format="bla", hold_name="bla", logger=l)
|
||||||
d=ZfsDataset(n,"test_source1@test")
|
d=ZfsDataset(n,"test_source1@test")
|
||||||
|
|
||||||
sp=d.send_pipe([], prev_snapshot=None, resume_token=None, show_progress=True, raw=False, send_pipes=[], send_properties=True, write_embedded=True, zfs_compressed=True)
|
sp=d.send_pipe([], prev_snapshot=None, resume_token=None, show_progress=True, raw=False, send_pipes=[], send_properties=True, write_embedded=True, zfs_compressed=True)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class TestZfsNode(unittest2.TestCase):
|
|||||||
def test_consistent_snapshot(self):
|
def test_consistent_snapshot(self):
|
||||||
logger = LogStub()
|
logger = LogStub()
|
||||||
description = "[Source]"
|
description = "[Source]"
|
||||||
node = ZfsNode(snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
node = ZfsNode(utc=False, snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
||||||
|
|
||||||
with self.subTest("first snapshot"):
|
with self.subTest("first snapshot"):
|
||||||
node.consistent_snapshot(node.selected_datasets(property_name="autobackup:test",exclude_paths=[], exclude_received=False, exclude_unchanged=False, min_change=200000), "test-20101111000001", 100000)
|
node.consistent_snapshot(node.selected_datasets(property_name="autobackup:test",exclude_paths=[], exclude_received=False, exclude_unchanged=False, min_change=200000), "test-20101111000001", 100000)
|
||||||
@ -74,7 +74,7 @@ test_target1
|
|||||||
def test_consistent_snapshot_prepostcmds(self):
|
def test_consistent_snapshot_prepostcmds(self):
|
||||||
logger = LogStub()
|
logger = LogStub()
|
||||||
description = "[Source]"
|
description = "[Source]"
|
||||||
node = ZfsNode(snapshot_time_format="test", hold_name="test", logger=logger, description=description, debug_output=True)
|
node = ZfsNode(utc=False, snapshot_time_format="test", hold_name="test", logger=logger, description=description, debug_output=True)
|
||||||
|
|
||||||
with self.subTest("Test if all cmds are executed correctly (no failures)"):
|
with self.subTest("Test if all cmds are executed correctly (no failures)"):
|
||||||
with OutputIO() as buf:
|
with OutputIO() as buf:
|
||||||
@ -124,6 +124,21 @@ test_target1
|
|||||||
self.assertIn("STDOUT > post1", buf.getvalue())
|
self.assertIn("STDOUT > post1", buf.getvalue())
|
||||||
self.assertIn("STDOUT > post2", buf.getvalue())
|
self.assertIn("STDOUT > post2", buf.getvalue())
|
||||||
|
|
||||||
|
def test_timestamps(self):
|
||||||
|
# Assert that timestamps keep relative order both for utc and for localtime
|
||||||
|
logger = LogStub()
|
||||||
|
description = "[Source]"
|
||||||
|
node_local = ZfsNode(utc=False, snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
||||||
|
node_utc = ZfsNode(utc=True, snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
||||||
|
|
||||||
|
for node in [node_local, node_utc]:
|
||||||
|
with self.subTest("timestamp ordering " + ("utc" if node == node_utc else "localtime")):
|
||||||
|
dataset_a = ZfsDataset(node,"test_source1@test-20101111000001")
|
||||||
|
dataset_b = ZfsDataset(node,"test_source1@test-20101111000002")
|
||||||
|
dataset_c = ZfsDataset(node,"test_source1@test-20240101020202")
|
||||||
|
self.assertGreater(dataset_b.timestamp, dataset_a.timestamp)
|
||||||
|
self.assertGreater(dataset_c.timestamp, dataset_b.timestamp)
|
||||||
|
|
||||||
|
|
||||||
def test_getselected(self):
|
def test_getselected(self):
|
||||||
|
|
||||||
@ -131,18 +146,24 @@ test_target1
|
|||||||
shelltest("zfs create test_source1/fs1/subexcluded")
|
shelltest("zfs create test_source1/fs1/subexcluded")
|
||||||
shelltest("zfs set autobackup:test=false test_source1/fs1/subexcluded")
|
shelltest("zfs set autobackup:test=false test_source1/fs1/subexcluded")
|
||||||
|
|
||||||
|
# only select parent
|
||||||
|
shelltest("zfs create test_source1/fs1/onlyparent")
|
||||||
|
shelltest("zfs create test_source1/fs1/onlyparent/child")
|
||||||
|
shelltest("zfs set autobackup:test=parent test_source1/fs1/onlyparent")
|
||||||
|
|
||||||
# should be excluded by being unchanged
|
# should be excluded by being unchanged
|
||||||
shelltest("zfs create test_source1/fs1/unchanged")
|
shelltest("zfs create test_source1/fs1/unchanged")
|
||||||
shelltest("zfs snapshot test_source1/fs1/unchanged@somesnapshot")
|
shelltest("zfs snapshot test_source1/fs1/unchanged@somesnapshot")
|
||||||
|
|
||||||
logger = LogStub()
|
logger = LogStub()
|
||||||
description = "[Source]"
|
description = "[Source]"
|
||||||
node = ZfsNode(snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
node = ZfsNode(utc=False, snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
||||||
s = pformat(node.selected_datasets(property_name="autobackup:test", exclude_paths=[], exclude_received=False, exclude_unchanged=True, min_change=1))
|
s = pformat(node.selected_datasets(property_name="autobackup:test", exclude_paths=[], exclude_received=False, exclude_unchanged=True, min_change=1))
|
||||||
print(s)
|
print(s)
|
||||||
|
|
||||||
# basics
|
# basics
|
||||||
self.assertEqual(s, """[(local): test_source1/fs1,
|
self.assertEqual(s, """[(local): test_source1/fs1,
|
||||||
|
(local): test_source1/fs1/onlyparent,
|
||||||
(local): test_source1/fs1/sub,
|
(local): test_source1/fs1/sub,
|
||||||
(local): test_source2/fs2/sub]""")
|
(local): test_source2/fs2/sub]""")
|
||||||
|
|
||||||
@ -150,7 +171,7 @@ test_target1
|
|||||||
def test_validcommand(self):
|
def test_validcommand(self):
|
||||||
logger = LogStub()
|
logger = LogStub()
|
||||||
description = "[Source]"
|
description = "[Source]"
|
||||||
node = ZfsNode(snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
node = ZfsNode(utc=False, snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
||||||
|
|
||||||
with self.subTest("test invalid option"):
|
with self.subTest("test invalid option"):
|
||||||
self.assertFalse(node.valid_command(["zfs", "send", "--invalid-option", "nonexisting"]))
|
self.assertFalse(node.valid_command(["zfs", "send", "--invalid-option", "nonexisting"]))
|
||||||
@ -160,7 +181,7 @@ test_target1
|
|||||||
def test_supportedsendoptions(self):
|
def test_supportedsendoptions(self):
|
||||||
logger = LogStub()
|
logger = LogStub()
|
||||||
description = "[Source]"
|
description = "[Source]"
|
||||||
node = ZfsNode(snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
node = ZfsNode(utc=False, snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description)
|
||||||
# -D propably always supported
|
# -D propably always supported
|
||||||
self.assertGreater(len(node.supported_send_options), 0)
|
self.assertGreater(len(node.supported_send_options), 0)
|
||||||
|
|
||||||
@ -168,7 +189,7 @@ test_target1
|
|||||||
logger = LogStub()
|
logger = LogStub()
|
||||||
description = "[Source]"
|
description = "[Source]"
|
||||||
# NOTE: this could hang via ssh if we dont close filehandles properly. (which was a previous bug)
|
# NOTE: this could hang via ssh if we dont close filehandles properly. (which was a previous bug)
|
||||||
node = ZfsNode(snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description, ssh_to='localhost')
|
node = ZfsNode(utc=False, snapshot_time_format="test-%Y%m%d%H%M%S", hold_name="zfs_autobackup:test", logger=logger, description=description, ssh_to='localhost')
|
||||||
self.assertIsInstance(node.supported_recv_options, list)
|
self.assertIsInstance(node.supported_recv_options, list)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ class CliBase(object):
|
|||||||
Overridden in subclasses that add stuff for the specific programs."""
|
Overridden in subclasses that add stuff for the specific programs."""
|
||||||
|
|
||||||
# also used by setup.py
|
# also used by setup.py
|
||||||
VERSION = "3.2-alpha2"
|
VERSION = "3.2-beta1"
|
||||||
HEADER = "{} v{} - (c)2022 E.H.Eefting (edwin@datux.nl)".format(os.path.basename(sys.argv[0]), VERSION)
|
HEADER = "{} v{} - (c)2022 E.H.Eefting (edwin@datux.nl)".format(os.path.basename(sys.argv[0]), VERSION)
|
||||||
|
|
||||||
def __init__(self, argv, print_arguments=True):
|
def __init__(self, argv, print_arguments=True):
|
||||||
@ -80,6 +80,8 @@ class CliBase(object):
|
|||||||
help='show zfs progress output. Enabled automaticly on ttys. (use --no-progress to disable)')
|
help='show zfs progress output. Enabled automaticly on ttys. (use --no-progress to disable)')
|
||||||
group.add_argument('--no-progress', action='store_true',
|
group.add_argument('--no-progress', action='store_true',
|
||||||
help=argparse.SUPPRESS) # needed to workaround a zfs recv -v bug
|
help=argparse.SUPPRESS) # needed to workaround a zfs recv -v bug
|
||||||
|
group.add_argument('--utc', action='store_true',
|
||||||
|
help='Use UTC instead of local time when dealing with timestamps for both formatting and parsing. To snapshot in an ISO 8601 compliant time format you may for example specify --snapshot-format "{}-%%Y-%%m-%%dT%%H:%%M:%%SZ". Changing this parameter after-the-fact (existing snapshots) will cause their timestamps to be interpreted as a different time than before.')
|
||||||
group.add_argument('--version', action='store_true',
|
group.add_argument('--version', action='store_true',
|
||||||
help='Show version.')
|
help='Show version.')
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,7 @@ class ZfsAuto(CliBase):
|
|||||||
self.verbose("")
|
self.verbose("")
|
||||||
self.verbose("Selecting dataset property : {}".format(self.property_name))
|
self.verbose("Selecting dataset property : {}".format(self.property_name))
|
||||||
self.verbose("Snapshot format : {}".format(self.snapshot_time_format))
|
self.verbose("Snapshot format : {}".format(self.snapshot_time_format))
|
||||||
|
self.verbose("Timezone : {}".format("UTC" if args.utc else "Local"))
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@ -93,8 +94,7 @@ class ZfsAuto(CliBase):
|
|||||||
group.add_argument('--hold-format', metavar='FORMAT', default="zfs_autobackup:{}",
|
group.add_argument('--hold-format', metavar='FORMAT', default="zfs_autobackup:{}",
|
||||||
help='ZFS hold string format. Default: %(default)s')
|
help='ZFS hold string format. Default: %(default)s')
|
||||||
group.add_argument('--strip-path', metavar='N', default=0, type=int,
|
group.add_argument('--strip-path', metavar='N', default=0, type=int,
|
||||||
help='Number of directories to strip from target path (use 1 when cloning zones between 2 '
|
help='Number of directories to strip from target path.')
|
||||||
'SmartOS machines)')
|
|
||||||
|
|
||||||
group=parser.add_argument_group("Selection options")
|
group=parser.add_argument_group("Selection options")
|
||||||
group.add_argument('--ignore-replicated', action='store_true', help=argparse.SUPPRESS)
|
group.add_argument('--ignore-replicated', action='store_true', help=argparse.SUPPRESS)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
from datetime import datetime
|
||||||
from signal import signal, SIGPIPE
|
from signal import signal, SIGPIPE
|
||||||
from .util import output_redir, sigpipe_handler
|
from .util import output_redir, sigpipe_handler
|
||||||
|
|
||||||
@ -12,7 +13,6 @@ from .Thinner import Thinner
|
|||||||
from .ZfsDataset import ZfsDataset
|
from .ZfsDataset import ZfsDataset
|
||||||
from .ZfsNode import ZfsNode
|
from .ZfsNode import ZfsNode
|
||||||
from .ThinnerRule import ThinnerRule
|
from .ThinnerRule import ThinnerRule
|
||||||
import os.path
|
|
||||||
|
|
||||||
class ZfsAutobackup(ZfsAuto):
|
class ZfsAutobackup(ZfsAuto):
|
||||||
"""The main zfs-autobackup class. Start here, at run() :)"""
|
"""The main zfs-autobackup class. Start here, at run() :)"""
|
||||||
@ -70,6 +70,9 @@ class ZfsAutobackup(ZfsAuto):
|
|||||||
help='If nothing has changed, still create empty snapshots. (Faster. Same as --min-change=0)')
|
help='If nothing has changed, still create empty snapshots. (Faster. Same as --min-change=0)')
|
||||||
group.add_argument('--other-snapshots', action='store_true',
|
group.add_argument('--other-snapshots', action='store_true',
|
||||||
help='Send over other snapshots as well, not just the ones created by this tool.')
|
help='Send over other snapshots as well, not just the ones created by this tool.')
|
||||||
|
group.add_argument('--set-snapshot-properties', metavar='PROPERTY=VALUE,...', type=str,
|
||||||
|
help='List of properties to set on the snapshot.')
|
||||||
|
|
||||||
|
|
||||||
group = parser.add_argument_group("Transfer options")
|
group = parser.add_argument_group("Transfer options")
|
||||||
group.add_argument('--no-send', action='store_true',
|
group.add_argument('--no-send', action='store_true',
|
||||||
@ -411,6 +414,15 @@ class ZfsAutobackup(ZfsAuto):
|
|||||||
|
|
||||||
return set_properties
|
return set_properties
|
||||||
|
|
||||||
|
def set_snapshot_properties_list(self):
|
||||||
|
|
||||||
|
if self.args.set_snapshot_properties:
|
||||||
|
set_snapshot_properties = self.args.set_snapshot_properties.split(",")
|
||||||
|
else:
|
||||||
|
set_snapshot_properties = []
|
||||||
|
|
||||||
|
return set_snapshot_properties
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -423,7 +435,8 @@ class ZfsAutobackup(ZfsAuto):
|
|||||||
source_thinner = None
|
source_thinner = None
|
||||||
else:
|
else:
|
||||||
source_thinner = Thinner(self.args.keep_source)
|
source_thinner = Thinner(self.args.keep_source)
|
||||||
source_node = ZfsNode(snapshot_time_format=self.snapshot_time_format, hold_name=self.hold_name, logger=self,
|
source_node = ZfsNode(utc=self.args.utc,
|
||||||
|
snapshot_time_format=self.snapshot_time_format, hold_name=self.hold_name, logger=self,
|
||||||
ssh_config=self.args.ssh_config,
|
ssh_config=self.args.ssh_config,
|
||||||
ssh_to=self.args.ssh_source, readonly=self.args.test,
|
ssh_to=self.args.ssh_source, readonly=self.args.test,
|
||||||
debug_output=self.args.debug_output, description=description, thinner=source_thinner)
|
debug_output=self.args.debug_output, description=description, thinner=source_thinner)
|
||||||
@ -442,11 +455,13 @@ class ZfsAutobackup(ZfsAuto):
|
|||||||
################# snapshotting
|
################# snapshotting
|
||||||
if not self.args.no_snapshot:
|
if not self.args.no_snapshot:
|
||||||
self.set_title("Snapshotting")
|
self.set_title("Snapshotting")
|
||||||
snapshot_name = time.strftime(self.snapshot_time_format)
|
dt = datetime.utcnow() if self.args.utc else datetime.now()
|
||||||
|
snapshot_name = dt.strftime(self.snapshot_time_format)
|
||||||
source_node.consistent_snapshot(source_datasets, snapshot_name,
|
source_node.consistent_snapshot(source_datasets, snapshot_name,
|
||||||
min_changed_bytes=self.args.min_change,
|
min_changed_bytes=self.args.min_change,
|
||||||
pre_snapshot_cmds=self.args.pre_snapshot_cmd,
|
pre_snapshot_cmds=self.args.pre_snapshot_cmd,
|
||||||
post_snapshot_cmds=self.args.post_snapshot_cmd)
|
post_snapshot_cmds=self.args.post_snapshot_cmd,
|
||||||
|
set_snapshot_properties=self.set_snapshot_properties_list())
|
||||||
|
|
||||||
################# sync
|
################# sync
|
||||||
# if target is specified, we sync the datasets, otherwise we just thin the source. (e.g. snapshot mode)
|
# if target is specified, we sync the datasets, otherwise we just thin the source. (e.g. snapshot mode)
|
||||||
@ -458,7 +473,8 @@ class ZfsAutobackup(ZfsAuto):
|
|||||||
target_thinner = None
|
target_thinner = None
|
||||||
else:
|
else:
|
||||||
target_thinner = Thinner(self.args.keep_target)
|
target_thinner = Thinner(self.args.keep_target)
|
||||||
target_node = ZfsNode(snapshot_time_format=self.snapshot_time_format, hold_name=self.hold_name,
|
target_node = ZfsNode(utc=self.args.utc,
|
||||||
|
snapshot_time_format=self.snapshot_time_format, hold_name=self.hold_name,
|
||||||
logger=self, ssh_config=self.args.ssh_config,
|
logger=self, ssh_config=self.args.ssh_config,
|
||||||
ssh_to=self.args.ssh_target,
|
ssh_to=self.args.ssh_target,
|
||||||
readonly=self.args.test, debug_output=self.args.debug_output,
|
readonly=self.args.test, debug_output=self.args.debug_output,
|
||||||
@ -524,7 +540,8 @@ def cli():
|
|||||||
|
|
||||||
signal(SIGPIPE, sigpipe_handler)
|
signal(SIGPIPE, sigpipe_handler)
|
||||||
|
|
||||||
sys.exit(ZfsAutobackup(sys.argv[1:], False).run())
|
failed_datasets=ZfsAutobackup(sys.argv[1:], False).run()
|
||||||
|
sys.exit(min(failed_datasets, 255))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -231,7 +231,8 @@ class ZfsAutoverify(ZfsAuto):
|
|||||||
self.set_title("Source settings")
|
self.set_title("Source settings")
|
||||||
|
|
||||||
description = "[Source]"
|
description = "[Source]"
|
||||||
source_node = ZfsNode(snapshot_time_format=self.snapshot_time_format, hold_name=self.hold_name, logger=self,
|
source_node = ZfsNode(utc=self.args.utc,
|
||||||
|
snapshot_time_format=self.snapshot_time_format, hold_name=self.hold_name, logger=self,
|
||||||
ssh_config=self.args.ssh_config,
|
ssh_config=self.args.ssh_config,
|
||||||
ssh_to=self.args.ssh_source, readonly=self.args.test,
|
ssh_to=self.args.ssh_source, readonly=self.args.test,
|
||||||
debug_output=self.args.debug_output, description=description)
|
debug_output=self.args.debug_output, description=description)
|
||||||
@ -249,7 +250,8 @@ class ZfsAutoverify(ZfsAuto):
|
|||||||
|
|
||||||
# create target_node
|
# create target_node
|
||||||
self.set_title("Target settings")
|
self.set_title("Target settings")
|
||||||
target_node = ZfsNode(snapshot_time_format=self.snapshot_time_format, hold_name=self.hold_name,
|
target_node = ZfsNode(utc=self.args.utc,
|
||||||
|
snapshot_time_format=self.snapshot_time_format, hold_name=self.hold_name,
|
||||||
logger=self, ssh_config=self.args.ssh_config,
|
logger=self, ssh_config=self.args.ssh_config,
|
||||||
ssh_to=self.args.ssh_target,
|
ssh_to=self.args.ssh_target,
|
||||||
readonly=self.args.test, debug_output=self.args.debug_output,
|
readonly=self.args.test, debug_output=self.args.debug_output,
|
||||||
@ -306,8 +308,8 @@ def cli():
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
signal(SIGPIPE, sigpipe_handler)
|
signal(SIGPIPE, sigpipe_handler)
|
||||||
|
failed = ZfsAutoverify(sys.argv[1:], False).run()
|
||||||
sys.exit(ZfsAutoverify(sys.argv[1:], False).run())
|
sys.exit(min(failed,255))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -18,7 +18,7 @@ class ZfsCheck(CliBase):
|
|||||||
# NOTE: common options argument parsing are in CliBase
|
# NOTE: common options argument parsing are in CliBase
|
||||||
super(ZfsCheck, self).__init__(argv, print_arguments)
|
super(ZfsCheck, self).__init__(argv, print_arguments)
|
||||||
|
|
||||||
self.node = ZfsNode(self.log, readonly=self.args.test, debug_output=self.args.debug_output)
|
self.node = ZfsNode(self.log, utc=self.args.utc, readonly=self.args.test, debug_output=self.args.debug_output)
|
||||||
|
|
||||||
self.block_hasher = BlockHasher(count=self.args.count, bs=self.args.block_size, skip=self.args.skip)
|
self.block_hasher = BlockHasher(count=self.args.count, bs=self.args.block_size, skip=self.args.skip)
|
||||||
|
|
||||||
@ -302,8 +302,8 @@ class ZfsCheck(CliBase):
|
|||||||
def cli():
|
def cli():
|
||||||
import sys
|
import sys
|
||||||
signal(SIGPIPE, sigpipe_handler)
|
signal(SIGPIPE, sigpipe_handler)
|
||||||
|
failed=ZfsCheck(sys.argv[1:], False).run()
|
||||||
sys.exit(ZfsCheck(sys.argv[1:], False).run())
|
sys.exit(min(failed,255))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .CachedProperty import CachedProperty
|
from .CachedProperty import CachedProperty
|
||||||
@ -129,7 +131,7 @@ class ZfsDataset:
|
|||||||
:type exclude_unchanged: bool
|
:type exclude_unchanged: bool
|
||||||
:type min_change: bool
|
:type min_change: bool
|
||||||
|
|
||||||
:param value: Value of the zfs property ("false"/"true"/"child"/"-")
|
:param value: Value of the zfs property ("false"/"true"/"child"/parent/"-")
|
||||||
:param source: Source of the zfs property ("local"/"received", "-")
|
:param source: Source of the zfs property ("local"/"received", "-")
|
||||||
:param inherited: True of the value/source was inherited from a higher dataset.
|
:param inherited: True of the value/source was inherited from a higher dataset.
|
||||||
"""
|
"""
|
||||||
@ -140,7 +142,7 @@ class ZfsDataset:
|
|||||||
raise (Exception(
|
raise (Exception(
|
||||||
"{} autobackup-property has illegal source: '{}' (possible BUG)".format(self.name, source)))
|
"{} autobackup-property has illegal source: '{}' (possible BUG)".format(self.name, source)))
|
||||||
|
|
||||||
if value not in ["false", "true", "child", "-"]:
|
if value not in ["false", "true", "child", "parent", "-"]:
|
||||||
# user error
|
# user error
|
||||||
raise (Exception(
|
raise (Exception(
|
||||||
"{} autobackup-property has illegal value: '{}'".format(self.name, value)))
|
"{} autobackup-property has illegal value: '{}'".format(self.name, value)))
|
||||||
@ -153,6 +155,10 @@ class ZfsDataset:
|
|||||||
if value == "child" and not inherited:
|
if value == "child" and not inherited:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# only select parent, no childs, ignore
|
||||||
|
if value == "parent" and inherited:
|
||||||
|
return False
|
||||||
|
|
||||||
# manually excluded by property
|
# manually excluded by property
|
||||||
if value == "false":
|
if value == "false":
|
||||||
self.verbose("Excluded")
|
self.verbose("Excluded")
|
||||||
@ -375,9 +381,22 @@ class ZfsDataset:
|
|||||||
"""get timestamp from snapshot name. Only works for our own snapshots
|
"""get timestamp from snapshot name. Only works for our own snapshots
|
||||||
with the correct format.
|
with the correct format.
|
||||||
"""
|
"""
|
||||||
|
dt = datetime.strptime(self.snapshot_name, self.zfs_node.snapshot_time_format)
|
||||||
time_secs = time.mktime(time.strptime(self.snapshot_name, self.zfs_node.snapshot_time_format))
|
if sys.version_info[0] >= 3:
|
||||||
return time_secs
|
from datetime import timezone
|
||||||
|
if self.zfs_node.utc:
|
||||||
|
dt = dt.replace(tzinfo=timezone.utc)
|
||||||
|
seconds = dt.timestamp()
|
||||||
|
else:
|
||||||
|
# python2 has no good functions to deal with UTC. Yet the unix timestamp
|
||||||
|
# must be in UTC to allow comparison against `time.time()` in on other parts
|
||||||
|
# of this project (e.g. Thinner.py). If we are handling UTC timestamps,
|
||||||
|
# we must adjust for that here.
|
||||||
|
if self.zfs_node.utc:
|
||||||
|
seconds = (dt - datetime(1970, 1, 1)).total_seconds()
|
||||||
|
else:
|
||||||
|
seconds = time.mktime(dt.timetuple())
|
||||||
|
return seconds
|
||||||
|
|
||||||
def from_names(self, names):
|
def from_names(self, names):
|
||||||
"""convert a list of names to a list ZfsDatasets for this zfs_node
|
"""convert a list of names to a list ZfsDatasets for this zfs_node
|
||||||
@ -1137,7 +1156,7 @@ class ZfsDataset:
|
|||||||
self.debug("Unmounting")
|
self.debug("Unmounting")
|
||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"umount", "-l", self.name
|
"umount", self.name
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,11 @@ from .ExecuteNode import ExecuteError
|
|||||||
class ZfsNode(ExecuteNode):
|
class ZfsNode(ExecuteNode):
|
||||||
"""a node that contains zfs datasets. implements global (systemwide/pool wide) zfs commands"""
|
"""a node that contains zfs datasets. implements global (systemwide/pool wide) zfs commands"""
|
||||||
|
|
||||||
def __init__(self, logger, snapshot_time_format="", hold_name="", ssh_config=None, ssh_to=None, readonly=False,
|
def __init__(self, logger, utc=False, snapshot_time_format="", hold_name="", ssh_config=None, ssh_to=None, readonly=False,
|
||||||
description="",
|
description="",
|
||||||
debug_output=False, thinner=None):
|
debug_output=False, thinner=None):
|
||||||
|
|
||||||
|
self.utc = utc
|
||||||
self.snapshot_time_format = snapshot_time_format
|
self.snapshot_time_format = snapshot_time_format
|
||||||
self.hold_name = hold_name
|
self.hold_name = hold_name
|
||||||
|
|
||||||
@ -180,7 +181,7 @@ class ZfsNode(ExecuteNode):
|
|||||||
self.logger.debug("{} {}".format(self.description, txt))
|
self.logger.debug("{} {}".format(self.description, txt))
|
||||||
|
|
||||||
def consistent_snapshot(self, datasets, snapshot_name, min_changed_bytes, pre_snapshot_cmds=[],
|
def consistent_snapshot(self, datasets, snapshot_name, min_changed_bytes, pre_snapshot_cmds=[],
|
||||||
post_snapshot_cmds=[]):
|
post_snapshot_cmds=[], set_snapshot_properties=[]):
|
||||||
"""create a consistent (atomic) snapshot of specified datasets, per pool.
|
"""create a consistent (atomic) snapshot of specified datasets, per pool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -218,6 +219,8 @@ class ZfsNode(ExecuteNode):
|
|||||||
# create consistent snapshot per pool
|
# create consistent snapshot per pool
|
||||||
for (pool_name, snapshots) in pools.items():
|
for (pool_name, snapshots) in pools.items():
|
||||||
cmd = ["zfs", "snapshot"]
|
cmd = ["zfs", "snapshot"]
|
||||||
|
for snapshot_property in set_snapshot_properties:
|
||||||
|
cmd += ['-o', snapshot_property]
|
||||||
|
|
||||||
cmd.extend(map(lambda snapshot_: str(snapshot_), snapshots))
|
cmd.extend(map(lambda snapshot_: str(snapshot_), snapshots))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user