1 # Copyright (c) 2010 Zmanda, Inc. All Rights Reserved.
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.
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
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
16 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 package Installcheck::Catalogs;
23 Installcheck::Catalogs - manage catalog info that can be used to test
24 tools that do not need access to actual vtapes
28 use Installcheck::Catalogs;
29 my $cat = Installcheck::Catalogs::load("skipped");
31 my @tags = $cat->get_tags();
35 The C<load> method loads a named set of catalog information from catalog files.
37 The resulting object just decodes the catalog information into a perl
38 structure. To actually write the catalog to disk, use the C<install> method of
41 Note that many test catalogs require a configuration to be loaded; this package
42 does not handle loading configurations. However, the C<install> method does
43 take care of erasing the C<logs> subdirectory of the configuration directory as
44 well as any stray holding-disk files.
46 A catalog can have multiple, named snippets of text attached, as well. These
47 are accessed via the C<get_text($name)> method.
49 =head2 Database Results
51 The C<%H>, C<%P>, and C<%D> directives set up a "shadow database" of dumps and
52 parts that are represented by the catalog. These are available in two hashes,
53 one for dumps and one for parts, available from methods C<get_dumps> and
54 C<get_parts>. The hashes are keyed by "tags", which are arbitrary strings.
55 The dumps and parts are built to look like those produced by
56 L<Amanda::DB::Catalog>; in particular, a dump has keys
58 parts (list of parts indexed by partnum)
73 dump (points to the parent dump)
80 a part will also have a C<holding_file> key if it is, indeed, a holding
81 file. The C<holding_filename($tag)> method will return the filename of a
86 Each file in C<installcheck/catalogs> with the suffix C<.cat> represents a
87 cached catalog. Since the Amanda catalog consists of many files (curinfo,
88 trace logs, index, disklist, tapelist, etc.), each catalog acts as a
89 container for several other named files. The file is parsed in a line-based
90 fashion, with the following conventions:
94 =item A line beginning with C<#> is a comment, and is ignored
96 =item A line beginning with C<%F> begins a new output file, with the rest of
97 the line (after whitespace) interpreted as a filename relative to the TESTCONF
98 configuration directory. Any intervening directories required will be created.
100 =item A line beginning with C<%T> begins a new text section. This is simliar
101 to C<%F>, but instead of a filename, the rest of the line specifies a text
102 handle. The text will not be written to the filesystem on C<install>.
104 =item A line beginning with C<%H> specifies a holding-disk file. The rest of
105 the line is a space-separated list:
107 %H tag datestamp hostname pathname level status size
109 A single-chunk holding-disk file of the appropriate size will be created,
110 filled with garbage, and the corresponding entries will be made in the dump and
113 =item A line beginning with C<%D> specifies a dump. The format, all on one line, is:
115 %D tag dump_timestamp write_timestamp hostname diskname level status
116 message nparts sec kb orig_kb
118 =item A line beginning with C<%P> specifies a part. The format, again all on
121 %P tag dumptag label filenum partnum status sec kb orig_kb
123 where C<dumptag> is the tag of the dump of which this is a part.
125 =item A line beginning with C<%%> is a custom tag, intended for use by scripts
126 to define their expectations of the logfile. The results are available from
127 the C<get_tags> method.
129 =item A line beginning with C<\> is copied literally into the current output
130 file, without the leading C<\>.
132 =item Blank lines are ignored.
141 return Installcheck::Catalogs::Catalog->new($name);
144 package Installcheck::Catalogs::Catalog;
152 use Amanda::Xfer qw( :constants );
153 use File::Path qw( mkpath rmtree );
155 my $holdingdir = "$Installcheck::TMP/holding";
161 my $filename = "$srcdir/catalogs/$name.cat";
162 die "no catalog file '$filename'" unless -f $filename;
173 $self->_parse($filename);
184 open(my $fh, "<", $filename) or die "could not open '$filename'";
191 } elsif (/^(%[TF])\s*(.*)$/) {
192 my $cur_filename = $2;
193 my $kind = ($1 eq '%F')? 'files' : 'texts';
194 die "duplicate file '$cur_filename'"
195 if exists $self->{$kind}{$cur_filename};
196 $self->{$kind}{$cur_filename} = '';
197 $fileref = \$self->{$kind}{$cur_filename};
200 } elsif (/^%H (\S+) (\S+) (\S+) (\S+) (\d+) (\S+) (\d+)$/) {
202 die "dump tag $1 already exists" if exists $self->{'dumps'}{$1};
203 die "part tag $1 already exists" if exists $self->{'parts'}{$1};
206 $safe_disk =~ tr{/}{_};
207 my $hfile = "$holdingdir/$2/$3.$safe_disk";
209 $self->{'holding_files'}->{$1} = [ $hfile, $2, $3, $4, $5, $6, $7 ];
211 my $dump = $self->{'dumps'}{$1} = {
212 dump_timestamp => $2,
219 write_timestamp => '00000000000000',
224 my $part = $self->{'parts'}{$1} = {
225 holding_file => $hfile,
227 status => $dump->{'status'},
233 $dump->{'parts'} = [ undef, $part ];
236 } elsif (/^%D (\S+) (\d+) (\d+) (\S+) (\S+) (\d+) (\S+) (\S+) (\d+) (\S+) (\d+) (\d+)/) {
237 die "dump tag $1 already exists" if exists $self->{'dumps'}{$1};
238 my $dump = $self->{'dumps'}{$1} = {
239 dump_timestamp => $2,
240 write_timestamp => $3,
252 # translate "" to an empty string
253 $dump->{'message'} = '' if $dump->{'message'} eq '""';
256 } elsif (/^%P (\S+) (\S+) (\S+) (\d+) (\d+) (\S+) (\S+) (\d+) (\d+)/) {
257 die "part tag $1 already exists" if exists $self->{'parts'}{$1};
258 die "dump tag $2 does not exist" unless exists $self->{'dumps'}{$2};
260 my $part = $self->{'parts'}{$1} = {
261 dump => $self->{dumps}{$2},
270 $self->{'dumps'}->{$2}->{'parts'}->[$5] = $part;
273 } elsif (/^%%\s*(.*)$/) {
274 push @{$self->{'tags'}}, $1;
279 die "invalid processing instruction '$_'";
281 # contents of the file (\-escaped)
286 # contents of the file (copy)
293 sub _make_holding_file {
294 my ($filename, $datestamp, $hostname, $diskname, $level, $status, $size) = @_;
296 # make the parent dir
298 $dir =~ s{/[^/]*$}{};
301 # (note that multi-chunk holding files are not used at this point)
302 my $hdr = Amanda::Header->new();
303 $hdr->{'type'} = $Amanda::Header::F_DUMPFILE;
304 $hdr->{'datestamp'} = $datestamp;
305 $hdr->{'dumplevel'} = $level+0;
306 $hdr->{'name'} = $hostname;
307 $hdr->{'disk'} = $diskname;
308 $hdr->{'program'} = "INSTALLCHECK";
309 $hdr->{'is_partial'} = ($status ne 'OK');
311 open(my $fh, ">", $filename) or die("opening '$filename': $!");
312 $fh->syswrite($hdr->to_string(32768,32768));
314 # transfer some data to that file
315 my $xfer = Amanda::Xfer->new([
316 Amanda::Xfer::Source::Pattern->new(1024*$size, "+-+-+-+-"),
317 Amanda::Xfer::Dest::Fd->new($fh),
321 my ($src, $msg, $xfer) = @_;
322 if ($msg->{type} == $XMSG_ERROR) {
323 die $msg->{elt} . " failed: " . $msg->{message};
324 } elsif ($msg->{'type'} == $XMSG_DONE) {
326 Amanda::MainLoop::quit();
329 Amanda::MainLoop::run();
336 # first, remove the logdir
337 my $logdir = "$Amanda::Paths::CONFIG_DIR/TESTCONF/log";
338 rmtree($logdir) if -e $logdir;
340 # write the new config files
341 for my $filename (keys %{$self->{'files'}}) {
342 my $pathname = "$Amanda::Paths::CONFIG_DIR/TESTCONF/$filename";
343 my $dirname = $pathname;
344 $dirname =~ s{/[^/]+$}{};
346 mkpath($dirname) unless -d $dirname;
347 Amanda::Util::burp($pathname, $self->{'files'}{$filename});
350 # erase holding and create some new holding files
352 for my $hldinfo (values %{$self->{'holding_files'}}) {
353 _make_holding_file(@$hldinfo);
359 return @{$self->{'tags'}};
364 return %{$self->{'dumps'}};
369 return %{$self->{'parts'}};
376 return $self->{'texts'}->{$name};
383 return $self->{'files'}->{$name};
386 sub holding_filename {
390 my $fn = $self->{'holding_files'}{$tag}[0];