lvmsync 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
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