zfstools 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +12 -0
- data/VERSION +1 -1
- data/bin/zfs-auto-snapshot +25 -6
- data/bin/zfs-cleanup-snapshots +18 -7
- data/bin/zfs-snapshot-mysql +12 -14
- data/lib/zfs/dataset.rb +44 -0
- data/lib/zfs/snapshot.rb +35 -10
- data/lib/zfstools.rb +106 -49
- data/spec/zfstools_spec.rb +247 -3
- data/zfstools.gemspec +80 -0
- metadata +25 -53
data/README.md
CHANGED
@@ -6,6 +6,12 @@ Various scripts for administrating ZFS. Modeled after [time-sliderd](http://mail
|
|
6
6
|
|
7
7
|
Install the gem.
|
8
8
|
|
9
|
+
## Production version
|
10
|
+
|
11
|
+
gem install zfstools
|
12
|
+
|
13
|
+
### Development version
|
14
|
+
|
9
15
|
rake install
|
10
16
|
|
11
17
|
Setup crontab entries for scripts wanted. See below.
|
@@ -37,6 +43,12 @@ Only datasets with the `com.sun:auto-snapshot` property set to `true` will be sn
|
|
37
43
|
|
38
44
|
zfs set com.sun:auto-snapshot=true DATASET
|
39
45
|
|
46
|
+
##### MySQL Support
|
47
|
+
|
48
|
+
Setting a MySQL dataset's property to `mysql` will hook it into the `zfs-snapshot-mysql` script. See its section for setup instructions.
|
49
|
+
|
50
|
+
zfs set com.sun:auto-snapshot=mysql DATASET
|
51
|
+
|
40
52
|
##### Overrides
|
41
53
|
|
42
54
|
You can override a child dataset to use, or not use auto snapshotting by settings its flag with the given interval.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/bin/zfs-auto-snapshot
CHANGED
@@ -7,29 +7,46 @@ require 'getoptlong'
|
|
7
7
|
require 'zfstools'
|
8
8
|
|
9
9
|
opts = GetoptLong.new(
|
10
|
-
[ "--utc",
|
11
|
-
[ "--
|
10
|
+
[ "--utc", "-u", GetoptLong::NO_ARGUMENT ],
|
11
|
+
[ "--keep-zero-sized-snapshots", "-k", GetoptLong::NO_ARGUMENT ],
|
12
|
+
[ "--parallel-snapshots", "-p", GetoptLong::NO_ARGUMENT ],
|
13
|
+
[ "--dry-run", "-n", GetoptLong::NO_ARGUMENT ],
|
14
|
+
[ "--verbose", "-v", GetoptLong::NO_ARGUMENT ],
|
15
|
+
[ "--debug", "-d", GetoptLong::NO_ARGUMENT ]
|
12
16
|
)
|
13
17
|
|
14
18
|
$use_utc = false
|
15
19
|
$dry_run = false
|
20
|
+
should_destroy_zero_sized_snapshots = true
|
16
21
|
opts.each do |opt, arg|
|
17
22
|
case opt
|
18
23
|
when '--utc'
|
19
24
|
$use_utc = true
|
25
|
+
when '--keep-zero-sized-snapshots'
|
26
|
+
should_destroy_zero_sized_snapshots = false
|
27
|
+
when '--parallel-snapshots'
|
28
|
+
$use_threads = true
|
20
29
|
when '--dry-run'
|
21
30
|
$dry_run = true
|
31
|
+
when '--verbose'
|
32
|
+
$verbose = true
|
33
|
+
when '--debug'
|
34
|
+
$debug = true
|
22
35
|
end
|
23
36
|
end
|
24
37
|
|
25
38
|
|
26
39
|
def usage
|
27
40
|
puts <<-EOF
|
28
|
-
Usage: $0 [-
|
41
|
+
Usage: $0 [-dknpuv] <INTERVAL> <KEEP>
|
29
42
|
EOF
|
30
43
|
format = " %-15s %s"
|
31
|
-
puts format % ["-
|
44
|
+
puts format % ["-d", "Show debug output."]
|
45
|
+
puts format % ["-k", "Keep zero-sized snapshots."]
|
32
46
|
puts format % ["-n", "Do a dry-run. Nothing is committed. Only show what would be done."]
|
47
|
+
puts format % ["-p", "Create snapshots in parallel."]
|
48
|
+
puts format % ["-u", "Use UTC for snapshots."]
|
49
|
+
puts format % ["-v", "Show what is being done."]
|
33
50
|
puts format % ["INTERVAL", "The interval to snapshot."]
|
34
51
|
puts format % ["KEEP", "How many snapshots to keep."]
|
35
52
|
exit
|
@@ -40,8 +57,10 @@ usage if ARGV.length < 2
|
|
40
57
|
interval=ARGV[0]
|
41
58
|
keep=ARGV[1].to_i
|
42
59
|
|
60
|
+
datasets = find_eligible_datasets(interval)
|
61
|
+
|
43
62
|
# Generate new snapshots
|
44
|
-
do_new_snapshots(interval) if keep > 0
|
63
|
+
do_new_snapshots(datasets, interval) if keep > 0
|
45
64
|
|
46
65
|
# Delete expired
|
47
|
-
cleanup_expired_snapshots(interval, keep)
|
66
|
+
cleanup_expired_snapshots(datasets, interval, keep, should_destroy_zero_sized_snapshots)
|
data/bin/zfs-cleanup-snapshots
CHANGED
@@ -7,32 +7,43 @@ require 'getoptlong'
|
|
7
7
|
require 'zfstools'
|
8
8
|
|
9
9
|
opts = GetoptLong.new(
|
10
|
-
[ "--
|
10
|
+
[ "--parallel-snapshots", "-p", GetoptLong::NO_ARGUMENT ],
|
11
|
+
[ "--dry-run", "-n", GetoptLong::NO_ARGUMENT ],
|
12
|
+
[ "--verbose", "-v", GetoptLong::NO_ARGUMENT ],
|
13
|
+
[ "--debug", "-d", GetoptLong::NO_ARGUMENT ]
|
11
14
|
)
|
12
15
|
|
13
16
|
$dry_run = false
|
17
|
+
$use_threads = false
|
14
18
|
opts.each do |opt, arg|
|
15
19
|
case opt
|
16
20
|
when '--dry-run'
|
17
21
|
$dry_run = true
|
22
|
+
when '--parallel-snapshots'
|
23
|
+
$use_threads = true
|
24
|
+
when '--verbose'
|
25
|
+
$verbose = true
|
26
|
+
when '--debug'
|
27
|
+
$debug = true
|
18
28
|
end
|
19
29
|
end
|
20
30
|
|
21
31
|
|
22
32
|
def usage
|
23
33
|
puts <<-EOF
|
24
|
-
Usage: $0 [-
|
34
|
+
Usage: $0 [-dnv]
|
25
35
|
EOF
|
26
36
|
format = " %-15s %s"
|
37
|
+
puts format % ["-d", "Show debug output."]
|
27
38
|
puts format % ["-n", "Do a dry-run. Nothing is committed. Only show what would be done."]
|
39
|
+
puts format % ["-v", "Show what is being done."]
|
28
40
|
exit
|
29
41
|
end
|
30
42
|
|
31
43
|
usage if ARGV.length > 0
|
32
44
|
|
33
|
-
snapshots = Zfs::Snapshot.
|
34
|
-
dataset_snapshots = group_snapshots_into_datasets(snapshots)
|
45
|
+
snapshots = Zfs::Snapshot.list.select { |snapshot| snapshot.used == 0 and !snapshot.name.include?(snapshot_prefix) }
|
35
46
|
## Group into datasets
|
36
|
-
|
37
|
-
|
38
|
-
|
47
|
+
datasets = Zfs::Dataset.list
|
48
|
+
dataset_snapshots = group_snapshots_into_datasets(snapshots, datasets)
|
49
|
+
dataset_snapshots = datasets_destroy_zero_sized_snapshots(dataset_snapshots)
|
data/bin/zfs-snapshot-mysql
CHANGED
@@ -7,7 +7,9 @@ require 'getoptlong'
|
|
7
7
|
require 'zfstools'
|
8
8
|
|
9
9
|
opts = GetoptLong.new(
|
10
|
-
[ "--dry-run", "-n", GetoptLong::NO_ARGUMENT ]
|
10
|
+
[ "--dry-run", "-n", GetoptLong::NO_ARGUMENT ],
|
11
|
+
[ "--verbose", "-v", GetoptLong::NO_ARGUMENT ],
|
12
|
+
[ "--debug", "-d", GetoptLong::NO_ARGUMENT ]
|
11
13
|
)
|
12
14
|
|
13
15
|
$dry_run = false
|
@@ -15,16 +17,22 @@ opts.each do |opt, arg|
|
|
15
17
|
case opt
|
16
18
|
when '--dry-run'
|
17
19
|
$dry_run = true
|
20
|
+
when '--verbose'
|
21
|
+
$verbose = true
|
22
|
+
when '--debug'
|
23
|
+
$debug = true
|
18
24
|
end
|
19
25
|
end
|
20
26
|
|
21
27
|
|
22
28
|
def usage
|
23
29
|
puts <<-EOF
|
24
|
-
Usage: $0 [-
|
30
|
+
Usage: $0 [-dnv] DATASET
|
25
31
|
EOF
|
26
32
|
format = " %-15s %s"
|
33
|
+
puts format % ["-d", "Show debug output."]
|
27
34
|
puts format % ["-n", "Do a dry-run. Nothing is committed. Only show what would be done."]
|
35
|
+
puts format % ["-v", "Show what is being done."]
|
28
36
|
exit
|
29
37
|
end
|
30
38
|
|
@@ -34,15 +42,5 @@ dataset=ARGV[0]
|
|
34
42
|
|
35
43
|
snapshot_format = "%Y-%m-%dT%H:%M:%S"
|
36
44
|
snapshot_name = Time.now.strftime(snapshot_format)
|
37
|
-
|
38
|
-
|
39
|
-
sql_query=%Q[
|
40
|
-
FLUSH LOGS;
|
41
|
-
FLUSH TABLES WITH READ LOCK;
|
42
|
-
SYSTEM zfs snapshot -r #{snapshot_name};
|
43
|
-
UNLOCK TABLES;
|
44
|
-
]
|
45
|
-
|
46
|
-
cmd = %Q!mysql -e "#{sql_query}"!
|
47
|
-
puts cmd
|
48
|
-
system cmd unless $dry_run
|
45
|
+
snapshot = "#{dataset}@#{snapshot_name}"
|
46
|
+
Zfs::Snapshot.create(snapshot, 'db' => 'mysql', 'recursive' => true)
|
data/lib/zfs/dataset.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Zfs
|
2
|
+
class Dataset
|
3
|
+
attr_reader :name
|
4
|
+
attr_reader :properties
|
5
|
+
attr_reader :db
|
6
|
+
def initialize(name, properties={}, options={})
|
7
|
+
@name = name
|
8
|
+
@properties = properties
|
9
|
+
if properties[snapshot_property] == "mysql"
|
10
|
+
self.contains_db!(properties[snapshot_property])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(dataset)
|
15
|
+
dataset.equal?(self) || (dataset && dataset.name == @name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def contains_db!(kind)
|
19
|
+
@db = kind
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.list(properties=[])
|
24
|
+
datasets = []
|
25
|
+
cmd_properties = ["name"] + properties
|
26
|
+
cmd="zfs list -H -t filesystem,volume -o #{cmd_properties.join(",")} -s name"
|
27
|
+
puts cmd if $debug
|
28
|
+
IO.popen cmd do |io|
|
29
|
+
io.readlines.each do |line|
|
30
|
+
values = line.split
|
31
|
+
name = values.shift
|
32
|
+
dataset_properties = {}
|
33
|
+
properties.each_with_index do |property_name, i|
|
34
|
+
value = values[i]
|
35
|
+
next if value == '-'
|
36
|
+
dataset_properties[property_name] = value
|
37
|
+
end
|
38
|
+
datasets << self.new(name, dataset_properties)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
datasets
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/zfs/snapshot.rb
CHANGED
@@ -10,23 +10,33 @@ module Zfs
|
|
10
10
|
def used
|
11
11
|
if @used.nil? or @@stale_snapshot_size
|
12
12
|
cmd = "zfs get -Hp -o value used #{@name}"
|
13
|
+
puts cmd if $debug
|
13
14
|
@used = %x[#{cmd}].to_i
|
14
15
|
end
|
15
16
|
@used
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
def is_zero?
|
20
|
+
if @used != 0
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
used
|
24
|
+
end
|
25
|
+
|
26
|
+
### List all snapshots
|
27
|
+
def self.list(dataset=nil, options={})
|
21
28
|
snapshots = []
|
22
|
-
|
29
|
+
flags=[]
|
30
|
+
flags << "-d 1" if dataset and !options['recursive']
|
31
|
+
flags << "-r" if options['recursive']
|
32
|
+
cmd = "zfs list #{flags.join(" ")} -H -t snapshot -o name,used -S name"
|
33
|
+
cmd += " #{dataset}" if dataset
|
34
|
+
puts cmd if $debug
|
23
35
|
IO.popen cmd do |io|
|
24
36
|
io.readlines.each do |line|
|
25
37
|
line.chomp!
|
26
|
-
|
27
|
-
|
28
|
-
snapshots << self.new(snapshot_name, used.to_i)
|
29
|
-
end
|
38
|
+
snapshot_name,used = line.split(' ')
|
39
|
+
snapshots << self.new(snapshot_name, used.to_i)
|
30
40
|
end
|
31
41
|
end
|
32
42
|
snapshots
|
@@ -37,7 +47,22 @@ module Zfs
|
|
37
47
|
flags=[]
|
38
48
|
flags << "-r" if options['recursive']
|
39
49
|
cmd = "zfs snapshot #{flags.join(" ")} #{snapshot}"
|
40
|
-
|
50
|
+
|
51
|
+
if options['db']
|
52
|
+
case options['db']
|
53
|
+
when 'mysql'
|
54
|
+
sql_query=<<-EOF.gsub(/^ {10}/, '')
|
55
|
+
|
56
|
+
FLUSH LOGS;
|
57
|
+
FLUSH TABLES WITH READ LOCK;
|
58
|
+
SYSTEM #{cmd};
|
59
|
+
UNLOCK TABLES;
|
60
|
+
EOF
|
61
|
+
cmd = %Q[mysql -e "#{sql_query}"]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
puts cmd if $debug || $verbose
|
41
66
|
system(cmd) unless $dry_run
|
42
67
|
end
|
43
68
|
|
@@ -50,7 +75,7 @@ module Zfs
|
|
50
75
|
flags=["-d"]
|
51
76
|
flags << "-r" if options['recursive']
|
52
77
|
cmd = "zfs destroy #{flags.join(" ")} #{@name}"
|
53
|
-
puts cmd
|
78
|
+
puts cmd if $debug
|
54
79
|
system(cmd) unless $dry_run
|
55
80
|
end
|
56
81
|
|
data/lib/zfstools.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
2
|
|
3
3
|
require 'zfs/snapshot'
|
4
|
+
require 'zfs/dataset'
|
5
|
+
|
6
|
+
def snapshot_property
|
7
|
+
"com.sun:auto-snapshot"
|
8
|
+
end
|
4
9
|
|
5
10
|
def snapshot_prefix(interval=nil)
|
6
11
|
prefix = "zfs-auto-snap"
|
@@ -24,27 +29,6 @@ def snapshot_name(interval)
|
|
24
29
|
snapshot_prefix(interval) + date
|
25
30
|
end
|
26
31
|
|
27
|
-
### Find eligible datasets
|
28
|
-
def find_datasets(datasets, property)
|
29
|
-
cmd="zfs list -H -t filesystem,volume -o name,#{property} -s name"
|
30
|
-
all_datasets = datasets['included'] + datasets['excluded']
|
31
|
-
|
32
|
-
IO.popen cmd do |io|
|
33
|
-
io.readlines.each do |line|
|
34
|
-
dataset,value = line.split(" ")
|
35
|
-
# Skip datasets with no value set
|
36
|
-
next if value == "-"
|
37
|
-
# If the dataset is already included/excluded, skip it (for override checking)
|
38
|
-
next if all_datasets.include? dataset
|
39
|
-
if value == "true"
|
40
|
-
datasets['included'] << dataset
|
41
|
-
elsif value == "false"
|
42
|
-
datasets['excluded'] << dataset
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
32
|
### Find which datasets can be recursively snapshotted
|
49
33
|
### single snapshot restrictions apply to datasets that have a child in the excluded list
|
50
34
|
def find_recursive_datasets(datasets)
|
@@ -57,7 +41,7 @@ def find_recursive_datasets(datasets)
|
|
57
41
|
datasets['included'].each do |dataset|
|
58
42
|
excluded_child = false
|
59
43
|
# Find all children_datasets
|
60
|
-
children_datasets = all_datasets.select { |child_dataset| child_dataset.start_with? dataset }
|
44
|
+
children_datasets = all_datasets.select { |child_dataset| child_dataset.name.start_with? dataset.name }
|
61
45
|
children_datasets.each do |child_dataset|
|
62
46
|
if datasets['excluded'].include?(child_dataset)
|
63
47
|
excluded_child = true
|
@@ -72,9 +56,9 @@ def find_recursive_datasets(datasets)
|
|
72
56
|
|
73
57
|
## Cleanup recursive
|
74
58
|
recursive.each do |dataset|
|
75
|
-
if dataset.include?('/')
|
76
|
-
parts = dataset.rpartition('/')
|
77
|
-
parent = parts[0]
|
59
|
+
if dataset.name.include?('/')
|
60
|
+
parts = dataset.name.rpartition('/')
|
61
|
+
parent = all_datasets.find { |dataset| dataset.name == parts[0] }
|
78
62
|
else
|
79
63
|
parent = dataset
|
80
64
|
end
|
@@ -89,43 +73,96 @@ def find_recursive_datasets(datasets)
|
|
89
73
|
cleaned_recursive << dataset unless recursive.include?(parent)
|
90
74
|
end
|
91
75
|
|
76
|
+
# If any children have a DB, need to set it in the recursive parent
|
77
|
+
cleaned_recursive.each do |parent|
|
78
|
+
all_datasets.each do |dataset|
|
79
|
+
# Is this dataset a child of the parent?
|
80
|
+
next if !dataset.name.include?(parent.name)
|
81
|
+
# If this dataset has a DB, set the parent to contain it as well.
|
82
|
+
if dataset.db
|
83
|
+
parent.contains_db!(dataset.db)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
92
88
|
|
93
|
-
{
|
89
|
+
{
|
90
|
+
'single' => single,
|
91
|
+
'recursive' => cleaned_recursive,
|
92
|
+
'included' => datasets['included'],
|
93
|
+
'excluded' => datasets['excluded'],
|
94
|
+
}
|
94
95
|
end
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
|
97
|
+
|
98
|
+
### Find eligible datasets
|
99
|
+
def filter_datasets(datasets, included_excluded_datasets, property)
|
100
|
+
all_datasets = included_excluded_datasets['included'] + included_excluded_datasets['excluded']
|
101
|
+
|
102
|
+
datasets.each do |dataset|
|
103
|
+
# If the dataset is already included/excluded, skip it (for override checking)
|
104
|
+
next if all_datasets.include? dataset
|
105
|
+
value = dataset.properties[property]
|
106
|
+
if value == "true" || value == "mysql"
|
107
|
+
included_excluded_datasets['included'] << dataset
|
108
|
+
elsif value
|
109
|
+
included_excluded_datasets['excluded'] << dataset
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def find_eligible_datasets(interval)
|
115
|
+
properties = [
|
116
|
+
"#{snapshot_property}:#{interval}",
|
117
|
+
snapshot_property,
|
118
|
+
]
|
119
|
+
datasets = Zfs::Dataset.list(properties)
|
120
|
+
|
121
|
+
### Group datasets into included/excluded for snapshotting
|
122
|
+
included_excluded_datasets = {
|
99
123
|
'included' => [],
|
100
124
|
'excluded' => [],
|
101
125
|
}
|
102
126
|
|
103
|
-
snapshot_name = snapshot_name(interval)
|
104
|
-
|
105
127
|
# Gather the datasets given the override property
|
106
|
-
|
128
|
+
filter_datasets datasets, included_excluded_datasets, "#{snapshot_property}:#{interval}"
|
107
129
|
# Gather all of the datasets without an override
|
108
|
-
|
130
|
+
filter_datasets datasets, included_excluded_datasets, snapshot_property
|
109
131
|
|
110
132
|
### Determine which datasets can be snapshotted recursively and which not
|
111
|
-
datasets = find_recursive_datasets
|
133
|
+
datasets = find_recursive_datasets included_excluded_datasets
|
134
|
+
end
|
135
|
+
|
136
|
+
### Generate new snapshots
|
137
|
+
def do_new_snapshots(datasets, interval)
|
138
|
+
snapshot_name = snapshot_name(interval)
|
112
139
|
|
140
|
+
threads = []
|
113
141
|
# Snapshot single
|
114
142
|
datasets['single'].each do |dataset|
|
115
|
-
|
143
|
+
threads << Thread.new do
|
144
|
+
Zfs::Snapshot.create("#{dataset.name}@#{snapshot_name}", 'db' => dataset.db)
|
145
|
+
end
|
146
|
+
threads.last.join unless $use_threads
|
116
147
|
end
|
117
148
|
|
118
149
|
# Snapshot recursive
|
119
150
|
datasets['recursive'].each do |dataset|
|
120
|
-
|
151
|
+
threads << Thread.new do
|
152
|
+
Zfs::Snapshot.create("#{dataset.name}@#{snapshot_name}", 'recursive' => true, 'db' => dataset.db)
|
153
|
+
end
|
154
|
+
threads.last.join unless $use_threads
|
121
155
|
end
|
156
|
+
|
157
|
+
threads.each { |th| th.join }
|
122
158
|
end
|
123
159
|
|
124
|
-
def group_snapshots_into_datasets(snapshots)
|
160
|
+
def group_snapshots_into_datasets(snapshots, datasets)
|
125
161
|
dataset_snapshots = Hash.new {|h,k| h[k] = [] }
|
126
162
|
### Sort into datasets
|
127
163
|
snapshots.each do |snapshot|
|
128
|
-
|
164
|
+
snapshot_name = snapshot.name.split('@')[0]
|
165
|
+
dataset = datasets.find { |dataset| dataset.name == snapshot_name }
|
129
166
|
dataset_snapshots[dataset] << snapshot
|
130
167
|
end
|
131
168
|
dataset_snapshots
|
@@ -137,8 +174,8 @@ def destroy_zero_sized_snapshots(snapshots)
|
|
137
174
|
saved_snapshot = snapshots.shift(1)
|
138
175
|
remaining_snapshots = [saved_snapshot]
|
139
176
|
snapshots.each do |snapshot|
|
140
|
-
if snapshot.
|
141
|
-
puts "Destroying zero-sized snapshot: #{snapshot.name}"
|
177
|
+
if snapshot.is_zero?
|
178
|
+
puts "Destroying zero-sized snapshot: #{snapshot.name}" if $verbose
|
142
179
|
snapshot.destroy
|
143
180
|
else
|
144
181
|
remaining_snapshots << snapshot
|
@@ -147,17 +184,32 @@ def destroy_zero_sized_snapshots(snapshots)
|
|
147
184
|
remaining_snapshots
|
148
185
|
end
|
149
186
|
|
150
|
-
|
151
|
-
def cleanup_expired_snapshots(interval, keep)
|
152
|
-
### Find all snapshots matching this interval
|
153
|
-
snapshots = Zfs::Snapshot.find snapshot_prefix(interval)
|
154
|
-
dataset_snapshots = group_snapshots_into_datasets(snapshots)
|
155
|
-
|
187
|
+
def datasets_destroy_zero_sized_snapshots(dataset_snapshots)
|
156
188
|
### Cleanup zero-sized snapshots before purging old snapshots
|
157
189
|
### Keep the most recent one of the zeros and restore it for the later expired purging
|
190
|
+
threads = []
|
158
191
|
dataset_snapshots.each do |dataset, snapshots|
|
159
|
-
##
|
160
|
-
|
192
|
+
## Safe to run this in a thread as each dataset's snapshots shift on themselves, but not outside.
|
193
|
+
threads << Thread.new do
|
194
|
+
## Delete all of the remaining zero-sized snapshots
|
195
|
+
dataset_snapshots[dataset] = destroy_zero_sized_snapshots(snapshots)
|
196
|
+
end
|
197
|
+
threads.last.join unless $use_threads
|
198
|
+
end
|
199
|
+
threads.each { |th| th.join }
|
200
|
+
dataset_snapshots
|
201
|
+
end
|
202
|
+
|
203
|
+
### Find and destroy expired snapshots
|
204
|
+
def cleanup_expired_snapshots(datasets, interval, keep, should_destroy_zero_sized_snapshots)
|
205
|
+
### Find all snapshots matching this interval
|
206
|
+
snapshots = Zfs::Snapshot.list.select { |snapshot| snapshot.name.include?(snapshot_prefix(interval)) }
|
207
|
+
dataset_snapshots = group_snapshots_into_datasets(snapshots, datasets['included'] + datasets['excluded'])
|
208
|
+
### Filter out datasets not included
|
209
|
+
dataset_snapshots.select! { |dataset, snapshots| datasets['included'].include?(dataset) }
|
210
|
+
|
211
|
+
if should_destroy_zero_sized_snapshots
|
212
|
+
dataset_snapshots = datasets_destroy_zero_sized_snapshots(dataset_snapshots)
|
161
213
|
end
|
162
214
|
|
163
215
|
### Now that zero-sized are removed, remove expired snapshots
|
@@ -166,7 +218,12 @@ def cleanup_expired_snapshots(interval, keep)
|
|
166
218
|
dataset_snapshots[dataset].shift(keep)
|
167
219
|
# ... Now the list only contains snapshots eligible to be destroyed.
|
168
220
|
end
|
221
|
+
threads = []
|
169
222
|
dataset_snapshots.values.flatten.each do |snapshot|
|
170
|
-
|
223
|
+
threads << Thread.new do
|
224
|
+
snapshot.destroy
|
225
|
+
end
|
226
|
+
threads.last.join unless $use_threads
|
171
227
|
end
|
228
|
+
threads.each { |th| th.join }
|
172
229
|
end
|
data/spec/zfstools_spec.rb
CHANGED
@@ -1,7 +1,251 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
describe "Zfstools" do
|
4
|
-
it "
|
5
|
-
|
3
|
+
describe "Zfstools", "#group_snapshots_into_datasets" do
|
4
|
+
it "Groups snapshots into their datasets" do
|
5
|
+
snapshots = {
|
6
|
+
'tank' => [
|
7
|
+
Zfs::Snapshot.new('tank@1'),
|
8
|
+
Zfs::Snapshot.new('tank@2'),
|
9
|
+
],
|
10
|
+
'tank/a' => [
|
11
|
+
Zfs::Snapshot.new('tank/a@1'),
|
12
|
+
Zfs::Snapshot.new('tank/a@2'),
|
13
|
+
],
|
14
|
+
'tank/a/1' => [
|
15
|
+
Zfs::Snapshot.new('tank/a/1@1'),
|
16
|
+
],
|
17
|
+
'tank/a/2' => [
|
18
|
+
Zfs::Snapshot.new('tank/a/2@1'),
|
19
|
+
],
|
20
|
+
'tank/b' => [
|
21
|
+
Zfs::Snapshot.new('tank/b@1'),
|
22
|
+
],
|
23
|
+
'tank/c' => [
|
24
|
+
Zfs::Snapshot.new('tank/c@1'),
|
25
|
+
],
|
26
|
+
'tank/d' => [
|
27
|
+
Zfs::Snapshot.new('tank/d@1'),
|
28
|
+
],
|
29
|
+
'tank/d/1' => [
|
30
|
+
Zfs::Snapshot.new('tank/d/1@2'),
|
31
|
+
]
|
32
|
+
}
|
33
|
+
datasets = {
|
34
|
+
'tank' => Zfs::Dataset.new('tank'),
|
35
|
+
'tank/a' => Zfs::Dataset.new('tank/a'),
|
36
|
+
'tank/a/1' => Zfs::Dataset.new('tank/a/1'),
|
37
|
+
'tank/a/2' => Zfs::Dataset.new('tank/a/2'),
|
38
|
+
'tank/b' => Zfs::Dataset.new('tank/b'),
|
39
|
+
'tank/c' => Zfs::Dataset.new('tank/c'),
|
40
|
+
'tank/d' => Zfs::Dataset.new('tank/d'),
|
41
|
+
'tank/d/1' => Zfs::Dataset.new('tank/d/1'),
|
42
|
+
}
|
43
|
+
dataset_snapshots = group_snapshots_into_datasets(snapshots.values.flatten, datasets.values)
|
44
|
+
dataset_snapshots.should eq({
|
45
|
+
datasets['tank'] => snapshots['tank'],
|
46
|
+
datasets['tank/a'] => snapshots['tank/a'],
|
47
|
+
datasets['tank/a/1'] => snapshots['tank/a/1'],
|
48
|
+
datasets['tank/a/2'] => snapshots['tank/a/2'],
|
49
|
+
datasets['tank/b'] => snapshots['tank/b'],
|
50
|
+
datasets['tank/c'] => snapshots['tank/c'],
|
51
|
+
datasets['tank/d'] => snapshots['tank/d'],
|
52
|
+
datasets['tank/d/1'] => snapshots['tank/d/1'],
|
53
|
+
})
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "Zfstools", "#find_recursive_datasets" do
|
59
|
+
it "considers all included as recursive" do
|
60
|
+
tank = Zfs::Dataset.new("tank")
|
61
|
+
datasets = {
|
62
|
+
'included' => [
|
63
|
+
Zfs::Dataset.new("tank"),
|
64
|
+
Zfs::Dataset.new("tank/a"),
|
65
|
+
Zfs::Dataset.new("tank/a/1"),
|
66
|
+
Zfs::Dataset.new("tank/b"),
|
67
|
+
],
|
68
|
+
'excluded' => [],
|
69
|
+
}
|
70
|
+
recursive_datasets = find_recursive_datasets(datasets)
|
71
|
+
recursive_datasets['recursive'].should eq([Zfs::Dataset.new("tank")])
|
72
|
+
recursive_datasets['single'].should eq([])
|
73
|
+
end
|
74
|
+
|
75
|
+
it "considers all multiple parent datasets as recursive" do
|
76
|
+
tank = Zfs::Dataset.new("tank")
|
77
|
+
datasets = {
|
78
|
+
'included' => [
|
79
|
+
Zfs::Dataset.new("tank"),
|
80
|
+
Zfs::Dataset.new("tank/a"),
|
81
|
+
Zfs::Dataset.new("tank/a/1"),
|
82
|
+
Zfs::Dataset.new("tank/b"),
|
83
|
+
Zfs::Dataset.new("rpool"),
|
84
|
+
Zfs::Dataset.new("rpool/a"),
|
85
|
+
Zfs::Dataset.new("rpool/b"),
|
86
|
+
Zfs::Dataset.new("zpool"),
|
87
|
+
Zfs::Dataset.new("zpool/a"),
|
88
|
+
Zfs::Dataset.new("zpool/b"),
|
89
|
+
],
|
90
|
+
'excluded' => [],
|
91
|
+
}
|
92
|
+
recursive_datasets = find_recursive_datasets(datasets)
|
93
|
+
recursive_datasets['recursive'].should eq([
|
94
|
+
Zfs::Dataset.new("tank"),
|
95
|
+
Zfs::Dataset.new("rpool"),
|
96
|
+
Zfs::Dataset.new("zpool"),
|
97
|
+
])
|
98
|
+
recursive_datasets['single'].should eq([])
|
99
|
+
end
|
100
|
+
|
101
|
+
it "considers all excluded as empty" do
|
102
|
+
tank = Zfs::Dataset.new("tank")
|
103
|
+
datasets = [
|
104
|
+
Zfs::Dataset.new("tank"),
|
105
|
+
Zfs::Dataset.new("tank/a"),
|
106
|
+
Zfs::Dataset.new("tank/a/1"),
|
107
|
+
Zfs::Dataset.new("tank/b"),
|
108
|
+
]
|
109
|
+
included_excluded_datasets = {
|
110
|
+
'included' => [],
|
111
|
+
'excluded' => datasets,
|
112
|
+
}
|
113
|
+
recursive_datasets = find_recursive_datasets(included_excluded_datasets)
|
114
|
+
recursive_datasets['recursive'].should eq([])
|
115
|
+
recursive_datasets['single'].should eq([])
|
116
|
+
end
|
117
|
+
|
118
|
+
it "considers first level excluded" do
|
119
|
+
included_excluded_datasets = {
|
120
|
+
'included' => [
|
121
|
+
Zfs::Dataset.new("tank"),
|
122
|
+
Zfs::Dataset.new("tank/a"),
|
123
|
+
Zfs::Dataset.new("tank/a/1"),
|
124
|
+
],
|
125
|
+
'excluded' => [
|
126
|
+
Zfs::Dataset.new("rpool"),
|
127
|
+
Zfs::Dataset.new("rpool/a"),
|
128
|
+
]
|
129
|
+
}
|
130
|
+
recursive_datasets = find_recursive_datasets(included_excluded_datasets)
|
131
|
+
recursive_datasets['recursive'].should eq([
|
132
|
+
Zfs::Dataset.new("tank"),
|
133
|
+
])
|
134
|
+
recursive_datasets['single'].should eq([])
|
135
|
+
end
|
136
|
+
|
137
|
+
it "considers second level excluded" do
|
138
|
+
included_excluded_datasets = {
|
139
|
+
'included' => [
|
140
|
+
Zfs::Dataset.new("tank"),
|
141
|
+
Zfs::Dataset.new("tank/a"),
|
142
|
+
Zfs::Dataset.new("tank/a/1"),
|
143
|
+
],
|
144
|
+
'excluded' => [
|
145
|
+
Zfs::Dataset.new("tank/b"),
|
146
|
+
]
|
147
|
+
}
|
148
|
+
recursive_datasets = find_recursive_datasets(included_excluded_datasets)
|
149
|
+
recursive_datasets['recursive'].should eq([
|
150
|
+
Zfs::Dataset.new("tank/a"),
|
151
|
+
])
|
152
|
+
recursive_datasets['single'].should eq([
|
153
|
+
Zfs::Dataset.new("tank"),
|
154
|
+
])
|
155
|
+
end
|
156
|
+
|
157
|
+
it "considers third level excluded" do
|
158
|
+
included_excluded_datasets = {
|
159
|
+
'included' => [
|
160
|
+
Zfs::Dataset.new("tank"),
|
161
|
+
Zfs::Dataset.new("tank/a"),
|
162
|
+
Zfs::Dataset.new("tank/a/1"),
|
163
|
+
Zfs::Dataset.new("tank/a/2"),
|
164
|
+
Zfs::Dataset.new("tank/b"),
|
165
|
+
Zfs::Dataset.new("tank/b/1"),
|
166
|
+
Zfs::Dataset.new("tank/b/2"),
|
167
|
+
],
|
168
|
+
'excluded' => [
|
169
|
+
Zfs::Dataset.new("tank/c"),
|
170
|
+
]
|
171
|
+
}
|
172
|
+
recursive_datasets = find_recursive_datasets(included_excluded_datasets)
|
173
|
+
recursive_datasets['recursive'].should eq([
|
174
|
+
Zfs::Dataset.new("tank/a"),
|
175
|
+
Zfs::Dataset.new("tank/b"),
|
176
|
+
])
|
177
|
+
recursive_datasets['single'].should eq([
|
178
|
+
Zfs::Dataset.new("tank"),
|
179
|
+
])
|
180
|
+
end
|
181
|
+
|
182
|
+
it "considers child with mysql db in parent recursive" do
|
183
|
+
included_excluded_datasets = {
|
184
|
+
'included' => [
|
185
|
+
Zfs::Dataset.new("tank"),
|
186
|
+
Zfs::Dataset.new("tank/a"),
|
187
|
+
Zfs::Dataset.new("tank/a/1"),
|
188
|
+
Zfs::Dataset.new("tank/a/2"),
|
189
|
+
Zfs::Dataset.new("tank/b"),
|
190
|
+
Zfs::Dataset.new("tank/b/1").contains_db!("mysql"),
|
191
|
+
Zfs::Dataset.new("tank/b/2"),
|
192
|
+
],
|
193
|
+
'excluded' => []
|
194
|
+
}
|
195
|
+
recursive_datasets = find_recursive_datasets(included_excluded_datasets)
|
196
|
+
recursive_datasets['recursive'].should eq([
|
197
|
+
Zfs::Dataset.new("tank").contains_db!("mysql"),
|
198
|
+
])
|
199
|
+
recursive_datasets['single'].should eq([])
|
200
|
+
end
|
201
|
+
|
202
|
+
it "considers child with mysql db in recursive with singles and exclusions" do
|
203
|
+
included_excluded_datasets = {
|
204
|
+
'included' => [
|
205
|
+
Zfs::Dataset.new("tank"),
|
206
|
+
Zfs::Dataset.new("tank/a"),
|
207
|
+
Zfs::Dataset.new("tank/a/1"),
|
208
|
+
Zfs::Dataset.new("tank/a/2").contains_db!("mysql"),
|
209
|
+
Zfs::Dataset.new("tank/b"),
|
210
|
+
Zfs::Dataset.new("tank/b/1"),
|
211
|
+
],
|
212
|
+
'excluded' => [
|
213
|
+
Zfs::Dataset.new("tank/b/2"),
|
214
|
+
]
|
215
|
+
}
|
216
|
+
recursive_datasets = find_recursive_datasets(included_excluded_datasets)
|
217
|
+
recursive_datasets['recursive'].should eq([
|
218
|
+
Zfs::Dataset.new("tank/a").contains_db!("mysql"),
|
219
|
+
Zfs::Dataset.new("tank/b/1"),
|
220
|
+
])
|
221
|
+
recursive_datasets['single'].should eq([
|
222
|
+
Zfs::Dataset.new("tank"),
|
223
|
+
Zfs::Dataset.new("tank/b"),
|
224
|
+
])
|
225
|
+
end
|
226
|
+
|
227
|
+
it "considers child with mysql db in single with recursives and exclusions" do
|
228
|
+
included_excluded_datasets = {
|
229
|
+
'included' => [
|
230
|
+
Zfs::Dataset.new("tank"),
|
231
|
+
Zfs::Dataset.new("tank/a"),
|
232
|
+
Zfs::Dataset.new("tank/a/1"),
|
233
|
+
Zfs::Dataset.new("tank/a/2"),
|
234
|
+
Zfs::Dataset.new("tank/b"),
|
235
|
+
Zfs::Dataset.new("tank/b/1").contains_db!("mysql"),
|
236
|
+
],
|
237
|
+
'excluded' => [
|
238
|
+
Zfs::Dataset.new("tank/b/2"),
|
239
|
+
]
|
240
|
+
}
|
241
|
+
recursive_datasets = find_recursive_datasets(included_excluded_datasets)
|
242
|
+
recursive_datasets['recursive'].should eq([
|
243
|
+
Zfs::Dataset.new("tank/a"),
|
244
|
+
Zfs::Dataset.new("tank/b/1").contains_db!("mysql"),
|
245
|
+
])
|
246
|
+
recursive_datasets['single'].should eq([
|
247
|
+
Zfs::Dataset.new("tank"),
|
248
|
+
Zfs::Dataset.new("tank/b"),
|
249
|
+
])
|
6
250
|
end
|
7
251
|
end
|
data/zfstools.gemspec
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "zfstools"
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Bryan Drewery"]
|
12
|
+
s.date = "2012-04-28"
|
13
|
+
s.description = "ZFS admin scripts, such as automatic snapshots, mysql snapshotting, scrubbing, etc."
|
14
|
+
s.email = "bryan@shatow.net"
|
15
|
+
s.executables = ["zfs-auto-snapshot", "zfs-cleanup-snapshots", "zfs-snapshot-mysql"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE.txt",
|
18
|
+
"README.md",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".rspec",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.md",
|
28
|
+
"README.rdoc",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"bin/zfs-auto-snapshot",
|
32
|
+
"bin/zfs-cleanup-snapshots",
|
33
|
+
"bin/zfs-snapshot-mysql",
|
34
|
+
"features/step_definitions/zfstools_steps.rb",
|
35
|
+
"features/support/env.rb",
|
36
|
+
"features/zfstools.feature",
|
37
|
+
"lib/zfs/dataset.rb",
|
38
|
+
"lib/zfs/snapshot.rb",
|
39
|
+
"lib/zfstools.rb",
|
40
|
+
"spec/spec_helper.rb",
|
41
|
+
"spec/zfstools_spec.rb",
|
42
|
+
"zfstools.gemspec"
|
43
|
+
]
|
44
|
+
s.homepage = "http://github.com/bdrewery/zfstools"
|
45
|
+
s.licenses = ["MIT"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = "1.8.16"
|
48
|
+
s.summary = "ZFSTools"
|
49
|
+
|
50
|
+
if s.respond_to? :specification_version then
|
51
|
+
s.specification_version = 3
|
52
|
+
|
53
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
54
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
|
55
|
+
s.add_development_dependency(%q<yard>, ["~> 0.7"])
|
56
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
57
|
+
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
59
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
60
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
61
|
+
else
|
62
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
63
|
+
s.add_dependency(%q<yard>, ["~> 0.7"])
|
64
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
65
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
68
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
72
|
+
s.add_dependency(%q<yard>, ["~> 0.7"])
|
73
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
74
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
75
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
76
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
77
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
metadata
CHANGED
@@ -1,120 +1,93 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zfstools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 1
|
8
|
-
- 0
|
9
|
-
prerelease: false
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- Bryan Drewery
|
13
|
-
autorequire:
|
9
|
+
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
date: 2012-
|
17
|
-
default_executable: !!null
|
12
|
+
date: 2012-04-28 00:00:00.000000000Z
|
18
13
|
dependencies:
|
19
14
|
- !ruby/object:Gem::Dependency
|
20
15
|
name: rspec
|
21
|
-
requirement: &
|
16
|
+
requirement: &15950360 !ruby/object:Gem::Requirement
|
22
17
|
none: false
|
23
18
|
requirements:
|
24
19
|
- - ~>
|
25
20
|
- !ruby/object:Gem::Version
|
26
21
|
version: 2.8.0
|
27
|
-
segments:
|
28
|
-
- 2
|
29
|
-
- 8
|
30
|
-
- 0
|
31
22
|
type: :development
|
32
23
|
prerelease: false
|
33
|
-
version_requirements: *
|
24
|
+
version_requirements: *15950360
|
34
25
|
- !ruby/object:Gem::Dependency
|
35
26
|
name: yard
|
36
|
-
requirement: &
|
27
|
+
requirement: &15949660 !ruby/object:Gem::Requirement
|
37
28
|
none: false
|
38
29
|
requirements:
|
39
30
|
- - ~>
|
40
31
|
- !ruby/object:Gem::Version
|
41
32
|
version: '0.7'
|
42
|
-
segments:
|
43
|
-
- 0
|
44
|
-
- 7
|
45
33
|
type: :development
|
46
34
|
prerelease: false
|
47
|
-
version_requirements: *
|
35
|
+
version_requirements: *15949660
|
48
36
|
- !ruby/object:Gem::Dependency
|
49
37
|
name: rdoc
|
50
|
-
requirement: &
|
38
|
+
requirement: &15948980 !ruby/object:Gem::Requirement
|
51
39
|
none: false
|
52
40
|
requirements:
|
53
41
|
- - ~>
|
54
42
|
- !ruby/object:Gem::Version
|
55
43
|
version: '3.12'
|
56
|
-
segments:
|
57
|
-
- 3
|
58
|
-
- 12
|
59
44
|
type: :development
|
60
45
|
prerelease: false
|
61
|
-
version_requirements: *
|
46
|
+
version_requirements: *15948980
|
62
47
|
- !ruby/object:Gem::Dependency
|
63
48
|
name: cucumber
|
64
|
-
requirement: &
|
49
|
+
requirement: &15948340 !ruby/object:Gem::Requirement
|
65
50
|
none: false
|
66
51
|
requirements:
|
67
52
|
- - ! '>='
|
68
53
|
- !ruby/object:Gem::Version
|
69
54
|
version: '0'
|
70
|
-
segments:
|
71
|
-
- 0
|
72
55
|
type: :development
|
73
56
|
prerelease: false
|
74
|
-
version_requirements: *
|
57
|
+
version_requirements: *15948340
|
75
58
|
- !ruby/object:Gem::Dependency
|
76
59
|
name: bundler
|
77
|
-
requirement: &
|
60
|
+
requirement: &15947700 !ruby/object:Gem::Requirement
|
78
61
|
none: false
|
79
62
|
requirements:
|
80
63
|
- - ~>
|
81
64
|
- !ruby/object:Gem::Version
|
82
65
|
version: 1.0.0
|
83
|
-
segments:
|
84
|
-
- 1
|
85
|
-
- 0
|
86
|
-
- 0
|
87
66
|
type: :development
|
88
67
|
prerelease: false
|
89
|
-
version_requirements: *
|
68
|
+
version_requirements: *15947700
|
90
69
|
- !ruby/object:Gem::Dependency
|
91
70
|
name: jeweler
|
92
|
-
requirement: &
|
71
|
+
requirement: &15946900 !ruby/object:Gem::Requirement
|
93
72
|
none: false
|
94
73
|
requirements:
|
95
74
|
- - ~>
|
96
75
|
- !ruby/object:Gem::Version
|
97
76
|
version: 1.8.3
|
98
|
-
segments:
|
99
|
-
- 1
|
100
|
-
- 8
|
101
|
-
- 3
|
102
77
|
type: :development
|
103
78
|
prerelease: false
|
104
|
-
version_requirements: *
|
79
|
+
version_requirements: *15946900
|
105
80
|
- !ruby/object:Gem::Dependency
|
106
81
|
name: simplecov
|
107
|
-
requirement: &
|
82
|
+
requirement: &15946240 !ruby/object:Gem::Requirement
|
108
83
|
none: false
|
109
84
|
requirements:
|
110
85
|
- - ! '>='
|
111
86
|
- !ruby/object:Gem::Version
|
112
87
|
version: '0'
|
113
|
-
segments:
|
114
|
-
- 0
|
115
88
|
type: :development
|
116
89
|
prerelease: false
|
117
|
-
version_requirements: *
|
90
|
+
version_requirements: *15946240
|
118
91
|
description: ZFS admin scripts, such as automatic snapshots, mysql snapshotting, scrubbing,
|
119
92
|
etc.
|
120
93
|
email: bryan@shatow.net
|
@@ -143,15 +116,16 @@ files:
|
|
143
116
|
- features/step_definitions/zfstools_steps.rb
|
144
117
|
- features/support/env.rb
|
145
118
|
- features/zfstools.feature
|
119
|
+
- lib/zfs/dataset.rb
|
146
120
|
- lib/zfs/snapshot.rb
|
147
121
|
- lib/zfstools.rb
|
148
122
|
- spec/spec_helper.rb
|
149
123
|
- spec/zfstools_spec.rb
|
150
|
-
|
124
|
+
- zfstools.gemspec
|
151
125
|
homepage: http://github.com/bdrewery/zfstools
|
152
126
|
licenses:
|
153
127
|
- MIT
|
154
|
-
post_install_message:
|
128
|
+
post_install_message:
|
155
129
|
rdoc_options: []
|
156
130
|
require_paths:
|
157
131
|
- lib
|
@@ -163,19 +137,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
163
137
|
version: '0'
|
164
138
|
segments:
|
165
139
|
- 0
|
166
|
-
hash:
|
140
|
+
hash: 1469645042708547290
|
167
141
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
142
|
none: false
|
169
143
|
requirements:
|
170
144
|
- - ! '>='
|
171
145
|
- !ruby/object:Gem::Version
|
172
146
|
version: '0'
|
173
|
-
segments:
|
174
|
-
- 0
|
175
147
|
requirements: []
|
176
|
-
rubyforge_project:
|
177
|
-
rubygems_version: 1.
|
178
|
-
signing_key:
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 1.8.16
|
150
|
+
signing_key:
|
179
151
|
specification_version: 3
|
180
152
|
summary: ZFSTools
|
181
153
|
test_files: []
|