* added option to send all or just latest snapshot to remote

* added options for zfs send and zfs receive flags
This commit is contained in:
Fredrik Eriksson 2017-05-22 21:25:51 +02:00
parent f328cef917
commit 8617e3c0c6
3 changed files with 88 additions and 45 deletions

View File

@ -39,6 +39,8 @@ DEFAULT_CONFIG = {
'keep_1min': 0, 'keep_1min': 0,
'keep_custom': 0, 'keep_custom': 0,
'remote_enable': False, 'remote_enable': False,
'remote_send_flags': '',
'remote_recv_flags': '',
'remote_zfs_cmd': None, 'remote_zfs_cmd': None,
'remote_test_cmd': None, 'remote_test_cmd': None,
'remote_zfs_target': None, 'remote_zfs_target': None,
@ -135,7 +137,6 @@ def main():
remote_targets = {} remote_targets = {}
for fs in fslist: for fs in fslist:
conf = get_config_for_fs(fs, config) conf = get_config_for_fs(fs, config)
remote_fslist = None
remote_snapshots = None remote_snapshots = None
if not conf['remote_enable']: if not conf['remote_enable']:
continue continue
@ -143,7 +144,15 @@ def main():
failed_snapshots.add(fs) failed_snapshots.add(fs)
continue continue
repl_mode = conf['remote_enable']
remote_fs = conf['remote_zfs_target'] remote_fs = conf['remote_zfs_target']
send_opts = []
recv_opts = []
if conf['remote_send_flags']:
send_opts = conf['remote_send_flags'].split()
if conf['remote_recv_flags']:
recv_opts = conf['remote_recv_flags'].split()
rel_local = [k for k, v in remote_targets.items() if v == remote_fs] rel_local = [k for k, v in remote_targets.items() if v == remote_fs]
if rel_local: if rel_local:
rel_local = rel_local[0] rel_local = rel_local[0]
@ -158,59 +167,70 @@ def main():
# know which host we're working with. # know which host we're working with.
if 'remote_host' in conf: if 'remote_host' in conf:
if conf['remote_host'] in remote_hosts: if conf['remote_host'] in remote_hosts:
remote_fslist = remote_hosts[conf['remote_host']]['fslist'] remote_snapshots = remote_hosts[conf['remote_host']]
remote_snapshots = remote_hosts[conf['remote_host']]['snapshots']
else: else:
remote_fslist = zsnaplib.get_filesystems(zfs_cmd=remote_zfs_cmd)
remote_snapshots = zsnaplib.get_snapshots(zfs_cmd=remote_zfs_cmd) remote_snapshots = zsnaplib.get_snapshots(zfs_cmd=remote_zfs_cmd)
remote_hosts[conf['remote_host']] = { remote_hosts[conf['remote_host']] = remote_snapshots
'fslist': remote_fslist,
'snapshots': remote_snapshots
}
if not remote_fslist:
remote_fslist = zsnaplib.get_filesystems(zfs_cmd=remote_zfs_cmd)
if not remote_snapshots: if not remote_snapshots:
remote_snapshots = zsnaplib.get_snapshots(zfs_cmd=remote_zfs_cmd) remote_snapshots = zsnaplib.get_snapshots(zfs_cmd=remote_zfs_cmd)
remote_zfs_cmd.extend(['receive', remote_fs])
if remote_fs not in remote_snapshots: if remote_fs not in remote_snapshots:
# Remote FS doesn't exist, send a new copy # Remote FS doesn't exist, send a new copy
log.info('{} sending base copy to {}'.format(fs, ' '.join(remote_zfs_cmd))) log.info('{} sending base copy to {}'.format(fs, ' '.join(remote_zfs_cmd)))
# oldest snapshot is base_snap if repl_mode != latest
base_snap = snapshots[fs][-1]
if repl_mode == 'latest':
base_snap = snapshots[fs][0]
try: try:
zsnaplib.send_snapshot(fs, snapshots[fs][0], remote_zfs_cmd, sudo) zsnaplib.send_snapshot(
fs,
base_snap,
remote_zfs_cmd,
remote_fs,
sudo=sudo,
send_opts=send_opts,
recv_opts=recv_opts)
log.info('{} base copy sent'.format(fs)) log.info('{} base copy sent'.format(fs))
except zsnaplib.ZFSSnapshotError as e: except zsnaplib.ZFSSnapshotError as e:
failed_snapshots.add(fs) failed_snapshots.add(fs)
log.warning(e) log.warning(e)
ret = RET_CODES['ERROR'] ret = RET_CODES['ERROR']
continue continue
else: remote_snapshots[remote_fs] = [base_snap]
# Remote FS exists, find last common snapshot
last_remote = None
for remote_snap in remote_snapshots[remote_fs]:
if remote_snap in snapshots[fs]:
last_remote = remote_snap
break
if not last_remote:
failed_snapshots.add(fs)
log.warning('No common snapshot local and remote, you need to create a new base copy!')
ret = RET_CODES['ERROR']
continue
last_local = snapshots[fs][0]
if last_remote == last_local:
log.info("{} snapshot from {} is already present on remote".format(fs, last_local))
continue
log.info('{} incremental {} -> {}, remote is {}'.format(fs, last_remote, snapshots[fs][0], ' '.join(remote_zfs_cmd))) # Remote FS now exists, one way or another find last common snapshot
try: last_remote = None
zsnaplib.send_snapshot(fs, snapshots[fs][0], remote_zfs_cmd, sudo, repl_from=last_remote) for remote_snap in remote_snapshots[remote_fs]:
log.info('{} successfully sent to remote'.format(fs)) if remote_snap in snapshots[fs]:
except zsnaplib.ZFSSnapshotError as e: last_remote = remote_snap
log.warning(e) break
if not last_remote:
failed_snapshots.add(fs)
log.warning('No common snapshot local and remote, you need to create a new base copy!')
ret = RET_CODES['ERROR']
continue
last_local = snapshots[fs][0]
if last_remote == last_local:
log.info("{} snapshot from {} is already present on remote".format(fs, last_local))
continue
log.info('{} incremental {} -> {}, remote is {}'.format(fs, last_remote, snapshots[fs][0], ' '.join(remote_zfs_cmd)))
try:
zsnaplib.send_snapshot(
fs,
snapshots[fs][0],
remote_zfs_cmd,
remote_fs,
sudo=sudo,
send_opts=send_opts,
recv_opts=recv_opts,
repl_from=last_remote,
repl_mode=repl_mode)
log.info('{} successfully sent to remote'.format(fs))
except zsnaplib.ZFSSnapshotError as e:
log.warning(e)
# Third iteration: weed old snapshots # Third iteration: weed old snapshots
remote_hosts = {}
for fs in fslist: for fs in fslist:
conf = get_config_for_fs(fs, config) conf = get_config_for_fs(fs, config)
if fs in failed_snapshots: if fs in failed_snapshots:
@ -236,7 +256,7 @@ def main():
zsnaplib.weed_snapshots( zsnaplib.weed_snapshots(
fs, fs,
# do not remove the snapshot just created # never remove the latest snapshot
snapshots[fs][1:], snapshots[fs][1:],
**kwargs) **kwargs)

View File

@ -50,15 +50,30 @@ def do_zfs_command(args, sudo, pipecmd=None, zfs_cmd=[zfs_bin]):
raise ZFSSnapshotError('Failed to execute {}: {}'.format(cmd, err)) raise ZFSSnapshotError('Failed to execute {}: {}'.format(cmd, err))
return out return out
def send_snapshot(fs, snap, recv_cmd, sudo=False, repl_from=None): def send_snapshot(
fs,
snap,
remote_zfs_cmd,
remote_target,
sudo=False,
send_opts=[],
recv_opts=[],
repl_mode='all',
repl_from=None):
snap = snap.strftime(time_format) snap = snap.strftime(time_format)
pipecmd = recv_cmd
if repl_from: if repl_from:
if repl_mode == 'latest':
inc_flag = '-i'
else:
inc_flag = '-I'
repl_from = repl_from.strftime(time_format) repl_from = repl_from.strftime(time_format)
args = [ 'send', '-i', repl_from, '{}@{}'.format(fs, snap) ] args = [ 'send' ] + send_opts + [ inc_flag, '{}@{}'.format(fs, repl_from), '{}@{}'.format(fs, snap) ]
else: else:
args = [ 'send', '{}@{}'.format(fs, snap) ] args = [ 'send', '{}@{}'.format(fs, snap) ]
pipecmd = remote_zfs_cmd + [ 'receive' ] + recv_opts + [ remote_target ]
do_zfs_command(args, sudo, pipecmd=pipecmd) do_zfs_command(args, sudo, pipecmd=pipecmd)
@ -103,8 +118,6 @@ def get_snapshots(sudo=False, zfs_cmd=[zfs_bin]):
def remove_snapshot(fs, date, sudo=False): def remove_snapshot(fs, date, sudo=False):
date = date.strftime(time_format) date = date.strftime(time_format)
args = [ 'destroy', '{}@{}'.format(fs, date) ] args = [ 'destroy', '{}@{}'.format(fs, date) ]
print("would remove snapshot {}@{}".format(fs, date))
return
do_zfs_command(args, sudo) do_zfs_command(args, sudo)

View File

@ -8,7 +8,13 @@
[tank] [tank]
# frequency of snapshots
snapshot_interval=1h snapshot_interval=1h
# Remote replication
# possible other value is 'latest' to only sync the last available snapshot
remote_enable=all
# NOTE: # NOTE:
# The command arguments must not contain whitespace characters, since # The command arguments must not contain whitespace characters, since
# split() is used to create an array to subprocess.Popen() # split() is used to create an array to subprocess.Popen()
@ -17,11 +23,13 @@ remote_test_cmd=/usr/bin/ssh ${remote_user}@${remote_host} echo "success"
remote_user=backup remote_user=backup
remote_host=hem.winterbird.org remote_host=hem.winterbird.org
remote_zfs_target=tank/backup/asuna/tank remote_zfs_target=tank/backup/asuna/tank
# NOTE: # These can be set to use custom arguments to zfs send and zfs receive
# should be empty or 0 for negative value ; remote_send_flags=-D -p
remote_enable=1 remote_send_flags=
remote_recv_flags=
keep_hourly=24 keep_hourly=24
keep_daily=7
keep_weekly=4 keep_weekly=4
keep_monthly=4 keep_monthly=4
@ -39,6 +47,8 @@ remote_enable=
[tank/var/log] [tank/var/log]
snapshot_interval=1m snapshot_interval=1m
keep_1min=5
keep_15min=4
[tank/var/tmp] [tank/var/tmp]
snapshot_interval= snapshot_interval=