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.
- 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
|