mogilefs-client 2.2.0 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.document +11 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +4 -0
  4. data/.wrongdoc.yml +5 -0
  5. data/GIT-VERSION-GEN +28 -0
  6. data/GNUmakefile +44 -0
  7. data/HACKING +33 -0
  8. data/{History.txt → History} +0 -1
  9. data/{LICENSE.txt → LICENSE} +0 -1
  10. data/Manifest.txt +34 -7
  11. data/README +51 -0
  12. data/Rakefile +11 -11
  13. data/TODO +10 -0
  14. data/bin/mog +109 -68
  15. data/examples/mogstored_rack.rb +189 -0
  16. data/lib/mogilefs.rb +56 -17
  17. data/lib/mogilefs/admin.rb +128 -62
  18. data/lib/mogilefs/backend.rb +205 -95
  19. data/lib/mogilefs/bigfile.rb +54 -70
  20. data/lib/mogilefs/bigfile/filter.rb +58 -0
  21. data/lib/mogilefs/chunker.rb +30 -0
  22. data/lib/mogilefs/client.rb +0 -2
  23. data/lib/mogilefs/copy_stream.rb +30 -0
  24. data/lib/mogilefs/http_file.rb +175 -0
  25. data/lib/mogilefs/http_reader.rb +79 -0
  26. data/lib/mogilefs/mogilefs.rb +242 -148
  27. data/lib/mogilefs/mysql.rb +3 -4
  28. data/lib/mogilefs/paths_size.rb +24 -0
  29. data/lib/mogilefs/pool.rb +0 -1
  30. data/lib/mogilefs/socket.rb +9 -0
  31. data/lib/mogilefs/socket/kgio.rb +55 -0
  32. data/lib/mogilefs/socket/pure_ruby.rb +70 -0
  33. data/lib/mogilefs/socket_common.rb +58 -0
  34. data/lib/mogilefs/util.rb +6 -169
  35. data/test/aggregate.rb +11 -11
  36. data/test/exec.rb +72 -0
  37. data/test/fresh.rb +222 -0
  38. data/test/integration.rb +43 -0
  39. data/test/setup.rb +1 -0
  40. data/test/socket_test.rb +98 -0
  41. data/test/test_admin.rb +14 -37
  42. data/test/test_backend.rb +50 -107
  43. data/test/test_bigfile.rb +2 -2
  44. data/test/test_db_backend.rb +1 -2
  45. data/test/test_fresh.rb +8 -0
  46. data/test/test_http_reader.rb +34 -0
  47. data/test/test_mogilefs.rb +278 -98
  48. data/test/test_mogilefs_integration.rb +174 -0
  49. data/test/test_mogilefs_integration_large_pipe.rb +62 -0
  50. data/test/test_mogilefs_integration_list_keys.rb +40 -0
  51. data/test/test_mogilefs_socket_kgio.rb +11 -0
  52. data/test/test_mogilefs_socket_pure.rb +7 -0
  53. data/test/test_mogstored_rack.rb +89 -0
  54. data/test/test_mogtool_bigfile.rb +116 -0
  55. data/test/test_mysql.rb +1 -2
  56. data/test/test_pool.rb +1 -1
  57. data/test/test_unit_mogstored_rack.rb +72 -0
  58. metadata +76 -54
  59. data/README.txt +0 -80
  60. data/lib/mogilefs/httpfile.rb +0 -157
  61. data/lib/mogilefs/network.rb +0 -107
  62. data/test/test_network.rb +0 -56
  63. data/test/test_util.rb +0 -121
