lvmsync 1.0.0 → 1.0.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/README.md CHANGED
@@ -114,11 +114,11 @@ commands should be run on `vmsrv1`:
114
114
 
115
115
  # Shutdown the VM -- the command you use will probably vary
116
116
  virsh shutdown somevm
117
-
117
+
118
118
  # Once it's shutdown and the block device isn't going to be written to
119
119
  # any more, then you can run lvmsync
120
120
  lvmsync /dev/vmsrv1/somevm-lvmsync vmsrv2:/dev/vmsrv2/somevm
121
-
121
+
122
122
  # You can now start up the VM on vmsrv2, after a fairly small period of
123
123
  # downtime. Once you're done, you can remove the snapshot and,
124
124
  # presumably, the LV itself, from `vmsrv1`
@@ -231,7 +231,7 @@ you'd do it like this:
231
231
 
232
232
  You can also do things like do an lvmsync *from* the destination -- this is
233
233
  useful if (for example) you can SSH from the destination to the source
234
- machine, but not the other way around (fkkn firewalls, how do they work?).
234
+ machine, but not the other way around (fkkn firewalls, how do they work?).
235
235
  You could do this by running something like the following on the destination
236
236
  machine:
237
237
 
@@ -248,7 +248,7 @@ order to work lvmsync.
248
248
  First, a little bit of background about how snapshot LVs work, before I
249
249
  describe how lvmsync makes use of them.
250
250
 
251
- An LVM snapshot "device" is actually not a block device in the usual sense.
251
+ An LVM snapshot "device" is actually not a block device in the usual sense.
252
252
  It isn't just a big area of disk space where you write things. Instead, it
253
253
  is a "meta" device, which points to both an "origin" LV, which is the LV
254
254
  from which the snapshot was made, and a "metadata" LV, which is where the
data/bin/lvmsync CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
 
3
3
  # Transfer a set of changes made to the origin of a snapshot LV to another
4
4
  # block device, possibly using SSH to send to a remote system.
@@ -33,7 +33,7 @@ def main()
33
33
  opts.separator " lvmsync [--snapback <file>] <snapshot device> [--stdout | [<desthost>:]<destdevice>]"
34
34
  opts.separator " lvmsync [--snapback <file>] --apply <changes file> <destdevice>"
35
35
  opts.separator ""
36
-
36
+
37
37
  opts.on("--server", "Run in server mode (deprecated; use '--apply -' instead)") do |v|
38
38
  options[:server] = true
39
39
  end
@@ -51,7 +51,7 @@ def main()
51
51
  options[:stdout] = true
52
52
  end
53
53
  end.parse!
54
-
54
+
55
55
  if options[:apply]
56
56
  if ARGV[0].nil?
57
57
  $stderr.puts "No destination device specified."
@@ -98,7 +98,7 @@ def run_apply(opts)
98
98
  snapfile = opts[:snapback] ? File.open(opts[:snapback], 'w') : nil
99
99
  infile = opts[:apply] == '-' ? $stdin : File.open(opts[:apply], 'r')
100
100
  destdev = opts[:device]
101
-
101
+
102
102
  process_dumpdata(infile, destdev, snapfile)
103
103
  ensure
104
104
  snapfile.close unless snapfile.nil?
@@ -116,7 +116,7 @@ def process_dumpdata(instream, destdev, snapback = nil)
116
116
  while header = instream.read(12)
117
117
  offset, chunksize = header.unpack("QN")
118
118
  offset = ntohq(offset)
119
-
119
+
120
120
  begin
121
121
  dest.seek offset * chunksize
122
122
  rescue Errno::EINVAL
@@ -126,13 +126,13 @@ def process_dumpdata(instream, destdev, snapback = nil)
126
126
  # seek past the end of the device. Yes, this may lose data, but
127
127
  # if you didn't notice that your dd shit itself, it's unlikely
128
128
  # you're going to notice now.
129
-
129
+
130
130
  # Skip the chunk of data
