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 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
1
+ 0.2.0
@@ -7,29 +7,46 @@ require 'getoptlong'
7
7
  require 'zfstools'
8
8
 
9
9
  opts = GetoptLong.new(
10
- [ "--utc", "-u", GetoptLong::NO_ARGUMENT ],
11
- [ "--dry-run", "-n", GetoptLong::NO_ARGUMENT ]
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 [-un] <INTERVAL> <KEEP>
41
+ Usage: $0 [-dknpuv] <INTERVAL> <KEEP>
29
42
  EOF
30
43
  format = " %-15s %s"
31
- puts format % ["-u", "Use UTC for snapshots."]
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)
@@ -7,32 +7,43 @@ require 'getoptlong'
7
7
  require 'zfstools'
8
8
 
9
9
  opts = GetoptLong.new(
10
- [ "--dry-run", "-n", GetoptLong::NO_ARGUMENT ]
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 [-n]
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.find.select { |snapshot| snapshot.used == 0 and !snapshot.name.include?(snapshot_prefix) }
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
- dataset_snapshots.each do |dataset, snapshots|
37
- destroy_zero_sized_snapshots(snapshots)
38
- end
47
+ datasets = Zfs::Dataset.list
48
+ dataset_snapshots = group_snapshots_into_datasets(snapshots, datasets)
49
+ dataset_snapshots = datasets_destroy_zero_sized_snapshots(dataset_snapshots)
@@ -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 [-n] DATASET
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
- snapshot_name = "#{dataset}@#{snapshot_name}"
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)
@@ -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
- ### Find all snapshots in the given interval
19
- ### @param String match_on The string to match on snapshots
20
- def self.find(match_on=nil)
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
- cmd = "zfs list -H -t snapshot -o name,used -S name"
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
- if match_on.nil? or line.include?(match_on)
27
- snapshot_name,used = line.split(' ')
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
- puts cmd
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
- { 'single' => single, 'recursive' => cleaned_recursive }
89
+ {
90
+ 'single' => single,
91
+ 'recursive' => cleaned_recursive,
92
+ 'included' => datasets['included'],
93
+ 'excluded' => datasets['excluded'],
94
+ }
94
95
  end
95
96
 
96
- ### Generate new snapshots
97
- def do_new_snapshots(interval)
98
- datasets = {
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
- find_datasets datasets, "com.sun:auto-snapshot:#{interval}"
128
+ filter_datasets datasets, included_excluded_datasets, "#{snapshot_property}:#{interval}"
107
129
  # Gather all of the datasets without an override
108
- find_datasets datasets, "com.sun:auto-snapshot"
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 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
- Zfs::Snapshot.create("#{dataset}@#{snapshot_name}")
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
- Zfs::Snapshot.create("#{dataset}@#{snapshot_name}", 'recursive' => true)
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
- dataset = snapshot.name.split('@')[0]
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.used == 0
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
- ### Find and destroy expired snapshots
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
- ## Delete all of the remaining zero-sized snapshots
160
- dataset_snapshots[dataset] = destroy_zero_sized_snapshots(snapshots)
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
- snapshot.destroy
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
@@ -1,7 +1,251 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- describe "Zfstools" do
4
- it "fails" do
5
- fail "hey buddy, you should probably rename this file and start specing for real"
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.1.0
5
- segments:
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: !!null
9
+ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
- date: 2012-02-15 00:00:00.000000000 -06:00
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: &14899340 !ruby/object:Gem::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: *14899340
24
+ version_requirements: *15950360
34
25
  - !ruby/object:Gem::Dependency
35
26
  name: yard
36
- requirement: &14896780 !ruby/object:Gem::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: *14896780
35
+ version_requirements: *15949660
48
36
  - !ruby/object:Gem::Dependency
49
37
  name: rdoc
50
- requirement: &14895400 !ruby/object:Gem::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: *14895400
46
+ version_requirements: *15948980
62
47
  - !ruby/object:Gem::Dependency
63
48
  name: cucumber
64
- requirement: &14894220 !ruby/object:Gem::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: *14894220
57
+ version_requirements: *15948340
75
58
  - !ruby/object:Gem::Dependency
76
59
  name: bundler
77
- requirement: &14893080 !ruby/object:Gem::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: *14893080
68
+ version_requirements: *15947700
90
69
  - !ruby/object:Gem::Dependency
91
70
  name: jeweler
92
- requirement: &14891840 !ruby/object:Gem::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: *14891840
79
+ version_requirements: *15946900
105
80
  - !ruby/object:Gem::Dependency
106
81
  name: simplecov
107
- requirement: &14865880 !ruby/object:Gem::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: *14865880
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
- has_rdoc: true
124
+ - zfstools.gemspec
151
125
  homepage: http://github.com/bdrewery/zfstools
152
126
  licenses:
153
127
  - MIT
154
- post_install_message: !!null
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: 1580374833721105612
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: !!null
177
- rubygems_version: 1.3.7
178
- signing_key: !!null
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: []