mogilefs-client 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,21 @@
1
+ = 1.3.0
2
+
3
+ * Fixed MogileFS#rename. Bug #14465 submitted by Justin Dossey.
4
+ * Removed infinite loop in MogileFS::HTTPFile#store_file. Patch #13789
5
+ submitted by Andy Lo-A-Foe.
6
+ * Made MogileFS#get_file_data timeout configurable. Bug #13490 submitted by
7
+ Andy Lo-A-Foe.
8
+ * Add MogileFS#size. Feature Request #14484 submitted by Justin Dossey.
9
+ * Fix MogileFS#get_file_data to return the data for HTTP mode. Bug #7133
10
+ submitted by John Wanko.
11
+ * New maintainer: Eric Wong
12
+ * Add `mog' command-line tool as a demo/example
13
+ * Lower memory consumption with large files
14
+ * Allow get_file_data to accept a block for large files
15
+ * Fix each_keys loop termination condition
16
+ * Apply error handling patch from Matthew Willson. Bug #15987
17
+ * Merge large file patch from Andy Lo-A-Foe. Bug #13764
18
+
1
19
  = 1.2.1
2
20
 
3
21
  * Switched to Hoe.
data/Manifest.txt CHANGED
@@ -3,6 +3,7 @@ LICENSE.txt
3
3
  Manifest.txt
4
4
  README.txt
5
5
  Rakefile
6
+ bin/mog
6
7
  lib/mogilefs.rb
7
8
  lib/mogilefs/admin.rb
8
9
  lib/mogilefs/backend.rb
@@ -11,6 +12,7 @@ lib/mogilefs/httpfile.rb
11
12
  lib/mogilefs/mogilefs.rb
12
13
  lib/mogilefs/nfsfile.rb
13
14
  lib/mogilefs/pool.rb
15
+ lib/mogilefs/util.rb
14
16
  test/setup.rb
15
17
  test/test_admin.rb
16
18
  test/test_backend.rb
data/Rakefile CHANGED
@@ -6,12 +6,15 @@ require 'mogilefs'
6
6
 
7
7
  Hoe.new 'mogilefs-client', MogileFS::VERSION do |p|
8
8
  p.rubyforge_name = 'seattlerb'
9
- p.author = 'Eric Hodel'
10
- p.email = 'drbrain@segment7.net'
9
+ p.author = [ 'Eric Wong', 'Eric Hodel' ]
10
+ p.email = 'normalperson@yhbt.net' # (Eric Wong)
11
+ # p.email = 'drbrain@segment7.net' # (Eric Hodel)
11
12
  p.summary = p.paragraphs_of('README.txt', 1).first
12
13
  p.description = p.paragraphs_of('README.txt', 9).first
13
14
  p.url = p.paragraphs_of('README.txt', 5).first
14
15
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
16
+
17
+ p.extra_dev_deps << ['ZenTest', '>= 3.6.1']
15
18
  end
16
19
 
17
20
  # vim: syntax=Ruby
data/bin/mog ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env ruby
2
+ require 'mogilefs'
3
+ require 'optparse'
4
+
5
+ trap('INT') { exit 130 }
6
+ trap('PIPE') { exit 0 }
7
+
8
+ # this is to be compatible with config files used by the Perl tools
9
+ def parse_config_file!(path, overwrite = false)
10
+ dest = {}
11
+ File.open(path).each_line do |line|
12
+ line.strip!
13
+ if /^(domain|class)\s*=\s*(\S+)/.match(line)
14
+ dest[$1.to_sym] = $2
15
+ elsif m = /^(?:trackers|hosts)\s*=\s*(.*)/.match(line)
16
+ dest[:hosts] = $1.split(/\s*,\s*/)
17
+ else
18
+ STDERR.puts "Ignored configuration line: #{line}" unless /^#/.match(line)
19
+ end
20
+ end
21
+ dest
22
+ end
23
+
24
+ # parse the default config file if one exists
25
+ def_file = File.expand_path("~/.mogilefs-client.conf")
26
+ def_cfg = File.exist?(def_file) ? parse_config_file!(def_file) : {}
27
+
28
+ # parse the command-line first, these options take precedence over all else
29
+ cli_cfg = {}
30
+ config_file = nil
31
+ ls_l = false
32
+ ls_h = false
33
+ test = {}
34
+
35
+ ARGV.options do |x|
36
+ x.banner = "Usage: #{$0} [options] <command> [<arguments>]"
37
+ x.separator ''
38
+
39
+ x.on('-c', '--config=/path/to/config',
40
+ 'config file to load') { |file| config_file = file }
41
+
42
+ x.on('-t', '--trackers=host1[,host2]', '--hosts=host1[,host2]', Array,
43
+ 'hostnames/IP addresses of trackers') do |trackers|
44
+ cli_cfg[:hosts] = trackers
45
+ end
46
+
47
+ x.on('-e', 'True if key exists') { test[:e] = true }
48
+
49
+ x.on('-C', '--class=s', 'class') { |klass| cli_cfg[:class] = klass }
50
+ x.on('-d', '--domain=s', 'domain') { |domain| cli_cfg[:domain] = domain }
51
+ x.on('-l', "long listing format (`ls' command)") { ls_l = true }
52
+ x.on('-h', '--human-readable',
53
+ "print sizes in human-readable format (`ls' command)") { ls_h = true }
54
+
55
+ x.separator ''
56
+ x.on('--help', 'Show this help message.') { puts x; exit }
57
+ x.parse!
58
+ end
59
+
60
+ # parse the config file specified at the command-line
61
+ file_cfg = config_file ? parse_config_file!(config_file, true) : {}
62
+
63
+ # read environment variables, too. This Ruby API favors the term
64
+ # "hosts", however upstream MogileFS teminology favors "trackers" instead.
65
+ # Favor the term more consistent with what the MogileFS inventors used.
66
+ env_cfg = {}
67
+ if ENV["MOG_TRACKERS"]
68
+ env_cfg[:hosts] = ENV["MOG_TRACKERS"].split(/\s*,\s*/)
69
+ end
70
+ if ENV["MOG_HOSTS"] && env_cfg[:hosts].empty?
71
+ env_cfg[:hosts] = ENV["MOG_HOSTS"].split(/\s*,\s*/)
72
+ end
73
+ env_cfg[:domain] = ENV["MOG_DOMAIN"] if ENV["MOG_DOMAIN"]
74
+ env_cfg[:class] = ENV["MOG_CLASS"] if ENV["MOG_CLASS"]
75
+
76
+ # merge the configs, favoring them in order specified:
77
+ cfg = {}.merge(def_cfg).merge(env_cfg).merge(file_cfg).merge(cli_cfg)
78
+
79
+ # error-checking
80
+ err = []
81
+ err << "trackers must be specified" if cfg[:hosts].nil? || cfg[:hosts].empty?
82
+ err << "domain must be specified" unless cfg[:domain]
83
+ if err.any?
84
+ STDERR.puts "Errors:\n #{err.join("\n ")}"
85
+ STDERR.puts ARGV.options
86
+ exit 1
87
+ end
88
+
89
+ unless cmd = ARGV.shift
90
+ STDERR.puts ARGV.options
91
+ exit 1
92
+ end
93
+
94
+ include MogileFS::Util
95
+ mg = MogileFS::MogileFS.new(cfg)
96
+
97
+ begin
98
+ case cmd
99
+ when 'cp'
100
+ filename = ARGV.shift or raise ArgumentError, '<filename> <key>'
101
+ key = ARGV.shift or raise ArgumentError, '<filename> <key>'
102
+ ARGV.shift and raise ArgumentError, '<filename> <key>'
103
+ cfg[:class] or raise ArgumentError, 'E: --class must be specified'
104
+ mg.store_file(key, cfg[:class], filename)
105
+ when 'cat'
106
+ ARGV.empty? and raise ArgumentError, '<key1> [<key2> ...]'
107
+ ARGV.each { |key| mg.get_file_data(key) { |fp| sysrwloop(fp, STDOUT) } }
108
+ when 'ls'
109
+ prefixes = ARGV.empty? ? [ nil ] : ARGV
110
+ prefixes.each do |prefix|
111
+ mg.each_key(prefix) do |key|
112
+ if ls_l
113
+ path_nr = "% 2d" % mg.get_paths(key).size
114
+ size = mg.size(key)
115
+ if ls_h && size > 1024
116
+ suff = ''
117
+ %w(K M G).each do |s|
118
+ size /= 1024.0
119
+ suff = s
120
+ break if size <= 1024
121
+ end
122
+ size = sprintf("%.1f%s", size, suff)
123
+ else
124
+ size = size.to_s
125
+ end
126
+ size = (' ' * (12 - size.length)) << size # right justify
127
+ puts [ path_nr, size, key ].pack("A4 A16 A32")
128
+ else
129
+ puts key
130
+ end
131
+ end
132
+ end
133
+ when 'rm'
134
+ ARGV.empty? and raise ArgumentError, '<key1> [<key2>]'
135
+ ARGV.each { |key| mg.delete(key) }
136
+ when 'mv'
137
+ from = ARGV.shift or raise ArgumentError, '<from> <to>'
138
+ to = ARGV.shift or raise ArgumentError, '<from> <to>'
139
+ ARGV.shift and raise ArgumentError, '<from> <to>'
140
+ mg.rename(from, to)
141
+ when 'stat' # this outputs a RFC822-like format
142
+ ARGV.empty? and raise ArgumentError, '<key1> [<key2>]'
143
+ ARGV.each_with_index do |key, i|
144
+ if size = mg.size(key)
145
+ puts "Key: #{key}"
146
+ puts "Size: #{size}"
147
+ mg.get_paths(key).each_with_index do |path,i|
148
+ puts "URL-#{i}: #{path}"
149
+ end
150
+ puts ""
151
+ else
152
+ STDERR.puts "No such key: #{key}"
153
+ end
154
+ end
155
+ when 'tee'
156
+ require 'tempfile'
157
+ key = ARGV.shift or raise ArgumentError, '<key>'
158
+ ARGV.shift and raise ArgumentError, '<key>'
159
+ cfg[:class] or raise ArgumentError, 'E: --class must be specified'
160
+ buf = ''
161
+ tmp = Tempfile.new('mog-tee') # TODO: explore Transfer-Encoding:chunked :)
162
+ at_exit { tmp.unlink }
163
+ begin
164
+ sysrwloop(STDIN, tmp)
165
+ mg.store_file(key, cfg[:class], tmp.path)
166
+ ensure
167
+ tmp.close
168
+ end
169
+ when 'test'
170
+ truth, ok = true, nil
171
+ raise ArgumentError, "-e must be specified" unless (test.size == 1)
172
+
173
+ truth, key = case ARGV.size
174
+ when 1
175
+ [ true, ARGV[0] ]
176
+ when 2
177
+ if ARGV[0] != "!"
178
+ raise ArgumentError, "#{ARGV[0]}: binary operator expected"
179
+ end
180
+ [ false, ARGV[1] ]
181
+ else
182
+ raise ArgumentError, "Too many arguments"
183
+ end
184
+
185
+ paths = mg.get_paths(key)
186
+ if test[:e]
187
+ ok = !!(paths && paths.size > 0)
188
+ else
189
+ raise ArgumentError, "Unknown flag: -#{test.keys.first}"
190
+ end
191
+
192
+ truth or ok = ! ok
193
+ exit ok ? 0 : 1
194
+ else
195
+ raise ArgumentError, "Unknown command: #{cmd}"
196
+ end
197
+ rescue ArgumentError => err
198
+ STDERR.puts "Usage: #{$0} #{cmd} #{err.message}"
199
+ exit 1
200
+ end
201
+ exit 0
data/lib/mogilefs.rb CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  module MogileFS
8
8
 
