1 ################################################################
6 Bacula® - The Network Backup Solution
8 Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
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.
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.
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.
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
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.
35 package scripts::functions;
37 # Export all functions needed to be used by a simple
38 # perl -Mscripts::functions -e '' script
40 our @ISA = qw(Exporter);
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 );
53 use File::Copy qw/copy/;
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);
62 if ($estat || $rstat || $zstat || $bstat || $dstat) {
68 # start by loading the ./config file
70 if (! -f "./config") {
71 die "Could not find ./config file\n";
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> ) {
77 if ($l =~ /^([\w\d]+)=(.+)/) {
78 next if ($1 eq 'SHELLOPTS'); # is in read-only
79 ($envar,$enval) = ($1, $2);
80 $ENV{$envar} = $enval;
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} || '';
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;
110 $estat = $rstat = $bstat = $zstat = 0;
115 system("$rscripts/cleanup");
123 system("$bin/bacula start");
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";
128 system("cat $tmp/bcmd | $bin/bconsole >/dev/null");
135 system("$bin/bacula stop");
141 my ($file, $type, $name) = @_;
143 open(FP, $file) or die "Can't open $file";
144 my $content = join("", <FP>);
146 if ($content =~ m/(^$type {[^}]+?Name\s*=\s*"?$name"?[^}]+?^})/ms) {
156 my $ret = get_resource(@_);
162 sub check_min_volume_size
164 my ($size, @vol) = @_;
167 foreach my $v (@vol) {
168 if (! -f "$tmp/$v") {
169 print "ERR: $tmp/$v not accessible\n";
173 if (-s "$tmp/$v" < $size) {
174 print "ERR: $tmp/$v too small\n";
182 sub check_max_volume_size
184 my ($size, @vol) = @_;
187 foreach my $v (@vol) {
188 if (! -f "$tmp/$v") {
189 print "ERR: $tmp/$v not accessible\n";
193 if (-s "$tmp/$v" > $size) {
194 print "ERR: $tmp/$v too big\n";
202 sub add_to_backup_list
204 open(FP, ">>$tmp/file-list") or die "Can't open $tmp/file-list for update $!";
205 print FP join("\n", @_);
209 # update client definition for the current test
210 # it permits to test remote client
213 my ($new_passwd, $new_address, $new_port) = @_;
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 {/) {
223 if ($in_client && $l =~ /Address/i) {
224 $l = "Address = $new_address\n";
227 if ($in_client && $l =~ /FDPort/i) {
228 $l = "FDPort = $new_port\n";
231 if ($in_client && $l =~ /Password/i) {
232 $l = "Password = \"$new_passwd\"\n";
235 if ($in_client && $l =~ /^}/) {
242 my $ret = copy("$tmp/bacula-dir.conf.$$", "$conf/bacula-dir.conf");
243 unlink("$tmp/bacula-dir.conf.$$");
247 # open a directory and update all files
248 sub update_some_files
254 print "Update files in $dest\n";
255 opendir(DIR, $dest) || die "$!";
259 open(FP, ">$f") or die "$f $!";
260 print FP "$t update $f\n";
266 print "$nb files updated\n";
269 # create big number of files in a given directory
270 # Inputs: dest destination directory
271 # nb number of file to create
273 # perl -Mscripts::functions -e 'create_many_files("$cwd/files", 100000)'
274 sub create_many_files
276 my ($dest, $nb) = @_;
281 $base = chr($nb % 26 + 65); # We use a base directory A-Z
284 if (-f "$dest/$base/a${base}a${nb}aaa${base}") {
285 print "Files already created\n";
289 # auto flush stdout for dots
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");
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 $!";
302 open(FP, ">$dir/b${base}a${i}csq$base") or die "$dir $!";
303 print FP "$base $i\n";
307 $dir = "$dest/$base/$base$i$base";
310 print "." if (!($i % 10000));
315 # create big number of dirs in a given directory
316 # Inputs: dest destination directory
317 # nb number of dirs to create
319 # perl -Mscripts::functions -e 'create_many_dirs("$cwd/files", 100000)'
322 my ($dest, $nb) = @_;
327 $base = chr($nb % 26 + 65); # We use a base directory A-Z
328 $base2 = chr(($nb+10) % 26 + 65);
330 if (-d "$dest/$base/$base2/$base/a${base}a${nb}aaa${base}") {
331 print "Files already created\n";
335 # auto flush stdout for dots
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");
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));
358 if (grep {/Wanted SQL_ASCII, got UTF8/}
359 `${bin}/bacula-dir -d50 -t -c ${conf}/bacula-dir.conf 2>&1`)
361 print "Found database encoding problem, please modify the ",
362 "database encoding (SQL_ASCII)\n";
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
375 my ($file, $nb, $obj, $name) = @_;
377 die "Can't get new maximumconcurrentjobs"
380 add_attribute($file, "Maximum Concurrent Jobs", $nb, $obj, $name);
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');
388 my ($file, $attr, $value, $obj, $name) = @_;
389 my ($cur_obj, $cur_name, $done);
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>)
400 if ($l =~ /^(\w+) {/) {
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;
414 if ($l =~ /^\s*Name\s*=\s*"?([\w\d\.-]+)"?/i) {
420 if ($cur_obj eq $obj) {
421 if (!$name || $cur_name eq $name) {
422 $l = " $attr = $value\n$l";
426 $cur_name = $cur_obj = undef;
432 copy("$tmp/1.$$", $file) or die "Can't copy $tmp/1.$$ to $file";
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
443 my %to_check = map { $_ => 1} @_;
448 open(FP, $f) or die "Can't open $f $!";
449 while (my $l = <FP>) # read all files to check
451 if ($l =~ /list jobs/) {
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";
465 if ($nb_list_job == 0) {
468 if ($l =~ /Pruned (\d+) Job for client/) {
470 print "ERROR: in $f, Prune command returns $1 jobs, want $nb\n";
475 if ($l =~ /No Jobs found to prune/) {
477 print "ERROR: in $f, Prune command returns 0 job, want $nb\n";
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) {
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";
499 if ($nb_list_job != 2) {
500 print "ERROR: in $f, not enough 'list jobs'\n";
506 # This test ensure that 'list copies' displays only each copy one time
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
513 my ($nb_to_found) = @_;
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
520 while (my $l = <>) # read all files to check
522 if ($l =~ /list copies/) {
528 # not in a list copies anymore
529 if ($in_list_copies && $l =~ /^ /) {
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";
542 $seen{$jobid}=$copyid;
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";
558 use POSIX qw/strftime/;
562 print strftime('%F %T', localtime(time+$sec)), "\n";
568 print join("\n", @_), "\n";
574 open(FP, ">$REMOTE_FILE/bacula-fd.conf") or
575 die "ERROR: Can't open $REMOTE_FILE/bacula-fd.conf $?";
579 Password = \"$REMOTE_PASSWORD\"
583 FDport = $REMOTE_PORT
584 WorkingDirectory = $REMOTE_FILE/working
585 Pid Directory = $REMOTE_FILE/working
586 Maximum Concurrent Jobs = 20
590 director = $HOST-dir = all, !skipped, !restored
594 system("mkdir -p '$REMOTE_FILE/working' '$REMOTE_FILE/save'");
595 system("rm -rf '$REMOTE_FILE/restore'");
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");
605 $pid = `cat $REMOTE_FILE/working/bacula-fd.$REMOTE_PORT.pid`;
608 # create files and tweak rights
609 create_many_files("$REMOTE_FILE/save", 5000);
610 chdir("$REMOTE_FILE/save");
613 for my $g ( split(' ', $( )) {
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";
623 chmod 0755, "test.sh";
626 link("test.sh", "link-test.sh");
628 # play with some symlinks
629 symlink("test.sh", "sym-test.sh");
630 symlink("$REMOTE_FILE/save/test.sh", "sym-abs-test.sh");
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");
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`'");
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'");