marius-zsnap 0.1.2
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.
- data/INSTALL +39 -0
- data/bin/zfs-snapshot-mgmt +36 -0
- data/conf/zfs-snapshot-mgmt.conf.sample +32 -0
- data/doc/zfs-snapshot-mgmt.8 +164 -0
- data/lib/zsnap.rb +178 -0
- data/test/zsnap_test.rb +73 -0
- metadata +60 -0
data/INSTALL
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
FreeBSD Installation
|
|
2
|
+
--------------------
|
|
3
|
+
|
|
4
|
+
FreeBSD users please use the sysutils/zfs-snapshot-mgmt port:
|
|
5
|
+
|
|
6
|
+
cd /usr/ports/sysutils/zfs-snapshot-mgmt
|
|
7
|
+
make install clean
|
|
8
|
+
|
|
9
|
+
and follow the instructions printed after installation.
|
|
10
|
+
|
|
11
|
+
See the manpage for details:
|
|
12
|
+
|
|
13
|
+
man zfs-snapshot-mgmt
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Manual Installation
|
|
18
|
+
-------------------
|
|
19
|
+
|
|
20
|
+
Requirements:
|
|
21
|
+
Ruby
|
|
22
|
+
(/usr/local/bin/ruby, edit the first line of script for a different path)
|
|
23
|
+
|
|
24
|
+
Copy the zfs-snapshot-mgmt script to /usr/local/bin.
|
|
25
|
+
Copy the zfs-snapshot-mgmt.conf.sample file to
|
|
26
|
+
/usr/local/etc/zfs-snapshot-mgmt.conf (this path is hardcoded in the script,
|
|
27
|
+
sorry. You may change it manually (grep for CONFIG_FILE)).
|
|
28
|
+
Copy the zfs-snapshot-mgmt.8 file to man8 directory on your system (usually
|
|
29
|
+
/usr/local/share/man/man8/ or just /usr/share/man/man8/).
|
|
30
|
+
|
|
31
|
+
Read the manual page (man zfs-snapshot-mgmt).
|
|
32
|
+
|
|
33
|
+
Edit the zfs-snapshot-mgmt.conf file.
|
|
34
|
+
Add the script to crontab (usually /etc/crontab but may differ on your system),
|
|
35
|
+
append a line like this:
|
|
36
|
+
|
|
37
|
+
*/5 * * * * root /usr/local/bin/zfs-snapshot-mgmt
|
|
38
|
+
|
|
39
|
+
From now on the snapshots will be automatically created.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
|
2
|
+
# Copyright (c) 2008, Marcin Simonides
|
|
3
|
+
# Copyright (c) 2009, Marius Nuennerich
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
# documentation and/or other materials provided with the distribution.
|
|
13
|
+
#
|
|
14
|
+
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
15
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
|
|
17
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
18
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
19
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
20
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
21
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
22
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
23
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
24
|
+
|
|
25
|
+
require 'zsnap'
|
|
26
|
+
|
|
27
|
+
config = Zsnap::Config.new(YAML::load(File.open(Zsnap::CONFIG_FILE_NAME).read))
|
|
28
|
+
|
|
29
|
+
now_minutes = Time.now.to_i / 60
|
|
30
|
+
|
|
31
|
+
config.filesystems.each do |fs|
|
|
32
|
+
unless config.busy_pools.include? fs.pool
|
|
33
|
+
fs.create_snapshot(now_minutes, config.snapshot_prefix)
|
|
34
|
+
fs.remove_snapshots(now_minutes, config.snapshot_prefix)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Automatic ZFS snapshot management configuration file
|
|
2
|
+
#
|
|
3
|
+
# This is a YAML file (see http://www.yaml.org)
|
|
4
|
+
# Use exactly 2 spaces for each indentation level
|
|
5
|
+
#
|
|
6
|
+
snapshot_prefix: auto-
|
|
7
|
+
filesystems:
|
|
8
|
+
tank/usr/home:
|
|
9
|
+
# Create snapshots recursively for all filesystems mounted under this one
|
|
10
|
+
recursive: true
|
|
11
|
+
# Create snapshots every 10 minutes, starting at midnight
|
|
12
|
+
creation_rule:
|
|
13
|
+
at_multiple: 10
|
|
14
|
+
offset: 0
|
|
15
|
+
# Keep all snapshots for the first 90 minutes,
|
|
16
|
+
# then only those that were created at 30 minute intervals for 12 hours
|
|
17
|
+
# (after snapshot creation),
|
|
18
|
+
# then only those that were created at 3 hour intervals, counting at 2:00
|
|
19
|
+
# (i.e. 2:00, 5:00, 8:00, 11:00, 14:00, 17:00, 20:00, 23:00)
|
|
20
|
+
# for 7 days
|
|
21
|
+
preservation_rules:
|
|
22
|
+
- { for_minutes: 90, at_multiple: 0, offset: 0 }
|
|
23
|
+
- { for_minutes: 720, at_multiple: 30, offset: 0 }
|
|
24
|
+
- { for_minutes: 10080, at_multiple: 180, offset: 120 }
|
|
25
|
+
tank/usr:
|
|
26
|
+
# Create snapshots every 24 hours, starting at 20:00.
|
|
27
|
+
creation_rule:
|
|
28
|
+
at_multiple: 1440
|
|
29
|
+
offset: 1200
|
|
30
|
+
# Keep daily snapshots created at 20:00 (in this case all).
|
|
31
|
+
preservation_rules:
|
|
32
|
+
- { for_minutes: 5760, at_multiple: 1440, offset: 1200 }
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
.Dd June 6, 2008
|
|
2
|
+
.Dt ZFS-SNAPSHOT-MGMT 8
|
|
3
|
+
.Os
|
|
4
|
+
.Sh NAME
|
|
5
|
+
.Nm zfs-snapshot-mgmt
|
|
6
|
+
.Nd automate creation of new and removal of stale ZFS snapshots
|
|
7
|
+
.Sh SYNOPSIS
|
|
8
|
+
.Nm
|
|
9
|
+
.Sh DESCRIPTION
|
|
10
|
+
The utility creates ZFS snapshots and removes snapshots it has created that
|
|
11
|
+
are stale.
|
|
12
|
+
Rules for creating and removing are defined in the configuration file.
|
|
13
|
+
.Pp
|
|
14
|
+
.Nm
|
|
15
|
+
is designed to be run by the
|
|
16
|
+
.Xr cron 8
|
|
17
|
+
utility.
|
|
18
|
+
.Sh CONFIGURATION
|
|
19
|
+
.Nm
|
|
20
|
+
reads settings from the
|
|
21
|
+
.Pa /usr/local/etc/zfs-snapshot-mgmt.conf
|
|
22
|
+
file.
|
|
23
|
+
.Ss General Structure
|
|
24
|
+
The file is in the YAML format: it contains keys and values separated by
|
|
25
|
+
a colon (:). Each value may span multiple lines and be a list or a map with
|
|
26
|
+
key-value pairs. The indentation is always a multiple of two spaces.
|
|
27
|
+
.Pp
|
|
28
|
+
There are two top-level options:
|
|
29
|
+
.Bl -tag -width "1234"
|
|
30
|
+
.It snapshot_prefix
|
|
31
|
+
prefix for snapshots created and recognised by this tool.
|
|
32
|
+
All created snapshots
|
|
33
|
+
will have names consisting of this prefix and current date and time.
|
|
34
|
+
Only snapshots beginning with this prefix will be considered for removal.
|
|
35
|
+
.It filesystems
|
|
36
|
+
the main part of configuration - specifies rules for creating and removing
|
|
37
|
+
snapshots at each filesystem.
|
|
38
|
+
The value for this key contains keys for each filesystem
|
|
39
|
+
(as seen by ZFS, e.g. tank/home)
|
|
40
|
+
with snapshot creation and preservation settings.
|
|
41
|
+
.El
|
|
42
|
+
.Pp
|
|
43
|
+
There is an example configuration in the
|
|
44
|
+
.Sx EXAMPLES
|
|
45
|
+
section.
|
|
46
|
+
.Ss Creation Rules
|
|
47
|
+
For each filesystem there is one snapshot
|
|
48
|
+
.Li creation_rule
|
|
49
|
+
defined.
|
|
50
|
+
It specifies when snapshots for the particular filesystem should be created.
|
|
51
|
+
It has two parameters.
|
|
52
|
+
All values are specified in minutes.
|
|
53
|
+
.Bl -tag -width "1234"
|
|
54
|
+
.It at_multiple
|
|
55
|
+
defines how often should a snapshot be created.
|
|
56
|
+
E.g. a value of 60 means that a snapshot is to be created every hour.
|
|
57
|
+
.It offset
|
|
58
|
+
defines an offset from midnight that is to be applied to the
|
|
59
|
+
.Ix at_multiple
|
|
60
|
+
parameter.
|
|
61
|
+
E.g. specifying a value of 30 means that the first snapshot will be taken 30
|
|
62
|
+
minutes after midnight.
|
|
63
|
+
.El
|
|
64
|
+
.Ss Preservation Rules
|
|
65
|
+
These rules specify which of the created snapshots should be preserved.
|
|
66
|
+
All that do not match the any of the rules and whose names begin with
|
|
67
|
+
.Li snapshot_prefix
|
|
68
|
+
are destroyed.
|
|
69
|
+
.Pp
|
|
70
|
+
Each of the rules has three parameters:
|
|
71
|
+
.Bl -tag -width "1234"
|
|
72
|
+
.It for_minutes
|
|
73
|
+
specifies how long after snapshot creation is the rule applicable.
|
|
74
|
+
.It at_multiple
|
|
75
|
+
analogous to the same parameter for
|
|
76
|
+
.Li creation_rule
|
|
77
|
+
- snapshots whose creation time
|
|
78
|
+
(in minutes since midnight)
|
|
79
|
+
is a multiple of this value are retained.
|
|
80
|
+
.It offset
|
|
81
|
+
analogous to the same parameter for
|
|
82
|
+
.Li creation_rule
|
|
83
|
+
- applies to
|
|
84
|
+
.Li at_multiple .
|
|
85
|
+
.El
|
|
86
|
+
.Sh EXAMPLES
|
|
87
|
+
.Ss Crontab Configuration
|
|
88
|
+
To invoke the program every five minutes you may add the following line to
|
|
89
|
+
the
|
|
90
|
+
.Pa /etc/crontab
|
|
91
|
+
configuration file:
|
|
92
|
+
.Pp
|
|
93
|
+
.D1 */5 * * * * root /usr/local/bin/zfs-snapshot-mgmt
|
|
94
|
+
.Pp
|
|
95
|
+
Bear in mind that this effectively limits the resolution to 5 minutes.
|
|
96
|
+
.Ss zfs-snapshot-mgmt.conf
|
|
97
|
+
Here is an example configuration.
|
|
98
|
+
.Bd -literal -offset indent
|
|
99
|
+
snapshot_prefix: auto-
|
|
100
|
+
filesystems:
|
|
101
|
+
tank/usr/home:
|
|
102
|
+
creation_rule:
|
|
103
|
+
at_multiple: 5
|
|
104
|
+
offset: 0
|
|
105
|
+
preservation_rules:
|
|
106
|
+
- { for_minutes: 90, at_multiple: 0, offset: 0 }
|
|
107
|
+
- { for_minutes: 720, at_multiple: 30, offset: 0 }
|
|
108
|
+
- { for_minutes: 10080, at_multiple: 180, offset: 120 }
|
|
109
|
+
tank/usr:
|
|
110
|
+
recursive: true
|
|
111
|
+
creation_rule:
|
|
112
|
+
at_multiple: 1440
|
|
113
|
+
offset: 1200
|
|
114
|
+
preservation_rules:
|
|
115
|
+
- { for_minutes: 5760, at_multiple: 1440, offset: 1200 }
|
|
116
|
+
.Ed
|
|
117
|
+
.Pp
|
|
118
|
+
which specifies the following settings:
|
|
119
|
+
.Bl -bullet
|
|
120
|
+
.It
|
|
121
|
+
names of created snapshots start with
|
|
122
|
+
.Li auto-
|
|
123
|
+
and only such snapshots are considered for removal.
|
|
124
|
+
.It
|
|
125
|
+
there are two filesystems:
|
|
126
|
+
.Li tank/usr/home
|
|
127
|
+
and
|
|
128
|
+
.Li tank/usr .
|
|
129
|
+
.It
|
|
130
|
+
snapshots for
|
|
131
|
+
.Li tank/usr/home
|
|
132
|
+
are created every five minutes, starting at midnight.
|
|
133
|
+
.It
|
|
134
|
+
all snapshots for
|
|
135
|
+
.Li tank/usr/home
|
|
136
|
+
are kept for 90 minutes after creation.
|
|
137
|
+
.It
|
|
138
|
+
only snapshots created af full and half hour are retained for 12 hours
|
|
139
|
+
(720 minutes) after creation for
|
|
140
|
+
.Li tank/usr/home .
|
|
141
|
+
.It
|
|
142
|
+
snapshots created at multiple of three hours starting with 2 a.m. are retained
|
|
143
|
+
for a week (10080 minutes) after creation for
|
|
144
|
+
.Li tank/usr/home .
|
|
145
|
+
This means: 2 a.m., 5 a.m., 8 a.m. and so on.
|
|
146
|
+
.It
|
|
147
|
+
snapshots on
|
|
148
|
+
.Li tank/usr
|
|
149
|
+
are created once a day at 20 p.m. and retained for 4 days.
|
|
150
|
+
.It
|
|
151
|
+
snapshots are taken recursively on
|
|
152
|
+
.Li tank/usr
|
|
153
|
+
filesystem and all filesystems mounted under it.
|
|
154
|
+
.El
|
|
155
|
+
.Sh FILES
|
|
156
|
+
.Pa /usr/local/etc/zfs-snapshot-mgmt.conf
|
|
157
|
+
The configuration file
|
|
158
|
+
.Sh SEE ALSO
|
|
159
|
+
.Xr cron 8 ,
|
|
160
|
+
.Xr zfs 1M
|
|
161
|
+
.Sh AUTHORS
|
|
162
|
+
.An Marcin Simonides Aq marcin@studio4plus.com
|
|
163
|
+
.Sh BUGS
|
|
164
|
+
There is no way to use alternative path for the configuration file.
|
data/lib/zsnap.rb
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
|
2
|
+
# Copyright (c) 2008, Marcin Simonides
|
|
3
|
+
# Copyright (c) 2009, Marius Nuennerich
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
# documentation and/or other materials provided with the distribution.
|
|
13
|
+
#
|
|
14
|
+
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
15
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
|
|
17
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
18
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
19
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
20
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
21
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
22
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
23
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
24
|
+
|
|
25
|
+
require 'yaml'
|
|
26
|
+
require 'time'
|
|
27
|
+
|
|
28
|
+
module Zsnap
|
|
29
|
+
|
|
30
|
+
CONFIG_FILE_NAME = '/usr/local/etc/zfs-snapshot-mgmt.conf'
|
|
31
|
+
|
|
32
|
+
class Rule
|
|
33
|
+
def initialize(args = {})
|
|
34
|
+
args = { 'offset' => 0, 'at_multiple' => 60 }.merge(args)
|
|
35
|
+
@at_multiple = args['at_multiple'].to_i
|
|
36
|
+
@offset = args['offset'].to_i
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def condition_met?(time_minutes)
|
|
40
|
+
divisor = @at_multiple
|
|
41
|
+
(divisor == 0) or ((time_minutes - @offset) % divisor) == 0
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class PreservationRule < Rule
|
|
46
|
+
def initialize(args = {})
|
|
47
|
+
super(args)
|
|
48
|
+
args = { 'for_minutes' => 240 }.merge(args)
|
|
49
|
+
@for_minutes = args['for_minutes'].to_i
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def applies?(now_minutes, creation_time_minutes)
|
|
53
|
+
(now_minutes - creation_time_minutes) < @for_minutes
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def condition_met_for_snapshot?(now_minutes, snapshot)
|
|
57
|
+
creation_time_minutes = snapshot.creation_time_minutes
|
|
58
|
+
applies?(now_minutes, creation_time_minutes) and
|
|
59
|
+
condition_met?(creation_time_minutes)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class SnapshotInfo
|
|
64
|
+
def initialize(name, fs_name, snapshot_prefix)
|
|
65
|
+
@name = name
|
|
66
|
+
@fs_name = fs_name
|
|
67
|
+
@creation_time = parse_timestamp(name[snapshot_prefix.length .. -1])
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def creation_time_minutes
|
|
71
|
+
@creation_time.to_i / 60
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns canonical name of the snapshot and FS (as accepted by zfs command)
|
|
75
|
+
# e.g.: /tank/usr@snapshot
|
|
76
|
+
def canonical_name
|
|
77
|
+
if @fs_name and @name
|
|
78
|
+
@fs_name + '@' + @name
|
|
79
|
+
else
|
|
80
|
+
raise "SnapshotInfo doesn't contain name and/or fs_name"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def parse_timestamp(time_string)
|
|
87
|
+
date, time = time_string.split('_')
|
|
88
|
+
year, month, day = date.split('-')
|
|
89
|
+
hour, minute = time.split('.')
|
|
90
|
+
Time.mktime(year, month, day, hour, minute)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class FSInfo
|
|
95
|
+
def initialize(fs_name, values)
|
|
96
|
+
@name = fs_name
|
|
97
|
+
raise "Filesystem #{fs_name} has no creation rule" unless values['creation_rule']
|
|
98
|
+
raise "Filesystem #{fs_name} has no preservation rules" unless values['preservation_rules']
|
|
99
|
+
|
|
100
|
+
@creation_rule = Rule.new(values['creation_rule'])
|
|
101
|
+
@preservation_rules = values['preservation_rules'].map do |value|
|
|
102
|
+
PreservationRule.new(value)
|
|
103
|
+
end
|
|
104
|
+
@is_recursive = values['recursive'] ? true : false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def create?(now_minutes)
|
|
108
|
+
@creation_rule.condition_met?(now_minutes)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def snapshots(prefix)
|
|
112
|
+
path = File.join(mount_point, '.zfs', 'snapshot')
|
|
113
|
+
Dir.open(path).select do |name|
|
|
114
|
+
name[0, prefix.length] == prefix
|
|
115
|
+
end.map { |name| SnapshotInfo.new(name, @name, prefix) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def snapshots_to_remove(now_minutes, prefix)
|
|
119
|
+
snapshots(prefix).reject do |snapshot|
|
|
120
|
+
@preservation_rules.any? do |rule|
|
|
121
|
+
rule.condition_met_for_snapshot?(now_minutes, snapshot)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def remove_snapshots(now_minutes, prefix)
|
|
127
|
+
snapshots_to_remove(now_minutes, prefix).each do |snapshot|
|
|
128
|
+
remove_snapshot(snapshot)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def create_snapshot(now_minutes, prefix)
|
|
133
|
+
if create?(now_minutes)
|
|
134
|
+
create_snapshot_from_info(SnapshotInfo.new(prefix + Time.now.strftime('%Y-%m-%d_%H.%M'), @name, prefix))
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def pool
|
|
139
|
+
# More or less according to ZFS Component Naming Requirements
|
|
140
|
+
# http://docs.sun.com/app/docs/doc/819-5461/gbcpt
|
|
141
|
+
@name[/\A[a-zA-Z_:.-]+/]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
def remove_snapshot(snapshot_info)
|
|
147
|
+
arguments = @is_recursive ? '-r ' : ''
|
|
148
|
+
system 'zfs destroy ' + arguments + snapshot_info.canonical_name
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def create_snapshot_from_info(snapshot_info)
|
|
152
|
+
arguments = @is_recursive ? '-r ' : ''
|
|
153
|
+
system 'zfs snapshot ' + arguments + snapshot_info.canonical_name
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def mount_point
|
|
157
|
+
`zfs mount`.collect { |line| line.split(' ') }.
|
|
158
|
+
select { |item| item.first == @name }.collect { |item| item.last }.first
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class Config
|
|
163
|
+
attr_reader :snapshot_prefix, :filesystems
|
|
164
|
+
|
|
165
|
+
def initialize(value)
|
|
166
|
+
@snapshot_prefix = value['snapshot_prefix']
|
|
167
|
+
@filesystems = value['filesystems'].map { |key, val| FSInfo.new(key, val) }
|
|
168
|
+
@pools = @filesystems.map { |fs| fs.pool }.uniq
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def busy_pools
|
|
172
|
+
@busy_pools ||= @pools.select do |pool|
|
|
173
|
+
`zpool status #{pool}`.any? { |line| line =~ /(scrub|resilver) in progress/ }
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
end
|
data/test/zsnap_test.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'lib/zsnap'
|
|
3
|
+
|
|
4
|
+
include Zsnap
|
|
5
|
+
|
|
6
|
+
class RuleTest < Test::Unit::TestCase
|
|
7
|
+
def test_rule
|
|
8
|
+
r = Rule.new
|
|
9
|
+
2.times do
|
|
10
|
+
assert r.condition_met?(0)
|
|
11
|
+
assert r.condition_met?(60)
|
|
12
|
+
assert r.condition_met?(120)
|
|
13
|
+
(1..59).each do |i|
|
|
14
|
+
assert !r.condition_met?(i)
|
|
15
|
+
end
|
|
16
|
+
r = Rule.new 'at_multiple' => 60
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_rule_with_offset
|
|
21
|
+
r = Rule.new 'at_multiple' => 60, 'offset' => 4
|
|
22
|
+
assert r.condition_met?(4)
|
|
23
|
+
assert r.condition_met?(64)
|
|
24
|
+
assert r.condition_met?(124)
|
|
25
|
+
(5..63).each do |i|
|
|
26
|
+
assert !r.condition_met?(i)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class PreservationRuleTest < Test::Unit::TestCase
|
|
32
|
+
def test_create
|
|
33
|
+
p = PreservationRule.new
|
|
34
|
+
assert p.applies?(239, 0)
|
|
35
|
+
assert !p.applies?(240, 0)
|
|
36
|
+
assert !p.applies?(241, 0)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_condition_met
|
|
40
|
+
p = PreservationRule.new
|
|
41
|
+
s = SnapshotInfo.new 'foo-2009-08-05_21.00', 'bar', 'foo-'
|
|
42
|
+
assert p.condition_met_for_snapshot? 17, s
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class SnapshotInfoTest < Test::Unit::TestCase
|
|
48
|
+
def test_create
|
|
49
|
+
s = SnapshotInfo.new 'foo-2009-08-05_21.42', 'bar', 'foo-'
|
|
50
|
+
assert_equal 20825022, s.creation_time_minutes
|
|
51
|
+
assert_equal 'bar@foo-2009-08-05_21.42', s.canonical_name
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_raises_error
|
|
55
|
+
s = SnapshotInfo.new 'foo-2009-08-05_21.42', nil, 'foo-'
|
|
56
|
+
assert_raise(RuntimeError) { s.canonical_name }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class FSInfoTest < Test::Unit::TestCase
|
|
61
|
+
def test_create
|
|
62
|
+
rules = {
|
|
63
|
+
'creation_rule' => {'at_multiple' => 60, 'offset' => 0 },
|
|
64
|
+
'preservation_rules' => [
|
|
65
|
+
{ 'for_minutes' => 240, 'at_multiple' => 0, 'offset' => 0 }
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
f = FSInfo.new 'tank/foo', rules
|
|
69
|
+
assert f.create?(60)
|
|
70
|
+
# Mock here
|
|
71
|
+
# assert_equal 'ff', f.send(:get_mount_point, 'tank/foo')
|
|
72
|
+
end
|
|
73
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: marius-zsnap
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Marcin Simonides
|
|
8
|
+
- Marius Nuennerich
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
|
|
13
|
+
date: 2009-05-16 00:00:00 -07:00
|
|
14
|
+
default_executable:
|
|
15
|
+
dependencies: []
|
|
16
|
+
|
|
17
|
+
description:
|
|
18
|
+
email: marius@nuenneri.ch
|
|
19
|
+
executables:
|
|
20
|
+
- zfs-snapshot-mgmt
|
|
21
|
+
extensions: []
|
|
22
|
+
|
|
23
|
+
extra_rdoc_files: []
|
|
24
|
+
|
|
25
|
+
files:
|
|
26
|
+
- INSTALL
|
|
27
|
+
- test/zsnap_test.rb
|
|
28
|
+
- lib/zsnap.rb
|
|
29
|
+
- conf/zfs-snapshot-mgmt.conf.sample
|
|
30
|
+
- bin/zfs-snapshot-mgmt
|
|
31
|
+
- doc/zfs-snapshot-mgmt.8
|
|
32
|
+
has_rdoc: false
|
|
33
|
+
homepage: http://github.com/marius/zsnap
|
|
34
|
+
licenses:
|
|
35
|
+
post_install_message:
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
|
|
38
|
+
require_paths:
|
|
39
|
+
- lib
|
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: "0"
|
|
45
|
+
version:
|
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: "0"
|
|
51
|
+
version:
|
|
52
|
+
requirements: []
|
|
53
|
+
|
|
54
|
+
rubyforge_project:
|
|
55
|
+
rubygems_version: 1.3.5
|
|
56
|
+
signing_key:
|
|
57
|
+
specification_version: 2
|
|
58
|
+
summary: A script to automatically create and delete zfs snapshots from cron
|
|
59
|
+
test_files:
|
|
60
|
+
- test/zsnap_test.rb
|