Skip to content

Commit

Permalink
Contrib a PoC shell script for logic addressing parts of oetiker#503
Browse files Browse the repository at this point in the history
  • Loading branch information
jimklimov committed Aug 12, 2020
1 parent e0506e3 commit 359b112
Showing 1 changed file with 179 additions and 0 deletions.
179 changes: 179 additions & 0 deletions contrib/synczbe
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#!/usr/bin/env bash

# bashisms: $RANDOM is used, and this:
set -o pipefail

# (C) 2020 by Jim Klimov
# PoC for: https://github.com/oetiker/znapzend/issues/503

# Warning: no code support for now to manage rootfs datasets with children!

# Here we send ALL incremental snapshots to allow branching off destinations
# Those not made by znapzend will not be cleaned by policy after regular sync!

# Assumptions: the datasets we care about are similarly named and structured
# zoneroots or rootfs'es under one container directory on source and dest:

# Examples below are for the setup discussed in the issue; you'll certainly
# need to set your own, as in `SR=rpool/zones/z1/ROOT DEBUG=no ./synczbe` :)
[ -n "$SR" ] || \
SR="nvpool/zones/omni151018/ROOT"
DR="backup-adata/snapshots/$SR"

# For rootfs SR=rpool/ROOT or similar.
# You can find the "SR" names for local zones by interpolating:
# ...zone root mountpoints from the OS:
# MPTZONEROOTS="`zoneadm list -cp | awk -F: '{print $4}' | egrep -v '^/$'`"
# /zones/omni151018
# ...and looking under znapzend configured source trees:
# ZFSZONEROOTS="`zfs list -Ho name,mountpoint -t filesystem -r nvpool/zones | egrep '(/ROOT\s|\s/)'`"
# nvpool/zones/lx5 /zones/lx5
# nvpool/zones/omni151018 /zones/omni151018
# nvpool/zones/omni151018/ROOT legacy
# In example above, "lx5" is not a root of a currently configured/recognized
# local zone, and/or it is a brand with some different structure to handle.
# The "omni151018" is one to process with this script, finding the mountpoint
# in one line and confirming that this dataset has a /ROOT child in another.

# Neuter writes by default until we are ready
ZFSW="echo ###WOULD### :; zfs"
[ "$DEBUG" = no ] && ZFSW=zfs \
&& echo "WARNING: zfs operations for changing pool data are ENABLED, be sure to NOT HAVE znapzend service RUNNING!" >&2 \
|| echo "WARNING: debug mode by default, zfs operations for changing pool data are DISABLED: >&2"

case "$SR" in
*/ROOT) ;;
*/) echo "ERROR: SR should be a dataset name" >&2 ;;
*) echo "WARNING: SR='$SR' is not a dataset name ending with '/ROOT', you might want to revise that (press Ctrl+C to abort now)" >&2
sleep 5
;;
esac

# First phase:
# 1a) find latest available common snap in history owner on older dst (children
# included) and same-named snapshot (with children) on newest src.
# 1b) zfs clone that into same relative name(s) on dst as newest src
# 1c) zfs promote all those clones on dst
# 1d) Replicate incrementally from that last common point to current tip
# (note `zfs send -R -I ...` is recursive)

# e.g. zbe-30 is last history owner on backup, zbe-60 on source
# clones on source are branches of zbe-60, clones on dst between
# zbe-31 and zbe-59 are standalone by znapzend and own their histories =>
# zfs list -d1 -tfilesystem -o name,origin -r {backup-adata/snapshots/,}nvpool/zones/omni151018/ROOT
### ^^^ Find and iterate rootfs/zbe children to find the recursively common snap between src and dst
# zfs clone backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-30@2019-01-29-09:14:13 backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-60
# zfs promote backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-60
# zfs send -R -I nvpool/zones/omni151018/ROOT/zbe-60@{2019-01-29-09:14:13,2020-08-09-19:22:53} | mbuffer -m 128M | zfs recv -vue backup-adata/snapshots/nvpool/zones/omni151018/ROOT

