Imported Upstream version 3.1.0
[debian/amanda] / installcheck / Amanda_Recovery_Planner.pl
1 # Copyright (c) 2010 Zmanda Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10 # for more details.
11 #
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
15 #
16 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
18
19 use Test::More tests => 11;
20 use File::Path;
21 use Data::Dumper;
22 use strict;
23 use warnings;
24
25 use lib "@amperldir@";
26 use Installcheck::Run;
27 use Amanda::Config qw( :init :getconf config_dir_relative );
28 use Amanda::Changer;
29 use Amanda::Debug;
30 use Amanda::DB::Catalog;
31 use Amanda::Recovery::Planner;
32 use Amanda::MainLoop;
33 use Amanda::Header;
34 use Amanda::Xfer qw( :constants );
35
36 # and disable Debug's die() and warn() overrides
37 Amanda::Debug::disable_die_override();
38
39 # put the debug messages somewhere
40 Amanda::Debug::dbopen("installcheck");
41 Installcheck::log_test_output();
42
43 my $testconf;
44 $testconf = Installcheck::Run->setup();
45 $testconf->write();
46
47 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
48 if ($cfg_result != $CFGERR_OK) {
49     my ($level, @errors) = Amanda::Config::config_errors();
50     die(join "\n", @errors);
51 }
52
53 ##
54 # Fill in some fake logfiles
55
56 my $logdir = config_dir_relative(getconf($CNF_LOGDIR));
57 my $tapelist_fn = config_dir_relative(getconf($CNF_TAPELIST));
58 my $holdingdir = "$Installcheck::TMP/holding";
59 my %holding_filenames;
60 my $write_timestamp;
61 my $output;
62
63 sub make_holding_file {
64     my ($name, $dump) = @_;
65
66     my $dir = "$holdingdir/$dump->{dump_timestamp}";
67     my $safe_disk = $dump->{'diskname'};
68     $safe_disk =~ tr{/}{_};
69     my $filename = "$dir/$dump->{hostname}.$safe_disk";
70     mkpath($dir);
71
72     # save the filename for later
73     $holding_filenames{$name} = $filename;
74
75     # (note that multi-chunk holding files are not used at this point)
76     my $hdr = Amanda::Header->new();
77     $hdr->{'type'} = $Amanda::Header::F_DUMPFILE;
78     $hdr->{'datestamp'} = $dump->{'dump_timestamp'};
79     $hdr->{'dumplevel'} = $dump->{'level'};
80     $hdr->{'name'} = $dump->{'hostname'};
81     $hdr->{'disk'} = $dump->{'diskname'};
82     $hdr->{'program'} = "INSTALLCHECK";
83     $hdr->{'is_partial'} = ($dump->{'status'} ne 'OK');
84
85     open(my $fh, ">", $filename) or die("opening '$filename': $!");
86     print $fh $hdr->to_string(32768,32768);
87
88     # transfer some data to that file
89     my $xfer = Amanda::Xfer->new([
90         Amanda::Xfer::Source::Pattern->new(1024*$dump->{'kb'}, "+-+-+-+-"),
91         Amanda::Xfer::Dest::Fd->new($fh),
92     ]);
93
94     $xfer->start(sub {
95         my ($src, $msg, $xfer) = @_;
96         if ($msg->{type} == $XMSG_ERROR) {
97             die $msg->{elt} . " failed: " . $msg->{message};
98         } elsif ($msg->{'type'} == $XMSG_DONE) {
99             $src->remove();
100             Amanda::MainLoop::quit();
101         }
102     });
103     Amanda::MainLoop::run();
104     close($fh);
105
106     return $filename;
107 }
108
109 open (my $tapelist, ">", $tapelist_fn);
110 while (<DATA>) {
111     # skip comments
112     next if (/^#/ or /^\S*$/);
113
114     # add to tapelist
115     if (/^:tapelist (\d+) (\S+)\s*$/) {
116         print $tapelist "$1 $2 reuse\n";
117         next;
118     }
119
120     # new logfile
121     if (/^::: (.*)/) {
122         open $output, ">", "$logdir/$1" or die("Could not open $1 for writing: $!");
123         next;
124     }
125
126     # new holding-disk file
127     if (/^:holding (\S+) (\S+) (\S+) (\S+) (\d+) (\S+) (\d+)/) {
128         my $dump = {
129             'dump_timestamp' => $2,     'hostname' => $3,           'diskname' => $4,
130             'level' => $5+0,            'status' => $6,             'kb' => $7,
131         };
132         make_holding_file($1, $dump);
133         next;
134     }
135
136
137     die("syntax error") if (/^:/);
138
139     print $output $_;
140 }
141 close($output);
142 close($tapelist);
143
144 ##
145 ## Tests!
146 ###
147
148 sub make_plan_sync {
149     my $plan;
150
151     Amanda::Recovery::Planner::make_plan(@_,
152         debug => 1,
153         plan_cb => sub {
154             (my $err, $plan) = @_;
155             die "$err" if $err;
156             Amanda::MainLoop::quit();
157         });
158
159     Amanda::MainLoop::run();
160     return $plan;
161 }
162
163 sub ds {
164     return Amanda::Cmdline::dumpspec_t->new($_[0], $_[1], $_[2], $_[3]);
165 }
166
167 sub is_plan {
168     my ($got, $exp, $msg) = @_;
169     my $got_dumps = $got->{'dumps'};
170
171     # make an "abbreviated" version of the plan for comparison with the
172     # expected
173     my @got_abbrev;
174     for my $d (@$got_dumps) {
175         my @parts;
176         push @got_abbrev, [
177             $d->{'hostname'},
178             $d->{'diskname'},
179             $d->{'dump_timestamp'},
180             "$d->{'level'}"+0, # strip bigints
181             \@parts ];
182
183         for my $p (@{$d->{'parts'}}) {
184             next unless defined $p;
185             if (exists $p->{'holding_file'}) {
186                 # extract the last two filename components, since the rest is variable
187                 my $hf = $p->{'holding_file'};
188                 $hf =~ s/^.*\/([^\/]*\/[^\/]*)$/$1/;
189                 push @parts, $hf;
190             } else {
191                 push @parts,
192                     $p->{'label'},
193                     "$p->{filenum}"+0; # strip bigints
194             }
195         }
196     }
197
198     is_deeply(\@got_abbrev, $exp, $msg)
199         or diag("got:\n" . Dumper(\@got_abbrev));
200 }
201
202 my $changer = undef; # not needed yet
203
204 is_plan(make_plan_sync(
205             dumpspec => ds("no-box-at-all"),
206             changer => $changer),
207     [ ],
208     "empty plan for nonexistent host");
209
210 is_plan(make_plan_sync(
211             dumpspec => ds("oldbox", "^/opt"),
212             changer => $changer),
213     [
214         [   "oldbox", "/opt", "20080414144444", 0, [
215                 '20080414144444/oldbox._opt',
216             ],
217         ],
218     ],
219     "simple plan for a dump on holding disk");
220
221 is_plan(make_plan_sync(
222             dumpspec => ds("somebox", "^/lib", "200801"),
223             changer => $changer),
224     [
225         [   "somebox", "/lib", "20080111000000", 0, [
226                 'Conf-001' => 1,
227             ],
228         ],
229     ],
230     "simple plan for just one dump");
231
232 is_plan(make_plan_sync(
233             dumpspec => ds("somebox", "^/lib"),
234             changer => $changer),
235     [
236         [   "somebox", "/lib", "20080111000000", 0, [
237                 'Conf-001' => 1,
238             ],
239         ],
240         [   "somebox", "/lib", "20080313133333", 0, [
241                 'Conf-003' => 2,
242                 'Conf-003' => 3,
243                 'Conf-003' => 4,
244                 'Conf-003' => 5,
245                 'Conf-003' => 6,
246                 'Conf-003' => 7,
247                 'Conf-003' => 8,
248                 'Conf-003' => 9,
249                 'Conf-003' => 10,
250                 'Conf-003' => 11,
251             ],
252         ],
253     ],
254     "plan for two dumps, in order by tape write time");
255
256 is_plan(make_plan_sync(
257             dumpspec => ds("otherbox", "^/lib"),
258             changer => $changer),
259     [
260         [   "otherbox", "/lib", "20080414144444", 1, [
261                 '20080414144444/otherbox._lib',
262             ],
263         ],
264         [   "otherbox", "/lib", "20080313133333", 0, [
265                 'Conf-003', 13,
266             ],
267         ],
268     ],
269     "plan for a two dumps, one on holding disk; holding dumps prioritized first");
270
271 is_plan(make_plan_sync(
272             dumpspecs => [
273                 ds("somebox", "^/lib", "20080111"),
274                 ds("euclid", "/home/dustin/code/backuppc"),
275             ],
276             changer => $changer),
277     [
278         [   "somebox", "/lib", "20080111000000", 0, [
279                 'Conf-001' => 1,
280             ],
281         ],
282         [   "euclid", "/home/dustin/code/backuppc", "20100127172011", 0, [
283                 'Conf-013' => 1,
284                 'Conf-013' => 2,
285                 'Conf-013' => 3,
286                 'Conf-014' => 1,
287                 'Conf-014' => 2,
288                 'Conf-014' => 3,
289             ],
290         ],
291     ],
292     "plan for two dumps, one of them spanned, in order by tape write time");
293
294 is_plan(make_plan_sync(
295             dumpspec => ds("somebox", "^/lib", "200803"),
296             one_dump_per_part => 1,
297             changer => $changer),
298     [
299         [   "somebox", "/lib", "20080313133333", 0, [
300                 'Conf-003' => 2,
301             ],
302         ],
303         [   "somebox", "/lib", "20080313133333", 0, [
304                 'Conf-003' => 3,
305             ],
306         ],
307         [   "somebox", "/lib", "20080313133333", 0, [
308                 'Conf-003' => 4,
309             ],
310         ],
311         [   "somebox", "/lib", "20080313133333", 0, [
312                 'Conf-003' => 5,
313             ],
314         ],
315         [   "somebox", "/lib", "20080313133333", 0, [
316                 'Conf-003' => 6,
317             ],
318         ],
319         [   "somebox", "/lib", "20080313133333", 0, [
320                 'Conf-003' => 7,
321             ],
322         ],
323         [   "somebox", "/lib", "20080313133333", 0, [
324                 'Conf-003' => 8,
325             ],
326         ],
327         [   "somebox", "/lib", "20080313133333", 0, [
328                 'Conf-003' => 9,
329             ],
330         ],
331         [   "somebox", "/lib", "20080313133333", 0, [
332                 'Conf-003' => 10,
333             ],
334         ],
335         [   "somebox", "/lib", "20080313133333", 0, [
336                 'Conf-003' => 11,
337             ],
338         ],
339     ],
340     "plan for a multipart dump, one_dump_per_part");
341
342 is_plan(make_plan_sync(
343             dumpspec => ds("oldbox", "^/opt", "20080414144444"),
344             holding_file => $holding_filenames{'oldbox_opt_20080414144444_holding'}),
345     [
346         [   "oldbox", "/opt", "20080414144444", 0, [
347                 '20080414144444/oldbox._opt',
348             ],
349         ],
350     ],
351     "make_plan creates an appropriate plan for an explicit holding-disk recovery");
352
353 is_plan(make_plan_sync(
354             holding_file => $holding_filenames{'oldbox_opt_20080414144444_holding'}),
355     [
356         [   "oldbox", "/opt", "20080414144444", 0, [
357                 '20080414144444/oldbox._opt',
358             ],
359         ],
360     ],
361     "same, without a dumpspec");
362
363 is_plan(make_plan_sync(
364             dumpspec => ds("euclid", "/home/dustin/code/backuppc"),
365             filelist => [
366                 'Conf-013' => [1, 2, 3],
367                 'Conf-014' => [1, 2, 3],
368             ],
369             changer => $changer),
370     [
371         [   "euclid", "/home/dustin/code/backuppc", "20100127172011", 0, [
372                 'Conf-013' => 1,
373                 'Conf-013' => 2,
374                 'Conf-013' => 3,
375                 'Conf-014' => 1,
376                 'Conf-014' => 2,
377                 'Conf-014' => 3,
378             ],
379         ],
380     ],
381     "plan based on filelist, with a dumpspec");
382
383 is_plan(make_plan_sync(
384             filelist => [
385                 'Conf-013' => [1, 2, 3],
386                 'Conf-014' => [1, 2, 3],
387             ],
388             changer => $changer),
389     [
390         [   "euclid", "/home/dustin/code/backuppc", "20100127172011", 0, [
391                 'Conf-013' => 1,
392                 'Conf-013' => 2,
393                 'Conf-013' => 3,
394                 'Conf-014' => 1,
395                 'Conf-014' => 2,
396                 'Conf-014' => 3,
397             ],
398         ],
399     ],
400     "plan based on filelist, without a dumpspec");
401
402 __DATA__
403 # a short-datestamp logfile with only a single, single-part file in it
404 ::: log.20080111.0
405 :tapelist 20080111 Conf-001
406 DISK planner somebox /lib
407 START planner date 20080111
408 START driver date 20080111
409 STATS driver hostname somebox
410 STATS driver startup time 0.051
411 FINISH planner date 20080111 time 82.721
412 START taper datestamp 20080111 label Conf-001 tape 1
413 SUCCESS dumper somebox /lib 20080111 0 [sec 0.209 kb 1970 kps 9382.2 orig-kb 1970]
414 SUCCESS chunker somebox /lib 20080111 0 [sec 0.305 kb 420 kps 1478.7]
415 STATS driver estimate somebox /lib 20080111 0 [sec 1 nkb 2002 ckb 480 kps 385]
416 PART taper Conf-001 1 somebox /lib 20080111 1/1 0 [sec 4.813543 kb 419 kps 87.133307]
417 DONE taper somebox /lib 20080111 1 0 [sec 4.813543 kb 419 kps 87.133307]
418 FINISH driver date 20080111 time 2167.581
419
420 # a logfile with several dumps in it, one of which comes in many parts, and one of which is
421 # from a previous run
422 ::: log.20080313133333.0
423 :tapelist 20080313133333 Conf-003
424 DISK planner somebox /usr/bin
425 DISK planner somebox /lib
426 DISK planner otherbox /lib
427 DISK planner otherbox /usr/bin
428 START planner date 20080313133333
429 START driver date 20080313133333
430 STATS driver hostname somebox
431 STATS driver startup time 0.059
432 INFO planner Full dump of somebox:/lib promoted from 2 days ahead.
433 FINISH planner date 20080313133333 time 0.286
434 SUCCESS dumper somebox /usr/bin 20080313133333 1 [sec 0.001 kb 20 kps 10352.0 orig-kb 20]
435 SUCCESS chunker somebox /usr/bin 20080313133333 1 [sec 1.023 kb 20 kps 50.8]
436 STATS driver estimate somebox /usr/bin 20080313133333 1 [sec 0 nkb 52 ckb 64 kps 1024]
437 START taper datestamp 20080313133333 label Conf-003 tape 1
438 PART taper Conf-003 1 somebox /usr/bin 20080313133333 1/1 1 [sec 0.000370 kb 20 kps 54054.054054]
439 DONE taper somebox /usr/bin 20080313133333 1 1 [sec 0.000370 kb 20 kps 54054.054054]
440 # a multi-part dump
441 SUCCESS dumper somebox /lib 20080313133333 0 [sec 0.189 kb 3156 kps 50253.1 orig-kb 3156]
442 SUCCESS chunker somebox /lib 20080313133333 0 [sec 5.250 kb 3156 kps 1815.5]
443 STATS driver estimate somebox /lib 20080313133333 0 [sec 1 nkb 3156 ckb 3156 kps 9500]
444 PART taper Conf-003 2 somebox /lib 20080313133333 1/10 0 [sec 0.005621 kb 1024 kps 182173.990393]
445 PART taper Conf-003 3 somebox /lib 20080313133333 2/10 0 [sec 0.006527 kb 1024 kps 156886.777999]
446 PART taper Conf-003 4 somebox /lib 20080313133333 3/10 0 [sec 0.005854 kb 1024 kps 174923.129484]
447 PART taper Conf-003 5 somebox /lib 20080313133333 4/10 0 [sec 0.007344 kb 1024 kps 147993.746743]
448 PART taper Conf-003 6 somebox /lib 20080313133333 5/10 0 [sec 0.007344 kb 1024 kps 147993.746743]
449 PART taper Conf-003 7 somebox /lib 20080313133333 6/10 0 [sec 0.007344 kb 1024 kps 147993.746743]
450 PART taper Conf-003 8 somebox /lib 20080313133333 7/10 0 [sec 0.007344 kb 1024 kps 147993.746743]
451 PART taper Conf-003 9 somebox /lib 20080313133333 8/10 0 [sec 0.007344 kb 1024 kps 147993.746743]
452 PART taper Conf-003 10 somebox /lib 20080313133333 9/10 0 [sec 0.007344 kb 1024 kps 147993.746743]
453 PART taper Conf-003 11 somebox /lib 20080313133333 10/10 0 [sec 0.001919 kb 284 kps 147993.746743]
454 DONE taper somebox /lib 20080313133333 10 0 [sec 0.051436 kb 3156 kps 184695.543977]
455 SUCCESS dumper otherbox /lib 20080313133333 0 [sec 0.001 kb 190 kps 10352.0 orig-kb 20]
456 SUCCESS chunker otherbox /lib 20080313133333 0 [sec 1.023 kb 190 kps 50.8]
457 STATS driver estimate otherbox /lib 20080313133333 0 [sec 0 nkb 190 ckb 190 kps 1024]
458 # this dump is from a previous run, with an older dump_timestamp
459 PART taper Conf-003 12 otherbox /usr/bin 20080311131133 1/1 0 [sec 0.002733 kb 240 kps 136425.648022]
460 DONE taper otherbox /usr/bin 20080311131133 1 0 [sec 0.002733 kb 240 kps 136425.648022]
461 PART taper Conf-003 13 otherbox /lib 20080313133333 1/1 0 [sec 0.001733 kb 190 kps 136425.648022]
462 DONE taper otherbox /lib 20080313133333 1 0 [sec 0.001733 kb 190 kps 136425.648022]
463 FINISH driver date 20080313133333 time 24.777
464
465 # A logfile with some partial parts (PARTPARTIAL) in it
466 ::: log.20080414144444.0
467 :tapelist 20080414144444 Conf-004
468 :tapelist 20080414144444 Conf-005
469 DISK planner otherbox /lib
470 START planner date 20080414144444
471 START driver date 20080414144444
472 STATS driver hostname otherbox
473 STATS driver startup time 0.075
474 INFO taper Will write new label `Conf-004' to new (previously non-amanda) tape
475 FINISH planner date 20080414144444 time 2.139
476 SUCCESS dumper otherbox /lib 20080414144444 1 [sec 0.003 kb 60 kps 16304.3 orig-kb 60]
477 SUCCESS chunker otherbox /lib 20080414144444 1 [sec 1.038 kb 60 kps 88.5]
478 STATS driver estimate otherbox /lib 20080414144444 1 [sec 0 nkb 92 ckb 96 kps 1024]
479 START taper datestamp 20080414144444 label Conf-004 tape 1
480 PARTPARTIAL taper Conf-004 1 otherbox /lib 20080414144444 1/1 1 [sec 0.000707 kb 32 kps 45261.669024] ""
481 INFO taper Will request retry of failed split part.
482 INFO taper Will write new label `Conf-005' to new (previously non-amanda) tape
483 START taper datestamp 20080414144444 label Conf-005 tape 2
484 PARTPARTIAL taper Conf-005 1 otherbox /lib 20080414144444 1/1 1 [sec 0.000540 kb 32 kps 59259.259259] ""
485 INFO taper Will request retry of failed split part.
486 WARNING driver Out of tapes; going into degraded mode.
487 PARTIAL taper otherbox /lib 20080414144444 1 1 [sec 0.000540 kb 32 kps 59259.259259] "full-up"
488 # a completely failed dump
489 FAIL taper otherbox /boot 20080414144444 0 "no-space"
490 FINISH driver date 20080414144444 time 6.959
491
492 # a spanned dump (yep, a real dump)
493 ::: log.20100127172011.0
494 :tapelist 20100127172011 Conf-013
495 :tapelist 20100127172011 Conf-014
496 INFO amdump amdump pid 30186
497 INFO planner planner pid 30207
498 START planner date 20100127172011
499 DISK planner euclid /home/dustin/code/backuppc
500 INFO planner Adding new disk euclid:/home/dustin/code/backuppc.
501 INFO driver driver pid 30208
502 START driver date 20100127172011
503 STATS driver hostname euclid
504 INFO dumper dumper pid 30220
505 STATS driver startup time 0.097
506 INFO dumper dumper pid 30222
507 INFO dumper dumper pid 30221
508 INFO dumper dumper pid 30213
509 INFO taper taper pid 30210
510 FINISH planner date 20100127172011 time 1.224
511 INFO planner pid-done 30207
512 INFO taper Will write new label `Conf-013' to new tape
513 INFO chunker chunker pid 30255
514 INFO dumper gzip pid 30259
515 SUCCESS dumper euclid /home/dustin/code/backuppc 20100127172011 0 [sec 0.933 kb 2770 kps 2968.5 orig-kb 2770]
516 SUCCESS chunker euclid /home/dustin/code/backuppc 20100127172011 0 [sec 0.943 kb 2770 kps 2970.1]
517 INFO chunker pid-done 30255
518 STATS driver estimate euclid /home/dustin/code/backuppc 20100127172011 0 [sec 2 nkb 2802 ckb 2816 kps 1024]
519 INFO dumper pid-done 30259
520 START taper datestamp 20100127172011 label Conf-013 tape 1
521 PART taper Conf-013 1 euclid /home/dustin/code/backuppc 20100127172011 1/-1 0 [sec 0.000763 kb 512 kps 670972.950092]
522 PART taper Conf-013 2 euclid /home/dustin/code/backuppc 20100127172011 2/-1 0 [sec 0.000770 kb 512 kps 664770.167400]
523 PART taper Conf-013 3 euclid /home/dustin/code/backuppc 20100127172011 3/-1 0 [sec 0.000877 kb 512 kps 583952.261903]
524 PARTPARTIAL taper Conf-013 4 euclid /home/dustin/code/backuppc 20100127172011 4/-1 0 [sec 0.000689 kb 352 kps 510888.307044] "No space left on device"
525 INFO taper Will request retry of failed split part.
526 INFO taper tape Conf-013 kb 1536 fm 4 [OK]
527 INFO taper Will write new label `Conf-014' to new tape
528 START taper datestamp 20100127172011 label Conf-014 tape 2
529 PART taper Conf-014 1 euclid /home/dustin/code/backuppc 20100127172011 4/-1 0 [sec 0.001346 kb 512 kps 380377.004130]
530 PART taper Conf-014 2 euclid /home/dustin/code/backuppc 20100127172011 5/-1 0 [sec 0.001338 kb 512 kps 382524.888399]
531 PART taper Conf-014 3 euclid /home/dustin/code/backuppc 20100127172011 6/-1 0 [sec 0.000572 kb 210 kps 367336.443449]
532 DONE taper euclid /home/dustin/code/backuppc 20100127172011 6 0 [sec 0.005666 kb 2770 kps 488860.596548]
533 INFO dumper pid-done 30213
534 INFO dumper pid-done 30220
535 INFO dumper pid-done 30222
536 INFO taper tape Conf-014 kb 1234 fm 3 [OK]
537 INFO dumper pid-done 30221
538 INFO taper pid-done 30210
539 FINISH driver date 20100127172011 time 4.197
540 INFO driver pid-done 30208
541
542 # holding-disk
543 :holding otherbox_lib_20080414144444_holding 20080414144444 otherbox /lib 1 OK 256
544 :holding oldbox_opt_20080414144444_holding 20080414144444 oldbox /opt 0 OK 1298