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