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
@@ -0,0 +1,1086 @@
|
|
1
|
+
# = transactional storage core
|
2
|
+
#
|
3
|
+
# Author:: $Author: toki $
|
4
|
+
# Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
|
5
|
+
# Revision:: $Revision: 559 $
|
6
|
+
#
|
7
|
+
# == license
|
8
|
+
# :include:LICENSE
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'forwardable'
|
12
|
+
require 'higgs/block'
|
13
|
+
require 'higgs/cache'
|
14
|
+
require 'higgs/flock'
|
15
|
+
require 'higgs/index'
|
16
|
+
require 'higgs/jlog'
|
17
|
+
require 'higgs/tar'
|
18
|
+
require 'higgs/thread'
|
19
|
+
require 'thread'
|
20
|
+
require 'yaml'
|
21
|
+
|
22
|
+
module Higgs
|
23
|
+
# = transactional storage core
|
24
|
+
class Storage
|
25
|
+
# for ident(1)
|
26
|
+
CVS_ID = '$Id: storage.rb 559 2007-09-25 15:20:20Z toki $'
|
27
|
+
|
28
|
+
extend Forwardable
|
29
|
+
include Exceptions
|
30
|
+
|
31
|
+
class Error < HiggsError
|
32
|
+
end
|
33
|
+
|
34
|
+
class PanicError < Error
|
35
|
+
end
|
36
|
+
|
37
|
+
class NotWritableError < Error
|
38
|
+
end
|
39
|
+
|
40
|
+
class ShutdownException < Exceptions::ShutdownException
|
41
|
+
end
|
42
|
+
|
43
|
+
PROPERTIES_CKSUM_TYPE = 'SUM16'
|
44
|
+
PROPERTIES_CKSUM_BITS = 16
|
45
|
+
|
46
|
+
DATA_HASH = {}
|
47
|
+
[ [ :SUM16, proc{|s| s.sum(16).to_s }, nil ],
|
48
|
+
[ :MD5, proc{|s| Digest::MD5.hexdigest(s) }, 'digest/md5' ],
|
49
|
+
[ :RMD160, proc{|s| Digest::RMD160.hexdigest(s) }, 'digest/rmd160' ],
|
50
|
+
[ :SHA1, proc{|s| Digest::SHA1.hexdigest(s) }, 'digest/sha1' ],
|
51
|
+
[ :SHA256, proc{|s| Digest::SHA256.hexdigest(s) }, 'digest/sha2' ],
|
52
|
+
[ :SHA384, proc{|s| Digest::SHA384.hexdigest(s) }, 'digest/sha2' ],
|
53
|
+
[ :SHA512, proc{|s| Digest::SHA512.hexdigest(s) }, 'digest/sha2' ]
|
54
|
+
].each do |hash_symbol, hash_proc, hash_lib|
|
55
|
+
if (hash_lib) then
|
56
|
+
begin
|
57
|
+
require(hash_lib)
|
58
|
+
rescue LoadError
|
59
|
+
next
|
60
|
+
end
|
61
|
+
end
|
62
|
+
DATA_HASH[hash_symbol] = hash_proc
|
63
|
+
end
|
64
|
+
|
65
|
+
DATA_HASH_BIN = {}
|
66
|
+
DATA_HASH.each do |cksum_symbol, cksum_proc|
|
67
|
+
DATA_HASH_BIN[cksum_symbol.to_s] = cksum_proc
|
68
|
+
end
|
69
|
+
|
70
|
+
# options for Higgs::Storage
|
71
|
+
module InitOptions
|
72
|
+
# these options are defined.
|
73
|
+
# [<tt>:number_of_read_io</tt>] number of read I/O handle of pool. default is <tt>2</tt>.
|
74
|
+
# [<tt>:read_only</tt>] if <tt>true</tt> then storage is read-only. default is <tt>false</tt>.
|
75
|
+
# [<tt>:properties_cache</tt>] read-cache for properties. default is a new instance of Higgs::LRUCache.
|
76
|
+
# [<tt>:data_hash_type</tt>] hash type (<tt>:SUM16</tt>, <tt>:MD5</tt>, <tt>:RMD160</tt>,
|
77
|
+
# <tt>:SHA1</tt>, <tt>:SHA256</tt>, <tt>:SHA384</tt> or <tt>:SHA512</tt>)
|
78
|
+
# for data check. default is <tt>:MD5</tt>.
|
79
|
+
# [<tt>:jlog_sync</tt>] see Higgs::JournalLogger for detail. default is <tt>false</tt>.
|
80
|
+
# [<tt>:jlog_hash_type</tt>] see Higgs::JournalLogger for detail. default is <tt>:MD5</tt>.
|
81
|
+
# [<tt>:jlog_rotate_size</tt>] when this size is exceeded, journal log is switched to a new file.
|
82
|
+
# default is <tt>1024 * 256</tt>.
|
83
|
+
# [<tt>:jlog_rotate_max</tt>] old journal log is preserved in this number.
|
84
|
+
# if <tt>:jlog_rotate_max</tt> is <tt>0</tt>, old journal log is
|
85
|
+
# not deleted. if online-backup is used, <tt>:jlog_rotate_max</tt>
|
86
|
+
# should be <tt>0</tt>. default is <tt>1</tt>.
|
87
|
+
# [<tt>:jlog_rotate_service_uri</tt>] URI for DRb remote call to switch journal log to a new file.
|
88
|
+
# if online-backup is used, <tt>:jlog_rotate_service_uri</tt>
|
89
|
+
# should be defined. default is not defined.
|
90
|
+
# [<tt>:logger</tt>] procedure to create a logger. default is a procedure to create a new
|
91
|
+
# instance of Logger with logging level <tt>Logger::WARN</tt>.
|
92
|
+
def init_options(options)
|
93
|
+
@number_of_read_io = options[:number_of_read_io] || 2
|
94
|
+
|
95
|
+
if (options.key? :read_only) then
|
96
|
+
@read_only = options[:read_only]
|
97
|
+
else
|
98
|
+
@read_only = false
|
99
|
+
end
|
100
|
+
|
101
|
+
if (options.key? :properties_cache) then
|
102
|
+
@properties_cache = options[:properties_cache]
|
103
|
+
else
|
104
|
+
@properties_cache = LRUCache.new
|
105
|
+
end
|
106
|
+
|
107
|
+
@data_hash_type = options[:data_hash_type] || :MD5
|
108
|
+
unless (DATA_HASH.key? @data_hash_type) then
|
109
|
+
raise "unknown data hash type: #{@data_hash_type}"
|
110
|
+
end
|
111
|
+
|
112
|
+
if (options.key? :jlog_sync) then
|
113
|
+
@jlog_sync = options[:jlog_sync]
|
114
|
+
else
|
115
|
+
@jlog_sync = false
|
116
|
+
end
|
117
|
+
|
118
|
+
@jlog_hash_type = options[:jlog_hash_type] || :MD5
|
119
|
+
@jlog_rotate_size = options[:jlog_rotate_size] || 1024 * 256
|
120
|
+
@jlog_rotate_max = options[:jlog_rotate_max] || 1
|
121
|
+
@jlog_rotate_service_uri = options[:jlog_rotate_service_uri]
|
122
|
+
|
123
|
+
if (options.key? :logger) then
|
124
|
+
@Logger = options[:logger]
|
125
|
+
else
|
126
|
+
require 'logger'
|
127
|
+
@Logger = proc{|path|
|
128
|
+
logger = Logger.new(path, 1)
|
129
|
+
logger.level = Logger::WARN
|
130
|
+
logger
|
131
|
+
}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
private :init_options
|
135
|
+
|
136
|
+
attr_reader :read_only
|
137
|
+
attr_reader :number_of_read_io
|
138
|
+
attr_reader :data_hash_type
|
139
|
+
attr_reader :jlog_sync
|
140
|
+
attr_reader :jlog_hash_type
|
141
|
+
attr_reader :jlog_rotate_size
|
142
|
+
attr_reader :jlog_rotate_max
|
143
|
+
attr_reader :jlog_rotate_service_uri
|
144
|
+
end
|
145
|
+
include InitOptions
|
146
|
+
|
147
|
+
# export Higgs::Storage methods from <tt>@storage</tt> instance variable.
|
148
|
+
#
|
149
|
+
# these methods are delegated.
|
150
|
+
# * Higgs::Storage#name
|
151
|
+
# * Higgs::Storage#read_only
|
152
|
+
# * Higgs::Storage#number_of_read_io
|
153
|
+
# * Higgs::Storage#data_hash_type
|
154
|
+
# * Higgs::Storage#jlog_sync
|
155
|
+
# * Higgs::Storage#jlog_hash_type
|
156
|
+
# * Higgs::Storage#jlog_rotate_size
|
157
|
+
# * Higgs::Storage#jlog_rotate_max
|
158
|
+
# * Higgs::Storage#jlog_rotate_service_uri
|
159
|
+
# * Higgs::Storage#shutdown
|
160
|
+
# * Higgs::Storage#shutdown?
|
161
|
+
# * Higgs::Storage#rotate_journal_log
|
162
|
+
# * Higgs::Storage#verify
|
163
|
+
#
|
164
|
+
module Export
|
165
|
+
extend Forwardable
|
166
|
+
|
167
|
+
def_delegator :@storage, :name
|
168
|
+
def_delegator :@storage, :read_only
|
169
|
+
def_delegator :@storage, :number_of_read_io
|
170
|
+
def_delegator :@storage, :data_hash_type
|
171
|
+
def_delegator :@storage, :jlog_sync
|
172
|
+
def_delegator :@storage, :jlog_hash_type
|
173
|
+
def_delegator :@storage, :jlog_rotate_size
|
174
|
+
def_delegator :@storage, :jlog_rotate_max
|
175
|
+
def_delegator :@storage, :jlog_rotate_service_uri
|
176
|
+
def_delegator :@storage, :shutdown
|
177
|
+
def_delegator :@storage, :shutdown?
|
178
|
+
def_delegator :@storage, :rotate_journal_log
|
179
|
+
def_delegator :@storage, :verify
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.load_conf(path)
|
183
|
+
conf = YAML.load(IO.read(path))
|
184
|
+
options = {}
|
185
|
+
for name, value in conf
|
186
|
+
case (name)
|
187
|
+
when 'data_hash_type', 'jlog_hash_type'
|
188
|
+
value = value.to_sym
|
189
|
+
when 'properties_cache_limit_size', 'master_cache_limit_size'
|
190
|
+
name = name.sub(/_limit_size$/, '')
|
191
|
+
value = LRUCache.new(value)
|
192
|
+
when 'logging_level'
|
193
|
+
require 'logger'
|
194
|
+
name = 'logger'
|
195
|
+
level = case (value)
|
196
|
+
when 'debug', 'info', 'warn', 'error', 'fatal'
|
197
|
+
Logger.const_get(value.upcase)
|
198
|
+
else
|
199
|
+
raise "unknown logging level: #{value}"
|
200
|
+
end
|
201
|
+
value = proc{|path|
|
202
|
+
logger = Logger.new(path, 1)
|
203
|
+
logger.level = level
|
204
|
+
logger
|
205
|
+
}
|
206
|
+
end
|
207
|
+
options[name.to_sym] = value
|
208
|
+
end
|
209
|
+
options
|
210
|
+
end
|
211
|
+
|
212
|
+
# <tt>name</tt> is storage name.
|
213
|
+
# see Higgs::Storage::InitOptions for <tt>options</tt>.
|
214
|
+
#
|
215
|
+
# storage is composed of the following 5 files.
|
216
|
+
# <tt>name.log</tt>:: event log. default logging level is <tt>WARN</tt>.
|
217
|
+
# <tt>name.tar</tt>:: data file compatible with unix TAR format.
|
218
|
+
# <tt>name.idx</tt>:: index snapshot. genuine index is Hash in the memory.
|
219
|
+
# see Higgs::Index for detail.
|
220
|
+
# <tt>name.jlog</tt>:: transaction journal log. see Higgs::JournalLogger for detail.
|
221
|
+
# <tt>name.lock</tt>:: lock file for File#flock. see Higgs::FileLock for detail.
|
222
|
+
#
|
223
|
+
def initialize(name, options={})
|
224
|
+
@name = name
|
225
|
+
@log_name = "#{@name}.log"
|
226
|
+
@tar_name = "#{@name}.tar"
|
227
|
+
@idx_name = "#{@name}.idx"
|
228
|
+
@jlog_name = "#{@name}.jlog"
|
229
|
+
@lock_name = "#{@name}.lock"
|
230
|
+
|
231
|
+
@commit_lock = Mutex.new
|
232
|
+
@state_lock = Mutex.new
|
233
|
+
@broken = false
|
234
|
+
@shutdown = false
|
235
|
+
|
236
|
+
init_options(options)
|
237
|
+
|
238
|
+
init_completed = false
|
239
|
+
begin
|
240
|
+
@flock = FileLock.new(@lock_name, @read_only)
|
241
|
+
if (@read_only) then
|
242
|
+
@flock.read_lock
|
243
|
+
else
|
244
|
+
@flock.write_lock
|
245
|
+
end
|
246
|
+
|
247
|
+
@logger = @Logger.call(@log_name)
|
248
|
+
@logger.info("storage open start...")
|
249
|
+
if (@read_only) then
|
250
|
+
@logger.info("get file lock for read")
|
251
|
+
else
|
252
|
+
@logger.info("get file lock for write")
|
253
|
+
end
|
254
|
+
|
255
|
+
@logger.info format('block format version: 0x%04X', Block::FMT_VERSION)
|
256
|
+
@logger.info("journal log hash type: #{@jlog_hash_type}")
|
257
|
+
@logger.info("index format version: #{Index::MAJOR_VERSION}.#{Index::MINOR_VERSION}")
|
258
|
+
@logger.info("storage data hash type: #{@data_hash_type}")
|
259
|
+
@logger.info("storage properties cksum type: #{PROPERTIES_CKSUM_TYPE}")
|
260
|
+
|
261
|
+
@logger.info("properties cache type: #{@properties_cache.class}")
|
262
|
+
@properties_cache = SharedWorkCache.new(@properties_cache) {|key|
|
263
|
+
value = read_record_body(key, :p) and decode_properties(key, value)
|
264
|
+
}
|
265
|
+
|
266
|
+
unless (@read_only) then
|
267
|
+
begin
|
268
|
+
w_io = File.open(@tar_name, File::WRONLY | File::CREAT | File::EXCL, 0660)
|
269
|
+
@logger.info("create and get I/O handle for write: #{@tar_name}")
|
270
|
+
rescue Errno::EEXIST
|
271
|
+
@logger.info("open I/O handle for write: #{@tar_name}")
|
272
|
+
w_io = File.open(@tar_name, File::WRONLY, 0660)
|
273
|
+
end
|
274
|
+
w_io.binmode
|
275
|
+
@w_tar = Tar::ArchiveWriter.new(w_io)
|
276
|
+
end
|
277
|
+
|
278
|
+
@logger.info("build I/O handle pool for read.")
|
279
|
+
@r_tar_pool = Pool.new(@number_of_read_io) {
|
280
|
+
r_io = File.open(@tar_name, File::RDONLY)
|
281
|
+
r_io.binmode
|
282
|
+
@logger.info("open I/O handle for read: #{@tar_name}")
|
283
|
+
Tar::ArchiveReader.new(Tar::RawIO.new(r_io))
|
284
|
+
}
|
285
|
+
|
286
|
+
@index = Index.new
|
287
|
+
if (File.exist? @idx_name) then
|
288
|
+
@logger.info("load index: #{@idx_name}")
|
289
|
+
@index.load(@idx_name)
|
290
|
+
end
|
291
|
+
if (JournalLogger.need_for_recovery? @jlog_name) then
|
292
|
+
recover
|
293
|
+
end
|
294
|
+
unless (@read_only) then
|
295
|
+
@logger.info("journal log sync mode: #{@jlog_sync}")
|
296
|
+
@logger.info("open journal log for write: #{@jlog_name}")
|
297
|
+
@jlog = JournalLogger.open(@jlog_name, @jlog_sync, @jlog_hash_type)
|
298
|
+
end
|
299
|
+
|
300
|
+
if (@jlog_rotate_service_uri) then
|
301
|
+
@logger.info("start journal log rotation service: #{@jlog_rotate_service_uri}")
|
302
|
+
require 'drb'
|
303
|
+
@jlog_rotate_service = DRb::DRbServer.new(@jlog_rotate_service_uri,
|
304
|
+
method(:rotate_journal_log))
|
305
|
+
else
|
306
|
+
@jlog_rotate_service = nil
|
307
|
+
end
|
308
|
+
|
309
|
+
init_completed = true
|
310
|
+
ensure
|
311
|
+
if (init_completed) then
|
312
|
+
@logger.info("completed storage open.")
|
313
|
+
else
|
314
|
+
@broken = true
|
315
|
+
|
316
|
+
if ($! && @logger) then
|
317
|
+
begin
|
318
|
+
@logger.error($!)
|
319
|
+
rescue
|
320
|
+
# ignore error
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
if (@jlog_rotate_service) then
|
325
|
+
begin
|
326
|
+
@jlog_rotate_service.stop_service
|
327
|
+
rescue
|
328
|
+
# ignore error
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
unless (@read_only) then
|
333
|
+
if (@jlog) then
|
334
|
+
begin
|
335
|
+
@jlog.close(false)
|
336
|
+
rescue
|
337
|
+
# ignore error
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
if (@r_tar_pool) then
|
343
|
+
@r_tar_pool.shutdown{|r_tar|
|
344
|
+
begin
|
345
|
+
r_tar.close
|
346
|
+
rescue
|
347
|
+
# ignore errno
|
348
|
+
end
|
349
|
+
}
|
350
|
+
end
|
351
|
+
|
352
|
+
unless (@read_only) then
|
353
|
+
if (@w_tar) then
|
354
|
+
begin
|
355
|
+
@w_tar.close(false)
|
356
|
+
rescue
|
357
|
+
# ignore error
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
if (@logger) then
|
363
|
+
begin
|
364
|
+
@logger.fatal("abrot storage open.")
|
365
|
+
@logger.close
|
366
|
+
rescue
|
367
|
+
# ignore error
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
if (@flock) then
|
372
|
+
begin
|
373
|
+
@flock.close
|
374
|
+
rescue
|
375
|
+
# ignore error
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def check_panic
|
383
|
+
@state_lock.synchronize{
|
384
|
+
if (@shutdown) then
|
385
|
+
raise ShutdownException, 'storage shutdown'
|
386
|
+
end
|
387
|
+
if (@broken) then
|
388
|
+
raise PanicError, 'broken storage'
|
389
|
+
end
|
390
|
+
}
|
391
|
+
end
|
392
|
+
private :check_panic
|
393
|
+
|
394
|
+
def recover
|
395
|
+
@logger.warn('incompleted storage and recover from journal log...')
|
396
|
+
|
397
|
+
check_panic
|
398
|
+
if (@read_only) then
|
399
|
+
@logger.warn('read only storage is not recoverable.')
|
400
|
+
raise NotWritableError, 'need for recovery'
|
401
|
+
end
|
402
|
+
|
403
|
+
recover_completed = false
|
404
|
+
begin
|
405
|
+
safe_pos = 0
|
406
|
+
@logger.info("open journal log for read: #{@jlog_name}")
|
407
|
+
File.open(@jlog_name, File::RDONLY) {|f|
|
408
|
+
f.binmode
|
409
|
+
begin
|
410
|
+
JournalLogger.scan_log(f) {|log|
|
411
|
+
change_number = log[0]
|
412
|
+
@logger.info("apply journal log: #{change_number}")
|
413
|
+
Storage.apply_journal(@w_tar, @index, log)
|
414
|
+
}
|
415
|
+
rescue Block::BrokenError
|
416
|
+
# nothing to do.
|
417
|
+
end
|
418
|
+
safe_pos = f.tell
|
419
|
+
}
|
420
|
+
@logger.info("last safe point of journal log: #{safe_pos}")
|
421
|
+
|
422
|
+
File.open(@jlog_name, File::WRONLY, 0660) {|f|
|
423
|
+
f.binmode
|
424
|
+
@logger.info("shrink journal log to erase last broken segment.")
|
425
|
+
f.truncate(safe_pos)
|
426
|
+
f.seek(safe_pos)
|
427
|
+
@logger.info("write eof mark to journal log.")
|
428
|
+
JournalLogger.eof_mark(f)
|
429
|
+
}
|
430
|
+
recover_completed = true
|
431
|
+
ensure
|
432
|
+
unless (recover_completed) then
|
433
|
+
@state_lock.synchronize{ @broken = true }
|
434
|
+
@logger.error("BROKEN: failed to recover.")
|
435
|
+
@logger.error($!) if $!
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
@logger.info('completed recovery from journal log.')
|
440
|
+
end
|
441
|
+
private :recover
|
442
|
+
|
443
|
+
attr_reader :name
|
444
|
+
|
445
|
+
def shutdown
|
446
|
+
@commit_lock.synchronize{
|
447
|
+
@state_lock.synchronize{
|
448
|
+
if (@shutdown) then
|
449
|
+
raise ShutdownException, 'storage shutdown'
|
450
|
+
end
|
451
|
+
@logger.info("shutdown start...")
|
452
|
+
@shutdown = true
|
453
|
+
|
454
|
+
if (@jlog_rotate_service) then
|
455
|
+
@logger.info("stop journal log rotation service: #{@jlog_rotate_service}")
|
456
|
+
@jlog_rotate_service.stop_service
|
457
|
+
end
|
458
|
+
|
459
|
+
unless (@read_only) then
|
460
|
+
if (@broken) then
|
461
|
+
@logger.warn("abort journal log: #{@jlog_name}")
|
462
|
+
@jlog.close(false)
|
463
|
+
else
|
464
|
+
@logger.info("close journal log: #{@jlog_name}")
|
465
|
+
@jlog.close
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
if (! @broken && ! @read_only) then
|
470
|
+
@logger.info("save index: #{@idx_name}")
|
471
|
+
@index.save(@idx_name)
|
472
|
+
end
|
473
|
+
|
474
|
+
@r_tar_pool.shutdown{|r_tar|
|
475
|
+
@logger.info("close I/O handle for read: #{@tar_name}")
|
476
|
+
r_tar.close
|
477
|
+
}
|
478
|
+
unless (@read_only) then
|
479
|
+
@logger.info("sync write data: #{@tar_name}")
|
480
|
+
@w_tar.fsync
|
481
|
+
@logger.info("close I/O handle for write: #{@tar_name}")
|
482
|
+
@w_tar.close(false)
|
483
|
+
end
|
484
|
+
|
485
|
+
@logger.info("unlock: #{@lock_name}")
|
486
|
+
@flock.close
|
487
|
+
|
488
|
+
@logger.info("completed shutdown.")
|
489
|
+
@logger.close
|
490
|
+
}
|
491
|
+
}
|
492
|
+
nil
|
493
|
+
end
|
494
|
+
|
495
|
+
def shutdown?
|
496
|
+
@state_lock.synchronize{ @shutdown }
|
497
|
+
end
|
498
|
+
|
499
|
+
def self.rotate_entries(name)
|
500
|
+
rotate_list = Dir["#{name}.*"].map{|nm|
|
501
|
+
n = Integer(nm[(name.length + 1)..-1])
|
502
|
+
[ nm, n ]
|
503
|
+
}.sort{|a, b|
|
504
|
+
a[1] <=> b[1]
|
505
|
+
}.map{|nm, n|
|
506
|
+
nm
|
507
|
+
}
|
508
|
+
rotate_list
|
509
|
+
end
|
510
|
+
|
511
|
+
def internal_rotate_journal_log(save_index)
|
512
|
+
@logger.info("start journal log rotation...")
|
513
|
+
|
514
|
+
commit_log = []
|
515
|
+
while (File.exist? "#{@jlog_name}.#{@index.change_number}")
|
516
|
+
@index.succ!
|
517
|
+
@logger.debug("index succ: #{@index.change_number}") if @logger.debug?
|
518
|
+
commit_log << { :ope => :succ, :cnum => @index.change_number }
|
519
|
+
end
|
520
|
+
unless (commit_log.empty?) then
|
521
|
+
@logger.debug("write journal log: #{@index.change_number}") if @logger.debug?
|
522
|
+
@jlog.write([ @index.change_number, commit_log ])
|
523
|
+
end
|
524
|
+
rot_jlog_name = "#{@jlog_name}.#{@index.change_number}"
|
525
|
+
|
526
|
+
if (save_index) then
|
527
|
+
case (save_index)
|
528
|
+
when String
|
529
|
+
@logger.info("save index: #{save_index}")
|
530
|
+
@index.save(save_index)
|
531
|
+
else
|
532
|
+
@logger.info("save index: #{@idx_name}")
|
533
|
+
@index.save(@idx_name)
|
534
|
+
end
|
535
|
+
else
|
536
|
+
@logger.info("no save index.")
|
537
|
+
end
|
538
|
+
|
539
|
+
@logger.info("close journal log.")
|
540
|
+
@jlog.close
|
541
|
+
@logger.info("rename journal log: #{@jlog_name} -> #{rot_jlog_name}")
|
542
|
+
File.rename(@jlog_name, rot_jlog_name)
|
543
|
+
if (@jlog_rotate_max > 0) then
|
544
|
+
rotate_list = Storage.rotate_entries(@jlog_name)
|
545
|
+
while (rotate_list.length > @jlog_rotate_max)
|
546
|
+
unlink_jlog_name = rotate_list.shift
|
547
|
+
@logger.info("unlink old journal log: #{unlink_jlog_name}")
|
548
|
+
File.unlink(unlink_jlog_name)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
@logger.info("open journal log: #{@jlog_name}")
|
552
|
+
@jlog = JournalLogger.open(@jlog_name, @jlog_sync, @jlog_hash_type)
|
553
|
+
|
554
|
+
@logger.info("completed journal log rotation.")
|
555
|
+
end
|
556
|
+
private :internal_rotate_journal_log
|
557
|
+
|
558
|
+
def rotate_journal_log(save_index=true)
|
559
|
+
@commit_lock.synchronize{
|
560
|
+
check_panic
|
561
|
+
if (@read_only) then
|
562
|
+
raise NotWritableError, 'failed to write to read only storage'
|
563
|
+
end
|
564
|
+
|
565
|
+
rotate_completed = false
|
566
|
+
begin
|
567
|
+
internal_rotate_journal_log(save_index)
|
568
|
+
rotate_completed = true
|
569
|
+
ensure
|
570
|
+
unless (rotate_completed) then
|
571
|
+
@state_lock.synchronize{ @broken = true }
|
572
|
+
@logger.error("BROKEN: failed to rotate journal log.")
|
573
|
+
@logger.error($!) if $!
|
574
|
+
end
|
575
|
+
end
|
576
|
+
}
|
577
|
+
|
578
|
+
nil
|
579
|
+
end
|
580
|
+
|
581
|
+
def raw_write_and_commit(write_list, commit_time=Time.now)
|
582
|
+
@commit_lock.synchronize{
|
583
|
+
@logger.debug("start raw_write_and_commit.") if @logger.debug?
|
584
|
+
|
585
|
+
check_panic
|
586
|
+
if (@read_only) then
|
587
|
+
raise NotWritableError, 'failed to write to read only storage'
|
588
|
+
end
|
589
|
+
|
590
|
+
commit_log = []
|
591
|
+
commit_completed = false
|
592
|
+
eoa = @index.eoa
|
593
|
+
|
594
|
+
begin
|
595
|
+
for ope, key, type, name, value in write_list
|
596
|
+
case (ope)
|
597
|
+
when :write
|
598
|
+
@logger.debug("journal log for write: (key,type)=(#{key},#{type})") if @logger.debug?
|
599
|
+
unless (value.kind_of? String) then
|
600
|
+
raise TypeError, "can't convert #{value.class} (value) to String"
|
601
|
+
end
|
602
|
+
blocked_size = Tar::Block::BLKSIZ + Tar::Block.blocked_size(value.length)
|
603
|
+
|
604
|
+
# recycle
|
605
|
+
if (pos = @index.free_fetch(blocked_size)) then
|
606
|
+
@logger.debug("write type of recycle free region: (pos,size)=(#{pos},#{blocked_size})") if @logger.debug?
|
607
|
+
commit_log << {
|
608
|
+
:ope => :free_fetch,
|
609
|
+
:pos => pos,
|
610
|
+
:siz => blocked_size
|
611
|
+
}
|
612
|
+
commit_log << {
|
613
|
+
:ope => :write,
|
614
|
+
:key => key,
|
615
|
+
:pos => pos,
|
616
|
+
:typ => type,
|
617
|
+
:mod => commit_time,
|
618
|
+
:nam => name,
|
619
|
+
:val => value
|
620
|
+
}
|
621
|
+
if (i = @index[key]) then
|
622
|
+
if (j = i[type]) then
|
623
|
+
commit_log << {
|
624
|
+
:ope => :free_store,
|
625
|
+
:pos => j[:pos],
|
626
|
+
:siz => j[:siz],
|
627
|
+
:mod => commit_time
|
628
|
+
}
|
629
|
+
@index.free_store(j[:pos], j[:siz])
|
630
|
+
j[:pos] = pos
|
631
|
+
j[:siz] = blocked_size
|
632
|
+
else
|
633
|
+
i[type] = { :pos => pos, :siz => blocked_size }
|
634
|
+
end
|
635
|
+
else
|
636
|
+
@index[key] = { type => { :pos => pos, :siz => blocked_size } }
|
637
|
+
end
|
638
|
+
next
|
639
|
+
end
|
640
|
+
|
641
|
+
# overwrite
|
642
|
+
if (i = @index[key]) then
|
643
|
+
if (j = i[type]) then
|
644
|
+
if (j[:siz] >= blocked_size) then
|
645
|
+
@logger.debug("write type of overwrite: (pos,size)=(#{j[:pos]},#{blocked_size})") if @logger.debug?
|
646
|
+
commit_log << {
|
647
|
+
:ope => :write,
|
648
|
+
:key => key,
|
649
|
+
:pos => j[:pos],
|
650
|
+
:typ => type,
|
651
|
+
:mod => commit_time,
|
652
|
+
:nam => name,
|
653
|
+
:val => value
|
654
|
+
}
|
655
|
+
if (j[:siz] > blocked_size) then
|
656
|
+
commit_log << {
|
657
|
+
:ope => :free_store,
|
658
|
+
:pos => j[:pos] + blocked_size,
|
659
|
+
:siz => j[:siz] - blocked_size,
|
660
|
+
:mod => commit_time
|
661
|
+
}
|
662
|
+
@logger.debug("tail free region: (pos,size)=(#{commit_log[-1][:pos]},#{commit_log[-1][:siz]})") if @logger.debug?
|
663
|
+
@index.free_store(commit_log.last[:pos], commit_log.last[:siz])
|
664
|
+
j[:siz] = blocked_size
|
665
|
+
end
|
666
|
+
next
|
667
|
+
end
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
# append
|
672
|
+
@logger.debug("write type of append: (pos,size)=(#{eoa},#{blocked_size})")
|
673
|
+
commit_log << {
|
674
|
+
:ope => :write,
|
675
|
+
:key => key,
|
676
|
+
:pos => eoa,
|
677
|
+
:typ => type,
|
678
|
+
:mod => commit_time,
|
679
|
+
:nam => name,
|
680
|
+
:val => value
|
681
|
+
}
|
682
|
+
if (i = @index[key]) then
|
683
|
+
if (j = i[type]) then
|
684
|
+
commit_log << {
|
685
|
+
:ope => :free_store,
|
686
|
+
:pos => j[:pos],
|
687
|
+
:siz => j[:siz],
|
688
|
+
:mod => commit_time
|
689
|
+
}
|
690
|
+
@index.free_store(j[:pos], j[:siz])
|
691
|
+
j[:pos] = eoa
|
692
|
+
j[:siz] = blocked_size
|
693
|
+
else
|
694
|
+
i[type] = { :pos => eoa, :siz => blocked_size }
|
695
|
+
end
|
696
|
+
else
|
697
|
+
@index[key] = { type => { :pos => eoa, :siz => blocked_size } }
|
698
|
+
end
|
699
|
+
eoa += blocked_size
|
700
|
+
commit_log << {
|
701
|
+
:ope => :eoa,
|
702
|
+
:pos => eoa
|
703
|
+
}
|
704
|
+
when :delete
|
705
|
+
@logger.debug("journal log for delete: #{key}") if @logger.debug?
|
706
|
+
if (i = @index.delete(key)) then
|
707
|
+
commit_log << {
|
708
|
+
:ope => :delete,
|
709
|
+
:key => key
|
710
|
+
}
|
711
|
+
i.each_value{|j|
|
712
|
+
commit_log << {
|
713
|
+
:ope => :free_store,
|
714
|
+
:pos => j[:pos],
|
715
|
+
:siz => j[:siz],
|
716
|
+
:mod => commit_time
|
717
|
+
}
|
718
|
+
@index.free_store(j[:pos], j[:siz])
|
719
|
+
}
|
720
|
+
end
|
721
|
+
else
|
722
|
+
raise ArgumentError, "unknown operation: #{cmd[:ope]}"
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
@index.succ!
|
727
|
+
@logger.debug("index succ: #{@index.change_number}") if @logger.debug?
|
728
|
+
commit_log << { :ope => :succ, :cnum => @index.change_number }
|
729
|
+
|
730
|
+
@logger.debug("write journal log: #{@index.change_number}") if @logger.debug?
|
731
|
+
@jlog.write([ @index.change_number, commit_log ])
|
732
|
+
|
733
|
+
for cmd in commit_log
|
734
|
+
case (cmd[:ope])
|
735
|
+
when :write
|
736
|
+
name = cmd[:nam][0, Tar::Block::MAX_LEN]
|
737
|
+
@logger.debug("write data to storage: (name,pos,size)=(#{name},#{cmd[:pos]},#{cmd[:val].size})") if @logger.debug?
|
738
|
+
@w_tar.seek(cmd[:pos])
|
739
|
+
@w_tar.add(name, cmd[:val], :mtime => cmd[:mod])
|
740
|
+
when :free_store
|
741
|
+
@logger.debug("write free region to storage: (pos,size)=(#{cmd[:pos]},#{cmd[:siz]})") if @logger.debug?
|
742
|
+
name = format('.free.%x', cmd[:pos] >> 9)
|
743
|
+
@w_tar.seek(cmd[:pos])
|
744
|
+
@w_tar.write_header(:name => name, :size => cmd[:siz] - Tar::Block::BLKSIZ, :mtime => cmd[:mod])
|
745
|
+
when :delete, :eoa, :free_fetch, :succ
|
746
|
+
# nothing to do.
|
747
|
+
else
|
748
|
+
raise "unknown operation: #{cmd[:ope]}"
|
749
|
+
end
|
750
|
+
end
|
751
|
+
if (@index.eoa != eoa) then
|
752
|
+
@logger.debug("write EOA to storage: #{eoa}")
|
753
|
+
@index.eoa = eoa
|
754
|
+
@w_tar.seek(eoa)
|
755
|
+
@w_tar.write_EOA
|
756
|
+
end
|
757
|
+
@logger.debug("flush storage.")
|
758
|
+
@w_tar.flush
|
759
|
+
|
760
|
+
if (@jlog_rotate_size > 0 && @jlog.size >= @jlog_rotate_size) then
|
761
|
+
internal_rotate_journal_log(true)
|
762
|
+
end
|
763
|
+
|
764
|
+
commit_completed = true
|
765
|
+
ensure
|
766
|
+
unless (commit_completed) then
|
767
|
+
@state_lock.synchronize{ @broken = true }
|
768
|
+
@logger.error("BROKEN: failed to commit.")
|
769
|
+
@logger.error($!) if $!
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
@logger.debug("completed raw_write_and_commit.") if @logger.debug?
|
774
|
+
}
|
775
|
+
|
776
|
+
nil
|
777
|
+
end
|
778
|
+
|
779
|
+
def self.apply_journal(w_tar, index, log)
|
780
|
+
change_number, commit_log = log
|
781
|
+
if (change_number - 1 < index.change_number) then
|
782
|
+
# skip old jounal log
|
783
|
+
elsif (change_number - 1 > index.change_number) then
|
784
|
+
raise PanicError, "lost journal log (cnum: #{index.change_number + 1})"
|
785
|
+
else # if (change_number - 1 == index.change_number) then
|
786
|
+
for cmd in commit_log
|
787
|
+
case (cmd[:ope])
|
788
|
+
when :write
|
789
|
+
name = "#{cmd[:key]}.#{cmd[:typ]}"[0, Tar::Block::MAX_LEN]
|
790
|
+
w_tar.seek(cmd[:pos])
|
791
|
+
w_tar.add(cmd[:nam], cmd[:val], :mtime => cmd[:mod])
|
792
|
+
blocked_size = Tar::Block::BLKSIZ + Tar::Block.blocked_size(cmd[:val].length)
|
793
|
+
if (i = index[cmd[:key]]) then
|
794
|
+
if (j = i[cmd[:typ]]) then
|
795
|
+
j[:pos] = cmd[:pos]
|
796
|
+
j[:siz] = blocked_size
|
797
|
+
else
|
798
|
+
i[cmd[:typ]] = { :pos => cmd[:pos], :siz => blocked_size }
|
799
|
+
end
|
800
|
+
else
|
801
|
+
index[cmd[:key]] = { cmd[:typ] => { :pos => cmd[:pos], :siz => blocked_size } }
|
802
|
+
end
|
803
|
+
when :delete
|
804
|
+
index.delete(cmd[:key])
|
805
|
+
when :free_fetch
|
806
|
+
index.free_fetch_at(cmd[:pos], cmd[:siz])
|
807
|
+
when :free_store
|
808
|
+
index.free_store(cmd[:pos], cmd[:siz])
|
809
|
+
name = format('.free.%x', cmd[:pos] >> 9)
|
810
|
+
w_tar.seek(cmd[:pos])
|
811
|
+
w_tar.write_header(:name => name, :size => cmd[:siz] - Tar::Block::BLKSIZ, :mtime => cmd[:mod])
|
812
|
+
when :eoa
|
813
|
+
index.eoa = cmd[:pos]
|
814
|
+
when :succ
|
815
|
+
index.succ!
|
816
|
+
if (index.change_number != cmd[:cnum]) then
|
817
|
+
raise PanicError, 'lost journal log'
|
818
|
+
end
|
819
|
+
else
|
820
|
+
raise "unknown operation from #{curr_jlog_name}: #{cmd[:ope]}"
|
821
|
+
end
|
822
|
+
end
|
823
|
+
end
|
824
|
+
nil
|
825
|
+
end
|
826
|
+
|
827
|
+
def self.recover(name, out=nil, verbose_level=1)
|
828
|
+
tar_name = "#{name}.tar"
|
829
|
+
idx_name = "#{name}.idx"
|
830
|
+
jlog_name = "#{name}.jlog"
|
831
|
+
lock_name = "#{name}.lock"
|
832
|
+
|
833
|
+
flock = FileLock.new(lock_name)
|
834
|
+
flock.synchronize{
|
835
|
+
begin
|
836
|
+
w_io = File.open(tar_name, File::WRONLY | File::CREAT | File::EXCL, 0660)
|
837
|
+
rescue Errno::EEXIST
|
838
|
+
w_io = File.open(tar_name, File::WRONLY, 0660)
|
839
|
+
end
|
840
|
+
w_io.binmode
|
841
|
+
w_tar = Tar::ArchiveWriter.new(w_io)
|
842
|
+
|
843
|
+
index = Index.new
|
844
|
+
index.load(idx_name) if (File.exist? idx_name)
|
845
|
+
|
846
|
+
|
847
|
+
out << "recovery target: #{name}\n" if (out && verbose_level >= 1)
|
848
|
+
for curr_name in rotate_entries(jlog_name)
|
849
|
+
JournalLogger.each_log(curr_name) do |log|
|
850
|
+
change_number = log[0]
|
851
|
+
out << "apply journal log: #{change_number}\n" if (out && verbose_level >= 1)
|
852
|
+
apply_journal(w_tar, index, log)
|
853
|
+
end
|
854
|
+
end
|
855
|
+
w_tar.seek(index.eoa)
|
856
|
+
w_tar.write_EOA
|
857
|
+
|
858
|
+
index.save(idx_name)
|
859
|
+
w_tar.fsync
|
860
|
+
w_tar.close(false)
|
861
|
+
}
|
862
|
+
flock.close
|
863
|
+
|
864
|
+
nil
|
865
|
+
end
|
866
|
+
|
867
|
+
def write_and_commit(write_list, commit_time=Time.now)
|
868
|
+
check_panic
|
869
|
+
if (@read_only) then
|
870
|
+
raise NotWritableError, 'failed to write to read only storage'
|
871
|
+
end
|
872
|
+
|
873
|
+
raw_write_list = []
|
874
|
+
deleted_entries = {}
|
875
|
+
update_properties = {}
|
876
|
+
|
877
|
+
for ope, key, value in write_list
|
878
|
+
case (ope)
|
879
|
+
when :write
|
880
|
+
unless (value.kind_of? String) then
|
881
|
+
raise TypeError, "can't convert #{value.class} (value) to String"
|
882
|
+
end
|
883
|
+
raw_write_list << [ :write, key, :d, key.to_s, value ]
|
884
|
+
deleted_entries[key] = false
|
885
|
+
if (properties = update_properties[key]) then
|
886
|
+
# nothing to do.
|
887
|
+
elsif (properties = internal_fetch_properties(key)) then
|
888
|
+
update_properties[key] = properties
|
889
|
+
else
|
890
|
+
# new properties
|
891
|
+
properties = {
|
892
|
+
'system_properties' => {
|
893
|
+
'hash_type' => @data_hash_type.to_s,
|
894
|
+
'hash_value' => nil,
|
895
|
+
'created_time' => commit_time,
|
896
|
+
'changed_time' => commit_time,
|
897
|
+
'modified_time' => nil,
|
898
|
+
'string_only' => false
|
899
|
+
},
|
900
|
+
'custom_properties' => {}
|
901
|
+
}
|
902
|
+
update_properties[key] = properties
|
903
|
+
end
|
904
|
+
properties['system_properties']['hash_value'] = DATA_HASH[@data_hash_type].call(value)
|
905
|
+
properties['system_properties']['modified_time'] = commit_time
|
906
|
+
@properties_cache.delete(key)
|
907
|
+
when :delete
|
908
|
+
raw_write_list << [ :delete, key ]
|
909
|
+
deleted_entries[key] = true
|
910
|
+
update_properties.delete(key)
|
911
|
+
@properties_cache.delete(key)
|
912
|
+
when :custom_properties, :system_properties
|
913
|
+
if (deleted_entries[key]) then
|
914
|
+
raise IndexError, "not exist properties at key: #{key}"
|
915
|
+
end
|
916
|
+
if (properties = update_properties[key]) then
|
917
|
+
# nothing to do.
|
918
|
+
elsif (properties = internal_fetch_properties(key)) then
|
919
|
+
update_properties[key] = properties
|
920
|
+
else
|
921
|
+
raise IndexError, "not exist properties at key: #{key}"
|
922
|
+
end
|
923
|
+
properties['system_properties']['changed_time'] = commit_time
|
924
|
+
case (ope)
|
925
|
+
when :custom_properties
|
926
|
+
properties['custom_properties'] = value
|
927
|
+
when :system_properties
|
928
|
+
if (value.key? 'string_only') then
|
929
|
+
properties['system_properties']['string_only'] = value['string_only'] ? true : false
|
930
|
+
end
|
931
|
+
else
|
932
|
+
raise ArgumentError, "unknown operation: #{ope}"
|
933
|
+
end
|
934
|
+
@properties_cache.delete(key)
|
935
|
+
else
|
936
|
+
raise ArgumentError, "unknown operation: #{ope}"
|
937
|
+
end
|
938
|
+
end
|
939
|
+
|
940
|
+
for key, properties in update_properties
|
941
|
+
raw_write_list << [ :write, key, :p, "#{key}.p", encode_properties(properties) ]
|
942
|
+
end
|
943
|
+
|
944
|
+
raw_write_and_commit(raw_write_list, commit_time)
|
945
|
+
|
946
|
+
nil
|
947
|
+
end
|
948
|
+
|
949
|
+
def read_record(key, type)
|
950
|
+
head_and_body = nil
|
951
|
+
if (i = @index[key]) then
|
952
|
+
if (j = i[type]) then
|
953
|
+
@r_tar_pool.transaction{|r_tar|
|
954
|
+
r_tar.seek(j[:pos])
|
955
|
+
head_and_body = r_tar.fetch
|
956
|
+
}
|
957
|
+
unless (head_and_body) then
|
958
|
+
@state_lock.synchronize{ @broken = true }
|
959
|
+
@logger.error("BROKEN: failed to read record: #{key}")
|
960
|
+
raise PanicError, "failed to read record: #{key}"
|
961
|
+
end
|
962
|
+
end
|
963
|
+
end
|
964
|
+
head_and_body
|
965
|
+
end
|
966
|
+
private :read_record
|
967
|
+
|
968
|
+
def read_record_body(key, type)
|
969
|
+
head_and_body = read_record(key, type) or return
|
970
|
+
head_and_body[:body]
|
971
|
+
end
|
972
|
+
private :read_record_body
|
973
|
+
|
974
|
+
def encode_properties(properties)
|
975
|
+
body = properties.to_yaml
|
976
|
+
head = "\# #{PROPERTIES_CKSUM_TYPE} #{body.sum(PROPERTIES_CKSUM_BITS)}\n"
|
977
|
+
head + body
|
978
|
+
end
|
979
|
+
private :encode_properties
|
980
|
+
|
981
|
+
def decode_properties(key, value)
|
982
|
+
head, body = value.split(/\n/, 2)
|
983
|
+
cksum_type, cksum_value = head.sub(/^#\s+/, '').split(/\s+/, 2)
|
984
|
+
if (cksum_type != PROPERTIES_CKSUM_TYPE) then
|
985
|
+
@state_lock.synchronize{ @broken = true }
|
986
|
+
@logger.error("BROKEN: unknown properties cksum type: #{cksum_type}")
|
987
|
+
raise PanicError, "unknown properties cksum type: #{cksum_type}"
|
988
|
+
end
|
989
|
+
if (body.sum(PROPERTIES_CKSUM_BITS) != Integer(cksum_value)) then
|
990
|
+
@state_lock.synchronize{ @broken = true }
|
991
|
+
@logger.error("BROKEN: mismatch properties cksum at #{key}")
|
992
|
+
raise PanicError, "mismatch properties cksum at #{key}"
|
993
|
+
end
|
994
|
+
YAML.load(body)
|
995
|
+
end
|
996
|
+
private :decode_properties
|
997
|
+
|
998
|
+
def internal_fetch_properties(key)
|
999
|
+
@properties_cache[key] # see initialize
|
1000
|
+
end
|
1001
|
+
private :internal_fetch_properties
|
1002
|
+
|
1003
|
+
def fetch_properties(key)
|
1004
|
+
check_panic
|
1005
|
+
internal_fetch_properties(key)
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
def fetch(key)
|
1009
|
+
check_panic
|
1010
|
+
value = read_record_body(key, :d) or return
|
1011
|
+
unless (properties = internal_fetch_properties(key)) then
|
1012
|
+
@state_lock.synchronize{ @broken = true }
|
1013
|
+
@logger.error("BROKEN: failed to read properties: #{key}")
|
1014
|
+
raise PanicError, "failed to read properties: #{key}"
|
1015
|
+
end
|
1016
|
+
hash_type = properties['system_properties']['hash_type']
|
1017
|
+
unless (cksum_proc = DATA_HASH_BIN[hash_type]) then
|
1018
|
+
@state_lock.synchronize{ @broken = true }
|
1019
|
+
@logger.error("BROKEN: unknown data hash type: #{hash_type}")
|
1020
|
+
raise PanicError, "unknown data hash type: #{hash_type}"
|
1021
|
+
end
|
1022
|
+
hash_value = cksum_proc.call(value)
|
1023
|
+
if (hash_value != properties['system_properties']['hash_value']) then
|
1024
|
+
@state_lock.synchronize{ @broken = true }
|
1025
|
+
@logger.error("BROKEN: mismatch hash value at #{key}")
|
1026
|
+
raise PanicError, "mismatch hash value at #{key}"
|
1027
|
+
end
|
1028
|
+
value
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
def string_only(key)
|
1032
|
+
properties = fetch_properties(key) or raise IndexError, "not exist properties at key: #{key}"
|
1033
|
+
properties['system_properties']['string_only']
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
def_delegator :@index, :identity
|
1037
|
+
|
1038
|
+
def key?(key)
|
1039
|
+
check_panic
|
1040
|
+
@index.key? key
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
def each_key
|
1044
|
+
check_panic
|
1045
|
+
@index.each_key do |key|
|
1046
|
+
yield(key)
|
1047
|
+
end
|
1048
|
+
self
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
VERIFY_VERBOSE_LIST = [
|
1052
|
+
[ 'hash_type', proc{|type| type } ],
|
1053
|
+
[ 'hash_value', proc{|value| value } ],
|
1054
|
+
[ 'created_time', proc{|t| t.strftime('%Y-%m-%d %H:%M:%S.') + format('%03d', Integer(t.to_f % 1000)) } ],
|
1055
|
+
[ 'changed_time', proc{|t| t.strftime('%Y-%m-%d %H:%M:%S.') + format('%03d', Integer(t.to_f % 1000)) } ],
|
1056
|
+
[ 'modified_time', proc{|t| t.strftime('%Y-%m-%d %H:%M:%S.') + format('%03d', Integer(t.to_f % 1000)) } ],
|
1057
|
+
[ 'string_only', proc{|flag| flag.to_s } ]
|
1058
|
+
]
|
1059
|
+
|
1060
|
+
def verify(out=nil, verbose_level=1)
|
1061
|
+
check_panic
|
1062
|
+
@index.each_key do |key|
|
1063
|
+
if (out && verbose_level >= 1) then
|
1064
|
+
out << "check #{key}\n"
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
data = fetch(key)
|
1068
|
+
|
1069
|
+
if (out && verbose_level >= 2) then
|
1070
|
+
out << " #{data.length} bytes\n"
|
1071
|
+
properties = fetch_properties(key) or raise PanicError, "not exist properties at key: #{key}"
|
1072
|
+
for key, format in VERIFY_VERBOSE_LIST
|
1073
|
+
value = properties['system_properties'][key]
|
1074
|
+
out << ' ' << key << ': ' << format.call(value) << "\n"
|
1075
|
+
end
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
nil
|
1079
|
+
end
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
# Local Variables:
|
1084
|
+
# mode: Ruby
|
1085
|
+
# indent-tabs-mode: nil
|
1086
|
+
# End:
|