9
- VERSION = '1.2.1'
9
+ VERSION = '1.3.0'
10
10
 
11
11
  ##
12
12
  # Raised when a socket remains unreadable for too long.
@@ -155,6 +155,7 @@ class MogileFS::Backend
155
155
  found = select [socket], nil, nil, @timeout
156
156
  if found.nil? or found.empty? then
157
157
  peer = (@socket ? "#{@socket.peeraddr[3]}:#{@socket.peeraddr[1]} " : nil)
158
+ socket.close # we DO NOT want the response we timed out waiting for, to crop up later on, on the same socket, intersperesed with a subsequent request! so, we close the socket if it times out like this
158
159
  raise MogileFS::UnreadableSocketError, "#{peer}never became readable"
159
160
  end
160
161
  return true
@@ -3,6 +3,7 @@ require 'socket'
3
3
  require 'stringio'
4
4
  require 'uri'
5
5
  require 'mogilefs/backend'
6
+ require 'mogilefs/util'
6
7
 
7
8
  ##
8
9
  # HTTPFile wraps up the new file operations for storing files onto an HTTP
@@ -17,6 +18,7 @@ require 'mogilefs/backend'
17
18
  # TODO dup'd content in MogileFS::NFSFile
18
19
 
19
20
  class MogileFS::HTTPFile < StringIO
