mogilefs-client 1.3.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|