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