1 # Copyright (c) 2005-2008 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 Mathlida Ave, Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 use Test::More tests => 113;
22 use lib "@amperldir@";
23 use Installcheck::Config;
26 use Amanda::Config qw( :init :getconf );
30 my $config_overwrites;
32 Amanda::Debug::dbopen("installcheck");
36 sub diag_config_errors {
37 my ($level, @errors) = Amanda::Config::config_errors();
38 for my $errmsg (@errors) {
44 # Try starting with no configuration at all
46 is(config_init(0, ''), $CFGERR_OK,
47 "Initialize with no configuration")
48 or diag_config_errors();
50 is(config_init(0, undef), $CFGERR_OK,
51 "Initialize with no configuration, passing a NULL config name")
52 or diag_config_errors();
54 $config_overwrites = new_config_overwrites(1);
55 add_config_overwrite($config_overwrites, "tapedev", "null:TEST");
56 apply_config_overwrites($config_overwrites);
58 is(getconf($CNF_TAPEDEV), "null:TEST",
59 "config overwrites work with null config");
62 # Check out error handling
64 $testconf = Installcheck::Config->new();
65 $testconf->add_param('rawtapedev', '"/dev/medium-rare-please"'); # a deprecated keyword -> warning
69 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF"), $CFGERR_WARNINGS,
70 "Deprecated keyword generates a warning");
71 my ($error_level, @errors) = Amanda::Config::config_errors();
72 like($errors[0], qr/is deprecated/,
73 "config_get_errors returns the warning string");
75 Amanda::Config::config_clear_errors();
76 ($error_level, @errors) = Amanda::Config::config_errors();
77 is(scalar(@errors), 0, "config_clear_errors clears error list");
80 $testconf = Installcheck::Config->new();
81 $testconf->add_param('invalid-param', 'random-value'); # a deprecated keyword -> warning
84 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "NO-SUCH-CONFIGURATION"), $CFGERR_ERRORS,
85 "Non-existent config generates an error");
87 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF"), $CFGERR_ERRORS,
88 "Invalid keyword generates an error");
91 # try a client configuration
93 $testconf = Installcheck::Config->new();
94 $testconf->add_client_param('property', '"client-prop" "yep"');
95 $testconf->add_client_param('property', 'priority "client-prop1" "foo"');
96 $testconf->add_client_param('property', 'append "client-prop" "bar"');
99 my $cfg_result = config_init($CONFIG_INIT_CLIENT, undef);
100 is($cfg_result, $CFGERR_OK,
101 "Load test client configuration")
102 or diag_config_errors();
104 is_deeply(getconf($CNF_PROPERTY), { "client-prop1" => { priority => 1,
106 values => [ "foo" ]},
107 "client-prop" => { priority => 0,
109 values => [ "yep", "bar" ] }},
110 "Client PROPERTY parameter parsed correctly");
113 # Parse up a basic configuration
115 # invent a "large" unsigned number, and make $size_t_num
116 # depend on the length of size_t
117 my $int64_num = '171801575472'; # 0xA000B000C000 / 1024
119 if (Amanda::Tests::sizeof_size_t() > 4) {
120 $size_t_num = $int64_num;
122 $size_t_num = '2147483647'; # 0x7fffffff
125 $testconf = Installcheck::Config->new();
126 $testconf->add_param('reserve', '75');
127 $testconf->add_param('autoflush', 'yes');
128 $testconf->add_param('tapedev', '"/dev/foo"');
129 $testconf->add_param('bumpsize', $int64_num);
130 $testconf->add_param('bumpmult', '1.4');
131 $testconf->add_param('reserved_udp-port', '100,200'); # note use of '-' and '_'
132 $testconf->add_param('device_output_buffer_size', $size_t_num);
133 $testconf->add_param('taperalgo', 'last');
134 $testconf->add_param('device_property', '"foo" "bar"');
135 $testconf->add_param('device_property', '"blue" "car" "tar"');
136 $testconf->add_param('displayunit', '"m"');
137 $testconf->add_param('debug_auth', '1');
138 $testconf->add_tapetype('mytapetype', [
139 'comment' => '"mine"',
142 $testconf->add_dumptype('mydump-type', [ # note dash
143 'comment' => '"mine"',
144 'priority' => 'high', # == 2
145 'bumpsize' => $int64_num,
148 'holdingdisk' => 'required',
149 'compress' => 'client best',
150 'encrypt' => 'server',
151 'strategy' => 'incronly',
152 'comprate' => '0.25,0.75',
153 'exclude list' => '"foo" "bar"',
154 'exclude list append' => '"true" "star"',
155 'exclude file' => '"foolist"',
156 'include list' => '"bing" "ting"',
157 'include list append' => '"string" "fling"',
158 'include file optional' => '"rhyme"',
159 'property' => '"prop" "erty"',
160 'property' => '"drop" "qwerty" "asdfg"',
162 $testconf->add_dumptype('second_dumptype', [ # note underscore
164 'comment' => '"refers to mydump-type with a dash"',
166 $testconf->add_dumptype('third_dumptype', [
167 '' => 'second_dumptype',
168 'comment' => '"refers to second_dumptype with an underscore"',
170 $testconf->add_interface('ethernet', [
171 'comment' => '"mine"',
174 $testconf->add_interface('nic', [
175 'comment' => '"empty"',
177 $testconf->add_holdingdisk('hd1', [
178 'comment' => '"mine"',
179 'directory' => '"/mnt/hd1"',
181 'chunksize' => '1024k',
183 $testconf->add_holdingdisk('hd2', [
184 'comment' => '"empty"',
186 $testconf->add_application('my_app', [
187 'comment' => '"my_app_comment"',
188 'plugin' => '"amgtar"',
190 $testconf->add_script('my_script', [
191 'comment' => '"my_script_comment"',
192 'plugin' => '"script-email"',
193 'execute-on' => 'pre-host-backup, post-host-backup',
194 'execute-where' => 'client',
195 'property' => '"mailto" "amandabackup" "amanda"',
197 $testconf->add_device('my_device', [
198 'comment' => '"my device is mine, not yours"',
199 'tapedev' => '"tape:/dev/nst0"',
200 'device_property' => '"BLOCK_SIZE" "128k"',
202 $testconf->add_changer('my_changer', [
203 'comment' => '"my changer is mine, not yours"',
204 'tpchanger' => '"chg-foo"',
205 'changerdev' => '"/dev/sg0"',
206 'changerfile' => '"chg.state"',
211 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
212 is($cfg_result, $CFGERR_OK,
213 "Load test configuration")
214 or diag_config_errors();
217 skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
219 is(Amanda::Config::get_config_name(), "TESTCONF",
221 is(Amanda::Config::get_config_dir(), "$CONFIG_DIR/TESTCONF",
223 is(Amanda::Config::get_config_filename(),
224 "$CONFIG_DIR/TESTCONF/amanda.conf",
225 "config_filename set");
228 SKIP: { # global parameters
229 skip "error loading config", 11 unless $cfg_result == $CFGERR_OK;
231 is(getconf($CNF_RESERVE), 75,
232 "integer global confparm");
233 is(getconf($CNF_BUMPSIZE), $int64_num+0,
234 "int64 global confparm");
235 is(getconf($CNF_TAPEDEV), "/dev/foo",
236 "string global confparm");
237 is(getconf($CNF_DEVICE_OUTPUT_BUFFER_SIZE), $size_t_num+0,
238 "size global confparm");
239 ok(getconf($CNF_AUTOFLUSH),
240 "boolean global confparm");
241 is(getconf($CNF_TAPERALGO), $Amanda::Config::ALGO_LAST,
242 "taperalgo global confparam");
243 is_deeply([getconf($CNF_RESERVED_UDP_PORT)], [100,200],
244 "intrange global confparm");
245 is(getconf($CNF_DISPLAYUNIT), "M",
246 "displayunit is correctly uppercased");
247 is_deeply(getconf($CNF_DEVICE_PROPERTY),
248 { "foo" => { priority => 0, append => 0, values => ["bar"]},
249 "blue" => { priority => 0, append => 0,
250 values => ["car", "tar"]} },
251 "proplist global confparm");
252 ok(getconf_seen($CNF_TAPEDEV),
253 "'tapedev' parm was seen");
254 ok(!getconf_seen($CNF_CHANGERFILE),
255 "'changerfile' parm was not seen");
258 SKIP: { # derived values
259 skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
261 is(Amanda::Config::getconf_unit_divisor(), 1024,
262 "correct unit divisor (from displayunit -> KB)");
263 ok($Amanda::Config::debug_auth,
264 "debug_auth setting reflected in global variable");
265 ok(!$Amanda::Config::debug_amandad,
266 "debug_amandad defaults to false");
270 skip "error loading config", 6 unless $cfg_result == $CFGERR_OK;
271 my $ttyp = lookup_tapetype("mytapetype");
272 ok($ttyp, "found mytapetype");
273 is(tapetype_getconf($ttyp, $TAPETYPE_COMMENT), 'mine',
275 is(tapetype_getconf($ttyp, $TAPETYPE_LENGTH), 128 * 1024,
278 ok(tapetype_seen($ttyp, $TAPETYPE_COMMENT),
279 "tapetype comment was seen");
280 ok(!tapetype_seen($ttyp, $TAPETYPE_LBL_TEMPL),
281 "tapetype lbl_templ was not seen");
283 is_deeply([ sort(+getconf_list("tapetype")) ],
284 [ sort("mytapetype", "TEST-TAPE") ],
285 "getconf_list lists all tapetypes");
289 skip "error loading config", 17 unless $cfg_result == $CFGERR_OK;
291 my $dtyp = lookup_dumptype("mydump-type");
292 ok($dtyp, "found mydump-type");
293 is(dumptype_getconf($dtyp, $DUMPTYPE_COMMENT), 'mine',
295 is(dumptype_getconf($dtyp, $DUMPTYPE_PRIORITY), 2,
296 "dumptype priority");
297 is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPSIZE), $int64_num+0,
299 is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPMULT), 1.75,
301 is(dumptype_getconf($dtyp, $DUMPTYPE_STARTTIME), 1829,
303 is(dumptype_getconf($dtyp, $DUMPTYPE_HOLDINGDISK), $HOLD_REQUIRED,
304 "dumptype holdingdisk");
305 is(dumptype_getconf($dtyp, $DUMPTYPE_COMPRESS), $COMP_BEST,
306 "dumptype compress");
307 is(dumptype_getconf($dtyp, $DUMPTYPE_ENCRYPT), $ENCRYPT_SERV_CUST,
309 is(dumptype_getconf($dtyp, $DUMPTYPE_STRATEGY), $DS_INCRONLY,
310 "dumptype strategy");
311 is_deeply([dumptype_getconf($dtyp, $DUMPTYPE_COMPRATE)], [0.25, 0.75],
312 "dumptype comprate");
313 is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_INCLUDE),
314 { 'file' => [ 'rhyme' ],
315 'list' => [ 'bing', 'ting', 'string', 'fling' ],
317 "dumptype include list");
318 is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_EXCLUDE),
319 { 'file' => [ 'foolist' ],
320 'list' => [ 'foo', 'bar', 'true', 'star' ],
322 "dumptype exclude list");
323 is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_PROPERTY),
324 { "prop" => { priority => 0, append => 0, values => ["erty"]},
325 "drop" => { priority => 0, append => 0,
326 values => ["qwerty", "asdfg"] }},
327 "dumptype proplist");
329 ok(dumptype_seen($dtyp, $DUMPTYPE_EXCLUDE),
330 "'exclude' parm was seen");
331 ok(!dumptype_seen($dtyp, $DUMPTYPE_RECORD),
332 "'record' parm was not seen");
334 is_deeply([ sort(+getconf_list("dumptype")) ],
336 mydump-type second_dumptype third_dumptype
337 NO-COMPRESS COMPRESS-FAST COMPRESS-BEST COMPRESS-CUST
338 SRVCOMPRESS BSD-AUTH KRB4-AUTH NO-RECORD NO-HOLD
341 "getconf_list lists all dumptypes (including defaults)");
345 skip "error loading config", 8 unless $cfg_result == $CFGERR_OK;
346 my $iface = lookup_interface("ethernet");
347 ok($iface, "found ethernet");
348 is(interface_name($iface), "ethernet",
349 "interface knows its name");
350 is(interface_getconf($iface, $INTER_COMMENT), 'mine',
351 "interface comment");
352 is(interface_getconf($iface, $INTER_MAXUSAGE), 100,
353 "interface maxusage");
355 $iface = lookup_interface("nic");
356 ok($iface, "found nic");
357 ok(interface_seen($iface, $INTER_COMMENT),
358 "seen set for parameters that appeared");
359 ok(!interface_seen($iface, $INTER_MAXUSAGE),
360 "seen not set for parameters that did not appear");
362 is_deeply([ sort(+getconf_list("interface")) ],
363 [ sort('ethernet', 'nic', 'default') ],
364 "getconf_list lists all interfaces (in any order)");
367 SKIP: { # holdingdisks
368 skip "error loading config", 13 unless $cfg_result == $CFGERR_OK;
369 my $hdisk = lookup_holdingdisk("hd1");
370 ok($hdisk, "found hd1");
371 is(holdingdisk_name($hdisk), "hd1",
372 "hd1 knows its name");
373 is(holdingdisk_getconf($hdisk, $HOLDING_COMMENT), 'mine',
374 "holdingdisk comment");
375 is(holdingdisk_getconf($hdisk, $HOLDING_DISKDIR), '/mnt/hd1',
376 "holdingdisk diskdir (directory)");
377 is(holdingdisk_getconf($hdisk, $HOLDING_DISKSIZE), 100*1024,
378 "holdingdisk disksize (use)");
379 is(holdingdisk_getconf($hdisk, $HOLDING_CHUNKSIZE), 1024,
380 "holdingdisk chunksize");
382 $hdisk = lookup_holdingdisk("hd2");
383 ok($hdisk, "found hd2");
384 ok(holdingdisk_seen($hdisk, $HOLDING_COMMENT),
385 "seen set for parameters that appeared");
386 ok(!holdingdisk_seen($hdisk, $HOLDING_CHUNKSIZE),
387 "seen not set for parameters that did not appear");
389 # only holdingdisks have this linked-list structure
391 $hdisk = getconf_holdingdisks();
392 like(holdingdisk_name($hdisk), qr/hd[12]/,
393 "one disk is first in list of holdingdisks");
394 $hdisk = holdingdisk_next($hdisk);
395 like(holdingdisk_name($hdisk), qr/hd[12]/,
396 "another is second in list of holdingdisks");
397 ok(!holdingdisk_next($hdisk),
398 "no third holding disk");
400 is_deeply([ sort(+getconf_list("holdingdisk")) ],
401 [ sort('hd1', 'hd2') ],
402 "getconf_list lists all holdingdisks (in any order)");
405 SKIP: { # application
406 skip "error loading config", 5 unless $cfg_result == $CFGERR_OK;
407 my $app = lookup_application("my_app");
408 ok($app, "found my_app");
409 is(application_name($app), "my_app",
410 "my_app knows its name");
411 is(application_getconf($app, $APPLICATION_COMMENT), 'my_app_comment',
412 "application comment");
413 is(application_getconf($app, $APPLICATION_PLUGIN), 'amgtar',
414 "application plugin (amgtar)");
416 is_deeply([ sort(+getconf_list("application-tool")) ],
418 "getconf_list lists all application-tool");
422 skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
423 my $sc = lookup_pp_script("my_script");
424 ok($sc, "found my_script");
425 is(pp_script_name($sc), "my_script",
426 "my_script knows its name");
427 is(pp_script_getconf($sc, $PP_SCRIPT_COMMENT), 'my_script_comment',
429 is(pp_script_getconf($sc, $PP_SCRIPT_PLUGIN), 'script-email',
430 "script plugin (script-email)");
431 is(pp_script_getconf($sc, $PP_SCRIPT_EXECUTE_WHERE), $ES_CLIENT,
432 "script execute_where (client)");
433 is(pp_script_getconf($sc, $PP_SCRIPT_EXECUTE_ON),
434 $EXECUTE_ON_PRE_HOST_BACKUP|$EXECUTE_ON_POST_HOST_BACKUP,
435 "script execute_on");
437 is_deeply([ sort(+getconf_list("script-tool")) ],
438 [ sort("my_script") ],
439 "getconf_list lists all script-tool");
443 skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
444 my $dc = lookup_device_config("my_device");
445 ok($dc, "found my_device");
446 is(device_config_name($dc), "my_device",
447 "my_device knows its name");
448 is(device_config_getconf($dc, $DEVICE_CONFIG_COMMENT), 'my device is mine, not yours',
450 is(device_config_getconf($dc, $DEVICE_CONFIG_TAPEDEV), 'tape:/dev/nst0',
452 # TODO do we really need all of this equipment for device properties?
453 is_deeply(device_config_getconf($dc, $DEVICE_CONFIG_DEVICE_PROPERTY),
454 { "BLOCK_SIZE" => { 'priority' => 0, 'values' => ["128k"], 'append' => 0 }, },
455 "device config proplist");
457 is_deeply([ sort(+getconf_list("device")) ],
458 [ sort("my_device") ],
459 "getconf_list lists all devices");
463 skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
464 my $dc = lookup_changer_config("my_changer");
465 ok($dc, "found my_changer");
466 is(changer_config_name($dc), "my_changer",
467 "my_changer knows its name");
468 is(changer_config_getconf($dc, $CHANGER_CONFIG_COMMENT), 'my changer is mine, not yours',
470 is(changer_config_getconf($dc, $CHANGER_CONFIG_CHANGERDEV), '/dev/sg0',
473 is_deeply([ sort(+getconf_list("changer")) ],
474 [ sort("my_changer") ],
475 "getconf_list lists all changers");
479 # Test config overwrites (using the config from above)
481 $config_overwrites = new_config_overwrites(1); # note estimate is too small
482 add_config_overwrite($config_overwrites, "tapedev", "null:TEST");
483 add_config_overwrite($config_overwrites, "tpchanger", "chg-test");
484 add_config_overwrite_opt($config_overwrites, "org=KAOS");
485 apply_config_overwrites($config_overwrites);
487 is(getconf($CNF_TAPEDEV), "null:TEST",
488 "config overwrites work with real config");
489 is(getconf($CNF_ORG), "KAOS",
490 "add_config_overwrite_opt parsed correctly");
493 $config_overwrites = new_config_overwrites(1);
494 add_config_overwrite($config_overwrites, "bogusparam", "foo");
495 apply_config_overwrites($config_overwrites);
497 my ($error_level, @errors) = Amanda::Config::config_errors();
498 is($error_level, $CFGERR_ERRORS, "bogus config overwrite flagged as an error");
501 # Test configuration dumping
503 # (uses the config from the previous section)
505 # fork a child and capture its stdout
506 my $pid = open(my $kid, "-|");
507 die "Can't fork: $!" unless defined($pid);
509 Amanda::Config::dump_configuration();
512 my $dump_first_line = <$kid>;
513 my $dump = join'', $dump_first_line, <$kid>;
517 my $fn = Amanda::Config::get_config_filename();
518 my $dump_filename = $dump_first_line;
519 chomp $dump_filename;
520 $dump_filename =~ s/^# AMANDA CONFIGURATION FROM FILE "//g;
521 $dump_filename =~ s/":$//g;
522 is($dump_filename, $fn,
523 "config filename is included correctly");
525 like($dump, qr/DEVICE_PROPERTY\s+"foo" "bar"\n/i,
526 "DEVICE_PROPERTY appears in dump output");
528 like($dump, qr/AMRECOVER_CHECK_LABEL\s+(yes|no)/i,
529 "AMRECOVER_CHECK_LABEL has a trailing space");
531 like($dump, qr/AMRECOVER_CHECK_LABEL\s+(yes|no)/i,
532 "AMRECOVER_CHECK_LABEL has a trailing space");
534 like($dump, qr/EXCLUDE\s+LIST "foo" "bar" "true" "star"/i,
535 "EXCLUDE LIST is in the dump");
536 like($dump, qr/EXCLUDE\s+FILE "foolist"/i,
537 "EXCLUDE FILE is in the dump");
538 like($dump, qr/INCLUDE\s+LIST OPTIONAL "bing" "ting" "string" "fling"/i,
539 "INCLUDE LIST is in the dump");
540 like($dump, qr/INCLUDE\s+FILE OPTIONAL "rhyme"/i,
541 "INCLUDE FILE is in the dump");
544 # Explore a quirk of exinclude parsing. Only the last
545 # exclude (or include) directive affects the 'optional' flag.
546 # We may want to change this, but we should do so intentionally.
547 # This is also tested by the 'amgetconf' installcheck.
549 $testconf = Installcheck::Config->new();
550 $testconf->add_dumptype('mydump-type', [
551 'exclude list' => '"foo" "bar"',
552 'exclude list optional append' => '"true" "star"',
553 'exclude list append' => '"true" "star"',
557 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
558 is($cfg_result, $CFGERR_OK,
559 "first exinclude parsing config loaded")
560 or diag_config_errors();
562 skip "error loading config", 2 unless $cfg_result == $CFGERR_OK;
564 my $dtyp = lookup_dumptype("mydump-type");
565 ok($dtyp, "found mydump-type");
566 is(dumptype_getconf($dtyp, $DUMPTYPE_EXCLUDE)->{'optional'}, 0,
567 "'optional' has no effect when not on the last occurrence");
571 # Check out where quoting is and is not required.
573 $testconf = Installcheck::Config->new();
575 # make sure an unquoted tapetype is OK
576 $testconf->add_param('tapetype', 'TEST-TAPE'); # unquoted (Installcheck::Config uses quoted)
578 # strings can optionally be quoted
579 $testconf->add_param('org', '"MyOrg"');
581 # enumerations (e.g., taperalgo) must not be quoted; implicitly tested above
584 $testconf->add_dumptype('"parent"', [ # note quotes
585 'bumpsize' => '10240',
587 $testconf->add_dumptype('child', [
588 '' => '"parent"', # note quotes
590 $testconf->add_dumptype('child2', [
595 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
596 is($cfg_result, $CFGERR_OK,
597 "parsed config to test strings vs. identifiers")
598 or diag_config_errors();
600 skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
602 my $dtyp = lookup_dumptype("parent");
603 ok($dtyp, "found parent");
604 $dtyp = lookup_dumptype("child");
605 ok($dtyp, "found child");
606 is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPSIZE), 10240,
607 "child dumptype correctly inherited bumpsize");