1 # Copyright (c) 2010-2012 Zmanda, Inc. All Rights Reserved.
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 package Installcheck::Catalogs;
24 Installcheck::Catalogs - manage catalog info that can be used to test
25 tools that do not need access to actual vtapes
29 use Installcheck::Catalogs;
30 my $cat = Installcheck::Catalogs::load("skipped");
32 my @tags = $cat->get_tags();
36 The C<load> method loads a named set of catalog information from catalog files.
38 The resulting object just decodes the catalog information into a perl
39 structure. To actually write the catalog to disk, use the C<install> method of
42 Note that many test catalogs require a configuration to be loaded; this package
43 does not handle loading configurations. However, the C<install> method does
44 take care of erasing the C<logs> subdirectory of the configuration directory as
45 well as any stray holding-disk files.
47 A catalog can have multiple, named snippets of text attached, as well. These
48 are accessed via the C<get_text($name)> method.
50 =head2 Database Results
52 The C<%H>, C<%P>, and C<%D> directives set up a "shadow database" of dumps and
53 parts that are represented by the catalog. These are available in two hashes,
54 one for dumps and one for parts, available from methods C<get_dumps> and
55 C<get_parts>. The hashes are keyed by "tags", which are arbitrary strings.
56 The dumps and parts are built to look like those produced by
57 L<Amanda::DB::Catalog>; in particular, a dump has keys
59 parts (list of parts indexed by partnum)
74 dump (points to the parent dump)
81 a part will also have a C<holding_file> key if it is, indeed, a holding
82 file. The C<holding_filename($tag)> method will return the filename of a
87 Each file in C<installcheck/catalogs> with the suffix C<.cat> represents a
88 cached catalog. Since the Amanda catalog consists of many files (curinfo,
89 trace logs, index, disklist, tapelist, etc.), each catalog acts as a
90 container for several other named files. The file is parsed in a line-based
91 fashion, with the following conventions:
95 =item A line beginning with C<#> is a comment, and is ignored
97 =item A line beginning with C<%F> begins a new output file, with the rest of
98 the line (after whitespace) interpreted as a filename relative to the TESTCONF
99 configuration directory. Any intervening directories required will be created.
101 =item A line beginning with C<%T> begins a new text section. This is simliar
102 to C<%F>, but instead of a filename, the rest of the line specifies a text
103 handle. The text will not be written to the filesystem on C<install>.
105 =item A line beginning with C<%H> specifies a holding-disk file. The rest of
106 the line is a space-separated list:
108 %H tag datestamp hostname pathname level status size
110 A single-chunk holding-disk file of the appropriate size will be created,
111 filled with garbage, and the corresponding entries will be made in the dump and
114 =item A line beginning with C<%D> specifies a dump. The format, all on one line, is:
116 %D tag dump_timestamp write_timestamp hostname diskname level status
117 message nparts sec kb orig_kb
119 =item A line beginning with C<%P> specifies a part. The format, again all on
122 %P tag dumptag label filenum partnum status sec kb orig_kb
124 where C<dumptag> is the tag of the dump of which this is a part.
126 =item A line beginning with C<%%> is a custom tag, intended for use by scripts
127 to define their expectations of the logfile. The results are available from
128 the C<get_tags> method.
130 =item A line beginning with C<\> is copied literally into the current output
131 file, without the leading C<\>.
133 =item Blank lines are ignored.
142 return Installcheck::Catalogs::Catalog->new($name);
145 package Installcheck::Catalogs::Catalog;
153 use Amanda::Xfer qw( :constants );
154 use File::Path qw( mkpath rmtree );
156 my $holdingdir = "$Installcheck::TMP/holding";
162 my $filename = "$srcdir/catalogs/$name.cat";
163 die "no catalog file '$filename'" unless -f $filename;
174 $self->_parse($filename);
185 open(my $fh, "<", $filename) or die "could not open '$filename'";
192 } elsif (/^(%[TF])\s*(.*)$/) {
193 my $cur_filename = $2;
194 my $kind = ($1 eq '%F')? 'files' : 'texts';
195 die "duplicate file '$cur_filename'"
196 if exists $self->{$kind}{$cur_filename};
197 $self->{$kind}{$cur_filename} = '';
198 $fileref = \$self->{$kind}{$cur_filename};
201 } elsif (/^%H (\S+) (\S+) (\S+) (\S+) (\d+) (\S+) (\d+)$/) {
203 die "dump tag $1 already exists" if exists $self->{'dumps'}{$1};
204 die "part tag $1 already exists" if exists $self->{'parts'}{$1};
207 $safe_disk =~ tr{/}{_};
208 my $hfile = "$holdingdir/$2/$3.$safe_disk";
210 $self->{'holding_files'}->{$1} = [ $hfile, $2, $3, $4, $5, $6, $7 ];
212 my $dump = $self->{'dumps'}{$1} = {
213 dump_timestamp => $2,
220 write_timestamp => '00000000000000',
225 my $part = $self->{'parts'}{$1} = {
226 holding_file => $hfile,
228 status => $dump->{'status'},
234 $dump->{'parts'} = [ undef, $part ];
237 } elsif (/^%D (\S+) (\d+) (\d+) (\S+) (\S+) (\d+) (\S+) (\S+) (\d+) (\S+) (\d+) (\d+)/) {
238 die "dump tag $1 already exists" if exists $self->{'dumps'}{$1};
239 my $dump = $self->{'dumps'}{$1} = {
240 dump_timestamp => $2,
241 write_timestamp => $3,
253 # translate "" to an empty string
254 $dump->{'message'} = '' if $dump->{'message'} eq '""';
257 } elsif (/^%P (\S+) (\S+) (\S+) (\d+) (\d+) (\S+) (\S+) (\d+) (\d+)/) {
258 die "part tag $1 already exists" if exists $self->{'parts'}{$1};
259 die "dump tag $2 does not exist" unless exists $self->{'dumps'}{$2};
261 my $part = $self->{'parts'}{$1} = {
262 dump => $self->{dumps}{$2},
271 $self->{'dumps'}->{$2}->{'parts'}->[$5] = $part;
274 } elsif (/^%%\s*(.*)$/) {
275 push @{$self->{'tags'}}, $1;
280 die "invalid processing instruction '$_'";
282 # contents of the file (\-escaped)
287 # contents of the file (copy)
294 sub _make_holding_file {
295 my ($filename, $datestamp, $hostname, $diskname, $level, $status, $size) = @_;
297 # make the parent dir
299 $dir =~ s{/[^/]*$}{};
302 # (note that multi-chunk holding files are not used at this point)
303 my $hdr = Amanda::Header->new();
304 $hdr->{'type'} = $Amanda::Header::F_DUMPFILE;
305 $hdr->{'datestamp'} = $datestamp;
306 $hdr->{'dumplevel'} = $level+0;
307 $hdr->{'name'} = $hostname;
308 $hdr->{'disk'} = $diskname;
309 $hdr->{'program'} = "INSTALLCHECK";
310 $hdr->{'is_partial'} = ($status ne 'OK');
312 open(my $fh, ">", $filename) or die("opening '$filename': $!");
313 $fh->syswrite($hdr->to_string(32768,32768));
315 # transfer some data to that file
316 my $xfer = Amanda::Xfer->new([
317 Amanda::Xfer::Source::Pattern->new(1024*$size, "+-+-+-+-"),
318 Amanda::Xfer::Dest::Fd->new($fh),
322 my ($src, $msg, $xfer) = @_;
323 if ($msg->{type} == $XMSG_ERROR) {
324 die $msg->{elt} . " failed: " . $msg->{message};
325 } elsif ($msg->{'type'} == $XMSG_DONE) {
327 Amanda::MainLoop::quit();
330 Amanda::MainLoop::run();
337 # first, remove the logdir
338 my $logdir = "$Amanda::Paths::CONFIG_DIR/TESTCONF/log";
339 rmtree($logdir) if -e $logdir;
341 # write the new config files
342 for my $filename (keys %{$self->{'files'}}) {
343 my $pathname = "$Amanda::Paths::CONFIG_DIR/TESTCONF/$filename";
344 my $dirname = $pathname;
345 $dirname =~ s{/[^/]+$}{};
347 mkpath($dirname) unless -d $dirname;
348 Amanda::Util::burp($pathname, $self->{'files'}{$filename});
351 # erase holding and create some new holding files
353 for my $hldinfo (values %{$self->{'holding_files'}}) {
354 _make_holding_file(@$hldinfo);
360 return @{$self->{'tags'}};
365 return %{$self->{'dumps'}};
370 return %{$self->{'parts'}};
377 return $self->{'texts'}->{$name};
384 return $self->{'files'}->{$name};
387 sub holding_filename {
391 my $fn = $self->{'holding_files'}{$tag}[0];