lvmsync 3.1.0 → 3.1.1

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.
Files changed (4) hide show
  1. data/bin/lvmsync +55 -29
  2. data/lib/lvm/logical_volume.rb +83 -0
  3. data/lib/lvm.rb +1 -0
  4. metadata +24 -7
data/bin/lvmsync CHANGED
@@ -20,6 +20,7 @@
20
20
  require 'optparse'
21
21
  require 'lvm'
22
22
  require 'git-version-bump'
23
+ require 'open3'
23
24
 
24
25
  PROTOCOL_VERSION = "lvmsync PROTO[3]"
25
26
 
@@ -111,13 +112,13 @@ def run_apply(opts)
111
112
  infile = opts[:apply] == '-' ? $stdin : File.open(opts[:apply], 'r')
112
113
  destdev = opts[:device]
113
114
 
114
- process_dumpdata(infile, destdev, snapfile)
115
+ process_dumpdata(infile, destdev, snapfile, opts)
115
116
  ensure
116
117
  snapfile.close unless snapfile.nil?
117
118
  infile.close unless infile.nil? or infile == $stdin
118
119
  end
119
120
 
120
- def process_dumpdata(instream, destdev, snapback = nil)
121
+ def process_dumpdata(instream, destdev, snapback = nil, opts)
121
122
  handshake = instream.readline.chomp
122
123
  unless handshake == PROTOCOL_VERSION
123
124
  $stderr.puts "Handshake failed; protocol mismatch? (saw '#{handshake}' expected '#{PROTOCOL_VERSION}'"
@@ -163,27 +164,19 @@ def run_client(opts)
163
164
  destdev = opts[:destdev]
164
165
  outfd = nil
165
166
 
166
- vg, lv = parse_snapshot_name(snapshot)
167
-
168
- vgconfig = LVM::VGConfig.new(vg)
169
-
170
- if vgconfig.logical_volumes[lv].nil?
171
- $stderr.puts "#{snapshot}: Could not find logical volume"
167
+ lv = begin
168
+ LVM::LogicalVolume.new(snapshot)
169
+ rescue RuntimeError => e
170
+ $stderr.puts "#{snapshot}: could not find logical volume (#{e.message})"
172
171
  exit 1
173
172
  end
174
173
 
175
- snap = if vgconfig.logical_volumes[lv].snapshot?
176
- if vgconfig.logical_volumes[lv].thin?
177
- LVM::ThinSnapshot.new(vg, lv)
178
- else
179
- LVM::Snapshot.new(vg, lv)
180
- end
181
- else
174
+ unless lv.snapshot?
182
175
  $stderr.puts "#{snapshot}: Not a snapshot device"
183
176
  exit 1
184
177
  end
185
178
 
186
- $stderr.puts "Origin device: #{vg}/#{snap.origin}" if opts[:verbose]
179
+ $stderr.puts "Origin device: #{lv.origin.path}" if opts[:verbose]
187
180
 
188
181
  # Since, in principle, we're not supposed to be reading from snapshot
189
182
  # devices directly, the kernel makes no attempt to make the device's read
@@ -195,47 +188,82 @@ def run_client(opts)
195
188
  snapback = opts[:snapback] ? "--snapback #{opts[:snapback]}" : ''
196
189
 
197
190
  if opts[:stdout]
198
- outfd = $stdout
191
+ dump_changes(lv, $stdout, opts)
199
192
  else
193
+ verbose = opts[:verbose] ? '-v' : ''
200
194
  server_cmd = if desthost
201
- "ssh #{desthost} lvmsync --apply - #{snapback} #{destdev}"
195
+ "ssh #{desthost} lvmsync --apply - #{snapback} #{verbose} #{destdev}"
202
196
  else
203
- "#{$0} --apply - #{snapback} #{destdev}"
197
+ "#{$0} --apply - #{snapback} #{verbose} #{destdev}"
198
+ end
199
+
200
+ exit_status = nil
201
+ errors = nil
202
+
203
+ Open3.popen3(server_cmd) do |stdin_fd, stdout_fd, stderr_fd, wait_thr|
204
+ dump_changes(lv, stdin_fd, opts) do
205
+ more_to_read = true
206
+ while more_to_read
207
+ more_to_read = false
208
+ (IO.select([stdout_fd, stderr_fd], [], [], 0) || [[]])[0].each do |fd|
209
+ more_to_read = true
210
+ $stderr.puts "\e[2K\rremote:#{fd.readline}"
211
+ end
212
+ end
213
+ end
214
+ stdin_fd.close
215
+ [stderr_fd, stdout_fd].each do |fd|
216
+ until fd.eof?
217
+ $stderr.puts "\e[2K\rremote:#{fd.readline}"
218
+ end
219
+ end
220
+ exit_status = wait_thr.value if wait_thr
204
221
  end
