lintian doesn't like orphan packages with uploaders...
[debian/amanda] / installcheck / Amanda_DB_Catalog.pl
index a93c48f891a040718942ab2bd357f27c02930f52..ee6d32b99f2627ae871538c243534845696f1475 100644 (file)
@@ -1,8 +1,9 @@
-# Copyright (c) 2008 Zmanda Inc.  All Rights Reserved.
+# Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
 #
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 2 as published
-# by the Free Software Foundation.
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 #
-# Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
+# Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
 
-# TODO: check large values of 'kb'
-
-use Test::More tests => 35;
+use Test::More tests => 85;
 use File::Path;
+use Data::Dumper;
 use strict;
+use warnings;
 
 use lib "@amperldir@";
-use Installcheck::Config;
+use Installcheck;
+use Installcheck::Run;
+use Installcheck::Catalogs;
 use Amanda::Config qw( :init :getconf config_dir_relative );
 use Amanda::DB::Catalog;
+use Amanda::Cmdline;
+use Amanda::Xfer qw( :constants );
+
+# send xfer logging somewhere
+Amanda::Debug::dbopen("installcheck");
+Installcheck::log_test_output();
 
 # set up and load a simple config
-my $testconf = Installcheck::Config->new();
+my $testconf = Installcheck::Run->setup();
 $testconf->write();
 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
     or die("Could not load test config");
