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 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: []