205
222
 
206
- outfd = IO.popen(server_cmd, 'w')
223
+ if (exit_status or $?).exitstatus != 0
224
+ $stderr.puts "APPLY FAILED."
225
+ end
207
226
  end
227
+ end
208
228
 
229
+ def dump_changes(lv, outfd, opts)
209
230
  outfd.puts PROTOCOL_VERSION
210
231
 
211
232
  start_time = Time.now
212
233
  xfer_count = 0
213
234
  xfer_size = 0
214
235
  total_size = 0
236
+ change_count = lv.changes.length
215
237
 
216
- originfile = "/dev/mapper/#{vg.gsub('-', '--')}-#{snap.origin.gsub('-', '--')}"
217
- File.open(originfile, 'r') do |origindev|
218
- snap.differences.each do |r|
238
+ File.open(lv.origin.path, 'r') do |origindev|
239
+ lv.changes.each do |r|
219
240
  xfer_count += 1
220
241
  chunk_size = r.last - r.first + 1
221
242
  xfer_size += chunk_size
222
243
 
223
244
  $stderr.puts "Sending chunk #{r.to_s}..." if opts[:verbose]
224
- $stderr.puts "Seeking to #{r.first} in #{originfile}" if opts[:verbose]
245
+ $stderr.puts "Seeking to #{r.first} in #{lv.origin.path}" if opts[:verbose]
225
246
 
226
247
  origindev.seek(r.first, IO::SEEK_SET)
227
248
 
228
- outfd.print [htonq(r.first), chunk_size].pack("QN")
229
- outfd.print origindev.read(chunk_size)
249
+ begin
250
+ outfd.print [htonq(r.first), chunk_size].pack("QN")
251
+ outfd.print origindev.read(chunk_size)
252
+ rescue Errno::EPIPE
253
+ $stderr.puts "Remote prematurely closed the connection"
254
+ yield if block_given?
255
+ return
256
+ end
230
257
 
231
258
  # Progress bar!
232
259
  if xfer_count % 100 == 50
233
260
  $stderr.printf "\e[2K\rSending chunk %i of %i, %.2fMB/s",
234
261
  xfer_count,
235
- snap.differences.length,
262
+ change_count,
236
263
  xfer_size / (Time.now - start_time) / 1048576
237
264
  $stderr.flush
238
265
  end
266
+ yield if block_given?
239
267
  end
240
268
 
241
269
  origindev.seek(0, IO::SEEK_END)
@@ -247,8 +275,6 @@ def run_client(opts)
247
275
 
248
276
  $stderr.printf "You transferred your changes %.2fx faster than a full dd!\n",
249
277
  total_size.to_f / xfer_size
250
- ensure
251
- outfd.close unless outfd.nil? or outfd == $stdout
252
278
  end
253
279
 
254
280
  # Take a device name in any number of different formats and return a [VG, LV] pair.
