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 +4 -4
- data/bin/lvmsync +18 -18
- data/lib/lvm/helpers.rb +1 -1
- data/lib/lvm/lv_config.rb +4 -4
- data/lib/lvm/snapshot.rb +18 -18
- data/lib/lvm/thin_snapshot.rb +18 -18
- data/lib/lvm/vg_config.rb +21 -16
- data/lib/vgcfgbackup.rb +12 -12
- data/lib/vgcfgbackup.treetop +7 -7
- metadata +4 -4
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
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
|
data/lib/lvm/thin_snapshot.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 }
|
data/lib/vgcfgbackup.treetop
CHANGED
@@ -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.
|
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-
|
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: -
|
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: -
|
233
|
+
hash: -3361242777286472333
|
234
234
|
requirements: []
|
235
235
|
rubyforge_project:
|
236
236
|
rubygems_version: 1.8.23
|