omgf 0.0.0.GIT
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/.document +4 -0
- data/.gitignore +14 -0
- data/.wrongdoc.yml +6 -0
- data/COPYING +661 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +5 -0
- data/README +69 -0
- data/examples/hyst.README +90 -0
- data/examples/hyst.bash +365 -0
- data/lib/omgf.rb +6 -0
- data/lib/omgf/hysterical_raisins.rb +336 -0
- data/lib/omgf/pool.rb +48 -0
- data/omgf.gemspec +26 -0
- data/pkg.mk +175 -0
- data/test/integration.rb +201 -0
- data/test/test_hyst.rb +78 -0
- data/test/test_hysterical_raisins.rb +238 -0
- metadata +134 -0
data/test/integration.rb
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Copyright (C) 2008-2012, Eric Wong <normalperson@yhbt.net>
|
|
2
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
|
3
|
+
$stdout.sync = $stderr.sync = true
|
|
4
|
+
Thread.abort_on_exception = true
|
|
5
|
+
if ENV["COVERAGE"]
|
|
6
|
+
require "coverage"
|
|
7
|
+
Coverage.start
|
|
8
|
+
at_exit do
|
|
9
|
+
# Dirty little text formatter. I tried simplecov but the default
|
|
10
|
+
# HTML+JS is unusable without a GUI (I hate GUIs :P) and it would've
|
|
11
|
+
# taken me longer to search the Internets to find a plain-text
|
|
12
|
+
# formatter I like...
|
|
13
|
+
res = Coverage.result
|
|
14
|
+
relevant = res.keys.grep(%r{/lib/omgf/\w+\.rb})
|
|
15
|
+
relevant.each do |file|
|
|
16
|
+
cov = res[file]
|
|
17
|
+
puts "==> #{file} <=="
|
|
18
|
+
File.readlines(file).each_with_index do |line, i|
|
|
19
|
+
n = cov[i]
|
|
20
|
+
if n == 0 # BAD
|
|
21
|
+
print(" *** 0 #{line}")
|
|
22
|
+
elsif n
|
|
23
|
+
printf("% 7u %s", n, line)
|
|
24
|
+
elsif line =~ /\S/ # probably a line with just "end" in it
|
|
25
|
+
print(" #{line}")
|
|
26
|
+
else # blank line
|
|
27
|
+
print "\n" # don't output trailing whitespace on blank lines
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
require 'test/unit'
|
|
34
|
+
require 'net/http'
|
|
35
|
+
require 'uri'
|
|
36
|
+
require 'tempfile'
|
|
37
|
+
require 'mogilefs'
|
|
38
|
+
require 'stringio'
|
|
39
|
+
require 'logger'
|
|
40
|
+
|
|
41
|
+
module TestMogileFSIntegration
|
|
42
|
+
def x(*cmd)
|
|
43
|
+
out = Tempfile.new("out")
|
|
44
|
+
err = Tempfile.new("err")
|
|
45
|
+
puts cmd.join(' ') if $VERBOSE
|
|
46
|
+
pid = fork do
|
|
47
|
+
$stderr.reopen(err.path, "a")
|
|
48
|
+
$stdout.reopen(out.path, "a")
|
|
49
|
+
out.close
|
|
50
|
+
err.close
|
|
51
|
+
ObjectSpace.each_object(Tempfile) do |tmp|
|
|
52
|
+
next if tmp.closed?
|
|
53
|
+
ObjectSpace.undefine_finalizer(tmp)
|
|
54
|
+
tmp.close_on_exec = true if tmp.respond_to?(:close_on_exec=)
|
|
55
|
+
end
|
|
56
|
+
exec(*cmd)
|
|
57
|
+
end
|
|
58
|
+
_, status = Process.waitpid2(pid)
|
|
59
|
+
out.rewind
|
|
60
|
+
err.rewind
|
|
61
|
+
[ status, out, err ]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def x!(*cmd)
|
|
65
|
+
status, out, err = x(*cmd)
|
|
66
|
+
assert status.success?, "#{status.inspect} / #{out.read} / #{err.read}"
|
|
67
|
+
[ status, out, err ]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def setup_mogilefs(plugins = nil)
|
|
71
|
+
@test_host = "127.0.0.1"
|
|
72
|
+
setup_mogstored
|
|
73
|
+
@tracker = TCPServer.new(@test_host, 0)
|
|
74
|
+
@tracker_port = @tracker.addr[1]
|
|
75
|
+
|
|
76
|
+
@dbname = Tempfile.new(["mogfresh", ".sqlite3"])
|
|
77
|
+
@mogilefsd_conf = Tempfile.new(["mogilefsd", "conf"])
|
|
78
|
+
@mogilefsd_pid = Tempfile.new(["mogilefsd", "pid"])
|
|
79
|
+
|
|
80
|
+
cmd = %w(mogdbsetup --yes --type=SQLite --dbname) << @dbname.path
|
|
81
|
+
x!(*cmd)
|
|
82
|
+
|
|
83
|
+
@mogilefsd_conf.puts "db_dsn DBI:SQLite:#{@dbname.path}"
|
|
84
|
+
@mogilefsd_conf.write <<EOF
|
|
85
|
+
conf_port #@tracker_port
|
|
86
|
+
listen #@test_host
|
|
87
|
+
pidfile #{@mogilefsd_pid.path}
|
|
88
|
+
replicate_jobs 1
|
|
89
|
+
fsck_jobs 1
|
|
90
|
+
query_jobs 1
|
|
91
|
+
mogstored_stream_port #@mogstored_mgmt_port
|
|
92
|
+
node_timeout 10
|
|
93
|
+
EOF
|
|
94
|
+
@mogilefsd_conf.flush
|
|
95
|
+
|
|
96
|
+
@trackers = @hosts = [ "#@test_host:#@tracker_port" ]
|
|
97
|
+
@tracker.close
|
|
98
|
+
x!("mogilefsd", "--daemon", "--config=#{@mogilefsd_conf.path}")
|
|
99
|
+
wait_for_port @tracker_port
|
|
100
|
+
@admin = MogileFS::Admin.new(:hosts => @hosts)
|
|
101
|
+
500.times do
|
|
102
|
+
break if File.size(@mogstored_pid.path) > 0
|
|
103
|
+
sleep 0.05
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
args = { :ip => @test_host, :port => @mogstored_http_port }
|
|
107
|
+
args[:status] = "alive"
|
|
108
|
+
@admin.create_host("me", args)
|
|
109
|
+
yield_for_monitor_update { @admin.get_hosts.empty? or break }
|
|
110
|
+
|
|
111
|
+
mogadm!("device", "add", "me", "dev1")
|
|
112
|
+
yield_for_monitor_update { @admin.get_devices.empty? or break }
|
|
113
|
+
wait_for_usage_file "dev1"
|
|
114
|
+
mogadm!("device", "add", "me", "dev2")
|
|
115
|
+
wait_for_usage_file "dev2"
|
|
116
|
+
yield_for_monitor_update { @admin.get_devices.size == 2 and break }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def mogadm(*args)
|
|
120
|
+
x("mogadm", "--trackers=#{@trackers.join(',')}", *args)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def mogadm!(*args)
|
|
124
|
+
status, out, err = mogadm(*args)
|
|
125
|
+
assert status.success?, "#{status.inspect} / #{out.read} / #{err.read}"
|
|
126
|
+
[ status, out, err ]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def yield_for_monitor_update
|
|
130
|
+
50.times do
|
|
131
|
+
yield
|
|
132
|
+
sleep 0.1
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def wait_for_port(port)
|
|
138
|
+
tries = 500
|
|
139
|
+
begin
|
|
140
|
+
TCPSocket.new(@test_host, port).close
|
|
141
|
+
return
|
|
142
|
+
rescue
|
|
143
|
+
sleep 0.05
|
|
144
|
+
end while (tries -= 1) > 0
|
|
145
|
+
raise "#@test_host:#{port} never became ready"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def teardown_mogilefs
|
|
149
|
+
if @mogstored_pid
|
|
150
|
+
pid = File.read(@mogstored_pid.path).to_i
|
|
151
|
+
Process.kill(:TERM, pid) if pid > 0
|
|
152
|
+
end
|
|
153
|
+
if @mogilefsd_pid
|
|
154
|
+
s = TCPSocket.new(@test_host, @tracker_port)
|
|
155
|
+
s.write "!shutdown\r\n"
|
|
156
|
+
s.close
|
|
157
|
+
end
|
|
158
|
+
FileUtils.rmtree(@docroot)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def wait_for_usage_file(device)
|
|
162
|
+
uri = URI("http://#@test_host:#@mogstored_http_port/#{device}/usage")
|
|
163
|
+
res = nil
|
|
164
|
+
100.times do
|
|
165
|
+
res = Net::HTTP.get_response(uri)
|
|
166
|
+
if Net::HTTPOK === res
|
|
167
|
+
puts res.body if $DEBUG
|
|
168
|
+
return
|
|
169
|
+
end
|
|
170
|
+
puts res.inspect if $DEBUG
|
|
171
|
+
sleep 0.1
|
|
172
|
+
end
|
|
173
|
+
raise "#{uri} failed to appear: #{res.inspect}"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def setup_mogstored
|
|
177
|
+
@docroot = Dir.mktmpdir(["mogfresh", "docroot"])
|
|
178
|
+
Dir.mkdir("#@docroot/dev1")
|
|
179
|
+
Dir.mkdir("#@docroot/dev2")
|
|
180
|
+
@mogstored_mgmt = TCPServer.new(@test_host, 0)
|
|
181
|
+
@mogstored_http = TCPServer.new(@test_host, 0)
|
|
182
|
+
@mogstored_mgmt_port = @mogstored_mgmt.addr[1]
|
|
183
|
+
@mogstored_http_port = @mogstored_http.addr[1]
|
|
184
|
+
@mogstored_conf = Tempfile.new(["mogstored", "conf"])
|
|
185
|
+
@mogstored_pid = Tempfile.new(["mogstored", "pid"])
|
|
186
|
+
@mogstored_conf.write <<EOF
|
|
187
|
+
pidfile = #{@mogstored_pid.path}
|
|
188
|
+
maxconns = 500
|
|
189
|
+
httplisten = #@test_host:#@mogstored_http_port
|
|
190
|
+
mgmtlisten = #@test_host:#@mogstored_mgmt_port
|
|
191
|
+
docroot = #@docroot
|
|
192
|
+
EOF
|
|
193
|
+
@mogstored_conf.flush
|
|
194
|
+
@mogstored_mgmt.close
|
|
195
|
+
@mogstored_http.close
|
|
196
|
+
|
|
197
|
+
x!("mogstored", "--daemon", "--config=#{@mogstored_conf.path}")
|
|
198
|
+
wait_for_port @mogstored_mgmt_port
|
|
199
|
+
wait_for_port @mogstored_http_port
|
|
200
|
+
end
|
|
201
|
+
end
|
data/test/test_hyst.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Copyright (C) 2008-2012, Eric Wong <normalperson@yhbt.net>
|
|
2
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
|
3
|
+
require './test/integration'
|
|
4
|
+
require 'omgf/hysterical_raisins'
|
|
5
|
+
begin
|
|
6
|
+
require 'unicorn'
|
|
7
|
+
rescue LoadError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class TestHystScript < Test::Unit::TestCase
|
|
11
|
+
include TestMogileFSIntegration
|
|
12
|
+
def setup
|
|
13
|
+
setup_mogilefs
|
|
14
|
+
@admin.create_domain("testdom")
|
|
15
|
+
srv = TCPServer.new(@test_host, 0)
|
|
16
|
+
@api_port = srv.addr[1]
|
|
17
|
+
ENV["UNICORN_FD"] = srv.fileno.to_s
|
|
18
|
+
api_addr = "#@test_host:#@api_port"
|
|
19
|
+
@app = OMGF::HystericalRaisins.new(:hosts => @hosts)
|
|
20
|
+
@out = Tempfile.new("out")
|
|
21
|
+
@err = Tempfile.new("err")
|
|
22
|
+
@unicorn_pid = fork do
|
|
23
|
+
$stdin.reopen("/dev/null")
|
|
24
|
+
$stdout.reopen(@out.path, "ab")
|
|
25
|
+
$stderr.reopen(@err.path, "ab")
|
|
26
|
+
# hopefully this Unicorn API doesn't change...
|
|
27
|
+
Unicorn::HttpServer.new(@app, :listeners => [api_addr]).start.join
|
|
28
|
+
end
|
|
29
|
+
srv.close
|
|
30
|
+
ENV["HYST_HOST"] = api_addr
|
|
31
|
+
ENV["MOG_DOMAIN"] = 'testdom'
|
|
32
|
+
@hyst = File.dirname(File.dirname(__FILE__)) + "/examples/hyst.bash"
|
|
33
|
+
assert File.executable?(@hyst)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_hyst
|
|
37
|
+
out = `#@hyst ls`
|
|
38
|
+
assert_equal $?, 0
|
|
39
|
+
assert_equal '', out
|
|
40
|
+
|
|
41
|
+
# tee is a little tricky, unicorn is one of the few Rack servers that
|
|
42
|
+
# handle chunked PUTs
|
|
43
|
+
out = `echo HI | #@hyst tee foo`
|
|
44
|
+
assert_equal 0, $?
|
|
45
|
+
assert_equal "HI\n", out
|
|
46
|
+
assert_equal "HI\n", `#@hyst cat foo`
|
|
47
|
+
|
|
48
|
+
# /dev/null optimization
|
|
49
|
+
out = `echo NULL | #@hyst tee bar >/dev/null`
|
|
50
|
+
assert_equal 0, $?
|
|
51
|
+
assert_equal "", out
|
|
52
|
+
assert_equal "NULL\n", `#@hyst cat bar`
|
|
53
|
+
|
|
54
|
+
# listings
|
|
55
|
+
assert_equal "foo\n", `#@hyst ls fo`
|
|
56
|
+
assert_equal "bar\n", `#@hyst ls b`
|
|
57
|
+
assert_equal "", `#@hyst ls z`
|
|
58
|
+
|
|
59
|
+
# remove a file once
|
|
60
|
+
assert(system("#@hyst rm bar"))
|
|
61
|
+
|
|
62
|
+
# really removed?
|
|
63
|
+
err = Tempfile.new('curl_err')
|
|
64
|
+
a = `#@hyst cat bar 2>#{err.path}`
|
|
65
|
+
assert_equal 22, $?.exitstatus
|
|
66
|
+
assert_equal '', a
|
|
67
|
+
assert_match(/\s+404\b/, err.read)
|
|
68
|
+
err.close!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def teardown
|
|
72
|
+
ENV.delete("HYST_HOST")
|
|
73
|
+
Process.kill :QUIT, @unicorn_pid
|
|
74
|
+
_, status = Process.waitpid2(@unicorn_pid)
|
|
75
|
+
assert status.success?, status.inspect
|
|
76
|
+
teardown_mogilefs
|
|
77
|
+
end
|
|
78
|
+
end if defined?(Unicorn)
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Copyright (C) 2008-2012, Eric Wong <normalperson@yhbt.net>
|
|
2
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
|
3
|
+
require './test/integration'
|
|
4
|
+
require 'rack/mock'
|
|
5
|
+
require 'open-uri'
|
|
6
|
+
require 'omgf/hysterical_raisins'
|
|
7
|
+
|
|
8
|
+
class TestHystericalRaisins < Test::Unit::TestCase
|
|
9
|
+
include TestMogileFSIntegration
|
|
10
|
+
def setup
|
|
11
|
+
setup_mogilefs
|
|
12
|
+
@app = OMGF::HystericalRaisins.new(:hosts => @hosts)
|
|
13
|
+
@req = Rack::MockRequest.new(@app)
|
|
14
|
+
@err = StringIO.new
|
|
15
|
+
@opts = { "rack.logger" => Logger.new(@err) }
|
|
16
|
+
@admin.create_domain("testdom")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_all
|
|
20
|
+
resp = @req.get("/")
|
|
21
|
+
assert_equal 200, resp.status, "/ response code is 200"
|
|
22
|
+
assert_equal "", resp.body, "/ returns empty body"
|
|
23
|
+
|
|
24
|
+
# domain listing
|
|
25
|
+
resp = @req.get("/testdom", @opts)
|
|
26
|
+
assert_equal 200, resp.status, "domain listing response code is 200"
|
|
27
|
+
assert_equal "", resp.body, "domain listing is empty"
|
|
28
|
+
assert_equal("text/plain", resp.headers["Content-Type"],
|
|
29
|
+
"content-type is text/plain")
|
|
30
|
+
|
|
31
|
+
json = { "HTTP_ACCEPT" => "application/json" }
|
|
32
|
+
|
|
33
|
+
# json domain listing
|
|
34
|
+
resp = @req.get("/testdom", @opts.merge(json))
|
|
35
|
+
assert_equal 200, resp.status, "domain listing response code is 200"
|
|
36
|
+
assert_equal "[]", resp.body, "domain listing is empty array"
|
|
37
|
+
assert_equal("application/json", resp.headers["Content-Type"],
|
|
38
|
+
"content-type is application/json")
|
|
39
|
+
|
|
40
|
+
# missing domain
|
|
41
|
+
resp = @req.get("/non-existent", @opts)
|
|
42
|
+
assert_equal 0, @err.string.size
|
|
43
|
+
assert_equal 404, resp.status, "non-existent domain listing gives 404"
|
|
44
|
+
|
|
45
|
+
# missing domain (json)
|
|
46
|
+
resp = @req.get("/non-existent", @opts.merge(json))
|
|
47
|
+
assert_equal 0, @err.string.size
|
|
48
|
+
assert_equal 404, resp.status, "non-existent domain listing gives 404"
|
|
49
|
+
|
|
50
|
+
# PUT to domain listing fails
|
|
51
|
+
opts = @opts.merge(:input => StringIO.new("HELLO"))
|
|
52
|
+
resp = @req.put("/non-existent", opts)
|
|
53
|
+
assert_equal 0, @err.string.size
|
|
54
|
+
assert_equal 404, resp.status, "non-existent domain listing PUT gives 404"
|
|
55
|
+
|
|
56
|
+
# DELETEs to bad domains fail
|
|
57
|
+
resp = @req.delete("/non-existent", opts)
|
|
58
|
+
assert_equal 0, @err.string.size
|
|
59
|
+
assert_equal 404, resp.status, "non-existent domain DELETE gives 404"
|
|
60
|
+
resp = @req.delete("/non-existent/key", opts)
|
|
61
|
+
assert_equal 0, @err.string.size
|
|
62
|
+
assert_equal 406, resp.status, "non-existent domain DELETE gives 406"
|
|
63
|
+
|
|
64
|
+
# PUT to bad domain fails
|
|
65
|
+
opts = @opts.merge(:input => StringIO.new("HELLO"))
|
|
66
|
+
resp = @req.put("/non-existent/key", opts)
|
|
67
|
+
assert_equal 0, @err.string.size
|
|
68
|
+
assert_equal 406, resp.status, "bad domain PUT w/key: #{resp.inspect}"
|
|
69
|
+
assert_equal "Invalid domain: non-existent\n", resp.body
|
|
70
|
+
|
|
71
|
+
# PUT with bad key fails
|
|
72
|
+
opts = @opts.merge(:input => StringIO.new("HELLO"))
|
|
73
|
+
resp = @req.put("/testdom/key%20space", opts)
|
|
74
|
+
assert_equal 0, @err.string.size, @err.string
|
|
75
|
+
assert_equal 406, resp.status, "bad key"
|
|
76
|
+
|
|
77
|
+
# PUT with long key fails
|
|
78
|
+
opts = @opts.merge(:input => StringIO.new("HELLO"))
|
|
79
|
+
resp = @req.put("/testdom/key#{'a' * 128}", opts)
|
|
80
|
+
assert_equal 0, @err.string.size, @err.string
|
|
81
|
+
assert_equal 406, resp.status, "bad key"
|
|
82
|
+
|
|
83
|
+
# PUT to good domain succeeds
|
|
84
|
+
opts = @opts.merge(:input => StringIO.new("HELLO"))
|
|
85
|
+
resp = @req.put("/testdom/key", opts)
|
|
86
|
+
assert_equal 0, @err.string.size, @err.string
|
|
87
|
+
assert_equal 200, resp.status
|
|
88
|
+
|
|
89
|
+
# GET succeeds with redirect
|
|
90
|
+
resp = @req.get("/testdom/key", @opts)
|
|
91
|
+
assert_equal 0, @err.string.size, @err.string
|
|
92
|
+
assert_equal 302, resp.status
|
|
93
|
+
assert_equal "HELLO", open(resp["Location"]).read
|
|
94
|
+
|
|
95
|
+
# PUT to good domain and REMOTE_USER succeeds succeeds with 201
|
|
96
|
+
opts = @opts.merge(:input => StringIO.new("HELLO"))
|
|
97
|
+
opts["REMOTE_USER"] = "root"
|
|
98
|
+
resp = @req.put("/testdom/user_key", opts)
|
|
99
|
+
assert_equal 0, @err.string.size, @err.string
|
|
100
|
+
assert_equal 201, resp.status
|
|
101
|
+
|
|
102
|
+
# plain-text key listing succeeds
|
|
103
|
+
resp = @req.get("/testdom", @opts)
|
|
104
|
+
assert_equal 0, @err.string.size, @err.string
|
|
105
|
+
assert_equal 200, resp.status, resp.inspect
|
|
106
|
+
assert_equal 2, resp.body.split(/\n/).size
|
|
107
|
+
assert_equal("text/plain", resp.headers["Content-Type"],
|
|
108
|
+
"content-type is text/plain")
|
|
109
|
+
keys = resp.body.split(/\n/)
|
|
110
|
+
assert_match(/\Akey\|5|[12]\z/ ,keys[0])
|
|
111
|
+
assert_match(/\Auser_key\|5|[12]\z/ ,keys[1])
|
|
112
|
+
|
|
113
|
+
# json key listing succeeds
|
|
114
|
+
resp = @req.get("/testdom", @opts.merge(json))
|
|
115
|
+
assert_equal 0, @err.string.size, @err.string
|
|
116
|
+
assert_equal 200, resp.status, resp.inspect
|
|
117
|
+
keys = JSON.parse(resp.body)
|
|
118
|
+
assert_equal 2, keys.size
|
|
119
|
+
assert_equal("application/json", resp.headers["Content-Type"],
|
|
120
|
+
"content-type is application/json")
|
|
121
|
+
assert_equal "key", keys[0][0]
|
|
122
|
+
assert_equal 5, keys[0][1]
|
|
123
|
+
assert_operator keys[0][2], :>=, 1
|
|
124
|
+
assert_operator keys[0][2], :<=, 2
|
|
125
|
+
assert_equal "user_key", keys[1][0]
|
|
126
|
+
assert_equal 5, keys[1][1]
|
|
127
|
+
assert_operator keys[1][2], :>=, 1
|
|
128
|
+
assert_operator keys[1][2], :<=, 2
|
|
129
|
+
|
|
130
|
+
# HEAD of json listing succeeds
|
|
131
|
+
resp = @req.head("/testdom", @opts.merge(json))
|
|
132
|
+
assert_equal("application/json", resp.headers["Content-Type"],
|
|
133
|
+
"content-type is application/json")
|
|
134
|
+
assert_equal 200, resp.status
|
|
135
|
+
assert_equal "", resp.body
|
|
136
|
+
|
|
137
|
+
# HEAD of text listing succeeds
|
|
138
|
+
resp = @req.head("/testdom", @opts)
|
|
139
|
+
assert_equal("text/plain", resp.headers["Content-Type"],
|
|
140
|
+
"content-type is text/plain")
|
|
141
|
+
assert_equal 200, resp.status
|
|
142
|
+
assert_equal "", resp.body
|
|
143
|
+
|
|
144
|
+
# DELETE succeeds
|
|
145
|
+
resp = @req.delete("/testdom/user_key", @opts)
|
|
146
|
+
assert_equal 0, @err.string.size, @err.string
|
|
147
|
+
assert_equal 204, resp.status, resp.inspect
|
|
148
|
+
|
|
149
|
+
# DELETE again fails
|
|
150
|
+
resp = @req.delete("/testdom/user_key", @opts)
|
|
151
|
+
assert_equal 0, @err.string.size, @err.string
|
|
152
|
+
assert_equal 404, resp.status, resp.inspect
|
|
153
|
+
|
|
154
|
+
# unauthed PUT fails to overwrite
|
|
155
|
+
opts = @opts.merge(:input => StringIO.new("FAIL"))
|
|
156
|
+
resp = @req.put("/testdom/key", opts)
|
|
157
|
+
assert_equal 0, @err.string.size, @err.string
|
|
158
|
+
assert_equal 403, resp.status, resp.inspect
|
|
159
|
+
|
|
160
|
+
# GET returns unchanged file
|
|
161
|
+
resp = @req.get("/testdom/key", @opts)
|
|
162
|
+
assert_equal 0, @err.string.size, @err.string
|
|
163
|
+
assert_equal 302, resp.status
|
|
164
|
+
assert_equal "HELLO", open(resp["Location"]).read
|
|
165
|
+
|
|
166
|
+
# PUT succeeds with overwrite
|
|
167
|
+
opts = @opts.merge(:input => StringIO.new("BLAH"))
|
|
168
|
+
opts["REMOTE_USER"] = "root"
|
|
169
|
+
opts["HTTP_X_OMGF_FORCE"] = "true"
|
|
170
|
+
resp = @req.put("/testdom/key", opts)
|
|
171
|
+
assert_equal 0, @err.string.size, @err.string
|
|
172
|
+
assert_equal 204, resp.status, resp.inspect
|
|
173
|
+
|
|
174
|
+
# GET succeeds with redirect
|
|
175
|
+
resp = @req.get("/testdom/key", @opts)
|
|
176
|
+
assert_equal 0, @err.string.size, @err.string
|
|
177
|
+
assert_equal 302, resp.status
|
|
178
|
+
assert_equal "BLAH", open(resp["Location"]).read
|
|
179
|
+
|
|
180
|
+
# HEAD shows size and metadata
|
|
181
|
+
resp = @req.head("/testdom/key", @opts)
|
|
182
|
+
assert_equal 0, @err.string.size, @err.string
|
|
183
|
+
assert_equal 200, resp.status, resp.inspect
|
|
184
|
+
assert_equal "4", resp["Content-Length"]
|
|
185
|
+
assert_equal "BLAH", open(resp["X-URL-0"]).read
|
|
186
|
+
|
|
187
|
+
# wait for replication
|
|
188
|
+
if ENV["EXPENSIVE"]
|
|
189
|
+
200.times do
|
|
190
|
+
resp["X-URL-1"] and break
|
|
191
|
+
sleep 0.1
|
|
192
|
+
resp = @req.head("/testdom/key", @opts)
|
|
193
|
+
end
|
|
194
|
+
assert_kind_of String, resp["X-URL-1"]
|
|
195
|
+
assert_equal "BLAH", open(resp["X-URL-1"]).read
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
reproxy_test
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def reproxy_test
|
|
202
|
+
@app.instance_variable_set(:@reproxy_path, "/reproxy")
|
|
203
|
+
opts = @opts.merge("HTTP_X_OMGF_REPROXY" => "1")
|
|
204
|
+
resp = @req.get("/testdom/key", opts)
|
|
205
|
+
assert_equal 0, @err.string.size, @err.string
|
|
206
|
+
t = Time.parse(resp["X-Redirect-Last-Modified"])
|
|
207
|
+
assert_equal "\"#{t.to_i}\"", resp["ETag"]
|
|
208
|
+
assert_equal "application/octet-stream", resp["X-Redirect-Content-Type"]
|
|
209
|
+
assert_equal "BLAH", open(resp["Location"]).read
|
|
210
|
+
assert_equal "/reproxy", resp["X-Accel-Redirect"]
|
|
211
|
+
assert_nil resp.original_headers["Content-Type"], resp.inspect
|
|
212
|
+
assert_nil resp["Last-Modified"]
|
|
213
|
+
|
|
214
|
+
# no point in redirecting HEAD requests
|
|
215
|
+
resp = @req.head("/testdom/key", opts)
|
|
216
|
+
assert_equal 0, @err.string.size, @err.string
|
|
217
|
+
t = Time.parse(resp["Last-Modified"])
|
|
218
|
+
assert_equal "\"#{t.to_i}\"", resp["ETag"]
|
|
219
|
+
assert_nil resp["X-Accel-Redirect"]
|
|
220
|
+
assert_equal "4", resp["Content-Length"]
|
|
221
|
+
assert_equal "application/octet-stream",
|
|
222
|
+
resp.original_headers["Content-Type"], resp.inspect
|
|
223
|
+
|
|
224
|
+
# explicit filename
|
|
225
|
+
resp = @req.head("/testdom/key?inline=foo.txt", opts)
|
|
226
|
+
assert_equal "inline; filename=foo.txt", resp["Content-Disposition"]
|
|
227
|
+
resp = @req.head("/testdom/key?attachment=foo.txt", opts)
|
|
228
|
+
assert_equal "attachment; filename=foo.txt", resp["Content-Disposition"]
|
|
229
|
+
resp = @req.head("/testdom/key?filename=foo.txt", opts)
|
|
230
|
+
assert_equal "attachment; filename=foo.txt", resp["Content-Disposition"]
|
|
231
|
+
|
|
232
|
+
@app.instance_variable_set(:@reproxy_path, nil)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def teardown
|
|
236
|
+
teardown_mogilefs
|
|
237
|
+
end
|
|
238
|
+
end
|