@@ -0,0 +1,189 @@
1
+ # -*- encoding: binary -*-
2
+ require 'tempfile'
3
+ require 'digest/md5'
4
+ require 'rack'
5
+
6
+ # Rack application for handling HTTP PUT/DELETE/MKCOL operations needed
7
+ # for a MogileFS storage server. GET requests are handled by
8
+ # Rack::File and Rack::Head _must_ be in the middleware stack for
9
+ # mogilefsd fsck to work properly with keepalive.
10
+ #
11
+ # Usage in rackup config file (config.ru):
12
+ #
13
+ # require "./mogstored_rack"
14
+ # use Rack::Head
15
+ # run MogstoredRack.new("/var/mogdata")
16
+ class MogstoredRack
17
+ class ContentMD5 < Digest::MD5
18
+ def content_md5
19
+ [ digest ].pack("m").strip!
20
+ end
21
+ end
22
+
23
+ def initialize(root, opts = {})
24
+ @root = File.expand_path(root)
25
+ @rack_file = (opts[:app] || Rack::File.new(@root))
26
+ @fsync = !! opts[:fsync]
27
+ @creat_perms = opts[:creat_perms] || (~File.umask & 0666)
28
+ @mkdir_perms = opts[:mkdir_perms] || (~File.umask & 0777)
29
+ @reread_verify = !! opts[:reread_verify]
30
+ end
31
+
32
+ def call(env)
33
+ case env["REQUEST_METHOD"]
34
+ when "GET", "HEAD"
35
+ case env["PATH_INFO"]
36
+ when "/"
37
+ r(200, "") # MogileFS seems to need this...
38
+ else
39
+ @rack_file.call(env)
40
+ end
41
+ when "PUT"
42
+ put(env)
43
+ when "DELETE"
44
+ delete(env)
45
+ when "MKCOL"
46
+ mkcol(env)
47
+ else
48
+ r(405, "unsupported method", env)
49
+ end
50
+ rescue Errno::EPERM, Errno::EACCES => err
51
+ r(403, "#{err.message} (#{err.class})", env)
52
+ rescue => err
53
+ r(500, "#{err.message} (#{err.class})", env)
54
+ end
55
+
56
+ def mkcol(env)
57
+ path = server_path(env) or return r(400)
58
+ Dir.mkdir(path, @mkdir_perms)
59
+ r(204)
60
+ rescue Errno::EEXIST # succeed (204) on race condition
61
+ File.directory?(path) ? r(204) : r(409)
62
+ end
63
+
64
+ def delete(env)
65
+ path = server_path(env) or return r(400)
66
+ File.exist?(path) or return r(404)
67
+ File.directory?(path) ? Dir.rmdir(path) : File.unlink(path)
68
+ r(204)
69
+ rescue Errno::ENOENT # return 404 on race condition
70
+ File.exist?(path) ? r(500) : r(404)
71
+ end
72
+
73
+ def put(env)
74
+ path = server_path(env) or return r(400)
75
+ dir = File.dirname(path)
76
+ File.directory?(dir) or return r(403)
77
+
78
+ Tempfile.open([File.basename(path), ".tmp"], dir) do |tmp|
79
+ tmp = tmp.to_io # delegated method calls are slower
80
+ tmp.sync = true
81
+ tmp.binmode
82
+ buf = ""
83
+ received = put_loop(env["rack.input"], tmp, buf)
84
+ err = content_md5_fail?(env, received) and return err
85
+ if @reread_verify && err = reread_md5_fail?(env, tmp, received, buf)
86
+ return err
87
+ end
88
+ tmp.chmod(@creat_perms)
89
+ begin
90
+ File.link(tmp.path, path)
91
+ rescue Errno::EEXIST
92
+ err = rename_overwrite_fail?(tmp.path, path) and return err
93
+ end
94
+ fsync(dir, tmp) if @fsync
95
+ resp = r(201)
96
+ resp[1]["X-Received-Content-MD5"] = received
97
+ return resp
98
+ end
99
+ end
100
+
101
+ def put_loop(src, dst, buf)
102
+ md5 = ContentMD5.new
103
+ while src.read(0x4000, buf)
104
+ md5.update(buf)
105
+ dst.write(buf)
106
+ end
107
+ md5.content_md5
108
+ end
109
+
110
+ def server_path(env)
111
+ path = env['PATH_INFO'].squeeze('/')
112
+ path.split(%r{/}).include?("..") and return false
113
+ "#@root#{path}"
114
+ end
115
+
116
+ # returns a plain-text HTTP response
117
+ def r(code, msg = nil, env = nil)
118
+ if env && logger = env["rack.logger"]
119
+ logger.warn("#{env['REQUEST_METHOD']} #{env['PATH_INFO']} " \
120
+ "#{code} #{msg.inspect}")
121
+ end
122
+ if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
123
+ [ code, {}, [] ]
124
+ else
125
+ msg ||= Rack::Utils::HTTP_STATUS_CODES[code] || ""
126
+ msg += "\n" if msg.size > 0
127
+ [ code,
128
+ { 'Content-Type' => 'text/plain', 'Content-Length' => msg.size.to_s },
129
+ [ msg ] ]
130
+ end
131
+ end
132
+
133
+ # Tries to detect filesystem/disk corruption.
134
+ # Unfortunately, posix_fadvise(2)/IO#advise is only advisory and
135
+ # can't guarantee we're not just reading the data in the kernel
136
+ # page cache.
137
+ def reread_md5_fail?(env, tmp, received, buf)
138
+ # try to force a reread from the storage device, not cache
139
+ tmp.fsync
140
+ tmp.rewind
141
+ tmp.advise(:dontneed) rescue nil # only in Ruby 1.9.3 and only advisory
142
+
143
+ md5 = ContentMD5.new
144
+ while tmp.read(0x4000, buf)
145
+ md5.update(buf)
146
+ end
147
+ reread = md5.content_md5
148
+ reread == received and return false # success
149
+ r(500, "reread MD5 mismatch\n" \
150
+ "received: #{received}\n" \
151
+ " reread: #{reread}", env)
152
+ end
153
+
154
+ # Tries to detect network corruption by verifying the client-supplied
155
+ # Content-MD5 is correct. It's highly unlikely the MD5 can be corrupted
156
+ # in a way that also allows corrupt data to pass through.
157
+ #
158
+ # The Rainbows!/Unicorn HTTP servers will populate the HTTP_CONTENT_MD5
159
+ # field in +env+ after env["rack.input"] is fully-consumed. Clients
160
+ # may also send Content-MD5 as a header and this will still work.
161
+ def content_md5_fail?(env, received)
162
+ expected = env["HTTP_CONTENT_MD5"] or return false
163
+ expected = expected.strip
164
+ expected == received and return false # success
165
+ r(400, "Content-MD5 mismatch\n" \
166
+ "expected: #{expected}\n" \
167
+ "received: #{received}", env)
168
+ end
169
+
170
+ def rename_overwrite_fail?(src, dst)
171
+ 10.times do
172
+ begin
173
+ tmp_dst = "#{dst}.#{rand}"
174
+ File.link(src, tmp_dst)
175
+ rescue Errno::EEXIST
176
+ next
177
+ end
178
+ File.rename(tmp_dst, dst)
179
+ return false # success!
180
+ end
181
+ r(409)
182
+ end
183
+
184
+ # fsync each and every directory component above us on the same device
185
+ def fsync(dir, tmp)
186
+ tmp.fsync
187
+ File.open(dir) { |io| io.fsync }
188
+ end
189
+ end
data/lib/mogilefs.rb CHANGED
@@ -1,41 +1,80 @@
1
1
  # -*- encoding: binary -*-
