mogilefs-client 1.2.1 → 1.3.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/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