admin: use Object#__send__ since Object#send can be overwritten
[ruby-mogilefs-client.git] / lib / mogilefs / admin.rb
blobb940902fe172cf5b363df1250b60635cb7f59e2a
1 # -*- encoding: binary -*-
2 ##
3 # A MogileFS Administration Client
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"=>"",
32   #     "http_port"=>"",
33   #     "hostid"=>"1",
34   #     "hostip"=>"",
35   #     "hostname"=>"rur-1",
36   #     "remoteroot"=>"/mnt/mogilefs/rur-1",
37   #     "altip"=>"",
38   #     "altmask"=>""}]
40   def get_hosts(hostid = nil)
41     clean('hosts', 'host',
42           @backend.get_hosts(hostid ? { :hostid => hostid } : {}))
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"=>"",
55   #     "mb_free"=>"0",
56   #     "devid"=>"1",
57   #     "hostid"=>"1",
58   #     "mb_used"=>"",
59   #     "mb_total"=>""}]
61   def get_devices(devid = nil)
62     clean('devices', 'dev',
63           @backend.get_devices(devid ? { :devid => devid } : {}))
64   end
66   ##
67   # Returns an Array of fid Hashes from +from_fid+ to +to_fid+.
68   #
69   #   admin.list_fids 0, 100
70   #
71   # Returns:
72   #
73   #   [{"fid"=>"99",
74   #     "class"=>"normal",
75   #     "domain"=>"test",
76   #     "devcount"=>"2",
77   #     "length"=>"4",
78   #     "key"=>"file_key"},
79   #    {"fid"=>"82",
80   #     "class"=>"normal",
81   #     "devcount"=>"2",
82   #     "domain"=>"test",
83   #     "length"=>"9",
84   #     "key"=>"new_new_key"}]
86   def list_fids(from_fid, to_fid)
87     clean('fid_count', 'fid_',
88           @backend.list_fids(:from => from_fid, :to => to_fid))
89   end
91   ##
92   # Returns a statistics structure representing the state of mogilefs.
93   #
94   #   admin.get_stats
95   #
96   # Returns:
97   #
98   #   {"fids"=>{"max"=>"99", "count"=>"2"},
99   #    "device"=>
100   #     [{"status"=>"alive", "files"=>"2", "id"=>"1", "host"=>"rur-1"},
101   #      {"status"=>"alive", "files"=>"2", "id"=>"2", "host"=>"rur-2"}],
102   #    "replication"=>
103   #     [{"files"=>"2", "class"=>"normal", "devcount"=>"2", "domain"=>"test"}],
104   #    "file"=>[{"files"=>"2", "class"=>"normal", "domain"=>"test"}]}
106   def get_stats(type = 'all')
107     res = @backend.stats type => 1
108     stats = {}
110     stats['device'] = clean 'devicescount', 'devices', res, false
111     stats['file'] = clean 'filescount', 'files', res, false
112     stats['replication'] = clean 'replicationcount', 'replication', res, false
114     if res['fidmax'] or res['fidcount'] then
115       stats['fids'] = {
116         'max' => res['fidmax'].to_i,
117         'count' => res['fidcount'].to_i
118       }
119     end
121     stats.delete 'device' if stats['device'].empty?
122     stats.delete 'file' if stats['file'].empty?
123     stats.delete 'replication' if stats['replication'].empty?
125     stats
126   end
128   ##
129   # Returns the domains and classes, and their policies present in the mogilefs.
130   #
131   #   admin.get_domains
132   #
133   # Returns (on newer MogileFS servers):
134   #   {
135   #     "test" => {
136   #       "default" => {
137   #         "mindevcount" => 2,
138   #         "replpolicy" => "MultipleHosts()"
139   #       }
140   #     }
141   #   }
142   #
143   # Returns (on older MogileFS servers without replication policies):
144   #
145   #   {"test"=>{"normal"=>3, "default"=>2}}
147   def get_domains
148     res = @backend.get_domains
149     have_replpolicy = false
151     domains = {}
152     (1..res['domains'].to_i).each do |i|
153       domain = clean "domain#{i}classes", "domain#{i}class", res, false
155       tmp = domains[res["domain#{i}"]] = {}
156       domain.each do |d|
157         tmp[d.delete("name")] = d
158         d["mindevcount"] = d["mindevcount"].to_i
159         have_replpolicy ||= d.include?("replpolicy")
160       end
161     end
163     # only for MogileFS 1.x?, maybe we can drop support for this...
164     unless have_replpolicy
165       domains.each do |namespace, class_data|
166         class_data.each do |class_name, data|
167           class_data[class_name] = data["mindevcount"]
168         end
169       end
170     end
172     domains
173   end
175   ##
176   # Creates a new domain named +domain+.  Returns nil if creation failed.
178   def create_domain(domain)
179     raise MogileFS::ReadOnlyError if readonly?
180     res = @backend.create_domain :domain => domain
181     res ? res['domain'] : nil
182   end
184   ##
185   # Deletes +domain+.  Returns true if successful, raises
186   # MogileFS::Backend::DomainNotFoundError if not
188   def delete_domain(domain)
189     raise MogileFS::ReadOnlyError if readonly?
190     ! @backend.delete_domain(:domain => domain).nil?
191   end
193   ##
194   # Creates a new class in +domain+ named +klass+ with +policy+ for
195   # replication.  Raises on failure.
197   def create_class(domain, klass, policy)
198     modify_class(domain, klass, policy, :create)
199   end
201   ##
202   # Updates class +klass+ in +domain+ with +policy+ for replication.
203   # Raises on failure.
205   def update_class(domain, klass, policy)
206     modify_class(domain, klass, policy, :update)
207   end
209   ##
210   # Removes class +klass+ from +domain+.  Returns true if successful.
211   # Raises on failure
213   def delete_class(domain, klass)
214     ! @backend.delete_class(:domain => domain, :class => klass).nil?
215   end
217   ##
218   # Creates a new host named +host+.  +args+ must contain :ip and :port.
219   # Returns true if successful, false if not.
221   def create_host(host, args = {})
222     raise ArgumentError, "Must specify ip and port" unless \
223       args.include? :ip and args.include? :port
225     modify_host(host, args, 'create')
226   end
228   ##
229   # Updates +host+ with +args+.  Returns true if successful, false if not.
231   def update_host(host, args = {})
232     modify_host(host, args, 'update')
233   end
235   ##
236   # Deletes host +host+.  Returns nil on failure.
238   def delete_host(host)
239     raise MogileFS::ReadOnlyError if readonly?
240     ! @backend.delete_host(:host => host).nil?
241   end
243   ##
244   # Changes the device status of +device+ on +host+ to +state+ which can be
245   # 'alive', 'down', or 'dead'.
247   def change_device_state(host, device, state)
248     raise MogileFS::ReadOnlyError if readonly?
249     ! @backend.set_state(:host => host, :device => device, :state => state).nil?
250   end
252   protected unless defined? $TESTING
254   ##
255   # Modifies +klass+ on +domain+ to store files on +mindevcount+ devices via
256   # +action+.  Returns the class name if successful, raises if not
258   def modify_class(domain, klass, policy, action)
259     raise MogileFS::ReadOnlyError if readonly?
260     args = { :domain => domain, :class => klass }
261     case policy
262     when Integer
263       args[:mindevcount] = policy
264     when String
265       args[:replpolicy] = policy
266     when Hash
267       args.merge!(policy)
268     else
269       raise ArgumentError,
270            "policy=#{policy.inspect} not understood for #{action}_class"
271     end
272     @backend.__send__("#{action}_class", args)["class"]
273   end
275   ##
276   # Modifies +host+ using +args+ via +action+.  Returns true if successful,
277   # false if not.
279   def modify_host(host, args = {}, action = 'create')
280     args[:host] = host
281     ! @backend.__send__("#{action}_host", args).nil?
282   end
284   ##
285   # Turns the response +res+ from the backend into an Array of Hashes from 1
286   # to res[+count+].  If +underscore+ is true then a '_' character is assumed
287   # between the prefix and the hash key value.
288   #
289   #   res = {"host1_remoteroot"=>"/mnt/mogilefs/rur-1",
290   #          "host1_hostname"=>"rur-1",
291   #          "host1_hostid"=>"1",
292   #          "host1_http_get_port"=>"",
293   #          "host1_altip"=>"",
294   #          "hosts"=>"1",
295   #          "host1_hostip"=>"",
296   #          "host1_http_port"=>"",
297   #          "host1_status"=>"alive",
298   #          "host1_altmask"=>""}
299   #   admin.clean 'hosts', 'host', res
300   #
301   # Returns:
302   #
303   #   [{"status"=>"alive",
304   #     "http_get_port"=>"",
305   #     "http_port"=>"",
306   #     "hostid"=>"1",
307   #     "hostip"=>"",
308   #     "hostname"=>"rur-1",
309   #     "remoteroot"=>"/mnt/mogilefs/rur-1",
310   #     "altip"=>"",
311   #     "altmask"=>""}]
313   def clean(count, prefix, res, underscore = true)
314     underscore = underscore ? '_' : ''
315     (1..res[count].to_i).map do |i|
316       dev = res.select { |k,_| k =~ /^#{prefix}#{i}#{underscore}/ }.map do |k,v|
317         [k.sub(/^#{prefix}#{i}#{underscore}/, ''), v]
318       end
319       Hash[*dev.flatten]
320     end
321   end