2
- ##
3
- # MogileFS is a Ruby client for Danga Interactive's open source distributed
4
- # filesystem.
5
2
  #
6
- # To read more about Danga's MogileFS: http://danga.com/mogilefs/
7
-
3
+ # To read more about \MogileFS, go to http://mogilefs.org/
4
+ #
5
+ # Client usage information is available in MogileFS::MogileFS.
8
6
  module MogileFS
9
7
 
10
- VERSION = '2.2.0'
8
+ # Standard error class for most MogileFS-specific errors
9
+ class Error < StandardError; end
11
10
 
12
- ##
13
11
  # Raised when a socket remains unreadable for too long.
14
-
15
- class Error < StandardError; end
16
12
  class UnreadableSocketError < Error; end
13
+
14
+ # Raised when a response is truncated while reading
15
+ # due to network/server # errors)
17
16
  class SizeMismatchError < Error; end
17
+
18
+ # Raised when checksum verification fails (only while reading deprecated
19
+ # "bigfiles" from the deprecated mogtool(1).
18
20
  class ChecksumMismatchError < RuntimeError; end
21
+
22
+ # Raised when a backend is in read-only mode
19
23
  class ReadOnlyError < Error
20
24
  def message; 'readonly mogilefs'; end
21
25
  end
22
- class EmptyPathError < Error
23
- def message; 'Empty path for mogile upload'; end
24
- end
25
26
 
