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
@@ -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
|
+
|