131
131
  instream.read(chunksize)
132
132
  # Go to the next chunk
133
133
  next
134
134
  end
135
-
135
+
136
136
  if snapback
137
137
  snapback.write(header)
138
138
  snapback.write dest.read(chunksize)
@@ -148,16 +148,16 @@ def run_client(opts)
148
148
  desthost = opts[:desthost]
149
149
  destdev = opts[:destdev]
150
150
  outfd = nil
151
-
151
+
152
152
  vg, lv = parse_snapshot_name(snapshot)
153
153
 
154
154
  vgconfig = LVM::VGConfig.new(vg)
155
-
155
+
156
156
  if vgconfig.logical_volumes[lv].nil?
157
157
  $stderr.puts "#{snapshot}: Could not find logical volume"
158
158
  exit 1
159
159
  end
160
-
160
+
161
161
  snap = if vgconfig.logical_volumes[lv].snapshot?
162
162
  if vgconfig.logical_volumes[lv].thin?
163
163
  LVM::ThinSnapshot.new(vg, lv)
@@ -170,11 +170,11 @@ def run_client(opts)
170
170
  end
171
171
 
172
172
  $stderr.puts "Origin device: #{vg}/#{snap.origin}" if opts[:verbose]
173
-
173
+
174
174
  # Since, in principle, we're not supposed to be reading from snapshot
175
175
  # devices directly, the kernel makes no attempt to make the device's read
176
176
  # cache stay in sync with the actual state of the device. As a result,
177
- # we have to manually drop all caches before the data looks consistent.
177
+ # we have to manually drop all caches before the data looks consistent.
178
178
  # PERFORMANCE WIN!
179
179
  File.open("/proc/sys/vm/drop_caches", 'w') { |fd| fd.print "3" }
180
180
 
@@ -188,12 +188,12 @@ def run_client(opts)
188
188
  else
189
189
  "lvmsync --apply - #{snapback} #{destdev}"
190
190
  end
191
-
191
+
192
192
  outfd = IO.popen(server_cmd, 'w')
193
193
  end
194
-
194
+
195
195
  outfd.puts PROTOCOL_VERSION
196
-
196
+
197
197
  start_time = Time.now
198
198
  xfer_count = 0
199
199
  xfer_size = 0
@@ -205,10 +205,10 @@ def run_client(opts)
205
205
  xfer_count += 1
206
206
  chunk_size = r.last - r.first + 1
207
207
  xfer_size += chunk_size
208
-
208
+
209
209
  $stderr.puts "Sending chunk #{r.to_s}..." if opts[:verbose]
210
210
  $stderr.puts "Seeking to #{r.first} in #{originfile}" if opts[:verbose]
211
-
211
+
212
212
  origindev.seek(r.first, IO::SEEK_SET)
213
213
 
214
214
  outfd.print [htonq(r.first), chunk_size].pack("QN")
@@ -230,7 +230,7 @@ def run_client(opts)
230
230
 
231
231
  $stderr.printf "\rTransferred %i bytes in %.2f seconds\n",
232
232
  xfer_size, Time.now - start_time
233
-
233
+
234
234
  $stderr.printf "You transferred your changes %.2fx faster than a full dd!\n",
235
235
  total_size.to_f / xfer_size
236
236
  ensure
data/lib/lvm/helpers.rb CHANGED
@@ -13,6 +13,6 @@ module LVM::Helpers
13
13
  end
14
14
 
15
15
  def ntohq val
16
- htonq val
16
+ htonq val
17
17
  end
18
18
  end
data/lib/lvm/lv_config.rb CHANGED
@@ -2,17 +2,17 @@ module LVM; end
2
2
 
3
3
  class LVM::LVConfig
4
4
  attr_reader :name
5
-
5
+
6
6
  def initialize(tree, name, vgcfg)
7
7
  @root = tree
8
8
  @name = name
9
9
  @vgcfg = vgcfg
10
10
  end
11
-
11
+
12
12
  def thin?
