higgs 0.1.0
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/ChangeLog +208 -0
- data/LICENSE +26 -0
- data/README +2 -0
- data/Rakefile +75 -0
- data/bin/higgs_backup +67 -0
- data/bin/higgs_dump_index +43 -0
- data/bin/higgs_dump_jlog +42 -0
- data/bin/higgs_verify +37 -0
- data/lib/cgi/session/higgs.rb +72 -0
- data/lib/higgs/block.rb +192 -0
- data/lib/higgs/cache.rb +117 -0
- data/lib/higgs/dbm.rb +55 -0
- data/lib/higgs/exceptions.rb +31 -0
- data/lib/higgs/flock.rb +77 -0
- data/lib/higgs/index.rb +164 -0
- data/lib/higgs/jlog.rb +159 -0
- data/lib/higgs/lock.rb +189 -0
- data/lib/higgs/storage.rb +1086 -0
- data/lib/higgs/store.rb +228 -0
- data/lib/higgs/tar.rb +390 -0
- data/lib/higgs/thread.rb +370 -0
- data/lib/higgs/tman.rb +513 -0
- data/lib/higgs/utils/bman.rb +285 -0
- data/lib/higgs/utils.rb +22 -0
- data/lib/higgs/version.rb +21 -0
- data/lib/higgs.rb +59 -0
- data/misc/cache_bench/cache_bench.rb +43 -0
- data/misc/dbm_bench/.strc +8 -0
- data/misc/dbm_bench/Rakefile +78 -0
- data/misc/dbm_bench/dbm_multi_thread.rb +199 -0
- data/misc/dbm_bench/dbm_rnd_delete.rb +43 -0
- data/misc/dbm_bench/dbm_rnd_read.rb +44 -0
- data/misc/dbm_bench/dbm_rnd_update.rb +44 -0
- data/misc/dbm_bench/dbm_seq_read.rb +45 -0
- data/misc/dbm_bench/dbm_seq_write.rb +44 -0
- data/misc/dbm_bench/st_verify.rb +28 -0
- data/misc/io_bench/cksum_bench.rb +48 -0
- data/misc/io_bench/jlog_bench.rb +71 -0
- data/misc/io_bench/write_bench.rb +128 -0
- data/misc/thread_bench/lock_bench.rb +132 -0
- data/mkrdoc.rb +8 -0
- data/rdoc.yml +13 -0
- data/sample/count.rb +60 -0
- data/sample/dbmtest.rb +38 -0
- data/test/Rakefile +45 -0
- data/test/run.rb +32 -0
- data/test/test_block.rb +163 -0
- data/test/test_cache.rb +214 -0
- data/test/test_cgi_session.rb +142 -0
- data/test/test_flock.rb +162 -0
- data/test/test_index.rb +258 -0
- data/test/test_jlog.rb +180 -0
- data/test/test_lock.rb +320 -0
- data/test/test_online_backup.rb +169 -0
- data/test/test_storage.rb +439 -0
- data/test/test_storage_conf.rb +202 -0
- data/test/test_storage_init_opts.rb +89 -0
- data/test/test_store.rb +211 -0
- data/test/test_tar.rb +432 -0
- data/test/test_thread.rb +541 -0
- data/test/test_tman.rb +875 -0
- data/test/test_tman_init_opts.rb +56 -0
- data/test/test_utils_bman.rb +234 -0
- metadata +115 -0
data/test/test_jlog.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'higgs/block'
|
5
|
+
require 'higgs/jlog'
|
6
|
+
require 'test/unit'
|
7
|
+
|
8
|
+
module Higgs::Test
|
9
|
+
class JournalLoggerTest < Test::Unit::TestCase
|
10
|
+
include Higgs
|
11
|
+
|
12
|
+
# for ident(1)
|
13
|
+
CVS_ID = '$Id: test_jlog.rb 559 2007-09-25 15:20:20Z toki $'
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@path = 't.jlog'
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
FileUtils.rm_f(@path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_jlog
|
24
|
+
log = JournalLogger.open(@path)
|
25
|
+
assert_equal(false, log.sync?)
|
26
|
+
assert_equal(0, log.size)
|
27
|
+
log.close
|
28
|
+
|
29
|
+
JournalLogger.each_log(@path) do |log|
|
30
|
+
assert_fail('not to reach')
|
31
|
+
end
|
32
|
+
|
33
|
+
log = JournalLogger.open(@path, false)
|
34
|
+
assert_equal(false, log.sync?)
|
35
|
+
log.write('foo')
|
36
|
+
assert_equal(1024, log.size)
|
37
|
+
log.close
|
38
|
+
|
39
|
+
count = 0
|
40
|
+
expected_values = [ 'foo' ]
|
41
|
+
JournalLogger.each_log(@path) do |log|
|
42
|
+
nth = "loop: #{count}"
|
43
|
+
assert(! expected_values.empty?, nth)
|
44
|
+
assert_equal(expected_values.shift, log, nth)
|
45
|
+
count += 1
|
46
|
+
end
|
47
|
+
assert(expected_values.empty?)
|
48
|
+
|
49
|
+
log = JournalLogger.open(@path, true)
|
50
|
+
assert_equal(true, log.sync?)
|
51
|
+
log.write(:bar)
|
52
|
+
assert_equal(2048, log.size)
|
53
|
+
log.close
|
54
|
+
|
55
|
+
count = 0
|
56
|
+
expected_values = [ 'foo', :bar ]
|
57
|
+
JournalLogger.each_log(@path) do |log|
|
58
|
+
nth = "loop: #{count}"
|
59
|
+
assert(! expected_values.empty?, nth)
|
60
|
+
assert_equal(expected_values.shift, log, nth)
|
61
|
+
count += 1
|
62
|
+
end
|
63
|
+
assert(expected_values.empty?)
|
64
|
+
|
65
|
+
log = JournalLogger.open(@path, false)
|
66
|
+
assert_equal(false, log.sync?)
|
67
|
+
log.write(777)
|
68
|
+
assert_equal(3072, log.size)
|
69
|
+
log.close
|
70
|
+
|
71
|
+
count = 0
|
72
|
+
expected_values = [ 'foo', :bar, 777 ]
|
73
|
+
JournalLogger.each_log(@path) do |log|
|
74
|
+
nth = "loop: #{count}"
|
75
|
+
assert(! expected_values.empty?, nth)
|
76
|
+
assert_equal(expected_values.shift, log, nth)
|
77
|
+
count += 1
|
78
|
+
end
|
79
|
+
assert(expected_values.empty?)
|
80
|
+
|
81
|
+
log = JournalLogger.open(@path, true)
|
82
|
+
assert_equal(true, log.sync?)
|
83
|
+
log.write('X' * Block::BLOCK_SIZE)
|
84
|
+
log.write('Y' * (Block::BLOCK_SIZE + 1))
|
85
|
+
log.close
|
86
|
+
|
87
|
+
count = 0
|
88
|
+
expected_values = [
|
89
|
+
'foo', :bar, 777,
|
90
|
+
'X' * Block::BLOCK_SIZE,
|
91
|
+
'Y' * (Block::BLOCK_SIZE + 1)
|
92
|
+
]
|
93
|
+
JournalLogger.each_log(@path) do |log|
|
94
|
+
nth = "loop: #{count}"
|
95
|
+
assert(! expected_values.empty?, nth)
|
96
|
+
assert_equal(expected_values.shift, log, nth)
|
97
|
+
count += 1
|
98
|
+
end
|
99
|
+
assert(expected_values.empty?)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_eof_mark
|
103
|
+
File.open(@path, 'w') {|w|
|
104
|
+
w.binmode
|
105
|
+
w << 'x' * Block::BLOCK_SIZE
|
106
|
+
JournalLogger.eof_mark(w)
|
107
|
+
}
|
108
|
+
assert_equal(true, (JournalLogger.has_eof_mark? @path))
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_eof_mark_no_file
|
112
|
+
assert_equal(false, (File.exist? @path))
|
113
|
+
assert_equal(true, (JournalLogger.has_eof_mark? @path))
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_eof_mark_empty_file
|
117
|
+
FileUtils.touch(@path)
|
118
|
+
assert_equal(false, (JournalLogger.has_eof_mark? @path))
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_eof_mark_broken_file
|
122
|
+
File.open(@path, 'w') {|w|
|
123
|
+
w.binmode
|
124
|
+
w << 'x' * Block::BLOCK_SIZE
|
125
|
+
JournalLogger.eof_mark(w)
|
126
|
+
}
|
127
|
+
File.truncate(@path, File.stat(@path).size - 1)
|
128
|
+
assert_equal(false, (JournalLogger.has_eof_mark? @path))
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_eof_mark_no_eof_mark
|
132
|
+
File.open(@path, 'w') {|w|
|
133
|
+
w.binmode
|
134
|
+
jlog = JournalLogger.new(w)
|
135
|
+
jlog.write(:no_EOF)
|
136
|
+
jlog.close(false)
|
137
|
+
}
|
138
|
+
assert_equal(false, (JournalLogger.has_eof_mark? @path))
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_open_error
|
142
|
+
log = JournalLogger.open(@path)
|
143
|
+
log.write(:foo)
|
144
|
+
log.close(false)
|
145
|
+
|
146
|
+
assert_raise(RuntimeError) {
|
147
|
+
JournalLogger.open(@path)
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_scan_log_error
|
152
|
+
log = JournalLogger.open(@path)
|
153
|
+
log.write(:foo)
|
154
|
+
log.write(:bar)
|
155
|
+
log.write(:baz)
|
156
|
+
log.close(false)
|
157
|
+
File.truncate(@path, File.stat(@path).size - 1)
|
158
|
+
|
159
|
+
File.open(@path) {|r|
|
160
|
+
r.binmode
|
161
|
+
count = 0
|
162
|
+
expected_values = [ :foo, :bar ]
|
163
|
+
assert_raise(Block::BrokenError) {
|
164
|
+
JournalLogger.scan_log(r) do |log|
|
165
|
+
nth = "loop: #{count}"
|
166
|
+
assert_equal(expected_values.shift, log, nth)
|
167
|
+
count += 1
|
168
|
+
end
|
169
|
+
}
|
170
|
+
assert(expected_values.empty?)
|
171
|
+
assert_equal(2048, r.tell)
|
172
|
+
}
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Local Variables:
|
178
|
+
# mode: Ruby
|
179
|
+
# indent-tabs-mode: nil
|
180
|
+
# End:
|
data/test/test_lock.rb
ADDED
@@ -0,0 +1,320 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'higgs/lock'
|
4
|
+
require 'higgs/thread'
|
5
|
+
require 'test/unit'
|
6
|
+
require 'thwait'
|
7
|
+
|
8
|
+
module Higgs::Test
|
9
|
+
module LockManagerTest
|
10
|
+
include Higgs
|
11
|
+
|
12
|
+
# for ident(1)
|
13
|
+
CVS_ID = '$Id: test_lock.rb 559 2007-09-25 15:20:20Z toki $'
|
14
|
+
|
15
|
+
WORK_COUNT = 100
|
16
|
+
THREAD_COUNT = 10
|
17
|
+
|
18
|
+
def test_read_transaction_single_thread
|
19
|
+
v = "foo"
|
20
|
+
WORK_COUNT.times do
|
21
|
+
@lock_manager.transaction(true) {|lock_handler|
|
22
|
+
lock_handler.lock(:foo)
|
23
|
+
assert_equal("foo", v)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_write_transaction_single_thread
|
29
|
+
count = 0
|
30
|
+
WORK_COUNT.times do
|
31
|
+
@lock_manager.transaction{|lock_handler|
|
32
|
+
lock_handler.lock(:foo)
|
33
|
+
count += 1
|
34
|
+
}
|
35
|
+
end
|
36
|
+
assert_equal(WORK_COUNT, count)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_read_transaction_multithread
|
40
|
+
v = "foo"
|
41
|
+
th_grp = ThreadGroup.new
|
42
|
+
barrier = Barrier.new(THREAD_COUNT + 1)
|
43
|
+
|
44
|
+
THREAD_COUNT.times{|i|
|
45
|
+
th_grp.add Thread.new{
|
46
|
+
@lock_manager.transaction(true) {|lock_handler|
|
47
|
+
lock_handler.lock(:foo)
|
48
|
+
barrier.wait
|
49
|
+
WORK_COUNT.times do |j|
|
50
|
+
assert_equal("foo", v, "read transaction: #{i}.#{j}")
|
51
|
+
end
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
barrier.wait
|
57
|
+
for t in th_grp.list
|
58
|
+
t.join
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_write_transaction_multithread
|
63
|
+
count = 0
|
64
|
+
th_grp = ThreadGroup.new
|
65
|
+
barrier = Barrier.new(THREAD_COUNT + 1)
|
66
|
+
|
67
|
+
THREAD_COUNT.times do
|
68
|
+
th_grp.add Thread.new{
|
69
|
+
barrier.wait
|
70
|
+
WORK_COUNT.times do
|
71
|
+
@lock_manager.transaction{|lock_handler|
|
72
|
+
lock_handler.lock(:foo)
|
73
|
+
count += 1
|
74
|
+
}
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
barrier.wait
|
80
|
+
for t in th_grp.list
|
81
|
+
t.join
|
82
|
+
end
|
83
|
+
assert_equal(THREAD_COUNT * WORK_COUNT, count)
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_read_write_transaction_multithread
|
87
|
+
count = 0
|
88
|
+
th_grp = ThreadGroup.new
|
89
|
+
barrier = Barrier.new(THREAD_COUNT * 2 + 1)
|
90
|
+
|
91
|
+
THREAD_COUNT.times{|i|
|
92
|
+
th_grp.add Thread.new{
|
93
|
+
@lock_manager.transaction(true) {|lock_handler|
|
94
|
+
lock_handler.lock(:foo)
|
95
|
+
barrier.wait
|
96
|
+
WORK_COUNT.times do |j|
|
97
|
+
assert_equal(0, count, "read transaction: #{i}.#{j}")
|
98
|
+
end
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
THREAD_COUNT.times do
|
104
|
+
th_grp.add Thread.new{
|
105
|
+
barrier.wait
|
106
|
+
WORK_COUNT.times do
|
107
|
+
@lock_manager.transaction{|lock_handler|
|
108
|
+
lock_handler.lock(:foo)
|
109
|
+
count += 1
|
110
|
+
}
|
111
|
+
end
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
barrier.wait
|
116
|
+
for t in th_grp.list
|
117
|
+
t.join
|
118
|
+
end
|
119
|
+
assert_equal(THREAD_COUNT * WORK_COUNT, count)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class GiantLockManagerTest < Test::Unit::TestCase
|
124
|
+
include Higgs
|
125
|
+
include LockManagerTest
|
126
|
+
|
127
|
+
# for ident(1)
|
128
|
+
CVS_ID = '$Id: test_lock.rb 559 2007-09-25 15:20:20Z toki $'
|
129
|
+
|
130
|
+
def setup
|
131
|
+
@lock_manager = GiantLockManager.new
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_write_read_transaction_multithread
|
135
|
+
count = 0
|
136
|
+
th_grp = ThreadGroup.new
|
137
|
+
barrier = Barrier.new(THREAD_COUNT + 2)
|
138
|
+
|
139
|
+
th_grp.add Thread.new{
|
140
|
+
@lock_manager.transaction{|lock_handler|
|
141
|
+
lock_handler.lock(:foo)
|
142
|
+
barrier.wait
|
143
|
+
THREAD_COUNT.times do
|
144
|
+
WORK_COUNT.times do
|
145
|
+
count += 1
|
146
|
+
end
|
147
|
+
end
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
THREAD_COUNT.times{|i|
|
152
|
+
th_grp.add Thread.new{
|
153
|
+
barrier.wait # different point from FineGrainLockManager
|
154
|
+
@lock_manager.transaction(true) {|lock_handler|
|
155
|
+
lock_handler.lock(:foo)
|
156
|
+
assert_equal(THREAD_COUNT * WORK_COUNT, count, "read transaction: #{i}")
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
barrier.wait
|
162
|
+
for t in th_grp.list
|
163
|
+
t.join
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class FineGrainLockManagerTest < Test::Unit::TestCase
|
169
|
+
include Higgs
|
170
|
+
include LockManagerTest
|
171
|
+
|
172
|
+
# for ident(1)
|
173
|
+
CVS_ID = '$Id: test_lock.rb 559 2007-09-25 15:20:20Z toki $'
|
174
|
+
|
175
|
+
def setup
|
176
|
+
@lock_manager = FineGrainLockManager.new
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_write_read_transaction_multithread
|
180
|
+
count = 0
|
181
|
+
th_grp = ThreadGroup.new
|
182
|
+
barrier = Barrier.new(THREAD_COUNT + 2)
|
183
|
+
|
184
|
+
th_grp.add Thread.new{
|
185
|
+
@lock_manager.transaction{|lock_handler|
|
186
|
+
lock_handler.lock(:foo)
|
187
|
+
barrier.wait
|
188
|
+
THREAD_COUNT.times do
|
189
|
+
WORK_COUNT.times do
|
190
|
+
count += 1
|
191
|
+
end
|
192
|
+
end
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
THREAD_COUNT.times{|i|
|
197
|
+
th_grp.add Thread.new{
|
198
|
+
@lock_manager.transaction(true) {|lock_handler|
|
199
|
+
barrier.wait # different point from GiantLockManager
|
200
|
+
lock_handler.lock(:foo)
|
201
|
+
assert_equal(THREAD_COUNT * WORK_COUNT, count, "read transaction: #{i}")
|
202
|
+
}
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
barrier.wait
|
207
|
+
for t in th_grp.list
|
208
|
+
t.join
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class GiantLockManagerNoDeadLockTest < Test::Unit::TestCase
|
214
|
+
include Higgs
|
215
|
+
|
216
|
+
# for ident(1)
|
217
|
+
CVS_ID = '$Id: test_lock.rb 559 2007-09-25 15:20:20Z toki $'
|
218
|
+
|
219
|
+
WORK_COUNT = 1000
|
220
|
+
|
221
|
+
def setup
|
222
|
+
@lock_manager = GiantLockManager.new
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_transaction_no_dead_lock
|
226
|
+
barrier = Barrier.new(3)
|
227
|
+
|
228
|
+
t1 = Thread.new{
|
229
|
+
barrier.wait
|
230
|
+
WORK_COUNT.times do
|
231
|
+
@lock_manager.transaction{|lock_handler|
|
232
|
+
lock_handler.lock(:foo)
|
233
|
+
lock_handler.lock(:bar)
|
234
|
+
}
|
235
|
+
end
|
236
|
+
}
|
237
|
+
|
238
|
+
t2 = Thread.new{
|
239
|
+
barrier.wait
|
240
|
+
WORK_COUNT.times do
|
241
|
+
@lock_manager.transaction{|lock_handler|
|
242
|
+
lock_handler.lock(:bar)
|
243
|
+
lock_handler.lock(:foo)
|
244
|
+
}
|
245
|
+
end
|
246
|
+
}
|
247
|
+
|
248
|
+
barrier.wait
|
249
|
+
t1.join
|
250
|
+
t2.join
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
class FineGrainLockManagerDeadLockTest < Test::Unit::TestCase
|
255
|
+
include Higgs
|
256
|
+
|
257
|
+
# for ident(1)
|
258
|
+
CVS_ID = '$Id: test_lock.rb 559 2007-09-25 15:20:20Z toki $'
|
259
|
+
|
260
|
+
def setup
|
261
|
+
@lock_manager = FineGrainLockManager.new(:spin_lock_count => 10,
|
262
|
+
:try_lock_limit => 0.1,
|
263
|
+
:try_lock_interval => 0.001)
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_transaction_dead_lock
|
267
|
+
barrier = Barrier.new(3)
|
268
|
+
|
269
|
+
m1 = Mutex.new
|
270
|
+
end_of_t1 = false
|
271
|
+
|
272
|
+
m2 = Mutex.new
|
273
|
+
end_of_t2 = false
|
274
|
+
|
275
|
+
t1 = Thread.new{
|
276
|
+
barrier.wait
|
277
|
+
begin
|
278
|
+
until (m2.synchronize{ end_of_t2 })
|
279
|
+
@lock_manager.transaction{|lock_handler|
|
280
|
+
lock_handler.lock(:foo)
|
281
|
+
sleep(0.03)
|
282
|
+
lock_handler.lock(:bar)
|
283
|
+
}
|
284
|
+
end
|
285
|
+
ensure
|
286
|
+
m1.synchronize{ end_of_t1 = true }
|
287
|
+
end
|
288
|
+
}
|
289
|
+
|
290
|
+
t2 = Thread.new{
|
291
|
+
barrier.wait
|
292
|
+
begin
|
293
|
+
until (m1.synchronize{ end_of_t1 })
|
294
|
+
@lock_manager.transaction{|lock_handler|
|
295
|
+
lock_handler.lock(:bar)
|
296
|
+
sleep(0.07)
|
297
|
+
lock_handler.lock(:foo)
|
298
|
+
}
|
299
|
+
end
|
300
|
+
ensure
|
301
|
+
m2.synchronize{ end_of_t2 = true }
|
302
|
+
end
|
303
|
+
}
|
304
|
+
|
305
|
+
barrier.wait
|
306
|
+
assert_raise(LockManager::TryLockTimeoutError) {
|
307
|
+
t1.join
|
308
|
+
t2.join
|
309
|
+
}
|
310
|
+
|
311
|
+
# join threads without exception
|
312
|
+
ThreadsWait.all_waits(t1, t2)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Local Variables:
|
318
|
+
# mode: Ruby
|
319
|
+
# indent-tabs-mode: nil
|
320
|
+
# End:
|
@@ -0,0 +1,169 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'drb'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'higgs/index'
|
6
|
+
require 'higgs/storage'
|
7
|
+
require 'logger'
|
8
|
+
require 'test/unit'
|
9
|
+
|
10
|
+
module Higgs::Test
|
11
|
+
module OnlineBackupParams
|
12
|
+
STORAGE_ITEMS = (ENV['STORAGE_ITEMS'] || '100').to_i
|
13
|
+
COMMIT_ITEMS = (ENV['COMMIT_ITEMS'] || '10').to_i
|
14
|
+
MAX_ITEM_BYTES = (ENV['MAX_ITEM_BYTES'] || '16384').to_i
|
15
|
+
LEAST_COMMITS_PER_ROTATION = (ENV['LEAST_COMMITS_PER_ROTATION'] || '8').to_i
|
16
|
+
UPTIME_SECONDS = (ENV['UPTIME_SECONDS'] || '3').to_i
|
17
|
+
ITEM_CHARS = ('A'..'Z').to_a + ('a'..'z').to_a
|
18
|
+
|
19
|
+
if ($DEBUG) then
|
20
|
+
puts 'online backup test parameters...'
|
21
|
+
for name in constants
|
22
|
+
puts "#{name} = #{const_get(name)}"
|
23
|
+
end
|
24
|
+
puts ''
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class OnlineBackupTest < Test::Unit::TestCase
|
29
|
+
include Higgs
|
30
|
+
include OnlineBackupParams
|
31
|
+
|
32
|
+
# for ident(1)
|
33
|
+
CVS_ID = '$Id: test_online_backup.rb 559 2007-09-25 15:20:20Z toki $'
|
34
|
+
|
35
|
+
def setup
|
36
|
+
srand(0)
|
37
|
+
|
38
|
+
@backup_dir = 'onbk_test_backup'
|
39
|
+
@backup_name = File.join(@backup_dir, 'foo')
|
40
|
+
FileUtils.rm_rf(@backup_dir) # for debug
|
41
|
+
FileUtils.mkdir_p(@backup_dir)
|
42
|
+
|
43
|
+
@restore_dir = 'onbk_test_restore'
|
44
|
+
@restore_name = File.join(@restore_dir, 'foo')
|
45
|
+
FileUtils.rm_rf(@restore_dir) # for debug
|
46
|
+
FileUtils.mkdir_p(@restore_dir)
|
47
|
+
|
48
|
+
@jlog_rotate_service_uri = 'druby://localhost:14142'
|
49
|
+
|
50
|
+
@start_latch = File.join(@backup_dir, '.start')
|
51
|
+
@stop_latch = File.join(@backup_dir, '.stop')
|
52
|
+
@stopped_latch = File.join(@backup_dir, '.stopped')
|
53
|
+
@end_latch = File.join(@backup_dir, '.end')
|
54
|
+
end
|
55
|
+
|
56
|
+
def teardown
|
57
|
+
FileUtils.rm_rf(@backup_dir) unless $DEBUG
|
58
|
+
FileUtils.rm_rf(@restore_dir) unless $DEBUG
|
59
|
+
end
|
60
|
+
|
61
|
+
def run_backup_storage
|
62
|
+
# step 0: storage starts with jlog_rotate_service_uri option
|
63
|
+
# (jlog_rotate_service_uri option is disabled by default)
|
64
|
+
st = Storage.new(@backup_name,
|
65
|
+
:jlog_rotate_max => 0,
|
66
|
+
:jlog_rotate_size => COMMIT_ITEMS * MAX_ITEM_BYTES * LEAST_COMMITS_PER_ROTATION,
|
67
|
+
:jlog_rotate_service_uri => @jlog_rotate_service_uri,
|
68
|
+
:logger => proc{|path|
|
69
|
+
logger = Logger.new(path, 1)
|
70
|
+
logger.level = Logger::DEBUG
|
71
|
+
logger
|
72
|
+
})
|
73
|
+
|
74
|
+
begin
|
75
|
+
FileUtils.touch(@start_latch)
|
76
|
+
until (File.exist? @stop_latch)
|
77
|
+
write_list = []
|
78
|
+
ope = [ :write, :system_properties, :custom_properties, :delete ][rand(3)]
|
79
|
+
key = rand(STORAGE_ITEMS)
|
80
|
+
case (ope)
|
81
|
+
when :write
|
82
|
+
value = rand(256).chr * rand(MAX_ITEM_BYTES)
|
83
|
+
write_list << [ ope, key, value ]
|
84
|
+
when :system_properties
|
85
|
+
next unless (st.key? key)
|
86
|
+
write_list << [ ope, key, { 'string_only' => [ true, false ][rand(2)] } ]
|
87
|
+
when :custom_properties
|
88
|
+
next unless (st.key? key)
|
89
|
+
value = ITEM_CHARS[rand(ITEM_CHARS.size)] * rand(MAX_ITEM_BYTES)
|
90
|
+
write_list << [ ope, key, { 'foo' => value } ]
|
91
|
+
when :delete
|
92
|
+
next unless (st.key? key)
|
93
|
+
write_list << [ ope, key ]
|
94
|
+
else
|
95
|
+
raise "unknown operation: #{ope}"
|
96
|
+
end
|
97
|
+
st.write_and_commit(write_list)
|
98
|
+
end
|
99
|
+
FileUtils.touch(@stopped_latch)
|
100
|
+
until (File.exist? @end_latch)
|
101
|
+
# spin lock
|
102
|
+
end
|
103
|
+
st.verify
|
104
|
+
ensure
|
105
|
+
st.shutdown
|
106
|
+
end
|
107
|
+
end
|
108
|
+
private :run_backup_storage
|
109
|
+
|
110
|
+
def test_online_backup
|
111
|
+
pid = fork{ run_backup_storage }
|
112
|
+
begin
|
113
|
+
until (File.exist? @start_latch)
|
114
|
+
# spin lock
|
115
|
+
end
|
116
|
+
jlog_rotate_service = DRbObject.new_with_uri(@jlog_rotate_service_uri)
|
117
|
+
sleep(UPTIME_SECONDS)
|
118
|
+
|
119
|
+
# step 1: backup index
|
120
|
+
jlog_rotate_service.call("#{@restore_name}.idx")
|
121
|
+
|
122
|
+
# step 2: backup data
|
123
|
+
FileUtils.cp("#{@backup_name}.tar", "#{@restore_name}.tar")
|
124
|
+
|
125
|
+
# transactions are stopped for comparison between original
|
126
|
+
# files and restored files. on real operation, transactions
|
127
|
+
# are not stopped.
|
128
|
+
FileUtils.touch(@stop_latch)
|
129
|
+
until (File.exist? @stopped_latch)
|
130
|
+
# spin lock
|
131
|
+
end
|
132
|
+
|
133
|
+
# step 3: rotate journal log
|
134
|
+
jlog_rotate_service.call(true)
|
135
|
+
|
136
|
+
# step 4: backup old journal logs
|
137
|
+
for path in Storage.rotate_entries("#{@backup_name}.jlog")
|
138
|
+
FileUtils.cp(path, File.join(@restore_dir, File.basename(path)))
|
139
|
+
FileUtils.rm(path)
|
140
|
+
end
|
141
|
+
|
142
|
+
# step 4: recover from backup
|
143
|
+
Storage.recover(@restore_name)
|
144
|
+
|
145
|
+
# recovered files are same as original files.
|
146
|
+
assert(FileUtils.cmp("#{@backup_name}.tar", "#{@restore_name}.tar"), 'tar')
|
147
|
+
assert_equal(Index.new.load("#{@backup_name}.idx").to_h,
|
148
|
+
Index.new.load("#{@restore_name}.idx").to_h, 'idx')
|
149
|
+
ensure
|
150
|
+
FileUtils.touch(@stop_latch)
|
151
|
+
FileUtils.touch(@end_latch)
|
152
|
+
Process.waitpid(pid)
|
153
|
+
end
|
154
|
+
assert_equal(0, $?.exitstatus)
|
155
|
+
|
156
|
+
st = Storage.new(@restore_name, :read_only => true)
|
157
|
+
begin
|
158
|
+
st.verify
|
159
|
+
ensure
|
160
|
+
st.shutdown
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Local Variables:
|
167
|
+
# mode: Ruby
|
168
|
+
# indent-tabs-mode: nil
|
169
|
+
# End:
|