@@ -41,434 +50,645 @@ is_deeply([ Amanda::DB::Catalog::get_write_timestamps() ], [],
 is_deeply(Amanda::DB::Catalog::get_latest_write_timestamp(), undef,
     "No latest write_timestamp in an empty catalog");
 
-is_deeply([ Amanda::DB::Catalog::get_dumps() ], [],
-    "No dumpfiles in an empty catalog");
+is_deeply(Amanda::DB::Catalog::get_latest_write_timestamp(type => 'amvault'), undef,
+    "No latest write_timestamp in an empty catalog, even of a specific type");
+
+is_deeply([ Amanda::DB::Catalog::get_parts() ], [],
+    "No parts in an empty catalog");
 
 # and add some logfiles to query, and a corresponding tapelist, while also gathering
-# a list of dumpfiles for comparison with the results from Amanda::DB::Catalog
-my $logdir = config_dir_relative(getconf($CNF_LOGDIR));
-my $tapelist_fn = config_dir_relative(getconf($CNF_TAPELIST));
-my $output;
-my $write_timestamp;
-my %dumpfiles;
-open (my $tapelist, ">", $tapelist_fn);
-while (<DATA>) {
-    # skip comments
-    next if (/^#/ or /^\S*$/);
-
-    # add to tapelist
-    if (/^:tapelist (\d+) (\S+)\s*$/) {
-       print $tapelist "$1 $2 reuse\n";
-       next;
-    }
-    
-    # new logfile
-    if (/^::: (.*)/) {
-       open $output, ">", "$logdir/$1" or die("Could not open $1 for writing: $!");
-       next;
+# a list of parts and dumps for comparison with the results from Amanda::DB::Catalog.
+# also add some files to holding disk
+my @dumpspecs;
+
+# install the bigdb catalog
+my $cat = Installcheck::Catalogs::load("bigdb");
+$cat->install();
+my %dumps = $cat->get_dumps();
+my %parts = $cat->get_parts();
+
+Amanda::DB::Catalog::_clear_cache();
+
+sub partstr {
+    my ($part) = @_;
+    if (exists $part->{'holding_file'}) {
+       return "$part->{holding_file}: " .
+              "$part->{dump}->{hostname} $part->{dump}->{diskname} " .
+              "w$part->{dump}->{write_timestamp} d$part->{dump}->{dump_timestamp}";
+   } else {
+       return "$part->{label}:$part->{filenum}: " .
+              "$part->{dump}->{hostname} $part->{dump}->{diskname} $part->{dump}->{orig_kb} " .
+              "w$part->{dump}->{write_timestamp} d$part->{dump}->{dump_timestamp}";
+   }
+}
+
+# filter out recursive links from part->dump->parts, without changing
+# the original objects.  Note that this removes the object-identity of
+# dumps.
+sub filter_parts {
+    my ($parts) = @_;
+
+    my @rv;
+    for my $p (@$parts) {
+       $p = do { my %t = %$p; \%t; }; # copy hash
+       $p->{'dump'} = do { my %t = %{$p->{'dump'}}; \%t; };
+       $p->{'dump'}->{'parts'} = undef;
+       push @rv, $p;
     }
 
-    # write_timestamp
-    if (/^:timestamp (\d+)/) {
-       $write_timestamp = $1;
-       next;
+    return \@rv;
+}
+
+sub got_parts {
+    my ($got, $exp, $msg, %params) = @_;
+
+    # give a warning if we accidentally select zero parts
+    if (@$exp == 0) {
+       diag("warning: zero parts expected: $msg")
+           unless $params{'zero_parts_expected'};
     }
 
-    # new dumpfile
-    if (/^:dumpfile (\S+) (\S+) (\S+) (\S+) (\d+) (\S+) (\d+) (\d+) (\d+) (\S+) (\S+) (\d+)/) {
-       $dumpfiles{$1} = {
-           'dump_timestamp' => $2,     'hostname' => $3,           'diskname' => $4,
-           'level' => $5+0,            'label' => $6,              'filenum' => $7+0,
-           'partnum' => $8+0,          'nparts' => $9+0,           'status' => $10,
-           'sec' => $11+0.0,           'kb' => $12+0,
-           'write_timestamp' => $write_timestamp,
-       };
-       next;
+    # filter recursive references to avoid confusing old is_deeply instances
+    if (!is_deeply(filter_parts($got), filter_parts($exp), $msg)) {
+       diag("got parts:");
+       my $i = 0;
+       for (@$got) {
+           diag(" [$i]  " . partstr($_));
+           $i++;
+       }
+
+       diag("expected parts:");
+       $i = 0;
+       for (@$exp) {
+           diag(" [$i]  " . partstr($_));
+           $i++;
+       }
+       return '';
     }
+    return 1;
+}
 
-    die("syntax error") if (/^:/);
+# get parts filtered by a regexp on the key
+sub parts_named($) {
+    my ($expr) = @_; 
+    my @selected_keys = grep { $_ =~ $expr } keys %parts;
+    return map { $parts{$_} } @selected_keys;
+}
 
-    print $output $_;
+# get parts filtered by an expression on the dumpfile itself
+sub parts_matching(&) {
+    my ($block) = @_; 
+    return grep { &$block } values %parts;
 }
-close($output);
-close($tapelist);
-Amanda::DB::Catalog::_clear_cache();
 
-##
-# Test the timestamps
+# put @_ in a canonical order
+sub sortparts {
+    map {
+       # convert bigints to strings and on to integers so is_deeply doesn't get confused
+       $_->{'dump'}->{'level'} = "$_->{dump}->{level}" + 0;
+       $_->{'dump'}->{'bytes'} = 0;
+       $_->{'dump'}->{'bytes'} = "$_->{dump}->{bytes}" + 0 if defined $_->{dump}->{bytes};
+       $_->{'dump'}->{'kb'} = "$_->{dump}->{kb}" + 0;
+       $_->{'dump'}->{'orig_kb'} = "$_->{dump}->{orig_kb}" + 0;
+       if (!defined $_->{filenum}) {
+           $_->{'filenum'} = 0;
+       } else {
+           $_->{'filenum'} = "$_->{filenum}" + 0;
+       }
+       $_->{'kb'} = "$_->{kb}" + 0;
+       $_->{'orig_kb'} = "$_->{orig_kb}" + 0;
+       $_->{'partnum'} = "$_->{partnum}" + 0;
+       $_;
+    } sort {
+       if (exists $a->{'holding_file'} and exists $b->{'holding_file'}) {
+           return $a->{'holding_file'} cmp $b->{'holding_file'};
+       } elsif (not exists $a->{'holding_file'} and not exists $b->{'holding_file'}) {
+           return ($a->{'label'} cmp $b->{'label'})
+               || ($a->{'filenum'} <=> $b->{'filenum'});
+       } else {
+           return (exists $a->{'holding_file'})? 1 : -1;
+       }
+    }
+    @_;
+}
 
-is_deeply([ Amanda::DB::Catalog::get_write_timestamps(), ],
-    [ '20080111000000', '20080222222222', '20080313133333', '20080414144444' ],
-    "get_write_timestamps returns all logfile datestamps in proper order, with zero-padding");
+sub dumpstr {
+    my ($dump) = @_;
+    return "$dump->{hostname} $dump->{diskname} " .
+          "$dump->{dump_timestamp} $dump->{level}";
+}
 
-is(Amanda::DB::Catalog::get_latest_write_timestamp(), '20080414144444',
-    "get_latest_write_timestamp correctly returns the latest write timestamp");
+# filter out recursive links from dump->parts->dump, without changing
+# the original objects.
+sub filter_dumps {
+    my ($dumps) = @_;
+
+    my @rv;
+    for my $d (@$dumps) {
+       $d = do { my %t = %$d; \%t; }; # copy hash
+       my @dparts = map {
+               return undef unless defined $_;
+               my $p = do { my %t = %$_; \%t }; # copy part
+               $p->{'dump'} = undef;
+               $p;
+           } @{$d->{'parts'}};
+       $d->{'parts'} = [ @dparts ];
+       push @rv, $d;
+    }
 
-##
-# test get_dumps and sort_dumps
+    return \@rv;
+}
+
+sub got_dumps {
+    my ($got, $exp, $msg, %params) = @_;
+
+    # give a warning if we accidentally select zero dumps
+    if (@$exp == 0) {
+       diag("warning: zero dumps expected: $msg")
+           unless $params{'zero_dumps_expected'};
+    }
+
+    # filter recursive references to avoid confusing old is_deeply instances
+    if (!is_deeply(filter_dumps($got), filter_dumps($exp), $msg)) {
+       diag("got dumps:");
+       for (@$got) {
+           diag("  " . dumpstr($_));
+       }
+       diag("expected dumps:");
+       for (@$exp) {
+           diag("  " . dumpstr($_));
+       }
+       return '';
+    }
+    return 1;
+}
 
 # get dumps filtered by a regexp on the key
-sub dump_names($) {
+sub dumps_named($) {
     my ($expr) = @_; 
-    my @selected_keys = grep { $_ =~ $expr } keys %dumpfiles;
-    return map { $dumpfiles{$_} } @selected_keys;
+    my @selected_keys = grep { $_ =~ $expr } keys %dumps;
+    return map { $dumps{$_} } @selected_keys;
 }
 
 # get dumps filtered by an expression on the dumpfile itself
-sub dumps(&) {
+sub dumps_matching(&) {
     my ($block) = @_; 
-    return grep { &$block } values %dumpfiles;
+    return grep { &$block } values %dumps;
 }
 
 # put @_ in a canonical order
 sub sortdumps {
     map {
-       # convert bigints to strings so is_deeply doesn't get confused
-       $_->{'level'} = "$_->{level}";
-       $_->{'filenum'} = "$_->{filenum}";
-       $_->{'kb'} = "$_->{kb}";
+       # convert bigints to strings and on to integers so is_deeply doesn't get confused
+       $_->{'level'} = "$_->{level}" + 0;
+       $_->{'bytes'} = 0;
+       $_->{'bytes'} = "$_->{bytes}" + 0 if defined $_->{bytes};
+       $_->{'kb'} = "$_->{kb}" + 0;
+       $_->{'orig_kb'} = 0;
+       $_->{'orig_kb'} = "$_->{orig_kb}" + 0 if defined $_->{orig_kb};
+       $_->{'nparts'} = "$_->{nparts}" + 0;
        $_;
     } sort {
-       $a->{'label'} cmp $b->{'label'}
-           or $a->{'filenum'} cmp $b->{'filenum'}
+       $a->{'write_timestamp'} cmp $b->{'write_timestamp'}
+           or $a->{'hostname'} cmp $b->{'hostname'}
+           or $a->{'diskname'} cmp $b->{'diskname'}
+           or $a->{'level'} <=> $b->{'level'}
     }
     @_;
 }
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps() ],
-    [ sortdumps dump_names qr/.*/ ],
-    "get_dumps returns all dumps when given no parameters");
+##
+# Test the timestamps
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(write_timestamp => '20080111000000') ],
-    [ sortdumps dump_names qr/somebox_lib_20080111/ ],
-    "get_dumps parameter write_timestamp");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(write_timestamp => '20080111') ],
-    [ sortdumps dump_names qr/somebox_lib_20080111/ ],
-    "get_dumps accepts a short write_timestamp and zero-pads it");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(write_timestamps => ['20080111000000','20080222222222']) ],
-    [ sortdumps dump_names qr/(20080111|20080222222222)$/ ],
-    "get_dumps parameter write_timestamps");
+is_deeply([ Amanda::DB::Catalog::get_write_timestamps(), ],
+    [ '20080111000000', '20080222222222', '20080313133333',
+      '20080414144444', '20080515155555', '20080616166666',
+      '20100722000000' ],
+    "get_write_timestamps returns all logfile datestamps in proper order, with zero-padding");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(dump_timestamp => '20080111000000') ],
-    [ sortdumps dump_names qr/somebox_lib_20080111/ ],
-    "get_dumps parameter dump_timestamp");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(dump_timestamp => '20080111') ],
-    [ sortdumps dump_names qr/somebox_lib_20080111/ ],
-    "get_dumps accepts a short dump_timestamp and zero-pads it");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(dump_timestamps => ['20080111000000','20080222222222']) ],
-    [ sortdumps dump_names qr/(20080111|20080222222222)$/ ],
-    "get_dumps parameter dump_timestamps");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(dump_timestamp_match => '200801-2') ],
-    [ sortdumps dump_names qr/20080[12]/ ],
-    "get_dumps parameter dump_timestamp_match");
+is(Amanda::DB::Catalog::get_latest_write_timestamp(), '20100722000000',
+    "get_latest_write_timestamp correctly returns the latest write timestamp");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(hostname => 'otherbox') ],
-    [ sortdumps dump_names qr/^otherbox_/ ],
-    "get_dumps parameter hostname");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(hostnames => ['otherbox','somebox']) ],
-    [ sortdumps dump_names qr/^(otherbox_|somebox_)/ ],
-    "get_dumps parameter hostnames");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(hostname_match => '*box') ],
-    [ sortdumps dump_names qr/box/ ],
-    "get_dumps parameter hostname_match");
+is(Amanda::DB::Catalog::get_latest_write_timestamp(type => 'amdump'), '20100722000000',
+    "get_latest_write_timestamp correctly returns the latest write timestamp of type amdump");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(diskname => '/lib') ],
-    [ sortdumps dump_names qr/_lib_/ ],
-    "get_dumps parameter diskname");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(disknames => ['/lib','/usr/bin']) ],
-    [ sortdumps dump_names qr/(_lib_|_usr_bin_)/ ],
-    "get_dumps parameter disknames");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(diskname_match => '/usr') ],
-    [ sortdumps dump_names qr/_usr_/ ],
-    "get_dumps parameter diskname_match");
+is(Amanda::DB::Catalog::get_latest_write_timestamp(type => 'amflush'), '20080111000000',
+    "get_latest_write_timestamp correctly returns the latest write timestamp of type amflush");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(label => 'Conf-001') ],
-    [ sortdumps dumps { $_->{'label'} eq 'Conf-001' } ],
-    "get_dumps parameter label");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(labels => ['Conf-002','Conf-003']) ],
-    [ sortdumps dumps { $_->{'label'} eq 'Conf-002' or $_->{'label'} eq 'Conf-003' } ],
-    "get_dumps parameter labels");
+is(Amanda::DB::Catalog::get_latest_write_timestamp(types => [qw(amvault amflush)]),
+    '20080222222222',
+    "get_latest_write_timestamp correctly returns the latest write timestamp of a set of ts's");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(level => 0) ],
-    [ sortdumps dumps { $_->{'level'} == 0 } ],
-    "get_dumps parameter level");
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(levels => [ 1 ]) ],
-    [ sortdumps dumps { $_->{'level'} == 1 } ],
-    "get_dumps parameter levels");
+is(Amanda::DB::Catalog::get_run_type('20080222222222'), "amvault",
+    "get_run_type detects amvault");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(status => "OK") ],
-    [ sortdumps dumps { $_->{'status'} eq "OK" } ],
-    "get_dumps parameter status = OK");
+is(Amanda::DB::Catalog::get_run_type('20080111'), "amflush",
+    "get_run_type detects amflush (short ts)");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(status => "PARTIAL") ],
-    [ sortdumps dumps { $_->{'status'} eq "PARTIAL" } ],
-    "get_dumps parameter status = PARTIAL");
+is(Amanda::DB::Catalog::get_run_type('20080111000000'), "amflush",
+    "get_run_type detects amflush (long ts)");
+
+##
+# test get_parts and sort_parts
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts() ],
+    [ sortparts parts_named qr/.*/ ],
+    "get_parts returns all parts when given no parameters");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(write_timestamp => '20080111000000') ],
+    [ sortparts parts_named qr/somebox_lib_20080111/ ],
+    "get_parts parameter write_timestamp");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(write_timestamp => '20080111') ],
+    [ sortparts parts_named qr/somebox_lib_20080111/ ],
+    "get_parts accepts a short write_timestamp and zero-pads it");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(write_timestamps => ['20080111000000','20080222222222']) ],
+    [ sortparts parts_named qr/(20080111|20080222222222_p\d*)$/ ],
+    "get_parts parameter write_timestamps");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(write_timestamp => '20080111', holding => 1) ],
+    [ ],
+    "get_parts parameter write_timestamp + holding => 1 returns nothing",
+    zero_parts_expected => 1);
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(dump_timestamp => '20080111000000') ],
+    [ sortparts parts_named qr/somebox_lib_20080111/ ],
+    "get_parts parameter dump_timestamp");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(dump_timestamp => '20080111') ],
+    [ sortparts parts_named qr/somebox_lib_20080111/ ],
+    "get_parts accepts a short dump_timestamp and zero-pads it");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(dump_timestamps => ['20080111000000','20080222222222']) ],
+    [ sortparts parts_named qr/(20080111|20080222222222_p\d*)$/ ],
+    "get_parts parameter dump_timestamps");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(dump_timestamp_match => '200801-2') ],
+    [ sortparts parts_named qr/20080[12]/ ],
+    "get_parts parameter dump_timestamp_match");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostname => 'otherbox') ],
+    [ sortparts parts_named qr/^otherbox_/ ],
+    "get_parts parameter hostname");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostnames => ['otherbox','somebox']) ],
+    [ sortparts parts_named qr/^(otherbox_|somebox_)/ ],
+    "get_parts parameter hostnames");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostname_match => '*box') ],
+    [ sortparts parts_named qr/box/ ],
+    "get_parts parameter hostname_match");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(diskname => '/lib') ],
+    [ sortparts parts_named qr/_lib_/ ],
+    "get_parts parameter diskname");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(disknames => ['/lib','/usr/bin']) ],
+    [ sortparts parts_named qr/(_lib_|_usr_bin_)/ ],
+    "get_parts parameter disknames");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(diskname_match => '/usr') ],
+    [ sortparts parts_named qr/_usr_/ ],
+    "get_parts parameter diskname_match");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(label => 'Conf-001') ],
+    [ sortparts parts_matching { defined $_->{'label'} and $_->{'label'} eq 'Conf-001' } ],
+    "get_parts parameter label");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(labels => ['Conf-002','Conf-003']) ],
+    [ sortparts parts_matching { defined $_->{'label'} and ($_->{'label'} eq 'Conf-002' or $_->{'label'} eq 'Conf-003') } ],
+    "get_parts parameter labels");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(level => 0) ],
+    [ sortparts parts_matching { $_->{'dump'}->{'level'} == 0 } ],
+    "get_parts parameter level");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(levels => [ 1 ]) ],
+    [ sortparts parts_matching { $_->{'dump'}->{'level'} == 1 } ],
+    "get_parts parameter levels");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(status => "OK") ],
+    [ sortparts parts_matching { $_->{'status'} eq "OK" } ],
+    "get_parts parameter status = OK");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(status => "PARTIAL") ],
+    [ sortparts parts_matching { $_->{'status'} eq "PARTIAL" } ],
+    "get_parts parameter status = PARTIAL");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostname => "oldbox") ],
+    [ sortparts parts_named qr/^oldbox_/ ],
+    "get_parts finds a holding-disk dump");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostname => "oldbox", holding => 0) ],
+    [ ],
+    "get_parts ignores a holding-disk dump if holding is false",
+    zero_parts_expected => 1);
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostname => "oldbox", holding => 1) ],
+    [ sortparts parts_named qr/^oldbox_/ ],
+    "get_parts supplies a holding-disk dump if holding is true");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostnames => [ "oldbox", "somebox" ]) ],
+    [ sortparts (parts_named qr/^oldbox_.*_holding/, parts_named qr/^somebox_/) ],
+    "get_parts returns both holding and on-media dumps");
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostnames => [ "oldbox", "somebox" ],
+                                                    holding => 1) ],
+    [ sortparts parts_named qr/^oldbox_.*_holding/ ],
+    "get_parts ignores an on-media dump if holding is true");
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostnames => [ "oldbox", "somebox" ],
+                                                    holding => 0) ],
+    [ sortparts parts_named qr/^somebox_/ ],
+    "get_parts ignores an holding dump if holding is false");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs([".*", "/lib"], 0);
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(dumpspecs => [ @dumpspecs ]) ],
+    [ sortparts parts_named qr/_lib_/ ],
+    "get_parts parameter dumpspecs with one dumpspec");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs([".*", "/lib", "somebox"], 0);
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(dumpspecs => [ @dumpspecs ]) ],
+    [ sortparts parts_matching { $_->{'dump'}->{'diskname'} eq '/lib'
+                             or $_->{'dump'}->{'hostname'} eq 'somebox' } ],
+    "get_parts parameter dumpspecs with two dumpspecs");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs(["otherbox", "*", "somebox"], 0);
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(dumpspecs => [ @dumpspecs ]) ],
+    [ sortparts parts_matching { $_->{'dump'}->{'hostname'} eq 'otherbox'
+                             or $_->{'dump'}->{'hostname'} eq 'somebox' } ],
+    "get_parts parameter dumpspecs with two non-overlapping dumpspecs");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs(["otherbox", "*", "somebox"], 0);
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(dumpspecs => [ @dumpspecs ], holding => 1), ],
+    [ sortparts parts_matching { $_->{'dump'}->{'hostname'} eq 'otherbox'
+                            and exists $_->{'holding_file'} } ],
+    "get_parts parameter dumpspecs with two non-overlapping dumpspecs, but holding files only");
 
 ## more complex, multi-parameter queries
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(hostname => 'somebox',
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(hostname => 'somebox',
                                                     diskname_match => '/lib') ],
