]> git.sur5r.net Git - bacula/bacula/blob - regress/scripts/functions.pm
Tweak regress debug
[bacula/bacula] / regress / scripts / functions.pm
1 ################################################################
2 use strict;
3
4 =head1 LICENSE
5
6    Bacula® - The Network Backup Solution
7
8    Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
9
10    The main author of Bacula is Kern Sibbald, with contributions from
11    many others, a complete list can be found in the file AUTHORS.
12
13    This program is Free Software; you can redistribute it and/or
14    modify it under the terms of version two of the GNU General Public
15    License as published by the Free Software Foundation plus additions
16    that are listed in the file LICENSE.
17
18    This program is distributed in the hope that it will be useful, but
19    WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21    General Public License for more details.
22
23    You should have received a copy of the GNU General Public License
24    along with this program; if not, write to the Free Software
25    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26    02110-1301, USA.
27
28    Bacula® is a registered trademark of Kern Sibbald.
29    The licensor of Bacula is the Free Software Foundation Europe
30    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
31    Switzerland, email:ftf@fsfeurope.org.
32
33 =cut
34
35 package scripts::functions;
36 # Export all functions needed to be used by a simple 
37 # perl -Mscripts::functions -e '' script
38 use Exporter;
39 our @ISA = qw(Exporter);
40
41 our @EXPORT = qw(update_some_files create_many_files check_multiple_copies
42                   update_client $HOST $BASEPORT add_to_backup_list
43                   check_volume_size create_many_dirs cleanup start_bacula
44                   stop_bacula get_resource set_maximum_concurrent_jobs get_time
45                   add_attribute check_prune_list check_min_volume_size
46                   check_max_volume_size $estat $bstat $rstat $zstat $cwd $bin
47                   $scripts $conf $rscripts $tmp $working $dstat extract_resource
48                   $db_name $db_user $db_password $src $tmpsrc
49                   remote_init remote_config remote_stop remote_diff );
50
51
52 use File::Copy qw/copy/;
53
54 our ($cwd, $bin, $scripts, $conf, $rscripts, $tmp, $working, $estat, $dstat,
55      $bstat, $zstat, $rstat, $debug,
56      $REMOTE_CLIENT, $REMOTE_ADDR, $REMOTE_FILE, $REMOTE_PORT, $REMOTE_PASSWORD,
57      $REMOTE_STORE_ADDR, $REGRESS_DEBUG,
58      $db_name, $db_user, $db_password, $src, $tmpsrc, $HOST, $BASEPORT);
59
60 END {
61     if ($estat || $rstat || $zstat || $bstat || $dstat) {
62         exit 1;
63     }
64 }
65
66 BEGIN {
67     # start by loading the ./config file
68     my ($envar, $enval);
69     if (! -f "./config") {
70         die "Could not find ./config file\n";
71     }
72     # load the ./config file in a subshell doesn't allow to use "env" to display all variable
73     open(IN, ". ./config; set |") or die "Could not run shell: $!\n";
74     while ( my $l = <IN> ) {
75         chomp ($l);
76         if ($l =~ /^([\w\d]+)='?([^']+)'?/) {
77             next if ($1 eq 'SHELLOPTS'); # is in read-only
78             ($envar,$enval) = ($1, $2);
79             $ENV{$envar} = $enval;
80         }
81     }
82     close(IN);
83     $cwd = `pwd`; 
84     chomp($cwd);
85
86     # set internal variable name and update environment variable
87     $ENV{db_name}     = $db_name     = $ENV{db_name}     || 'regress';
88     $ENV{db_user}     = $db_user     = $ENV{db_user}     || 'regress';
89     $ENV{db_password} = $db_password = $ENV{db_password} || '';
90
91     $ENV{bin}      = $bin      =  $ENV{bin}      || "$cwd/bin";
92     $ENV{tmp}      = $tmp      =  $ENV{tmp}      || "$cwd/tmp";
93     $ENV{src}      = $src      =  $ENV{src}      || "$cwd/src";
94     $ENV{conf}     = $conf     =  $ENV{conf}     || $bin;
95     $ENV{scripts}  = $scripts  =  $ENV{scripts}  || $bin;
96     $ENV{tmpsrc}   = $tmpsrc   =  $ENV{tmpsrc}   || "$cwd/tmp/build";
97     $ENV{working}  = $working  =  $ENV{working}  || "$cwd/working";    
98     $ENV{rscripts} = $rscripts =  $ENV{rscripts} || "$cwd/scripts";
99     $ENV{HOST}     = $HOST     =  $ENV{HOST}     || "localhost";
100     $ENV{BASEPORT} = $BASEPORT =  $ENV{BASEPORT} || "8101";
101     $ENV{REGRESS_DEBUG} = $debug         = $ENV{REGRESS_DEBUG} || 0;
102     $ENV{REMOTE_CLIENT} = $REMOTE_CLIENT = $ENV{REMOTE_CLIENT} || 'remote-fd';
103     $ENV{REMOTE_ADDR}   = $REMOTE_ADDR   = $ENV{REMOTE_ADDR}   || undef;
104     $ENV{REMOTE_FILE}   = $REMOTE_FILE   = $ENV{REMOTE_FILE}   || "/tmp";
105     $ENV{REMOTE_PORT}   = $REMOTE_PORT   = $ENV{REMOTE_PORT}   || 9102;
106     $ENV{REMOTE_PASSWORD} = $REMOTE_PASSWORD = $ENV{REMOTE_PASSWORD} || "xxx";
107     $ENV{REMOTE_STORE_ADDR}=$REMOTE_STORE_ADDR=$ENV{REMOTE_STORE_ADDR} || undef;
108     
109     $estat = $rstat = $bstat = $zstat = 0;
110 }
111
112 sub cleanup
113 {
114     system("$rscripts/cleanup");
115     return $? == 0;
116 }
117
118 sub start_bacula
119 {
120     my $ret;
121     $ENV{LANG}='C';
122     system("$bin/bacula start");
123     $ret = $? == 0;
124     open(FP, ">$tmp/bcmd");
125     print FP "sql\ntruncate client_group;\ntruncate client_group_member;\nupdate Media set LocationId=0;\ntruncate location;\n\n";
126     close(FP);
127     system("cat $tmp/bcmd | $bin/bconsole >/dev/null");
128     return $ret;
129 }
130
131 sub stop_bacula
132 {
133     $ENV{LANG}='C';
134     system("$bin/bacula stop");
135     return $? == 0;
136 }
137
138 sub get_resource
139 {
140     my ($file, $type, $name) = @_;
141     my $ret;
142     open(FP, $file) or die "Can't open $file";
143     my $content = join("", <FP>);
144     
145     if ($content =~ m/(^$type {[^}]+?Name\s*=\s*"?$name"?[^}]+?^})/ms) {
146         $ret = $1;
147     }
148
149     close(FP);
150     return $ret;
151 }
152
153 sub extract_resource
154 {
155     my $ret = get_resource(@_);
156     if ($ret) {
157         print $ret, "\n";
158     }
159 }
160
161 sub check_min_volume_size
162 {
163     my ($size, @vol) = @_;
164     my $ret=0;
165
166     foreach my $v (@vol) {
167         if (! -f "$tmp/$v") {
168             print "ERR: $tmp/$v not accessible\n";
169             $ret++;
170             next;
171         }
172         if (-s "$tmp/$v" < $size) {
173             print "ERR: $tmp/$v too small\n";
174             $ret++;
175         }
176     }
177     $estat+=$ret;
178     return $ret;
179 }
180
181 sub check_max_volume_size
182 {
183     my ($size, @vol) = @_;
184     my $ret=0;
185
186     foreach my $v (@vol) {
187         if (! -f "$tmp/$v") {
188             print "ERR: $tmp/$v not accessible\n";
189             $ret++;
190             next;
191         }
192         if (-s "$tmp/$v" > $size) {
193             print "ERR: $tmp/$v too big\n";
194             $ret++;
195         }
196     }
197     $estat+=$ret;
198     return $ret;
199 }
200
201 sub add_to_backup_list
202 {
203     open(FP, ">>$tmp/file-list") or die "Can't open $tmp/file-list for update $!";
204     print FP join("\n", @_);
205     close(FP);
206 }
207
208 # update client definition for the current test
209 # it permits to test remote client
210 sub update_client
211 {
212     my ($new_passwd, $new_address, $new_port) = @_;
213     my $in_client=0;
214
215     open(FP, "$conf/bacula-dir.conf") or die "can't open source $!";
216     open(NEW, ">$tmp/bacula-dir.conf.$$") or die "can't open dest $!";
217     while (my $l = <FP>) {
218         if (!$in_client && $l =~ /^Client {/) {
219             $in_client=1;
220         }
221         
222         if ($in_client && $l =~ /Address/i) {
223             $l = "Address = $new_address\n";
224         }
225
226         if ($in_client && $l =~ /FDPort/i) {
227             $l = "FDPort = $new_port\n";
228         }
229
230         if ($in_client && $l =~ /Password/i) {
231             $l = "Password = \"$new_passwd\"\n";
232         }
233
234         if ($in_client && $l =~ /^}/) {
235             $in_client=0;
236         }
237         print NEW $l;
238     }
239     close(FP);
240     close(NEW);
241     my $ret = copy("$tmp/bacula-dir.conf.$$", "$conf/bacula-dir.conf");
242     unlink("$tmp/bacula-dir.conf.$$");
243     return $ret;
244 }
245
246 # open a directory and update all files
247 sub update_some_files
248 {
249     my ($dest)=@_;
250     my $t=rand();
251     my $f;
252     my $nb=0;
253     print "Update files in $dest\n";
254     opendir(DIR, $dest) || die "$!";
255     map {
256         $f = "$dest/$_";
257         if (-f $f) {
258             open(FP, ">$f") or die "$f $!";
259             print FP "$t update $f\n";
260             close(FP);
261             $nb++;
262         }
263     } readdir(DIR);
264     closedir DIR;
265     print "$nb files updated\n";
266 }
267
268 # create big number of files in a given directory
269 # Inputs: dest  destination directory
270 #         nb    number of file to create
271 # Example:
272 # perl -Mscripts::functions -e 'create_many_files("$cwd/files", 100000)'
273 sub create_many_files
274 {
275     my ($dest, $nb) = @_;
276     my $base;
277     my $dir=$dest;
278     $nb = $nb || 750000;
279     mkdir $dest;
280     $base = chr($nb % 26 + 65); # We use a base directory A-Z
281
282     # already done
283     if (-f "$dest/$base/a${base}a${nb}aaa${base}") {
284         print "Files already created\n";
285         return;
286     }
287
288     # auto flush stdout for dots
289     $| = 1;
290     print "Create $nb files into $dest\n";
291     for(my $i=0; $i < 26; $i++) {
292         $base = chr($i + 65);
293         mkdir("$dest/$base") if (! -d "$dest/$base");
294     }
295     for(my $i=0; $i<=$nb; $i++) {
296         $base = chr($i % 26 + 65);
297         open(FP, ">$dest/$base/a${base}a${i}aaa$base") or die "$dest/$base $!";
298         print FP "$i\n";
299         close(FP);
300         
301         open(FP, ">$dir/b${base}a${i}csq$base") or die "$dir $!";
302         print FP "$base $i\n";
303         close(FP);
304         
305         if (!($i % 100)) {
306             $dir = "$dest/$base/$base$i$base";
307             mkdir $dir;
308         }
309         print "." if (!($i % 10000));
310     }
311     print "\n";
312 }
313
314 # create big number of dirs in a given directory
315 # Inputs: dest  destination directory
316 #         nb    number of dirs to create
317 # Example:
318 # perl -Mscripts::functions -e 'create_many_dirs("$cwd/files", 100000)'
319 sub create_many_dirs
320 {
321     my ($dest, $nb) = @_;
322     my ($base, $base2);
323     my $dir=$dest;
324     $nb = $nb || 750000;
325     mkdir $dest;
326     $base = chr($nb % 26 + 65); # We use a base directory A-Z
327     $base2 = chr(($nb+10) % 26 + 65);
328     # already done
329     if (-d "$dest/$base/$base2/$base/a${base}a${nb}aaa${base}") {
330         print "Files already created\n";
331         return;
332     }
333
334     # auto flush stdout for dots
335     $| = 1;
336     print "Create $nb dirs into $dest\n";
337     for(my $i=0; $i < 26; $i++) {
338         $base = chr($i + 65);
339         $base2 = chr(($i+10) % 26 + 65);
340         mkdir("$dest/$base");
341         mkdir("$dest/$base/$base2");
342         mkdir("$dest/$base/$base2/$base$base2");
343         mkdir("$dest/$base/$base2/$base$base2/$base$base2");
344         mkdir("$dest/$base/$base2/$base$base2/$base$base2/$base2$base");
345     }
346     for(my $i=0; $i<=$nb; $i++) {
347         $base = chr($i % 26 + 65);
348         $base2 = chr(($i+10) % 26 + 65);
349         mkdir("$dest/$base/$base2/$base$base2/$base$base2/$base2$base/a${base}a${i}aaa$base");  
350         print "." if (!($i % 10000));
351     }
352     print "\n";
353 }
354
355 sub check_encoding
356 {
357     if (grep {/Wanted SQL_ASCII, got UTF8/} 
358         `${bin}/bacula-dir -d50 -t -c ${conf}/bacula-dir.conf 2>&1`)
359     {
360         print "Found database encoding problem, please modify the ",
361               "database encoding (SQL_ASCII)\n";
362         exit 1;
363     }
364 }
365
366 # You can change the maximum concurrent jobs for any config file
367 # If specified, you can change only one Resource or one type of
368 # resource at the time (optional)
369 #  set_maximum_concurrent_jobs('$conf/bacula-dir.conf', 100);
370 #  set_maximum_concurrent_jobs('$conf/bacula-dir.conf', 100, 'Director');
371 #  set_maximum_concurrent_jobs('$conf/bacula-dir.conf', 100, 'Device', 'Drive-0');
372 sub set_maximum_concurrent_jobs
373 {
374     my ($file, $nb, $obj, $name) = @_;
375
376     die "Can't get new maximumconcurrentjobs" 
377         unless ($nb);
378
379     add_attribute($file, "Maximum Concurrent Jobs", $nb, $obj, $name);
380 }
381
382 # You can add option to a resource
383 #  add_attribute('$conf/bacula-dir.conf', 'FDTimeout', 1600, 'Director');
384 #  add_attribute('$conf/bacula-dir.conf', 'FDTimeout', 1600, 'Storage', 'FileStorage');
385 sub add_attribute
386 {
387     my ($file, $attr, $value, $obj, $name) = @_;
388     my ($cur_obj, $cur_name, $done);
389
390     open(FP, ">$tmp/1.$$") or die "Can't write to $tmp/1.$$";
391     open(SRC, $file) or die "Can't open $file";
392     while (my $l = <SRC>)
393     {
394         if ($l =~ /^#/) {
395             print FP $l;
396             next;
397         }
398
399         if ($l =~ /^(\w+) {/) {
400             $cur_obj = $1;
401             $done=0;
402         }
403
404         if ($l =~ /^\s*\Q$attr\E/i) {
405             if (!$obj || $cur_obj eq $obj) {
406                 if (!$name || $cur_name eq $name) {
407                     $l =~ s/\Q$attr\E\s*=\s*.+/$attr = $value/ig;
408                     $done=1
409                 }
410             }
411         }
412
413         if ($l =~ /^\s*Name\s*=\s*"?([\w\d\.-]+)"?/i) {
414             $cur_name = $1;
415         }
416
417         if ($l =~ /^}/) {
418             if (!$done) {
419                 if ($cur_obj eq $obj) {
420                     if (!$name || $cur_name eq $name) {
421                         $l = "  $attr = $value\n$l";
422                     }
423                 }
424             }
425             $cur_name = $cur_obj = undef;
426         }
427         print FP $l;
428     }
429     close(SRC);
430     close(FP);
431     copy("$tmp/1.$$", $file) or die "Can't copy $tmp/1.$$ to $file";
432 }
433
434 # This test the list jobs output to check differences
435 # Input: read file argument
436 #        check if all jobids in argument are present in the first
437 #        'list jobs' and not present in the second
438 # Output: exit(1) if something goes wrong and print error
439 sub check_prune_list
440 {
441     my $f = shift;
442     my %to_check = map { $_ => 1} @_;
443     my %seen;
444     my $in_list_jobs=0;
445     my $nb_list_job=0;
446     my $nb = scalar(@_);
447     open(FP, $f) or die "Can't open $f $!";
448     while (my $l = <FP>)          # read all files to check
449     {
450         if ($l =~ /list jobs/) {
451             $in_list_jobs=1;
452             $nb_list_job++;
453             
454             if ($nb_list_job == 2) {
455                 foreach my $jobid (keys %to_check) {
456                     if (!$seen{$jobid}) {
457                         print "ERROR: in $f, can't find $jobid in first 'list jobs'\n";
458                         exit 1;
459                     }
460                 }
461             }
462             next;
463         }
464         if ($nb_list_job == 0) {
465             next;
466         }
467         if ($l =~ /Pruned (\d+) Job for client/) {
468             if ($1 != $nb) {
469                 print "ERROR: in $f, Prune command returns $1 jobs, want $nb\n";
470                 exit 1;
471             }
472         }
473
474         if ($l =~ /No Jobs found to prune/) {
475            if ($nb != 0) {
476                 print "ERROR: in $f, Prune command returns 0 job, want $nb\n";
477                 exit 1;
478             }            
479         }
480
481         # list jobs ouput:
482         # | 1 | NightlySave | 2010-06-16 22:43:05 | B | F | 27 | 4173577 | T |
483         if ($l =~ /^\|\s+(\d+)/) {
484             if ($nb_list_job == 1) {
485                 $seen{$1}=1;
486             } else {
487                 delete $seen{$1};
488             }
489         }
490     }
491     close(FP);
492     foreach my $jobid (keys %to_check) {
493         if (!$seen{$jobid}) {
494             print "******* listing of $f *********\n"
495             system("cat $f");
496             print "******* end listing of $f *********\n"
497             print "ERROR: in $f, $jobid is still present in the 2nd 'list jobs'\n";
498             exit 1;
499         }
500     }
501     if ($nb_list_job != 2) {
502         print "ERROR: in $f, not enough 'list jobs'\n";
503         exit 1;
504     }
505     exit 0;
506 }
507
508 # This test ensure that 'list copies' displays only each copy one time
509 #
510 # Input: read stream from stdin or with file list argument
511 #        check the number of copies with the ARGV[1]
512 # Output: exit(1) if something goes wrong and print error
513 sub check_multiple_copies
514 {
515     my ($nb_to_found) = @_;
516
517     my $in_list_copies=0;       # are we or not in a list copies block
518     my $nb_found=0;             # count the number of copies found
519     my $ret = 0;
520     my %seen;
521
522     while (my $l = <>)          # read all files to check
523     {
524         if ($l =~ /list copies/) {
525             $in_list_copies=1;
526             %seen = ();
527             next;
528         }
529
530         # not in a list copies anymore
531         if ($in_list_copies && $l =~ /^ /) {
532             $in_list_copies=0;
533             next;
534         }
535
536         # list copies ouput:
537         # |     3 | Backup.2009-09-28 |  9 | DiskChangerMedia |
538         if ($in_list_copies && $l =~ /^\|\s+\d+/) {
539             my (undef, $jobid, undef, $copyid, undef) = split(/\s*\|\s*/, $l);
540             if (exists $seen{$jobid}) {
541                 print "ERROR: $jobid/$copyid already known as $seen{$jobid}\n";
542                 $ret = 1;
543             } else {
544                 $seen{$jobid}=$copyid;
545                 $nb_found++;
546             }
547         }
548     }
549     
550     # test the number of copies against the given arg
551     if ($nb_to_found && ($nb_to_found != $nb_found)) {
552         print "ERROR: Found wrong number of copies ",
553               "($nb_to_found != $nb_found)\n";
554         exit 1;
555     }
556
557     exit $ret;
558 }
559
560 use POSIX qw/strftime/;
561 sub get_time
562 {
563     my ($sec) = @_;
564     print strftime('%F %T', localtime(time+$sec)), "\n";
565 }
566
567 sub debug
568 {
569     if ($debug) {
570         print join("\n", @_), "\n";
571     }
572 }
573
574 sub remote_config
575 {
576     open(FP, ">$REMOTE_FILE/bacula-fd.conf") or 
577         die "ERROR: Can't open $REMOTE_FILE/bacula-fd.conf $!";
578     print FP "
579 Director {
580   Name = $HOST-dir
581   Password = \"$REMOTE_PASSWORD\"
582 }
583 FileDaemon {
584   Name = remote-fd
585   FDport = $REMOTE_PORT
586   WorkingDirectory = $REMOTE_FILE/working
587   Pid Directory = $REMOTE_FILE/working
588   Maximum Concurrent Jobs = 20
589 }
590 Messages {
591   Name = Standard
592   director = $HOST-dir = all, !skipped, !restored
593 }
594 ";  
595     close(FP);
596     system("mkdir -p '$REMOTE_FILE/working' '$REMOTE_FILE/save'");
597     system("rm -rf '$REMOTE_FILE/restore'");
598     my $pid = fork();
599     if (!$pid) {
600         close(STDIN);  open(STDIN, "/dev/null");
601         close(STDOUT); open(STDOUT, ">/dev/null");
602         close(STDERR); open(STDERR, ">/dev/null");        
603         exec("/opt/bacula/bin/bacula-fd -c $REMOTE_FILE/bacula-fd.conf");
604         exit 1;
605     }
606     sleep(2);
607     $pid = `cat $REMOTE_FILE/working/bacula-fd.$REMOTE_PORT.pid`;
608     chomp($pid);
609
610     # create files and tweak rights
611     create_many_files("$REMOTE_FILE/save", 5000);
612     chdir("$REMOTE_FILE/save");
613     my $d = 'A';
614     my $r = 0700;
615     for my $g ( split(' ', $( )) {
616         chmod $r++, $d;
617         chown $<, $g, $d++;
618     }
619
620     # create a simple script to execute
621     open(FP, ">test.sh") or die "Can't open test.sh $!";
622     print FP "#!/bin/sh\n";
623     print FP "echo this is a script";
624     close(FP);
625     chmod 0755, "test.sh";
626
627     # create a hardlink
628     link("test.sh", "link-test.sh");
629
630     # create long filename
631     mkdir("b" x 255) or print "can't create long dir $!\n";
632     copy("test.sh", ("b" x 255) . '/' . ("a" x 255)) or print "can't create long dir $!\n";
633
634     # play with some symlinks
635     symlink("test.sh", "sym-test.sh");
636     symlink("$REMOTE_FILE/save/test.sh", "sym-abs-test.sh");
637
638     if ($pid) {
639         system("ps $pid");
640         $estat = ($? != 0);
641     } else {
642         $estat = 1;
643     }
644 }
645
646 sub remote_diff
647 {
648     debug("Doing diff between save and restore");
649     system("ssh $REMOTE_ADDR " . 
650      "$REMOTE_FILE/scripts/diff.pl -s $REMOTE_FILE/save -d $REMOTE_FILE/restore/$REMOTE_FILE/save");
651     $dstat = ($? != 0);
652 }
653
654 sub remote_stop
655 {
656     debug("Kill remote bacula-fd");
657     system("ssh $REMOTE_ADDR " . 
658              "'test -f $REMOTE_FILE/working/bacula-fd.$REMOTE_PORT.pid && " . 
659               "kill `cat $REMOTE_FILE/working/bacula-fd.$REMOTE_PORT.pid`'");
660 }
661
662 sub remote_init
663 {
664     system("ssh $REMOTE_ADDR mkdir -p '$REMOTE_FILE/scripts/'");
665     system("scp -q scripts/functions.pm scripts/diff.pl $REMOTE_ADDR:$REMOTE_FILE/scripts/");
666     system("scp -q config $REMOTE_ADDR:$REMOTE_FILE/");
667     debug("INFO: Configuring remote client");
668     system("ssh $REMOTE_ADDR 'cd $REMOTE_FILE && perl -Mscripts::functions -e remote_config'");
669 }
670 1;