added support to create remote snapshots

This commit is contained in:
Fredrik Eriksson 2018-12-27 07:55:06 +01:00
parent 8adf8f14f8
commit eee2f12004
3 changed files with 72 additions and 18 deletions

View File

@ -75,13 +75,14 @@ def str_to_timedelta(deltastr):
delta += datetime.timedelta(minutes=int(match.group(1))) delta += datetime.timedelta(minutes=int(match.group(1)))
return delta return delta
def get_config_for_fs(fs, config): def get_config_for_fs(fs, config, remote=''):
fs_config = DEFAULT_CONFIG fs_config = DEFAULT_CONFIG.copy()
fs_build = '' fs_build = ''
for fs_part in fs.split('/'): for fs_part in fs.split('/'):
fs_build += fs_part fs_build += fs_part
if fs_build in config: section = "{}@{}".format(fs_build, remote)
fs_config.update(config[fs_build]) if section in config:
fs_config.update(config[section])
if fs_build == fs: if fs_build == fs:
break break
fs_build += '/' fs_build += '/'
@ -89,13 +90,15 @@ def get_config_for_fs(fs, config):
return fs_config return fs_config
def do_snapshots(fslist, snapshots, config, sudo): def do_snapshots(fslist, snapshots, config, sudo, remote=None, zfs_cmd=None):
failed_snapshots = set() failed_snapshots = set()
now = datetime.datetime.now() now = datetime.datetime.now()
log = logging.getLogger(LOGGER) log = logging.getLogger(LOGGER)
if not remote:
remote = ''
for fs in fslist: for fs in fslist:
conf = get_config_for_fs(fs, config) conf = get_config_for_fs(fs, config, remote=remote)
if not conf['snapshot_interval']: if not conf['snapshot_interval']:
continue continue
@ -106,13 +109,28 @@ def do_snapshots(fslist, snapshots, config, sudo):
last_snap = datetime.datetime.min last_snap = datetime.datetime.min
if interval > datetime.timedelta() and last_snap+interval < now: if interval > datetime.timedelta() and last_snap+interval < now:
try: try:
zsnaplib.create_snapshot(fs, sudo) if zfs_cmd:
log.info('{} snapshot created'.format(fs)) zsnaplib.create_snapshot(fs, sudo, zfs_cmd=zfs_cmd)
log.info('{} snapshot created on {}'.format(fs, remote))
else:
zsnaplib.create_snapshot(fs, sudo)
log.info('{} snapshot created'.format(fs))
except zsnaplib.ZFSSnapshotError as e: except zsnaplib.ZFSSnapshotError as e:
log.warning(e) log.warning(e)
failed_snapshots.add(fs) failed_snapshots.add(fs)
return failed_snapshots return failed_snapshots
def get_remote_hosts(config):
ret = {}
for section in config.sections():
if '@' in section and 'remote_zfs_cmd' in config[section]:
fs, remote = section.split('@', 1)
remote_zfs_cmd = Template(config[section]['remote_zfs_cmd']).safe_substitute(config[section])
remote_zfs_cmd = remote_zfs_cmd.split()
ret[remote] = remote_zfs_cmd
return ret
def send_snapshots(fslist, snapshots, config, sudo): def send_snapshots(fslist, snapshots, config, sudo):
failed_snapshots = set() failed_snapshots = set()
remote_hosts = {} remote_hosts = {}
@ -290,8 +308,43 @@ def main():
log.error('lockfile {} exists but does not seem to contain a pid. Will not continue'.format(lockfile)) log.error('lockfile {} exists but does not seem to contain a pid. Will not continue'.format(lockfile))
return RET_CODES['FAILED'] return RET_CODES['FAILED']
# create any remote snapshots
remotes = get_remote_hosts(config)
remote_fs = {}
remote_snapshots = {}
failed_remote_snapshots = {}
for remote, zfs_cmd in remotes.items():
try:
remote_fs[remote] = sorted(zsnaplib.get_filesystems(zfs_cmd=zfs_cmd))
remote_snapshots[remote] = zsnaplib.get_snapshots(zfs_cmd=zfs_cmd)
failed_remote_snapshots[remote] = do_snapshots(
remote_fs[remote],
remote_snapshots[remote],
config,
False, # sudo should be configured in zfs_cmd already
remote=remote,
zfs_cmd=zfs_cmd)
except zsnaplib.ZFSSnapshotError:
if remote in remote_fs:
del remote_fs[remote]
if remote in remote_snapshots:
del remote_snapshots[remote]
log.warning("Failed to snapshot on {}".format(remote))
ret = RET_CODES['ERROR']
for remote, filesystems in failed_remote_snapshots.items():
for fs in filesystems:
log.warning("Failed to snapshot {} on {}".format(fs, remote))
# reload all snapshots so we get our new snapshots here # reload all snapshots so we get our new snapshots here
for remote, zfs_cmd in remotes.items():
try:
if remote in remote_snapshots:
remote_snapshots[remote] = zsnaplib.get_snapshots(zfs_cmd=zfs_cmd)
except zsnaplib.ZFSSnapshotError:
del remote_snapshots[remote]
log.warning("Could not refresh snapshots on {}".format(remote))
snapshots = zsnaplib.get_snapshots(sudo) snapshots = zsnaplib.get_snapshots(sudo)
failed_send = send_snapshots(fslist, snapshots, config, sudo) failed_send = send_snapshots(fslist, snapshots, config, sudo)
if failed_send: if failed_send:

