admin: add numeric conversions for consistency
[ruby-mogilefs-client.git] / lib / mogilefs / admin.rb
blob926b6cffb09e60906d42bf7b6b9d12ea10c04af9
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
7   ##
8   # Enumerates fids using #list_fids.
10   def each_fid
11     low = 0
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 }
18       low = high + 1
19     end
20   end
22   ##
23   # Returns an Array of host status Hashes.  If +hostid+ is given only that
24   # host is returned.
25   #
26   #   admin.get_hosts 1
27   #
28   # Returns:
29   #
30   #   [{"status"=>"alive",
31   #     "http_get_port"=>nil,
32   #     "http_port"=>"",
33   #     "hostid"=>"1",
34   #     "hostip"=>"",
35   #     "hostname"=>"rur-1",
36   #     "altip"=>"",
37   #     "altmask"=>""}]
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)
43   end
45   ##
46   # Returns an Array of device status Hashes.  If devid is given only that
47   # device is returned.
48   #
49   #   admin.get_devices 1
50   #
51   # Returns:
52   #
53   #   [{"status"=>"alive",
54   #     "mb_asof"=>nil,
55   #     "mb_free"=>666000,
56   #     "devid"=>1,
57   #     "hostid"=>1,
58   #     "mb_used"=>666,
59   #     "mb_total"=>666666}]
61   def get_devices(devid = nil)
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
73   end
75   ##
76   # Returns an Array of fid Hashes from +from_fid+ to +to_fid+.
77   #
78   #   admin.list_fids 0, 100
79   #
80   # Returns:
81   #
82   #   [{"fid"=>99,
83   #     "class"=>"normal",
84   #     "domain"=>"test",
85   #     "devcount"=>2,
86   #     "length"=>4,
87   #     "key"=>"file_key"},
88   #    {"fid"=>82,
89   #     "class"=>"normal",
90   #     "devcount"=>2,
91   #     "domain"=>"test",
92   #     "length"=>9,
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)
99   end
101   ##
102   # Returns a statistics structure representing the state of mogilefs.
103   #
104   # *** This command no longer works with recent versions of MogileFS ***
105   # *** Use mogstats(1) from the MogileFS::Utils package on CPAN ***
106   #
107   #   admin.get_stats
108   #
109   # Returns:
110   #
111   #   {"fids"=>{"max"=>"99", "count"=>"2"},
112   #    "device"=>
113   #     [{"status"=>"alive", "files"=>"2", "id"=>"1", "host"=>"rur-1"},
114   #      {"status"=>"alive", "files"=>"2", "id"=>"2", "host"=>"rur-2"}],
115   #    "replication"=>
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
121     stats = {}
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
128       stats['fids'] = {
129         'max' => res['fidmax'].to_i,
130         'count' => res['fidcount'].to_i
131       }
132     end
134     stats.delete 'device' if stats['device'].empty?
135     stats.delete 'file' if stats['file'].empty?
136     stats.delete 'replication' if stats['replication'].empty?
138     stats
139   end
141   ##
142   # Returns the domains and classes, and their policies present in the mogilefs.
143   #
144   #   admin.get_domains
145   #
146   # Returns (on newer MogileFS servers):
147   #   {
148   #     "test" => {
149   #       "default" => {
150   #         "mindevcount" => 2,
151   #         "replpolicy" => "MultipleHosts()"
152   #       }
153   #     }
154   #   }
155   #
156   # Returns (on older MogileFS servers without replication policies):
157   #
158   #   {"test"=>{"normal"=>3, "default"=>2}}
160   def get_domains
161     res = @backend.get_domains
162     have_replpolicy = false
164     domains = {}
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] = {}
170       domain.each do |d|
171         tmp[d.delete("name").freeze] = d
172         have_replpolicy ||= d.include?("replpolicy")
173       end
174     end
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"]
181         end
182       end
183     end
185     domains
186   end
188   ##
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
195   end
197   ##
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?
204   end
206   ##
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)
212   end
214   ##
215   # Updates class +klass+ in +domain+ with +policy+ for replication.
216   # Raises on failure.
218   def update_class(domain, klass, policy)
219     modify_class(domain, klass, policy, :update)
220   end
222   ##
223   # Removes class +klass+ from +domain+.  Returns true if successful.
224   # Raises on failure
226   def delete_class(domain, klass)
227     ! @backend.delete_class(:domain => domain, :class => klass).nil?
228   end
230   ##
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')
239   end
241   ##
242   # Updates +host+ with +args+.  Returns true if successful, false if not.
244   def update_host(host, args = {})
245     modify_host(host, args, 'update')
246   end
248   ##
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?
254   end
256   ##
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?
263   end
265   # reschedules all deferred replication, returns a hash with the number
266   # of files rescheduled:
267   #
268   #   admin.replicate_now => { "count" => 5 }
269   def replicate_now
270     rv = @backend.replicate_now
271     rv["count"] = rv["count"].to_i
272     rv
273   end
275   def clear_cache
276     @backend.clear_cache
277   end
279   protected unless defined? $TESTING
281   ##
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 }
288     case policy
289     when Integer
290       args[:mindevcount] = policy
291     when String
292       args[:replpolicy] = policy
293     when Hash
294       args.merge!(policy)
295     else
296       raise ArgumentError,
297            "policy=#{policy.inspect} not understood for #{action}_class"
298     end
299     @backend.__send__("#{action}_class", args)["class"]
300   end
302   ##
303   # Modifies +host+ using +args+ via +action+.  Returns true if successful,
304   # false if not.
306   def modify_host(host, args = {}, action = 'create')
307     args[:host] = host
308     ! @backend.__send__("#{action}_host", args).nil?
309   end
311   ##
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.
315   #
316   #   res = {"host1_remoteroot"=>"/mnt/mogilefs/rur-1",
317   #          "host1_hostname"=>"rur-1",
318   #          "host1_hostid"=>"1",
319   #          "host1_http_get_port"=>"",
320   #          "host1_altip"=>"",
321   #          "hosts"=>"1",
322   #          "host1_hostip"=>"",
323   #          "host1_http_port"=>"",
324   #          "host1_status"=>"alive",
325   #          "host1_altmask"=>""}
326   #   admin.clean 'hosts', 'host', res
327   #
328   # Returns:
329   #
330   #   [{"status"=>"alive",
331   #     "http_get_port"=>"",
332   #     "http_port"=>"",
333   #     "hostid"=>"1",
334   #     "hostip"=>"",
335   #     "hostname"=>"rur-1",
336   #     "remoteroot"=>"/mnt/mogilefs/rur-1",
337   #     "altip"=>"",
338   #     "altmask"=>""}]
340   def clean(count, prefix, res, underscore = true, to_i = [])
341     empty = ""
342     underscore = underscore ? '_' : empty
343     keys = res.keys
344     (1..res[count].to_i).map do |i|
345       re = /^#{prefix}#{i}#{underscore}/
346       row = {}
347       keys.grep(re).each do |k|
348         v = res[k]
349         k = k.sub(re, empty).freeze
350         row[k] = to_i.include?(k) ? (empty == v ? nil : v.to_i) : v
351       end
352       row
353     end
354   end