mogilefs-client 2.2.0 → 3.0.0.rc1

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