Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/scripts/send_zfs_filesystem.py
Views: 687
#!/usr/bin/env python1###############################################################################2#3# CoCalc: Collaborative Calculation4#5# Copyright (C) 2016, Sagemath Inc.6#7# This program is free software: you can redistribute it and/or modify8# it under the terms of the GNU General Public License as published by9# the Free Software Foundation, either version 3 of the License, or10# (at your option) any later version.11#12# This program is distributed in the hope that it will be useful,13# but WITHOUT ANY WARRANTY; without even the implied warranty of14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the15# GNU General Public License for more details.16#17# You should have received a copy of the GNU General Public License18# along with this program. If not, see <http://www.gnu.org/licenses/>.19#20###############################################################################2122# This is a very conservative approach to sending a ZFS filesystem from one machine to another,23# with exactly the same name on each side, and not including subfilesystems recursively.24# It determines snapshots locally and remotely.25# It sends first snapshot, if nothing exists remotely. Then...26# Then it does this from local for each successive snapshot a0 a1:27#28# time zfs send -v -i a0 a1 | ssh remote "zfs recv filesystem"29#30# If this fails (e.g., due to network/buffer issues), it will retry the31# number of times giving by the RETRIES parameter:3233RETRIES = 23435# Why not just use "zfs send -I"? Because it often doesn't work. Simple as that.36# The main reason for existence of this script is due to zfsonlinux being still37# somewhat broken.3839import argparse, os, sys, time40from subprocess import Popen, PIPE414243def cmd(v):44t = time.time()45print v if isinstance(v, str) else ' '.join(v),46sys.stdout.flush()47out = Popen(48v, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=isinstance(v, str))49x = out.stdout.read()50y = out.stderr.read()51e = out.wait()52if e:53raise RuntimeError(y)54print " (%.2f seconds)" % (time.time() - t)55return x565758def system(c):59for n in range(RETRIES):60print c61if not os.system(c):62return63raise RuntimeError('error running "%s" %s times' % (c, RETRIES))646566def send(filesystem, remote):67if ':' in filesystem:68local_filesystem, remote_filesystem = filesystem.split(':')69else:70local_filesystem = remote_filesystem = filesystem71print "sending %s to %s on %s" % (local_filesystem, remote_filesystem,72remote)7374# get list of snapshots locally, sorted by time.75s = [76'zfs', 'list', '-r', '-H', '-t', 'snapshot', '-o', 'name', '-s',77'creation'78]79local_snapshots = cmd(s + [local_filesystem]).splitlines()8081if len(local_snapshots) == 0:82#TODO83raise RuntimeError("you must have at least one local snapshot of %s" %84local_filesystem)8586# get list of snapshots remotely, sorted by time87if remote == 'localhost':88remote_snapshots = cmd(' '.join(s + [remote_filesystem])).splitlines()89else:90remote_snapshots = cmd(91"ssh %s '%s'" % (remote,92' '.join(s + [remote_filesystem]))).splitlines()9394local_snapshot_names = set([x.split('@')[1] for x in local_snapshots])9596if len(remote_snapshots) == 0:97# transfer up to first snapshot to remote98first = local_snapshots[0]99if remote == 'localhost':100system('time zfs send -v %s | zfs recv -F %s' %101(first, remote_filesystem))102else:103system('time zfs send -v %s | ssh %s "zfs recv -F %s"' %104(first, remote, remote_filesystem))105start = first106else:107# transfer starting with newest snapshot this available locally (destructively killing all older snapshots).108i = len(remote_snapshots) - 1109while i >= 0 and remote_snapshots[i].split(110'@')[1] not in local_snapshot_names:111i -= 1112if i == -1:113start = local_snapshots[0]114else:115start = remote_snapshots[i]116117i = local_snapshots.index(118"%s@%s" % (local_filesystem, start.split('@')[1]))119v = range(i + 1, len(local_snapshots))120n = 1121for j in v:122print "(%s/%s) sending %s" % (n, len(v), local_snapshots[j])123if remote == 'localhost':124system('time zfs send -v -i %s %s | zfs recv -F %s' % (125local_snapshots[j - 1], local_snapshots[j], remote_filesystem))126else:127system('time zfs send -v -i %s %s | ssh %s "zfs recv -F %s"' %128(local_snapshots[j - 1], local_snapshots[j], remote,129remote_filesystem))130n += 1131132133if __name__ == "__main__":134135parser = argparse.ArgumentParser(description="Send ZFS filesystems")136parser.add_argument(137"remote",138help="remote machine's ip address/hostname (or localhost)",139type=str)140parser.add_argument(141"filesystem",142help=143"name of filesystem to send or name_local:name_remote to send to a different remote name",144type=str,145nargs="+")146args = parser.parse_args()147148for filesystem in args.filesystem:149send(filesystem, args.remote)150151152