Whamcloud - gitweb
569daf6b57fcdda1d8bc3b50b0e7e9606efb698f
[fs/lustre-release.git] / lustre / scripts / ldev
1 #!/usr/bin/perl
2 #
3 # ldev - parser for /etc/ldev.conf
4 #
5 use strict;
6 use File::Basename;
7 use Getopt::Long qw/ :config posix_default no_ignore_case/;
8
9 $ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin";
10
11 my $prog = basename($0);
12
13 my $usage = <<EOF;
14 Usage: $prog [OPTIONS]...
15
16 Parse ldev.conf and answer the following queries:
17
18   -h, --help          Display this help.
19   -c, --config FILE   Set path to config file.
20   -H, --hostname NAME Use NAME instead of local hostname for queries.
21   -p, --partner       Print hostname of failover partner.
22   -l, --local         Print labels for local devices.
23   -f, --foreign       Print labels for foreign devices.
24   -a, --all           Print labels for local and foreign devices.
25   -F, --filesys NAME  Print labels for file system NAME.
26   -s, --sanity        Sanity check config on this node.
27   -d, --device LABEL  Print storage device of LABEL.
28   -j, --journal LABEL Print journal device of LABEL if it exists.
29   -r, --raidtab LABEL Print raidtab of LABEL if it exists.
30   -t, --type LABEL    Print device type of LABEL, i.e. "zfs" or "md".
31   -z, --zpool LABEL   Print zpool containing LABEL.
32   CMD [ARGS] ...      Run CMD in parallel for each device substituting:
33                       %f=fsname  %d=device  %i=dec-index %n=main-nid %l=label
34                       %t=srvtype %j=journal %I=hex-index %N=fail-nid %m=mgs-nid
35                       May be used in combination with -l, -f, -a, -F options.
36 EOF
37
38 my %eparse = (
39    elabel_uniq  =>    "label used more than once",
40    epairwise    =>    "local and foreign host not mapped to each other",
41    efieldcount  =>    "line has less than the minimum number of fields (4)",
42    ekeyval      =>    "malformed id=name",
43 );
44
45 my %conf = ();
46
47 #
48 # Main
49 #
50
51 parse_cmdline ();
52
53 parse_config ();
54
55 sanity ()         if $conf{sanity};
56 exec_cmd ()       if $conf{execcmd};
57 query_partner ()  if $conf{partner};
58 query_local ()    if $conf{local};
59 query_foreign ()  if $conf{foreign};
60 query_all ()      if $conf{all};
61 query_device ()   if $conf{device};
62 query_journal ()  if $conf{journal};
63 query_raidtab ()  if $conf{raidtab};
64 query_type ()     if $conf{type};
65 query_zpool ()    if $conf{zpool};
66 query_filesys ()  if $conf{fsname};
67
68 exit(0);
69
70 #
71 # Subroutines
72 #
73
74 sub parse_cmdline
75 {
76     my $help = 0;
77     my $host = "";
78
79     $conf{partner} = 0;
80     $conf{all} = 0;
81     $conf{local} = 0;
82     $conf{foreign} = 0;
83     $conf{config} = "/etc/ldev.conf";
84     $conf{nidsfile} = "/etc/nids";
85     $conf{hostname} = `uname -n`; chomp $conf{hostname};
86     $conf{device} = "";
87     $conf{sanity} = 0;
88     $conf{execcmd} = "";
89     $conf{journal} = "";
90     $conf{fsname} = "";
91
92     my $rc = GetOptions (
93         "help|h!"         => \$help,
94         "partner|p!"      => \$conf{partner},
95         "all|a!"          => \$conf{all},
96         "local|l!"        => \$conf{local},
97         "foreign|f!"      => \$conf{foreign},
98         "config|c=s"      => \$conf{config},
99         "nidsfile|n=s"    => \$conf{nidsfile},
100         "hostname|H=s"    => \$conf{hostname},
101         "sanity|s!"       => \$conf{sanity},
102         "device|d=s"      => \$conf{device},
103         "journal|j=s"     => \$conf{journal},
104         "raidtab|r=s"     => \$conf{raidtab},
105         "type|t=s"        => \$conf{type},
106         "zpool|z=s"       => \$conf{zpool},
107         "filesys|F=s"     => \$conf{fsname},
108     );
109
110     usage() if $help || !$rc;
111
112     log_fatal ("cannot read config file\n") if (! -r $conf{config});
113
114     my $filters = 0;
115     $filters++ if ($conf{local});
116     $filters++ if ($conf{foreign});
117     $filters++ if ($conf{all});
118     $filters++ if ($conf{fsname});
119     log_fatal ("Only one of -l, -f, -a, or -F may be used.\n") if ($filters > 1);
120
121     if (@ARGV) {
122         $conf{execcmd} = " " . join " ", @ARGV;
123     }
124
125     parse_nids () if ($conf{execcmd} =~ /(%n|%N|%m)/);
126 }
127
128 sub parse_config
129 {
130     my $line = 0;
131     my %l2f = ();
132     my %label2local = ();
133     my %label2dev = ();
134     my %label2journal = ();
135     my %label2raidtab = ();
136     my %label2type = ();
137     my %label2zpool = ();
138     my %filesys2mgs = ();
139     my %label2hostname = ();
140     my @local_labels = ();
141     my @foreign_labels = ();
142     my @fs_labels = ();
143
144     open (CONF, "< $conf{config}") or log_fatal ("$conf{config}: $!\n");
145
146     while (<CONF>) {
147         my $type;
148         $line++;
149         s/#.*//;
150         s/(\s)*$//;
151         next if (/^(\s)*$/);
152         chomp;
153         my ($local, $foreign, $label, $dev, $j, $raidtab) = split;
154         if ($dev !~ /^\// && $dev =~ /^([^:]+):(.+)$/) {
155             $type = $1;
156             $dev = $2;
157         }
158         eparse_line ($line, "efieldcount") if (!defined $dev);
159         eparse_line ($line, "epairwise") if (exists $l2f{$local}
160                                          && $l2f{$local} ne $foreign);
161         $l2f{$local} = $foreign;
162
163         eparse_line ($line, "elabel_uniq") if (exists $label2dev{$label}
164                                          || exists $label2local{$label});
165         /(\w+)-(OST|MDT|MGT|MGS)([0-9a-fA-F]{4})/, $label;
166         my $filesys = $1;
167         my $nodetype = $2;
168         $label2dev{$label} = $dev;
169         $label2local{$label} = $local;
170         $label2journal{$label} = $j if defined $j;
171         $label2raidtab{$label} = $raidtab if defined $raidtab;
172         if (defined $type) {
173             $label2type{$label} = $type;
174             if ($type eq "zfs" && $dev =~ m{^([^/]+)/[^/]+$}) {
175                 $label2zpool{$label} = $1;
176             }
177         }
178         $label2hostname{$label}=$local;
179         if ($nodetype eq "MGS" or $nodetype eq "MGT") {
180             $filesys2mgs{$filesys} = $label;
181         }
182
183         if ($local eq $conf{hostname}) {
184             push @local_labels, $label;
185         } elsif ($foreign eq $conf{hostname}) {
186             push @foreign_labels, $label;
187         }
188
189         if ($conf{fsname} and $filesys eq $conf{fsname}) {
190             push @fs_labels, $label;
191         }
192     }
193     close CONF;
194
195     foreach (keys %l2f) {
196         my $foreign = $l2f{$_};
197         next if ($foreign eq "-");
198         eparse_str ($_, "epairwise")
199                     unless (!exists $l2f{$foreign} or $l2f{$foreign} eq $_);
200     }
201
202     @{$conf{local_labels}} = @local_labels;
203     @{$conf{foreign_labels}} = @foreign_labels;
204     @{$conf{fs_labels}} = @fs_labels;
205     %{$conf{l2f}} = %l2f;
206     %{$conf{label2dev}} = %label2dev;
207     %{$conf{label2local}} = %label2local;
208     %{$conf{label2journal}} = %label2journal;
209     %{$conf{label2raidtab}} = %label2raidtab;
210     %{$conf{label2type}} = %label2type;
211     %{$conf{label2zpool}} = %label2zpool;
212     %{$conf{filesys2mgs}} = %filesys2mgs;
213     %{$conf{label2hostname}} = %label2hostname;
214 }
215
216 sub parse_nids ()
217 {
218     my $line = 0;
219     my %host2nid = ();
220     my %nid2host = ();
221
222     open (NIDS, "< $conf{nidsfile}") or log_fatal ("$conf{nidsfile}: $!\n");
223
224     while (<NIDS>) {
225         $line++;
226         s/#.*//;
227         next if (/^(\s)*$/);
228         chomp;
229         my ($host, $nid, $morenids) = split (/\s+/, $_, 3);
230         if (!defined $nid) {
231             log_fatal ("$conf{nidsfile} line $line: incomplete line\n");
232         }
233         $host2nid{$host} = $nid;
234         $nid2host{$nid} = $host;
235         map { $nid2host{$_} = $host; } split (/\s+/, $morenids);
236     }
237     close NIDS;
238
239     %{$conf{host2nid}} = %host2nid;
240     %{$conf{nid2host}} = %nid2host;
241 }
242
243 sub query_partner
244 {
245     my %l2f = %{$conf{l2f}};
246     my $hostname = $conf{hostname};
247     if (exists $l2f{$hostname} && $l2f{$hostname} ne "-") {
248         print "$l2f{$hostname}\n";
249     }
250 }
251
252 sub query_local
253 {
254     map { print "$_\n"; } @{$conf{local_labels}};
255 }
256
257 sub query_foreign
258 {
259     map { print "$_\n"; } @{$conf{foreign_labels}};
260 }
261
262 sub query_filesys
263 {
264     map { print "$_\n"; } @{$conf{fs_labels}};
265 }
266
267 sub query_all
268 {
269     query_local ();
270     query_foreign ();
271 }
272
273 sub query_device
274 {
275     my %label2dev = %{$conf{label2dev}};
276
277     if (exists $label2dev{$conf{device}}) {
278         print "$label2dev{$conf{device}}\n";
279     }
280 }
281
282 sub query_raidtab
283 {
284     my %label2raidtab = %{$conf{label2raidtab}};
285
286     if (exists $label2raidtab{$conf{raidtab}}) {
287         print "$label2raidtab{$conf{raidtab}}\n";
288     }
289 }
290
291 sub query_journal
292 {
293     my %label2journal = %{$conf{label2journal}};
294
295     if (exists $label2journal{$conf{journal}} &&
296        $label2journal{$conf{journal}} ne "-") {
297         print "$label2journal{$conf{journal}}\n";
298     }
299 }
300
301 sub query_type
302 {
303     my %label2type = %{$conf{label2type}};
304
305     if (exists $label2type{$conf{type}}) {
306         print "$label2type{$conf{type}}\n";
307     }
308 }
309
310 sub query_zpool
311 {
312     my %label2zpool = %{$conf{label2zpool}};
313
314     if (exists $label2zpool{$conf{zpool}}) {
315         print "$label2zpool{$conf{zpool}}\n";
316     }
317 }
318
319 sub dd_test
320 {
321     my ($dpath) = @_;
322     my $retval = 0;
323     my $bs =      `blockdev --getss   $dpath 2>/dev/null`; chomp $bs;
324     my $max512  = `blockdev --getsize $dpath 2>/dev/null`; chomp $max512;
325     if ($? == 0 && $bs > 0 && $max512 > 0) {
326         my $maxb = ($max512 / $bs) * 512;
327         my $count = 10 * 1024 * 1024 / $bs;  # read first 10mb
328         my $dev = `readlink -f $dpath`; chomp $dev;
329         $count = $maxb if ($count > $maxb);
330         `dd if=$dev of=/dev/null bs=$bs count=$count >/dev/null 2>&1`;
331         $retval = ($? == 0);
332     }
333     return $retval;
334 }
335
336 sub sanity
337 {
338     my $exit_val = 0;
339
340     my @local_labels = @{$conf{local_labels}};
341     my @foreign_labels = @{$conf{foreign_labels}};
342     my %label2dev = %{$conf{label2dev}};
343     my %label2journal = %{$conf{label2journal}};
344
345     foreach (@local_labels, @foreign_labels) {
346         my $lpath = "/dev/disk/by-label/$_";
347         my $dpath = $label2dev{$_};
348         my $jpath = $label2journal{$_};
349         my $label = $_;
350         if (! -e $lpath) {
351             log_error ("$lpath does not exist\n");
352             $exit_val = 1;
353         }
354         if (! -e $dpath) {
355             log_error ("$dpath does not exist\n");
356             $exit_val = 1;
357         } elsif (!dd_test ($dpath)) {
358             log_error ("$dpath failed dd test\n");
359             $exit_val = 1;
360         }
361         if (`readlink -f $lpath` ne `readlink -f $dpath`) {
362             log_error ("$lpath and $dpath point to different things\n");
363             $exit_val = 1;
364         }
365         if ($jpath) {
366             if (! -e $jpath) {
367                 log_error ("$jpath (journal for $label) does not exist\n");
368                 $exit_val = 1;
369             } elsif (!dd_test ($jpath)) {
370                 log_error ("$jpath failed dd test\n");
371                 $exit_val = 1;
372             }
373         }
374     }
375     exit($exit_val);
376 }
377
378 sub par_exec
379 {
380     my @pids = ();
381     my %pid2label = ();
382     my %pid2cmd = ();
383     my $pid;
384     my $result = 0;
385
386     my $tmpfile = `mktemp \${TMPDIR:-/tmp}/ldev.XXXXXXXXXX`; chomp $tmpfile;
387     log_fatal ("failed to create $tmpfile\n") if (! -e $tmpfile);
388
389     foreach (@_) {
390         my ($label, $cmd) = split (/\s+/, $_, 2);
391         my ($basecmd) = split (/\s+/, $cmd);
392         if (($pid = fork)) {       # parent
393             $pid2label{$pid} = $label;
394             $pid2cmd{$pid} = $basecmd;
395         } elsif (defined $pid) {   # child
396             #print STDERR "$label: running $cmd\n";
397             exec "($cmd 2>&1 || rm -f $tmpfile) | sed -e 's/^/$label: /'";
398             print STDERR "$label: exec $basecmd: $!\n"; unlink $tmpfile;
399         } else {                   # error
400             log_fatal ("label: fork: $!\n"); unlink $tmpfile;
401         }
402     }
403     while (($pid = wait) != -1) {
404         #print STDERR "$pid2label{$pid}: completed\n";
405     }
406
407     # sentinel is intact, so there were no errors
408     if (-e $tmpfile) {
409         unlink $tmpfile;
410         $result = 1;
411     }
412
413     return $result;
414 }
415
416 sub exec_cmd
417 {
418     my @labels = ();
419     my @cmds = ();
420     my %label2dev = %{$conf{label2dev}};
421     my %label2journal = %{$conf{label2journal}};
422     my %filesys2mgs = %{$conf{filesys2mgs}};
423     my %label2hostname = %{$conf{label2hostname}};
424     my %l2f = %{$conf{l2f}};
425     my ($nid, $failnid);
426
427     if ($conf{execcmd} =~ /%n/) {
428         my %host2nid = %{$conf{host2nid}};
429         if (!defined $host2nid{$conf{hostname}}) {
430             log_fatal ("%n used but no nid defined for this host\n");
431         }
432         $nid = $host2nid{$conf{hostname}};
433     }
434     if ($conf{execcmd} =~ /%N/) {
435         if (!defined $l2f{$conf{hostname}}) {
436             log_fatal ("%N used but foreign host is undefined\n");
437         }
438         my %host2nid = %{$conf{host2nid}};
439         if (!defined $host2nid{$l2f{$conf{hostname}}}) {
440             log_fatal ("%N used but foreign nid is undefined\n");
441         }
442         $failnid = $host2nid{$l2f{$conf{hostname}}};
443     }
444
445     if ($conf{foreign}) {
446         @labels = @{$conf{foreign_labels}};
447     } elsif ($conf{all}) {
448         @labels = (@{$conf{local_labels}}, @{$conf{foreign_labels}});
449     } elsif ($conf{fsname}) {
450         @labels = (@labels, @{$conf{fs_labels}});
451     } else {
452         @labels = @{$conf{local_labels}};
453     }
454
455     foreach (@labels) {
456         /(\w+)-(OST|MDT|MGT|MGS)([0-9a-fA-F]{4})/;
457
458         my $fsname = $1;
459         my $type = $2; $type =~ tr/A-Z/a-z/;
460         my $hexindex = $3;
461         my $decindex = hex($3);
462         my $label = $_;
463         my $cmd = $conf{execcmd};
464         my $device = $label2dev{$_};
465         if ($conf{execcmd} =~ /%j/ && !defined $label2journal{$_}) {
466             log_fatal ("%j used but no journal defined for $_\n");
467         }
468         my $journal = $label2journal{$_};
469         my $mgsnid;
470         if ($cmd =~ /%m/) {
471             if (!exists $filesys2mgs{$fsname}) {
472                  log_fatal ("$fsname has no MGS defined\n");
473             }
474
475             my $mgs = $filesys2mgs{$fsname};
476             if (!exists $label2hostname{$mgs}) {
477                  log_fatal ("$mgs has no hostname defined\n");
478             }
479
480             my %host2nid = %{$conf{host2nid}};
481             $mgs = $label2hostname{$mgs};
482             if (!exists $host2nid{$mgs}) {
483                  log_fatal ("$mgs has no NID defined\n");
484             }
485             $mgsnid = $host2nid{$mgs};
486         }
487
488         $cmd =~ s/%f/$fsname/g;  # %f = fsname
489         $cmd =~ s/%t/$type/g;    # %t = server type
490         $cmd =~ s/%I/$hexindex/g;# %I = index (hex)
491         $cmd =~ s/%i/$decindex/g;# %i = index (dec)
492         $cmd =~ s/%l/$label/g;   # %l = label
493         $cmd =~ s/%d/$device/g;  # %d = device
494         $cmd =~ s/%j/$journal/g; # %j = journal device
495         $cmd =~ s/%n/$nid/g;     # %n = nid
496         $cmd =~ s/%N/$failnid/g; # %N = failnid
497         $cmd =~ s/%m/$mgsnid/g;  # %m = MGS nid
498
499         push @cmds, "$_ $cmd";
500     }
501
502     par_exec (@cmds) or log_fatal ("parallel command execution failed\n");
503     exit 0;
504 }
505
506 sub usage
507 {
508     print STDERR "$usage";
509     exit 0;
510 }
511
512 sub log_msg     { print STDERR "$prog: ", @_; }
513 sub log_error   { log_msg ("Error: ", @_) }
514 sub log_fatal   { log_msg ("Fatal: ", @_); exit 1; }
515 sub eparse_line { log_fatal ("$conf{config} line $_[0]: $eparse{$_[1]}\n"); }
516 sub eparse_str  { log_fatal ("$conf{config}: $_[0]: $eparse{$_[1]}\n"); }