lvmsync 3.1.0 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/lvmsync +55 -29
- data/lib/lvm/logical_volume.rb +83 -0
- data/lib/lvm.rb +1 -0
- 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
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
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: #{
|
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
|
-
|
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
|
-
|
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
|
-
|
217
|
-
|
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 #{
|
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
|
-
|
229
|
-
|
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
|
-
|
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
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.
|
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-
|
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:
|
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: :
|
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:
|
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: -
|
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: -
|
250
|
+
hash: -3100396621721855174
|
234
251
|
requirements: []
|
235
252
|
rubyforge_project:
|
236
253
|
rubygems_version: 1.8.23
|