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