27
+ # Raised when an upload is impossible
28
+ class EmptyPathError < Error; end
29
+
30
+ # Raised when we are given an unsupported protocol to upload to.
31
+ # Currently, the \MogileFS (2.55) server only supports HTTP and
32
+ # this library is only capable of HTTP.
26
33
  class UnsupportedPathError < Error; end
34
+
35
+ # Raised when a request (HTTP or tracker) was truncated due to a network or
36
+ # server error. It may be possible to retry idempotent requests from this.
27
37
  class RequestTruncatedError < Error; end
38
+
39
+ # Raised when a response from a server (HTTP or tracker) is not in a format
40
+ # that we expected (or truncated..
28
41
  class InvalidResponseError < Error; end
29
- class UnreachableBackendError < Error
30
- def message; "couldn't connect to mogilefsd backend"; end
42
+
43
+ # Raised when all known backends have failed.
44
+ class UnreachableBackendError < Error; end
45
+
46
+ # There was an error as a result of the use of the (experimental)
47
+ # pipelining code to the tracker backend
48
+ class PipelineError < Error; end
49
+
50
+ # :stopdoc:
51
+ class << self
52
+ # somebody could use IO::Splice from the "io_splice" RubyGem, too
53
+ # don't consider this a stable API, though...
54
+ attr_accessor :io
55
+ end
56
+
57
+ # IO.copy_stream was buggy in Ruby 1.9.2 and earlier
58
+ if RUBY_VERSION >= "1.9.3"
59
+ @io = IO
60
+ else
61
+ require "mogilefs/copy_stream"
62
+ @io = MogileFS::CopyStream
31
63
  end
32
64
 
65
+ # autoload rarely-used things:
66
+ autoload :Mysql, 'mogilefs/mysql'
67
+ autoload :Pool, 'mogilefs/pool'
68
+ autoload :Admin, 'mogilefs/admin'
69
+ # :startdoc:
33
70
  end
34
71
 
72
+ require 'mogilefs/util'
73
+ require 'mogilefs/socket'
35
74
  require 'mogilefs/backend'
36
- require 'mogilefs/httpfile'
75
+ require 'mogilefs/http_file'
76
+ require 'mogilefs/http_reader'
37
77
  require 'mogilefs/client'
38
78
  require 'mogilefs/bigfile'
39
79
  require 'mogilefs/mogilefs'
40
- require 'mogilefs/admin'
41
-
80
+ require 'mogilefs/version' # generated by ./GIT-VERSION-GEN
@@ -1,25 +1,22 @@
1
1
  # -*- encoding: binary -*-
2
- require 'mogilefs/client'
3
-
4
- ##
5
- # A MogileFS Administration Client
6
2
 
3
+ # \MogileFS administration client, this has little real-world usage
4
+ # and is considered a work-in-progress
7
5
  class MogileFS::Admin < MogileFS::Client
8
6
 
9
7
  ##
10
- # Enumerates fids using #list_fids.
8
+ # Enumerates fids using #list_fids. Returns the number of valid fids
9
+ # processed
11
10
 
12
11
  def each_fid
13
- low = 0
14
- high = nil
15
-
16
- max = get_stats('fids')['fids']['max']
17
-
18
- 0.step max, 100 do |high|
19
- fids = list_fids low, high
12
+ low = -1
13
+ rv = 0
14
+ begin
15
+ fids = list_fids(low + 1)
20
16
  fids.each { |fid| yield fid }