13
13
  @root.groups['segment1'].variable_value('type') == 'thin'
14
14
  end
15
-
15
+
16
16
  def snapshot?
17
17
  thin? ? !origin.nil? : !@vgcfg.logical_volumes.values.find { |lv| lv.cow_store == name }.nil?
18
18
  end
@@ -32,7 +32,7 @@ class LVM::LVConfig
32
32
  def cow_store
33
33
  @root.groups['segment1'].variable_value('cow_store')
34
34
  end
35
-
35
+
36
36
  def chunk_size
37
37
  @root.groups['segment1'].variable_value('chunk_size') * 512
38
38
  end
data/lib/lvm/snapshot.rb CHANGED
@@ -5,7 +5,7 @@ module LVM; end
5
5
 
6
6
  class LVM::Snapshot
7
7
  include LVM::Helpers
8
-
8
+
9
9
  def initialize(vg, lv)
10
10
  @vg = vg
11
11
  @lv = lv
@@ -18,16 +18,16 @@ class LVM::Snapshot
18
18
  # For a regular, old-skool snapshot, getting the differences is
19
19
  # pretty trivial -- just read through the snapshot metadata, and
20
20
  # the list of changed blocks is right there.
21
- #
21
+ #
22
22
  diff_block_list = []
23
23
 
24
24
  File.open(metadata_device, 'r') do |metafd|
25
25
  in_progress = true
26
-
26
+
27
27
  # The first chunk of the metadata LV is the header, which we
28
28
  # don't care for at all
29
29
  metafd.seek chunk_size, IO::SEEK_SET
30
-
30
+
31
31
  while in_progress
32
32
  # The snapshot on-disk format is a stream of <blocklist>, <blockdata>
33
33
  # sets; within each <blocklist>, it's network-byte-order 64-bit block
@@ -39,7 +39,7 @@ class LVM::Snapshot
39
39
  origin_offset, snap_offset = metafd.read(16).unpack("QQ")
40
40
  origin_offset = ntohq(origin_offset)
41
41
  snap_offset = ntohq(snap_offset)
42
-
42
+
43
43
  # A snapshot offset of 0 would point back to the metadata
44
44
  # device header, so that's clearly invalid -- hence it's the
45
45
  # "no more blocks" indicator.
@@ -47,21 +47,21 @@ class LVM::Snapshot
47
47
  in_progress = false
48
48
  break
49
49
  end
50
-
50
+
51
51
  diff_block_list << origin_offset
52
52
  end
53
-
53
+
54
54
  # We've read through a set of origin => data mappings; now we need
55
55
  # to take a giant leap over the data blocks that follow it.
56
56
  metafd.seek chunk_size * chunk_size / 16, IO::SEEK_CUR
57
57
  end
58
58
  end
59
-
59
+
60
60
  # Block-to-byte-range is pretty trivial, and we're done!
61
61
  diff_block_list.map do |b|
62
62
  ((b*chunk_size)..(((b+1)*chunk_size)-1))
63
63
  end
64
-
64
+
65
65
  # There is one optimisation we could make here that we haven't --
66
66
  # coalescing adjacent byte ranges into single larger ranges. I haven't
67
67
  # done it for two reasons: Firstly, I don't have any idea how much of a
@@ -69,44 +69,44 @@ class LVM::Snapshot
69
69
  # to do it elegantly. So I punted.
70
70
  end
71
71
  end
72
-
72
+
73
73
  def origin
74
74
  # Man old-skool snapshots are weird
75
75
  vgcfg.logical_volumes.values.find { |lv| lv.cow_store == @lv }.origin
76
76
  end
77
-
77
+
78
78
  private
79
79
  def vgcfg
80
80
  @vgcfg ||= LVM::VGConfig.new(@vg)
81
81
  end
82
-
82
+
83
83
  def chunk_size
84
84
  @chunk_size ||= metadata_header[:chunk_size]
85
85
  end
86
-
86
+
87
87
  def metadata_header
