1 # -*- encoding: binary -*-
3 # \MogileFS administration client, this has little real-world usage
4 # and is considered a work-in-progress
5 class MogileFS::Admin < MogileFS::Client
8 # Enumerates fids using #list_fids. Returns the number of valid fids
16 fids.each { |fid| yield fid }
18 end while last = fids[-1] and low = last["fid"]
23 # Returns an Array of host status Hashes. If +hostid+ is given only that
30 # [{"status"=>"alive",
31 # "http_get_port"=>nil,
35 # "hostname"=>"rur-1",
39 def get_hosts(hostid = nil)
40 to_i = %w(hostid http_port http_get_port)
41 want = %w(status hostip hostname altip altmask).concat(to_i)
42 rv = @backend.get_hosts(hostid ? { :hostid => hostid } : {})
43 clean('hosts', 'host', rv, true, to_i, want)
47 # Returns an Array of device status Hashes. If devid is given only that
54 # [{"status"=>"alive",
61 # "reject_bad_md5"=>false,
62 # "observed_state"=>"writeable",
63 # "mb_total"=>666666}]
65 def get_devices(devid = nil)
66 to_i = %w(mb_asof mb_free mb_used mb_total devid weight hostid)
67 want = %w(status observed_state).concat(to_i)
68 rv = @backend.get_devices(devid ? { :devid => devid } : {})
69 rv = clean('devices', 'dev', rv, true, to_i, want)
70 ostates = Hash[%w(readable writeable unreachable).map! { |f| [f,f] }]
73 u = row["utilization"] and
74 row["utilization"] = nil == u ? nil : u.to_f
76 # maps "" to nil (for dead devices)
77 row["observed_state"] = ostates[row["observed_state"]]
79 # be sure we do not set this at all for pre-2.60 MogileFS-Server
80 case row["reject_bad_md5"]
82 row["reject_bad_md5"] = true
84 row["reject_bad_md5"] = false
90 # Returns an Array of fid Hashes from +from_fid+, limited to +count+
92 # admin.list_fids 0, 100
101 # "key"=>"file_key"},
107 # "key"=>"new_new_key"}]
109 def list_fids(from_fid, count = 100)
110 to_i = %w(fid devcount length)
111 want = %w(domain class key).concat(to_i)
112 rv = @backend.list_fids(:from => from_fid, :to => count)
113 # :to is now :count internally in mogilefsd
114 clean('fid_count', 'fid_', rv, true, to_i, want)
118 # Returns a statistics structure representing the state of mogilefs.
120 # *** This command no longer works with recent versions of MogileFS ***
121 # *** Use mogstats(1) from the MogileFS::Utils package on CPAN ***
127 # {"fids"=>{"max"=>"99", "count"=>"2"},
129 # [{"status"=>"alive", "files"=>"2", "id"=>"1", "host"=>"rur-1"},
130 # {"status"=>"alive", "files"=>"2", "id"=>"2", "host"=>"rur-2"}],
132 # [{"files"=>"2", "class"=>"normal", "devcount"=>"2", "domain"=>"test"}],
133 # "file"=>[{"files"=>"2", "class"=>"normal", "domain"=>"test"}]}
135 def get_stats(type = 'all')
136 res = @backend.stats type => 1
139 stats['device'] = clean 'devicescount', 'devices', res, false
140 stats['file'] = clean 'filescount', 'files', res, false
141 stats['replication'] = clean 'replicationcount', 'replication', res, false
143 if res['fidmax'] or res['fidcount'] then
145 'max' => res['fidmax'].to_i,
146 'count' => res['fidcount'].to_i
150 stats.delete 'device' if stats['device'].empty?
151 stats.delete 'file' if stats['file'].empty?
152 stats.delete 'replication' if stats['replication'].empty?
158 # Returns the domains and classes, and their policies present in the mogilefs.
162 # Returns (on newer MogileFS servers):
166 # "mindevcount" => 2,
167 # "replpolicy" => "MultipleHosts()",
173 # Returns (on older MogileFS servers without replication policies):
175 # {"test"=>{"normal"=>3, "default"=>2}}
178 res = @backend.get_domains
179 have_replpolicy = false
182 to_i = %w(mindevcount)
183 want = %w(name replpolicy hashtype mindevcount)
184 (1..res['domains'].to_i).each do |i|
185 domain = clean("domain#{i}classes", "domain#{i}class", res, false, to_i,
187 tmp = domains[res["domain#{i}"]] = {}
189 tmp[d.delete("name")] = d
190 have_replpolicy ||= d.include?("replpolicy")
194 # only for MogileFS 1.x?, maybe we can drop support for this...
195 unless have_replpolicy
196 domains.each do |namespace, class_data|
197 class_data.each do |class_name, data|
198 class_data[class_name] = data["mindevcount"]
207 # Creates a new domain named +domain+. Returns nil if creation failed.
209 def create_domain(domain)
210 raise MogileFS::ReadOnlyError if readonly?
211 res = @backend.create_domain :domain => domain
212 res ? res['domain'] : nil
216 # Deletes +domain+. Returns true if successful, raises
217 # MogileFS::Backend::DomainNotFoundError if not
219 def delete_domain(domain)
220 raise MogileFS::ReadOnlyError if readonly?
221 ! @backend.delete_domain(:domain => domain).nil?
225 # Creates a new class in +domain+ named +klass+ with +policy+ for
226 # replication. Raises on failure.
228 def create_class(domain, klass, policy)
229 modify_class(domain, klass, policy, :create)
233 # Updates class +klass+ in +domain+ with +policy+ for replication.
236 def update_class(domain, klass, policy)
237 modify_class(domain, klass, policy, :update)
241 # Removes class +klass+ from +domain+. Returns true if successful.
244 def delete_class(domain, klass)
245 ! @backend.delete_class(:domain => domain, :class => klass).nil?
249 # Creates a new host named +host+. +args+ must contain :ip and :port.
250 # Returns true if successful, false if not.
252 def create_host(host, args = {})
253 raise ArgumentError, "Must specify ip and port" unless \
254 args.include? :ip and args.include? :port
256 modify_host(host, args, 'create')
260 # Updates +host+ with +args+. Returns true if successful, false if not.
262 def update_host(host, args = {})
263 modify_host(host, args, 'update')
267 # Deletes host +host+. Returns nil on failure.
269 def delete_host(host)
270 raise MogileFS::ReadOnlyError if readonly?
271 ! @backend.delete_host(:host => host).nil?
275 # Creates device with Integer +devid+ on +host+
276 # +host+ may be an integer for hostid or String for hostname
277 def create_device(host, devid, opts = {})
278 raise MogileFS::ReadOnlyError if readonly?
285 opts[:hostname] = host
287 raise ArgumentError, "host=#{host.inspect} is not a String or Integer"
291 ! @backend.create_device(opts).nil?
295 # Changes the device status of +device+ on +host+ to +state+ which can be
296 # 'alive', 'down', or 'dead'.
298 def change_device_state(host, device, state)
299 raise MogileFS::ReadOnlyError if readonly?
300 ! @backend.set_state(:host => host, :device => device, :state => state).nil?
304 # Changes the device weight of +device+ on +host+ to +weight+.
305 # +weight+ should be a non-negative Integer. Devices with higher
306 # +weight+ values are more likely to be chosen for reads and writes.
307 def change_device_weight(host, device, weight)
308 raise MogileFS::ReadOnlyError if readonly?
309 opts = { :host => host, :device => device, :weight => weight }
310 ! @backend.set_weight(opts).nil?
313 # reschedules all deferred replication, returns a hash with the number
314 # of files rescheduled:
316 # admin.replicate_now => { "count" => 5 }
318 rv = @backend.replicate_now
319 rv["count"] = rv["count"].to_i
323 # Clears the tracker caches. Not implemented in all versions of MogileFS
328 protected unless defined? $TESTING
331 # Modifies +klass+ on +domain+ to store files on +mindevcount+ devices via
332 # +action+. Returns the class name if successful, raises if not
334 def modify_class(domain, klass, policy, action)
335 raise MogileFS::ReadOnlyError if readonly?
336 args = { :domain => domain, :class => klass }
339 args[:mindevcount] = policy
341 args[:replpolicy] = policy
346 "policy=#{policy.inspect} not understood for #{action}_class"
348 @backend.__send__("#{action}_class", args)["class"]
352 # Modifies +host+ using +args+ via +action+. Returns true if successful,
355 def modify_host(host, args = {}, action = 'create')
357 ! @backend.__send__("#{action}_host", args).nil?
361 # Turns the response +res+ from the backend into an Array of Hashes from 1
362 # to res[+count+]. If +underscore+ is true then a '_' character is assumed
363 # between the prefix and the hash key value.
365 # res = {"host1_remoteroot"=>"/mnt/mogilefs/rur-1",
366 # "host1_hostname"=>"rur-1",
367 # "host1_hostid"=>"1",
368 # "host1_http_get_port"=>"",
371 # "host1_hostip"=>"",
372 # "host1_http_port"=>"",
373 # "host1_status"=>"alive",
374 # "host1_altmask"=>""}
375 # admin.clean 'hosts', 'host', res
379 # [{"status"=>"alive",
380 # "http_get_port"=>nil,
383 # "hostip"=>"192.168.1.3",
384 # "hostname"=>"rur-1",
388 def clean(count, prefix, res, underscore = true, to_i = [], want = nil)
390 underscore = underscore ? '_' : empty
392 # convert array to hash for O(1) lookups
393 to_i = to_i.inject({}) { |m,k| m[k] = m }
395 (1..res[count].to_i).map do |i|
398 v = res["#{prefix}#{i}#{underscore}#{k}"] or next
399 row[k] = to_i.include?(k) ? (empty == v ? nil : v.to_i) : v
405 (1..res[count].to_i).map do |i|
406 re = /^#{prefix}#{i}#{underscore}/
408 keys.grep(re).each do |k|
411 row[k] = to_i.include?(k) ? (empty == v ? nil : v.to_i) : v