omgf 0.0.0.GIT
Sign up to get free protection for your applications and to get access to all the features.
- 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
|