88
88
  @metadata_header ||= begin
89
89
  magic, valid, version, chunk_size = File.read(metadata_device, 16).unpack("VVVV")
90
-
90
+
91
91
  unless magic == 0x70416e53
92
92
  raise RuntimeError,
93
93
  "#{@vg}/#{@lv}: Invalid snapshot magic number"
94
94
  end
95
-
95
+
96
96
  unless valid == 1
97
97
  raise RuntimeError,
98
98
  "#{@vg}/#{@lv}: Snapshot is marked as invalid"
99
99
  end
100
-
100
+
101
101
  unless version == 1
102
102
  raise RuntimeError,
103
103
  "#{@vg}/#{@lv}: Incompatible snapshot metadata version"
104
104
  end
105
-
105
+
106
106
  { :chunk_size => chunk_size * 512 }
107
107
  end
108
108
  end
109
-
109
+
110
110
  def metadata_device
111
111
  "/dev/mapper/#{@vg}-#{@lv}-cow"
112
112
  end
@@ -69,19 +69,19 @@ class LVM::ThinSnapshot
69
69
  diff_maps = ((flat_origin_blocklist - flat_snapshot_blocklist) +
70
70
  (flat_snapshot_blocklist - flat_origin_blocklist)
71
71
  ).uniq
72
-
72
+
73
73
  # At this point, we're off to a good start -- we've got the mappings
74
74
  # that are different. But we're not actually interested in the
75
75
  # mappings themselves -- all we want is "the list of LV blocks which
76
76
  # are different" (we'll translate LV blocks into byte ranges next).
77
77
  #
78
78
  changed_blocks = diff_maps.map { |m| m[0] }.uniq
79
-
79
+
80
80
  # Block-to-byte-range is pretty trivial, and we're done!
81
81
  changed_blocks.map do |b|
82
82
  ((b*chunk_size)..(((b+1)*chunk_size)-1))
83
83
  end
84
-
84
+
85
85
  # There is one optimisation we could make here that we haven't --
86
86
  # coalescing adjacent byte ranges into single larger ranges. I haven't
87
87
  # done it for two reasons: Firstly, I don't have any idea how much of a
@@ -89,44 +89,44 @@ class LVM::ThinSnapshot
89
89
  # to do it elegantly. So I punted.
90
90
  end
91
91
  end
92
-
92
+
93
93
  def origin
94
94
  @origin ||= vgcfg.logical_volumes[@lv].origin
95
95
  end
96
-
96
+
97
97
  private
98
98
  def vgcfg
99
99
  @vgcfg ||= LVM::VGConfig.new(@vg)
100
100
  end
101
-
101
+
102
102
  def flat_origin_blocklist
103
103
  @flat_origin_blocklist ||= flatten_blocklist(origin_blocklist)
104
104
  end
105
-
105
+
106
106
  def flat_snapshot_blocklist
107
107
  @flat_snapshot_blocklist ||= flatten_blocklist(snapshot_blocklist)
108
108
  end
109
-
109
+
110
110
  def origin_blocklist
111
111
  @origin_blocklist ||= vg_block_dump[@vgcfg.logical_volumes[origin].device_id]
112
112
  end
113
-
113
+
114
114
  def snapshot_blocklist
115
115
  @snapshot_blocklist ||= vg_block_dump[@vgcfg.logical_volumes[@lv].device_id]
116
116
  end
117
-
117
+
118
118
  def thin_pool_name
119
119
  @thin_pool_name ||= vgcfg.logical_volumes[@lv].thin_pool
120
120
  end
121
-
121
+
122
122
  def thin_pool
123
123
  @thin_pool ||= vgcfg.logical_volumes[thin_pool_name]
124
124
  end
125
-
125
+
126
126
  def chunk_size
127
127
  @chunk_size ||= thin_pool.chunk_size
128
128
  end
129
-
129
+
130
130
  # Take a hash of <block-or-range> => <block-or-range> elements and turn
