File Coverage

blib/lib/MogileFS/Worker/Query.pm
Criterion Covered Total %
statement 33 973 3.3
branch 0 466 0.0
condition 0 187 0.0
subroutine 11 83 13.2
pod 0 58 0.0
total 44 1767 2.4


line stmt bran cond sub pod time code
1             package MogileFS::Worker::Query;
2             # responds to queries from Mogile clients
3              
4 21     21   91 use strict;
  21         33  
  21         835  
5 21     21   93 use warnings;
  21         33  
  21         679  
6              
7 21     21   92 use base 'MogileFS::Worker';
  21         25  
  21         7546  
8 21     21   119 use fields qw(querystarttime reqid callid);
  21         27  
  21         89  
9 21         1304 use MogileFS::Util qw(error error_code first weighted_list
10 21     21   1155 device_state eurl decode_url_args);
  21         33  
11 21     21   7756 use MogileFS::HTTPFile;
  21         45  
  21         583  
12 21     21   8218 use MogileFS::Rebalance;
  21         45  
  21         657  
13 21     21   130 use MogileFS::Config;
  21         30  
  21         2645  
14 21     21   105 use MogileFS::Server;
  21         30  
  21         12695  
15              
16             sub new {
17 0     0 0   my ($class, $psock) = @_;
18 0           my $self = fields::new($class);
19 0           $self->SUPER::new($psock);
20              
21 0           $self->{querystarttime} = undef;
22 0           $self->{reqid} = undef;
23 0           $self->{callid} = undef;
24 0           return $self;
25             }
26              
27             # no query should take 30 seconds, and we check in every 5 seconds.
28 0     0 0   sub watchdog_timeout { 30 }
29              
30             # called by plugins to register a command in the namespace
31             sub register_command {
32 0     0 0   my ($cmd, $sub) = @_;
33              
34             # validate the command, then convert it to the actual thing the user
35             # will be calling
36 0 0         return 0 unless $cmd =~ /^[\w\d]+$/;
37 0           $cmd = "plugin_$cmd";
38              
39             # register in namespace with 'cmd_' which we will automatically find
40 21     21   157 no strict 'refs';
  21         37  
  21         8705  
41 0           *{"cmd_$cmd"} = $sub;
  0            
42              
43             # all's well
44 0           return 1;
45             }
46              
47             sub work {
48 0     0 0   my $self = shift;
49 0           my $psock = $self->{psock};
50 0           my $rin = '';
51 0           vec($rin, fileno($psock), 1) = 1;
52 0           my $buf;
53              
54 0           while (1) {
55 0           my $rout;
56 0 0         unless (select($rout=$rin, undef, undef, 5.0)) {
57 0           $self->still_alive;
58 0           next;
59             }
60              
61 0           my $newread;
62 0           my $rv = sysread($psock, $newread, Mgd::UNIX_RCVBUF_SIZE());
63 0 0         if (!$rv) {
64 0 0         if (defined $rv) {
65 0           die "While reading pipe from parent, got EOF. Parent's gone. Quitting.\n";
66             } else {
67 0           die "Error reading pipe from parent: $!\n";
68             }
69             }
70 0           $buf .= $newread;
71              
72 0           while ($buf =~ s/^(.+?)\r?\n//) {
73 0           my $line = $1;
74 0 0         if ($self->process_generic_command(\$line)) {
75 0           $self->still_alive; # no-op for watchdog
76             } else {
77 0           $self->validate_dbh;
78 0           $self->process_line(\$line);
79             }
80             }
81             }
82             }
83              
84             sub process_line {
85 0     0 0   my MogileFS::Worker::Query $self = shift;
86 0           my $lineref = shift;
87              
88             # see what kind of command this is
89 0 0         return $self->err_line('unknown_command')
90             unless $$lineref =~ /^(\d+-\d+)?\s*(\S+)\s*(.*)/;
91              
92 0   0       $self->{reqid} = $1 || undef;
93 0           my ($client_ip, $line) = ($2, $3);
94              
95             # set global variables for zone determination
96 0           local $MogileFS::REQ_client_ip = $client_ip;
97              
98             # Use as array here, otherwise we get a string which breaks usage of
99             # Time::HiRes::tv_interval further on.
100 0           $self->{querystarttime} = [ Time::HiRes::gettimeofday() ];
101              
102             # fallback to normal command handling
103 0 0         if ($line =~ /^(\w+)\s*(.*)/) {
104 0           my ($cmd, $orig_args) = ($1, $2);
105 0           $cmd = lc($cmd);
106              
107 21     21   103 no strict 'refs';
  21         31  
  21         186957  
108 0           my $cmd_handler = *{"cmd_$cmd"}{CODE};
  0            
109 0           my $args = decode_url_args(\$orig_args);
110 0           $self->{callid} = $args->{callid};
111 0 0         if ($cmd_handler) {
112 0   0       local $MogileFS::REQ_altzone = ($args->{zone} && $args->{zone} eq 'alt');
113 0           eval {
114 0           $cmd_handler->($self, $args);
115             };
116 0 0         if ($@) {
117 0           my $errc = error_code($@);
118 0 0         if ($errc eq "dup") {
119 0           return $self->err_line("dup");
120             } else {
121 0           warn "Error: $@\n";
122 0           error("Error running command '$cmd': $@");
123 0           return $self->err_line("failure");
124             }
125             }
126 0           return;
127             }
128             }
129              
130 0           return $self->err_line('unknown_command');
131             }
132              
133             # this is a half-finished command. in particular, errors tend to
134             # crash the parent or child or something. it's a quick hack for a quick
135             # ops task that needs done. note in particular how it reaches across
136             # package boundaries into an API that the Replicator probably doesn't
137             # want exposed.
138             sub cmd_httpcopy {
139 0     0 0   my MogileFS::Worker::Query $self = shift;
140 0           my $args = shift;
141 0           my $sdevid = $args->{sdevid};
142 0           my $ddevid = $args->{ddevid};
143 0           my $fid = $args->{fid};
144              
145 0           my $err;
146 0           my $rv = MogileFS::Worker::Replicate::http_copy(sdevid => $sdevid,
147             ddevid => $ddevid,
148             fid => $fid,
149             errref => \$err);
150 0 0         if ($rv) {
151 0           my $dfid = MogileFS::DevFID->new($ddevid, $fid);
152 0 0         $dfid->add_to_db
153             or return $self->err_line("copy_err", "failed to add link to database");
154 0           return $self->ok_line;
155             } else {
156 0           return $self->err_line("copy_err", $err);
157             }
158             }
159              
160             # returns 0 on error, or dmid of domain
161             sub check_domain {
162 0     0 0   my MogileFS::Worker::Query $self = shift;
163 0           my $args = shift;
164              
165 0           my $domain = $args->{domain};
166              
167 0 0 0       return $self->err_line("no_domain") unless defined $domain && length $domain;
168              
169             # validate domain
170 0 0         my $dmid = eval { Mgd::domain_factory()->get_by_name($domain)->id } or
  0            
171             return $self->err_line("unreg_domain");
172              
173 0           return $dmid;
174             }
175              
176             sub cmd_sleep {
177 0     0 0   my MogileFS::Worker::Query $self = shift;
178 0           my $args = shift;
179 0   0       sleep($args->{duration} || 10);
180 0           return $self->ok_line;
181             }
182              
183             sub cmd_test {
184 0     0 0   my MogileFS::Worker::Query $self = shift;
185 0           my $args = shift;
186 0 0         die "Crashed on purpose" if $args->{crash};
187 0           return $self->ok_line;
188             }
189              
190             sub cmd_clear_cache {
191 0     0 0   my MogileFS::Worker::Query $self = shift;
192              
193 0           $self->forget_that_monitor_has_run;
194 0           $self->send_to_parent(":refresh_monitor");
195 0           $self->wait_for_monitor;
196              
197 0           return $self->ok_line(@_);
198             }
199              
200             sub cmd_create_open {
201 0     0 0   my MogileFS::Worker::Query $self = shift;
202 0           my $args = shift;
203              
204             # has to be filled out for some plugins
205 0 0         $args->{dmid} = $self->check_domain($args) or return;
206              
207             # first, pass this to a hook to do any manipulations needed
208 0           eval {MogileFS::run_global_hook('cmd_create_open', $args)};
  0            
209              
210 0 0         return $self->err_line("plugin_aborted", "$@")
211             if $@;
212              
213             # validate parameters
214 0           my $dmid = $args->{dmid};
215 0   0       my $key = $args->{key} || "";
216 0 0         my $multi = $args->{multi_dest} ? 1 : 0;
217 0   0       my $size = $args->{size} || undef; # Size is optional at create time,
218             # but used to grep devices if available
219              
220             # optional profiling of stages, if $args->{debug_profile}
221 0           my @profpoints; # array of [point,hires-starttime]
222             my $profstart = sub {
223 0     0     my $pt = shift;
224 0           push @profpoints, [$pt, Time::HiRes::time()];
225 0           };
226 0 0   0     $profstart = sub {} unless $args->{debug_profile};
  0            
227 0           $profstart->("begin");
228              
229             # we want it to be undef if not explicit, else force to numeric
230 0 0         my $exp_fidid = $args->{fid} ? int($args->{fid}) : undef;
231              
232             # get DB handle
233 0           my $sto = Mgd::get_store();
234              
235             # figure out what classid this file is for
236 0   0       my $class = $args->{class} || "";
237 0           my $classid = 0;
238 0 0         if (length($class)) {
239 0 0         $classid = eval { Mgd::class_factory()->get_by_name($dmid, $class)->id }
  0            
240             or return $self->err_line("unreg_class");
241             }
242              
243             # if we haven't heard from the monitoring job yet, we need to chill a bit
244             # to prevent a race where we tell a user that we can't create a file when
245             # in fact we've just not heard from the monitor
246 0           $profstart->("wait_monitor");
247 0           $self->wait_for_monitor;
248              
249 0           $profstart->("find_deviceid");
250              
251 0           my @devices = Mgd::device_factory()->get_all;
252 0 0         if ($size) {
253             # We first ignore all the devices with an unknown space free.
254 0 0         @devices = grep { length($_->mb_free) && ($_->mb_free * 1024*1024) > $size } @devices;
  0            
255              
256             # If we didn't find any, try all the devices with an unknown space free.
257             # This may happen if mogstored isn't running.
258 0 0         if (!@devices) {
259 0           @devices = grep { !length($_->mb_free) } Mgd::device_factory()->get_all;
  0            
260             }
261             }
262              
263 0 0         unless (MogileFS::run_global_hook('cmd_create_open_order_devices', [ @devices ], \@devices)) {
264 0           @devices = sort_devs_by_freespace(@devices);
265             }
266              
267             # find suitable device(s) to put this file on.
268 0           my @dests; # MogileFS::Device objects which are suitable
269              
270 0 0         while (scalar(@dests) < ($multi ? 3 : 1)) {
271 0           my $ddev = shift @devices;
272              
273 0 0         last unless $ddev;
274 0 0         next unless $ddev->not_on_hosts(map { $_->host } @dests);
  0            
275              
276 0           push @dests, $ddev;
277             }
278 0 0         return $self->err_line("no_devices") unless @dests;
279              
280 0           my $fidid = eval {
281 0           $sto->register_tempfile(
282             fid => $exp_fidid, # may be undef/NULL to mean auto-increment
283             dmid => $dmid,
284             key => $key,
285             classid => $classid,
286 0           devids => join(',', map { $_->id } @dests),
287             );
288             };
289 0 0         unless ($fidid) {
290 0           my $errc = error_code($@);
291 0 0         return $self->err_line("fid_in_use") if $errc eq "dup";
292 0           warn "Error registering tempfile: $@\n";
293 0           return $self->err_line("db");
294             }
295              
296             # make sure directories exist for client to be able to PUT into
297 0           my %dir_done;
298 0           $profstart->("vivify_dir_on_all_devs");
299              
300 0           my $t0 = Time::HiRes::time();
301 0           foreach my $dev (@dests) {
302 0           my $dfid = MogileFS::DevFID->new($dev, $fidid);
303             $dfid->vivify_directories(sub {
304 0     0     $dir_done{$dfid->devid} = Time::HiRes::time() - $t0;
305 0           });
306             }
307              
308             # don't start the event loop if results are all cached
309 0 0         if (scalar keys %dir_done != scalar @dests) {
310 0     0     Danga::Socket->SetPostLoopCallback(sub { scalar keys %dir_done != scalar @dests });
  0            
311 0           Danga::Socket->EventLoop;
312             }
313 0           $profstart->("end");
314              
315             # common reply variables
316 0           my $res = {
317             fid => $fidid,
318             };
319              
320             # add profiling data
321 0 0         if (@profpoints) {
322 0           $res->{profpoints} = 0;
323 0           for (my $i=0; $i<$#profpoints; $i++) {
324 0           my $ptnum = ++$res->{profpoints};
325 0           $res->{"prof_${ptnum}_name"} = $profpoints[$i]->[0];
326 0           $res->{"prof_${ptnum}_time"} =
327             sprintf("%0.03f",
328             $profpoints[$i+1]->[1] - $profpoints[$i]->[1]);
329             }
330 0           while (my ($devid, $time) = each %dir_done) {
331 0           my $ptnum = ++$res->{profpoints};
332 0           $res->{"prof_${ptnum}_name"} = "vivify_dir_on_dev$devid";
333 0           $res->{"prof_${ptnum}_time"} = sprintf("%0.03f", $time);
334             }
335             }
336              
337             # add path info
338 0 0         if ($multi) {
339 0           my $ct = 0;
340 0           foreach my $dev (@dests) {
341 0           $ct++;
342 0           $res->{"devid_$ct"} = $dev->id;
343 0           $res->{"path_$ct"} = MogileFS::DevFID->new($dev, $fidid)->url;
344             }
345 0           $res->{dev_count} = $ct;
346             } else {
347 0           $res->{devid} = $dests[0]->id;
348 0           $res->{path} = MogileFS::DevFID->new($dests[0], $fidid)->url;
349             }
350              
351 0           return $self->ok_line($res);
352             }
353              
354             sub sort_devs_by_freespace {
355 0           my @devices_with_weights = map {
356 0           [$_, 100 * $_->percent_free]
357             } sort {
358 0           $b->percent_free <=> $a->percent_free;
359             } grep {
360 0     0 0   $_->should_get_new_files;
361             } @_;
362              
363 0           my @list =
364             MogileFS::Util::weighted_list(splice(@devices_with_weights, 0, 20));
365              
366 0           return @list;
367             }
368              
369             sub valid_key {
370 0     0 0   my ($key) = @_;
371              
372 0   0       return defined($key) && length($key);
373             }
374              
375             sub cmd_create_close {
376 0     0 0   my MogileFS::Worker::Query $self = shift;
377 0           my $args = shift;
378              
379             # has to be filled out for some plugins
380 0 0         $args->{dmid} = $self->check_domain($args) or return;
381              
382             # call out to a hook that might modify the arguments for us
383 0           MogileFS::run_global_hook('cmd_create_close', $args);
384              
385             # late validation of parameters
386 0           my $dmid = $args->{dmid};
387 0           my $key = $args->{key};
388 0 0         my $fidid = $args->{fid} or return $self->err_line("no_fid");
389 0 0         my $devid = $args->{devid} or return $self->err_line("no_devid");
390 0 0         my $path = $args->{path} or return $self->err_line("no_path");
391 0           my $checksum = $args->{checksum};
392              
393 0 0         if ($checksum) {
394 0           $checksum = eval { MogileFS::Checksum->from_string($fidid, $checksum) };
  0            
395 0 0         return $self->err_line("invalid_checksum_format") if $@;
396             }
397              
398 0           my $fid = MogileFS::FID->new($fidid);
399 0           my $dfid = MogileFS::DevFID->new($devid, $fid);
400              
401             # is the provided path what we'd expect for this fid/devid?
402 0 0         return $self->err_line("bogus_args")
403             unless $path eq $dfid->url;
404              
405 0           my $sto = Mgd::get_store();
406              
407             # find the temp file we're closing and making real. If another worker
408             # already has it, bail out---the client closed it twice.
409             # this is racy, but the only expected use case is a client retrying.
410             # should still be fixed better once more scalable locking is available.
411 0 0         my $trow = $sto->delete_and_return_tempfile_row($fidid) or
412             return $self->err_line("no_temp_file");
413              
414             # Protect against leaving orphaned uploads.
415             my $failed = sub {
416 0     0     $dfid->add_to_db;
417 0           $fid->delete;
418 0           };
419              
420 0 0         unless ($trow->{devids} =~ m/\b$devid\b/) {
421 0           $failed->();
422 0           return $self->err_line("invalid_destdev", "File uploaded to invalid dest $devid. Valid devices were: " . $trow->{devids});
423             }
424              
425             # if a temp file is closed without a provided-key, that means to
426             # delete it.
427 0 0         unless (valid_key($key)) {
428 0           $failed->();
429 0           return $self->ok_line;
430             }
431              
432             # get size of file and verify that it matches what we were given, if anything
433 0           my $httpfile = MogileFS::HTTPFile->at($path);
434 0           my $size = $httpfile->size;
435              
436             # size check is optional? Needs to support zero byte files.
437 0 0         $args->{size} = -1 unless $args->{size};
438 0 0 0       if (!defined($size) || $size == MogileFS::HTTPFile::FILE_MISSING) {
439             # storage node is unreachable or the file is missing
440 0 0         my $type = defined $size ? "missing" : "cantreach";
441 0           my $lasterr = MogileFS::Util::last_error();
442 0           $failed->();
443 0           return $self->err_line("size_verify_error", "Expected: $args->{size}; actual: 0 ($type); path: $path; error: $lasterr")
444             }
445              
446 0 0 0       if ($args->{size} > -1 && ($args->{size} != $size)) {
447 0           $failed->();
448 0           return $self->err_line("size_mismatch", "Expected: $args->{size}; actual: $size; path: $path")
449             }
450              
451             # checksum validation is optional as it can be very expensive
452             # However, we /always/ verify it if the client wants us to, even
453             # if the class does not enforce or store it.
454 0 0 0       if ($checksum && $args->{checksumverify}) {
455 0           my $alg = $checksum->hashname;
456 0     0     my $actual = $httpfile->digest($alg, sub { $self->still_alive });
  0            
457 0 0         if ($actual ne $checksum->{checksum}) {
458 0           $failed->();
459 0           $actual = "$alg:" . unpack("H*", $actual);
460 0           return $self->err_line("checksum_mismatch",
461             "Expected: $checksum; actual: $actual; path: $path");
462             }
463             }
464              
465             # see if we have a fid for this key already
466 0           my $old_fid = MogileFS::FID->new_from_dmid_and_key($dmid, $key);
467 0 0         if ($old_fid) {
468             # Fail if a file already exists for this fid. Should never
469             # happen, as it should not be possible to close a file twice.
470 0 0         return $self->err_line("fid_exists")
471             unless $old_fid->{fidid} != $fidid;
472              
473 0           $old_fid->delete;
474             }
475              
476             # TODO: check for EIO?
477              
478             # insert file_on row
479 0           $dfid->add_to_db;
480              
481 0 0         $checksum->maybe_save($dmid, $trow->{classid}) if $checksum;
482              
483 0           $sto->replace_into_file(
484             fidid => $fidid,
485             dmid => $dmid,
486             key => $key,
487             length => $size,
488             classid => $trow->{classid},
489             devcount => 1,
490             );
491              
492             # mark it as needing replicating:
493 0           $fid->enqueue_for_replication();
494              
495             # call the hook - if this fails, we need to back the file out
496 0           my $rv = MogileFS::run_global_hook('file_stored', $args);
497 0 0 0       if (defined $rv && ! $rv) { # undef = no hooks, 1 = success, 0 = failure
498 0           $fid->delete;
499 0           return $self->err_line("plugin_aborted");
500             }
501              
502             # all went well, we would've hit condthrow on DB errors
503 0           return $self->ok_line;
504             }
505              
506             sub cmd_updateclass {
507 0     0 0   my MogileFS::Worker::Query $self = shift;
508 0           my $args = shift;
509              
510 0 0         $args->{dmid} = $self->check_domain($args) or return;
511              
512             # call out to a hook that might modify the arguments for us, abort if it tells us to
513 0           my $rv = MogileFS::run_global_hook('cmd_updateclass', $args);
514 0 0 0       return $self->err_line('plugin_aborted') if defined $rv && ! $rv;
515              
516 0           my $dmid = $args->{dmid};
517 0           my $key = $args->{key};
518 0 0         valid_key($key) or return $self->err_line("no_key");
519 0 0         my $class = $args->{class} or return $self->err_line("no_class");
520              
521 0 0         my $classobj = Mgd::class_factory()->get_by_name($dmid, $class)
522             or return $self->err_line('class_not_found');
523 0           my $classid = $classobj->id;
524              
525 0 0         my $fid = MogileFS::FID->new_from_dmid_and_key($dmid, $key)
526             or return $self->err_line('invalid_key');
527              
528 0           my @devids = $fid->devids;
529 0 0         return $self->err_line("no_devices") unless @devids;
530              
531 0 0         if ($fid->classid != $classid) {
532 0           $fid->update_class(classid => $classid);
533 0           $fid->enqueue_for_replication();
534             }
535              
536 0           return $self->ok_line;
537             }
538              
539             sub cmd_delete {
540 0     0 0   my MogileFS::Worker::Query $self = shift;
541 0           my $args = shift;
542              
543             # validate domain for plugins
544 0 0         $args->{dmid} = $self->check_domain($args) or return;
545              
546             # now invoke the plugin, abort if it tells us to
547 0           my $rv = MogileFS::run_global_hook('cmd_delete', $args);
548 0 0 0       return $self->err_line('plugin_aborted')
549             if defined $rv && ! $rv;
550              
551             # validate parameters
552 0           my $dmid = $args->{dmid};
553 0           my $key = $args->{key};
554              
555 0 0         valid_key($key) or return $self->err_line("no_key");
556              
557             # is this fid still owned by this key?
558 0 0         my $fid = MogileFS::FID->new_from_dmid_and_key($dmid, $key)
559             or return $self->err_line("unknown_key");
560              
561 0           $fid->delete;
562              
563 0           return $self->ok_line;
564             }
565              
566             # Takes either domain/dkey or fid and tries to return as much as possible.
567             sub cmd_file_debug {
568 0     0 0   my MogileFS::Worker::Query $self = shift;
569 0           my $args = shift;
570             # Talk to the master since this is "debug mode"
571 0           my $sto = Mgd::get_store();
572 0           my $ret = {};
573              
574             # If a FID is provided, just use that.
575 0           my $fid;
576             my $fidid;
577 0 0         if ($args->{fid}) {
578 0           $fidid = $args->{fid}+0;
579             # It's not fatal if we don't find the row here.
580 0           $fid = $sto->file_row_from_fidid($args->{fid}+0);
581             } else {
582             # If not, require dmid/dkey and pick up the fid from there.
583 0 0         $args->{dmid} = $self->check_domain($args) or return;
584 0 0         return $self->err_line("no_key") unless valid_key($args->{key});
585            
586             # now invoke the plugin, abort if it tells us to
587 0           my $rv = MogileFS::run_global_hook('cmd_file_debug', $args);
588 0 0 0       return $self->err_line('plugin_aborted')
589             if defined $rv && ! $rv;
590              
591 0           $fid = $sto->file_row_from_dmid_key($args->{dmid}, $args->{key});
592 0 0         return $self->err_line("unknown_key") unless $fid;
593 0           $fidid = $fid->{fid};
594             }
595              
596 0 0         if ($fid) {
597 0           $fid->{domain} = Mgd::domain_factory()->get_by_id($fid->{dmid})->name;
598 0           $fid->{class} = Mgd::class_factory()->get_by_id($fid->{dmid},
599             $fid->{classid})->name;
600             }
601              
602             # Fetch all of the queue data.
603 0           my $tfile = $sto->tempfile_row_from_fid($fidid);
604 0           my $repl = $sto->find_fid_from_file_to_replicate($fidid);
605 0           my $del = $sto->find_fid_from_file_to_delete2($fidid);
606 0           my $reb = $sto->find_fid_from_file_to_queue($fidid, REBAL_QUEUE);
607 0           my $fsck = $sto->find_fid_from_file_to_queue($fidid, FSCK_QUEUE);
608              
609             # Fetch file_on rows, and turn into paths.
610 0           my @devids = $sto->fid_devids($fidid);
611 0           for my $devid (@devids) {
612             # Won't matter if we can't make the path (dev is dead/deleted/etc)
613 0           eval {
614 0           my $dfid = MogileFS::DevFID->new($devid, $fidid);
615 0           my $path = $dfid->get_url;
616 0           $ret->{'devpath_' . $devid} = $path;
617             };
618             }
619 0 0         $ret->{devids} = join(',', @devids) if @devids;
620              
621             # Always look for a checksum
622 0           my $checksum = Mgd::get_store()->get_checksum($fidid);
623 0 0         if ($checksum) {
624 0           $checksum = MogileFS::Checksum->new($checksum);
625 0           $ret->{checksum} = $checksum->info;
626             } else {
627 0           $ret->{checksum} = 'NONE';
628             }
629              
630             # Return file row (if found) and all other data.
631 0           my %toret = (fid => $fid, tempfile => $tfile, replqueue => $repl,
632             delqueue => $del, rebqueue => $reb, fsckqueue => $fsck);
633 0           while (my ($key, $hash) = each %toret) {
634 0           while (my ($name, $val) = each %$hash) {
635 0           $ret->{$key . '_' . $name} = $val;
636             }
637             }
638              
639 0 0         return $self->err_line("unknown_fid") unless keys %$ret;
640 0           return $self->ok_line($ret);
641             }
642              
643             sub cmd_file_info {
644 0     0 0   my MogileFS::Worker::Query $self = shift;
645 0           my $args = shift;
646              
647             # validate domain for plugins
648 0 0         $args->{dmid} = $self->check_domain($args) or return;
649              
650             # now invoke the plugin, abort if it tells us to
651 0           my $rv = MogileFS::run_global_hook('cmd_file_info', $args);
652 0 0 0       return $self->err_line('plugin_aborted')
653             if defined $rv && ! $rv;
654              
655             # validate parameters
656 0           my $dmid = $args->{dmid};
657 0           my $key = $args->{key};
658              
659 0 0         valid_key($key) or return $self->err_line("no_key");
660              
661 0           my $fid;
662             Mgd::get_store()->slaves_ok(sub {
663 0     0     $fid = MogileFS::FID->new_from_dmid_and_key($dmid, $key);
664 0           });
665 0 0         $fid or return $self->err_line("unknown_key");
666              
667 0           my $ret = {};
668 0           $ret->{fid} = $fid->id;
669 0           $ret->{domain} = Mgd::domain_factory()->get_by_id($fid->dmid)->name;
670 0           my $class = Mgd::class_factory()->get_by_id($fid->dmid, $fid->classid);
671 0           $ret->{class} = $class->name;
672 0 0         if ($class->{hashtype}) {
673 0           my $checksum = Mgd::get_store()->get_checksum($fid->id);
674 0 0         if ($checksum) {
675 0           $checksum = MogileFS::Checksum->new($checksum);
676 0           $ret->{checksum} = $checksum->info;
677             } else {
678 0           $ret->{checksum} = "MISSING";
679             }
680             }
681 0           $ret->{key} = $key;
682 0           $ret->{'length'} = $fid->length;
683 0           $ret->{devcount} = $fid->devcount;
684             # Only if requested, also return the raw devids.
685             # Caller should use get_paths if they intend to fetch the file.
686 0 0         if ($args->{devices}) {
687 0           $ret->{devids} = join(',', $fid->devids);
688             }
689              
690 0           return $self->ok_line($ret);
691             }
692              
693             sub cmd_list_fids {
694 0     0 0   my MogileFS::Worker::Query $self = shift;
695 0           my $args = shift;
696              
697             # validate parameters
698 0   0       my $fromfid = ($args->{from} || 0)+0;
699 0   0       my $count = ($args->{to} || 0)+0;
700 0   0       $count ||= 100;
701 0 0 0       $count = 500 if $count > 500 || $count < 0;
702              
703 0           my $rows = Mgd::get_store()->file_row_from_fidid_range($fromfid, $count);
704 0 0         return $self->err_line('failure') unless $rows;
705 0 0         return $self->ok_line({ fid_count => 0 }) unless @$rows;
706              
707             # setup temporary storage of class/host
708 0           my (%domains, %classes);
709              
710             # now iterate over our data rows and construct result
711 0           my $ct = 0;
712 0           my $ret = {};
713 0           foreach my $r (@$rows) {
714 0           $ct++;
715 0           my $fid = $r->{fid};
716 0           $ret->{"fid_${ct}_fid"} = $fid;
717 0   0       $ret->{"fid_${ct}_domain"} = ($domains{$r->{dmid}} ||=
718             Mgd::domain_factory()->get_by_id($r->{dmid})->name);
719 0   0       $ret->{"fid_${ct}_class"} = ($classes{$r->{dmid}}{$r->{classid}} ||=
720             Mgd::class_factory()->get_by_id($r->{dmid}, $r->{classid})->name);
721 0           $ret->{"fid_${ct}_key"} = $r->{dkey};
722 0           $ret->{"fid_${ct}_length"} = $r->{length};
723 0           $ret->{"fid_${ct}_devcount"} = $r->{devcount};
724             }
725 0           $ret->{fid_count} = $ct;
726 0           return $self->ok_line($ret);
727             }
728              
729             sub cmd_list_keys {
730 0     0 0   my MogileFS::Worker::Query $self = shift;
731 0           my $args = shift;
732              
733             # validate parameters
734 0 0         my $dmid = $self->check_domain($args) or return;
735 0           my ($prefix, $after, $limit) = ($args->{prefix}, $args->{after}, $args->{limit});
736              
737 0 0 0       if (defined $prefix and $prefix ne '') {
738             # now validate that after matches prefix
739 0 0 0       return $self->err_line('after_mismatch')
740             if $after && $after !~ /^$prefix/;
741             }
742              
743 0   0       $limit ||= 1000;
744 0           $limit += 0;
745 0 0         $limit = 1000 if $limit > 1000;
746              
747 0           my $keys = Mgd::get_store()->get_keys_like($dmid, $prefix, $after, $limit);
748              
749             # if we got nothing, say so
750 0 0 0       return $self->err_line('none_match') unless $keys && @$keys;
751              
752             # construct the output and send
753 0           my $ret = { key_count => 0, next_after => '' };
754 0           foreach my $key (@$keys) {
755 0           $ret->{key_count}++;
756 0 0         $ret->{next_after} = $key
757             if $key gt $ret->{next_after};
758 0           $ret->{"key_$ret->{key_count}"} = $key;
759             }
760 0           return $self->ok_line($ret);
761             }
762              
763             sub cmd_rename {
764 0     0 0   my MogileFS::Worker::Query $self = shift;
765 0           my $args = shift;
766              
767             # validate parameters
768 0 0         my $dmid = $self->check_domain($args) or return;
769 0           my ($fkey, $tkey) = ($args->{from_key}, $args->{to_key});
770 0 0 0       unless (valid_key($fkey) && valid_key($tkey)) {
771 0           return $self->err_line("no_key");
772             }
773              
774 0 0         my $fid = MogileFS::FID->new_from_dmid_and_key($dmid, $fkey)
775             or return $self->err_line("unknown_key");
776              
777 0 0         $fid->rename($tkey) or
778             $self->err_line("key_exists");
779              
780 0           return $self->ok_line;
781             }
782              
783             sub cmd_get_hosts {
784 0     0 0   my MogileFS::Worker::Query $self = shift;
785 0           my $args = shift;
786              
787 0           my $ret = { hosts => 0 };
788 0           for my $host (Mgd::host_factory()->get_all) {
789 0 0 0       next if defined $args->{hostid} && $host->id != $args->{hostid};
790 0           my $n = ++$ret->{hosts};
791 0           my $fields = $host->fields(qw(hostid status hostname hostip http_port
792             http_get_port altip altmask));
793 0           while (my ($key, $val) = each %$fields) {
794             # must be regular data so copy it in
795 0           $ret->{"host${n}_$key"} = $val;
796             }
797             }
798              
799 0           return $self->ok_line($ret);
800             }
801              
802             sub cmd_get_devices {
803 0     0 0   my MogileFS::Worker::Query $self = shift;
804 0           my $args = shift;
805              
806 0           my $ret = { devices => 0 };
807 0           for my $dev (Mgd::device_factory()->get_all) {
808 0 0 0       next if defined $args->{devid} && $dev->id != $args->{devid};
809 0           my $n = ++$ret->{devices};
810              
811 0           my $sum = $dev->fields;
812 0           while (my ($key, $val) = each %$sum) {
813 0           $ret->{"dev${n}_$key"} = $val;
814             }
815             }
816              
817 0           return $self->ok_line($ret);
818             }
819              
820             sub cmd_create_device {
821 0     0 0   my MogileFS::Worker::Query $self = shift;
822 0           my $args = shift;
823              
824 0   0       my $status = $args->{state} || "alive";
825 0 0         return $self->err_line("invalid_state") unless
826             device_state($status);
827              
828 0           my $devid = $args->{devid};
829 0 0 0       return $self->err_line("invalid_devid") unless $devid && $devid =~ /^\d+$/;
830              
831 0           my $hostid;
832              
833 0           my $sto = Mgd::get_store();
834 0 0 0       if ($args->{hostid} && $args->{hostid} =~ /^\d+$/) {
    0          
835 0           $hostid = $sto->get_hostid_by_id($args->{hostid});
836 0 0         return $self->err_line("unknown_hostid") unless $hostid;
837             } elsif (my $hname = $args->{hostname}) {
838 0           $hostid = $sto->get_hostid_by_name($hname);
839 0 0         return $self->err_line("unknown_host") unless $hostid;
840             } else {
841 0           return $self->err_line("bad_args", "No hostid/hostname parameter");
842             }
843              
844 0 0         if (eval { $sto->create_device($devid, $hostid, $status) }) {
  0            
845 0           return $self->cmd_clear_cache;
846             }
847              
848 0           my $errc = error_code($@);
849 0 0         return $self->err_line("existing_devid") if $errc;
850 0           die $@; # rethrow;
851             }
852              
853             sub cmd_create_domain {
854 0     0 0   my MogileFS::Worker::Query $self = shift;
855 0           my $args = shift;
856              
857 0 0         my $domain = $args->{domain} or
858             return $self->err_line('no_domain');
859              
860 0           my $dom = eval { Mgd::get_store()->create_domain($domain); };
  0            
861 0 0         if ($@) {
862 0 0         if (error_code($@) eq "dup") {
863 0           return $self->err_line('domain_exists');
864             }
865 0           return $self->err_line('failure', "$@");
866             }
867              
868 0           return $self->cmd_clear_cache({ domain => $domain });
869             }
870              
871             sub cmd_delete_domain {
872 0     0 0   my MogileFS::Worker::Query $self = shift;
873 0           my $args = shift;
874              
875 0 0         my $domain = $args->{domain} or
876             return $self->err_line('no_domain');
877              
878 0           my $sto = Mgd::get_store();
879 0 0         my $dmid = $sto->get_domainid_by_name($domain) or
880             return $self->err_line('domain_not_found');
881              
882 0 0         if (eval { $sto->delete_domain($dmid) }) {
  0            
883 0           return $self->cmd_clear_cache({ domain => $domain });
884             }
885              
886 0           my $err = error_code($@);
887 0 0         return $self->err_line('domain_has_files') if $err eq "has_files";
888 0 0         return $self->err_line('domain_has_classes') if $err eq "has_classes";
889 0           return $self->err_line("failure");
890             }
891              
892             sub cmd_create_class {
893 0     0 0   my MogileFS::Worker::Query $self = shift;
894 0           my $args = shift;
895              
896 0           my $domain = $args->{domain};
897 0 0         return $self->err_line('no_domain') unless length $domain;
898              
899 0           my $class = $args->{class};
900 0 0         return $self->err_line('no_class') unless length $class;
901              
902 0           my $mindevcount = $args->{mindevcount}+0;
903 0 0         return $self->err_line('invalid_mindevcount') unless $mindevcount > 0;
904              
905 0   0       my $replpolicy = $args->{replpolicy} || '';
906 0 0         if ($replpolicy) {
907 0           eval {
908 0           MogileFS::ReplicationPolicy->new_from_policy_string($replpolicy);
909             };
910 0 0         return $self->err_line('invalid_replpolicy', $@) if $@;
911             }
912              
913 0           my $hashtype = $args->{hashtype};
914 0 0 0       if ($hashtype && $hashtype ne 'NONE') {
915 0           my $tmp = $MogileFS::Checksum::NAME2TYPE{$hashtype};
916 0 0         return $self->err_line('invalid_hashtype') unless $tmp;
917 0           $hashtype = $tmp;
918             }
919              
920 0           my $sto = Mgd::get_store();
921 0 0         my $dmid = $sto->get_domainid_by_name($domain) or
922             return $self->err_line('domain_not_found');
923              
924 0           my $clsid = $sto->get_classid_by_name($dmid, $class);
925 0 0 0       if (!defined $clsid && $args->{update} && $class eq 'default') {
      0        
926 0           $args->{update} = 0;
927             }
928 0 0         if ($args->{update}) {
929 0 0         return $self->err_line('class_not_found') if ! defined $clsid;
930 0           $sto->update_class_name(dmid => $dmid, classid => $clsid,
931             classname => $class);
932             } else {
933 0           $clsid = eval { $sto->create_class($dmid, $class); };
  0            
934 0 0         if ($@) {
935 0 0         if (error_code($@) eq "dup") {
936 0           return $self->err_line('class_exists');
937             }
938 0           return $self->err_line('failure', "$@");
939             }
940             }
941 0           $sto->update_class_mindevcount(dmid => $dmid, classid => $clsid,
942             mindevcount => $mindevcount);
943             # don't erase an existing replpolicy if we're not setting a new one.
944 0 0         $sto->update_class_replpolicy(dmid => $dmid, classid => $clsid,
945             replpolicy => $replpolicy) if $replpolicy;
946 0 0         if ($hashtype) {
947 0 0         $sto->update_class_hashtype(dmid => $dmid, classid => $clsid,
948             hashtype => $hashtype eq 'NONE' ? undef : $hashtype);
949             }
950              
951             # return success
952 0           return $self->cmd_clear_cache({ class => $class, mindevcount => $mindevcount, domain => $domain });
953             }
954              
955             sub cmd_update_class {
956 0     0 0   my MogileFS::Worker::Query $self = shift;
957 0           my $args = shift;
958              
959             # simply passes through to create_class with update set
960 0           $self->cmd_create_class({ %$args, update => 1 });
961             }
962              
963             sub cmd_delete_class {
964 0     0 0   my MogileFS::Worker::Query $self = shift;
965 0           my $args = shift;
966              
967 0           my $domain = $args->{domain};
968 0 0         return $self->err_line('no_domain') unless length $domain;
969 0           my $class = $args->{class};
970 0 0         return $self->err_line('no_class') unless length $domain;
971              
972 0 0         return $self->err_line('nodel_default_class') if $class eq 'default';
973              
974 0           my $sto = Mgd::get_store();
975 0 0         my $dmid = $sto->get_domainid_by_name($domain) or
976             return $self->err_line('domain_not_found');
977 0           my $clsid = $sto->get_classid_by_name($dmid, $class);
978 0 0         return $self->err_line('class_not_found') unless defined $clsid;
979              
980 0 0         if (eval { Mgd::get_store()->delete_class($dmid, $clsid) }) {
  0            
981 0           return $self->cmd_clear_cache({ domain => $domain, class => $class });
982             }
983              
984 0           my $errc = error_code($@);
985 0 0         return $self->err_line('class_has_files') if $errc eq "has_files";
986 0           return $self->err_line('failure');
987             }
988              
989             sub cmd_create_host {
990 0     0 0   my MogileFS::Worker::Query $self = shift;
991 0           my $args = shift;
992              
993 0 0         my $hostname = $args->{host} or
994             return $self->err_line('no_host');
995              
996 0           my $sto = Mgd::get_store();
997 0           my $hostid = $sto->get_hostid_by_name($hostname);
998              
999             # if we're creating a new host, require ip/port, and default to
1000             # host being down if client didn't specify
1001 0 0         if ($args->{update}) {
1002 0 0         return $self->err_line('host_not_found') unless $hostid;
1003             } else {
1004 0 0         return $self->err_line('host_exists') if $hostid;
1005 0 0         return $self->err_line('no_ip') unless $args->{ip};
1006 0 0         return $self->err_line('no_port') unless $args->{port};
1007 0   0       $args->{status} ||= 'down';
1008             }
1009              
1010 0 0         if ($args->{status}) {
1011 0 0         return $self->err_line('unknown_state')
1012             unless MogileFS::Host->valid_state($args->{status});
1013             }
1014              
1015             # arguments all good, let's do it.
1016              
1017 0   0       $hostid ||= $sto->create_host($hostname, $args->{ip});
1018              
1019             # Protocol mismatch data fixup.
1020 0 0         $args->{hostip} = delete $args->{ip} if exists $args->{ip};
1021 0 0         $args->{http_port} = delete $args->{port} if exists $args->{port};
1022 0 0         $args->{http_get_port} = delete $args->{getport} if exists $args->{getport};
1023 0           my @toupdate = grep { exists $args->{$_} } qw(status hostip http_port
  0            
1024             http_get_port altip altmask);
1025 0           $sto->update_host($hostid, { map { $_ => $args->{$_} } @toupdate });
  0            
1026              
1027             # return success
1028 0           return $self->cmd_clear_cache({ hostid => $hostid, hostname => $hostname });
1029             }
1030              
1031             sub cmd_update_host {
1032 0     0 0   my MogileFS::Worker::Query $self = shift;
1033 0           my $args = shift;
1034              
1035             # simply passes through to create_host with update set
1036 0           $self->cmd_create_host({ %$args, update => 1 });
1037             }
1038              
1039             sub cmd_delete_host {
1040 0     0 0   my MogileFS::Worker::Query $self = shift;
1041 0           my $args = shift;
1042              
1043 0           my $sto = Mgd::get_store();
1044 0 0         my $hostid = $sto->get_hostid_by_name($args->{host})
1045             or return $self->err_line('unknown_host');
1046              
1047             # TODO: $sto->delete_host should have a "has_devices" test internally
1048 0           for my $dev ($sto->get_all_devices) {
1049 0 0         return $self->err_line('host_not_empty')
1050             if $dev->{hostid} == $hostid;
1051             }
1052              
1053 0           $sto->delete_host($hostid);
1054              
1055 0           return $self->cmd_clear_cache;
1056             }
1057              
1058             sub cmd_get_domains {
1059 0     0 0   my MogileFS::Worker::Query $self = shift;
1060 0           my $args = shift;
1061              
1062 0           my $ret = {};
1063 0           my $dm_n = 0;
1064 0           for my $dom (Mgd::domain_factory()->get_all) {
1065 0           $dm_n++;
1066 0           $ret->{"domain${dm_n}"} = $dom->name;
1067 0           my $cl_n = 0;
1068 0           foreach my $cl ($dom->classes) {
1069 0           $cl_n++;
1070 0           $ret->{"domain${dm_n}class${cl_n}name"} = $cl->name;
1071 0           $ret->{"domain${dm_n}class${cl_n}mindevcount"} = $cl->mindevcount;
1072 0           $ret->{"domain${dm_n}class${cl_n}replpolicy"} =
1073             $cl->repl_policy_string;
1074 0           $ret->{"domain${dm_n}class${cl_n}hashtype"} = $cl->hashtype_string;
1075             }
1076 0           $ret->{"domain${dm_n}classes"} = $cl_n;
1077             }
1078 0           $ret->{"domains"} = $dm_n;
1079              
1080 0           return $self->ok_line($ret);
1081             }
1082              
1083             sub cmd_get_paths {
1084 0     0 0   my MogileFS::Worker::Query $self = shift;
1085 0           my $args = shift;
1086              
1087             # memcache mappings are as follows:
1088             # mogfid:: -> fidid
1089             # mogdevids: -> \@devids (and TODO: invalidate when deletion is run!)
1090              
1091             # if you specify 'noverify', that means a correct answer isn't needed and memcache can
1092             # be used.
1093 0           my $memc = MogileFS::Config->memcache_client;
1094 0   0       my $get_from_memc = $memc && $args->{noverify};
1095 0   0       my $memcache_ttl = MogileFS::Config->server_setting_cached("memcache_ttl") || 3600;
1096              
1097             # validate domain for plugins
1098 0 0         $args->{dmid} = $self->check_domain($args) or return;
1099              
1100             # now invoke the plugin, abort if it tells us to
1101 0           my $rv = MogileFS::run_global_hook('cmd_get_paths', $args);
1102 0 0 0       return $self->err_line('plugin_aborted')
1103             if defined $rv && ! $rv;
1104              
1105             # validate parameters
1106 0           my $dmid = $args->{dmid};
1107 0           my $key = $args->{key};
1108              
1109 0 0         valid_key($key) or return $self->err_line("no_key");
1110              
1111             # We default to returning two possible paths.
1112             # but the client may ask for more if they want.
1113 0   0       my $pathcount = $args->{pathcount} || 2;
1114 0 0         $pathcount = 2 if $pathcount < 2;
1115              
1116             # get DB handle
1117 0           my $fid;
1118 0           my $need_fid_in_memcache = 0;
1119 0           my $mogfid_memkey = "mogfid:$args->{dmid}:$key";
1120 0 0         if ($get_from_memc) {
1121 0 0         if (my $fidid = $memc->get($mogfid_memkey)) {
1122 0           $fid = MogileFS::FID->new($fidid);
1123             } else {
1124 0           $need_fid_in_memcache = 1;
1125             }
1126             }
1127 0 0         unless ($fid) {
1128             Mgd::get_store()->slaves_ok(sub {
1129 0     0     $fid = MogileFS::FID->new_from_dmid_and_key($dmid, $key);
1130 0           });
1131 0 0         $fid or return $self->err_line("unknown_key");
1132             }
1133              
1134             # add to memcache, if needed. for an hour.
1135 0 0 0       $memc->set($mogfid_memkey, $fid->id, $memcache_ttl ) if $need_fid_in_memcache || ($memc && !$get_from_memc);
      0        
1136              
1137 0           my $dmap = Mgd::device_factory()->map_by_id;
1138              
1139 0           my $ret = {
1140             paths => 0,
1141             };
1142              
1143             # find devids that FID is on in memcache or db.
1144 0           my @fid_devids;
1145 0           my $need_devids_in_memcache = 0;
1146 0           my $devid_memkey = "mogdevids:" . $fid->id;
1147 0 0         if ($get_from_memc) {
1148 0 0         if (my $list = $memc->get($devid_memkey)) {
1149 0           @fid_devids = @$list;
1150             } else {
1151 0           $need_devids_in_memcache = 1;
1152             }
1153             }
1154 0 0         unless (@fid_devids) {
1155             Mgd::get_store()->slaves_ok(sub {
1156 0     0     @fid_devids = $fid->devids;
1157 0           });
1158 0 0 0       $memc->set($devid_memkey, \@fid_devids, $memcache_ttl ) if $need_devids_in_memcache || ($memc && !$get_from_memc);
      0        
1159             }
1160              
1161 0           my @devices = map { $dmap->{$_} } @fid_devids;
  0            
1162              
1163 0           my @sorted_devs;
1164 0 0         unless (MogileFS::run_global_hook('cmd_get_paths_order_devices', \@devices, \@sorted_devs)) {
1165 0           @sorted_devs = sort_devs_by_utilization(@devices);
1166             }
1167              
1168             # keep one partially-bogus path around just in case we have nothing else to send.
1169 0           my $backup_path;
1170              
1171             # files on devices set for drain may disappear soon.
1172             my @drain_paths;
1173              
1174             # construct result paths
1175 0           foreach my $dev (@sorted_devs) {
1176 0 0 0       next unless $dev && $dev->host;
1177              
1178 0           my $dfid = MogileFS::DevFID->new($dev, $fid);
1179 0           my $path = $dfid->get_url;
1180 0           my $currently_up = $dev->should_read_from;
1181              
1182 0 0         if (! $currently_up) {
1183 0           $backup_path = $path;
1184 0           next;
1185             }
1186              
1187             # only verify size one first one, and never verify if they've asked not to
1188             next unless
1189 0 0 0       $ret->{paths} ||
      0        
1190             $args->{noverify} ||
1191             $dfid->size_matches;
1192              
1193 0 0         if ($dev->dstate->should_drain) {
1194 0           push @drain_paths, $path;
1195 0           next;
1196             }
1197              
1198 0           my $n = ++$ret->{paths};
1199 0           $ret->{"path$n"} = $path;
1200 0 0         last if $n == $pathcount; # one verified, one likely seems enough for now. time will tell.
1201             }
1202              
1203             # deprioritize devices set for drain, they could disappear soon...
1204             # Clients /should/ try to use lower-numbered paths first to avoid this.
1205 0 0 0       if ($ret->{paths} < $pathcount && @drain_paths) {
1206 0           foreach my $path (@drain_paths) {
1207 0           my $n = ++$ret->{paths};
1208 0           $ret->{"path$n"} = $path;
1209 0 0         last if $n == $pathcount;
1210             }
1211             }
1212              
1213             # use our backup path if all else fails
1214 0 0 0       if ($backup_path && ! $ret->{paths}) {
1215 0           $ret->{paths} = 1;
1216 0           $ret->{path1} = $backup_path;
1217             }
1218              
1219 0           return $self->ok_line($ret);
1220             }
1221              
1222             sub sort_devs_by_utilization {
1223 0     0 0   my @devices_with_weights;
1224              
1225             # is this fid still owned by this key?
1226 0           foreach my $dev (@_) {
1227 0           my $weight;
1228 0           my $util = $dev->observed_utilization;
1229              
1230 0 0 0       if (defined($util) and $util =~ /\A\d+\Z/) {
1231 0           $weight = 102 - $util;
1232 0   0       $weight ||= 100;
1233             } else {
1234 0           $weight = $dev->weight;
1235 0   0       $weight ||= 100;
1236             }
1237 0           push @devices_with_weights, [$dev, $weight];
1238             }
1239              
1240             # randomly weight the devices
1241 0           my @list = MogileFS::Util::weighted_list(@devices_with_weights);
1242              
1243 0           return @list;
1244             }
1245              
1246             # ------------------------------------------------------------
1247             #
1248             # NOTE: cmd_edit_file is EXPERIMENTAL. Please see the documentation
1249             # for edit_file in L.
1250             # It is not recommended to use cmd_edit_file on production systems.
1251             #
1252             # cmd_edit_file is similar to cmd_get_paths, except we:
1253             # - take the device of the first path we would have returned
1254             # - get a tempfile with a new fid (pointing to nothing) on the same device
1255             # the tempfile has the same key, so will replace the old contents on
1256             # create_close
1257             # - detach the old fid from that device (leaving the file in place)
1258             # - attach the new fid to that device
1259             # - returns only the first path to the old fid and a path to new fid
1260             # (the client then DAV-renames the old path to the new path)
1261             #
1262             # TODO - what to do about situations where we would be reducing the
1263             # replica count to zero?
1264             # TODO - what to do about pending replications where we remove the source?
1265             # TODO - the current implementation of cmd_edit_file is based on a copy
1266             # of cmd_get_paths. Once proven mature, consider factoring out common
1267             # code from the two functions.
1268             # ------------------------------------------------------------
1269             sub cmd_edit_file {
1270 0     0 0   my MogileFS::Worker::Query $self = shift;
1271 0           my $args = shift;
1272              
1273 0           my $memc = MogileFS::Config->memcache_client;
1274              
1275             # validate domain for plugins
1276 0 0         $args->{dmid} = $self->check_domain($args) or return;
1277              
1278             # now invoke the plugin, abort if it tells us to
1279 0           my $rv = MogileFS::run_global_hook('cmd_get_paths', $args);
1280 0 0 0       return $self->err_line('plugin_aborted')
1281             if defined $rv && ! $rv;
1282              
1283             # validate parameters
1284 0           my $dmid = $args->{dmid};
1285 0           my $key = $args->{key};
1286              
1287 0 0         valid_key($key) or return $self->err_line("no_key");
1288              
1289             # get DB handle
1290 0           my $fid;
1291 0           my $need_fid_in_memcache = 0;
1292 0           my $mogfid_memkey = "mogfid:$args->{dmid}:$key";
1293 0 0         if (my $fidid = $memc->get($mogfid_memkey)) {
1294 0           $fid = MogileFS::FID->new($fidid);
1295             } else {
1296 0           $need_fid_in_memcache = 1;
1297             }
1298 0 0         unless ($fid) {
1299             Mgd::get_store()->slaves_ok(sub {
1300 0     0     $fid = MogileFS::FID->new_from_dmid_and_key($dmid, $key);
1301 0           });
1302 0 0         $fid or return $self->err_line("unknown_key");
1303             }
1304              
1305             # add to memcache, if needed. for an hour.
1306 0 0         $memc->add($mogfid_memkey, $fid->id, 3600) if $need_fid_in_memcache;
1307              
1308 0           my $dmap = Mgd::device_factory()->map_by_id;
1309              
1310 0           my @devices_with_weights;
1311              
1312             # find devids that FID is on in memcache or db.
1313             my @fid_devids;
1314 0           my $need_devids_in_memcache = 0;
1315 0           my $devid_memkey = "mogdevids:" . $fid->id;
1316 0 0         if (my $list = $memc->get($devid_memkey)) {
1317 0           @fid_devids = @$list;
1318             } else {
1319 0           $need_devids_in_memcache = 1;
1320             }
1321 0 0         unless (@fid_devids) {
1322             Mgd::get_store()->slaves_ok(sub {
1323 0     0     @fid_devids = $fid->devids;
1324 0           });
1325 0 0         $memc->add($devid_memkey, \@fid_devids, 3600) if $need_devids_in_memcache;
1326             }
1327              
1328             # is this fid still owned by this key?
1329 0           foreach my $devid (@fid_devids) {
1330 0           my $weight;
1331 0           my $dev = $dmap->{$devid};
1332 0           my $util = $dev->observed_utilization;
1333              
1334 0 0 0       if (defined($util) and $util =~ /\A\d+\Z/) {
1335 0           $weight = 102 - $util;
1336 0   0       $weight ||= 100;
1337             } else {
1338 0           $weight = $dev->weight;
1339 0   0       $weight ||= 100;
1340             }
1341 0           push @devices_with_weights, [$devid, $weight];
1342             }
1343              
1344             # randomly weight the devices
1345             # TODO - should we reverse the order, to leave the best
1346             # one there for get_paths?
1347 0           my @list = MogileFS::Util::weighted_list(@devices_with_weights);
1348              
1349             # Filter out bad devs
1350 0           @list = grep {
1351 0           my $devid = $_;
1352 0           my $dev = $dmap->{$devid};
1353              
1354 0 0         $dev && $dev->should_read_from;
1355             } @list;
1356              
1357             # Take first remaining device from list
1358 0           my $devid = $list[0];
1359              
1360 0           my $classid = $fid->classid;
1361 0           my $newfid = eval {
1362 0           Mgd::get_store()->register_tempfile(
1363             fid => undef, # undef => let the store pick a fid
1364             dmid => $dmid,
1365             key => $key, # This tempfile will ultimately become this key
1366             classid => $classid,
1367             devids => $devid,
1368             );
1369             };
1370 0 0         unless ($newfid) {
1371 0           my $errc = error_code($@);
1372 0 0         return $self->err_line("fid_in_use") if $errc eq "dup";
1373 0           warn "Error registering tempfile: $@\n";
1374 0           return $self->err_line("db");
1375             }
1376 0 0         unless (Mgd::get_store()->remove_fidid_from_devid($fid->id, $devid)) {
1377 0           warn "Error removing fidid from devid";
1378 0           return $self->err_line("db");
1379             }
1380 0 0         unless (Mgd::get_store()->add_fidid_to_devid($newfid, $devid)) {
1381 0           warn "Error removing fidid from devid";
1382 0           return $self->err_line("db");
1383             }
1384              
1385 0           my @paths = map {
1386 0           my $dfid = MogileFS::DevFID->new($devid, $_);
1387 0           my $path = $dfid->get_url;
1388             } ($fid, $newfid);
1389 0           my $ret;
1390 0           $ret->{oldpath} = $paths[0];
1391 0           $ret->{newpath} = $paths[1];
1392 0           $ret->{fid} = $newfid;
1393 0           $ret->{devid} = $devid;
1394 0           $ret->{class} = $classid;
1395 0           return $self->ok_line($ret);
1396             }
1397              
1398             sub cmd_set_weight {
1399 0     0 0   my MogileFS::Worker::Query $self = shift;
1400 0           my $args = shift;
1401              
1402             # figure out what they want to do
1403 0           my ($hostname, $devid, $weight) = ($args->{host}, $args->{device}+0, $args->{weight}+0);
1404 0 0 0       return $self->err_line('bad_params')
      0        
1405             unless $hostname && $devid && $weight >= 0;
1406              
1407 0           my $dev = Mgd::device_factory()->get_by_id($devid);
1408 0 0         return $self->err_line('no_device') unless $dev;
1409 0 0         return $self->err_line('host_mismatch')
1410             unless $dev->host->hostname eq $hostname;
1411              
1412 0           Mgd::get_store()->set_device_weight($dev->id, $weight);
1413              
1414 0           return $self->cmd_clear_cache;
1415             }
1416              
1417             sub cmd_set_state {
1418 0     0 0   my MogileFS::Worker::Query $self = shift;
1419 0           my $args = shift;
1420              
1421             # figure out what they want to do
1422 0           my ($hostname, $devid, $state) = ($args->{host}, $args->{device}+0, $args->{state});
1423              
1424 0           my $dstate = device_state($state);
1425 0 0 0       return $self->err_line('bad_params')
      0        
1426             unless $hostname && $devid && $dstate;
1427              
1428 0           my $dev = Mgd::device_factory()->get_by_id($devid);
1429 0 0         return $self->err_line('no_device') unless $dev;
1430 0 0         return $self->err_line('host_mismatch')
1431             unless $dev->host->hostname eq $hostname;
1432              
1433             # make sure the destination state isn't too high
1434 0 0         return $self->err_line('state_too_high')
1435             unless $dev->can_change_to_state($state);
1436              
1437 0           Mgd::get_store()->set_device_state($dev->id, $state);
1438 0           return $self->cmd_clear_cache;
1439             }
1440              
1441             sub cmd_noop {
1442 0     0 0   my MogileFS::Worker::Query $self = shift;
1443 0           my $args = shift;
1444 0           return $self->ok_line;
1445             }
1446              
1447             sub cmd_replicate_now {
1448 0     0 0   my MogileFS::Worker::Query $self = shift;
1449              
1450 0           my $rv = Mgd::get_store()->replicate_now;
1451 0           return $self->ok_line({ count => int($rv) });
1452             }
1453              
1454             sub cmd_set_server_setting {
1455 0     0 0   my MogileFS::Worker::Query $self = shift;
1456 0           my $args = shift;
1457 0 0         my $key = $args->{key} or
1458             return $self->err_line("bad_params");
1459 0           my $val = $args->{value};
1460              
1461 0 0         my $chk = MogileFS::Config->server_setting_is_writable($key) or
1462             return $self->err_line("not_writable");
1463              
1464 0           my $cleanval = eval { $chk->($val); };
  0            
1465 0 0         return $self->err_line("invalid_format", $@) if $@;
1466              
1467 0           MogileFS::Config->set_server_setting($key, $cleanval);
1468              
1469             # GROSS HACK: slave settings are managed directly by MogileFS::Client, but
1470             # I need to add a version key, so we check and inject that code here.
1471             # FIXME: Move this when slave keys are managed by query worker commands!
1472 0 0         if ($key =~ /^slave_/) {
1473 0           Mgd::get_store()->incr_server_setting('slave_version', 1);
1474             }
1475              
1476 0           return $self->ok_line;
1477             }
1478              
1479             sub cmd_server_setting {
1480 0     0 0   my MogileFS::Worker::Query $self = shift;
1481 0           my $args = shift;
1482 0           my $key = $args->{key};
1483 0 0         return $self->err_line("bad_params") unless $key;
1484 0           my $value = MogileFS::Config->server_setting($key);
1485 0           return $self->ok_line({key => $key, value => $value});
1486             }
1487              
1488             sub cmd_server_settings {
1489 0     0 0   my MogileFS::Worker::Query $self = shift;
1490 0           my $ss = Mgd::get_store()->server_settings;
1491 0           my $ret = {};
1492 0           my $n = 0;
1493 0           while (my ($k, $v) = each %$ss) {
1494 0 0         next unless MogileFS::Config->server_setting_is_readable($k);
1495 0           $ret->{"key_count"} = ++$n;
1496 0           $ret->{"key_$n"} = $k;
1497 0           $ret->{"value_$n"} = $v;
1498             }
1499 0           return $self->ok_line($ret);
1500             }
1501              
1502             sub cmd_do_monitor_round {
1503 0     0 0   my MogileFS::Worker::Query $self = shift;
1504 0           my $args = shift;
1505 0           $self->forget_that_monitor_has_run;
1506 0           $self->wait_for_monitor;
1507 0           return $self->ok_line;
1508             }
1509              
1510             sub cmd_fsck_start {
1511 0     0 0   my MogileFS::Worker::Query $self = shift;
1512 0           my $sto = Mgd::get_store();
1513              
1514 0           my $fsck_host = MogileFS::Config->server_setting("fsck_host");
1515 0           my $rebal_host = MogileFS::Config->server_setting("rebal_host");
1516              
1517 0 0         return $self->err_line("fsck_running", "fsck is already running") if $fsck_host;
1518 0 0         return $self->err_line("rebal_running", "rebalance running; cannot run fsck at same time") if $rebal_host;
1519              
1520             # reset position, if a previous fsck was already completed.
1521 0 0   0     my $intss = sub { MogileFS::Config->server_setting($_[0]) || 0 };
  0            
1522 0           my $checked_fid = $intss->("fsck_highest_fid_checked");
1523 0           my $final_fid = $intss->("fsck_fid_at_end");
1524 0 0 0       if (($checked_fid && $final_fid && $checked_fid >= $final_fid) ||
      0        
      0        
      0        
1525             (!$final_fid && !$checked_fid)) {
1526 0 0         $self->_do_fsck_reset or return $self->err_line("db");
1527             }
1528              
1529             # set params for stats:
1530 0           $sto->set_server_setting("fsck_start_time", $sto->get_db_unixtime);
1531 0           $sto->set_server_setting("fsck_stop_time", undef);
1532 0           $sto->set_server_setting("fsck_fids_checked", 0);
1533 0   0       my $start_fid =
1534             MogileFS::Config->server_setting('fsck_highest_fid_checked') || 0;
1535 0           $sto->set_server_setting("fsck_start_fid", $start_fid);
1536              
1537             # and start it:
1538 0           $sto->set_server_setting("fsck_host", MogileFS::Config->hostname);
1539 0           MogileFS::ProcManager->wake_a("fsck");
1540              
1541 0           return $self->ok_line;
1542             }
1543              
1544             sub cmd_fsck_stop {
1545 0     0 0   my MogileFS::Worker::Query $self = shift;
1546 0           my $sto = Mgd::get_store();
1547 0           $sto->set_server_setting("fsck_host", undef);
1548 0           $sto->set_server_setting("fsck_stop_time", $sto->get_db_unixtime);
1549 0           return $self->ok_line;
1550             }
1551              
1552             sub cmd_fsck_reset {
1553 0     0 0   my MogileFS::Worker::Query $self = shift;
1554 0           my $args = shift;
1555              
1556 0           my $sto = Mgd::get_store();
1557 0 0         $sto->set_server_setting("fsck_opt_policy_only",
1558             ($args->{policy_only} ? "1" : undef));
1559 0 0         $sto->set_server_setting("fsck_highest_fid_checked",
1560             ($args->{startpos} ? $args->{startpos} : "0"));
1561              
1562 0 0         $self->_do_fsck_reset or return $self->err_line("db");
1563 0           return $self->ok_line;
1564             }
1565              
1566             sub _do_fsck_reset {
1567 0     0     my MogileFS::Worker::Query $self = shift;
1568 0           eval {
1569 0           my $sto = Mgd::get_store();
1570 0           $sto->set_server_setting("fsck_start_time", undef);
1571 0           $sto->set_server_setting("fsck_stop_time", undef);
1572 0           $sto->set_server_setting("fsck_fids_checked", 0);
1573 0           $sto->set_server_setting("fsck_fid_at_end", $sto->max_fidid);
1574              
1575             # clear existing event counts summaries.
1576 0           my $ss = $sto->server_settings;
1577 0           foreach my $k (keys %$ss) {
1578 0 0         next unless $k =~ /^fsck_sum_evcount_/;
1579 0           $sto->set_server_setting($k, undef);
1580             }
1581 0           my $logid = $sto->max_fsck_logid;
1582 0           $sto->set_server_setting("fsck_start_maxlogid", $logid);
1583 0           $sto->set_server_setting("fsck_logid_processed", $logid);
1584             };
1585 0 0         if ($@) {
1586 0           error("DB error in _do_fsck_reset: $@");
1587 0           return 0;
1588             }
1589 0           return 1;
1590             }
1591              
1592             sub cmd_fsck_clearlog {
1593 0     0 0   my MogileFS::Worker::Query $self = shift;
1594 0           my $sto = Mgd::get_store();
1595 0           $sto->clear_fsck_log;
1596 0           return $self->ok_line;
1597             }
1598              
1599             sub cmd_fsck_getlog {
1600 0     0 0   my MogileFS::Worker::Query $self = shift;
1601 0           my $args = shift;
1602              
1603 0           my $sto = Mgd::get_store();
1604 0           my @rows = $sto->fsck_log_rows($args->{after_logid}, 100);
1605 0           my $ret;
1606 0           my $n = 0;
1607 0           foreach my $row (@rows) {
1608 0           $n++;
1609 0           foreach my $k (keys %$row) {
1610 0 0         $ret->{"row_${n}_$k"} = $row->{$k} if defined $row->{$k};
1611             }
1612             }
1613 0           $ret->{row_count} = $n;
1614 0           return $self->ok_line($ret);
1615             }
1616              
1617             sub cmd_fsck_status {
1618 0     0 0   my MogileFS::Worker::Query $self = shift;
1619              
1620 0           my $sto = Mgd::get_store();
1621             # Kick up the summary before we read the values
1622 0           $sto->fsck_log_summarize;
1623 0           my $fsck_host = MogileFS::Config->server_setting('fsck_host');
1624 0 0   0     my $intss = sub { MogileFS::Config->server_setting($_[0]) || 0 };
  0            
1625 0 0         my $ret = {
1626             running => ($fsck_host ? 1 : 0),
1627             host => $fsck_host,
1628             max_fid_checked => $intss->('fsck_highest_fid_checked'),
1629             policy_only => $intss->('fsck_opt_policy_only'),
1630             end_fid => $intss->('fsck_fid_at_end'),
1631             start_time => $intss->('fsck_start_time'),
1632             stop_time => $intss->('fsck_stop_time'),
1633             current_time => $sto->get_db_unixtime,
1634             max_logid => $sto->max_fsck_logid,
1635             };
1636              
1637             # throw some stats in.
1638 0           my $ss = $sto->server_settings;
1639 0           foreach my $k (keys %$ss) {
1640 0 0         next unless $k =~ /^fsck_sum_evcount_(.+)/;
1641 0           $ret->{"num_$1"} += $ss->{$k};
1642             }
1643              
1644 0           return $self->ok_line($ret);
1645             }
1646              
1647             sub cmd_rebalance_status {
1648 0     0 0   my MogileFS::Worker::Query $self = shift;
1649              
1650 0           my $sto = Mgd::get_store();
1651              
1652 0           my $rebal_state = MogileFS::Config->server_setting('rebal_state');
1653 0 0         return $self->err_line('no_rebal_state') unless $rebal_state;
1654 0           return $self->ok_line({ state => $rebal_state });
1655             }
1656              
1657             sub cmd_rebalance_start {
1658 0     0 0   my MogileFS::Worker::Query $self = shift;
1659              
1660 0           my $rebal_host = MogileFS::Config->server_setting("rebal_host");
1661 0           my $fsck_host = MogileFS::Config->server_setting("fsck_host");
1662              
1663 0 0         return $self->err_line("rebal_running", "rebalance is already running") if $rebal_host;
1664 0 0         return $self->err_line("fsck_running", "fsck running; cannot run rebalance at same time") if $fsck_host;
1665              
1666 0           my $rebal_state = MogileFS::Config->server_setting('rebal_state');
1667 0 0         unless ($rebal_state) {
1668 0           my $rebal_pol = MogileFS::Config->server_setting('rebal_policy');
1669 0 0         return $self->err_line('no_rebal_policy') unless $rebal_pol;
1670              
1671 0           my $rebal = MogileFS::Rebalance->new;
1672 0           $rebal->policy($rebal_pol);
1673 0           my @devs = Mgd::device_factory()->get_all;
1674 0           $rebal->init(\@devs);
1675 0           my $sdevs = $rebal->source_devices;
1676              
1677 0           $rebal_state = $rebal->save_state;
1678 0           MogileFS::Config->set_server_setting('rebal_state', $rebal_state);
1679             }
1680             # TODO: register start time somewhere.
1681 0           MogileFS::Config->set_server_setting('rebal_host', MogileFS::Config->hostname);
1682 0           return $self->ok_line({ state => $rebal_state });
1683             }
1684              
1685             sub cmd_rebalance_test {
1686 0     0 0   my MogileFS::Worker::Query $self = shift;
1687 0           my $rebal_pol = MogileFS::Config->server_setting('rebal_policy');
1688 0           my $rebal_state = MogileFS::Config->server_setting('rebal_state');
1689 0 0         return $self->err_line('no_rebal_policy') unless $rebal_pol;
1690              
1691 0           my $rebal = MogileFS::Rebalance->new;
1692 0           my @devs = Mgd::device_factory()->get_all;
1693 0           $rebal->policy($rebal_pol);
1694 0           $rebal->init(\@devs);
1695              
1696             # client should display list of source, destination devices.
1697             # FIXME: can probably avoid calling this twice by pulling state?
1698             # *or* not running init.
1699 0           my $sdevs = $rebal->filter_source_devices(\@devs);
1700 0           my $ddevs = $rebal->filter_dest_devices(\@devs);
1701 0           my $ret = {};
1702 0           $ret->{sdevs} = join(',', @$sdevs);
1703 0           $ret->{ddevs} = join(',', @$ddevs);
1704              
1705 0           return $self->ok_line($ret);
1706             }
1707              
1708             sub cmd_rebalance_reset {
1709 0     0 0   my MogileFS::Worker::Query $self = shift;
1710 0           my $host = MogileFS::Config->server_setting('rebal_host');
1711 0 0         if ($host) {
1712 0 0         return $self->err_line("rebal_running", "rebalance is running") if $host;
1713             }
1714 0           MogileFS::Config->set_server_setting('rebal_state', undef);
1715 0           return $self->ok_line;
1716             }
1717              
1718             sub cmd_rebalance_stop {
1719 0     0 0   my MogileFS::Worker::Query $self = shift;
1720 0           my $host = MogileFS::Config->server_setting('rebal_host');
1721 0 0         unless ($host) {
1722 0           return $self->err_line('rebal_not_started');
1723             }
1724 0           MogileFS::Config->set_server_setting('rebal_signal', 'stop');
1725 0           return $self->ok_line;
1726             }
1727              
1728             sub cmd_rebalance_set_policy {
1729 0     0 0   my MogileFS::Worker::Query $self = shift;
1730 0           my $args = shift;
1731              
1732 0           my $rebal_host = MogileFS::Config->server_setting("rebal_host");
1733 0 0         return $self->err_line("no_set_rebal", "cannot change rebalance policy while rebalance is running") if $rebal_host;
1734              
1735             # load policy object, test policy, set policy.
1736 0           my $rebal = MogileFS::Rebalance->new;
1737 0           eval {
1738 0           $rebal->policy($args->{policy});
1739             };
1740 0 0         if ($@) {
1741 0           return $self->err_line("bad_rebal_pol", $@);
1742             }
1743              
1744 0           MogileFS::Config->set_server_setting('rebal_policy', $args->{policy});
1745 0           MogileFS::Config->set_server_setting('rebal_state', undef);
1746 0           return $self->ok_line;
1747             }
1748              
1749             sub ok_line {
1750 0     0 0   my MogileFS::Worker::Query $self = shift;
1751              
1752 0           my $delay = '';
1753 0 0         if ($self->{querystarttime}) {
1754 0           $delay = sprintf("%.4f ", Time::HiRes::tv_interval( $self->{querystarttime} ));
1755 0           $self->{querystarttime} = undef;
1756             }
1757              
1758 0 0         my $id = defined $self->{reqid} ? "$self->{reqid} " : '';
1759              
1760 0   0       my $args = shift || {};
1761 0 0         $args->{callid} = $self->{callid} if defined $self->{callid};
1762 0           my $argline = join('&', map { eurl($_) . "=" . eurl($args->{$_}) } keys %$args);
  0            
1763 0           $self->send_to_parent("${id}${delay}OK $argline");
1764 0           return 1;
1765             }
1766              
1767             # first argument: error code.
1768             # second argument: optional error text. text will be taken from code if no text provided.
1769             sub err_line {
1770 0     0 0   my MogileFS::Worker::Query $self = shift;
1771              
1772 0           my $err_code = shift;
1773 0   0       my $err_text = shift || {
1774             'dup' => "Duplicate name/number used.",
1775             'after_mismatch' => "Pattern does not match the after-value?",
1776             'bad_params' => "Invalid parameters to command; please see documentation",
1777             'class_exists' => "That class already exists in that domain",
1778             'class_has_files' => "Class still has files, unable to delete",
1779             'class_not_found' => "Class not found",
1780             'db' => "Database error",
1781             'domain_has_files' => "Domain still has files, unable to delete",
1782             'domain_exists' => "That domain already exists",
1783             'domain_not_empty' => "Domain still has classes, unable to delete",
1784             'domain_not_found' => "Domain not found",
1785             'failure' => "Operation failed",
1786             'host_exists' => "That host already exists",
1787             'host_mismatch' => "The device specified doesn't belong to the host specified",
1788             'host_not_empty' => "Unable to delete host; it contains devices still",
1789             'host_not_found' => "Host not found",
1790             'invalid_checker_level' => "Checker level invalid. Please see documentation on this command.",
1791             'invalid_mindevcount' => "The mindevcount must be at least 1",
1792             'key_exists' => "Target key name already exists; can't overwrite.",
1793             'no_class' => "No class provided",
1794             'no_devices' => "No devices found to store file",
1795             'no_device' => "Device not found",
1796             'no_domain' => "No domain provided",
1797             'no_host' => "No host provided",
1798             'no_ip' => "IP required to create host",
1799             'no_port' => "Port required to create host",
1800             'no_temp_file' => "No tempfile or file already closed",
1801             'none_match' => "No keys match that pattern and after-value (if any).",
1802             'plugin_aborted' => "Action aborted by plugin",
1803             'state_too_high' => "Status cannot go from dead to alive; must use down",
1804             'unknown_command' => "Unknown server command",
1805             'unknown_host' => "Host not found",
1806             'unknown_state' => "Invalid/unknown state",
1807             'unreg_domain' => "Domain name invalid/not found",
1808             'rebal_not_started' => "Rebalance not running",
1809             'no_rebal_state' => "No available rebalance status",
1810             'no_rebal_policy' => "No rebalance policy available",
1811             'nodel_default_class' => "Cannot delete the default class",
1812             }->{$err_code} || $err_code;
1813              
1814 0           my $delay = '';
1815 0 0         if ($self->{querystarttime}) {
1816 0           $delay = sprintf("%.4f ", Time::HiRes::tv_interval($self->{querystarttime}));
1817 0           $self->{querystarttime} = undef;
1818             } else {
1819             # don't send another ERR line if we already sent one
1820 0           error("err_line called redundantly with $err_code ( " . eurl($err_text) . ")");
1821 0           return 0;
1822             }
1823              
1824 0 0         my $id = defined $self->{reqid} ? "$self->{reqid} " : '';
1825 0 0         my $callid = defined $self->{callid} ? ' ' . eurl($self->{callid}) : '';
1826              
1827 0           $self->send_to_parent("${id}${delay}ERR $err_code " . eurl($err_text) . $callid);
1828 0           return 0;
1829             }
1830              
1831             1;
1832              
1833             # Local Variables:
1834             # mode: perl
1835             # c-basic-indent: 4
1836             # indent-tabs-mode: nil
1837             # End:
1838              
1839             __END__