zfs_mgmt 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/COPYING +674 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +41 -0
- data/README.md +191 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/readsnaps +4 -0
- data/bin/setup +8 -0
- data/bin/zfs-list-snapshots +10 -0
- data/bin/zfsfuncs +126 -0
- data/bin/zfsmgr +40 -0
- data/bin/zfsrecvman +154 -0
- data/bin/zfssendman +252 -0
- data/bin/zfssnapman +69 -0
- data/lib/zfs_mgmt/version.rb +3 -0
- data/lib/zfs_mgmt.rb +314 -0
- data/zfs_mgmt.gemspec +46 -0
- metadata +156 -0
data/Gemfile.lock
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
zfs_mgmt (0.2.4)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.3)
|
10
|
+
filesize (0.2.0)
|
11
|
+
rake (13.0.1)
|
12
|
+
rspec (3.9.0)
|
13
|
+
rspec-core (~> 3.9.0)
|
14
|
+
rspec-expectations (~> 3.9.0)
|
15
|
+
rspec-mocks (~> 3.9.0)
|
16
|
+
rspec-core (3.9.1)
|
17
|
+
rspec-support (~> 3.9.1)
|
18
|
+
rspec-expectations (3.9.0)
|
19
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
20
|
+
rspec-support (~> 3.9.0)
|
21
|
+
rspec-mocks (3.9.1)
|
22
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
23
|
+
rspec-support (~> 3.9.0)
|
24
|
+
rspec-support (3.9.2)
|
25
|
+
text-table (1.2.4)
|
26
|
+
thor (1.0.1)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
bundler (~> 1.16)
|
33
|
+
filesize
|
34
|
+
rake (>= 12.3.3)
|
35
|
+
rspec (~> 3.0)
|
36
|
+
text-table
|
37
|
+
thor
|
38
|
+
zfs_mgmt!
|
39
|
+
|
40
|
+
BUNDLED WITH
|
41
|
+
1.16.6
|
data/README.md
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
# ZfsMgmt
|
2
|
+
|
3
|
+
zfs_mgmt aims to provide some useful helpers for managing zfs snapshots, and eventually send/recv duties via the zfsmgr script in bin/.
|
4
|
+
|
5
|
+
Currently only snapshot destruction is implemented by a policy specification stored in zfs properties.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Currently zfs_mgmt is only useful for it's zfsmgr binary, although
|
10
|
+
eventually the library might be useful for writing other applications
|
11
|
+
around managing zfs.
|
12
|
+
|
13
|
+
Therefore, building the gem and installing, or running ruby inside the src/ directory would be most useful:
|
14
|
+
|
15
|
+
$ ruby -I lib bin/zfsmgr
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
The most common usage pattern would be to set zfs properties as explained below, then use **zfsmgr snapshot policy** to print a table of what would be kept and for what reason. Then use **zfsmgr snapshot destroy --noop** to see what would be destroyed, and finally **zfsmgr snapshot destroy** without the --noop option to actually remove snapshots.
|
20
|
+
|
21
|
+
Commands:
|
22
|
+
zfsmgr help [COMMAND] # Describe available commands or one specific command
|
23
|
+
zfsmgr snapshot SUBCOMMAND ...ARGS # manage snapshots
|
24
|
+
zfsmgr zfsget [ZFS] # execute zfs get for the given properties and types and parse the output into a nested hash
|
25
|
+
|
26
|
+
|
27
|
+
zfsmgr snapshot create # execute zfs snapshot based on zfs properties
|
28
|
+
zfsmgr snapshot destroy # apply the snapshot destroy policy to zfs
|
29
|
+
zfsmgr snapshot help [COMMAND] # Describe subcommands or one specific subcommand
|
30
|
+
zfsmgr snapshot policy # print the policy table for zfs
|
31
|
+
|
32
|
+
Options:
|
33
|
+
[--noop], [--no-noop] # pass -n option to zfs commands
|
34
|
+
[--verbose], [--no-verbose] # pass -v option to zfs commands
|
35
|
+
[--debug], [--no-debug] # set logging level to debug
|
36
|
+
[--filter=FILTER] # only act on zfs matching this regexp
|
37
|
+
# Default: .+
|
38
|
+
|
39
|
+
|
40
|
+
## Example output
|
41
|
+
[aranc23@beast:~/src/zfs_mgmt] (master)$ zfs get all | egrep 'zfsmgmt.+local'
|
42
|
+
backup zfsmgmt:manage true local
|
43
|
+
backup zfsmgmt:policy 10y60m104w365d168h local
|
44
|
+
backup zfsmgmt:minage 7D local
|
45
|
+
backup zfsmgmt:ignoresnaps ^syncoid_ local
|
46
|
+
backup/beast/data/archive zfsmgmt:policy 1h local
|
47
|
+
backup/beast/data/archive zfsmgmt:minage 1s local
|
48
|
+
backup/beast/data/archive zfsmgmt:matchsnaps archive local
|
49
|
+
|
50
|
+
[aranc23@beast:~/src/zfs_mgmt] (master)$ ruby -I lib bin/zfsmgr snapshot policy --filter pics
|
51
|
+
+------------------------------------------------------------+---------------------------+--------------------+------------+--------------+---------+--------+
|
52
|
+
| snap | creation | hourly | daily | weekly | monthly | yearly |
|
53
|
+
+------------------------------------------------------------+---------------------------+--------------------+------------+--------------+---------+--------+
|
54
|
+
| backup/beast/data/pics@autosnap-2020-02-27T12:17:01-0600 | 2020-02-27T12:17:01-06:00 | 2020-02-27 Hour 12 | | | | |
|
55
|
+
| backup/beast/data/pics@autosnap-2020-02-27T11:17:01-0600 | 2020-02-27T11:17:01-06:00 | 2020-02-27 Hour 11 | | | | |
|
56
|
+
| backup/beast/data/pics@autosnap-2020-02-27T10:17:01-0600 | 2020-02-27T10:17:01-06:00 | 2020-02-27 Hour 10 | | | | |
|
57
|
+
| backup/beast/data/pics@autosnap-2020-02-27T09:17:01-0600 | 2020-02-27T09:17:02-06:00 | 2020-02-27 Hour 09 | | | | |
|
58
|
+
| backup/beast/data/pics@autosnap-2020-02-27T08:17:01-0600 | 2020-02-27T08:17:01-06:00 | 2020-02-27 Hour 08 | | | | |
|
59
|
+
| backup/beast/data/pics@autosnap-2020-02-27T07:17:01-0600 | 2020-02-27T07:17:01-06:00 | 2020-02-27 Hour 07 | | | | |
|
60
|
+
| backup/beast/data/pics@autosnap-2020-02-27T06:17:01-0600 | 2020-02-27T06:17:01-06:00 | 2020-02-27 Hour 06 | | | | |
|
61
|
+
| backup/beast/data/pics@autosnap-2020-02-27T05:17:01-0600 | 2020-02-27T05:17:01-06:00 | 2020-02-27 Hour 05 | | | | |
|
62
|
+
| backup/beast/data/pics@autosnap-2020-02-27T04:17:01-0600 | 2020-02-27T04:17:02-06:00 | 2020-02-27 Hour 04 | | | | |
|
63
|
+
| backup/beast/data/pics@autosnap-2020-02-27T03:17:01-0600 | 2020-02-27T03:17:01-06:00 | 2020-02-27 Hour 03 | | | | |
|
64
|
+
| backup/beast/data/pics@autosnap-2020-02-27T02:17:01-0600 | 2020-02-27T02:17:01-06:00 | 2020-02-27 Hour 02 | | | | |
|
65
|
+
| backup/beast/data/pics@autosnap-2020-02-27T01:17:01-0600 | 2020-02-27T01:17:02-06:00 | 2020-02-27 Hour 01 | | | | |
|
66
|
+
| backup/beast/data/pics@autosnap-2020-02-27T00:17:01-0600 | 2020-02-27T00:17:01-06:00 | 2020-02-27 Hour 00 | 2020-02-27 | | | |
|
67
|
+
...
|
68
|
+
| backup/beast/data/pics@zfssendman-20140604092215 | 2014-06-04T09:22:43-05:00 | | 2014-06-04 | 2014 Week 22 | 2014-06 | |
|
69
|
+
| backup/beast/data/pics@migrate3 | 2014-05-26T08:17:31-05:00 | | 2014-05-26 | | | |
|
70
|
+
| backup/beast/data/pics@migrate2 | 2014-05-25T21:57:28-05:00 | | 2014-05-25 | 2014 Week 21 | | |
|
71
|
+
| backup/beast/data/pics@migrate1 | 2014-05-24T10:31:56-05:00 | | 2014-05-24 | 2014 Week 20 | 2014-05 | 2014 |
|
72
|
+
| backup/beast/data/pics@20131108144154 | 2013-11-08T14:41:57-06:00 | | 2013-11-08 | 2013 Week 44 | 2013-11 | 2013 |
|
73
|
+
+------------------------------------------------------------+---------------------------+--------------------+------------+--------------+---------+--------+
|
74
|
+
|
75
|
+
[aranc23@beast:~/src/zfs_mgmt] (master)$ ruby -I lib bin/zfsmgr snapshot destroy --filter pics --noop
|
76
|
+
I, [2020-02-27T16:27:33.381645 #4914] INFO -- : deleting 21 snapshots for backup/beast/data/pics
|
77
|
+
I, [2020-02-27T16:27:33.381731 #4914] INFO -- : zfs destroy -pn backup/beast/data/pics@autosnap_2020-02-19_21:00:05_hourly,autosnap_2020-02-19_22:00:05_hourly,autosnap_2020-02-19_23:00:01_hourly,autosnap_2020-02-20_00:00:05_daily,autosnap_2020-02-20_01:00:04_hourly,autosnap_2020-02-20_02:00:04_hourly,autosnap_2020-02-20_03:00:04_hourly,autosnap_2020-02-20_04:00:05_hourly,autosnap_2020-02-20_05:00:05_hourly,autosnap_2020-02-20_07:00:04_hourly,autosnap_2020-02-20_08:00:01_hourly,autosnap_2020-02-20_09:00:05_hourly,autosnap_2020-02-20_10:00:05_hourly,autosnap_2020-02-20_11:00:05_hourly,autosnap_2020-02-20_12:00:05_hourly,autosnap_2020-02-20_13:00:01_hourly,autosnap_2020-02-20_14:00:05_hourly,autosnap_2020-02-20_15:00:05_hourly,autosnap_2020-02-20_16:00:05_hourly,autosnap_2020-02-20_17:00:05_hourly,autosnap_2020-02-20_18:00:05_hourly
|
78
|
+
destroy backup/beast/data/pics@autosnap_2020-02-19_21:00:05_hourly
|
79
|
+
destroy backup/beast/data/pics@autosnap_2020-02-19_22:00:05_hourly
|
80
|
+
destroy backup/beast/data/pics@autosnap_2020-02-19_23:00:01_hourly
|
81
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_00:00:05_daily
|
82
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_01:00:04_hourly
|
83
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_02:00:04_hourly
|
84
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_03:00:04_hourly
|
85
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_04:00:05_hourly
|
86
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_05:00:05_hourly
|
87
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_07:00:04_hourly
|
88
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_08:00:01_hourly
|
89
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_09:00:05_hourly
|
90
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_10:00:05_hourly
|
91
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_11:00:05_hourly
|
92
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_12:00:05_hourly
|
93
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_13:00:01_hourly
|
94
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_14:00:05_hourly
|
95
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_15:00:05_hourly
|
96
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_16:00:05_hourly
|
97
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_17:00:05_hourly
|
98
|
+
destroy backup/beast/data/pics@autosnap_2020-02-20_18:00:05_hourly
|
99
|
+
reclaim 0
|
100
|
+
|
101
|
+
## Development
|
102
|
+
|
103
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
104
|
+
|
105
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
106
|
+
|
107
|
+
## Contributing
|
108
|
+
|
109
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/aranc23/zfs_mgmt.
|
110
|
+
|
111
|
+
## zfs user properties
|
112
|
+
|
113
|
+
Destruction of zfs snapshots is based on the following zfs user properties:
|
114
|
+
|
115
|
+
### zfsmgmt:manage
|
116
|
+
manage snapshots for this filesystem if this property is 'true' (string literal)
|
117
|
+
|
118
|
+
### zfsmgmt:policy
|
119
|
+
|
120
|
+
A policy specification consisting of the number of snapshots of a
|
121
|
+
certain time frame to keep. A zfs must have a valid policy
|
122
|
+
specification or zfs_mgmt will not destroy any snapshots.
|
123
|
+
|
124
|
+
Examples:
|
125
|
+
- 30d ( 30 daily snapshots )
|
126
|
+
- 8w15d (8 weekly, and 15 daily snapshots)
|
127
|
+
- 1y1m1y1d1h (1 of each time frame worth of snapshots)
|
128
|
+
- 72h (72 hourly snapshots)
|
129
|
+
|
130
|
+
The order in which each timeframe is listed in does not matter, and the supported specs are as follows:
|
131
|
+
|
132
|
+
- h - hourly
|
133
|
+
- d - daily
|
134
|
+
- w - weekly (Sunday)
|
135
|
+
- m - monthly
|
136
|
+
- y - yearly
|
137
|
+
|
138
|
+
### zfsmgmt:minage
|
139
|
+
The minimum age of a snapshot before it will be considered for
|
140
|
+
deletion, as specified in seconds, or using a multiplier of:
|
141
|
+
|
142
|
+
- s (seconds, same as not specifiying a multiplier)
|
143
|
+
- m (minutes, x60)
|
144
|
+
- h (hours, x60x60)
|
145
|
+
- d (days, x24x60x60)
|
146
|
+
- w (weeks, x7x24x60x60)
|
147
|
+
|
148
|
+
The intended purpose of minage is to keep recent snapshots regardless
|
149
|
+
of policy, possibly to ensure zfs send/recv has recent snapshots to
|
150
|
+
work with, or simply out of paranoia.
|
151
|
+
|
152
|
+
### zfsmgmt:matchsnaps
|
153
|
+
If this property is set, the snapshot portion of a snapshot name
|
154
|
+
(right of the @) must match this as interpreted as a regular
|
155
|
+
expression in order to match the policy as specified above. The
|
156
|
+
intended use is to match application specific snapshots, (ie: ^backup-
|
157
|
+
) in an environment where automatic snapshots are still created but
|
158
|
+
there is no need to keep them. Snapshots matching this pattern can and
|
159
|
+
will still be deleted if they aren't marked to be saved by the policy
|
160
|
+
in place for the zfs.
|
161
|
+
|
162
|
+
### zfsmgmt:ignoresnaps
|
163
|
+
Ignore snapshots matching this regexp pattern. They are neither used
|
164
|
+
to match the specified policy for the zfs, nor will they be deleted.
|
165
|
+
The intended use is match zfs send/recv snapshots or hand-created
|
166
|
+
snapshots, etc. ie: ^syncoid_
|
167
|
+
|
168
|
+
### zfsmgmt:snapshot
|
169
|
+
If this property is 'true' then create a snapshot in the format of
|
170
|
+
zfsmgmt-%FT%T%z. If this property is 'recursive' then create a
|
171
|
+
recursive snapshot of this zfs.
|
172
|
+
|
173
|
+
### zfsmgmt:snap_prefix
|
174
|
+
Change the zfsmgmt portion of created snapshots, ie: 'autosnap' would
|
175
|
+
create snapshots called autosnap-%FT%T%z.
|
176
|
+
|
177
|
+
### zfsmgmt:snap_timestamp
|
178
|
+
strftime format string used when creating snapshot names, default
|
179
|
+
being %FT%T%z.
|
180
|
+
|
181
|
+
## Snapshot Management / zfs destroy
|
182
|
+
When destroying snapshots according to a given policy, all snapshots
|
183
|
+
should be considered for deletion and all snapshots should be
|
184
|
+
considered as potentially satisfying the retention policy regardless
|
185
|
+
of the name of the snapshot. Only the creation property really
|
186
|
+
matters unless the user configures zfsmgmt otherwise. If the user
|
187
|
+
wants to preserve a given snapshot it should be preserved using the
|
188
|
+
zfs hold mechanism or excluded by the ignoresnaps property. This
|
189
|
+
allows zfs_mgmt to manage snapshots indepentantly of the mechanism
|
190
|
+
used to create them.
|
191
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "zfs_mgmt"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/readsnaps
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#! /bin/bash -e
|
2
|
+
|
3
|
+
timeout=1800
|
4
|
+
output=/etc/zfs-list-snapshots.txt
|
5
|
+
lock=/etc/zfs-list-snapshots.lock
|
6
|
+
tempfile=$(mktemp)
|
7
|
+
|
8
|
+
flock -w $timeout $lock /usr/sbin/zfs list -Hprt snapshot -o name,creation,used,written,refer,logicalreferenced,logicalused,zfssnapman:destroy,zfssnapman:snap -s creation > $tempfile
|
9
|
+
mv -f $tempfile $output
|
10
|
+
chmod 0644 $output
|
data/bin/zfsfuncs
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
#-*- mode: sh ; -*-
|
2
|
+
|
3
|
+
function zfssendrecv {
|
4
|
+
local OPTIND OPTARG opt
|
5
|
+
local zfs
|
6
|
+
local snap
|
7
|
+
local dest
|
8
|
+
local inc=''
|
9
|
+
local hold=0
|
10
|
+
while getopts "z:s:d:i:I:h" opt; do
|
11
|
+
case $opt in
|
12
|
+
z)
|
13
|
+
zfs=$OPTARG
|
14
|
+
;;
|
15
|
+
s)
|
16
|
+
snap=$OPTARG
|
17
|
+
;;
|
18
|
+
d)
|
19
|
+
dest=$OPTARG
|
20
|
+
;;
|
21
|
+
i)
|
22
|
+
inc="-i ${OPTARG}"
|
23
|
+
;;
|
24
|
+
I)
|
25
|
+
inc="-I ${OPTARG}"
|
26
|
+
;;
|
27
|
+
h)
|
28
|
+
hold=1
|
29
|
+
;;
|
30
|
+
esac
|
31
|
+
done
|
32
|
+
shift $((OPTIND-1))
|
33
|
+
local zfs_normal=$( echo $zfs|sed 's/[\:\|\/\\ ]/_/g' )
|
34
|
+
local lock="${LOCK_DIR}/${zfs_normal}.lock"
|
35
|
+
local zfs_recv_status
|
36
|
+
local zfs_send_status
|
37
|
+
local pipe_status
|
38
|
+
(
|
39
|
+
if ! $FLOCK; then
|
40
|
+
$ulog "unable to lock ${lock}"
|
41
|
+
return -2
|
42
|
+
fi
|
43
|
+
if [[ $TEST == 0 && $hold == 1 ]]; then
|
44
|
+
zfs hold -r zfsrecvman $snap 2>&1 | $ulog
|
45
|
+
local hold_status="${PIPESTATUS[0]}"
|
46
|
+
if [[ $hold_status != 0 ]]; then
|
47
|
+
$ulog "unable to place a hold on our snapshots: ${snap}"
|
48
|
+
return -3
|
49
|
+
fi
|
50
|
+
fi
|
51
|
+
$ulog "estimating size of sending ${snap}"
|
52
|
+
local size=$( zfs $SEND -v -n $inc $snap 2>&1 | tail -1 | cut -d" " -f 5 )
|
53
|
+
# could be 0 or 400 or 4K or 9.3g, etc.
|
54
|
+
local suf=$( echo $size | sed -E 's/[0-9]+\.?[0-9]*//' | tr '[:lower:]' '[:upper:]' )
|
55
|
+
size=$( echo $size | sed -E 's/[pPtTgGmMkKB]$//' ) # remove known suffixes
|
56
|
+
if [[ $suf != 'B' ]]; then
|
57
|
+
size=$( echo "${size} * 1024" | bc | sed -E 's/\.[0-9]+//' ) # use bc to multiply decimals, sed to make ceil()
|
58
|
+
fi
|
59
|
+
case $suf in
|
60
|
+
B)
|
61
|
+
suf=''
|
62
|
+
;;
|
63
|
+
K)
|
64
|
+
suf=''
|
65
|
+
;;
|
66
|
+
M)
|
67
|
+
suf='K'
|
68
|
+
;;
|
69
|
+
G)
|
70
|
+
suf='M'
|
71
|
+
;;
|
72
|
+
T)
|
73
|
+
suf='G'
|
74
|
+
;;
|
75
|
+
P)
|
76
|
+
suf='T'
|
77
|
+
;;
|
78
|
+
esac
|
79
|
+
$ulog "estimated size of sending ${snap} is ${size}${suf}"
|
80
|
+
local pv_more="-s ${size}${suf}"
|
81
|
+
if [[ $USE_MBUFFER == 'yes' ]]; then
|
82
|
+
ssh "${USER}@${REMOTE}" "mbuffer ${MBUFFER} -q -I ${PORT} | zfs ${RECV} ${dest}" 2>&1 | $ulog &
|
83
|
+
sleep 5
|
84
|
+
zfs $SEND $inc $snap 2> >($ulog)|
|
85
|
+
mbuffer $MBUFFER $MBUFFER_SEND_OPTS -O ${REMOTE}:${PORT}
|
86
|
+
zfs_send_status="${PIPESTATUS[0]}"
|
87
|
+
$ulog "zfs send exited with status: ${zfs_send_status}"
|
88
|
+
$ulog "about to wait on zfs send (this may take a while and appear to have hung)"
|
89
|
+
wait
|
90
|
+
zfs_recv_status="${PIPESTATUS[0]}"
|
91
|
+
$ulog "zfs recv exited with status: ${zfs_recv_status}"
|
92
|
+
else
|
93
|
+
zfs $SEND $inc $snap 2> >($ulog) | pv $PV_OPTS $pv_more | ssh "${USER}@${REMOTE}" "zfs ${RECV} ${dest}" 2>&1 | $ulog
|
94
|
+
pipe_status=("${PIPESTATUS[@]}")
|
95
|
+
zfs_send_status="${pipe_status[0]}"
|
96
|
+
zfs_recv_status="${pipe_status[2]}"
|
97
|
+
$ulog "zfs send exited with status: ${zfs_send_status}"
|
98
|
+
$ulog "zfs recv exited with status: ${zfs_recv_status}"
|
99
|
+
fi
|
100
|
+
if [[ $zfs_send_status != 0 ]]; then
|
101
|
+
return $zfs_send_status
|
102
|
+
elif [[ $zfs_recv_status != 0 ]]; then
|
103
|
+
return $zfs_recv_status
|
104
|
+
else
|
105
|
+
# both must be zero
|
106
|
+
return 0
|
107
|
+
fi
|
108
|
+
) 9>$lock
|
109
|
+
}
|
110
|
+
|
111
|
+
function terminal_options {
|
112
|
+
if [ -t 1 ]; then
|
113
|
+
ISTERM=1
|
114
|
+
LOGGER_EXTRA='-s' # enable logger output to stderr
|
115
|
+
PV_OPTS='-perb' # enable all the magic output from pv
|
116
|
+
MBUFFER_SEND_OPTS='' # don't do quiet mode, we have a term
|
117
|
+
ulog="logger ${LOGGER_EXTRA} -p user.notice -t "$(basename $0 2>/dev/null)"[${$}]"
|
118
|
+
else
|
119
|
+
ISTERM=0
|
120
|
+
LOGGER_EXTRA='' # don't enable stderr output
|
121
|
+
PV_OPTS='-q' # make pv quiet
|
122
|
+
MBUFFER_SEND_OPTS='-q' # enable send side -q, no terminal
|
123
|
+
ulog="logger ${LOGGER_EXTRA} -p user.notice -t "$(basename $0 2>/dev/null)"[${$}]"
|
124
|
+
fi
|
125
|
+
}
|
126
|
+
|
data/bin/zfsmgr
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "zfs_mgmt"
|
3
|
+
|
4
|
+
class Snapshot < Thor
|
5
|
+
class_option :noop, :type => :boolean, :default => false,
|
6
|
+
:desc => 'pass -n option to zfs commands'
|
7
|
+
class_option :verbose, :type => :boolean, :default => false,
|
8
|
+
:desc => 'pass -v option to zfs commands'
|
9
|
+
class_option :debug, :type => :boolean, :default => false,
|
10
|
+
:desc => 'set logging level to debug'
|
11
|
+
class_option :filter, :type => :string, :default => '.+',
|
12
|
+
:desc => 'only act on zfs matching this regexp'
|
13
|
+
desc "destroy", "apply the snapshot destroy policy to zfs"
|
14
|
+
def destroy()
|
15
|
+
ZfsMgmt.snapshot_destroy(noop: options[:noop], verbopt: options[:verbose], debugopt: options[:debug], filter: options[:filter])
|
16
|
+
end
|
17
|
+
desc "policy", "print the policy table for zfs"
|
18
|
+
def policy()
|
19
|
+
ZfsMgmt.snapshot_policy(verbopt: options[:verbose], debugopt: options[:debug], filter: options[:filter])
|
20
|
+
end
|
21
|
+
desc "create", "execute zfs snapshot based on zfs properties"
|
22
|
+
def create()
|
23
|
+
ZfsMgmt.snapshot_create(verbopt: options[:verbose], debugopt: options[:debug], filter: options[:filter])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ZfsMgr < Thor
|
28
|
+
desc "zfsget [ZFS]", "execute zfs get for the given properties and types and parse the output into a nested hash"
|
29
|
+
method_option :properties, :type => :array, :default => ['name'], :desc => "List of properties passed to zfs get"
|
30
|
+
method_option :types, :type => :array, :default => ['filesystem','volume'], enum: ['filesystem','volume','snapshot'], :desc => "list of types"
|
31
|
+
def zfsget(zfs)
|
32
|
+
pp ZfsMgmt.zfsget(properties: options[:properties],
|
33
|
+
types: options[:types],
|
34
|
+
zfs: zfs)
|
35
|
+
end
|
36
|
+
desc "snapshot SUBCOMMAND ...ARGS", "manage snapshots"
|
37
|
+
subcommand "snapshot", Snapshot
|
38
|
+
end
|
39
|
+
|
40
|
+
ZfsMgr.start(ARGV)
|
data/bin/zfsrecvman
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
#! /bin/bash
|
2
|
+
|
3
|
+
export PATH=$PATH:/sbin
|
4
|
+
|
5
|
+
FILTER='.'
|
6
|
+
SENDER='beast'
|
7
|
+
REMOTE='blob'
|
8
|
+
RECVER='blob'
|
9
|
+
USER='root'
|
10
|
+
DEST="blob"/$(hostname -s)
|
11
|
+
SEND='send -R'
|
12
|
+
RECV='recv -u -e -F'
|
13
|
+
FLOCK='/usr/bin/flock -w 60 -n 9'
|
14
|
+
PORT='1337'
|
15
|
+
MBUFFER='-s 128k -m 1G -4'
|
16
|
+
USE_MBUFFER='no'
|
17
|
+
LOCK_DIR='/var/run/'$(basename $0)
|
18
|
+
TEST=0
|
19
|
+
VERB=0
|
20
|
+
|
21
|
+
test -f $HOME/.keychain/$HOSTNAME-sh && . $HOME/.keychain/$HOSTNAME-sh
|
22
|
+
|
23
|
+
. /usr/bin/zfsfuncs
|
24
|
+
|
25
|
+
terminal_options
|
26
|
+
|
27
|
+
if [[ `hostname -s` == $SENDER ]]; then
|
28
|
+
SENDER_PREF=""
|
29
|
+
RECVER_PREF="ssh ${USER}@${SENDER}"
|
30
|
+
elif [[ `hostname -s` == $RECVER ]]; then
|
31
|
+
SENDER_PREF="ssh ${USER}@${SENDER}"
|
32
|
+
RECVER_PREF=""
|
33
|
+
else
|
34
|
+
$ulog "can only be run on ${SENDER} or ${RECVER}"
|
35
|
+
exit -1
|
36
|
+
fi
|
37
|
+
|
38
|
+
while getopts ":p:f:L:mnv" opt; do
|
39
|
+
case $opt in
|
40
|
+
p)
|
41
|
+
PORT=$OPTARG
|
42
|
+
;;
|
43
|
+
f)
|
44
|
+
FILTER=$OPTARG
|
45
|
+
;;
|
46
|
+
L)
|
47
|
+
PV_OPTS="${PV_OPTS} -L ${OPTARG}"
|
48
|
+
;;
|
49
|
+
m)
|
50
|
+
USE_MBUFFER='yes'
|
51
|
+
;;
|
52
|
+
n)
|
53
|
+
RECV="${RECV} -n"
|
54
|
+
TEST=1
|
55
|
+
VERB=1
|
56
|
+
PV_OPTS='-q' # make pv quiet
|
57
|
+
MBUFFER_SEND_OPTS='-q' # enable send side -q, no terminal
|
58
|
+
;;
|
59
|
+
v)
|
60
|
+
VERB=1
|
61
|
+
;;
|
62
|
+
esac
|
63
|
+
done
|
64
|
+
|
65
|
+
if [[ $VERB == 1 ]]; then
|
66
|
+
echo $RECV | grep -q -- -v || RECV="${RECV} -v"
|
67
|
+
fi
|
68
|
+
|
69
|
+
for zpool in $( zpool list -H -o name | egrep "$FILTER" | sort ); do
|
70
|
+
target="${DEST}/${zpool}"
|
71
|
+
target_dir=$( dirname $target )
|
72
|
+
# recv_last is the last snapshot on the recv side of this zfs
|
73
|
+
if ! ssh "${USER}@${REMOTE}" zfs get written $target >/dev/null 2>/dev/null; then
|
74
|
+
$ulog sending initial snapshot of $zpool to $target_dir on $REMOTE
|
75
|
+
snap=$( zfs list -t snapshot -o name -s creation -d 1 -H $zpool | grep @zfssnapman- | tail -1 )
|
76
|
+
result='-1'
|
77
|
+
zfssendrecv -z $zpool \
|
78
|
+
-s $snap \
|
79
|
+
-d $target_dir \
|
80
|
+
-h # create hold
|
81
|
+
result=$?
|
82
|
+
if [[ $TEST == 0 ]]; then
|
83
|
+
echo "${zpool}:${REMOTE}:${snap}:${result}" >> ~/.zfssendrecv.log
|
84
|
+
if [[ $result != 0 ]]; then
|
85
|
+
zfs release -r zfsrecvman $snap || $ulog "unable to remove hold on our source snapshot: ${snap}"
|
86
|
+
fi
|
87
|
+
fi
|
88
|
+
fi
|
89
|
+
|
90
|
+
# last known good snapshot sent
|
91
|
+
pattern="^${zpool}:${REMOTE}:.+:0$"
|
92
|
+
if ! egrep -q "${pattern}" ~/.zfssendrecv.log; then
|
93
|
+
$ulog "no known good snapshot logged for ${zpool} on ${REMOTE}, unable to continue"
|
94
|
+
continue;
|
95
|
+
fi
|
96
|
+
last_snap=$( egrep "${pattern}" ~/.zfssendrecv.log | tail -1 | cut -d: -f 3)
|
97
|
+
|
98
|
+
remote_snaps=$( mktemp )
|
99
|
+
ssh "${USER}@${REMOTE}" zfs list -t snapshot -o name -s creation -d 1 -H $target > $remote_snaps
|
100
|
+
if [[ $? != 0 ]]; then
|
101
|
+
$ulog "unable to retrieve list of remote snapshots for ${zpool} on ${REMOTE}"
|
102
|
+
continue;
|
103
|
+
fi
|
104
|
+
|
105
|
+
if ! egrep -q "^${target_dir}/${last_snap}" $remote_snaps; then
|
106
|
+
$ulog "${last_snap} does not exist on ${REMOTE}, you must destroy the filesystem: ${target}"
|
107
|
+
continue;
|
108
|
+
fi
|
109
|
+
if ! tail -1 $remote_snaps | egrep -q "^${target_dir}/${last_snap}"; then
|
110
|
+
$ulog "${last_snap} is not the most recent snapshot on ${REMOTE}, rollback will occur on ${target}"
|
111
|
+
fi
|
112
|
+
|
113
|
+
rm -f $remote_snaps
|
114
|
+
# grab the most recent local recursive snapshot
|
115
|
+
current=$( zfs list -t snapshot -o name -s creation -d 1 -H $zpool | grep @zfssnapman- | tail -1 )
|
116
|
+
if [[ "${last_snap}" == "${current}" ]]; then
|
117
|
+
$ulog "${zpool} is in sync on source and destination (${target})"
|
118
|
+
continue
|
119
|
+
fi
|
120
|
+
$ulog sending $last_snap through $current to $target
|
121
|
+
result='-1'
|
122
|
+
zfssendrecv -z $zpool \
|
123
|
+
-I $last_snap \
|
124
|
+
-s $current \
|
125
|
+
-d $target_dir \
|
126
|
+
-h
|
127
|
+
result=$?
|
128
|
+
if [[ $TEST == 1 ]]; then
|
129
|
+
continue
|
130
|
+
fi
|
131
|
+
echo "${zpool}:${REMOTE}:${current}:${result}" >> ~/.zfssendrecv.log
|
132
|
+
if [[ $result == 0 ]]; then
|
133
|
+
$ulog "${zpool} is in sync on source and destination (${target})"
|
134
|
+
zfs get -t filesystem -H -r all $zpool | ssh "${USER}@${REMOTE}" "cat > ~/.zpool-properties-${zpool}" ||
|
135
|
+
$ulog "unable to write zpool properties backup for ${zpool}"
|
136
|
+
zfs release -r zfsrecvman $last_snap ||
|
137
|
+
$ulog "unable to release old snapshot: ${last_snap}"
|
138
|
+
ssh "${USER}@${REMOTE}" "zfs release -r zfsrecvman ${target_dir}/${last_snap}" ||
|
139
|
+
$ulog "unable to release old snapshot on remote side: ${target_dir}/${last_snap}"
|
140
|
+
ssh "${USER}@${REMOTE}" "zfs hold -r zfsrecvman ${target_dir}/${current}" ||
|
141
|
+
$ulog "unable to hold snapshot on remote side: ${target_dir}/${current}"
|
142
|
+
com=''
|
143
|
+
for zfs in $(ssh "${USER}@${REMOTE}" "zfs list -H -o name -t filesystem -r ${target}" | sort -r ); do
|
144
|
+
for prop in 'canmount=off' 'sharenfs=off' 'sharesmb=off' 'mountpoint=none'; do
|
145
|
+
com="${com}zfs set ${prop} ${zfs};"
|
146
|
+
done
|
147
|
+
done
|
148
|
+
$ulog "fixing zfs properties for ${zpool} (this may take a while)"
|
149
|
+
ssh "${USER}@${REMOTE}" "${com}" 2>&1 | $ulog
|
150
|
+
else
|
151
|
+
zfs release -r zfsrecvman $current || $ulog "unable to release current snapshot: ${current}"
|
152
|
+
$ulog zfs exited with $result while sending $send through $current to $target
|
153
|
+
fi
|
154
|
+
done
|