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.
13 max = get_stats('fids')['fids']['max']
15 0.step max, 100 do |high|
16 fids = list_fids low, high
17 fids.each { |fid| yield 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 = { "hostid" => true, "http_port" => true, "http_get_port" => true }
41 clean('hosts', 'host',
42 @backend.get_hosts(hostid ? { :hostid => hostid } : {}), true, to_i)
46 # Returns an Array of device status Hashes. If devid is given only that
53 # [{"status"=>"alive",
59 # "mb_total"=>666666}]
61 def get_devices(devid = nil)
63 "mb_asof" => true, "mb_free" => true,
64 "mb_used" => true, "mb_total" => true ,
65 "devid" => true, "weight" => true, "hostid" => true
67 rv = @backend.get_devices(devid ? { :devid => devid } : {})
68 rv = clean('devices', 'dev', rv, true, to_i)
70 u = row["utilization"] and
71 row["utilization"] = nil == u ? nil : u.to_f
76 # Returns an Array of fid Hashes from +from_fid+ to +to_fid+.
78 # admin.list_fids 0, 100
93 # "key"=>"new_new_key"}]
95 def list_fids(from_fid, to_fid)
96 to_i = { "fid" => true, "devcount" => true, "length" => true }
97 clean('fid_count', 'fid_',
98 @backend.list_fids(:from => from_fid, :to => to_fid), true, to_i)
102 # Returns a statistics structure representing the state of mogilefs.
104 # *** This command no longer works with recent versions of MogileFS ***
105 # *** Use mogstats(1) from the MogileFS::Utils package on CPAN ***
111 # {"fids"=>{"max"=>"99", "count"=>"2"},
113 # [{"status"=>"alive", "files"=>"2", "id"=>"1", "host"=>"rur-1"},
114 # {"status"=>"alive", "files"=>"2", "id"=>"2", "host"=>"rur-2"}],
116 # [{"files"=>"2", "class"=>"normal", "devcount"=>"2", "domain"=>"test"}],
117 # "file"=>[{"files"=>"2", "class"=>"normal", "domain"=>"test"}]}
119 def get_stats(type = 'all')
120 res = @backend.stats type => 1
123 stats['device'] = clean 'devicescount', 'devices', res, false
124 stats['file'] = clean 'filescount', 'files', res, false
125 stats['replication'] = clean 'replicationcount', 'replication', res, false
127 if res['fidmax'] or res['fidcount'] then
129 'max' => res['fidmax'].to_i,
130 'count' => res['fidcount'].to_i
134 stats.delete 'device' if stats['device'].empty?
135 stats.delete 'file' if stats['file'].empty?
136 stats.delete 'replication' if stats['replication'].empty?
142 # Returns the domains and classes, and their policies present in the mogilefs.
146 # Returns (on newer MogileFS servers):
150 # "mindevcount" => 2,
151 # "replpolicy" => "MultipleHosts()"
156 # Returns (on older MogileFS servers without replication policies):
158 # {"test"=>{"normal"=>3, "default"=>2}}
161 res = @backend.get_domains
162 have_replpolicy = false
165 to_i = { "mindevcount" => true }
166 (1..res['domains'].to_i).each do |i|
167 domain = clean "domain#{i}classes", "domain#{i}class", res, false, to_i
169 tmp = domains[res["domain#{i}"].freeze] = {}
171 tmp[d.delete("name").freeze] = d
172 have_replpolicy ||= d.include?("replpolicy")
176 # only for MogileFS 1.x?, maybe we can drop support for this...
177 unless have_replpolicy
178 domains.each do |namespace, class_data|
179 class_data.each do |class_name, data|
180 class_data[class_name] = data["mindevcount"]
189 # Creates a new domain named +domain+. Returns nil if creation failed.
191 def create_domain(domain)
192 raise MogileFS::ReadOnlyError if readonly?
193 res = @backend.create_domain :domain => domain
194 res ? res['domain'] : nil
198 # Deletes +domain+. Returns true if successful, raises
199 # MogileFS::Backend::DomainNotFoundError if not
201 def delete_domain(domain)
202 raise MogileFS::ReadOnlyError if readonly?
203 ! @backend.delete_domain(:domain => domain).nil?
207 # Creates a new class in +domain+ named +klass+ with +policy+ for
208 # replication. Raises on failure.
210 def create_class(domain, klass, policy)
211 modify_class(domain, klass, policy, :create)
215 # Updates class +klass+ in +domain+ with +policy+ for replication.
218 def update_class(domain, klass, policy)
219 modify_class(domain, klass, policy, :update)
223 # Removes class +klass+ from +domain+. Returns true if successful.
226 def delete_class(domain, klass)
227 ! @backend.delete_class(:domain => domain, :class => klass).nil?
231 # Creates a new host named +host+. +args+ must contain :ip and :port.
232 # Returns true if successful, false if not.
234 def create_host(host, args = {})
235 raise ArgumentError, "Must specify ip and port" unless \
236 args.include? :ip and args.include? :port
238 modify_host(host, args, 'create')
242 # Updates +host+ with +args+. Returns true if successful, false if not.
244 def update_host(host, args = {})
245 modify_host(host, args, 'update')
249 # Deletes host +host+. Returns nil on failure.
251 def delete_host(host)
252 raise MogileFS::ReadOnlyError if readonly?
253 ! @backend.delete_host(:host => host).nil?
257 # Changes the device status of +device+ on +host+ to +state+ which can be
258 # 'alive', 'down', or 'dead'.
260 def change_device_state(host, device, state)
261 raise MogileFS::ReadOnlyError if readonly?
262 ! @backend.set_state(:host => host, :device => device, :state => state).nil?
265 # reschedules all deferred replication, returns a hash with the number
266 # of files rescheduled:
268 # admin.replicate_now => { "count" => 5 }
270 rv = @backend.replicate_now
271 rv["count"] = rv["count"].to_i
279 protected unless defined? $TESTING
282 # Modifies +klass+ on +domain+ to store files on +mindevcount+ devices via
283 # +action+. Returns the class name if successful, raises if not
285 def modify_class(domain, klass, policy, action)
286 raise MogileFS::ReadOnlyError if readonly?
287 args = { :domain => domain, :class => klass }
290 args[:mindevcount] = policy
292 args[:replpolicy] = policy
297 "policy=#{policy.inspect} not understood for #{action}_class"
299 @backend.__send__("#{action}_class", args)["class"]
303 # Modifies +host+ using +args+ via +action+. Returns true if successful,
306 def modify_host(host, args = {}, action = 'create')
308 ! @backend.__send__("#{action}_host", args).nil?
312 # Turns the response +res+ from the backend into an Array of Hashes from 1
313 # to res[+count+]. If +underscore+ is true then a '_' character is assumed
314 # between the prefix and the hash key value.
316 # res = {"host1_remoteroot"=>"/mnt/mogilefs/rur-1",
317 # "host1_hostname"=>"rur-1",
318 # "host1_hostid"=>"1",
319 # "host1_http_get_port"=>"",
322 # "host1_hostip"=>"",
323 # "host1_http_port"=>"",
324 # "host1_status"=>"alive",
325 # "host1_altmask"=>""}
326 # admin.clean 'hosts', 'host', res
330 # [{"status"=>"alive",
331 # "http_get_port"=>"",
335 # "hostname"=>"rur-1",
336 # "remoteroot"=>"/mnt/mogilefs/rur-1",
340 def clean(count, prefix, res, underscore = true, to_i = [])
342 underscore = underscore ? '_' : empty
344 (1..res[count].to_i).map do |i|
345 re = /^#{prefix}#{i}#{underscore}/
347 keys.grep(re).each do |k|
349 k = k.sub(re, empty).freeze
350 row[k] = to_i.include?(k) ? (empty == v ? nil : v.to_i) : v