# TODO: check if there are child datasets under ZBEs or rootfs'es
# and for now bail as unsupported (to develop)

# TODO also detect if the current src and dst are already in sync
# (same name, same tip; presume or check same intermediate snaps)
# and then skip phase 1 quickly.
echo "TODO: Phase 1: Make newest history owners same-named (and with same incremental snaps) on src and dst" >&2

# Phase 2: rewrite existing standalone backups
echo "=== Gathering data for Phase 2 over '$SR' => '$DR' resync ..." >&2

# 2a) Rediscover current layout:
# Note that sorting is magic :) e.g. by creation time on destination, you see
# the replicated tip first and clones made from it by `zfs recv` appeared later
# while on source they would be more ordered (if source was not replicated too).
SNO="`zfs list -Honame -d1 -tfilesystem,volume -screation -o name,origin -r "$SR"`"
DNO="`zfs list -Honame -d1 -tfilesystem,volume -screation -o name,origin -r "$DR"`"

# 2b) Find history owners on destination (rootfs/zbe names whom would we rewrite if we still can?)
# Note we ignore all lines that list a snapshot "@" here, although some histories can be owned by different tips:
# backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-27 backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-60@2018-10-17-21:49:25
# backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-28 backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-30@2019-02-13-01:22:08 ### <<< zbe-30@...
# backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-29 backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-60@2019-01-29-09:14:13
# backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-30 backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-60@2019-01-29-09:14:13
# backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-31 -
# This can be due to a miss in choice of latest common snapshot to clone and
# replicate that is not the latest branch on destination, or independent trees
# like rootfs with distro and failsafe-boot env, or other distros in same rpool.
# Might go over such later, filtering by "${newestbe}\@" ...

DZ="`echo "$DNO" | grep -v @ | awk '{print $1}' | awk -F/ '{print $NF}' | grep -v ROOT`"

# 2c) Find all histories we can rebase, and their span of snapshots
# from branching point to tip of the dataset:
# zbe-31 => nvpool/zones/omni151018/ROOT/zbe-60@2019-10-04-12:28:11 .. nvpool/zones/omni151018/ROOT/zbe-31@znapzend-auto-2020-08-08T00:38:11Z
# zbe-32 => nvpool/zones/omni151018/ROOT/zbe-60@2019-11-15-14:33:16 .. nvpool/zones/omni151018/ROOT/zbe-32@znapzend-auto-2020-08-08T00:38:11Z
# === zbe-33 not in source
# zbe-34 => nvpool/zones/omni151018/ROOT/zbe-60@2019-12-19-09:10:31 .. nvpool/zones/omni151018/ROOT/zbe-34@znapzend-auto-2020-08-08T00:38:11Z
# ...
# zbe-58 => nvpool/zones/omni151018/ROOT/zbe-60@2020-06-22-16:58:55 .. nvpool/zones/omni151018/ROOT/zbe-58@znapzend-auto-2020-08-08T00:38:11Z
# zbe-59 => nvpool/zones/omni151018/ROOT/zbe-60@2020-08-09-19:22:53 ..
# zbe-60 => - .. nvpool/zones/omni151018/ROOT/zbe-60@2020-08-09-19:22:53
# No big surprise with znapzend involved that newest snapnames all look the same
# For zbe-59 - it was recently branched on source from zbe-60 (in fact vice
# versa, but then `zfs promote` reshuffled them) and does not yet have snapshots
# of its own. zbe-60 is the current history owner.

RESFINAL=0
echo "=== Processing collected dataset details..." >&2
for Z in $DZ ; do
RES=0
ZL="`echo "$SNO" | egrep "^${SR}/${Z}\s"`" || { echo "!!! SKIP: $Z not in source" >&2 ; continue ; }
O="`echo "$ZL" | awk '{print $NF}'`" # origin on src
L="`zfs list -d1 -Honame -tsnapshot -screation -r "${SR}/${Z}" | tail -1`" || continue # latest on src
echo "$Z => $O .. $L" >&2
# We got listing for 3b up here