21
- low = high + 1
22
- end
17
+ rv += fids.size
18
+ end while last = fids[-1] and low = last["fid"]
19
+ rv
23
20
  end
24
21
 
25
22
  ##
@@ -31,18 +28,18 @@ class MogileFS::Admin < MogileFS::Client
31
28
  # Returns:
32
29
  #
33
30
  # [{"status"=>"alive",
34
- # "http_get_port"=>"",
31
+ # "http_get_port"=>nil,
35
32
  # "http_port"=>"",
36
33
  # "hostid"=>"1",
37
34
  # "hostip"=>"",
38
35
  # "hostname"=>"rur-1",
39
- # "remoteroot"=>"/mnt/mogilefs/rur-1",
40
36
  # "altip"=>"",
41
37
  # "altmask"=>""}]
42
38
 
43
39
  def get_hosts(hostid = nil)
40
+ to_i = { "hostid" => true, "http_port" => true, "http_get_port" => true }
44
41
  clean('hosts', 'host',
45
- @backend.get_hosts(hostid ? { :hostid => hostid } : {}))
42
+ @backend.get_hosts(hostid ? { :hostid => hostid } : {}), true, to_i)
46
43
  end
47
44
 
48
45
  ##
@@ -54,46 +51,60 @@ class MogileFS::Admin < MogileFS::Client
54
51
  # Returns:
55
52
  #
56
53
  # [{"status"=>"alive",
57
- # "mb_asof"=>"",
58
- # "mb_free"=>"0",
59
- # "devid"=>"1",
60
- # "hostid"=>"1",
61
- # "mb_used"=>"",
62
- # "mb_total"=>""}]
54
+ # "mb_asof"=>nil,
55
+ # "mb_free"=>666000,
56
+ # "devid"=>1,
57
+ # "hostid"=>1,
58
+ # "mb_used"=>666,
59
+ # "mb_total"=>666666}]
63
60
 
64
61
  def get_devices(devid = nil)
65
- clean('devices', 'dev',
66
- @backend.get_devices(devid ? { :devid => devid } : {}))
62
+ to_i = {
63
+ "mb_asof" => true, "mb_free" => true,
64
+ "mb_used" => true, "mb_total" => true ,
65
+ "devid" => true, "weight" => true, "hostid" => true
66
+ }
67
+ rv = @backend.get_devices(devid ? { :devid => devid } : {})
68
+ rv = clean('devices', 'dev', rv, true, to_i)
69
+ rv.each do |row|
70
+ u = row["utilization"] and
71
+ row["utilization"] = nil == u ? nil : u.to_f
72
+ end
67
73
  end
68
74
 
69
75
  ##
70
- # Returns an Array of fid Hashes from +from_fid+ to +to_fid+.
76
+ # Returns an Array of fid Hashes from +from_fid+, limited to +count+
71
77
  #
72
78
  # admin.list_fids 0, 100
73
79
  #
74
80
  # Returns:
75
81
  #
76
- # [{"fid"=>"99",
82
+ # [{"fid"=>99,
77
83
  # "class"=>"normal",
78
84
  # "domain"=>"test",
79
- # "devcount"=>"2",
80
- # "length"=>"4",
85
+ # "devcount"=>2,
86
+ # "length"=>4,
81
87
  # "key"=>"file_key"},
82
- # {"fid"=>"82",
88
+ # {"fid"=>82,
83
89
  # "class"=>"normal",
84
- # "devcount"=>"2",
90
+ # "devcount"=>2,
85
91
  # "domain"=>"test",
86
- # "length"=>"9",
92
+ # "length"=>9,
87
93
  # "key"=>"new_new_key"}]
88
94
 
89
- def list_fids(from_fid, to_fid)
95
+ def list_fids(from_fid, count = 100)
96
+ to_i = { "fid" => true, "devcount" => true, "length" => true }
97
+ # :to is now :count internally in mogilefsd
90
98
  clean('fid_count', 'fid_',
91
- @backend.list_fids(:from => from_fid, :to => to_fid))
99
+ @backend.list_fids(:from => from_fid, :to => count), true, to_i)
92
100
  end
