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
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'mogilefs'
|
2
|
+
require 'socket'
|
3
|
+
require 'mogilefs/util'
|
4
|
+
|
5
|
+
module MogileFS::Network
|
6
|
+
# given an array of URIs, verify that at least one of them is accessible
|
7
|
+
# with the expected HTTP code within the timeout period (in seconds).
|
8
|
+
def verify_uris(uris = [], expect = '200', timeout = 2.00)
|
9
|
+
uri_socks = {}
|
10
|
+
ok_uris = []
|
11
|
+
sockets = []
|
12
|
+
|
13
|
+
# first, we asynchronously connect to all of them
|
14
|
+
uris.each do |uri|
|
15
|
+
sock = Socket.mogilefs_new_nonblock(uri.host, uri.port) rescue next
|
16
|
+
uri_socks[sock] = uri
|
17
|
+
end
|
18
|
+
|
19
|
+
# wait for at least one of them to finish connecting and send
|
20
|
+
# HTTP requests to the connected ones
|
21
|
+
begin
|
22
|
+
t0 = Time.now
|
23
|
+
r = IO.select(nil, uri_socks.keys, nil, timeout > 0 ? timeout : 0)
|
24
|
+
timeout -= (Time.now - t0)
|
25
|
+
break unless r && r[1]
|
26
|
+
r[1].each do |sock|
|
27
|
+
begin
|
28
|
+
# we don't about short/interrupted writes here, if the following
|
29
|
+
# request fails or blocks then the server is flat-out hopeless
|
30
|
+
sock.syswrite "HEAD #{uri_socks[sock].request_uri} HTTP/1.0\r\n\r\n"
|
31
|
+
sockets << sock
|
32
|
+
rescue
|
33
|
+
sock.close rescue nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end until sockets[0] || timeout < 0
|
37
|
+
|
38
|
+
# Await a response from the sockets we had written to, we only need one
|
39
|
+
# valid response, but we'll take more if they return simultaneously
|
40
|
+
if sockets[0]
|
41
|
+
begin
|
42
|
+
t0 = Time.now
|
43
|
+
r = IO.select(sockets, nil, nil, timeout > 0 ? timeout : 0)
|
44
|
+
timeout -= (Time.now - t0)
|
45
|
+
break unless r && r[0]
|
46
|
+
r[0].each do |sock|
|
47
|
+
buf = sock.recv_nonblock(128, Socket::MSG_PEEK) rescue next
|
48
|
+
if buf && /\AHTTP\/[\d\.]+ #{expect} / =~ buf
|
49
|
+
ok_uris << uri_socks.delete(sock)
|
50
|
+
sock.close rescue nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end until ok_uris[0] || timeout < 0
|
55
|
+
|
56
|
+
ok_uris
|
57
|
+
ensure
|
58
|
+
uri_socks.keys.each { |sock| sock.close rescue nil }
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
include MogileFS::Util
|
63
|
+
|
64
|
+
end # module MogileFS::Network
|
data/lib/mogilefs/pool.rb
CHANGED
data/lib/mogilefs/util.rb
CHANGED
@@ -1,30 +1,161 @@
|
|
1
|
+
require 'mogilefs'
|
2
|
+
require 'socket'
|
3
|
+
|
1
4
|
module MogileFS::Util
|
2
5
|
|
3
6
|
CHUNK_SIZE = 65536
|
4
7
|
|
5
8
|
# for copying large files while avoiding GC thrashing as much as possible
|
6
|
-
|
9
|
+
# writes the contents of io_rd into io_wr, running through filter if
|
10
|
+
# it is a Proc object. The filter proc must respond to a string
|
11
|
+
# argument (and return a string) and to nil (possibly returning a
|
12
|
+
# string or nil). This can be used to filter I/O through an
|
13
|
+
# Zlib::Inflate or Digest::MD5 object
|
14
|
+
def sysrwloop(io_rd, io_wr, filter = nil)
|
7
15
|
copied = 0
|
8
16
|
# avoid making sysread repeatedly allocate a new String
|
9
17
|
# This is not well-documented, but both read/sysread can take
|
10
18
|
# an optional second argument to use as the buffer to avoid
|
11
19
|
# GC overhead of creating new strings in a loop
|
12
20
|
buf = ' ' * CHUNK_SIZE # preallocate to avoid GC thrashing
|
13
|
-
|
21
|
+
io_rd.flush rescue nil # flush may be needed for sockets/pipes, be safe
|
22
|
+
io_wr.flush
|
23
|
+
io_rd.sync = io_wr.sync = true
|
14
24
|
loop do
|
15
|
-
begin
|
25
|
+
b = begin
|
16
26
|
io_rd.sysread(CHUNK_SIZE, buf)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
break if w == buf.size
|
21
|
-
buf = buf[w..-1]
|
22
|
-
end
|
27
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
28
|
+
IO.select([io_rd], nil, nil, nil)
|
29
|
+
retry
|
23
30
|
rescue EOFError
|
24
31
|
break
|
25
32
|
end
|
33
|
+
b = filter.call(b) if filter
|
34
|
+
copied += syswrite_full(io_wr, b)
|
35
|
+
end
|
36
|
+
|
37
|
+
# filter must take nil as a possible argument to indicate EOF
|
38
|
+
if filter
|
39
|
+
b = filter.call(nil)
|
40
|
+
copied += syswrite_full(io_wr, b) if b && b.length > 0
|
26
41
|
end
|
27
42
|
copied
|
28
43
|
end # sysrwloop
|
29
44
|
|
45
|
+
# writes the contents of buf to io_wr in full w/o blocking
|
46
|
+
def syswrite_full(io_wr, buf, timeout = nil)
|
47
|
+
written = 0
|
48
|
+
loop do
|
49
|
+
begin
|
50
|
+
w = io_wr.syswrite(buf)
|
51
|
+
written += w
|
52
|
+
return written if w == buf.size
|
53
|
+
buf = buf[w..-1]
|
54
|
+
|
55
|
+
# a short syswrite means the next syswrite will likely block
|
56
|
+
# inside the interpreter. so force an IO.select on it so we can
|
57
|
+
# timeout there if one was specified
|
58
|
+
raise Errno::EAGAIN if timeout
|
59
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
60
|
+
t0 = Time.now if timeout
|
61
|
+
IO.select(nil, [io_wr], nil, timeout)
|
62
|
+
if timeout && ((timeout -= (Time.now - t0)) < 0)
|
63
|
+
raise MogileFS::Timeout, 'syswrite_full timeout'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
# should never get here
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
require 'timeout'
|
73
|
+
##
|
74
|
+
# Timeout error class. Subclassing it from Timeout::Error is the only
|
75
|
+
# reason we require the 'timeout' module, otherwise that module is
|
76
|
+
# broken and worthless to us.
|
77
|
+
class MogileFS::Timeout < Timeout::Error; end
|
78
|
+
|
79
|
+
class Socket
|
80
|
+
attr_accessor :mogilefs_addr, :mogilefs_connected
|
81
|
+
|
82
|
+
TCP_CORK = 3 if ! defined?(TCP_CORK) && RUBY_PLATFORM =~ /linux/
|
83
|
+
|
84
|
+
def mogilefs_tcp_cork=(set)
|
85
|
+
if defined?(TCP_CORK)
|
86
|
+
self.setsockopt(SOL_TCP, TCP_CORK, set ? 1 : 0) rescue nil
|
87
|
+
end
|
88
|
+
set
|
89
|
+
end
|
90
|
+
|
91
|
+
# Socket lacks peeraddr method of the IPSocket/TCPSocket classes
|
92
|
+
def mogilefs_peername
|
93
|
+
Socket.unpack_sockaddr_in(getpeername).reverse.map {|x| x.to_s }.join(':')
|
94
|
+
end
|
95
|
+
|
96
|
+
def mogilefs_init(host = nil, port = nil)
|
97
|
+
return true if defined?(@mogilefs_connected)
|
98
|
+
|
99
|
+
@mogilefs_addr = Socket.sockaddr_in(port, host).freeze if port && host
|
100
|
+
|
101
|
+
begin
|
102
|
+
connect_nonblock(@mogilefs_addr)
|
103
|
+
@mogilefs_connected = true
|
104
|
+
rescue Errno::EINPROGRESS
|
105
|
+
nil
|
106
|
+
rescue Errno::EISCONN
|
107
|
+
@mogilefs_connected = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class << self
|
112
|
+
|
113
|
+
# Creates a new (TCP) Socket and initiates (but does not wait for) the
|
114
|
+
# connection
|
115
|
+
def mogilefs_new_nonblock(host, port)
|
116
|
+
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
117
|
+
sock.sync = true
|
118
|
+
if defined?(Socket::TCP_NODELAY)
|
119
|
+
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
120
|
+
end
|
121
|
+
sock.mogilefs_init(host, port)
|
122
|
+
sock
|
123
|
+
end
|
124
|
+
|
125
|
+
# Like TCPSocket.new(host, port), but with an explicit timeout
|
126
|
+
# (and we don't care for local address/port we're binding to).
|
127
|
+
# This raises MogileFS::Timeout if timeout expires
|
128
|
+
def mogilefs_new(host, port, timeout = 5.0)
|
129
|
+
sock = mogilefs_new_nonblock(host, port) or return sock
|
130
|
+
|
131
|
+
while timeout > 0
|
132
|
+
t0 = Time.now
|
133
|
+
r = IO.select(nil, [sock], nil, timeout)
|
134
|
+
return sock if r && r[1] && sock.mogilefs_init
|
135
|
+
timeout -= (Time.now - t0)
|
136
|
+
end
|
137
|
+
|
138
|
+
sock.close rescue nil
|
139
|
+
raise MogileFS::Timeout, 'socket write timeout'
|
140
|
+
end
|
141
|
+
|
142
|
+
include MogileFS::Util
|
143
|
+
|
144
|
+
# Makes a request on a new TCP Socket and returns with a readble socket
|
145
|
+
# within the given timeout.
|
146
|
+
# This raises MogileFS::Timeout if timeout expires
|
147
|
+
def mogilefs_new_request(host, port, request, timeout = 5.0)
|
148
|
+
t0 = Time.now
|
149
|
+
sock = mogilefs_new(host, port, timeout)
|
150
|
+
syswrite_full(sock, request, timeout)
|
151
|
+
timeout -= (Time.now - t0)
|
152
|
+
raise MogileFS::Timeout, 'socket read timeout' if timeout < 0
|
153
|
+
r = IO.select([sock], nil, nil, timeout)
|
154
|
+
return sock if r && r[0]
|
155
|
+
raise MogileFS::Timeout, 'socket read timeout'
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
30
160
|
end
|
161
|
+
|
data/test/.gitignore
ADDED
data/test/aggregate.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/ruby -n
|
2
|
+
BEGIN { $tests = $assertions = $failures = $errors = 0 }
|
3
|
+
|
4
|
+
$_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
|
5
|
+
$tests += $1.to_i
|
6
|
+
$assertions += $2.to_i
|
7
|
+
$failures += $3.to_i
|
8
|
+
$errors += $4.to_i
|
9
|
+
|
10
|
+
END {
|
11
|
+
printf("\n%d tests, %d assertions, %d failures, %d errors\n",
|
12
|
+
$tests, $assertions, $failures, $errors)
|
13
|
+
}
|
data/test/setup.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
+
STDIN.sync = STDOUT.sync = STDERR.sync = true
|
1
2
|
require 'test/unit'
|
2
3
|
|
3
4
|
require 'fileutils'
|
4
5
|
require 'tmpdir'
|
5
6
|
require 'stringio'
|
6
7
|
|
7
|
-
require 'rubygems'
|
8
|
-
require 'test/zentest_assertions'
|
9
|
-
|
10
8
|
$TESTING = true
|
11
9
|
|
12
10
|
require 'mogilefs'
|
@@ -21,6 +19,14 @@ class FakeBackend
|
|
21
19
|
@lasterrstr = nil
|
22
20
|
end
|
23
21
|
|
22
|
+
def error(err_snake)
|
23
|
+
err_camel = err_snake.gsub(/(?:^|_)([a-z])/) { $1.upcase } << 'Error'
|
24
|
+
unless MogileFS::Backend.const_defined?(err_camel)
|
25
|
+
MogileFS::Backend.class_eval("class #{err_camel} < MogileFS::Error; end")
|
26
|
+
end
|
27
|
+
MogileFS::Backend.const_get(err_camel)
|
28
|
+
end
|
29
|
+
|
24
30
|
def method_missing(meth, *args)
|
25
31
|
meth = meth.to_s
|
26
32
|
if meth =~ /(.*)=$/ then
|
@@ -31,6 +37,7 @@ class FakeBackend
|
|
31
37
|
when Array then
|
32
38
|
@lasterr = response.first
|
33
39
|
@lasterrstr = response.last
|
40
|
+
raise error(@lasterr), @lasterrstr
|
34
41
|
return nil
|
35
42
|
end
|
36
43
|
return response
|
@@ -39,90 +46,36 @@ class FakeBackend
|
|
39
46
|
|
40
47
|
end
|
41
48
|
|
42
|
-
class FakeSocket
|
43
|
-
|
44
|
-
attr_reader :read_s
|
45
|
-
attr_reader :write_s
|
46
|
-
attr_reader :sync
|
47
|
-
|
48
|
-
def initialize(read = '', write = StringIO.new)
|
49
|
-
@read_s = read.class.method_defined?(:sysread) ? read : StringIO.new(read)
|
50
|
-
@write_s = write
|
51
|
-
@closed = false
|
52
|
-
@sync = false
|
53
|
-
end
|
54
|
-
|
55
|
-
def sync=(do_sync)
|
56
|
-
@write_s.sync = do_sync
|
57
|
-
@read_s.sync = do_sync
|
58
|
-
end
|
59
|
-
|
60
|
-
def closed?
|
61
|
-
@closed
|
62
|
-
end
|
63
|
-
|
64
|
-
def close
|
65
|
-
@closed = true
|
66
|
-
return nil
|
67
|
-
end
|
68
|
-
|
69
|
-
def gets
|
70
|
-
@read_s.gets
|
71
|
-
end
|
72
|
-
|
73
|
-
def peeraddr
|
74
|
-
['AF_INET', 6001, 'localhost', '127.0.0.1']
|
75
|
-
end
|
76
|
-
|
77
|
-
def read(bytes)
|
78
|
-
@read_s.read bytes
|
79
|
-
end
|
80
|
-
|
81
|
-
def sysread(bytes, buf = '')
|
82
|
-
@read_s.sysread bytes, buf
|
83
|
-
end
|
84
|
-
|
85
|
-
def recv_nonblock(bytes, flags = 0)
|
86
|
-
ret = @read_s.sysread(bytes)
|
87
|
-
# Ruby doesn't expose pread(2)
|
88
|
-
if (flags & Socket::MSG_PEEK) != 0
|
89
|
-
@read_s.sysseek(-ret.size, IO::SEEK_CUR)
|
90
|
-
end
|
91
|
-
ret
|
92
|
-
end
|
93
|
-
alias_method :recv, :recv_nonblock
|
94
|
-
|
95
|
-
def write(data)
|
96
|
-
@write_s.write data
|
97
|
-
end
|
98
|
-
|
99
|
-
def syswrite(data)
|
100
|
-
@write_s.syswrite data
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
49
|
class MogileFS::Client
|
106
50
|
attr_writer :readonly
|
107
51
|
end
|
108
52
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@
|
53
|
+
require 'socket'
|
54
|
+
class TempServer
|
55
|
+
attr_reader :port
|
56
|
+
|
57
|
+
def self.destroy_all!
|
58
|
+
ObjectSpace.each_object(TempServer) { |t| t.destroy! }
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(server_proc)
|
62
|
+
@thr = @port = @sock = nil
|
63
|
+
retries = 0
|
64
|
+
begin
|
65
|
+
@port = 1024 + rand(32768 - 1024)
|
66
|
+
@sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
67
|
+
@sock.bind(Socket.pack_sockaddr_in(@port, '127.0.0.1'))
|
68
|
+
@sock.listen(5)
|
69
|
+
rescue Errno::EADDRINUSE, Errno::EACCES
|
70
|
+
@sock.close rescue nil
|
71
|
+
retry if (retries += 1) < 10
|
122
72
|
end
|
73
|
+
@thr = Thread.new(@sock, @port) { |s,p| server_proc.call(s, p) }
|
74
|
+
end
|
123
75
|
|
124
|
-
|
125
|
-
|
76
|
+
def destroy!
|
77
|
+
@sock.close rescue nil
|
78
|
+
Thread.kill(@thr) rescue nil
|
126
79
|
end
|
127
80
|
|
128
81
|
end
|
@@ -132,22 +85,50 @@ class TestMogileFS < Test::Unit::TestCase
|
|
132
85
|
undef_method :default_test
|
133
86
|
|
134
87
|
def setup
|
135
|
-
@
|
136
|
-
@root = File.join @tempdir, 'root'
|
137
|
-
FileUtils.mkdir_p @root
|
138
|
-
|
139
|
-
@client = @klass.new :hosts => ['kaa:6001'], :domain => 'test',
|
140
|
-
:root => @root
|
88
|
+
@client = @klass.new :hosts => ['kaa:6001'], :domain => 'test'
|
141
89
|
@backend = FakeBackend.new
|
142
90
|
@client.instance_variable_set '@backend', @backend
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
# for our mock results
|
96
|
+
class Array
|
97
|
+
alias_method :fetch_row, :shift
|
98
|
+
end
|
99
|
+
|
100
|
+
class FakeMysql
|
101
|
+
attr_reader :expect
|
102
|
+
TBL_DEVICES = [
|
103
|
+
# devid, hostip, altip, http_port, http_get_port
|
104
|
+
[ 1, '10.0.0.1', '192.168.0.1', 7500, 7600 ],
|
105
|
+
[ 2, '10.0.0.2', '192.168.0.2', 7500, 7600 ],
|
106
|
+
[ 3, '10.0.0.3', nil, 7500, nil ],
|
107
|
+
[ 4, '10.0.0.4', nil, 7500, nil ],
|
108
|
+
]
|
109
|
+
TBL_DOMAINS = [
|
110
|
+
# dmid, namespace
|
111
|
+
[ 1, 'test' ],
|
112
|
+
[ 2, 'foo' ],
|
113
|
+
]
|
143
114
|
|
144
|
-
|
145
|
-
|
115
|
+
def initialize
|
116
|
+
@expect = []
|
117
|
+
end
|
118
|
+
|
119
|
+
def quote(str)
|
120
|
+
str.to_s.gsub(/\\/, '\&\&').gsub(/'/, "''")
|
146
121
|
end
|
147
122
|
|
148
|
-
def
|
149
|
-
|
123
|
+
def query(sql = '')
|
124
|
+
case sql
|
125
|
+
when MogileFS::Mysql::GET_DEVICES then TBL_DEVICES
|
126
|
+
when MogileFS::Mysql::GET_DOMAINS then TBL_DOMAINS
|
127
|
+
else
|
128
|
+
@expect.shift
|
129
|
+
end
|
150
130
|
end
|
151
131
|
|
152
132
|
end
|
153
133
|
|
134
|
+
|