Implement --utc switch to force UTC time (closes #155)

This commit is contained in:
oddlama 2022-07-04 22:18:45 +02:00 committed by DatuX
parent 0b587b3800
commit 78d7dbab6d
9 changed files with 43 additions and 19 deletions

View File

@ -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)

View File

@ -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:
@ -137,7 +137,7 @@ test_target1
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)
@ -150,7 +150,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 +160,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 +168,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)

View File

@ -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.')
@ -106,4 +108,4 @@ class CliBase(object):
def set_title(self, title): def set_title(self, title):
self.log.verbose("") self.log.verbose("")
self.log.verbose("#### " + title) self.log.verbose("#### " + title)

View File

@ -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

View File

@ -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() :)"""
@ -435,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)
@ -454,7 +455,8 @@ 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,
@ -471,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,

View File

@ -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,

View File

@ -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)

View File

@ -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
@ -375,9 +377,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

View File

@ -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