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