21
+ include MogileFS::Util
20
22
 
21
23
  ##
22
24
  # The path this file will be stored to.
@@ -34,6 +36,11 @@ class MogileFS::HTTPFile < StringIO
34
36
 
35
37
  attr_reader :class
36
38
 
39
+ ##
40
+ # The bigfile name in case we have file > 256M
41
+
42
+ attr_accessor :bigfile
43
+
37
44
  ##
38
45
  # Works like File.open. Use MogileFS::MogileFS#new_file instead of this
39
46
  # method.
@@ -62,6 +69,7 @@ class MogileFS::HTTPFile < StringIO
62
69
  @devid = devid
63
70
  @klass = klass
64
71
  @key = key
72
+ @bigfile = nil
65
73
 
66
74
  @dests = dests.map { |(_,u)| URI.parse u }
67
75
  @tried = {}
@@ -75,7 +83,17 @@ class MogileFS::HTTPFile < StringIO
75
83
  def close
76
84
  connect_socket
77
85
 
78
- @socket.write "PUT #{@path.request_uri} HTTP/1.0\r\nContent-length: #{length}\r\n\r\n#{string}"
86
+ file_size = nil
87
+ if @bigfile
88
+ # Don't try to run out of memory
89
+ fp = File.open(@bigfile)
90
+ file_size = File.size(@bigfile)
91
+ @socket.write "PUT #{@path.request_uri} HTTP/1.0\r\nContent-Length: #{file_size}\r\n\r\n"
92
+ sysrwloop(fp, @socket)
93
+ fp.close
94
+ else
95
+ @socket.write "PUT #{@path.request_uri} HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n#{string}"
96
+ end
79
97
 
