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.
@@ -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
@@ -19,7 +19,7 @@ class MogileFS::Pool
19
19
  object = @klass.new(*@args)
20
20
  @objects << object
21
21
  end
22
- return object
22
+ object
23
23
  end
24
24
 
25
25
  def put(o)
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
- def sysrwloop(io_rd, io_wr)
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
- io_wr.sync = true
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
- loop do
18
- w = io_wr.syswrite(buf)
19
- copied += w
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
@@ -0,0 +1,2 @@
1
+ *.log
2
+ *.log+
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
- class TCPSocket
110
-
111
- class << self
112
-
113
- attr_accessor :connections
114
- attr_accessor :sockets
115
-
116
- alias old_new new
117
-
118
- def new(host, port)
119
- raise Errno::ECONNREFUSED if @sockets.empty?
120
- @connections << [host, port]
121
- @sockets.pop
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
- alias open new
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
- @tempdir = File.join Dir.tmpdir, "test_mogilefs_#{$$}"
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
- TCPSocket.sockets = []
145
- TCPSocket.connections = []
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 teardown
149
- FileUtils.rm_rf @tempdir
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
+