zfs_mgmt 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|