mogilefs-client 1.3.1 → 2.0.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/.gitignore +2 -0
- data/GNUmakefile +32 -0
- data/History.txt +23 -0
- data/LICENSE.txt +1 -0
- data/Manifest.txt +12 -1
- data/README.txt +22 -13
- data/bin/mog +37 -6
- data/lib/mogilefs.rb +19 -5
- data/lib/mogilefs/admin.rb +27 -34
- data/lib/mogilefs/backend.rb +106 -39
- data/lib/mogilefs/bigfile.rb +153 -0
- data/lib/mogilefs/client.rb +1 -5
- data/lib/mogilefs/httpfile.rb +65 -71
- data/lib/mogilefs/mogilefs.rb +102 -102
- data/lib/mogilefs/mysql.rb +166 -0
- data/lib/mogilefs/network.rb +64 -0
- data/lib/mogilefs/pool.rb +1 -1
- data/lib/mogilefs/util.rb +140 -9
- data/test/.gitignore +2 -0
- data/test/aggregate.rb +13 -0
- data/test/setup.rb +72 -91
- data/test/test_admin.rb +2 -2
- data/test/test_backend.rb +100 -38
- data/test/test_bigfile.rb +48 -0
- data/test/test_client.rb +7 -2
- data/test/test_db_backend.rb +73 -0
- data/test/test_mogilefs.rb +287 -107
- data/test/test_mysql.rb +94 -0
- data/test/test_network.rb +27 -0
- data/test/test_util.rb +59 -0
- metadata +22 -6
- data/lib/mogilefs/nfsfile.rb +0 -81
data/lib/mogilefs/backend.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'thread'
|
3
1
|
require 'mogilefs'
|
2
|
+
require 'mogilefs/util'
|
3
|
+
require 'thread'
|
4
4
|
|
5
5
|
##
|
6
6
|
# MogileFS::Backend communicates with the MogileFS trackers.
|
@@ -18,17 +18,29 @@ class MogileFS::Backend
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
BACKEND_ERRORS = {}
|
22
|
+
|
23
|
+
# this converts an error code from a mogilefsd tracker to an exception:
|
24
|
+
#
|
25
|
+
# Examples of some exceptions that get created:
|
26
|
+
# class AfterMismatchError < MogileFS::Error; end
|
27
|
+
# class DomainNotFoundError < MogileFS::Error; end
|
28
|
+
# class InvalidCharsError < MogileFS::Error; end
|
29
|
+
def self.add_error(err_snake)
|
30
|
+
err_camel = err_snake.gsub(/(?:^|_)([a-z])/) { $1.upcase } << 'Error'
|
31
|
+
unless self.const_defined?(err_camel)
|
32
|
+
self.class_eval("class #{err_camel} < MogileFS::Error; end")
|
33
|
+
end
|
34
|
+
BACKEND_ERRORS[err_snake] = self.const_get(err_camel)
|
35
|
+
end
|
36
|
+
|
21
37
|
##
|
22
38
|
# The last error
|
23
|
-
#--
|
24
|
-
# TODO Use Exceptions
|
25
39
|
|
26
40
|
attr_reader :lasterr
|
27
41
|
|
28
42
|
##
|
29
43
|
# The string attached to the last error
|
30
|
-
#--
|
31
|
-
# TODO Use Exceptions
|
32
44
|
|
33
45
|
attr_reader :lasterrstr
|
34
46
|
|
@@ -61,8 +73,10 @@ class MogileFS::Backend
|
|
61
73
|
# Closes this backend's socket.
|
62
74
|
|
63
75
|
def shutdown
|
64
|
-
|
65
|
-
|
76
|
+
if @socket
|
77
|
+
@socket.close rescue nil # ignore errors
|
78
|
+
@socket = nil
|
79
|
+
end
|
66
80
|
end
|
67
81
|
|
68
82
|
# MogileFS::MogileFS commands
|
@@ -76,7 +90,7 @@ class MogileFS::Backend
|
|
76
90
|
add_command :list_keys
|
77
91
|
|
78
92
|
# MogileFS::Backend commands
|
79
|
-
|
93
|
+
|
80
94
|
add_command :get_hosts
|
81
95
|
add_command :get_devices
|
82
96
|
add_command :list_fids
|
@@ -92,14 +106,44 @@ class MogileFS::Backend
|
|
92
106
|
add_command :delete_host
|
93
107
|
add_command :set_state
|
94
108
|
|
95
|
-
|
109
|
+
# Errors copied from MogileFS/Worker/Query.pm
|
110
|
+
add_error 'dup'
|
111
|
+
add_error 'after_mismatch'
|
112
|
+
add_error 'bad_params'
|
113
|
+
add_error 'class_exists'
|
114
|
+
add_error 'class_has_files'
|
115
|
+
add_error 'class_not_found'
|
116
|
+
add_error 'db'
|
117
|
+
add_error 'domain_has_files'
|
118
|
+
add_error 'domain_exists'
|
119
|
+
add_error 'domain_not_empty'
|
120
|
+
add_error 'domain_not_found'
|
121
|
+
add_error 'failure'
|
122
|
+
add_error 'host_exists'
|
123
|
+
add_error 'host_mismatch'
|
124
|
+
add_error 'host_not_empty'
|
125
|
+
add_error 'host_not_found'
|
126
|
+
add_error 'invalid_chars'
|
127
|
+
add_error 'invalid_checker_level'
|
128
|
+
add_error 'invalid_mindevcount'
|
129
|
+
add_error 'key_exists'
|
130
|
+
add_error 'no_class'
|
131
|
+
add_error 'no_devices'
|
132
|
+
add_error 'no_domain'
|
133
|
+
add_error 'no_host'
|
134
|
+
add_error 'no_ip'
|
135
|
+
add_error 'no_key'
|
136
|
+
add_error 'no_port'
|
137
|
+
add_error 'none_match'
|
138
|
+
add_error 'plugin_aborted'
|
139
|
+
add_error 'state_too_high'
|
140
|
+
add_error 'unknown_command'
|
141
|
+
add_error 'unknown_host'
|
142
|
+
add_error 'unknown_key'
|
143
|
+
add_error 'unknown_state'
|
144
|
+
add_error 'unreg_domain'
|
96
145
|
|
97
|
-
|
98
|
-
# Returns a new TCPSocket connected to +port+ on +host+.
|
99
|
-
|
100
|
-
def connect_to(host, port)
|
101
|
-
return TCPSocket.new(host, port)
|
102
|
-
end
|
146
|
+
private unless defined? $TESTING
|
103
147
|
|
104
148
|
##
|
105
149
|
# Performs the +cmd+ request with +args+.
|
@@ -111,17 +155,18 @@ class MogileFS::Backend
|
|
111
155
|
begin
|
112
156
|
bytes_sent = socket.send request, 0
|
113
157
|
rescue SystemCallError
|
114
|
-
|
115
|
-
raise
|
158
|
+
shutdown
|
159
|
+
raise MogileFS::UnreachableBackendError
|
116
160
|
end
|
117
161
|
|
118
162
|
unless bytes_sent == request.length then
|
119
|
-
raise
|
163
|
+
raise MogileFS::RequestTruncatedError,
|
164
|
+
"request truncated (sent #{bytes_sent} expected #{request.length})"
|
120
165
|
end
|
121
166
|
|
122
167
|
readable?
|
123
168
|
|
124
|
-
|
169
|
+
parse_response(socket.gets)
|
125
170
|
end
|
126
171
|
end
|
127
172
|
|
@@ -129,7 +174,15 @@ class MogileFS::Backend
|
|
129
174
|
# Makes a new request string for +cmd+ and +args+.
|
130
175
|
|
131
176
|
def make_request(cmd, args)
|
132
|
-
|
177
|
+
"#{cmd} #{url_encode args}\r\n"
|
178
|
+
end
|
179
|
+
|
180
|
+
# this converts an error code from a mogilefsd tracker to an exception
|
181
|
+
# Most of these exceptions should already be defined, but since the
|
182
|
+
# MogileFS server code is liable to change and we may not always be
|
183
|
+
# able to keep up with the changes
|
184
|
+
def error(err_snake)
|
185
|
+
BACKEND_ERRORS[err_snake] || self.class.add_error(err_snake)
|
133
186
|
end
|
134
187
|
|
135
188
|
##
|
@@ -140,25 +193,41 @@ class MogileFS::Backend
|
|
140
193
|
if line =~ /^ERR\s+(\w+)\s*(.*)/ then
|
141
194
|
@lasterr = $1
|
142
195
|
@lasterrstr = $2 ? url_unescape($2) : nil
|
196
|
+
raise error(@lasterr)
|
143
197
|
return nil
|
144
198
|
end
|
145
199
|
|
146
200
|
return url_decode($1) if line =~ /^OK\s+\d*\s*(\S*)/
|
147
201
|
|
148
|
-
raise
|
202
|
+
raise MogileFS::InvalidResponseError,
|
203
|
+
"Invalid response from server: #{line.inspect}"
|
149
204
|
end
|
150
205
|
|
151
206
|
##
|
152
207
|
# Raises if the socket does not become readable in +@timeout+ seconds.
|
153
208
|
|
154
209
|
def readable?
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
210
|
+
timeleft = @timeout
|
211
|
+
peer = nil
|
212
|
+
loop do
|
213
|
+
t0 = Time.now
|
214
|
+
found = IO.select([socket], nil, nil, timeleft)
|
215
|
+
return true if found && found[0]
|
216
|
+
timeleft -= (Time.now - t0)
|
217
|
+
|
218
|
+
if timeleft < 0
|
219
|
+
peer = @socket ? "#{@socket.mogilefs_peername} " : nil
|
220
|
+
|
221
|
+
# we DO NOT want the response we timed out waiting for, to crop up later
|
222
|
+
# on, on the same socket, intersperesed with a subsequent request! so,
|
223
|
+
# we close the socket if it times out like this
|
224
|
+
shutdown
|
225
|
+
raise MogileFS::UnreadableSocketError, "#{peer}never became readable"
|
226
|
+
break
|
227
|
+
end
|
228
|
+
shutdown
|
160
229
|
end
|
161
|
-
|
230
|
+
false
|
162
231
|
end
|
163
232
|
|
164
233
|
##
|
@@ -173,8 +242,8 @@ class MogileFS::Backend
|
|
173
242
|
next if @dead.include? host and @dead[host] > now - 5
|
174
243
|
|
175
244
|
begin
|
176
|
-
@socket =
|
177
|
-
rescue SystemCallError
|
245
|
+
@socket = Socket.mogilefs_new(*(host.split(/:/) << @timeout))
|
246
|
+
rescue SystemCallError, MogileFS::Timeout
|
178
247
|
@dead[host] = now
|
179
248
|
next
|
180
249
|
end
|
@@ -182,25 +251,23 @@ class MogileFS::Backend
|
|
182
251
|
return @socket
|
183
252
|
end
|
184
253
|
|
185
|
-
raise
|
254
|
+
raise MogileFS::UnreachableBackendError
|
186
255
|
end
|
187
256
|
|
188
257
|
##
|
189
258
|
# Turns a url params string into a Hash.
|
190
259
|
|
191
260
|
def url_decode(str)
|
192
|
-
|
193
|
-
pair.split(
|
194
|
-
|
195
|
-
|
196
|
-
return Hash[*pairs.flatten]
|
261
|
+
Hash[*(str.split(/&/).map { |pair|
|
262
|
+
pair.split(/=/, 2).map { |x| url_unescape(x) }
|
263
|
+
} ).flatten]
|
197
264
|
end
|
198
265
|
|
199
266
|
##
|
200
267
|
# Turns a Hash (or Array of pairs) into a url params string.
|
201
268
|
|
202
269
|
def url_encode(params)
|
203
|
-
|
270
|
+
params.map do |k,v|
|
204
271
|
"#{url_escape k.to_s}=#{url_escape v.to_s}"
|
205
272
|
end.join("&")
|
206
273
|
end
|
@@ -209,14 +276,14 @@ class MogileFS::Backend
|
|
209
276
|
# Escapes naughty URL characters.
|
210
277
|
|
211
278
|
def url_escape(str)
|
212
|
-
|
279
|
+
str.gsub(/([^\w\,\-.\/\\\: ])/) { "%%%02x" % $1[0] }.tr(' ', '+')
|
213
280
|
end
|
214
281
|
|
215
282
|
##
|
216
283
|
# Unescapes naughty URL characters.
|
217
284
|
|
218
285
|
def url_unescape(str)
|
219
|
-
|
286
|
+
str.gsub(/%([a-f0-9][a-f0-9])/i) { [$1.to_i(16)].pack 'C' }.tr('+', ' ')
|
220
287
|
end
|
221
288
|
|
222
289
|
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'uri'
|
4
|
+
require 'mogilefs/util'
|
5
|
+
|
6
|
+
module MogileFS::Bigfile
|
7
|
+
GZIP_HEADER = "\x1f\x8b".freeze # mogtool(1) has this
|
8
|
+
# VALID_TYPES = %w(file tarball partition).map { |x| x.freeze }.freeze
|
9
|
+
|
10
|
+
# returns a big_info hash if successful
|
11
|
+
def bigfile_stat(key)
|
12
|
+
parse_info(get_file_data(key))
|
13
|
+
end
|
14
|
+
|
15
|
+
# returns total bytes written and the big_info hash if successful, raises an
|
16
|
+
# exception if not wr_io is expected to be an IO-like object capable of
|
17
|
+
# receiving the syswrite method.
|
18
|
+
def bigfile_write(key, wr_io, opts = { :verify => false })
|
19
|
+
info = bigfile_stat(key)
|
20
|
+
zi = nil
|
21
|
+
md5 = opts[:verify] ? Digest::MD5.new : nil
|
22
|
+
total = 0
|
23
|
+
|
24
|
+
# we only decode raw zlib deflated streams that mogtool (unfortunately)
|
25
|
+
# generates. tarballs and gzip(1) are up to to the application to decrypt.
|
26
|
+
filter = Proc.new do |buf|
|
27
|
+
if zi == nil
|
28
|
+
if info[:compressed] && info[:type] == 'file' &&
|
29
|
+
buf.length >= 2 && buf[0,2] != GZIP_HEADER
|
30
|
+
zi = Zlib::Inflate.new
|
31
|
+
|
32
|
+
# mogtool(1) seems to have a bug that causes it to generate bogus
|
33
|
+
# MD5s if zlib deflate is used. Don't trust those MD5s for now...
|
34
|
+
md5 = nil
|
35
|
+
else
|
36
|
+
zi = false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
buf ||= ''
|
40
|
+
if zi
|
41
|
+
zi.inflate(buf)
|
42
|
+
else
|
43
|
+
md5 << buf
|
44
|
+
buf
|
45
|
+
end
|
46
|
+
end if (info[:compressed] || md5)
|
47
|
+
|
48
|
+
info[:parts].each_with_index do |part,part_nr|
|
49
|
+
next if part_nr == 0 # info[:parts][0] is always empty
|
50
|
+
uris = verify_uris(part[:paths].map { |path| URI.parse(path) })
|
51
|
+
if uris.empty?
|
52
|
+
# part[:paths] may not be valid anymore due to rebalancing, however we
|
53
|
+
# can get_keys on key,<part_nr> and retry paths if all paths fail
|
54
|
+
part[:paths] = get_paths("#{key.gsub(/^big_info:/, '')},#{part_nr}")
|
55
|
+
uris = verify_uris(part[:paths].map { |path| URI.parse(path) })
|
56
|
+
raise MogileFS::Backend::NoDevices if uris.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
sock = http_get_sock(uris[0])
|
60
|
+
md5.reset if md5
|
61
|
+
w = sysrwloop(sock, wr_io, filter)
|
62
|
+
|
63
|
+
if md5 && md5.hexdigest != part[:md5]
|
64
|
+
raise MogileFS::ChecksumMismatchError, "#{md5} != #{part[:md5]}"
|
65
|
+
end
|
66
|
+
total += w
|
67
|
+
end
|
68
|
+
|
69
|
+
syswrite_full(wr_io, zi.finish) if zi
|
70
|
+
|
71
|
+
[ total, info ]
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
include MogileFS::Util
|
77
|
+
|
78
|
+
##
|
79
|
+
# parses the contents of a _big_info: string or IO object
|
80
|
+
def parse_info(info = '')
|
81
|
+
rv = { :parts => [] }
|
82
|
+
info.each_line do |line|
|
83
|
+
line.chomp!
|
84
|
+
case line
|
85
|
+
when /^(des|type|filename)\s+(.+)$/
|
86
|
+
rv[$1.to_sym] = $2
|
87
|
+
when /^compressed\s+([01])$/
|
88
|
+
rv[:compressed] = ($1 == '1')
|
89
|
+
when /^(chunks|size)\s+(\d+)$/
|
90
|
+
rv[$1.to_sym] = $2.to_i
|
91
|
+
when /^part\s+(\d+)\s+bytes=(\d+)\s+md5=(.+)\s+paths:\s+(.+)$/
|
92
|
+
rv[:parts][$1.to_i] = {
|
93
|
+
:bytes => $2.to_i,
|
94
|
+
:md5 => $3.downcase,
|
95
|
+
:paths => $4.split(/\s*,\s*/),
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
rv
|
101
|
+
end
|
102
|
+
|
103
|
+
end # module MogileFS::Bigfile
|
104
|
+
|
105
|
+
__END__
|
106
|
+
# Copied from mogtool:
|
107
|
+
# http://code.sixapart.com/svn/mogilefs/utils/mogtool, r1221
|
108
|
+
|
109
|
+
# this is a temporary file that we delete when we're doing recording all chunks
|
110
|
+
|
111
|
+
_big_pre:<key>
|
112
|
+
|
113
|
+
starttime=UNIXTIMESTAMP
|
114
|
+
|
115
|
+
# when done, we write the _info file and delete the _pre.
|
116
|
+
|
117
|
+
_big_info:<key>
|
118
|
+
|
119
|
+
des Cow's ljdb backup as of 2004-11-17
|
120
|
+
type { partition, file, tarball }
|
121
|
+
compressed {0, 1}
|
122
|
+
filename ljbinlog.305.gz
|
123
|
+
partblocks 234324324324
|
124
|
+
|
125
|
+
|
126
|
+
part 1 <bytes> <md5hex>
|
127
|
+
part 2 <bytes> <md5hex>
|
128
|
+
part 3 <bytes> <md5hex>
|
129
|
+
part 4 <bytes> <md5hex>
|
130
|
+
part 5 <bytes> <md5hex>
|
131
|
+
|
132
|
+
_big:<key>,<n>
|
133
|
+
_big:<key>,<n>
|
134
|
+
_big:<key>,<n>
|
135
|
+
|
136
|
+
|
137
|
+
Receipt format:
|
138
|
+
|
139
|
+
BEGIN MOGTOOL RECIEPT
|
140
|
+
type partition
|
141
|
+
des Foo
|
142
|
+
compressed foo
|
143
|
+
|
144
|
+
part 1 bytes=23423432 md5=2349823948239423984 paths: http://dev5/2/23/23/.fid, http://dev6/23/423/4/324.fid
|
145
|
+
part 1 bytes=23423432 md5=2349823948239423984 paths: http://dev5/2/23/23/.fid, http://dev6/23/423/4/324.fid
|
146
|
+
part 1 bytes=23423432 md5=2349823948239423984 paths: http://dev5/2/23/23/.fid, http://dev6/23/423/4/324.fid
|
147
|
+
part 1 bytes=23423432 md5=2349823948239423984 paths: http://dev5/2/23/23/.fid, http://dev6/23/423/4/324.fid
|
148
|
+
|
149
|
+
|
150
|
+
END RECIEPT
|
151
|
+
|
152
|
+
|
153
|
+
|
data/lib/mogilefs/client.rb
CHANGED
@@ -38,8 +38,6 @@ class MogileFS::Client
|
|
38
38
|
|
39
39
|
##
|
40
40
|
# The last error reported by the backend.
|
41
|
-
#--
|
42
|
-
# TODO use Exceptions
|
43
41
|
|
44
42
|
def err
|
45
43
|
@backend.lasterr
|
@@ -47,8 +45,6 @@ class MogileFS::Client
|
|
47
45
|
|
48
46
|
##
|
49
47
|
# The last error message reported by the backend.
|
50
|
-
#--
|
51
|
-
# TODO use Exceptions
|
52
48
|
|
53
49
|
def errstr
|
54
50
|
@backend.lasterrstr
|
@@ -58,7 +54,7 @@ class MogileFS::Client
|
|
58
54
|
# Is this a read-only client?
|
59
55
|
|
60
56
|
def readonly?
|
61
|
-
|
57
|
+
@readonly
|
62
58
|
end
|
63
59
|
|
64
60
|
end
|
data/lib/mogilefs/httpfile.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'fcntl'
|
2
|
-
require 'socket'
|
3
1
|
require 'stringio'
|
4
2
|
require 'uri'
|
5
3
|
require 'mogilefs/backend'
|
@@ -12,18 +10,23 @@ require 'mogilefs/util'
|
|
12
10
|
# You really don't want to create an HTTPFile by hand. Instead you want to
|
13
11
|
# create a new file using MogileFS::MogileFS.new_file.
|
14
12
|
#
|
15
|
-
# WARNING! HTTP mode is completely untested as I cannot make it work on
|
16
|
-
# FreeBSD. Please send patches/tests if you find bugs.
|
17
13
|
#--
|
18
14
|
# TODO dup'd content in MogileFS::NFSFile
|
19
15
|
|
20
16
|
class MogileFS::HTTPFile < StringIO
|
21
17
|
include MogileFS::Util
|
22
18
|
|
19
|
+
class EmptyResponseError < MogileFS::Error; end
|
20
|
+
class BadResponseError < MogileFS::Error; end
|
21
|
+
class UnparseableResponseError < MogileFS::Error; end
|
22
|
+
class NoStorageNodesError < MogileFS::Error
|
23
|
+
def message; 'Unable to open socket to storage node'; end
|
24
|
+
end
|
25
|
+
|
23
26
|
##
|
24
|
-
# The
|
27
|
+
# The URI this file will be stored to.
|
25
28
|
|
26
|
-
attr_reader :
|
29
|
+
attr_reader :uri
|
27
30
|
|
28
31
|
##
|
29
32
|
# The key for this file. This key won't represent a real file until you've
|
@@ -37,9 +40,9 @@ class MogileFS::HTTPFile < StringIO
|
|
37
40
|
attr_reader :class
|
38
41
|
|
39
42
|
##
|
40
|
-
# The
|
43
|
+
# The big_io name in case we have file > 256M
|
41
44
|
|
42
|
-
attr_accessor :
|
45
|
+
attr_accessor :big_io
|
43
46
|
|
44
47
|
##
|
45
48
|
# Works like File.open. Use MogileFS::MogileFS#new_file instead of this
|
@@ -61,93 +64,84 @@ class MogileFS::HTTPFile < StringIO
|
|
61
64
|
# Creates a new HTTPFile with MogileFS-specific data. Use
|
62
65
|
# MogileFS::MogileFS#new_file instead of this method.
|
63
66
|
|
64
|
-
def initialize(mg, fid,
|
67
|
+
def initialize(mg, fid, klass, key, dests, content_length)
|
65
68
|
super ''
|
66
69
|
@mg = mg
|
67
70
|
@fid = fid
|
68
|
-
@
|
69
|
-
@devid = devid
|
71
|
+
@uri = @devid = nil
|
70
72
|
@klass = klass
|
71
73
|
@key = key
|
72
|
-
@
|
74
|
+
@big_io = nil
|
73
75
|
|
74
|
-
@dests = dests
|
76
|
+
@dests = dests
|
75
77
|
@tried = {}
|
76
78
|
|
77
79
|
@socket = nil
|
78
80
|
end
|
79
81
|
|
80
82
|
##
|
81
|
-
#
|
82
|
-
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
# Writes an HTTP PUT request to +sock+ to upload the file and
|
84
|
+
# returns file size if the socket finished writing
|
85
|
+
def upload(devid, uri)
|
86
|
+
file_size = length
|
87
|
+
sock = Socket.mogilefs_new(uri.host, uri.port)
|
88
|
+
sock.mogilefs_tcp_cork = true
|
89
|
+
|
90
|
+
if @big_io
|
88
91
|
# Don't try to run out of memory
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
File.open(@big_io) do |fp|
|
93
|
+
file_size = fp.stat.size
|
94
|
+
fp.sync = true
|
95
|
+
syswrite_full(sock, "PUT #{uri.request_uri} HTTP/1.0\r\n" \
|
96
|
+
"Content-Length: #{file_size}\r\n\r\n")
|
97
|
+
sysrwloop(fp, sock)
|
98
|
+
end
|
94
99
|
else
|
95
|
-
|
100
|
+
syswrite_full(sock, "PUT #{uri.request_uri} HTTP/1.0\r\n" \
|
101
|
+
"Content-Length: #{length}\r\n\r\n#{string}")
|
96
102
|
end
|
103
|
+
sock.mogilefs_tcp_cork = false
|
104
|
+
|
105
|
+
line = sock.gets or
|
106
|
+
raise EmptyResponseError, 'Unable to read response line from server'
|
97
107
|
|
98
|
-
if
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
if line =~ %r%^HTTP/\d+\.\d+\s+(\d+)% then
|
103
|
-
status = Integer $1
|
104
|
-
case status
|
105
|
-
when 200..299 then # success!
|
106
|
-
else
|
107
|
-
raise "HTTP response status from upload: #{status}"
|
108
|
-
end
|
108
|
+
if line =~ %r%^HTTP/\d+\.\d+\s+(\d+)% then
|
109
|
+
case $1.to_i
|
110
|
+
when 200..299 then # success!
|
109
111
|
else
|
110
|
-
raise "
|
112
|
+
raise BadResponseError, "HTTP response status from upload: #{$1}"
|
111
113
|
end
|
112
|
-
|
113
|
-
|
114
|
+
else
|
115
|
+
raise UnparseableResponseError, "Response line not understood: #{line}"
|
114
116
|
end
|
115
117
|
|
116
|
-
@mg.backend.create_close(:fid => @fid, :devid =>
|
118
|
+
@mg.backend.create_close(:fid => @fid, :devid => devid,
|
117
119
|
:domain => @mg.domain, :key => @key,
|
118
|
-
:path =>
|
119
|
-
|
120
|
-
|
121
|
-
end
|
120
|
+
:path => uri.to_s, :size => file_size)
|
121
|
+
file_size
|
122
|
+
end # def upload
|
122
123
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
@socket = TCPSocket.new @path.host, @path.port
|
141
|
-
end
|
142
|
-
|
143
|
-
def next_path
|
144
|
-
@path = nil
|
145
|
-
@dests.each do |dest|
|
146
|
-
unless @tried.include? dest then
|
147
|
-
@path = dest
|
148
|
-
return
|
124
|
+
def close
|
125
|
+
try_dests = @dests.dup
|
126
|
+
last_err = nil
|
127
|
+
|
128
|
+
loop do
|
129
|
+
devid, url = try_dests.shift
|
130
|
+
devid && url or break
|
131
|
+
|
132
|
+
uri = URI.parse(url)
|
133
|
+
begin
|
134
|
+
bytes = upload(devid, uri)
|
135
|
+
@devid, @uri = devid, uri
|
136
|
+
return bytes
|
137
|
+
rescue SystemCallError, Errno::ECONNREFUSED, MogileFS::Timeout,
|
138
|
+
EmptyResponseError, BadResponseError,
|
139
|
+
UnparseableResponseError => err
|
140
|
+
last_err = @tried[url] = err
|
149
141
|
end
|
150
142
|
end
|
143
|
+
|
144
|
+
raise last_err ? last_err : NoStorageNodesError
|
151
145
|
end
|
152
146
|
|
153
147
|
end
|