-    [ sortdumps dump_names qr/^somebox_lib_/ ],
-    "get_dumps parameters hostname and diskname_match");
+    [ sortparts parts_named qr/^somebox_lib_/ ],
+    "get_parts parameters hostname and diskname_match");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(write_timestamp => '20080313133333',
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(write_timestamp => '20080313133333',
                                                     dump_timestamp => '20080311131133') ],
-    [ sortdumps dumps { $_->{'dump_timestamp'} eq '20080311131133' 
-                    and $_->{'write_timestamp'} eq '20080313133333' } ],
-    "get_dumps parameters write_timestamp and dump_timestamp");
+    [ sortparts parts_matching { $_->{'dump'}->{'dump_timestamp'} eq '20080311131133' 
+                    and $_->{'dump'}->{'write_timestamp'} eq '20080313133333' } ],
+    "get_parts parameters write_timestamp and dump_timestamp");
 
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(write_timestamp => '20080414144444',
+got_parts([ sortparts Amanda::DB::Catalog::get_parts(write_timestamp => '20080414144444',
                                                     status => 'OK') ],
     [ ], # there were no OK dumps on that date
-    "get_dumps parameters write_timestamp status");
+    "get_parts parameters write_timestamp status",
+    zero_parts_expected => 1);
 
-## test sorting
-# (this is not exhaustive, as that would make the tests more complex than the
-#  code being tested)
+## test part sorting
 
