2 # vim:ts=4 sw=4 ft=perl et:
7 use LWP
::Simple
; # FIXME: use of this makes 'mog check' hang too long when multiple things down
10 my @topcmds = qw(check stats host device domain class slave fsck rebalance settings);
13 des
=> "Check the state of the MogileFS world.",
16 des
=> "Show MogileFS system statistics. (DEPRECATED: use mogstats instead)",
19 des
=> "Change/list server settings.",
22 des
=> "List all server settings",
25 args
=> "<key> <value>",
26 des
=> "Set server setting 'key' to 'value'.",
31 des
=> "Add/modify hosts.",
34 des
=> "List all hosts.",
37 des
=> "Add a host to MogileFS.",
38 args
=> "<hostname> [opts]",
40 "<hostname>" => "Hostname of machine",
41 "--status=s" => "One of {alive,down,readonly}. Default 'down'.",
42 "--ip=s" => "IP address of machine.",
43 "--port=i" => "HTTP port of mogstored",
44 "--getport=i" => "Alternate HTTP port serving readonly traffic",
45 "--altip=s" => "Alternate IP that is machine is reachable from",
46 "--altmask=s" => "Netmask which, when matches client, uses alt IP",
50 des
=> "Modify a host's properties.",
51 args
=> "<hostname> [opts]",
53 "<hostname>" => "Host name.",
54 "--status=s" => "One of {alive,down,readonly}.",
55 "--ip=s" => "IP address of machine.",
56 "--port=i" => "HTTP port of mogstored",
57 "--getport=i" => "Alternate HTTP port serving readonly traffic",
58 "--altip=s" => "Alternate IP that is machine is reachable from",
59 "--altmask=s" => "Netmask which, when matches client, uses alt IP",
63 des
=> "Change the status of a host. (equivalent to 'modify --status')",
64 args
=> "<hostname> <status>",
66 "<hostname>" => "Host name to bring up or down.",
67 "<status>" => "One of {alive,down,readonly}.",
71 des
=> "Delete a host.",
74 "<hostname>" => "Host name to delete.",
80 des
=> "Add/modify devices.",
83 des
=> "List all devices, for each host.",
86 "--all" => "Include dead devices in list.",
87 "--hostname=s" => "Limit results to a given host.",
91 des
=> "List the summary of devices, for each host.",
94 "--status=s" => "Devices of status A. Defaults to 'alive,readonly,drain'",
95 "--hostname=s" => "Limit results to a given host.",
99 des
=> "Add a device to a host.",
100 args
=> "<hostname> <devid> [opts]",
102 "<hostname>" => "Hostname to add a device",
103 "<devid>" => "Numeric devid. Never reuse these.",
104 "--status=s" => "One of 'alive' or 'down'. Defaults to 'alive'.",
108 des
=> "Mark a device as {alive,dead,down,drain,readonly}",
109 args
=> "<hostname> <devid> <status>",
111 "<hostname>" => "Hostname of device",
112 "<devid>" => "Numeric devid to modify.",
113 "<status>" => "One of {alive,dead,down,drain,readonly}",
117 des
=> "Modify a device's properties.",
118 args
=> "<hostname> <devid> [opts]",
120 "<hostname>" => "Hostname of device",
121 "<devid>" => "Numeric devid to modify.",
122 "--status=s" => "One of {alive,dead,down,drain,readonly}",
123 "--weight=i" => "Positive numeric weight for device",
127 des
=> "Show the next available devid.",
132 des
=> "Add/modify domains (namespaces)",
135 des
=> "List all hosts.",
138 des
=> "Add a domain (namespace)",
141 "<domain>" => "Domain (namespace) to add.",
145 des
=> "Delete a domain.",
148 "<domain>" => "Domain (namespace) to add.",
154 des
=> "Add/modify file classes.",
157 des
=> "List all classes, for each domain.",
160 des
=> "Add a file class to a domain.",
161 args
=> "<domain> <class> [opts]",
163 "<domain>" => "Domain to add class to.",
164 "<class>" => "Name of class to add.",
165 "--mindevcount=i" => "Minimum number of replicas.",
166 "--replpolicy=s" => "Replication policy string.",
167 "--hashtype=s" => "Hash algorithm string ('MD5', 'NONE').",
171 des
=> "Modify properties of a file class.",
172 args
=> "<domain> <class> [opts]",
174 "<domain>" => "Domain to add class to.",
175 "<class>" => "Name of class to add.",
176 "--mindevcount=i" => "Minimum number of replicas.",
177 "--replpolicy=s" => "Replication policy string.",
178 "--hashtype=s" => "Hash algorithm string ('MD5', 'NONE').",
182 des
=> "Delete a file class from a domain.",
183 args
=> "<domain> <class>",
185 "<domain>" => "Domain of class to delete.",
186 "<class>" => "Class to delete.",
193 des
=> 'Manipulate slave database information in a running mogilefsd.',
196 des
=> 'List current store slave nodes.',
199 des
=> 'Add a slave node for store usage',
200 args
=> '<slave_key> [opts]',
202 '--dsn=s' => "DBI DSN specifying what database to connect to.",
203 '--username=s' => "DBI username for connecting.",
204 '--password=s' => "DBI password for connecting.",
208 des
=> 'Modify a slave node for store usage',
209 args
=> '<slave_key> [opts]',
211 '--dsn=s' => "DBI DSN specifying what database to connect to.",
212 '--username=s' => "DBI username for connecting.",
213 '--password=s' => "DBI password for connecting.",
217 des
=> 'Delete a slave node for store usage',
218 args
=> '<slave_key>',
223 des
=> "Control file rebalancing operations.",
226 des
=> 'Start a rebalance job',
229 des
=> 'Stop a rebalance job',
232 des
=> 'Show status of current rebalance job',
235 des
=> 'Display rebalance settings',
238 des
=> 'Show what devices the current policy would match',
241 des
=> 'Reset an existing policy',
244 des
=> 'Add or adjust the current policy',
247 '--options=s' => "Policy string (see docs/wiki for details)",
253 des
=> "Control a background filesystem check operation.",
256 des
=> 'Start (or resume) background fsck',
259 des
=> 'Stop (pause) background fsck',
262 des
=> 'Show fsck status',
265 des
=> 'Reset fsck position back to the beginning',
268 '--policy-only' => "Check repl policy (assumed locations); don't stat storage nodes",
269 '--startpos=i' => "FID to start at.",
273 des
=> 'Clear the fsck log',
276 des
=> 'Display the fsck log',
279 des
=> 'Tail the fsck log',
286 # load up our config files
289 Getopt
::Long
::Configure
("require_order", "pass_through");
291 "trackers=s" => \
$opts{trackers
},
292 "config=s" => \
$opts{config
},
293 "lib=s" => \
$opts{lib
},
294 "help" => \
$opts{help
},
295 "verbose" => \
$opts{verbose
},
296 ) or abortWithUsage
();
297 Getopt
::Long
::Configure
("require_order", "no_pass_through");
299 my @configs = ($opts{config
}, "$ENV{HOME}/.mogilefs.conf", "/etc/mogilefs/mogilefs.conf");
300 foreach my $fn (reverse @configs) {
301 next unless $fn && -e
$fn;
303 or die "unable to open $fn: $!\n";
306 next unless m!^\s*(\w+)\s*=\s*(.+?)\s*$!;
307 $opts{$1} = $2 unless ( defined $opts{$1} );
313 abortWithUsage
() if $opts{help
};
315 # make sure we have at least a topcmd
316 my $topcmd = shift(@ARGV);
317 abortWithUsage
() unless $topcmd && $usage->{$topcmd};
319 # break up the trackers and ensure we got some
320 if ($opts{trackers
}) {
321 $opts{trackers
} = [ split(/\s*,\s*/, $opts{trackers
}) ];
323 fail_text
('no_trackers')
324 unless ($opts{trackers
} && @
{$opts{trackers
}}) || detect_local_tracker
();
326 # okay, load up the libraries that we need
328 eval "use lib '$opts{lib}';";
330 eval "use MogileFS::Admin; use MogileFS::Client; 1;" or fail_text
('cant_find_module');
332 # dispatch if it's special
333 if ($topcmd eq 'check') {
334 die "Unknown options/arguments to 'check' command.\n" if @ARGV;
336 } elsif ($topcmd eq 'stats') {
337 die "Unknown options/arguments to 'stats' command.\n" if @ARGV;
342 my $verb = shift(@ARGV) or
343 abort_with_topcmd_help
($topcmd);
344 my $cmdinfo = $usage->{$topcmd}{subcmd
}{$verb};
345 abort_with_topcmd_help
($topcmd) unless $cmdinfo;
349 abort_with_topcmd_help
($topcmd, $verb, $msg);
352 # get the non-option (non --foo) arguments:
354 if (my $args = $cmdinfo->{args
}) {
355 my @args = split(/ /, $args);
356 foreach my $arg (@args) {
357 # positional (but named) parameter
358 if ($arg =~ /^<(.+)>$/) {
360 my $val = shift @ARGV;
361 # map e.g. "dev5" to 5
362 if ($argname eq "devid" && $val && $val =~ /^dev(\d+)$/) {
365 $badargs->("Missing argument '$argname'") unless defined $val;
366 $badargs->("Unexpected option. Expected argument '$argname'") if $val =~ /^-/;
367 $cmdargs{$argname} = $val;
368 } elsif ($arg eq "[opts]") {
371 die "INTERNAL ERROR.";
374 $badargs->("Unexpected extra argument.") if @ARGV && $ARGV[0] !~ /^-/;
376 $badargs->("Unexpected arguments when expecting none.") if @ARGV;
380 if (my $opts = $cmdinfo->{opts
}) {
382 foreach (keys %$opts) {
384 next if $k =~ /^</; # don't care about these.
385 die "BOGUS KEY: '$k'" unless $k =~ /^--([\w\-]+)(=.+)?$/;
386 my ($oname, $type) = ($1, $2 || "");
387 $getopts{"$oname$type"} = \
$cmdargs{$1};
390 or abort_with_topcmd_help
($topcmd, $verb);
393 # see what we should do
396 *{"cmd_${topcmd}_${verb}"} or abortWithUsage
();
399 # now call our lovely lovely sub
400 $cmdsub->(\
%cmdargs);
403 sub detect_local_tracker
{
404 require IO
::Socket
::INET
;
405 my $loctrack = "127.0.0.1:7001";
406 my $sock = IO
::Socket
::INET
->new(PeerAddr
=> $loctrack, Timeout
=> 1);
407 return 0 unless $sock;
408 $opts{trackers
} = [$loctrack];
412 ###########################################################################
414 ###########################################################################
417 # step 1: we want to check each tracker for responsiveness
419 my ($hosts, $devices);
421 print "Checking trackers...\n";
422 foreach my $t (@
{$opts{trackers
}}) {
424 my $mogadm = mogadm
($t);
426 my $lhosts = hosts
($mogadm);
427 my $ldevs = devices
($mogadm);
428 if ($lhosts && $ldevs) {
433 print "REQUEST FAILURE (is the tracker up?)\n";
436 print "INITIAL FAILURE (bad configuration?)\n";
440 # we should have hosts if we get here
441 fail_text
('no_hosts') unless $hosts;
444 # step 2: now hit each of the hosts for responsiveness
445 print "Checking hosts...\n";
447 foreach my $hostid (sort { $a <=> $b } keys %$hosts) {
448 printf " [%2d] %s ... ", $hostid, $hosts->{$hostid}->{hostname
};
449 if ($hosts->{$hostid}->{status
} eq 'alive') {
450 my $url = 'http://' . $hosts->{$hostid}->{hostip
} . ':' . $hosts->{$hostid}->{http_port
} . '/';
451 my $file = get
($url);
454 push @urls, [ $hostid, $url ];
456 print "REQUEST FAILURE FETCHING: $url\n";
459 print "skipping; status = $hosts->{$hostid}->{status}\n";
463 # everything should be chill
464 fail_text
('no_devices') unless @urls;
467 # step 3: check devices for each host
468 print "Checking devices...\n";
469 printf " host device %10s %10s %10s %7s %7s %4s\n", 'size(G)', 'used(G)', 'free(G)', 'use% ', 'ob state', 'I/O%';
470 printf " ---- ------------ ---------- ---------- ---------- ------ ---------- -----\n";
472 # Initialize to zero so that the total outputs doesn't need to check for undefined.
473 map { $total{$_} = 0; } qw(total used avail);
475 foreach my $hosturl (@urls) {
476 my ($hostid, $url) = @
$hosturl;
477 my $devs = $devices->{$hostid};
478 DEV
: foreach my $devid (sort { $a <=> $b } keys %$devs) {
479 my $dev = $devs->{$devid};
480 my $status = $dev->{status
} || "??";
481 next if $status eq "dead";
483 printf " [%2d] %-7s", $hostid, "dev$devid";
485 my $usage = get
($url . "/dev$devid/usage");
486 if (! defined $usage) {
487 print "REQUEST FAILURE FETCHING: $url" . "dev$devid/usage\n";
490 if (length($usage) < 1) {
491 print "USAGE FILE BROKEN OR EMPTY: $url" . "dev$devid/usage\n";
494 my %data = ( map { split(/:\s+/, $_) } split(/\r?\n/, $usage) );
495 foreach (qw(time used total avail)) {
496 $data{$_} = 0 if (!$data{$_} ||
497 $data{$_} !~ /\A\d+(\.\d+)?\Z/);
499 foreach (qw(available device disk)) {
500 if (! exists $data{$_} || !$data{$_}) {
501 print "MISSING FIELD ($_) FROM USAGE FILE: $url" . "dev$devid/usage\n";
505 $data{age
} = $now - $data{time};
506 $data{used
} /= 1024**2;
507 $data{total
} /= 1024**2;
508 $data{available
} /= 1024**2;
509 $data{avail
} = $data{available
};
510 my $pct = 100 - $data{available
}/$data{total
}*100;
511 $total{used
} += $data{used
};
512 $total{avail
} += $data{avail
};
513 $total{total
} += $data{total
};
516 if (defined($dev->{utilization
}) && $dev->{utilization
} =~ /\A\d+(\.\d+)?\Z/) {
517 $util = sprintf("%.1f", $dev->{utilization
});
520 printf(" %10.3f %10.3f %10.3f %6.2f%% %-7s %5s\n",
521 (map { $data{$_} } qw(total used avail)),
522 $pct, ($dev->{observed_state
} || "?"),
527 # Avoid division by zero
528 $pct = 100 - $total{avail
}/$total{total
}*100 if($total{total
} > 0);
530 printf " ---- ------------ ---------- ---------- ---------- ------\n";
531 printf " total:%10.3f %10.3f %10.3f %6.2f%%\n", (map { $total{$_} } qw(total used avail)), $pct;
533 # if we get here, all's well
538 fail
("mogadm stats is deprecated by new 'mogstats' utility");
543 fail_text
('no_hosts') unless $hosts;
545 foreach my $hostid (sort keys %$hosts) {
546 my $host = $hosts->{$hostid};
547 print "$host->{hostname} [$hostid]: $host->{status}\n";
549 'IP', "$host->{hostip}:$host->{http_port}",
550 'Alt IP', $host->{altip
},
551 'Alt Mask', $host->{altmask
},
552 'GET Port', $host->{http_get_port
},
554 while (my ($k, $v) = splice(@data, 0, 2)) {
556 printf " %-10s\%s\n", "$k:", $v;
566 my $hosts = hosts_byname
();
567 fail_text
('no_hosts') unless $hosts;
569 my $name = delete $args->{hostname
};
570 cmd_help_die
("No hostname") unless $name;
571 fail
('Host already exists.') if $hosts->{$name};
573 # make sure we have an ip
574 unless ($args->{ip
}) {
575 my $addr = gethostbyname($name);
576 fail_text
('host_add_no_ip') unless $addr;
577 $args->{ip
} = inet_ntoa
($addr);
581 $args->{port
} ||= 7500;
582 $args->{status
} ||= 'down';
584 # FIXME: verify the status can't be 'alive' if we can't get to ip:port
585 # OR BETTER: also make default status the reachability of that ip:port
587 # now create the host
588 my $mogadm = mogadm
();
589 $mogadm->create_host($name, $args);
591 fail
("Failure creating host: " . $mogadm->errstr);
594 ok
('Host has been created.');
597 sub cmd_host_modify
{
599 my $name = delete $args->{hostname
};
601 # FIXME: verify the status can't be 'alive' if we can't get to ip:port
603 # now modify the host
604 my $mogadm = mogadm
();
605 $mogadm->update_host($name, $args);
607 fail
("Failure modifying host: " . $mogadm->errstr);
610 ok
('Host has been modified.');
613 sub cmd_host_delete
{
615 my $name = delete $args->{hostname
};
617 # now modify the host
618 my $mogadm = mogadm
();
619 $mogadm->delete_host($name);
621 fail
("Failure deleting host: " . $mogadm->errstr);
624 ok
('Host has been deleted.');
630 my $mogadm = mogadm
();
631 $mogadm->update_host($args->{hostname
}, { status
=> $args->{status
} });
633 fail
("Failure updating host status: " . $mogadm->errstr);
636 ok
('Host status updated.');
639 sub cmd_domain_list
{
640 # actually lists domains and classes
641 my $domains = domains
() or
642 fail_text
('no_domains');
644 printf " %-20s %-20s %-12s %-12s %-7s\n", "domain", "class", "mindevcount", "replpolicy", "hashtype";
645 printf "%-20s %-20s %-12s %-12s %-7s\n", '-' x
20, '-' x
20, '-' x
13, '-' x
12, '-' x
7;
646 foreach my $domain (sort keys %$domains) {
647 foreach my $class (sort keys %{$domains->{$domain}}) {
648 my $dom = $domains->{$domain}->{$class};
649 printf " %-20s %-20s %-8d %-13s %-7s\n", $domain, $class,
650 $dom->{mindevcount
} || 0, $dom->{replpolicy
} || '',
651 $dom->{hashtype
} || '';
662 my $domain = delete $args->{domain
};
665 my $mogadm = mogadm
();
666 $mogadm->create_domain($domain);
668 fail
('Error creating domain: ' . $mogadm->errstr);
671 ok
('Domain created.');
674 sub cmd_domain_delete
{
677 my $domain = $args->{domain
};
680 my $mogadm = mogadm
();
681 $mogadm->delete_domain($domain);
683 fail
('Error deleting domain: ' . $mogadm->errstr);
686 ok
('Domain deleted.');
690 # same, pass it through
697 my $domain = delete $args->{domain
};
698 my $class = delete $args->{class};
700 cmd_help_die
() unless $domain && $class;
702 $args->{mindevcount
} ||= 2;
703 $args->{replpolicy
} ||= '';
705 my $mogadm = mogadm
();
706 $mogadm->create_class($domain, $class, $args);
708 fail
('Error creating class: ' . $mogadm->errstr);
711 ok
('Class created.');
714 sub cmd_class_modify
{
717 my $domain = delete $args->{domain
};
718 my $class = delete $args->{class};
720 cmd_help_die
() unless $domain && $class;
722 $args->{mindevcount
} ||= 2;
723 $args->{replpolicy
} ||= '';
725 my $mogadm = mogadm
();
726 $mogadm->update_class($domain, $class, $args);
728 fail
('Error updating class: ' . $mogadm->errstr);
731 ok
('Class updated.');
734 sub cmd_class_delete
{
737 my $domain = $args->{domain
};
738 my $class = $args->{class};
740 cmd_help_die
() unless $domain && $class;
742 my $mogadm = mogadm
();
743 $mogadm->delete_class($domain, $class);
745 fail
('Error deleting class: ' . $mogadm->errstr);
748 ok
('Class deleted.');
754 my $hosts = hosts
() or
755 fail_text
('no_hosts');
757 my $host = $args->{hostname
};
758 my $devid = $args->{devid
};
759 my $state = $args->{status
} || "alive";
761 cmd_help_die
("devid should be numeric") unless $devid =~ /^\d+$/;
763 # FIXME: server should be fixed to verify via HTTP that the devid directory exists
765 my $mogadm = mogadm
();
766 $mogadm->create_device(hostname
=> $host, devid
=> $devid, state => $state);
769 fail
('Error adding device: ' . $mogadm->errstr);
777 if ($args->{status
} && $args->{status
} eq 'drain') {
778 print "***NOTE***: As of server version 2.40 'drain' has changed. See docs/wiki\n";
782 sub cmd_device_mark
{
785 warn_on_drain
($args);
786 my $mogadm = mogadm
();
787 $mogadm->change_device_state($args->{hostname
},
791 fail
('Error updating device: ' . $mogadm->errstr);
794 ok
('Device updated.');
797 sub cmd_device_modify
{
799 my $hostname = delete $args->{hostname
};
800 my $devid = delete $args->{devid
};
802 warn_on_drain
($args);
803 my $mogadm = mogadm
();
804 $mogadm->update_device($hostname, $devid, $args);
807 fail
('Error updating device: ' . $mogadm->errstr);
810 ok
('Device updated.');
813 sub cmd_device_list
{
817 fail_text
('no_hosts') unless $hosts;
819 my $devs = devices
();
820 fail_text
('no_devices') unless $devs;
821 my $hostname = $args->{hostname
};
823 foreach my $hostid (sort keys %$hosts) {
824 my $host = $hosts->{$hostid};
825 if (defined $hostname && $hostname ne $host->{hostname
}) {
828 print "$host->{hostname} [$hostid]: $host->{status}\n";
830 printf "%7s %-7s %10s %10s %10s %10s\n", '', '', 'used(G)', 'free(G)', 'total(G)', 'weight(%)';
831 foreach my $devid (sort keys %{$devs->{$hostid} || {}}) {
832 my $dev = $devs->{$hostid}->{$devid};
833 next if $dev->{status
} eq "dead" && ! $args->{all
};
835 my $total = $dev->{mb_total
} / 1024;
836 my $used = $dev->{mb_used
} / 1024;
837 my $free = $total - $used;
838 printf "%7s: %7s %10.3f %10.3f %10.3f %10u\n", "dev$devid", $dev->{status
}, $used, $free, $total, $dev->{weight
};
847 sub cmd_device_summary
{
849 my $hostname = $args->{hostname
};
851 $show_state{$_} = 1 foreach split(/,/, ($args->{status
} || "alive,readonly,drain"));
854 fail_text
('no_hosts') unless $hosts;
856 my $devs = devices
();
857 fail_text
('no_devices') unless $devs;
859 printf "%-15s %6s %7s %8s %8s %8s %8s\n", 'Hostname', 'HostID', 'Status', 'used(G)', 'free(G)', 'total(G)', '%Used';
860 foreach my $hostid (sort keys %$hosts) {
861 my $host = $hosts->{$hostid};
862 if (defined $hostname && $hostname ne $host->{hostname
}) {
865 my ($total,$used) = (0, 0);
867 foreach my $devid (sort keys %{$devs->{$hostid} || {}}) {
868 my $dev = $devs->{$hostid}->{$devid};
869 next unless $show_state{$dev->{status
}};
871 my $devtotal = $dev->{mb_total
} / 1024;
872 my $devused = $dev->{mb_used
} / 1024;
877 my $free = $total - $used;
878 printf "%-15s [%4d]: %6s", $host->{hostname
}, $hostid, $host->{status
};
879 printf " %8.3f %8.3f %8.3f ", $used, $free, $total;
880 printf "%8.2f", 100*$used/$total if $total;
888 sub cmd_device_next
{
889 my $devs = mogadm
()->get_devices;
890 fail_text
('no_devices') unless @
$devs;
892 foreach my $dev (@
$devs) {
893 my $devid = $dev->{devid
};
894 if ($devid > $maxid) {
899 print($maxid + 1, "\n");
904 my $mogadm = mogadm
();
906 my $slaves = $mogadm->slave_list();
908 foreach my $key (sort keys %$slaves) {
909 my $slavedata = $slaves->{$key};
910 my ($dsn, $username, $password) = @
$slavedata;
911 print "$key --dsn=$dsn --username=$username --password=$password\n";
918 my $mogadm = mogadm
();
921 my $rc = $mogadm->slave_add($args->{slave_key
}, $args->{dsn
}, $args->{username
}, $args->{password
});
926 fail
("Slave failed to be added");
930 sub cmd_slave_modify
{
931 my $mogadm = mogadm
();
934 my $key = delete $args->{slave_key
} or cmd_help_die
("Key argument is required");
936 my $rc = $mogadm->slave_modify($key, %$args);
939 ok
("Slave modify success");
941 fail
("Slave modify failure: " . $mogadm->errstr);
945 sub cmd_slave_delete
{
946 my $mogadm = mogadm
();
950 my $rc = $mogadm->slave_delete($args->{slave_key
});
955 fail
("Slave delete failed");
959 sub cmd_rebalance_start
{
960 my $mogadm = mogadm
();
961 my $res = $mogadm->rebalance_start || fail
($mogadm->errstr);
962 ok
("rebalance started");
965 sub cmd_rebalance_stop
{
966 my $mogadm = mogadm
();
967 my $res = $mogadm->rebalance_stop || fail
($mogadm->errstr);
968 ok
("rebalance stopped");
971 sub cmd_rebalance_reset
{
972 my $mogadm = mogadm
();
973 my $res = $mogadm->rebalance_reset || fail
($mogadm->errstr);
974 ok
("rebalance reset");
977 # TODO: Make output prettier? Put hostname next to device name, print device
979 sub cmd_rebalance_test
{
980 my $mogadm = mogadm
();
981 my $res = $mogadm->rebalance_test || fail
($mogadm->errstr);
982 print "Tested rebalance policy...\n";
983 my $s = $mogadm->server_settings;
984 print "Policy: ", $s->{rebal_policy
}, "\n\n";
985 print "Source devices:\n";
986 for my $dev (sort split /,/, $res->{sdevs
}) {
987 print " - ", $dev, "\n";
989 print "Destination devices:\n";
990 for my $dev (sort split /,/, $res->{ddevs
}) {
991 print " - ", $dev, "\n";
995 sub cmd_rebalance_status
{
996 my $mogadm = mogadm
();
998 my $ss = $mogadm->server_settings or fail
($mogadm->errstr);
999 my $res = $mogadm->rebalance_status or fail
($mogadm->errstr);
1000 if ($ss->{rebal_host
}) {
1001 print "Rebalance is running\n";
1003 print "Rebalance is stopped\n";
1005 print "Rebalance status:\n";
1006 for my $o (sort split /\s+/, $res->{state}) {
1007 my ($k, $v) = split /=/, $o;
1008 printf("%25s = %-s\n", $k, $v);
1012 sub cmd_rebalance_policy
{
1013 my $mogadm = mogadm
();
1016 my $res = $mogadm->rebalance_set_policy($args->{options
})
1017 or fail
($mogadm->errstr);
1019 ok
("changed policy setting");
1022 sub cmd_rebalance_settings
{
1023 my $mogadm = mogadm
();
1025 my $ss = $mogadm->server_settings
1026 or fail
("can't get settings");
1027 foreach my $k (sort keys %$ss) {
1028 next unless ($k =~ '^rebal_');
1029 next if ($k eq 'rebal_state');
1030 printf("%25s = %-s\n", $k, $ss->{$k});
1034 sub cmd_fsck_start
{
1035 my $mogadm = mogadm
();
1036 my $res = $mogadm->fsck_start || fail
($mogadm->errstr);
1041 my $mogadm = mogadm
();
1042 my $res = $mogadm->fsck_stop || fail
($mogadm->errstr);
1046 sub cmd_fsck_reset
{
1047 my $mogadm = mogadm
();
1049 my $res = $mogadm->fsck_reset(
1050 policy_only
=> $args->{"policy-only"},
1051 startpos
=> $args->{"startpos"},
1053 or fail
($mogadm->errstr);
1057 sub cmd_fsck_clearlog
{
1058 my $mogadm = mogadm
();
1059 my $res = $mogadm->fsck_clearlog || fail
($mogadm->errstr);
1060 ok
("fsck log cleared");
1065 my $max = $opts{start
};
1066 my $mogadm = mogadm
();
1068 my $fmt = "%-20s %5s %13s %10s\n";
1069 printf($fmt, "unixtime", "event", "fid", "devid");
1071 my @rows = $mogadm->fsck_log_rows(after_logid
=> $max);
1073 $opts{on_stall
}->();
1076 foreach my $row (@rows) {
1081 $row->{devid
} || "-");
1082 $max = $row->{logid
};
1087 sub cmd_fsck_printlog
{
1088 _log_dump
(start
=> 0,
1089 on_stall
=> sub { exit 0; });
1092 sub cmd_fsck_taillog
{
1093 my $mogadm = mogadm
();
1094 my $status = $mogadm->fsck_status
1095 or fail
("can't get fsck status");
1096 _log_dump
(start
=> $status->{max_logid
} - 20,
1097 on_stall
=> sub { sleep 5; });
1100 sub cmd_fsck_status
{
1101 my $mogadm = mogadm
();
1102 my $status = $mogadm->fsck_status
1103 or fail
("can't get fsck status");
1105 my %known = map { $_ => 1 } qw(
1112 return $status->{$k};
1116 printf("%11s: %-s\n", @_);
1119 my $host = $st->('host');
1120 $line->("Running", $st->('running') ?
"Yes (on $host)" : "No");
1122 my $ratio = $st->('end_fid') ?
($st->('max_fid_checked') / $st->('end_fid')) : 0;
1123 my $perc = sprintf("%0.02f%%", 100 * $ratio);
1126 $st->('max_fid_checked') . " / " . $st->('end_fid')
1128 my $elap = $st->('start_time') ?
1129 (($st->('stop_time') || $st->('current_time')) - $st->('start_time')) :
1133 return int($s) . "s" if $s < 60;
1134 return int($s/60) . "m";
1136 my $per_sec = $elap ?
($st->('max_fid_checked') / $elap) : 0;
1137 $line->("Time", sprintf("%s (%d fids/s; %s remain)",
1139 sprintf("%0.1f", $per_sec),
1140 $as_time->($per_sec ?
1141 (($st->('end_fid') - $st->('max_fid_checked'))
1145 $line->("Check Type", ($st->('policy_only') ?
1146 "Repl policy only (skip file checks)" :
1147 "Normal (check policy + files)"));
1149 if (my @unk = grep { !$known{$_} } sort keys %$status) {
1152 $line->("[$_]", $status->{$_});
1158 sub cmd_settings_list
{
1159 my $mogadm = mogadm
();
1160 unless ($mogadm->can("server_settings")) {
1161 fail
("settings commands require MogileFS::Client >= 1.07");
1163 my $ss = $mogadm->server_settings
1164 or fail
("can't get settings");
1165 foreach my $k (sort keys %$ss) {
1166 # Don't list noisy "setting"
1167 next if ($k =~ '^rebal');
1168 printf("%25s = %-s\n", $k, $ss->{$k});
1172 sub cmd_settings_set
{
1173 my $mogadm = mogadm
();
1174 unless ($mogadm->can("set_server_setting")) {
1175 fail
("settings commands require MogileFS::Client >= 1.07");
1179 $mogadm->set_server_setting($args->{key
}, $args->{value
})
1180 or fail
($mogadm->errstr);
1184 ###########################################################################
1186 ###########################################################################
1188 sub abortWithUsage
{
1189 my $ret = "Usage: (enter any command prefix, leaving off options, for further help)\n\n";
1190 foreach my $cmd (@topcmds) {
1191 my $sbc = $usage->{$cmd}->{subcmd
};
1193 $ret .= " mogadm $cmd ...\n";
1195 $ret .= sprintf(" mogadm %-25s %-s\n",
1197 $usage->{$cmd}{des
} || "");
1200 foreach my $v (sort keys %$sbc) {
1201 my $scv = $usage->{$cmd}{subcmd
}{$v};
1203 my $dotdot = $scv->{args
} ?
"..." : "";
1204 $ret .= sprintf(" %-25s %-s\n",
1214 sub abort_with_topcmd_help
{
1215 my ($cmd, $verb, $msg) = @_;
1217 print "\nERROR: $msg\n\n";
1219 my $cmdsfx = $verb ?
"-$verb" : "";
1220 my $ret = "Help for '$cmd$cmdsfx' command:\n";
1222 $ret .= " (enter any command prefix, leaving off options, for further help)\n";
1225 foreach my $subcmdv (sort keys %{$usage->{$cmd}{subcmd
}}) {
1226 next if $verb && $verb ne $subcmdv;
1227 my $scv = $usage->{$cmd}{subcmd
}{$subcmdv};
1228 $ret .= sprintf(" %-50s %-s\n",
1229 "mogadm $cmd $subcmdv " . ($scv->{args
} || ""),
1234 my $scv = $usage->{$cmd}{subcmd
}{$verb};
1235 foreach my $opt (sort {
1236 (substr($b, 0, 1) cmp substr($a, 0, 1)) ||
1238 } keys %{$scv->{opts
} || {}})
1240 printf(" %-20s %s\n", $opt, $scv->{opts
}->{$opt});
1249 abort_with_topcmd_help
($topcmd, $verb, $msg);
1256 ######################################################################
1257 cant_find_module
=> <<END,
1258 Error loading modules: $@
1260 Please ensure that you have the MogileFS::Admin and MogileFS::Client
1261 modules and all necessary dependencies installed in a location in your
1262 search path. Or, add a search path to mogadm:
1264 mogadm --lib=/path/to/lib
1266 Or add it to the configuration file:
1271 ######################################################################
1273 Unable to access MogileFS tracker and/or instantiate a MogileFS::Admin object.
1276 ######################################################################
1277 no_domains
=> "Unable to retrieve domains from tracker(s).\n",
1279 ######################################################################
1280 no_devices
=> "No devices found on tracker(s).\n",
1282 ######################################################################
1283 host_add_no_ip
=> <<END,
1284 Hostname does not resolve to an IP, and you didn\'t specify one on the options
1285 list. Please either verify the host resolves, or try again:
1287 mogadm host add <hostname> --ip=<ipaddr> [...]
1290 ######################################################################
1292 Unable to retrieve host information from tracker(s).
1295 ######################################################################
1296 no_trackers
=> <<END,
1297 In order to use the mogadm toolkit, you need to provide the information about
1298 where your trackers are.
1300 In your configuration file:
1302 trackers = 10.10.0.33:7001, 10.10.0.34:7001
1304 Or on the command line
1306 mogadm --trackers=10.10.0.33:7001,10.10.0.34:7001
1309 }->{$_[0]} || "UNDEFINED [$_[0]]";
1313 print STDERR text
($_[0]) . "\n";
1318 print STDERR
$_[0] . "\n";
1323 if ($opts{verbose
}) {
1324 print STDOUT
$_[0] . "\n" if $_[0];
1332 $host = [ $host ] unless ref $host;
1334 $host = $opts{trackers
};
1336 # 10 seconds is the max time used for any of the admin locks (fsck status)
1337 # plus we leave a bit of time for work.
1339 $timeout = $opts{timeout
} if $opts{timeout
} && $opts{timeout
} =~ /^[0-9]+$/;
1340 # $MogileFS::DEBUG = 2;
1341 my $mogadm = MogileFS
::Admin
->new( hosts
=> $host, timeout
=> $timeout );
1342 fail_text
('no_mogadm') unless $mogadm;
1347 my $mogadm = shift() || mogadm
();
1350 $res = $mogadm->get_stats();
1357 my $mogadm = shift() || mogadm
();
1358 fail_text
('no_mogadm') unless $mogadm;
1362 $res = _array_to_hashref
($mogadm->get_hosts(), 'hostname');
1369 my $mogadm = shift() || mogadm
();
1370 fail_text
('no_mogadm') unless $mogadm;
1374 $res = _array_to_hashref
($mogadm->get_hosts(), 'hostid');
1381 my $mogadm = shift() || mogadm
();
1382 fail_text
('no_mogadm') unless $mogadm;
1386 $res = _array_to_hashref
($mogadm->get_devices(), [ 'hostid', 'devid' ]);
1393 my $mogadm = shift() || mogadm
();
1394 fail_text
('no_mogadm') unless $mogadm;
1398 $res = $mogadm->get_domains();
1404 sub _array_to_hashref
{
1405 my ($array, $key) = @_;
1406 die "bad caller to _array_to_hashref\n"
1407 unless $array && $key;
1408 $key = [ $key ] unless ref $key eq 'ARRAY';
1409 my $kmax = scalar(@
$key) - 1;
1411 # and a dose of handwavium...
1413 foreach my $row (@
$array) {
1415 for (my $i = 0; $i <= $kmax; $i++) {
1417 # we're on the last key so just assign into $ref
1418 $ref->{$row->{$key->[$i]}} = $row;
1420 # not on the last, so keep descending
1421 $ref->{$row->{$key->[$i]}} ||= {};
1422 $ref = $ref->{$row->{$key->[$i]}};
1427 # return result.. duh
1435 mogadm - MogileFS admin tool
1439 $ mogadm [config options] <argument(s)> [argument options]
1443 (prints contextual help, if missing command/arguments)
1450 =item B<--lib=/path/to/lib>
1452 Set this option to a path to include this directory in the module
1455 =item B<--trackers=10.0.0.117:7001,10.0.0.118:7001,...>
1457 Use these MogileFS trackers for status information.
1467 Check to ensure that all of the MogileFS system components are functioning
1468 and that we can contact everybody. The quickest way of ensuring that the
1469 entire MogileFS system is functional from the current machine's point of view.
1471 =item B<host add E<lt>hostE<gt> [host options]>
1473 =item B<host modify E<lt>hostE<gt> [host options]>
1475 =item B<host mark E<lt>hostE<gt> E<lt>statusE<gt>>
1477 =item B<host delete E<lt>hostE<gt>>
1481 Functions for manipulating hosts. For add and modify, host options is in
1482 the format of normal command line options and can include anything in the
1483 L</"HOST OPTIONS"> section.
1485 =item B<device add E<lt>hostE<gt> E<lt>device idE<gt>>
1487 =item B<device mark E<lt>hostE<gt> E<lt>device idE<gt> E<lt>statusE<gt>>
1489 =item B<device modify E<lt>hostE<gt> E<lt>deviceE<gt> [device options]>
1491 =item B<device delete E<lt>hostE<gt> E<lt>deviceE<gt>>
1493 =item B<device list>
1495 =item B<device next>
1497 Functions for manipulating devices. For add and modify, device options are in
1498 the format of normal command line options and can include anything in the
1499 L</"DEVICE OPTIONS"> section.
1501 =item B<domain add E<lt>domainE<gt>>
1503 =item B<domain delete E<lt>domainE<gt>>
1505 =item B<domain list>
1507 Simple commands for managing MogileFS domains. Note that you cannot delete
1508 a domain unless it has no classes and is devoid of files.
1510 =item B<class add E<lt>domainE<gt> E<lt>classE<gt> [class options]>
1512 =item B<class modify E<lt>domainE<gt> E<lt>classE<gt> [class options]>
1514 =item B<class delete E<lt>domainE<gt> E<lt>classE<gt>>
1518 Commands for working with classes. Please see the L</"CLASS OPTIONS"> section
1519 for the options to use with add/modify. Also, delete requires that the class
1520 have no files in it before it will allow the deletion.
1524 Add/remove slaves replicating from MogileFS master database.
1528 Run B<mogadm slave> by itself for contextual help.
1530 =item B<fsck printlog>
1532 =item B<fsck taillog>
1534 =item B<fsck clearlog>
1536 Display or clear the log of fsck events.
1538 =item B<fsck reset [fsck options]>
1540 Reset fsck position back to the beginning. Please see the L</"FSCK OPTIONS">
1541 section for options to use with fsck.
1545 Start (or resume) background fsck from the last checked fid. If you want to
1546 check every fid, you must call B<fsck reset> before calling start.
1548 =item B<fsck status>
1550 Show the status of the presently active (or last if none active) fsck. This
1551 includes what FIDs are being checked, time statistics, check type as well as a
1552 summary of problems encountered so far.
1556 Stop (pause) background fsck
1558 =item B<settings list>
1560 Display all present MogileFS settings.
1562 =item B<settings set E<lt>keyE<gt> E<lt>valueE<gt>>
1564 Set the server setting for 'key' to 'value'.
1566 The current settings are E<lt>enable_rebalanceE<gt> (set to 1 to start
1567 rebalance mode to move files to under-used devices) and
1568 E<lt>memcache_serversE<gt> (enable memcached caching in the tracker).
1576 =item B<--ip=E<lt>ip of hostE<gt>>
1578 =item B<--port=E<lt>port of mogstored on hostE<gt>>
1580 Contact information for the host. This is the minimum set of information needed
1583 =item B<--getport=E<lt>alternate retrieval part on hostE<gt>>
1585 If provided, causes the tracker to use this port for retrieving files. Uploads are
1586 still processed at the standard port.
1588 =item B<--altip=E<lt>alternate IPE<gt>>
1590 =item B<--altmask=E<lt>mask to activate alternate IPE<gt>>
1592 If a client request comes in from an IP that matches the alternate mask, then the
1593 host IP is treated as the alternate IP instead of the standard IP. This can be
1594 used, for example, if you have two networks and you need to return one IP to
1595 reach the node on one network, but a second IP to reach it on the alternate
1598 =item B<--status=E<lt>host statusE<gt>>
1600 Valid host statuses are one of: alive, down, readonly.
1604 =head1 DEVICE OPTIONS
1608 =item B<--status=E<lt>device statusE<gt>>
1610 Valid device statuses are one of: alive, dead, down, drain, readonly.
1612 =item B<--weight=E<lt>device weight<gt>>
1614 The weight used in calculation of preferred paths. It must be a positive
1619 =head1 CLASS OPTIONS
1623 =item B<--mindevcount=E<lt>valueE<gt>>
1625 Number of devices the files in this class should be replicated across. Can be
1626 set to anything >= 1.
1628 =item B<--replpolicy=E<lt>valueE<gt>>
1630 Stringified replication policy. ie "MultipleHosts(3)" is equivalent to a
1631 --mindevcount=3. See documentation or plugins on alternative policies.
1633 =item B<--hashtype=E<lt>valueE<gt>>
1635 Name of the hash algorithm used for checksumming. "MD5" or "NONE" for no
1644 =item B<--policy-only>
1646 Check replication policy (assumed locations) only; don't stat storage nodes for
1647 actual file presence.
1656 $ mogadm host add foo.local
1657 $ mogadm host add foo.local --status=down --ip=10.0.0.34 --port=7900
1658 $ mogadm host mark foo.local down
1659 $ mogadm host modify foo.local --port=7500
1660 $ mogadm host delete foo.local
1662 Device manipulation:
1664 $ mogadm device list
1665 $ mogadm device summary
1666 $ mogadm device summary --status=dead,down
1667 $ mogadm device next
1668 $ mogadm device add foo.local 16
1669 $ mogadm device add foo.local 17 --status=alive
1670 $ mogadm device mark foo.local 17 down
1671 $ mogadm device modify foo.local 17 --status=alive --weight=10
1672 $ mogadm device delete foo.local 17
1674 Domain manipulation:
1676 $ mogadm domain list
1677 $ mogadm domain add first.domain
1678 $ mogadm domain delete first.domain
1683 $ mogadm class add first.domain my.class
1684 $ mogadm class add first.domain my.class --mindevcount=3
1685 $ mogadm class add first.domain my.class --replpolicy="MultipleHosts(3)"
1686 $ mogadm class modify first.domain my.class --mindevcount=2
1687 $ mogadm class modify first.domain my.class --replpolicy="MultipleHosts(3)"
1688 $ mogadm class delete first.domain my.class
1690 Check the status of your entire MogileFS system:
1694 Check every file in the entire MogileFS system:
1698 $ mogadm fsck status
1699 $ mogadm fsck printlog
1701 See all the things mogadm can do:
1705 Get help on a sub-command:
1710 =head1 CONFIGURATION
1712 It is recommended that you create a configuration file such as C</etc/mogilefs/mogilefs.conf> (or at C<~/.mogilefs.conf>) to
1713 be used for configuration information. Basically all you need is something like:
1715 trackers = 10.0.0.23:7001, 10.0.0.15:7001
1717 # if MogileFS::Admin files aren't installed in standard places:
1718 lib = /home/mogilefs/cgi-bin
1720 Note that these can also be specified on the command line, as per above.
1724 Brad Fitzpatrick E<lt>L<brad@danga.com>E<gt>
1726 Mark Smith E<lt>L<junior@danga.com>E<gt>
1728 Leon Brocard E<lt>L<acme@astray.com>E<gt>, open sourced permissions from Foxtons Ltd.
1730 Robin H. Johnson E<lt>robbat2@orbis-terrarum.netE<gt>
1734 Please report any on the MogileFS mailing list: L<http://groups.google.com/group/mogile/>.
1738 Licensed for use and redistribution under the same terms as Perl itself.