93
101
 
94
102
  ##
95
103
  # Returns a statistics structure representing the state of mogilefs.
96
104
  #
105
+ # *** This command no longer works with recent versions of MogileFS ***
106
+ # *** Use mogstats(1) from the MogileFS::Utils package on CPAN ***
107
+ #
97
108
  # admin.get_stats
98
109
  #
99
110
  # Returns:
@@ -129,22 +140,47 @@ class MogileFS::Admin < MogileFS::Client
129
140
  end
130
141
 
131
142
  ##
132
- # Returns the domains present in the mogilefs.
143
+ # Returns the domains and classes, and their policies present in the mogilefs.
133
144
  #
134
145
  # admin.get_domains
135
146
  #
136
- # Returns:
147
+ # Returns (on newer MogileFS servers):
148
+ # {
149
+ # "test" => {
150
+ # "default" => {
151
+ # "mindevcount" => 2,
152
+ # "replpolicy" => "MultipleHosts()"
153
+ # }
154
+ # }
155
+ # }
156
+ #
157
+ # Returns (on older MogileFS servers without replication policies):
137
158
  #
138
159
  # {"test"=>{"normal"=>3, "default"=>2}}
139
160
 
140
161
  def get_domains
141
162
  res = @backend.get_domains
163
+ have_replpolicy = false
142
164
 
143
165
  domains = {}
166
+ to_i = { "mindevcount" => true }
144
167
  (1..res['domains'].to_i).each do |i|
145
- domain = clean "domain#{i}classes", "domain#{i}class", res, false
146
- domain = domain.map { |d| [d.values.first, d.values.last.to_i] }
147
- domains[res["domain#{i}"]] = Hash[*domain.flatten]
168
+ domain = clean "domain#{i}classes", "domain#{i}class", res, false, to_i
169
+
170
+ tmp = domains[res["domain#{i}"].freeze] = {}
171
+ domain.each do |d|
172
+ tmp[d.delete("name").freeze] = d
173
+ have_replpolicy ||= d.include?("replpolicy")
174
+ end
175
+ end
176
+
177
+ # only for MogileFS 1.x?, maybe we can drop support for this...
178
+ unless have_replpolicy
179
+ domains.each do |namespace, class_data|
180
+ class_data.each do |class_name, data|
181
+ class_data[class_name] = data["mindevcount"]
182
+ end
183
+ end
148
184
  end
149
185
 
150
186
  domains
@@ -160,7 +196,8 @@ class MogileFS::Admin < MogileFS::Client
160
196
  end
161
197
 
162
198
  ##
163
- # Deletes +domain+. Returns true if successful, false if not.
199
+ # Deletes +domain+. Returns true if successful, raises
200
+ # MogileFS::Backend::DomainNotFoundError if not
164
201
 
165
202
  def delete_domain(domain)
166
203
  raise MogileFS::ReadOnlyError if readonly?
@@ -168,24 +205,24 @@ class MogileFS::Admin < MogileFS::Client
168
205
  end
169
206
 
170
207
  ##
171
- # Creates a new class in +domain+ named +klass+ with files replicated to
172
- # +mindevcount+ devices. Returns nil on failure.
208
+ # Creates a new class in +domain+ named +klass+ with +policy+ for
209
+ # replication. Raises on failure.
173
210
 
174
- def create_class(domain, klass, mindevcount)
175
- modify_class(domain, klass, mindevcount, :create)
211
+ def create_class(domain, klass, policy)
212
+ modify_class(domain, klass, policy, :create)
176
213
  end
177
214
 
178
215
  ##
179
- # Updates class +klass+ in +domain+ to be replicated to +mindevcount+
180
- # devices. Returns nil on failure.
216
+ # Updates class +klass+ in +domain+ with +policy+ for replication.
217
+ # Raises on failure.
181
218
 