80
98
  if connected? then
81
99
  line = @socket.gets
@@ -86,27 +104,19 @@ class MogileFS::HTTPFile < StringIO
86
104
  case status
87
105
  when 200..299 then # success!
88
106
  else
89
- found_header = false
90
- body = []
91
- line = @socket.gets
92
- until line.nil? do
93
- line.strip
94
- found_header = true if line.nil?
95
- next unless found_header
96
- body << " #{line}"
97
- end
98
- body = body[0, 512] if body.length > 512
99
- raise "HTTP response status from upload: #{body}"
107
+ raise "HTTP response status from upload: #{status}"
100
108
  end
101
109
  else
102
110
  raise "Response line not understood: #{line}"
103
111
  end
112
+
104
113
  @socket.close
105
114
  end
106
115
 
107
116
  @mg.backend.create_close(:fid => @fid, :devid => @devid,
108
117
  :domain => @mg.domain, :key => @key,
109
118
  :path => @path, :size => length)
119
+ return file_size if @bigfile
110
120
  return nil
111
121
  end
112
122
 
@@ -1,4 +1,5 @@
1
1
  require 'open-uri'
2
+ require 'net/http'
2
3
  require 'timeout'
3
4
 
4
5
  require 'mogilefs/client'
@@ -24,6 +25,11 @@ class MogileFS::MogileFS < MogileFS::Client
24
25
 
25
26
  attr_reader :domain
26
27
 
28
+ ##
29
+ # The timeout for get_file_data. Defaults to five seconds.
30
+
31
+ attr_accessor :get_file_data_timeout
32
+
27
33
  ##
28
34
  # Creates a new MogileFS::MogileFS instance. +args+ must include a key
29
35
  # :domain specifying the domain of this client. A key :root will be used to
@@ -33,6 +39,8 @@ class MogileFS::MogileFS < MogileFS::Client
33
39
  @domain = args[:domain]
34
40
  @root = args[:root]
35
41
 
42
+ @get_file_data_timeout = 5
43
+
36
44
  raise ArgumentError, "you must specify a domain" unless @domain
37
45
 
38
46
  super
@@ -46,7 +54,7 @@ class MogileFS::MogileFS < MogileFS::Client
46
54
 
47
55
  keys, after = list_keys prefix
48
56
 
49
- until keys.empty? do
57
+ until keys.nil? or keys.empty? do
50
58
  keys.each { |k| yield k }
51
59
  keys, after = list_keys prefix, after
52
60
  end
@@ -57,7 +65,7 @@ class MogileFS::MogileFS < MogileFS::Client
57
65
  ##
58
66
  # Retrieves the contents of +key+.
59
67
 
60
- def get_file_data(key)
68
+ def get_file_data(key, &block)
61
69
  paths = get_paths key
62
70
 
63
71
  return nil unless paths
@@ -68,8 +76,21 @@ class MogileFS::MogileFS < MogileFS::Client
68
76
  when /^http:\/\// then
69
77
  begin
70
78
  path = URI.parse path
71
- data = timeout(5, MogileFS::Timeout) { path.read }
72
- return data
79
+
80
+ if block_given?
81
+ sock = nil
82
+ timeout @get_file_data_timeout, MogileFS::Timeout do
83
+ sock = TCPSocket.new(path.host, path.port)
84
+ sock.sync = true
85
+ sock.syswrite("GET #{path.request_uri} HTTP/1.0\r\n\r\n")
86
+ buf = sock.recv(4096, Socket::MSG_PEEK)
87
+ head, body = buf.split(/\r\n\r\n/, 2)
88
+ head = sock.recv(head.size + 4)
89
+ end
90
+ return yield(sock)
91
+ else
92
+ return path.read
93
+ end
73
94
  rescue MogileFS::Timeout
74
95
  next
75
96
  end
@@ -108,7 +129,7 @@ class MogileFS::MogileFS < MogileFS::Client
108
129
  res = @backend.create_open(:domain => @domain, :class => klass,
109
130
  :key => key, :multi_dest => 1)
110
131
 
111
- raise "#{@backend.lasterr}: #{@backend.lasterrstr}" if res.nil? # HACK
132
+ raise "#{@backend.lasterr}: #{@backend.lasterrstr}" if res.nil? || res == {} # HACK
112
133
 
