forked from Rudd-O/zfs-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathzfs-fetch-pool
executable file
·160 lines (138 loc) · 4.87 KB
/
zfs-fetch-pool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python
from subprocess import Popen,PIPE,check_call,call
import datetime
import sys
import os
import logging
import re
logging.basicConfig()
logging.getLogger().setLevel(logging.WARN)
if "-v" in sys.argv[1:]:
logging.getLogger().setLevel(logging.INFO)
sys.argv = [ sys.argv[0] ] + sys.argv[2:]
log = logging.getLogger().info
error = logging.getLogger().error
# to ensure the ZFS utilities are in the path. Just in case.
os.environ["PATH"] = "/sbin:/usr/sbin:/usr/local/sbin:" + os.environ["PATH"]
try:
local_pool = sys.argv[1]
m = re.findall("^(|ssh://|rsh://)([^/.]+)@(.+)/(.+)$",sys.argv[2])[0]
method,user,host,remote_pool = m
if not method: method = "ssh"
elif method == "ssh://": method = "ssh"
elif method == "rsh://": method = "rsh"
else: assert False
log("Connecting through %s as %s to %s and graft %s into %s",
method,user,host,remote_pool,local_pool)
except (IndexError,ValueError), e:
error("usage: zfs-fetch-pool [-v] <localpool> <user@host:remotepool>")
sys.exit(os.EX_USAGE)
local_cmd = ["zfs"]
remote_cmd = [method,"-l",user,host]
def get_todays_snapshot_name():
d = datetime.date.today()
name = "%04d-%02d-%02d"%(d.year,d.month,d.day)
return "zfs-tools-%s"%name
def parse_zfs_list(text):
datasets = [ line.split()[0] for line in text.splitlines() if line ][1:]
filesystems = [ f for f in datasets if "@" not in f ]
snapshots = [ f.split("@",1) for f in datasets if "@" in f ]
b = {}
for fs,snapname in snapshots:
if fs not in b: b[fs] = []
b[fs].append(snapname)
return filesystems,b
def get_datasets(cmd):
return parse_zfs_list(Popen(cmd + ["list"], stdout=PIPE).communicate()[0])
def get_local_datasets(): return get_datasets(local_cmd)
def get_remote_datasets(): return get_datasets(remote_cmd)
def make_snapshot(cmd,pool,name):
check_call(cmd + ["snapshot","-r","%s@%s"%(pool,name)])
def remove_snapshot(cmd,pool,name):
check_call(cmd + ["destroy","-r","%s@%s"%(pool,name)])
todays_snapshot = get_todays_snapshot_name()
if not get_remote_datasets()[1] or todays_snapshot not in get_remote_datasets()[1][remote_pool]:
log("Making today's snapshot in %s"%host)
make_snapshot(remote_cmd,remote_pool,todays_snapshot)
local_filesystems,local_snapshots = get_local_datasets()
remote_filesystems,remote_snapshots = get_remote_datasets()
for remote_fs in remote_filesystems:
local_fs = "%s/%s"%(local_pool,remote_fs)
if local_fs not in local_filesystems:
# send today's snapshot over there
log("Receiving %s from %s in full"%(remote_fs,host))
sendcmd = remote_cmd + [
"send",
"%s@%s"%(remote_fs,todays_snapshot)
]
receivecmd = local_cmd + [
"recv",
"%s@%s"%(local_fs,todays_snapshot)
]
else:
if local_fs not in local_snapshots:
error("%s exists in local pool %s but has no snapshots"%(remote_fs,local_pool))
continue
# if the latest snapshot is the backup server
if todays_snapshot in local_snapshots[local_fs]:
# we safely continue to the next item
log("Skipping %s: today's snapshot already in pool %s"%(local_fs,local_pool))
continue
# find latest common ancestor
last_common_snapshot = None
for s in [ m for m in local_snapshots[local_fs] if m.startswith("zfs-tools-") ]:
if s in remote_snapshots[remote_fs]: last_common_snapshot = s
# no common ancestor?
if not last_common_snapshot:
# bomb out, motherfucker!
error("%s exists in local pool %s, but has no common snapshots locally"%remote_fs,local_pool)
continue
# and now we're ready to do the incremental backup
log("Receiving %s from %s incrementally"%(remote_fs,host))
sendcmd = remote_cmd + [
"send",
"-i",
"@%s"%last_common_snapshot,
"%s@%s"%(remote_fs,todays_snapshot)
]
receivecmd = local_cmd + [
"recv",
"-F",
"%s@%s"%(local_fs,todays_snapshot)
]
receive = Popen(receivecmd,stdin=PIPE)
send = Popen(sendcmd,stdout=receive.stdin)
receive_ret = receive.wait()
if receive_ret:
error("ZFS recv failed with return code %s."%receive_ret)
send_ret = send.wait()
if send_ret:
error("ZFS send failed with return code %s. Verify if zfs-shell is working."%send_ret)
if receive_ret != 0 or send_ret != 0:
ret = [ m for m in [receive_ret,send_ret] if m != 0][0]
error("Aborting prematurely without destructive action")
sys.exit(ret)
local_filesystems,local_snapshots = get_local_datasets()
remote_filesystems,remote_snapshots = get_remote_datasets()
for snapshot in [
m for m
in remote_snapshots["%s"%remote_pool]
if m.startswith("zfs-tools-")
]:
if snapshot != todays_snapshot:
log("Removing obsolete remote snapshot %s"%snapshot)
remove_snapshot(
remote_cmd,
remote_pool,
snapshot)
for snapshot in [
m for m
in local_snapshots["%s/%s"%(local_pool,remote_pool)]
if m.startswith("zfs-tools-")
]:
if snapshot != todays_snapshot:
log("Removing obsolete local snapshot %s"%snapshot)
remove_snapshot(
local_cmd,
"%s/%s"%(local_pool,remote_pool),
snapshot)