View File

@ -6,7 +6,7 @@ try:
except ImportError: except ImportError:
from distutils import setup from distutils import setup
version = '0.2' version = '0.3'
setup( setup(
name='zsnapper', name='zsnapper',

View File

@ -13,7 +13,7 @@ logger = 'zsnapper'
class ZFSSnapshotError(Exception): class ZFSSnapshotError(Exception):
pass pass
def do_zfs_command(args, sudo, pipecmd=None, zfs_cmd=[zfs_bin]): def do_zfs_command(args, sudo, zfs_cmd, pipecmd=None):
cmd = [] cmd = []
sudopw = None sudopw = None
if sudo: if sudo:
@ -55,6 +55,7 @@ def send_snapshot(
snap, snap,
remote_zfs_cmd, remote_zfs_cmd,
remote_target, remote_target,
zfs_cmd=[zfs_bin],
sudo=False, sudo=False,
send_opts=[], send_opts=[],
recv_opts=[], recv_opts=[],
@ -74,18 +75,18 @@ def send_snapshot(
pipecmd = remote_zfs_cmd + [ 'receive' ] + recv_opts + [ remote_target ] pipecmd = remote_zfs_cmd + [ 'receive' ] + recv_opts + [ remote_target ]
do_zfs_command(args, sudo, pipecmd=pipecmd) do_zfs_command(args, sudo, zfs_cmd, pipecmd=pipecmd)
def create_snapshot(fs, sudo=False): def create_snapshot(fs, sudo=False, zfs_cmd=[zfs_bin]):
d = datetime.datetime.now().strftime(time_format) d = datetime.datetime.now().strftime(time_format)
args = ['snapshot', '{}@{}'.format(fs, d)] args = ['snapshot', '{}@{}'.format(fs, d)]
do_zfs_command(args, sudo) do_zfs_command(args, sudo, zfs_cmd)
def get_filesystems(sudo=False, zfs_cmd=[zfs_bin]): def get_filesystems(sudo=False, zfs_cmd=[zfs_bin]):
args = ['list', '-H'] args = ['list', '-H']
out = do_zfs_command(args, sudo, zfs_cmd=zfs_cmd) out = do_zfs_command(args, sudo, zfs_cmd)
ret = set() ret = set()
for row in out.splitlines(): for row in out.splitlines():
@ -96,7 +97,7 @@ def get_filesystems(sudo=False, zfs_cmd=[zfs_bin]):
def get_snapshots(sudo=False, zfs_cmd=[zfs_bin]): def get_snapshots(sudo=False, zfs_cmd=[zfs_bin]):
args = [ 'list', '-H', '-t', 'snapshot' ] args = [ 'list', '-H', '-t', 'snapshot' ]
out = do_zfs_command(args, sudo, zfs_cmd=zfs_cmd) out = do_zfs_command(args, sudo, zfs_cmd)
snapshots = {} snapshots = {}
for row in out.splitlines(): for row in out.splitlines():
@ -115,10 +116,10 @@ def get_snapshots(sudo=False, zfs_cmd=[zfs_bin]):
return snapshots return snapshots
def remove_snapshot(fs, date, sudo=False): def remove_snapshot(fs, date, sudo=False, zfs_cmd=[zfs_bin]):
date = date.strftime(time_format) date = date.strftime(time_format)
args = [ 'destroy', '{}@{}'.format(fs, date) ] args = [ 'destroy', '{}@{}'.format(fs, date) ]
do_zfs_command(args, sudo) do_zfs_command(args, sudo, zfs_cmd)
def weed_snapshots( def weed_snapshots(