113
134
  dests = nil
114
135
 
@@ -130,6 +151,8 @@ class MogileFS::MogileFS < MogileFS::Client
130
151
  devid, path = dest
131
152
 
132
153
  case path
154
+ when nil, '' then
155
+ raise 'Empty path for mogile upload'
133
156
  when /^http:\/\// then
134
157
  MogileFS::HTTPFile.open(self, res['fid'], path, devid, klass, key,
135
158
  dests, bytes, &block)
@@ -146,10 +169,15 @@ class MogileFS::MogileFS < MogileFS::Client
146
169
  raise 'readonly mogilefs' if readonly?
147
170
 
148
171
  new_file key, klass do |mfp|
149
- if file.respond_to? :read then
150
- return copy(file, mfp)
172
+ if file.respond_to? :sysread then
173
+ return sysrwloop(file, mfp)
151
174
  else
152
- return File.open(file) { |fp| copy(fp, mfp) }
175
+ if File.size(file) > 0x10000 # Bigass file, handle differently
176
+ mfp.bigfile = file
177
+ return mfp.close
178
+ else
179
+ return File.open(file) { |fp| sysrwloop(fp, mfp) }
180
+ end
153
181
  end
154
182
  end
155
183
  end
@@ -196,8 +224,43 @@ class MogileFS::MogileFS < MogileFS::Client
196
224
  res = @backend.rename :domain => @domain, :from_key => from, :to_key => to
197
225
 
198
226
  if res.nil? and @backend.lasterr != 'unknown_key' then
199
- raise "unable to rename #{from_key} to #{to_key}: #{@backend.lasterr}"
227
+ raise "unable to rename #{from} to #{to}: #{@backend.lasterr}"
228
+ end
229
+ end
230
+
231
+ ##
232
+ # Returns the size of +key+.
233
+ def size(key)
234
+ paths = get_paths key
235
+
236
+ return nil unless paths
237
+
238
+ paths.each do |path|
239
+ next unless path
240
+ case path
241
+ when /^http:\/\// then
242
+ begin
243
+ url = URI.parse path
244
+
245
+ req = Net::HTTP::Head.new url.request_uri
246
+
247
+ res = timeout @get_file_data_timeout, MogileFS::Timeout do
248
+ Net::HTTP.start url.host, url.port do |http|
249
+ http.request req
250
+ end
251
+ end
252
+
253
+ return res['Content-Length'].to_i
254
+ rescue MogileFS::Timeout
255
+ next
256
+ end
257
+ else
258
+ next unless File.exist? path
259
+ return File.size(path)
260
+ end
200
261
  end
262
+
263
+ return nil
201
264
  end
202
265
 
203
266
  ##
@@ -215,19 +278,5 @@ class MogileFS::MogileFS < MogileFS::Client
215
278
  return keys, res['next_after']
216
279
  end
217
280
 
218
- private
219
-
220
- def copy(from, to) # HACK use FileUtils
221
- bytes = 0
222
-
223
- until from.eof? do
224
- chunk = from.read 8192
225
- to.write chunk
226
- bytes += chunk.length
227
- end
228
-
229
- return bytes
230
- end
231
-
232
281
  end
233
282
 
@@ -0,0 +1,30 @@
1
+ module MogileFS::Util
2
+
3
+ CHUNK_SIZE = 65536
4
+
5
+ # for copying large files while avoiding GC thrashing as much as possible
6
+ def sysrwloop(io_rd, io_wr)
7
+ copied = 0
8
+ # avoid making sysread repeatedly allocate a new String
9
+ # This is not well-documented, but both read/sysread can take
10
+ # an optional second argument to use as the buffer to avoid
11
+ # GC overhead of creating new strings in a loop
12
+ buf = ' ' * CHUNK_SIZE # preallocate to avoid GC thrashing
13
+ io_wr.sync = true
14
+ loop do
15
+ begin
16
+ 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
23
+ rescue EOFError
24
+ break
25
+ end
26
+ end
27
+ copied
28
+ end # sysrwloop
29
+
30
+ end
data/test/setup.rb CHANGED
@@ -1,5 +1,12 @@
1
1
  require 'test/unit'
2
2
 
3
+ require 'fileutils'
4
+ require 'tmpdir'
5
+ require 'stringio'
6
+
7
+ require 'rubygems'
8
+ require 'test/zentest_assertions'
9
+
3
10
  $TESTING = true
4
11
 
5
12
  require 'mogilefs'
@@ -32,22 +39,114 @@ class FakeBackend
32
39
 
33
40
  end
34
41
 
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
+
35
105
  class MogileFS::Client
36
106
  attr_writer :readonly
37
107
  end