131
131
  # it into an array of [block, block] pairs -- any <range> => <range>
132
132
  # elements get expanded out into their constituent <block> => <block>
@@ -138,9 +138,9 @@ class LVM::ThinSnapshot
138
138
  if elem[0].is_a? Range
139
139
  lv_blocks = elem[0].to_a
140
140
  data_blocks = elem[1].to_a
141
-
141
+
142
142
  # This will now produce an array of two-element arrays, which
143
- # will itself be inside the top-level array that we're mapping.
143
+ # will itself be inside the top-level array that we're mapping.
144
144
  # A flatten(1) at the end will take care of that problem,
145
145
  # though.
146
146
  lv_blocks.inject([]) { |a, v| a << [v, data_blocks[a.length]] }
@@ -155,14 +155,14 @@ class LVM::ThinSnapshot
155
155
  end
156
156
  end.flatten(1)
157
157
  end
158
-
158
+
159
159
  def vg_block_dump
160
160
  @vg_block_dump ||= begin
161
161
  doc = REXML::Document.new(`thin_dump /dev/mapper/#{@vg.gsub('-', '--')}-#{thin_pool_name.gsub('-','--')}_tmeta`)
162
-
162
+
163
163
  doc.elements['superblock'].inject({}) do |h, dev|
164
164
  next h unless dev.node_type == :element
165
-
165
+
166
166
  maps = dev.elements[''].inject({}) do |h2, r|
167
167
  next h2 unless r.node_type == :element
168
168
 
data/lib/lvm/vg_config.rb CHANGED
@@ -23,50 +23,55 @@ class LVM::VGConfig
23
23
  "Cannot parse vgcfgbackup output: #{@parser.failure_reason}"
24
24
  end
25
25
  end
26
-
26
+
27
27
  def version
28
28
  @version ||= @root.variable_value('version')
29
29
  end
30
-
30
+
31
31
  def description
32
32
  @description ||= @root.variable_value('description')
33
33
  end
34
-
34
+
35
35
  def uuid
36
36
  @uuid ||= volume_group.variable_value('id')
37
37
  end
38
-
38
+
39
39
  def volume_group
40
40
  @volume_group ||= @root.groups[@vg_name]
41
41
  end
42
-
42
+
43
43
  def physical_volumes
44
44
  @physical_volumes ||= volume_group.groups['physical_volumes'].groups.to_a.inject({}) { |h,v| h[v[0]] = LVM::PVConfig.new(v[1]); h }
45
45
  end
46
-
46
+
47
47
  def logical_volumes
48
48
  @logical_volumes ||= volume_group.groups['logical_volumes'].groups.to_a.inject({}) { |h,v| h[v[0]] = LVM::LVConfig.new(v[1], v[0], self); h }
49
49
  end
50
-
50
+
51
51
  private
52
52
  def vgcfgbackup_output
53
53
  @vgcfgbackup_output ||= begin
54
+ out = nil
55
+
54
56
  Tempfile.open('vg_config') do |tmpf|
55
57
  cmd = "#{@vgcfgbackup_cmd} -f #{tmpf.path} #{@vg_name}"
56
- Open3.popen3(cmd) do |stdin_fd, stdout_fd, stderr_fd, thr|
58
+ stdout = nil
59
+ stderr = nil
60
+ Open3.popen3(cmd) do |stdin_fd, stdout_fd, stderr_fd|
57
61
  stdin_fd.close
58
62
  stdout = stdout_fd.read
59
63
  stderr = stderr_fd.read
60
- exit_status = thr.value
61
-
62
- if exit_status != 0
63
- raise RuntimeError,
64
- "Failed to run vgcfgbackup: #{stdout}\n#{stderr}"
65
- end
66
64
  end
67
-
68
- File.read(tmpf.path)
65
+
66
+ if $?.exitstatus != 0
67
+ raise RuntimeError,
68
+ "Failed to run vgcfgbackup: #{stdout}\n#{stderr}"
69
+ end
70
+
71
+ out = File.read(tmpf.path)
69
72
  end