-is_deeply([ Amanda::DB::Catalog::sort_dumps(['write_timestamp'],
-               @dumpfiles{'somebox_lib_20080222222222','somebox_lib_20080111'}) ],
-             [ @dumpfiles{'somebox_lib_20080111','somebox_lib_20080222222222'} ],
+got_parts([ Amanda::DB::Catalog::sort_parts(['write_timestamp'],
+               @parts{'somebox_lib_20080222222222_p1','somebox_lib_20080111'}) ],
+             [ @parts{'somebox_lib_20080111','somebox_lib_20080222222222_p1'} ],
     "sort by write_timestamps");
-is_deeply([ Amanda::DB::Catalog::sort_dumps(['-write_timestamp'],
-               @dumpfiles{'somebox_lib_20080111','somebox_lib_20080222222222'}) ],
-             [ @dumpfiles{'somebox_lib_20080222222222','somebox_lib_20080111'} ],
+got_parts([ Amanda::DB::Catalog::sort_parts(['-write_timestamp'],
+               @parts{'somebox_lib_20080111','somebox_lib_20080222222222_p1'}) ],
+             [ @parts{'somebox_lib_20080222222222_p1','somebox_lib_20080111'} ],
     "sort by write_timestamps, reverse");
 
-is_deeply([ Amanda::DB::Catalog::sort_dumps(['hostname', '-diskname', 'write_timestamp'],
-               @dumpfiles{
-                   'somebox_lib_20080222222222',
+got_parts([ Amanda::DB::Catalog::sort_parts(['hostname', '-diskname', 'write_timestamp'],
+               @parts{
+                   'somebox_lib_20080222222222_p1',
                    'somebox_usr_bin_20080313133333',
                    'somebox_lib_20080313133333_p4',
                    'otherbox_lib_20080313133333',
                    'somebox_lib_20080111',
                    }) ],
-             [ @dumpfiles{
+             [ @parts{
                    'otherbox_lib_20080313133333',
                    'somebox_usr_bin_20080313133333',
                    'somebox_lib_20080111',
-                   'somebox_lib_20080222222222',
+                   'somebox_lib_20080222222222_p1',
                    'somebox_lib_20080313133333_p4',
                    } ],
     "multi-key sort");
 
-## add log entries
-
-# one to an existing logfile, same tape
-Amanda::DB::Catalog::add_dump({
-    'write_timestamp' => '20080111',
-    'dump_timestamp' => '20080707070707',
-    'hostname' => 'newbox',
-    'diskname' => '/newdisk',
-    'level' => 3,
-    'label' => 'Conf-001',
-    'filenum' => 2,
-    'partnum' => 1,
-    'nparts' => 1,
-    'status' => 'OK',
-    'sec' => 13.0,
-    'kb' => 12380,
-});
-
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(hostname => 'newbox') ],
-    [ sortdumps {
-       'write_timestamp' => '20080111000000',
-       'dump_timestamp' => '20080707070707',
-       'hostname' => 'newbox',
-       'diskname' => '/newdisk',
-       'level' => 3,
-       'label' => 'Conf-001',
-       'filenum' => 2,
-       'partnum' => 1,
-       'nparts' => 1,
-       'status' => 'OK',
-       'sec' => 13.0,
-       'kb' => 12380,
-    } ],
-    "successfully re-read an added dump in an existing logfile");
-
-# and again, to test the last-logfile cache in Amanda::DB::Catalog
-Amanda::DB::Catalog::add_dump({
-    'write_timestamp' => '20080111',
-    'dump_timestamp' => '20080707070707',
-    'hostname' => 'newbox',
-    'diskname' => '/newdisk2',
-    'level' => 0,
-    'label' => 'Conf-001',
-    'filenum' => 3,
-    'partnum' => 1,
-    'nparts' => 1,
-    'status' => 'OK',
-    'sec' => 27.0,
-    'kb' => 32380,
-});
-
-is(scalar Amanda::DB::Catalog::get_dumps(hostname => 'newbox'), 2,
-    "adding another dump to that logfile and re-reading gives 2 dumps");
-
-# and another in a new file, as well as a tapelist entry
-Amanda::DB::Catalog::add_dump({
-    'write_timestamp' => '20080707070707',
-    'dump_timestamp' => '20080707070707',
-    'hostname' => 'newlog',
-    'diskname' => '/newdisk',
-    'level' => 3,
-    'label' => 'Conf-009',
-    'filenum' => 1,
-    'partnum' => 1,
-    'nparts' => 1,
-    'status' => 'OK',
-    'sec' => 13.0,
-    'kb' => 12380,
-});
-
-is_deeply([ sortdumps Amanda::DB::Catalog::get_dumps(hostname => 'newlog') ],
-    [ sortdumps {
-       'write_timestamp' => '20080707070707',
-       'dump_timestamp' => '20080707070707',
-       'hostname' => 'newlog',
-       'diskname' => '/newdisk',
-       'level' => 3,
-       'label' => 'Conf-009',
-       'filenum' => 1,
-       'partnum' => 1,
-       'nparts' => 1,
-       'status' => 'OK',
-       'sec' => 13.0,
-       'kb' => 12380,
-    } ],
-    "successfully re-read an added dump in a new logfile");
-
-# and add a multipart dump to that same logfile
-for (my $i = 1; $i <= 5; $i++) {
-    Amanda::DB::Catalog::add_dump({
-       'write_timestamp' => '20080707070707',
-       'dump_timestamp' => '20080707070707',
-       'hostname' => 'newlog',
-       'diskname' => '/bigdisk',
-       'level' => 1,
-       'label' => 'Conf-009',
-       'filenum' => $i+1,
-       'partnum' => $i,
-       'nparts' => 5,
-       'status' => 'OK',
-       'sec' => 13.0,
-       'kb' => 12380,
-    });
-}
+got_parts([ Amanda::DB::Catalog::sort_parts(['filenum'],
+               @parts{
+                   'somebox_lib_20080313133333_p9',
+                   'somebox_lib_20080313133333_p10',
+                   'somebox_lib_20080313133333_p1',
+                   }) ],
+             [ @parts{
+                   'somebox_lib_20080313133333_p1',
+                   'somebox_lib_20080313133333_p9',
+                   'somebox_lib_20080313133333_p10',
+                   } ],
+               "filenum is sorted numerically, not lexically");
+
+got_parts([ Amanda::DB::Catalog::sort_parts(['-partnum'],
+               @parts{
+                   'somebox_lib_20080313133333_p9',
+                   'somebox_lib_20080313133333_p10',
+                   'somebox_lib_20080313133333_p1',
+                   }) ],
+             [ @parts{
+                   'somebox_lib_20080313133333_p10',
+                   'somebox_lib_20080313133333_p9',
+                   'somebox_lib_20080313133333_p1',
+                   } ],
+               "partnum is sorted numerically (and in reverse!), not lexically");
+
+got_parts([ Amanda::DB::Catalog::sort_parts(['nparts'],
+               @parts{
+                   'somebox_lib_20080313133333_p9', # nparts=10
+                   'somebox_lib_20080222222222_p2', # nparts=2
+                   }) ],
+             [ @parts{
+                   'somebox_lib_20080222222222_p2', # nparts=2
+                   'somebox_lib_20080313133333_p9', # nparts=10
+                   } ],
+               "nparts is sorted numerically, not lexically");
+
+got_parts([ Amanda::DB::Catalog::sort_parts(['label'],
+               @parts{
+                   'somebox_lib_20080313133333_p9', # Conf-003
+                   'somebox_lib_20080222222222_p2', # Conf-002
+                   }) ],
+             [ @parts{
+                   'somebox_lib_20080222222222_p2', # Conf-002
+                   'somebox_lib_20080313133333_p9', # Conf-003
+                   } ],
+               "labels sort correctly");
+
+### test dump selecting
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps() ],
+    [ sortdumps dumps_named qr/.*/ ],
+    "get_dumps returns all dumps when given no parameters");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(write_timestamp => '20080111000000') ],
+    [ sortdumps dumps_named qr/somebox_lib_20080111/ ],
+    "get_dumps parameter write_timestamp");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(write_timestamp => '20080111') ],
+    [ sortdumps dumps_named qr/somebox_lib_20080111/ ],
+    "get_dumps accepts a short write_timestamp and zero-pads it");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(write_timestamps => ['20080111000000','20080222222222']) ],
+    [ sortdumps dumps_named qr/(20080111|20080222222222)$/ ],
+    "get_dumps parameter write_timestamps");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(hostname => 'otherbox') ],
+    [ sortdumps dumps_named qr/^otherbox/ ],
+    "get_dumps parameter hostname");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(hostname => 'oldbox') ],
+    [ sortdumps dumps_named qr/^oldbox_.*_holding/ ],
+    "get_dumps parameter hostname, holding");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(hostnames => ['notthere', 'otherbox']) ],
+    [ sortdumps dumps_named qr/^otherbox/ ],
+    "get_dumps parameter hostnames");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(hostname_match => 'other*') ],
+    [ sortdumps dumps_named qr/^otherbox/ ],
+    "get_dumps parameter hostname_match");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(diskname => '/lib') ],
+    [ sortdumps dumps_named qr/^[^_]*_lib_/ ],
+    "get_dumps parameter diskname");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(disknames => ['/lib', '/usr/bin']) ],
+    [ sortdumps dumps_named qr/^[^_]*_(usr_bin|lib)_/ ],
+    "get_dumps parameter disknames");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(diskname_match => 'bin') ],
+    [ sortdumps dumps_named qr/.*_bin_/ ],
+    "get_dumps parameter diskname_match");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(dump_timestamp => '20080414144444') ],
+    [ sortdumps dumps_matching { $_->{'dump_timestamp'} eq '20080414144444' } ],
+    "get_dumps parameter dump_timestamp");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(
+                       dump_timestamps => ['20080414144444', '20080311131133']) ],
+    [ sortdumps dumps_matching { $_->{'dump_timestamp'} eq '20080414144444' 
+                             or $_->{'dump_timestamp'} eq '20080311131133' } ],
+    "get_dumps parameter dump_timestamps");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(dump_timestamp_match => '200804-7') ],
+    [ sortdumps dumps_matching { $_->{'dump_timestamp'} =~ /^20080[4567]/ } ],
+    "get_dumps parameter dump_timestamp_match");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(level => 0) ],
+    [ sortdumps dumps_matching { $_->{'level'} == 0 } ],
+    "get_dumps parameter level");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(levels => [ 1 ]) ],
+    [ sortdumps dumps_matching { $_->{'level'} == 1 } ],
+    "get_dumps parameter levels");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(status => "OK") ],
+    [ sortdumps dumps_matching { $_->{'status'} eq "OK" } ],
+    "get_dumps parameter status = OK");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(status => "PARTIAL") ],
+    [ sortdumps dumps_matching { $_->{'status'} eq "PARTIAL" } ],
+    "get_dumps parameter status = PARTIAL");
+
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(status => "FAIL") ],
+    [ sortdumps dumps_matching { $_->{'status'} eq "FAIL" } ],
+    "get_dumps parameter status = FAIL");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs([".*", "/lib"], 0);
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(dumpspecs => [ @dumpspecs ]) ],
+    [ sortdumps dumps_named qr/_lib_/ ],
+    "get_dumps parameter dumpspecs with one dumpspec");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs([".*", "/lib"], 0);
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(dumpspecs => [ @dumpspecs ], holding => 1) ],
+    [ sortdumps dumps_named qr/_lib_.*_holding/ ],
+    "get_dumps parameter dumpspecs with one dumpspec");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs([".*", "/lib", "somebox"], 0);
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(dumpspecs => [ @dumpspecs ]) ],
+    [ sortdumps dumps_matching { $_->{'diskname'} eq '/lib'
+                             or $_->{'hostname'} eq 'somebox' } ],
+    "get_dumps parameter dumpspecs with two dumpspecs");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs(["otherbox", "*", "somebox"], 0);
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(dumpspecs => [ @dumpspecs ]) ],
+    [ sortdumps dumps_matching { $_->{'hostname'} eq 'otherbox'
+                             or $_->{'hostname'} eq 'somebox' } ],
+    "get_dumps parameter dumpspecs with two non-overlapping dumpspecs");
+
+@dumpspecs = Amanda::Cmdline::parse_dumpspecs(["does-not-exist"], 0);
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(dumpspecs => [ @dumpspecs ]) ],
+    [ ],
+    "get_dumps parameter dumpspecs with a dumpspec that matches nothing",
+    zero_dumps_expected => 1);
+
+@dumpspecs = Amanda::Cmdline::dumpspec_t->new(undef, undef, undef, undef, '20080222222222');
+got_dumps([ sortdumps Amanda::DB::Catalog::get_dumps(dumpspecs => [ @dumpspecs ]) ],
+    [ sortdumps dumps_matching { $_->{'write_timestamp'} eq '20080222222222' }],
+    "get_dumps parameter dumpspecs with write_timestamp");
+
+## test dump sorting
+
+got_dumps([ Amanda::DB::Catalog::sort_dumps(['write_timestamp'],
+               @dumps{'somebox_lib_20080222222222','somebox_lib_20080111'}) ],
+             [ @dumps{'somebox_lib_20080111','somebox_lib_20080222222222'} ],
+    "sort dumps by write_timestamps");
+got_dumps([ Amanda::DB::Catalog::sort_dumps(['-write_timestamp'],
+               @dumps{'somebox_lib_20080111','somebox_lib_20080222222222'}) ],
+             [ @dumps{'somebox_lib_20080222222222','somebox_lib_20080111'} ],
+    "sort dumps by write_timestamps, reverse");
+
+got_dumps([ Amanda::DB::Catalog::sort_dumps(['hostname', '-diskname', 'write_timestamp'],
+               @dumps{
+                   'somebox_lib_20080222222222',
+                   'somebox_usr_bin_20080313133333',
+                   'somebox_lib_20080313133333',
+                   'otherbox_lib_20080313133333',
+                   'somebox_lib_20080111',
+                   }) ],
+             [ @dumps{
+                   'otherbox_lib_20080313133333',
+                   'somebox_usr_bin_20080313133333',
+                   'somebox_lib_20080111',
+                   'somebox_lib_20080222222222',
+                   'somebox_lib_20080313133333',
+                   } ],
+               "multi-key dump sort");
+
+got_dumps([ Amanda::DB::Catalog::sort_dumps(['nparts'],
+               @dumps{
+                   'somebox_lib_20080313133333', # nparts=10
+                   'somebox_lib_20080222222222', # nparts=2
+                   }) ],
+             [ @dumps{
+                   'somebox_lib_20080222222222', # nparts=2
+                   'somebox_lib_20080313133333', # nparts=10
+                   } ],
+               "dumps' nparts is sorted numerically, not lexically");
+
+got_dumps([ Amanda::DB::Catalog::sort_dumps(['-level'],
+               @dumps{
+                   'somebox_lib_20080313133333', # level=0
+                   'somebox_usr_bin_20080313133333', # level=1
+                   }) ],
+             [ @dumps{
+                   'somebox_usr_bin_20080313133333', # level=1
+                   'somebox_lib_20080313133333', # level=0
+                   } ],
+               "sort dumps by level, reversed");
+
+got_dumps([ Amanda::DB::Catalog::sort_dumps(['dump_timestamp'],
+               @dumps{
+                   'somebox_lib_20080313133333', # dts=20080313133333
+                   'otherbox_usr_bin_20080313133333_1', # dts=20080311131133
+                   }) ],
+             [ @dumps{
+                   'otherbox_usr_bin_20080313133333_1', # dts=20080311131133
+                   'somebox_lib_20080313133333', # dts=20080313133333
+                   } ],
+               "sort dumps by write_timestamp");
+
+
+# install the multi-taper catalog
+$cat = Installcheck::Catalogs::load("multi-taper");
+$cat->install();
+%dumps = $cat->get_dumps();
+%parts = $cat->get_parts();
+
+Amanda::DB::Catalog::_clear_cache();
+
+got_parts([ sortparts Amanda::DB::Catalog::get_parts() ],
+       [ sortparts parts_named qr/.*/ ],
+       "get_parts returns all parts when given no parameters");
 