38
108
 
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
122
+ end
123
+
124
+ alias open new
125
+
126
+ end
127
+
128
+ end
129
+
39
130
  class TestMogileFS < Test::Unit::TestCase
40
131
 
132
+ undef_method :default_test
133
+
41
134
  def setup
42
- return if self.class == TestMogileFS
43
- @root = '/mogilefs/test'
135
+ @tempdir = File.join Dir.tmpdir, "test_mogilefs_#{$$}"
136
+ @root = File.join @tempdir, 'root'
137
+ FileUtils.mkdir_p @root
138
+
44
139
  @client = @klass.new :hosts => ['kaa:6001'], :domain => 'test',
45
140
  :root => @root
46
141
  @backend = FakeBackend.new
47
142
  @client.instance_variable_set '@backend', @backend
143
+
144
+ TCPSocket.sockets = []
145
+ TCPSocket.connections = []
48
146
  end
49
147
 
50
- def test_nothing
148
+ def teardown
149
+ FileUtils.rm_rf @tempdir
51
150
  end
52
151
 
53
152
  end
data/test/test_backend.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'test/unit'
2
+ require 'test/setup'
2
3
 
3
4
  $TESTING = true
4
5
 
@@ -12,30 +13,11 @@ class MogileFS::Backend
12
13
 
13
14
  end
14
15
 
15
- class FakeSocket
16
-
17
- def initialize
18
- @closed = false
19
- end
20
-
21
- def closed?
22
- @closed
23
- end
24
-
25
- def close
26
- @closed = true
27
- return nil
28
- end
29
-
30
- def peeraddr
31
- ['AF_INET', 6001, 'localhost', '127.0.0.1']
32
- end
33
-
34
- end
35
-
36
16
  class TestBackend < Test::Unit::TestCase
37
17
 
38
18
  def setup
19
+ TCPSocket.connections = []
20
+ TCPSocket.sockets = []
39
21
  @backend = MogileFS::Backend.new :hosts => ['localhost:1']
40
22
  end
41
23
 
@@ -1,15 +1,23 @@
1
1
  require 'test/setup'
2
+ require 'stringio'
3
+ require 'tempfile'
4
+ require 'fileutils'
2
5
 
3
6
  class URI::HTTP
4
7
 
5
8
  class << self
6
9
  attr_accessor :read_data
10
+ attr_accessor :open_data
7
11
  end
8
12
 
9
13
  def read
10
14
  self.class.read_data.shift
11
15
  end
12
16
 
17
+ def open(&block)
18
+ yield self.class.open_data
19
+ end
20
+
13
21
  end
14
22
 
15
23
  class TestMogileFS__MogileFS < TestMogileFS
@@ -21,7 +29,7 @@ class TestMogileFS__MogileFS < TestMogileFS
21
29
 
22
30
  def test_initialize
23
31
  assert_equal 'test', @client.domain
24
- assert_equal '/mogilefs/test', @client.root
32
+ assert_equal @root, @client.root
25
33
 
26
34
  assert_raises ArgumentError do
27
35
  MogileFS::MogileFS.new :hosts => ['kaa:6001'], :root => '/mogilefs/test'
@@ -39,6 +47,43 @@ class TestMogileFS__MogileFS < TestMogileFS
39
47
  assert_equal 'data!', @client.get_file_data('key')
40
48
  end
41
49
 
50
+ def test_get_file_data_http_block
51
+ tmpfp = Tempfile.new('test_mogilefs.open_data')
52
+ nr = 100 # tested with 1000
53
+ chunk_size = 1024 * 1024
54
+ expect_size = nr * chunk_size
55
+ header = "HTTP/1.0 200 OK\r\n" \
56
+ "Content-Length: #{expect_size}\r\n\r\n"
57
+ assert_equal header.size, tmpfp.syswrite(header)
58
+ nr.times { assert_equal chunk_size, tmpfp.syswrite(' ' * chunk_size) }
59
+ assert_equal expect_size + header.size, File.size(tmpfp.path)
60
+ tmpfp.sysseek(0)
61
+ socket = FakeSocket.new(tmpfp)
62
+ TCPSocket.sockets << socket
63
+
64
+ path1 = 'http://rur-1/dev1/0/000/000/0000000062.fid'
65
+ path2 = 'http://rur-2/dev2/0/000/000/0000000062.fid'
66
+
67
+ @backend.get_paths = { 'paths' => 2, 'path1' => path1, 'path2' => path2 }
68
+
69
+ data = Tempfile.new('test_mogilefs.dest_data')
70
+ @client.get_file_data('key') do |fp|
71
+ buf = ''
72
+ read_nr = nr = 0
73
+ loop do
74
+ begin
75
+ fp.sysread(16384, buf)
76
+ read_nr = buf.size
77
+ nr += read_nr
78
+ assert_equal read_nr, data.syswrite(buf), "partial write"
79
+ rescue EOFError
80
+ break
81
+ end
82
+ end
83
+ assert_equal expect_size, nr, "size mismatch"
84
+ end
85
+ end
86
+
42
87
  def test_get_paths
