Merge pull request #80 from tuffnatty/pre-post-snapshot-cmd

Add support for pre- and post-snapshot scripts (#39)
This commit is contained in:
DatuX 2021-06-17 11:48:09 +02:00 committed by GitHub
commit 1286bfafd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 9 deletions

View File

@ -279,6 +279,19 @@ root@ws1:~# zfs-autobackup test --verbose
This also allows you to make several snapshots during the day, but only backup the data at night when the server is not busy. This also allows you to make several snapshots during the day, but only backup the data at night when the server is not busy.
## Running custom commands before and after snapshotting
If you need, e.g. to quiesce a couple of mysql databases to make on-disk data consistent before snapshotting, try:
```sh
zfs-autobackup \
--pre-snapshot-cmd 'daemon -f jexec mysqljail1 mysql -s -e "set autocommit=0;flush logs;flush tables with read lock;\\! echo \$\$ > /tmp/mysql_lock.pid && sleep 60"' \
--pre-snapshot-cmd 'daemon -f jexec mysqljail2 mysql -s -e "set autocommit=0;flush logs;flush tables with read lock;\\! echo \$\$ > /tmp/mysql_lock.pid && sleep 60"' \
--post-snapshot-cmd 'pkill -F /jails/mysqljail1/tmp/mysql_lock.pid' \
--post-snapshot-cmd 'pkill -F /jails/mysqljail2/tmp/mysql_lock.pid' \
test
```
## Thinning out obsolete snapshots ## Thinning out obsolete snapshots
The thinner is the thing that destroys old snapshots on the source and target. The thinner is the thing that destroys old snapshots on the source and target.
@ -493,7 +506,8 @@ Look in man ssh_config for many more options.
```console ```console
usage: zfs-autobackup [-h] [--ssh-config CONFIG-FILE] [--ssh-source USER@HOST] usage: zfs-autobackup [-h] [--ssh-config CONFIG-FILE] [--ssh-source USER@HOST]
[--ssh-target USER@HOST] [--keep-source SCHEDULE] [--ssh-target USER@HOST] [--keep-source SCHEDULE]
[--keep-target SCHEDULE] [--other-snapshots] [--keep-target SCHEDULE] [--pre-snapshot-cmd COMMAND]
[--post-snapshot-cmd COMMAND] [--other-snapshots]
[--no-snapshot] [--no-send] [--no-thinning] [--no-holds] [--no-snapshot] [--no-send] [--no-thinning] [--no-holds]
[--min-change BYTES] [--allow-empty] [--min-change BYTES] [--allow-empty]
[--ignore-replicated] [--strip-path N] [--ignore-replicated] [--strip-path N]
@ -531,6 +545,12 @@ optional arguments:
--keep-target SCHEDULE --keep-target SCHEDULE
Thinning schedule for old target snapshots. Default: Thinning schedule for old target snapshots. Default:
10,1d1w,1w1m,1m1y 10,1d1w,1w1m,1m1y
--pre-snapshot-cmd COMMAND
Run COMMAND before snapshotting (can be used multiple
times.
--post-snapshot-cmd COMMAND
Run COMMAND after snapshotting (can be used multiple
times.
--other-snapshots Send over other snapshots as well, not just the ones --other-snapshots Send over other snapshots as well, not just the ones
created by this tool. created by this tool.
--no-snapshot Don't create new snapshots (useful for finishing --no-snapshot Don't create new snapshots (useful for finishing

View File

@ -45,6 +45,10 @@ class ZfsAutobackup:
help='Target ZFS filesystem (optional: if not specified, zfs-autobackup will only operate ' help='Target ZFS filesystem (optional: if not specified, zfs-autobackup will only operate '
'as snapshot-tool on source)') 'as snapshot-tool on source)')
parser.add_argument('--pre-snapshot-cmd', metavar="COMMAND", default=[], action='append',
help='Run COMMAND before snapshotting (can be used multiple times.')
parser.add_argument('--post-snapshot-cmd', metavar="COMMAND", default=[], action='append',
help='Run COMMAND after snapshotting (can be used multiple times.')
parser.add_argument('--other-snapshots', action='store_true', parser.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.')
parser.add_argument('--no-snapshot', action='store_true', parser.add_argument('--no-snapshot', action='store_true',
@ -506,7 +510,9 @@ class ZfsAutobackup:
if not self.args.no_snapshot: if not self.args.no_snapshot:
self.set_title("Snapshotting") self.set_title("Snapshotting")
source_node.consistent_snapshot(source_datasets, source_node.new_snapshotname(), source_node.consistent_snapshot(source_datasets, source_node.new_snapshotname(),
min_changed_bytes=self.args.min_change) min_changed_bytes=self.args.min_change,
pre_snapshot_cmd=self.args.pre_snapshot_cmd,
post_snapshot_cmd=self.args.post_snapshot_cmd)
################# 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)

View File

@ -1,6 +1,7 @@
# python 2 compatibility # python 2 compatibility
from __future__ import print_function from __future__ import print_function
import re import re
import shlex
import subprocess import subprocess
import sys import sys
import time import time
@ -161,7 +162,7 @@ class ZfsNode(ExecuteNode):
"""determine uniq new snapshotname""" """determine uniq new snapshotname"""
return self.backup_name + "-" + time.strftime("%Y%m%d%H%M%S") return self.backup_name + "-" + time.strftime("%Y%m%d%H%M%S")
def consistent_snapshot(self, datasets, snapshot_name, min_changed_bytes): def consistent_snapshot(self, datasets, snapshot_name, min_changed_bytes, pre_snapshot_cmd=[], post_snapshot_cmd=[]):
"""create a consistent (atomic) snapshot of specified datasets, per pool. """create a consistent (atomic) snapshot of specified datasets, per pool.
""" """
@ -191,14 +192,24 @@ class ZfsNode(ExecuteNode):
self.verbose("No changes anywhere: not creating snapshots.") self.verbose("No changes anywhere: not creating snapshots.")
return return
# create consistent snapshot per pool try:
for (pool_name, snapshots) in pools.items(): for cmd in pre_snapshot_cmd:
cmd = ["zfs", "snapshot"] self.verbose("Running pre-snapshot-cmd")
self.run(cmd=shlex.split(cmd), readonly=False)
cmd.extend(map(lambda snapshot_: str(snapshot_), snapshots)) # create consistent snapshot per pool
for (pool_name, snapshots) in pools.items():
cmd = ["zfs", "snapshot"]
self.verbose("Creating snapshots {} in pool {}".format(snapshot_name, pool_name)) cmd.extend(map(lambda snapshot_: str(snapshot_), snapshots))
self.run(cmd, readonly=False)
self.verbose("Creating snapshots {} in pool {}".format(snapshot_name, pool_name))
self.run(cmd, readonly=False)
finally:
for cmd in post_snapshot_cmd:
self.verbose("Running post-snapshot-cmd")
self.run(cmd=shlex.split(cmd), readonly=False, valid_exitcodes=[])
def selected_datasets(self, exclude_received, exclude_paths): def selected_datasets(self, exclude_received, exclude_paths):
"""determine filesystems that should be backed up by looking at the special autobackup-property, systemwide """determine filesystems that should be backed up by looking at the special autobackup-property, systemwide