zfstools 0.1.0 → 0.2.0
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/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: []
|