-is(scalar Amanda::DB::Catalog::get_dumps(diskname => '/bigdisk'), 5,
-    "multi-part dump added and re-read successfully");
-
-__DATA__
-# a short-datestamp logfile with only a single, single-part file in it
-::: log.20080111.0
-:tapelist 20080111 Conf-001
-:timestamp 20080111000000
-DISK planner somebox /lib
-START planner date 20080111
-START driver date 20080111
-STATS driver hostname somebox
-STATS driver startup time 0.051
-FINISH planner date 20080111 time 82.721
-START taper datestamp 20080111 label Conf-001 tape 1
-SUCCESS dumper somebox /lib 20080111 0 [sec 0.209 kb 1970 kps 9382.2 orig-kb 1970]
-SUCCESS chunker somebox /lib 20080111 0 [sec 0.305 kb 420 kps 1478.7]
-STATS driver estimate somebox /lib 20080111 0 [sec 1 nkb 2002 ckb 480 kps 385]
-:dumpfile somebox_lib_20080111 20080111000000 somebox /lib 0 Conf-001 1 1 1 OK 4.813543 419
-PART taper Conf-001 1 somebox /lib 20080111 1/1 0 [sec 4.813543 kb 419 kps 87.133307]
-DONE taper somebox /lib 20080111 1 0 [sec 4.813543 kb 419 kps 87.133307]
-FINISH driver date 20080111 time 2167.581
-
-# a long-datestamp logfile, also fairly simple
-::: log.20080222222222.0
-:tapelist 20080222222222 Conf-002
-:timestamp 20080222222222
-DISK planner somebox /lib
-START planner date 20080222222222
-START driver date 20080222222222
-STATS driver hostname somebox
-STATS driver startup time 0.051
-FINISH planner date 20080222222222 time 0.102
-SUCCESS dumper somebox /lib 20080222222222 0 [sec 0.012 kb 100 kps 8115.6 orig-kb 100]
-SUCCESS chunker somebox /lib 20080222222222 0 [sec 5.075 kb 100 kps 26.0]
-STATS driver estimate somebox /lib 20080222222222 0 [sec 0 nkb 132 ckb 160 kps 1024]
-START taper datestamp 20080222222222 label Conf-002 tape 1
-:dumpfile somebox_lib_20080222222222 20080222222222 somebox /lib 0 Conf-002 1 1 1 OK 0.000733 100
-PART taper Conf-002 1 somebox /lib 20080222222222 1/1 0 [sec 0.000733 kb 100 kps 136425.648022]
-DONE taper somebox /lib 20080222222222 1 0 [sec 0.000733 kb 100 kps 136425.648022]
-FINISH driver date 20080222222222 time 6.206
-
-# a logfile with several dumps in it, one of which comes in many parts, and one of which is
-# from a previous run
-::: log.20080313133333.0
-:tapelist 20080313133333 Conf-003
-:timestamp 20080313133333
-DISK planner somebox /usr/bin
-DISK planner somebox /lib
-DISK planner otherbox /lib
-DISK planner otherbox /usr/bin
-START planner date 20080313133333
-START driver date 20080313133333
-STATS driver hostname somebox
-STATS driver startup time 0.059
-INFO planner Full dump of somebox:/lib promoted from 2 days ahead.
-FINISH planner date 20080313133333 time 0.286
-SUCCESS dumper somebox /usr/bin 20080313133333 1 [sec 0.001 kb 20 kps 10352.0 orig-kb 20]
-SUCCESS chunker somebox /usr/bin 20080313133333 1 [sec 1.023 kb 20 kps 50.8]
-STATS driver estimate somebox /usr/bin 20080313133333 1 [sec 0 nkb 52 ckb 64 kps 1024]
-START taper datestamp 20080313133333 label Conf-003 tape 1
-:dumpfile somebox_usr_bin_20080313133333 20080313133333 somebox /usr/bin 1 Conf-003 1 1 1 OK 0.000370 20
-PART taper Conf-003 1 somebox /usr/bin 20080313133333 1/1 1 [sec 0.000370 kb 20 kps 54054.054054]
-DONE taper somebox /usr/bin 20080313133333 1 1 [sec 0.000370 kb 20 kps 54054.054054]
-# a multi-part dump
-SUCCESS dumper somebox /lib 20080313133333 0 [sec 0.189 kb 3156 kps 50253.1 orig-kb 3156]
-SUCCESS chunker somebox /lib 20080313133333 0 [sec 5.250 kb 3156 kps 1815.5]
-STATS driver estimate somebox /lib 20080313133333 0 [sec 1 nkb 3156 ckb 3156 kps 9500]
-:dumpfile somebox_lib_20080313133333_p1 20080313133333 somebox /lib 0 Conf-003 2 1 4 OK 0.005621 1024
-PART taper Conf-003 2 somebox /lib 20080313133333 1/4 0 [sec 0.005621 kb 1024 kps 182173.990393]
-:dumpfile somebox_lib_20080313133333_p2 20080313133333 somebox /lib 0 Conf-003 3 2 4 OK 0.006527 1024
-PART taper Conf-003 3 somebox /lib 20080313133333 2/4 0 [sec 0.006527 kb 1024 kps 156886.777999]
-:dumpfile somebox_lib_20080313133333_p3 20080313133333 somebox /lib 0 Conf-003 4 3 4 OK 0.005854 1024
-PART taper Conf-003 4 somebox /lib 20080313133333 3/4 0 [sec 0.005854 kb 1024 kps 174923.129484]
-:dumpfile somebox_lib_20080313133333_p4 20080313133333 somebox /lib 0 Conf-003 5 4 4 OK 0.001919 284
-PART taper Conf-003 5 somebox /lib 20080313133333 4/4 0 [sec 0.001919 kb 284 kps 147993.746743]
-DONE taper somebox /lib 20080313133333 10 0 [sec 0.051436 kb 3156 kps 184695.543977]
-SUCCESS dumper otherbox /lib 20080313133333 0 [sec 0.001 kb 190 kps 10352.0 orig-kb 20]
-SUCCESS chunker otherbox /lib 20080313133333 0 [sec 1.023 kb 190 kps 50.8]
-STATS driver estimate otherbox /lib 20080313133333 0 [sec 0 nkb 190 ckb 190 kps 1024]
-# this dump is from a previous run, with an older dump_timestamp
-:dumpfile otherbox_usr_bin_20080313133333 20080311131133 otherbox /usr/bin 0 Conf-003 6 1 1 OK 0.002733 240
-PART taper Conf-003 6 otherbox /usr/bin 20080311131133 1/1 0 [sec 0.002733 kb 240 kps 136425.648022]
-:dumpfile otherbox_lib_20080313133333 20080313133333 otherbox /lib 0 Conf-003 7 1 1 OK 0.001733 190
-PART taper Conf-003 7 otherbox /lib 20080313133333 1/1 0 [sec 0.001733 kb 190 kps 136425.648022]
-DONE taper otherbox /lib 20080313133333 1 0 [sec 0.001733 kb 190 kps 136425.648022]
-FINISH driver date 20080313133333 time 24.777
-
-# A logfile with some partial parts (PARTPARTIAL) in it
-::: log.20080414144444.0
-:tapelist 20080414144444 Conf-004
-:tapelist 20080414144444 Conf-005
-:timestamp 20080414144444
-DISK planner otherbox /lib
-START planner date 20080414144444
-START driver date 20080414144444
-STATS driver hostname otherbox
-STATS driver startup time 0.075
-INFO taper Will write new label `Conf-004' to new (previously non-amanda) tape
-FINISH planner date 20080414144444 time 2.139
-SUCCESS dumper otherbox /lib 20080414144444 1 [sec 0.003 kb 60 kps 16304.3 orig-kb 60]
-SUCCESS chunker otherbox /lib 20080414144444 1 [sec 1.038 kb 60 kps 88.5]
-STATS driver estimate otherbox /lib 20080414144444 1 [sec 0 nkb 92 ckb 96 kps 1024]
-START taper datestamp 20080414144444 label Conf-004 tape 1
-:dumpfile otherbox_lib_20080414144444_try1 20080414144444 otherbox /lib 1 Conf-004 1 1 1 PARTIAL 0.000707 32
-PARTPARTIAL taper Conf-004 1 otherbox /lib 20080414144444 1/1 1 [sec 0.000707 kb 32 kps 45261.669024] ""
-INFO taper Will request retry of failed split part.
-INFO taper Will write new label `Conf-005' to new (previously non-amanda) tape
-START taper datestamp 20080414144444 label Conf-005 tape 2
-:dumpfile otherbox_lib_20080414144444_try2 20080414144444 otherbox /lib 1 Conf-005 1 1 1 PARTIAL 0.000540 32
-PARTPARTIAL taper Conf-005 1 otherbox /lib 20080414144444 1/1 1 [sec 0.000540 kb 32 kps 59259.259259] ""
-INFO taper Will request retry of failed split part.
-WARNING driver Out of tapes; going into degraded mode.
-PARTIAL taper otherbox /lib 20080414144444 1 1 [sec 0.000540 kb 32 kps 59259.259259] ""
-FINISH driver date 20080414144444 time 6.959