43
88
  path1 = 'rur-1/dev1/0/000/000/0000000062.fid'
44
89
  path2 = 'rur-2/dev2/0/000/000/0000000062.fid'
@@ -114,8 +159,120 @@ class TestMogileFS__MogileFS < TestMogileFS
114
159
  end
115
160
  end
116
161
 
162
+ def test_size_http
163
+ socket = FakeSocket.new <<-EOF
164
+ HTTP/1.0 200 OK\r
165
+ Content-Length: 5\r
166
+ EOF
167
+
168
+ TCPSocket.sockets << socket
169
+
170
+ path = 'http://example.com/path'
171
+
172
+ @backend.get_paths = { 'paths' => 1, 'path1' => path }
173
+
174
+ assert_equal 5, @client.size('key')
175
+
176
+ socket.write_s.rewind
177
+
178
+ assert_equal "HEAD /path HTTP/1.1\r\n", socket.write_s.gets
179
+
180
+ assert_equal ['example.com', 80], TCPSocket.connections.shift
181
+ assert_empty TCPSocket.connections
182
+ end
183
+
184
+ def test_size_nfs
185
+ path = File.join @root, 'path'
186
+
187
+ File.open path, 'w' do |fp| fp.write 'data!' end
188
+
189
+ @backend.get_paths = { 'paths' => 1, 'path1' => 'path' }
190
+
191
+ assert_equal 5, @client.size('key')
192
+ end
193
+
194
+ def test_store_content_http
195
+ socket = FakeSocket.new 'HTTP/1.0 200 OK'
196
+
197
+ TCPSocket.sockets << socket
198
+
199
+ @backend.create_open = {
200
+ 'devid' => '1',
201
+ 'path' => 'http://example.com/path',
202
+ }
203
+
204
+ @client.store_content 'new_key', 'test', 'data'
205
+
206
+ expected = <<-EOF.chomp
207
+ PUT /path HTTP/1.0\r
208
+ Content-Length: 4\r
209
+ \r
210
+ data
211
+ EOF
212
+
213
+ assert_equal expected, socket.write_s.string
214
+
215
+ assert_equal ['example.com', 80], TCPSocket.connections.shift
216
+ assert_empty TCPSocket.connections
217
+ end
218
+
219
+ def test_store_content_http_empty
220
+ socket = FakeSocket.new 'HTTP/1.0 200 OK'
221
+
222
+ TCPSocket.sockets << socket
223
+
224
+ @backend.create_open = {
225
+ 'devid' => '1',
226
+ 'path' => 'http://example.com/path',
227
+ }
228
+
229
+ @client.store_content 'new_key', 'test', ''
230
+
231
+ expected = <<-EOF
232
+ PUT /path HTTP/1.0\r
233
+ Content-Length: 0\r
234
+ \r
235
+ EOF
236
+
237
+ assert_equal expected, socket.write_s.string
238
+
239
+ assert_equal ['example.com', 80], TCPSocket.connections.shift
240
+ assert_empty TCPSocket.connections
241
+ end
242
+
243
+ def test_store_content_nfs
244
+ @backend.create_open = {
245
+ 'dev_count' => '1',
246
+ 'devid_1' => '1',
247
+ 'path_1' => '/path',
248
+ }
249
+
250
+ @client.store_content 'new_key', 'test', 'data'
251
+
252
+ dest_file = File.join(@root, 'path')
253
+
254
+ assert File.exist?(dest_file)
255
+ assert_equal 'data', File.read(dest_file)
256
+ end
257
+
258
+ def test_store_content_nfs_empty
259
+ @backend.create_open = {
260
+ 'dev_count' => '1',
261
+ 'devid_1' => '1',
262
+ 'path_1' => '/path',
263
+ }
264
+
265
+ @client.store_content 'new_key', 'test', ''
266
+
267
+ dest_file = File.join(@root, 'path')
268
+
269
+ assert File.exist?(dest_file)
270
+ assert_equal '', File.read(dest_file)
271
+ end
272
+
117
273
  def test_store_content_readonly
118
274
  @client.readonly = true
275
+
119
276
  assert_raises RuntimeError do
120
277
  @client.store_content 'new_key', 'test', nil
121
278
  end
@@ -130,23 +287,34 @@ class TestMogileFS__MogileFS < TestMogileFS
130
287
 
131
288
  def test_rename_existing
132
289
  @backend.rename = {}
133
- assert_nothing_raised do
134
- assert_equal(nil, @client.rename('from_key', 'to_key'))
135
- end
290
+
291
+ assert_nil @client.rename('from_key', 'to_key')
136
292
  end
