8 class Keep < Sinatra::Base
15 mime_type :binary, 'application/octet-stream'
17 set :port, (ENV['PORT'] || '25107').to_i
18 set :bind, (ENV['IP'] || '0.0.0.0')
21 def verify_hash(data, hash)
23 Digest::MD5.hexdigest(data) == hash && hash
24 elsif hash.length == 40
25 Digest::SHA1.hexdigest(data) == hash && hash
32 if ENV['DEBUG'] and ENV['DEBUG'].match /^-?\d+/
39 def self.debuglog(loglevel, msg)
40 if debuglevel >= loglevel
41 $stderr.puts "[keepd/#{$$} #{Time.now}] #{msg}"
45 self.class.debuglog *args
49 return @@keepdirs if defined? @@keepdirs
50 # Configure backing store directories
52 rootdir = (ENV['KEEP_ROOT'] || '/').sub /\/$/, ''
53 `mount`.split("\n").each do |mountline|
54 dev, on_txt, mountpoint, type_txt, fstype, opts = mountline.split
55 if on_txt == 'on' and type_txt == 'type'
56 debuglog 2, "dir #{mountpoint} is mounted"
57 if mountpoint[0..(rootdir.length)] == rootdir + '/'
58 debuglog 2, "dir #{mountpoint} is in #{rootdir}/"
59 keepdir = "#{mountpoint.sub /\/$/, ''}/keep"
60 if File.exists? "#{keepdir}/."
64 :arvados_file => File.join(keepdir, 'arvados_keep_disk.json'),
67 :device_inode => File.stat(dev).ino
69 if opts.gsub(/[\(\)]/, '').split(',').index('ro')
72 debuglog 2, "keepdir #{kd.inspect}"
74 kd[:arvados] = JSON.parse(File.read(kd[:arvados_file]), symbolize_names: true)
76 debuglog 0, "keepdir #{kd.inspect} is new (no #{kd[:arvados_file]})"
83 Dir.open('/dev/disk/by-uuid/').each do |fs_uuid|
84 next if fs_uuid.match /^\./
85 fs_root_inode = File.stat("/dev/disk/by-uuid/#{fs_uuid}").ino
86 @@keepdirs.each do |kd|
87 if kd[:device_inode] == fs_root_inode
88 kd[:filesystem_uuid] = fs_uuid
89 debuglog 0, "keepdir #{kd.reject { |k,v| k==:arvados }.inspect}"
97 def find_backfile(hash, opts)
99 @@keepdirs.each do |keepdir|
100 backfile = "#{keepdir[:root]}/#{subdir}/#{hash}"
101 if File.exists? backfile
103 File.open("#{keepdir[:root]}/lock", "a+") do |f|
104 if f.flock File::LOCK_EX
105 data = File.read backfile
108 if data and (!opts[:verify_hash] or verify_hash data, hash)
109 return [backfile, data]
116 get '/:locator' do |locator|
117 regs = locator.match /^([0-9a-f]{32,})/
120 backfile, data = find_backfile hash, :verify_hash => false
131 self.class.ping_arvados
134 put '/:locator' do |locator|
135 data = request.body.read
136 hash = verify_hash(data, locator)
139 body "Checksum mismatch"
142 backfile, havedata = find_backfile hash, :verify_hash => true
149 @@keepdirs.each do |keepdir|
150 next if keepdir[:readonly]
151 backdir = "#{keepdir[:root]}/#{subdir}"
152 if !File.exists? backdir
158 backfile = "#{keepdir[:root]}/#{subdir}/#{hash}"
159 File.open("#{keepdir[:root]}/lock", "a+") do |lf|
160 if lf.flock File::LOCK_EX
161 File.open(backfile + ".tmp", "a+") do |wf|
162 if wf.flock File::LOCK_EX
163 wf.seek 0, File::SEEK_SET
165 wrote = wf.write data
167 if wrote == data.length
168 File.rename backfile+".tmp", backfile
171 File.unlink backfile+".tmp"
177 if wrote == data.length
185 self.class.ping_arvados
190 def self.ping_arvados
191 return if defined? @@last_ping_at and @@last_ping_at > Time.now - 300
192 @@last_ping_at = Time.now
194 @@arvados ||= Arvados.new(api_token: '')
195 @@keepdirs.each do |kd|
196 ack = @@arvados.keep_disk.ping(uuid: kd[:arvados][:uuid],
197 service_port: settings.port,
198 service_ssl_flag: Keep.ssl_flag,
199 ping_secret: kd[:arvados][:ping_secret],
201 is_writable: !kd[:readonly],
202 filesystem_uuid: kd[:filesystem_uuid])
203 if ack and ack[:last_ping_at]
204 debuglog 0, "device #{kd[:device]} uuid #{ack[:uuid]} last_ping_at #{ack[:last_ping_at]}"
205 if kd[:arvados].empty?
206 File.open(kd[:arvados_file]+'.tmp', 'a+', 0600) do end
207 File.open(kd[:arvados_file]+'.tmp', 'r+', 0600) do |f|
208 if f.flock File::LOCK_EX
209 f.seek 0, File::SEEK_SET
212 File.rename kd[:arvados_file]+'.tmp', kd[:arvados_file]
218 debuglog 0, "device #{kd[:device]} ping fail"
221 rescue Exception => e
222 debuglog 0, "ping_arvados: #{e.inspect}"
229 if ENV['SSL_CERT'] and ENV['SSL_KEY']
231 :cert_chain_file => ENV['SSL_CERT'],
232 :private_key_file => ENV['SSL_KEY'],
233 :verify_peer => false
237 server.ssl_options = ssl_options