1 # Copyright (c) 2007-2012 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 use Test::More tests => 39;
24 use lib "@amperldir@";
26 use Installcheck::Config;
32 use Amanda::Debug qw( :logging );
33 use Amanda::Logfile qw(:logtype_t :program_t open_logfile get_logline
34 close_logfile log_add $amanda_log_trace_log );
35 use Amanda::Config qw( :init :getconf config_dir_relative );
37 Amanda::Debug::dbopen("installcheck");
39 my $log_filename = "$Installcheck::TMP/Amanda_Logfile_test.log";
41 # write a logfile and return the filename
45 if (!-e $Installcheck::TMP) {
46 mkpath($Installcheck::TMP);
49 open my $logfile, ">", $log_filename or die("Could not create temporary log file '$log_filename': $!");
50 print $logfile $contents;
63 # Test out the constant functions
65 is(logtype_t_to_string($L_MARKER), "L_MARKER", "logtype_t_to_string works");
66 is(program_t_to_string($P_DRIVER), "P_DRIVER", "program_t_to_string works");
69 # Regression test for previously missing program types
70 is( program_t_to_string($P_AMDUMP),
71 "P_AMDUMP", "program type for amdump defined" );
72 is( program_t_to_string($P_AMIDXTAPED),
73 "P_AMIDXTAPED", "program type for amidxtaped defined" );
74 is( program_t_to_string($P_AMFETCHDUMP),
75 "P_AMFETCHDUMP", "program type for amfetchdump defined" );
76 is( program_t_to_string($P_AMCHECKDUMP),
77 "P_AMCHECKDUMP", "program type for amcheckdump defined" );
80 # Test a simple logfile
83 START planner date 20071026183200
86 $logfile = open_logfile(write_logfile($logdata));
87 ok($logfile, "can open a simple logfile");
88 is_deeply([ get_logline($logfile) ],
89 [ $L_START, $P_PLANNER, "date 20071026183200" ],
90 "reads START line correctly");
91 ok(!get_logline($logfile), "no second line");
92 close_logfile($logfile);
95 # Test continuation lines
102 $logfile = open_logfile(write_logfile($logdata));
103 ok($logfile, "can open a logfile containing continuation lines");
104 is_deeply([ get_logline($logfile) ],
105 [ $L_INFO, $P_CHUNKER, "line1" ],
106 "can read INFO line");
107 is_deeply([ get_logline($logfile) ],
108 [ $L_CONT, $P_CHUNKER, "line2" ],
109 "can read continuation line");
110 ok(!get_logline($logfile), "no third line");
111 close_logfile($logfile);
114 # Test skipping blank lines
116 # (retain the two blank lines in the following:)
123 $logfile = open_logfile(write_logfile($logdata));
124 ok($logfile, "can open a logfile containing blank lines");
125 is_deeply([ get_logline($logfile) ],
126 [ $L_STATS, $P_TAPER, "foo" ],
127 "reads non-blank line correctly");
128 ok(!get_logline($logfile), "no second line");
129 close_logfile($logfile);
132 # Test BOGUS values and short lines
135 SOMETHINGWEIRD somerandomprog bar
137 MARKER amflush put something in curstr
141 $logfile = open_logfile(write_logfile($logdata));
142 ok($logfile, "can open a logfile containing bogus entries");
143 is_deeply([ get_logline($logfile) ],
144 [ $L_BOGUS, $P_UNKNOWN, "bar" ],
145 "can read line with bogus program and logtype");
146 is_deeply([ get_logline($logfile) ],
147 [ $L_MARKER, $P_AMFLUSH, "" ],
148 "can read line with an empty string");
149 ok(get_logline($logfile), "can read third line (to fill in curstr with some text)");
150 is_deeply([ get_logline($logfile) ],
151 [ $L_PART, $P_UNKNOWN, "" ],
152 "can read a one-word line, with P_UNKNOWN");
153 ok(!get_logline($logfile), "no next line");
154 close_logfile($logfile);
156 ## HIGHER-LEVEL FUNCTIONS
158 # a utility function for is_deeply checks, below. Converts a hash to
159 # an array, for more succinct comparisons
170 $res->{'dump_status'},
173 "$res->{'totalparts'}"
177 # set up a basic config
178 my $testconf = Installcheck::Config->new();
179 $testconf->add_param("tapecycle", "20");
183 config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF") == $CFGERR_OK
184 or die("Could not load config");
185 my $tapelist = config_dir_relative("tapelist");
186 my $logdir = $testconf->{'logdir'};
190 my $filename = "$logdir/log";
192 -f "$filename" and unlink("$filename");
193 log_add($L_INFO, "This is my info");
194 log_add($L_START, "blah blah blah date 20300405060708 blah blah");
196 open(my $fh, "<", $filename) or die("open $filename: $!");
197 my $logdata = do { local $/; <$fh> };
200 like($logdata, qr/^INFO Amanda_Logfile This is my info/, "log_add works");
202 is(Amanda::Logfile::get_current_log_timestamp(), "20300405060708",
203 "get_current_log_timestamp finds a timestamp");
205 Amanda::Logfile::log_rename("20300405060708");
207 ok(! -f $filename, "after log_rename, /log is gone");
208 ok(-f "$filename.20300405060708.0", "..and log.20300405060708.0 exists");
211 # set up and read the tapelist (we don't use Amanda::Tapelist to write this,
212 # in case it's broken)
213 open my $tlf, ">", $tapelist or die("Could not write tapelist");
214 print $tlf "20071111010002 TESTCONF006 reuse\n";
215 print $tlf "20071110010002 TESTCONF005 reuse\n";
216 print $tlf "20071109010002 TESTCONF004 reuse\n";
217 print $tlf "20071109010002 TESTCONF003 reuse\n";
218 print $tlf "20071109010002 TESTCONF002 reuse\n";
219 print $tlf "20071108010001 TESTCONF001 reuse\n";
221 Amanda::Tapelist->new($tapelist);
223 # set up a number of logfiles in logdir.
226 # (an old log file that should be ignored)
227 open $logf, ">", "$logdir/log.20071106010002.0" or die("Could not write logfile");
228 print $logf "START taper datestamp 20071107010002 label TESTCONF017 tape 1\n";
231 # (a logfile with two tapes)
232 open $logf, ">", "$logdir/log.20071106010002.0" or die("Could not write logfile");
233 print $logf "START taper datestamp 20071106010002 label TESTCONF018 tape 1\n";
234 print $logf "START taper datestamp 20071106010002 label TESTCONF019 tape 2\n";
237 open $logf, ">", "$logdir/log.20071108010001.0" or die("Could not write logfile");
238 print $logf "START taper datestamp 20071108010001 label TESTCONF001 tape 1\n";
241 # a logfile with some detail, to run search_logfile against
242 open $logf, ">", "$logdir/log.20071109010002.0" or die("Could not write logfile");
244 START taper datestamp 20071109010002 label TESTCONF002 tape 1
245 PART taper TESTCONF002 1 clihost /usr 20071109010002 1 0 [regular single part PART]
246 DONE taper clihost /usr 20071109010002 1 0 [regular single part DONE]
247 PART taper TESTCONF002 2 clihost "/my documents" 20071109010002 1 0 [diskname quoting]
248 DONE taper clihost "/my documents" 20071109010002 1 0 [diskname quoting]
249 PART taper TESTCONF002 3 thatbox /var 1 [regular 'old style' PART]
250 DONE taper thatbox /var 1 [regular 'old style' DONE]
251 PART taper TESTCONF002 4 clihost /home 20071109010002 1/5 0 [multi-part dump]
252 PART taper TESTCONF002 5 clihost /home 20071109010002 2/5 0 [multi-part dump]
253 PART taper TESTCONF002 6 clihost /home 20071109010002 3/5 0 [multi-part dump]
254 PART taper TESTCONF002 7 clihost /home 20071109010002 4/5 0 [multi-part dump]
255 PART taper TESTCONF002 8 clihost /home 20071109010002 5/5 0 [multi-part dump]
256 DONE taper clihost /home 20071109010002 5 0 [multi-part dump]
257 PART taper TESTCONF002 9 thatbox /u_lose 20071109010002 1/4 2 [multi-part failure]
258 PART taper TESTCONF002 10 thatbox /u_lose 20071109010002 2/4 2 [multi-part failure]
259 PARTPARTIAL taper TESTCONF002 11 thatbox /u_lose 20071109010002 3/4 2 [multi-part retry]
260 START taper datestamp 20071109010002 label TESTCONF003 tape 1
261 PART taper TESTCONF003 1 thatbox /u_lose 20071109010002 3/4 2 [multi-part failure]
262 FAIL taper thatbox /u_lose 20071109010002 2 error "Oh no!"
263 PART taper TESTCONF003 2 thatbox /u_win 20071109010002 1/4 3 [multi-part retry]
264 PART taper TESTCONF003 3 thatbox /u_win 20071109010002 2/4 3 [multi-part retry]
265 PARTPARTIAL taper TESTCONF003 4 thatbox /u_win 20071109010002 3/4 3 [multi-part retry]
266 START taper datestamp 20071109010002 label TESTCONF004 tape 1
267 PART taper TESTCONF004 1 thatbox /u_win 20071109010002 3/4 3 [multi-part retry]
268 PART taper TESTCONF004 2 thatbox /u_win 20071109010002 4/4 3 [multi-part retry]
269 DONE taper thatbox /u_win 20071109010002 4 3 [multi-part retry]
273 # "old-style amflush log"
274 open $logf, ">", "$logdir/log.20071110010002.amflush" or die("Could not write logfile");
275 print $logf "START taper datestamp 20071110010002 label TESTCONF005 tape 1\n";
278 # "old-style main log"
279 open $logf, ">", "$logdir/log.20071111010002" or die("Could not write logfile");
280 print $logf "START taper datestamp 20071111010002 label TESTCONF006 tape 1\n";
283 is_deeply([ Amanda::Logfile::find_log() ],
284 [ "log.20071111010002", "log.20071110010002.amflush",
285 "log.20071109010002.0", "log.20071108010001.0" ],
286 "find_log returns correct logfiles in the correct order");
291 @results = Amanda::Logfile::search_logfile(undef, "20071109010002",
292 "$logdir/log.20071109010002.0", 1);
293 is($#results+1, 17, "search_logfile returned 17 results");
295 # sort by filenum so we can compare each to what it should be
296 @results = sort { $a->{'label'} cmp $b->{'label'} ||
297 $a->{'filenum'} <=> $b->{'filenum'} } @results;
299 # and convert the hashes to arrays for easy comparison
300 @results_arr = map { res2arr($_) } @results;
301 is_deeply(\@results_arr,
303 [ '20071109010002', 'clihost', '/usr', 0, 'TESTCONF002', 1, 'OK', 'OK', '', 1, 1 ],
304 [ '20071109010002', 'clihost', '/my documents', 0, 'TESTCONF002', 2, 'OK', 'OK', '', 1, 1 ],
305 [ '20071109010002', 'thatbox', '/var', 1, 'TESTCONF002', 3, 'OK', 'OK', '', 1, 1 ],
306 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 4, 'OK', 'OK', '', 1, 5 ],
307 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 5, 'OK', 'OK', '', 2, 5 ],
308 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 6, 'OK', 'OK', '', 3, 5 ],
309 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 7, 'OK', 'OK', '', 4, 5 ],
310 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 8, 'OK', 'OK', '', 5, 5 ],
311 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 9, 'OK', 'FAIL', '"Oh no!"', 1, 4 ],
312 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 10, 'OK', 'FAIL', '"Oh no!"', 2, 4 ],
313 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 11, 'PARTIAL', 'FAIL', '"Oh no!"', 3, 4 ],
314 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF003', 1, 'OK', 'FAIL', '"Oh no!"', 3, 4 ],
315 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 2, 'OK', 'OK', '', 1, 4 ],
316 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 3, 'OK', 'OK', '', 2, 4 ],
317 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 4, 'PARTIAL', 'OK', '', 3, 4 ],
318 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF004', 1, 'OK', 'OK', '', 3, 4 ],
319 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF004', 2, 'OK', 'OK', '', 4, 4 ],
320 ], "results are correct");
325 @filtered = Amanda::Logfile::dumps_match([@results], "thatbox", undef, undef, undef, 0);
326 is($#filtered+1, 10, "ten results match 'thatbox'");
327 @filtered = sort { $a->{'label'} cmp $b->{'label'} ||
328 $a->{'filenum'} <=> $b->{'filenum'} } @filtered;
330 @filtered_arr = map { res2arr($_) } @filtered;
332 is_deeply(\@filtered_arr,
334 [ '20071109010002', 'thatbox', '/var', 1, 'TESTCONF002', 3, 'OK', 'OK', '', 1, 1 ],
335 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 9, 'OK', 'FAIL', '"Oh no!"', 1, 4 ],
336 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 10, 'OK', 'FAIL', '"Oh no!"', 2, 4 ],
337 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 11, 'PARTIAL', 'FAIL', '"Oh no!"', 3, 4 ],
338 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF003', 1, 'OK', 'FAIL', '"Oh no!"', 3, 4 ],
339 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 2, 'OK', 'OK', '', 1, 4 ],
340 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 3, 'OK', 'OK', '', 2, 4 ],
341 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 4, 'PARTIAL', 'OK', '', 3, 4 ],
342 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF004', 1, 'OK', 'OK', '', 3, 4 ],
343 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF004', 2, 'OK', 'OK', '', 4, 4 ],
344 ], "results are correct");
346 @filtered = Amanda::Logfile::dumps_match([@results], "thatbox", "/var", undef, undef, 0);
347 is($#filtered+1, 1, "only one result matches 'thatbox:/var'");
349 @filtered = Amanda::Logfile::dumps_match([@results], undef, undef, "20071109010002", undef, 0);
350 is($#filtered+1, 17, "all 17 results match '20071109010002'");
352 @filtered = Amanda::Logfile::dumps_match([@results], undef, undef, "20071109010002", undef, 1);
353 is($#filtered+1, 12, "of those, 12 results are 'OK'");
355 @filtered = Amanda::Logfile::dumps_match([@results], undef, undef, undef, "2", 0);
356 is($#filtered+1, 4, "4 results are at level 2");
358 # test dumps_match_dumpspecs
362 @dumpspecs = Amanda::Cmdline::parse_dumpspecs(["thatbox", "/var"], 0);
363 @filtered = Amanda::Logfile::dumps_match_dumpspecs([@results], [@dumpspecs], 0);
364 is_deeply([ map { res2arr($_) } @filtered ],
366 [ '20071109010002', 'thatbox', '/var', 1, 'TESTCONF002', 3, 'OK', 'OK', '', 1, 1 ],
367 ], "filter with dumpspecs 'thatbox /var'");
369 @dumpspecs = Amanda::Cmdline::parse_dumpspecs(["thatbox", "/var", "clihost"], 0);
370 @filtered = Amanda::Logfile::dumps_match_dumpspecs([@results], [@dumpspecs], 0);
371 @filtered = sort { $a->{'label'} cmp $b->{'label'} ||
372 $a->{'filenum'} <=> $b->{'filenum'} } @filtered;
373 is_deeply([ map { res2arr($_) } @filtered ],
375 [ '20071109010002', 'clihost', '/usr', 0, 'TESTCONF002', 1, 'OK', 'OK', '', 1, 1 ],
376 [ '20071109010002', 'clihost', '/my documents', 0, 'TESTCONF002', 2, 'OK', 'OK', '', 1, 1 ],
377 [ '20071109010002', 'thatbox', '/var', 1, 'TESTCONF002', 3, 'OK', 'OK', '', 1, 1 ],
378 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 4, 'OK', 'OK', '', 1, 5 ],
379 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 5, 'OK', 'OK', '', 2, 5 ],
380 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 6, 'OK', 'OK', '', 3, 5 ],
381 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 7, 'OK', 'OK', '', 4, 5 ],
382 [ '20071109010002', 'clihost', '/home', 0, 'TESTCONF002', 8, 'OK', 'OK', '', 5, 5 ],
383 ], "filter with dumpspecs 'thatbox /var clihost' (union of two disjoint sets)");
385 # if multiple dumpspecs specify the same dump, it will be included in the output multiple times
386 @dumpspecs = Amanda::Cmdline::parse_dumpspecs([".*", "/var", "thatbox"], 0);
387 @filtered = Amanda::Logfile::dumps_match_dumpspecs([@results], [@dumpspecs], 0);
388 @filtered = sort { $a->{'label'} cmp $b->{'label'} ||
389 $a->{'filenum'} <=> $b->{'filenum'} } @filtered;
390 is_deeply([ map { res2arr($_) } @filtered ],
392 [ '20071109010002', 'thatbox', '/var', 1, 'TESTCONF002', 3, 'OK', 'OK' , '', 1, 1 ],
393 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 9, 'OK', 'FAIL', '"Oh no!"', 1, 4 ],
394 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 10, 'OK', 'FAIL', '"Oh no!"', 2, 4 ],
395 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 11, 'PARTIAL', 'FAIL', '"Oh no!"', 3, 4 ],
396 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF003', 1, 'OK', 'FAIL', '"Oh no!"', 3, 4 ],
397 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 2, 'OK', 'OK' , '', 1, 4 ],
398 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 3, 'OK', 'OK' , '', 2, 4 ],
399 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 4, 'PARTIAL', 'OK' , '', 3, 4 ],
400 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF004', 1, 'OK', 'OK' , '', 3, 4 ],
401 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF004', 2, 'OK', 'OK' , '', 4, 4 ],
402 ], "filter with dumpspecs '.* /var thatbox' (union of two overlapping sets includes dupes)");
404 @dumpspecs = Amanda::Cmdline::dumpspec_t->new('thatbox', undef, undef, undef, '20071109010002');
405 @filtered = Amanda::Logfile::dumps_match_dumpspecs([@results], [@dumpspecs], 0);
406 @filtered = sort { $a->{'label'} cmp $b->{'label'} ||
407 $a->{'filenum'} <=> $b->{'filenum'} } @filtered;
408 is_deeply([ map { res2arr($_) } @filtered ],
410 [ '20071109010002', 'thatbox', '/var', 1, 'TESTCONF002', 3, 'OK', 'OK' , '', 1, 1 ],
411 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 9, 'OK', 'FAIL', '"Oh no!"', 1, 4 ],
412 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 10, 'OK', 'FAIL', '"Oh no!"', 2, 4 ],
413 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF002', 11, 'PARTIAL', 'FAIL', '"Oh no!"', 3, 4 ],
414 [ '20071109010002', 'thatbox', '/u_lose', 2, 'TESTCONF003', 1, 'OK', 'FAIL', '"Oh no!"', 3, 4 ],
415 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 2, 'OK', 'OK' , '', 1, 4 ],
416 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 3, 'OK', 'OK' , '', 2, 4 ],
417 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF003', 4, 'PARTIAL', 'OK' , '', 3, 4 ],
418 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF004', 1, 'OK', 'OK' , '', 3, 4 ],
419 [ '20071109010002', 'thatbox', '/u_win', 3, 'TESTCONF004', 2, 'OK', 'OK' , '', 4, 4 ],
420 ], "filter with dumpspecs with host 'thatbox' and a write_timestamp");
421 unlink($log_filename);
423 # search_holding_disk and match_* are tested via Amanda::DB::Catalog's installcheck