73
+
74
+ out
70
75
  end
71
76
  end
72
77
  end
data/lib/vgcfgbackup.rb CHANGED
@@ -1,30 +1,30 @@
1
1
  module VgCfgBackup
2
2
  class Node < Treetop::Runtime::SyntaxNode
3
3
  end
4
-
4
+
5
5
  class Group < Node
6
6
  def name
7
7
  self.elements[0].text_value
8
8
  end
9
-
9
+
10
10
  def variables
11
11
  self.elements[3].elements.select { |e| e.is_a? Variable }
12
12
  end
13
-
13
+
14
14
  def variable_value(name)
15
15
  self.variables.find { |v| v.name == name }.value rescue nil
16
16
  end
17
-
17
+
18
18
  def groups
19
19
  self.elements[3].elements.select { |e| e.is_a? Group }.inject({}) { |h,v| h[v.name] = v; h }
20
20
  end
21
21
  end
22
-
22
+
23
23
  class Config < Group
24
24
  def name
25
25
  nil
26
26
  end
27
-
27
+
28
28
  def variables
29
29
  self.elements.select { |e| e.is_a? Variable }
30
30
  end
@@ -33,32 +33,32 @@ module VgCfgBackup
33
33
  self.elements.select { |e| e.is_a? Group }.inject({}) { |h,v| h[v.name] = v; h }
34
34
  end
35
35
  end
36
-
36
+
37
37
  class Variable < Node
38
38
  def name
39
39
  self.elements[0].text_value
40
40
  end
41
-
41
+
42
42
  def value
43
43
  self.elements[2].value
44
44
  end
45
45
  end
46
-
46
+
47
47
  class VariableName < Node
48
48
  end
49
-
49
+
50
50
  class Integer < Node
51
51
  def value
52
52
  self.text_value.to_i
53
53
  end
54
54
  end
55
-
55
+
56
56
  class String < Node
57
57
  def value
58
58
  self.elements[1].text_value
59
59
  end
60
60
  end
61
-
61
+
62
62
  class List < Node
63
63
  def value
64
64
  self.elements.find { |e| e.is_a?(String) }.map { |e| e.value }
@@ -6,31 +6,31 @@ grammar VgCfgBackup
6
6
  rule variable
7
7
  variable_name " = " (integer / string / list) <Variable>
8
8
  end
9
-
9
+
10
10
  rule variable_name
11
11
  [a-z0-9_]+ <VariableName>
12
12
  end
13
-
13
+
14
14
  rule group
15
15
  variable_name space "{" (variable / group / space / comment)+ "}" <Group>
16
16
  end
17
-
17
+
18
18
  rule integer
19
19
  [1-9] [0-9]* <Integer> / "0" <Integer>
20
20
  end
21
-
21
+
22
22
  rule string
23
23
  '"' [^"]* '"' <String>
24
24
  end
25
-
25
+
26
26
  rule list
27
27
  "[" space? ((string / integer) ", " / (string / integer))* space? "]" <List>
28
28
  end
29
-
29
+
30
30
  rule space
31
31
  [\s]+
32
32
  end
33
-
33
+
34
34
  rule comment
35
35
  "#" [^\n]*
36
36
  end
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: 1.0.0
4
+ version: 1.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-22 00:00:00.000000000 Z
12
+ date: 2014-05-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: git-version-bump
@@ -221,7 +221,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
221
221
  version: '0'
222
222
  segments:
223
223
  - 0
224
- hash: -3927931017616602686
224
+ hash: -3361242777286472333
225
225
  required_rubygems_version: !ruby/object:Gem::Requirement
226
226
  none: false
227
227
  requirements:
@@ -230,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
230
  version: '0'
231
231
  segments:
232
232
  - 0
233
- hash: -3927931017616602686
233
+ hash: -3361242777286472333
234
234
  requirements: []
235
235
  rubyforge_project:
236
236
  rubygems_version: 1.8.23