if [ -z "$O" -o "$O" = "-" ] ; then
echo "=== SKIP a history owner: $Z" >&2
continue
fi

# OS is snapname, but useful is OSD with ZBE/rootfs name relative to ROOT/
#OS="`echo "$O" | sed 's/^.*@//'`"
OSD="`echo "$O" | sed "s@^${SR}/@@"`"

echo "=== Move away the destination to back it up for now: '$DR/$Z' => '$DR/$Z.bak'" >&2
$ZFSW set zoned=off "$DR/$Z"
$ZFSW rename "$DR/$Z" "$DR/$Z.bak"
$ZFSW inherit zoned "$DR/$Z.bak"

#echo "=== Make new destination clone: '$DR/$OSD' => '$DR/$Z'" >&2
#$ZFSW clone "$DR/$OSD" "$DR/$Z"
# UGH: manually making clone and send+recv fails with:
# cannot receive new filesystem stream: destination 'backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-31' is a clone
# must destroy it to overwrite it
# but having no named destination and sufficient source snapshot automakes the dest

if [ -z "$L" ] ; then
# create snap, send it as the increment to have ZFS create clone the way it likes
# PoC NOTE: Assumes GNU date or compatible with +%s to add uniqueness to the snapname string
L="$SR/$Z@auto-firstsnap-`date -u +%s || date -u | tr '[ ,:@_\.]' '-'`-$$-$RANDOM"
echo "=== SNAP a recent clone (has no snaps yet on src): $Z => make '$L' to sync" >&2
$ZFSW snapshot "$L"
#continue
fi

#LS="`echo "$L" | sed 's/^.*@//'`"

# NOTE: For rootfs with children, have all children clones prepared before zfs send/recv

echo "=== Update the new destination + create dst clone: increment '$O' => '$L', write into autotarget under '$DR' (overwrite with -F if needed?)" >&2
echo ":; time zfs send -R -I '$O' '$L' | mbuffer -m 128M | zfs recv -vue '$DR'"
time zfs send -R -I "$O" "$L" | mbuffer -m 128M | $ZFSW recv -vue "$DR" ; RES=$?
#zfs send -R -I nvpool/zones/omni151018/ROOT/zbe-60@{2019-01-29-09:14:13,2020-08-09-19:22:53} | mbuffer -m 128M | $ZFSW recv -vue backup-adata/snapshots/nvpool/zones/omni151018/ROOT
# found clone origin backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-60@2019-10-04-12:28:11
# receiving incremental stream of nvpool/zones/omni151018/ROOT/zbe-31@znapzend-auto-2019-10-11T17:30:00Z into backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-31@znapzend-auto-2019-10-11T17:30:00Z
# in @ 0.0 KiB/s, out @ 0.0 KiB/s, 316 KiB total, buffer 1% full
# received 312B stream in 103 seconds (3B/sec)
# receiving incremental stream of nvpool/zones/omni151018/ROOT/zbe-31@znapzend-auto-2019-10-12T00:00:00Z into backup-adata/snapshots/nvpool/zones/omni151018/ROOT/zbe-31@znapzend-auto-2019-10-12T00:00:00Z
# in @ 0.0 KiB/s, out @ 0.0 KiB/s, 316 KiB total, buffer 1% full
# received 312B stream in 6 seconds (52B/sec)

echo "=== DONE ($RES) with $Z"; echo ""
[ "$RES" = 0 ] || RESFINAL="$RES"
done

echo "TODO: Pass over the current dataset layout and see if there are any datasets on source that are not on dest, remove ZBE-N.bak on dest if unneeded anymore" >&2

echo "Final result of sync: $RESFINAL" >&2
exit $RESFINAL

0 comments on commit 359b112

Please sign in to comment.