@@ -0,0 +1,83 @@
1
+ module LVM; end
2
+
3
+ # This class represents an LVM logical volume, in all its glory. You can
4
+ # perform various operations on it.
5
+ class LVM::LogicalVolume
6
+ # Create a new instance of LVM::LogicalVolume.
7
+ #
8
+ # New instances can be created in one of two ways:
9
+ #
10
+ # * Pass a single argument, containing any path which LVM can resolve to
11
+ # a logical volume. Typically, this will either be `/dev/<vg>/<lv>`
12
+ # or `/dev/mapper/<vg>-<lv>`, but we don't try to parse it ourselves,
13
+ # relying instead on `lvs` to do the heavy lifting.
14
+ #
15
+ # * Pass two arguments, which are the volume group name and logical
16
+ # volume name, respectively.
17
+ #
18
+ # This method will raise `RuntimeError` if the path specified can't be
19
+ # resolved to an LV, or if the specified VG name or LV name don't resolve
20
+ # to an active logical volume.
21
+ #
22
+ def initialize(path_or_vg_name, lv_name=nil)
23
+ if lv_name.nil?
24
+ path = path_or_vg_name
25
+ @vg_name, @lv_name = `lvs --noheadings -o vg_name,lv_name #{path} 2>/dev/null`.strip.split(/\s+/, 2)
26
+ if $?.exitstatus != 0
27
+ raise RuntimeError,
28
+ "Failed to interrogate LVM about '#{path}'. Perhaps you misspelt it?"
29
+ end
30
+ else
31
+ @vg_name = path_or_vg_name
32
+ @lv_name = lv_name
33
+ end
34
+
35
+ @vgcfg = LVM::VGConfig.new(@vg_name)
36
+ @lvcfg = @vgcfg.logical_volumes[@lv_name]
37
+
38
+ if @lvcfg.nil?
39
+ raise RuntimeError,
40
+ "Logical volume #{@lv_name} does not exist in volume group #{@vg_name}"
41
+ end
42
+ end
43
+
44
+ # Return a string containing a canonical path to the block device
45
+ # representing this LV.
46
+ def path
47
+ "/dev/mapper/#{@vg_name.gsub('-', '--')}-#{@lv_name.gsub('-', '--')}"
48
+ end
49
+
50
+ # Is this LV a snapshot?
51
+ def snapshot?
52
+ @lvcfg.snapshot?
53
+ end
54
+
55
+ # Return an LVM::LogicalVolume object which is the origin volume of
56
+ # this one (if this LV is a snapshot), or `nil` otherwise.
57
+ def origin
58
+ return nil unless snapshot?
59
+
60
+ if @lvcfg.origin
61
+ LVM::LogicalVolume.new(@vg_name, @lvcfg.origin)
62
+ else
63
+ origin_lv_name = @vgcfg.logical_volumes.values.find { |lv| lv.cow_store == @lv_name }.origin
64
+ LVM::LogicalVolume.new(@vg_name, origin_lv_name)
65
+ end
66
+ end
67
+
68
+ # Return an array of ranges, each of which represents an inclusive range
69
+ # of bytes which are different between this logical volume and its
70
+ # origin.
71
+ #
72
+ # If this LV is not a snapshot, this method returns an empty array.
73
+ #
74
+ def changes
75
+ return [] unless snapshot?
76
+
77
+ if @lvcfg.thin?
78
+ LVM::ThinSnapshot.new(@vg_name, @lv_name)
79
+ else
80
+ LVM::Snapshot.new(@vg_name, @lv_name)
81
+ end.differences
82
+ end
83
+ end
data/lib/lvm.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'lvm/helpers'
2
2
  require 'lvm/thin_snapshot'
3
3
  require 'lvm/snapshot'
4
+ require 'lvm/logical_volume'
4
5
  require 'lvm/lv_config'
5
6
  require 'lvm/vg_config'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lvmsync
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,10 +9,26 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-10-06 00:00:00.000000000 Z
12
+ date: 2014-10-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: git-version-bump
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.10'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.10'
30
+ - !ruby/object:Gem::Dependency
31
+ name: treetop
16
32
  requirement: !ruby/object:Gem::Requirement
17
33
  none: false
18
34
  requirements:
@@ -28,14 +44,14 @@ dependencies:
28
44
  - !ruby/object:Gem::Version
29
45
  version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
- name: treetop
47
+ name: bundler
32
48
  requirement: !ruby/object:Gem::Requirement
33
49
  none: false
34
50
  requirements:
35
51
  - - ! '>='
36
52
  - !ruby/object:Gem::Version
37
53
  version: '0'
38
- type: :runtime
54
+ type: :development
39
55
  prerelease: false
40
56
  version_requirements: !ruby/object:Gem::Requirement
41
57
  none: false
@@ -44,7 +60,7 @@ dependencies:
44
60
  - !ruby/object:Gem::Version
45
61
  version: '0'
46
62
  - !ruby/object:Gem::Dependency
47
- name: bundler
63
+ name: github-release
48
64
  requirement: !ruby/object:Gem::Requirement
49
65
  none: false
50
66
  requirements:
@@ -200,6 +216,7 @@ files:
200
216
  - bin/lvmsync
201
217
  - lib/lvm.rb
202
218
  - lib/lvm/lv_config.rb
219
+ - lib/lvm/logical_volume.rb
203
220
  - lib/lvm/thin_snapshot.rb
204
221
  - lib/lvm/snapshot.rb
205
222
  - lib/lvm/vg_config.rb
@@ -221,7 +238,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
221
238
  version: '0'
222
239
  segments:
223
240
  - 0
224
- hash: -3110588239684965704
241
+ hash: -3100396621721855174
225
242
  required_rubygems_version: !ruby/object:Gem::Requirement
226
243
  none: false
227
244
  requirements:
@@ -230,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
247
  version: '0'
231
248
  segments:
232
249
  - 0
233
- hash: -3110588239684965704
250
+ hash: -3100396621721855174
234
251
  requirements: []
235
252
  rubyforge_project:
236
253
  rubygems_version: 1.8.23