182
- def update_class(domain, klass, mindevcount)
183
- modify_class(domain, klass, mindevcount, :update)
219
+ def update_class(domain, klass, policy)
220
+ modify_class(domain, klass, policy, :update)
184
221
  end
185
222
 
186
223
  ##
187
- # Removes class +klass+ from +domain+. Returns true if successful, false if
188
- # not.
224
+ # Removes class +klass+ from +domain+. Returns true if successful.
225
+ # Raises on failure
189
226
 
190
227
  def delete_class(domain, klass)
191
228
  ! @backend.delete_class(:domain => domain, :class => klass).nil?
@@ -226,18 +263,41 @@ class MogileFS::Admin < MogileFS::Client
226
263
  ! @backend.set_state(:host => host, :device => device, :state => state).nil?
227
264
  end
228
265
 
266
+ # reschedules all deferred replication, returns a hash with the number
267
+ # of files rescheduled:
268
+ #
269
+ # admin.replicate_now => { "count" => 5 }
270
+ def replicate_now
271
+ rv = @backend.replicate_now
272
+ rv["count"] = rv["count"].to_i
273
+ rv
274
+ end
275
+
276
+ def clear_cache
277
+ @backend.clear_cache
278
+ end
279
+
229
280
  protected unless defined? $TESTING
230
281
 
231
282
  ##
232
283
  # Modifies +klass+ on +domain+ to store files on +mindevcount+ devices via
233
- # +action+. Returns the class name if successful, nil if not.
284
+ # +action+. Returns the class name if successful, raises if not
234
285
 
235
- def modify_class(domain, klass, mindevcount, action)
286
+ def modify_class(domain, klass, policy, action)
236
287
  raise MogileFS::ReadOnlyError if readonly?
237
- res = @backend.send("#{action}_class", :domain => domain, :class => klass,
238
- :mindevcount => mindevcount)
239
-
240
- res ? res['class'] : nil
288
+ args = { :domain => domain, :class => klass }
289
+ case policy
290
+ when Integer
291
+ args[:mindevcount] = policy
292
+ when String
293
+ args[:replpolicy] = policy
294
+ when Hash
295
+ args.merge!(policy)
296
+ else
297
+ raise ArgumentError,
298
+ "policy=#{policy.inspect} not understood for #{action}_class"
299
+ end
300
+ @backend.__send__("#{action}_class", args)["class"]
241
301
  end
242
302
 
243
303
  ##
@@ -246,7 +306,7 @@ class MogileFS::Admin < MogileFS::Client
246
306
 
247
307
  def modify_host(host, args = {}, action = 'create')
248
308
  args[:host] = host
249
- ! @backend.send("#{action}_host", args).nil?
309
+ ! @backend.__send__("#{action}_host", args).nil?
250
310
  end
251
311
 
252
312
  ##
@@ -278,13 +338,19 @@ class MogileFS::Admin < MogileFS::Client
278
338
  # "altip"=>"",
279
339
  # "altmask"=>""}]
280
340
 
281
- def clean(count, prefix, res, underscore = true)
282
- underscore = underscore ? '_' : ''
341
+ def clean(count, prefix, res, underscore = true, to_i = [])
342
+ empty = ""
343
+ underscore = underscore ? '_' : empty
344
+ keys = res.keys
283
345
  (1..res[count].to_i).map do |i|
284
- dev = res.select { |k,_| k =~ /^#{prefix}#{i}#{underscore}/ }.map do |k,v|
285
- [k.sub(/^#{prefix}#{i}#{underscore}/, ''), v]
346
+ re = /^#{prefix}#{i}#{underscore}/
347
+ row = {}
348
+ keys.grep(re).each do |k|
349
+ v = res[k]
350
+ k = k.sub(re, empty).freeze
351
+ row[k] = to_i.include?(k) ? (empty == v ? nil : v.to_i) : v
286
352
  end
287
- Hash[*dev.flatten]
353
+ row
288
354
  end
289
355
  end
290
356