137
293
 
138
294
  def test_rename_nonexisting
139
295
  @backend.rename = 'unknown_key', ''
140
- assert_nothing_raised do
141
- assert_equal(nil, @client.rename('from_key', 'to_key'))
296
+
297
+ assert_nil @client.rename('from_key', 'to_key')
298
+ end
299
+
300
+ def test_rename_no_key
301
+ @backend.rename = 'no_key', ''
302
+
303
+ e = assert_raises RuntimeError do
304
+ @client.rename 'new_key', 'test'
142
305
  end
306
+
307
+ assert_equal 'unable to rename new_key to test: no_key', e.message
143
308
  end
144
309
 
145
310
  def test_rename_readonly
146
311
  @client.readonly = true
147
- assert_raises RuntimeError do
312
+
313
+ e = assert_raises RuntimeError do
148
314
  @client.rename 'new_key', 'test'
149
315
  end
316
+
317
+ assert_equal 'readonly mogilefs', e.message
150
318
  end
151
319
 
152
320
  def test_sleep
metadata CHANGED
@@ -1,45 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4.3
3
- specification_version: 1
4
2
  name: mogilefs-client
5
3
  version: !ruby/object:Gem::Version
6
- version: 1.2.1
7
- date: 2007-07-31 00:00:00 -07:00
8
- summary: A Ruby MogileFS client
9
- require_paths:
10
- - lib
11
- email: drbrain@segment7.net
12
- homepage: http://seattlerb.org/mogilefs-client
13
- rubyforge_project: seattlerb
14
- description: A Ruby MogileFS client. MogileFS is a distributed filesystem written by Danga Interactive. This client supports NFS and HTTP modes.
15
- autorequire:
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
25
- required_rubygems_version: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - ">"
28
- - !ruby/object:Gem::Version
29
- version: 0.0.0
30
- version:
4
+ version: 1.3.0
31
5
  platform: ruby
32
- signing_key:
33
- cert_chain:
34
- post_install_message:
35
6
  authors:
7
+ - Eric Wong
36
8
  - Eric Hodel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-09-18 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: ZenTest
18
+ type: :development
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 3.6.1
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: hoe
28
+ type: :development
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 1.7.0
35
+ version:
36
+ description: A Ruby MogileFS client. MogileFS is a distributed filesystem written by Danga Interactive. This client supports NFS and HTTP modes.
37
+ email: normalperson@yhbt.net
38
+ executables:
39
+ - mog
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - LICENSE.txt
45
+ - Manifest.txt
46
+ - README.txt
37
47
  files:
38
48
  - History.txt
39
49
  - LICENSE.txt
40
50
  - Manifest.txt
41
51
  - README.txt
42
52
  - Rakefile
53
+ - bin/mog
43
54
  - lib/mogilefs.rb
44
55
  - lib/mogilefs/admin.rb
45
56
  - lib/mogilefs/backend.rb
@@ -48,39 +59,43 @@ files:
48
59
  - lib/mogilefs/mogilefs.rb
49
60
  - lib/mogilefs/nfsfile.rb
50
61
  - lib/mogilefs/pool.rb
62
+ - lib/mogilefs/util.rb
51
63
  - test/setup.rb
52
64
  - test/test_admin.rb
53
65
  - test/test_backend.rb
54
66
  - test/test_client.rb
55
67
  - test/test_mogilefs.rb
56
68
  - test/test_pool.rb
57
- test_files:
58
- - test/test_admin.rb
59
- - test/test_backend.rb
60
- - test/test_client.rb
61
- - test/test_mogilefs.rb
62
- - test/test_pool.rb
69
+ has_rdoc: true
70
+ homepage: http://seattlerb.org/mogilefs-client
71
+ post_install_message:
63
72
  rdoc_options:
64
73
  - --main
65
74
  - README.txt
66
- extra_rdoc_files:
67
- - History.txt
68
- - LICENSE.txt
69
- - Manifest.txt
70
- - README.txt
71
- executables: []
72
-
73
- extensions: []
74
-
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
75
89
  requirements: []
76
90
 
77
- dependencies:
78
- - !ruby/object:Gem::Dependency
79
- name: hoe
80
- version_requirement:
81
- version_requirements: !ruby/object:Gem::Requirement
82
- requirements:
83
- - - ">="
84
- - !ruby/object:Gem::Version
85
- version: 1.2.2
86
- version:
91
+ rubyforge_project: seattlerb
92
+ rubygems_version: 1.2.0
93
+ signing_key:
94
+ specification_version: 2
95
+ summary: A Ruby MogileFS client
96
+ test_files:
97
+ - test/test_pool.rb
98
+ - test/test_mogilefs.rb
99
+ - test/test_client.rb
100
+ - test/test_admin.rb
101
+ - test/test_backend.rb