Imported Upstream version 3.2.1
[debian/amanda] / installcheck / Amanda_Config.pl
1 # Copyright (c) 2007, 2008, 2009, 2010 Zmanda, Inc.  All Rights Reserved.
2 #
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.
6 #
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
10 # for more details.
11 #
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
15 #
16 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
18
19 use Test::More tests => 204;
20 use strict;
21 use warnings;
22 use Data::Dumper;
23
24 use lib "@amperldir@";
25 use Installcheck::Config;
26 use Amanda::Paths;
27 use Amanda::Tests;
28 use Amanda::Config qw( :init :getconf string_to_boolean amandaify_property_name );
29 use Amanda::Debug;
30
31 my $testconf;
32 my $config_overrides;
33
34 Amanda::Debug::dbopen("installcheck");
35 Installcheck::log_test_output();
36
37 # utility function
38
39 sub diag_config_errors {
40     my ($level, @errors) = Amanda::Config::config_errors();
41     for my $errmsg (@errors) {
42         diag $errmsg;
43     }
44 }
45
46 ##
47 # Try starting with no configuration at all
48
49 is(config_init(0, ''), $CFGERR_OK,
50     "Initialize with no configuration")
51     or diag_config_errors();
52
53 config_uninit();
54 $config_overrides = new_config_overrides(1);
55 add_config_override($config_overrides, "tapedev", "null:TEST");
56 set_config_overrides($config_overrides);
57
58 is(config_init(0, undef), $CFGERR_OK,
59     "Initialize with no configuration, passing a NULL config name")
60     or diag_config_errors();
61
62 is(getconf($CNF_TAPEDEV), "null:TEST",
63     "config overwrites work with null config");
64
65 ##
66 # Check out error handling
67
68 $testconf = Installcheck::Config->new();
69 $testconf->add_param('label_new_tapes', '"xx"'); # a deprecated keyword -> warning
70 $testconf->write();
71
72 {
73     is(config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF"), $CFGERR_WARNINGS,
74         "Deprecated keyword generates a warning");
75     my ($error_level, @errors) = Amanda::Config::config_errors();
76     like($errors[0], qr/is deprecated/, 
77         "config_get_errors returns the warning string");
78
79     Amanda::Config::config_clear_errors();
80     ($error_level, @errors) = Amanda::Config::config_errors();
81     is(scalar(@errors), 0, "config_clear_errors clears error list");
82 }
83
84 $testconf = Installcheck::Config->new();
85 $testconf->add_param('invalid-param', 'random-value'); # a deprecated keyword -> warning
86 $testconf->write();
87
88 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "NO-SUCH-CONFIGURATION"), $CFGERR_ERRORS,
89     "Non-existent config generates an error");
90
91 is(config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF"), $CFGERR_ERRORS,
92     "Invalid keyword generates an error");
93
94 ##
95 # try a client configuration
96
97 # (note use of uppercase letters to test lower-casing of property names)
98 $testconf = Installcheck::Config->new();
99 $testconf->add_client_param('property', '"client-prop" "yep"');
100 $testconf->add_client_param('property', 'priority "clIent-prop1" "foo"');
101 $testconf->add_client_param('property', 'append "clieNt-prop" "bar"');
102 $testconf->add_client_param('property', '"ANotHer_prOp" "baz"');
103 $testconf->add_client_param('property', 'append "ANOTHER-prop" "boo"');
104 $testconf->write();
105
106 my $cfg_result = config_init($CONFIG_INIT_CLIENT, undef);
107 is($cfg_result, $CFGERR_OK,
108     "Load test client configuration")
109     or diag_config_errors();
110
111 is_deeply(getconf($CNF_PROPERTY), { "client-prop1" => { priority => 1,
112                                                         append   => 0,
113                                                         values => [ "foo" ]},
114                                     "client-prop" => { priority => 0,
115                                                        append   => 1,
116                                                        values => [ "yep", "bar" ] },
117                                     "another-prop" => { priority => 0,
118                                                         append   => 1,
119                                                         values => [ "baz", "boo" ] }},
120     "Client PROPERTY parameter parsed correctly");
121
122 ##
123 # Parse up a basic configuration
124
125 # invent a "large" unsigned number, and make $size_t_num 
126 # depend on the length of size_t
127 my $int64_num = '171801575472'; # 0xA000B000C000 / 1024
128 my $size_t_num;
129 if (Amanda::Tests::sizeof_size_t() > 4) {
130     $size_t_num = $int64_num;
131 } else {
132     $size_t_num = '2147483647'; # 0x7fffffff
133 }
134
135 $testconf = Installcheck::Config->new();
136 $testconf->add_param('reserve', '75');
137 $testconf->add_param('autoflush', 'yes');
138 $testconf->add_param('usetimestamps', '0');
139 $testconf->add_param('tapedev', '"/dev/foo"');
140 $testconf->add_param('bumpsize', $int64_num);
141 $testconf->add_param('bumpmult', '1.4');
142 $testconf->add_param('reserved_udp-port', '100,200'); # note use of '-' and '_'
143 $testconf->add_param('device_output_buffer_size', $size_t_num);
144 $testconf->add_param('taperalgo', 'last');
145 $testconf->add_param('device_property', '"foo" "bar"');
146 $testconf->add_param('device_property', '"blUE" "car" "tar"');
147 $testconf->add_param('autolabel', 'non-amanda empty');
148 $testconf->add_param('displayunit', '"m"');
149 $testconf->add_param('debug_auth', '1');
150 $testconf->add_tapetype('mytapetype', [
151     'comment' => '"mine"',
152     'length' => '128 M',
153     'part_size' => '100M',
154     'part_cache_type' => 'disk',
155     'part_cache_dir' => '"/usr/bin"',
156     'part_cache_max_size' => '50M',
157 ]);
158 $testconf->add_dumptype('mydump-type', [    # note dash
159     'comment' => '"mine"',
160     'priority' => 'high',  # == 2
161     'bumpsize' => $int64_num,
162     'bumpmult' => 1.75,
163     'starttime' => 1829,
164     'holdingdisk' => 'required',
165     'compress' => 'client best',
166     'encrypt' => 'server',
167     'strategy' => 'incronly',
168     'comprate' => '0.25,0.75',
169     'exclude list' => '"foo" "bar"',
170     'exclude list append' => '"true" "star"',
171     'exclude file' => '"foolist"',
172     'include list' => '"bing" "ting"',
173     'include list append' => '"string" "fling"',
174     'include file optional' => '"rhyme"',
175     'property' => '"prop" "erty"',
176     'property' => '"DROP" "qwerty" "asdfg"',
177     'estimate' => 'server calcsize client',
178     'allow_split' => 'no',
179     'allow_split' => 'no',
180 ]);
181 $testconf->add_dumptype('second_dumptype', [ # note underscore
182     '' => 'mydump-type',
183     'comment' => '"refers to mydump-type with a dash"',
184 ]);
185 $testconf->add_dumptype('third_dumptype', [
186     '' => 'second_dumptype',
187     'comment' => '"refers to second_dumptype with an underscore"',
188     'recovery-limit' => '"left" same-host "right"',
189 ]);
190 $testconf->add_interface('ethernet', [
191     'comment' => '"mine"',
192     'use' => '100',
193 ]);
194 $testconf->add_interface('nic', [
195     'comment' => '"empty"',
196 ]);
197 $testconf->add_holdingdisk('hd1', [
198     'comment' => '"mine"',
199     'directory' => '"/mnt/hd1"',
200     'use' => '100M',
201     'chunksize' => '1024k',
202 ]);
203 $testconf->add_holdingdisk('hd2', [
204     'comment' => '"empty"',
205 ]);
206 $testconf->add_application('my_app', [
207     'comment' => '"my_app_comment"',
208     'plugin' => '"amgtar"',
209 ]);
210 $testconf->add_script('my_script', [
211   'comment' => '"my_script_comment"',
212   'plugin' => '"script-email"',
213   'execute-on' => 'pre-host-backup, post-host-backup',
214   'execute-where' => 'client',
215   'property' => '"mailto" "amandabackup" "amanda"',
216 ]);
217 $testconf->add_device('my_device', [
218   'comment' => '"my device is mine, not yours"',
219   'tapedev' => '"tape:/dev/nst0"',
220   'device_property' => '"BLOCK_SIZE" "128k"',
221   'device_property' => '"CoMmENT" "what up?"',
222 ]);
223 $testconf->add_changer('my_changer', [
224   'comment' => '"my changer is mine, not yours"',
225   'tpchanger' => '"chg-foo"',
226   'changerdev' => '"/dev/sg0"',
227   'changerfile' => '"chg.state"',
228   'property' => '"testprop" "testval"',
229   'device_property' => '"testdprop" "testdval"',
230 ]);
231
232 $testconf->write();
233
234 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
235 if (!is($cfg_result, $CFGERR_OK,
236     "Load test configuration")) {
237     diag_config_errors();
238     die "aborting after config errors";
239 }
240
241 is(Amanda::Config::get_config_name(), "TESTCONF",
242     "config_name set");
243 is(Amanda::Config::get_config_dir(), "$CONFIG_DIR/TESTCONF",
244     "config_dir set");
245 is(Amanda::Config::get_config_filename(),
246     "$CONFIG_DIR/TESTCONF/amanda.conf",
247     "config_filename set");
248
249 is(getconf($CNF_RESERVE), 75,
250     "integer global confparm");
251 is(getconf($CNF_BUMPSIZE), $int64_num+0,
252     "int64 global confparm");
253 is(getconf($CNF_TAPEDEV), "/dev/foo",
254     "string global confparm");
255 is(getconf($CNF_DEVICE_OUTPUT_BUFFER_SIZE), $size_t_num+0,
256     "size global confparm");
257 ok(getconf($CNF_AUTOFLUSH),
258     "boolean global confparm");
259 is(getconf($CNF_USETIMESTAMPS), 0,
260     "boolean global confparm, passing an integer (0)");
261 is(getconf($CNF_TAPERALGO), $Amanda::Config::ALGO_LAST,
262     "taperalgo global confparam");
263 is_deeply([getconf($CNF_RESERVED_UDP_PORT)], [100,200],
264     "intrange global confparm");
265 is(getconf($CNF_DISPLAYUNIT), "M",
266     "displayunit is correctly uppercased");
267 is_deeply(getconf($CNF_DEVICE_PROPERTY),
268           { "foo" => { priority => 0, append => 0, values => ["bar"]},
269             "blue" => { priority => 0, append => 0,
270                         values => ["car", "tar"]} },
271         "proplist global confparm");
272 is_deeply(getconf($CNF_AUTOLABEL),
273         { template => undef, other_config => '',
274           non_amanda => 1, volume_error => '', empty => 1 },
275         "'autolabel non-amanda empty' represented correctly");
276 ok(getconf_seen($CNF_TAPEDEV),
277     "'tapedev' parm was seen");
278 ok(!getconf_seen($CNF_CHANGERFILE),
279     "'changerfile' parm was not seen");
280
281 is(Amanda::Config::getconf_unit_divisor(), 1024,
282     "correct unit divisor (from displayunit -> KB)");
283 ok($Amanda::Config::debug_auth,
284     "debug_auth setting reflected in global variable");
285 ok(!$Amanda::Config::debug_amandad,
286     "debug_amandad defaults to false");
287
288 my $ttyp = lookup_tapetype("mytapetype");
289 ok($ttyp, "found mytapetype");
290 is(tapetype_getconf($ttyp, $TAPETYPE_COMMENT), 'mine',
291     "tapetype comment");
292 is(tapetype_getconf($ttyp, $TAPETYPE_LENGTH), 128 * 1024,
293     "tapetype comment");
294
295 ok(tapetype_seen($ttyp, $TAPETYPE_COMMENT),
296     "tapetype comment was seen");
297 ok(!tapetype_seen($ttyp, $TAPETYPE_LBL_TEMPL),
298     "tapetype lbl_templ was not seen");
299
300 is(tapetype_getconf($ttyp, $TAPETYPE_PART_SIZE), 100*1024,
301     "tapetype part_size");
302 is(tapetype_getconf($ttyp, $TAPETYPE_PART_CACHE_TYPE), $PART_CACHE_TYPE_DISK,
303     "tapetype part_cache_type");
304 is(tapetype_getconf($ttyp, $TAPETYPE_PART_CACHE_DIR), "/usr/bin",
305     "tapetype part_cache_dir");
306 is(tapetype_getconf($ttyp, $TAPETYPE_PART_CACHE_MAX_SIZE), 50*1024,
307     "tapetype part_cache_max_size");
308
309 is_deeply([ sort(+getconf_list("tapetype")) ],
310           [ sort("mytapetype", "TEST-TAPE") ],
311     "getconf_list lists all tapetypes");
312
313 my $dtyp = lookup_dumptype("mydump-type");
314 ok($dtyp, "found mydump-type");
315 is(dumptype_getconf($dtyp, $DUMPTYPE_COMMENT), 'mine',
316     "dumptype string");
317 is(dumptype_getconf($dtyp, $DUMPTYPE_PRIORITY), 2,
318     "dumptype priority");
319 is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPSIZE), $int64_num+0,
320     "dumptype size");
321 is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPMULT), 1.75,
322     "dumptype real");
323 is(dumptype_getconf($dtyp, $DUMPTYPE_STARTTIME), 1829,
324     "dumptype time");
325 is(dumptype_getconf($dtyp, $DUMPTYPE_HOLDINGDISK), $HOLD_REQUIRED,
326     "dumptype holdingdisk");
327 is(dumptype_getconf($dtyp, $DUMPTYPE_COMPRESS), $COMP_BEST,
328     "dumptype compress");
329 is(dumptype_getconf($dtyp, $DUMPTYPE_ENCRYPT), $ENCRYPT_SERV_CUST,
330     "dumptype encrypt");
331 is(dumptype_getconf($dtyp, $DUMPTYPE_STRATEGY), $DS_INCRONLY,
332     "dumptype strategy");
333 is_deeply([dumptype_getconf($dtyp, $DUMPTYPE_COMPRATE)], [0.25, 0.75],
334     "dumptype comprate");
335 is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_INCLUDE),
336     { 'file' => [ 'rhyme' ],
337       'list' => [ 'bing', 'ting', 'string', 'fling' ],
338       'optional' => 1 },
339     "dumptype include list");
340 is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_EXCLUDE),
341     { 'file' => [ 'foolist' ],
342       'list' => [ 'foo', 'bar', 'true', 'star' ],
343       'optional' => 0 },
344     "dumptype exclude list");
345 is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_ESTIMATELIST),
346           [ $ES_SERVER, $ES_CALCSIZE, $ES_CLIENT ],
347     "dumptype estimate list");
348 is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_PROPERTY),
349           { "prop" => { priority => 0, append => 0, values => ["erty"]},
350             "drop" => { priority => 0, append => 0,
351                         values => ["qwerty", "asdfg"] }},
352         "dumptype proplist");
353 is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_RECOVERY_LIMIT),
354     [],
355     "dumptype recovery limit with no limit specified => empty");
356
357 ok(dumptype_seen($dtyp, $DUMPTYPE_EXCLUDE),
358     "'exclude' parm was seen");
359 ok(!dumptype_seen($dtyp, $DUMPTYPE_RECORD),
360     "'record' parm was not seen");
361
362 is_deeply([ sort(+getconf_list("dumptype")) ],
363           [ sort(qw(
364             mydump-type second_dumptype third_dumptype
365             NO-COMPRESS COMPRESS-FAST COMPRESS-BEST COMPRESS-CUST
366             SRVCOMPRESS BSD-AUTH NO-RECORD NO-HOLD
367             NO-FULL
368             )) ],
369     "getconf_list lists all dumptypes (including defaults)");
370 is(dumptype_getconf($dtyp, $DUMPTYPE_ALLOW_SPLIT), 0,
371     "dumptype allow_split");
372
373 my $iface = lookup_interface("ethernet");
374 ok($iface, "found ethernet");
375 is(interface_name($iface), "ethernet",
376     "interface knows its name");
377 is(interface_getconf($iface, $INTER_COMMENT), 'mine',
378     "interface comment");
379 is(interface_getconf($iface, $INTER_MAXUSAGE), 100,
380     "interface maxusage");
381
382 $iface = lookup_interface("nic");
383 ok($iface, "found nic");
384 ok(interface_seen($iface, $INTER_COMMENT),
385     "seen set for parameters that appeared");
386 ok(!interface_seen($iface, $INTER_MAXUSAGE),
387     "seen not set for parameters that did not appear");
388
389 is_deeply([ sort(+getconf_list("interface")) ],
390           [ sort('ethernet', 'nic', 'default') ],
391     "getconf_list lists all interfaces (in any order)");
392
393 skip "error loading config", 13 unless $cfg_result == $CFGERR_OK;
394 my $hdisk = lookup_holdingdisk("hd1");
395 ok($hdisk, "found hd1");
396 is(holdingdisk_name($hdisk), "hd1",
397     "hd1 knows its name");
398 is(holdingdisk_getconf($hdisk, $HOLDING_COMMENT), 'mine',
399     "holdingdisk comment");
400 is(holdingdisk_getconf($hdisk, $HOLDING_DISKDIR), '/mnt/hd1',
401     "holdingdisk diskdir (directory)");
402 is(holdingdisk_getconf($hdisk, $HOLDING_DISKSIZE), 100*1024,
403     "holdingdisk disksize (use)");
404 is(holdingdisk_getconf($hdisk, $HOLDING_CHUNKSIZE), 1024,
405     "holdingdisk chunksize");
406
407 $hdisk = lookup_holdingdisk("hd2");
408 ok($hdisk, "found hd2");
409 ok(holdingdisk_seen($hdisk, $HOLDING_COMMENT),
410     "seen set for parameters that appeared");
411 ok(!holdingdisk_seen($hdisk, $HOLDING_CHUNKSIZE),
412     "seen not set for parameters that did not appear");
413
414 # only holdingdisks have this linked-list structure
415 # exposed
416 my $hdisklist = getconf($CNF_HOLDINGDISK);
417 my $first_disk = @$hdisklist[0];
418 $hdisk = lookup_holdingdisk($first_disk);
419 like(holdingdisk_name($hdisk), qr/hd[12]/,
420     "one disk is first in list of holdingdisks");
421 $hdisk = lookup_holdingdisk(@$hdisklist[1]);
422 like(holdingdisk_name($hdisk), qr/hd[12]/,
423     "another is second in list of holdingdisks");
424 ok($#$hdisklist == 1,
425     "no third holding disk");
426
427 is_deeply([ sort(+getconf_list("holdingdisk")) ],
428           [ sort('hd1', 'hd2') ],
429     "getconf_list lists all holdingdisks (in any order)");
430
431 skip "error loading config", 5 unless $cfg_result == $CFGERR_OK;
432 my $app = lookup_application("my_app");
433 ok($app, "found my_app");
434 is(application_name($app), "my_app",
435     "my_app knows its name");
436 is(application_getconf($app, $APPLICATION_COMMENT), 'my_app_comment',
437     "application comment");
438 is(application_getconf($app, $APPLICATION_PLUGIN), 'amgtar',
439     "application plugin (amgtar)");
440
441 is_deeply([ sort(+getconf_list("application-tool")) ],
442           [ sort("my_app") ],
443     "getconf_list lists all applications");
444 # test backward compatibility
445 is_deeply([ sort(+getconf_list("application")) ],
446           [ sort("my_app") ],
447     "getconf_list works for 'application-tool', too");
448
449 my $sc = lookup_pp_script("my_script");
450 ok($sc, "found my_script");
451 is(pp_script_name($sc), "my_script",
452     "my_script knows its name");
453 is(pp_script_getconf($sc, $PP_SCRIPT_COMMENT), 'my_script_comment',
454     "script comment");
455 is(pp_script_getconf($sc, $PP_SCRIPT_PLUGIN), 'script-email',
456     "script plugin (script-email)");
457 is(pp_script_getconf($sc, $PP_SCRIPT_EXECUTE_WHERE), $ES_CLIENT,
458     "script execute_where (client)");
459 is(pp_script_getconf($sc, $PP_SCRIPT_EXECUTE_ON),
460     $EXECUTE_ON_PRE_HOST_BACKUP|$EXECUTE_ON_POST_HOST_BACKUP,
461     "script execute_on");
462
463 is_deeply([ sort(+getconf_list("script")) ],
464           [ sort("my_script") ],
465     "getconf_list lists all script");
466
467 is_deeply([ sort(+getconf_list("script-tool")) ],
468           [ sort("my_script") ],
469     "getconf_list works for 'script-tool', too");
470
471 my $dc = lookup_device_config("my_device");
472 ok($dc, "found my_device");
473 is(device_config_name($dc), "my_device",
474     "my_device knows its name");
475 is(device_config_getconf($dc, $DEVICE_CONFIG_COMMENT), 'my device is mine, not yours',
476     "device comment");
477 is(device_config_getconf($dc, $DEVICE_CONFIG_TAPEDEV), 'tape:/dev/nst0',
478     "device tapedev");
479 # TODO do we really need all of this equipment for device properties?
480 is_deeply(device_config_getconf($dc, $DEVICE_CONFIG_DEVICE_PROPERTY),
481       { "block-size" => { 'priority' => 0, 'values' => ["128k"], 'append' => 0 },
482         "comment" => { 'priority' => 0, 'values' => ["what up?"], 'append' => 0 }, },
483     "device config proplist");
484
485 is_deeply([ sort(+getconf_list("device")) ],
486           [ sort("my_device") ],
487     "getconf_list lists all devices");
488
489 skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
490 $dc = lookup_changer_config("my_changer");
491 ok($dc, "found my_changer");
492 is(changer_config_name($dc), "my_changer",
493     "my_changer knows its name");
494 is(changer_config_getconf($dc, $CHANGER_CONFIG_COMMENT), 'my changer is mine, not yours',
495     "changer comment");
496 is(changer_config_getconf($dc, $CHANGER_CONFIG_CHANGERDEV), '/dev/sg0',
497     "changer tapedev");
498 is_deeply(changer_config_getconf($dc, $CHANGER_CONFIG_PROPERTY),
499     { 'testprop' => {
500             'priority' => 0,
501             'values' => [ 'testval' ],
502             'append' => 0,
503         }
504     }, "changer properties represented correctly");
505
506 is_deeply(changer_config_getconf($dc, $CHANGER_CONFIG_DEVICE_PROPERTY),
507     { 'testdprop' => {
508             'priority' => 0,
509             'values' => [ 'testdval' ],
510             'append' => 0,
511         }
512     }, "changer device properties represented correctly");
513
514 is_deeply([ sort(+getconf_list("changer")) ],
515           [ sort("my_changer") ],
516     "getconf_list lists all changers");
517
518 ##
519 # Test config overwrites (using the config from above)
520
521 config_uninit();
522 $config_overrides = new_config_overrides(1); # note estimate is too small
523 add_config_override($config_overrides, "tapedev", "null:TEST");
524 add_config_override($config_overrides, "tpchanger", "chg-test");
525 add_config_override_opt($config_overrides, "org=KAOS");
526 set_config_overrides($config_overrides);
527 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
528
529 is(getconf($CNF_TAPEDEV), "null:TEST",
530     "config overwrites work with real config");
531 is(getconf($CNF_ORG), "KAOS",
532     "add_config_override_opt parsed correctly");
533
534 # introduce an error
535 config_uninit();
536 $config_overrides = new_config_overrides(1);
537 add_config_override($config_overrides, "bogusparam", "foo");
538 set_config_overrides($config_overrides);
539 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
540
541 my ($error_level, @errors) = Amanda::Config::config_errors();
542 is($error_level, $CFGERR_ERRORS, "bogus config overwrite flagged as an error");
543
544 ##
545 # Test configuration dumping
546
547 # (uses the config from the previous section)
548
549 # fork a child and capture its stdout
550 my $pid = open(my $kid, "-|");
551 die "Can't fork: $!" unless defined($pid);
552 if (!$pid) {
553     Amanda::Config::dump_configuration();
554     exit 1;
555 }
556 my $dump_first_line = <$kid>;
557 my $dump = join'', $dump_first_line, <$kid>;
558 close $kid;
559 waitpid $pid, 0;
560
561 my $fn = Amanda::Config::get_config_filename();
562 my $dump_filename = $dump_first_line;
563 chomp $dump_filename;
564 $dump_filename =~ s/^# AMANDA CONFIGURATION FROM FILE "//g;
565 $dump_filename =~ s/":$//g;
566 is($dump_filename, $fn, 
567     "config filename is included correctly");
568
569 like($dump, qr/DEVICE-PROPERTY\s+"foo" "bar"\n/i,
570     "DEVICE-PROPERTY appears in dump output");
571
572 like($dump, qr/AMRECOVER-CHECK-LABEL\s+(yes|no)/i,
573     "AMRECOVER-CHECK-LABEL has a trailing space");
574
575 like($dump, qr/AMRECOVER-CHECK-LABEL\s+(yes|no)/i,
576     "AMRECOVER-CHECK-LABEL has a trailing space");
577
578 like($dump, qr/EXCLUDE\s+LIST "foo" "bar" "true" "star"/i,
579     "EXCLUDE LIST is in the dump");
580 like($dump, qr/EXCLUDE\s+FILE "foolist"/i,
581     "EXCLUDE FILE is in the dump");
582 like($dump, qr/INCLUDE\s+LIST OPTIONAL "bing" "ting" "string" "fling"/i,
583     "INCLUDE LIST is in the dump");
584 like($dump, qr/INCLUDE\s+FILE OPTIONAL "rhyme"/i,
585     "INCLUDE FILE is in the dump");
586 like($dump, qr/RECOVERY-LIMIT.*SAME-HOST/i,
587     "RECOVERY-LIST is in the dump");
588
589 ##
590 # Test nested definitions inside a dumptype
591
592 $testconf = Installcheck::Config->new();
593 $testconf->add_dumptype('nested_stuff', [
594     'comment' => '"contains a nested application, pp_script"',
595     'application' => '{
596         comment "my app"
597         plugin "amfun"
598 }',
599     'script' => '{
600         comment "my script"
601         plugin "ppfun"
602 }',
603 ]);
604
605 $testconf->write();
606
607 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
608 is($cfg_result, $CFGERR_OK, 
609     "parsing nested config loaded")
610     or diag_config_errors();
611 SKIP: {
612     skip "error loading config", 8 unless $cfg_result == $CFGERR_OK;
613
614     my $dtyp = lookup_dumptype("nested_stuff");
615     ok($dtyp, "found nested_stuff");
616
617     my $appname = dumptype_getconf($dtyp, $DUMPTYPE_APPLICATION);
618     like($appname, qr/^custom\(/,
619         "DUMPTYPE_APPLICATION is the generated name of an application subsection");
620
621     my $app = lookup_application($appname);
622     ok($app, ".. and that name leads to an application object");
623     is(application_getconf($app, $APPLICATION_COMMENT), "my app",
624         ".. that has the right comment");
625
626     my $sc = dumptype_getconf($dtyp, $DUMPTYPE_SCRIPTLIST);
627     ok(ref($sc) eq 'ARRAY' && @$sc == 1, "DUMPTYPE_SCRIPTLIST returns a 1-element list");
628     like($sc->[0], qr/^custom\(/,
629         ".. and the first element is the generated name of a script subsection");
630
631     $sc = lookup_pp_script($sc->[0]);
632     ok($sc, ".. and that name leads to a pp_script object");
633     is(pp_script_getconf($sc, $PP_SCRIPT_COMMENT), "my script",
634         ".. that has the right comment");
635 }
636
637 ##
638 # Explore a quirk of exinclude parsing.  Only the last
639 # exclude (or include) directive affects the 'optional' flag.
640 # We may want to change this, but we should do so intentionally.
641 # This is also tested by the 'amgetconf' installcheck.
642
643 $testconf = Installcheck::Config->new();
644 $testconf->add_dumptype('mydump-type', [
645     'exclude list' => '"foo" "bar"',
646     'exclude list optional append' => '"true" "star"',
647     'exclude list append' => '"true" "star"',
648 ]);
649 $testconf->write();
650
651 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
652 is($cfg_result, $CFGERR_OK, 
653     "first exinclude parsing config loaded")
654     or diag_config_errors();
655 SKIP: {
656     skip "error loading config", 2 unless $cfg_result == $CFGERR_OK;
657
658     my $dtyp = lookup_dumptype("mydump-type");
659     ok($dtyp, "found mydump-type");
660     is(dumptype_getconf($dtyp, $DUMPTYPE_EXCLUDE)->{'optional'}, 0,
661         "'optional' has no effect when not on the last occurrence");
662 }
663
664 ##
665 # Check out recovery-limit parsing
666
667 $testconf = Installcheck::Config->new();
668 $testconf->add_param('recovery-limit', '"foo" "bar"');
669 $testconf->add_dumptype('rl1', [
670     'recovery-limit' => 'same-host',
671 ]);
672 $testconf->add_dumptype('rl2', [
673     'recovery-limit' => '"somehost"',
674 ]);
675 $testconf->add_dumptype('rl3', [
676     'recovery-limit' => 'same-host "somehost"',
677 ]);
678 $testconf->add_dumptype('rl4', [
679     'recovery-limit' => '"foohost" same-host',
680 ]);
681 $testconf->write();
682
683 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
684 is($cfg_result, $CFGERR_OK,
685     "recovery-limit config loaded")
686     or diag_config_errors();
687 SKIP: {
688     skip "error loading config", 5 unless $cfg_result == $CFGERR_OK;
689     my $dtyp;
690
691     is_deeply(getconf($CNF_RECOVERY_LIMIT),
692         [ 'foo', 'bar' ],
693         "global recovery-limit parameter");
694
695     $dtyp = lookup_dumptype("rl1");
696     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_RECOVERY_LIMIT),
697         [ undef ],
698         "same-host => undef in list");
699
700     $dtyp = lookup_dumptype("rl2");
701     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_RECOVERY_LIMIT),
702         [ "somehost" ],
703         "hostname => match pattern");
704
705     $dtyp = lookup_dumptype("rl3");
706     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_RECOVERY_LIMIT),
707         [ undef, "somehost" ],
708         "hostname and same-host parsed correctly");
709
710     $dtyp = lookup_dumptype("rl4");
711     is_deeply(dumptype_getconf($dtyp, $DUMPTYPE_RECOVERY_LIMIT),
712         [ undef, "foohost" ], # note that the order is an implementation detail
713         ".. even if same-host comes last");
714 }
715
716 ##
717 # Try an autolabel with a template and 'any'
718
719 $testconf = Installcheck::Config->new();
720 $testconf->add_param('autolabel', '"FOO%%%BAR" any');
721 $testconf->write();
722
723 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
724 is($cfg_result, $CFGERR_OK, 
725     "first exinclude parsing config loaded")
726     or diag_config_errors();
727 SKIP: {
728     skip "error loading config", 1 unless $cfg_result == $CFGERR_OK;
729     is_deeply(getconf($CNF_AUTOLABEL),
730             { template => "FOO%%%BAR", other_config => 1,
731               non_amanda => 1, volume_error => 1, empty => 1 },
732             "'autolabel \"FOO%%%BAR\" any' represented correctly");
733 }
734
735 ##
736 # Check out where quoting is and is not required.
737
738 $testconf = Installcheck::Config->new();
739
740 # make sure an unquoted tapetype is OK
741 $testconf->add_param('tapetype', 'TEST-TAPE'); # unquoted (Installcheck::Config uses quoted)
742
743 # strings can optionally be quoted
744 $testconf->add_param('dumporder', '"STSTST"');
745
746 # enumerations (e.g., taperalgo) must not be quoted; implicitly tested above
747
748 # definitions
749 $testconf->add_dumptype('"parent"', [ # note quotes
750     'bumpsize' => '10240',
751 ]);
752 $testconf->add_dumptype('child', [
753     '' => '"parent"', # note quotes
754 ]);
755 $testconf->add_dumptype('child2', [
756     '' => 'parent',
757 ]);
758 $testconf->write();
759
760 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
761 is($cfg_result, $CFGERR_OK,
762     "parsed config to test strings vs. identifiers")
763     or diag_config_errors();
764 SKIP: {
765     skip "error loading config", 3 unless $cfg_result == $CFGERR_OK;
766
767     my $dtyp = lookup_dumptype("parent");
768     ok($dtyp, "found parent");
769     $dtyp = lookup_dumptype("child");
770     ok($dtyp, "found child");
771     is(dumptype_getconf($dtyp, $DUMPTYPE_BUMPSIZE), 10240,
772         "child dumptype correctly inherited bumpsize");
773 }
774
775 ##
776 # Explore a quirk of read_int_or_str parsing.
777
778 $testconf = Installcheck::Config->new();
779 $testconf->add_dumptype('mydump-type1', [
780     'client_port' => '12345',
781 ]);
782 $testconf->add_dumptype('mydump-type2', [
783     'client_port' => '"newamanda"',
784 ]);
785 $testconf->add_dumptype('mydump-type3', [
786     'client_port' => '"67890"',
787 ]);
788 $testconf->write();
789
790 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
791 is($cfg_result, $CFGERR_OK, 
792     "read_int_or_str parsing config loaded")
793     or diag_config_errors();
794 SKIP: {
795     skip "error loading config", 6 unless $cfg_result == $CFGERR_OK;
796
797     my $dtyp = lookup_dumptype("mydump-type1");
798     ok($dtyp, "found mydump-type1");
799     is(dumptype_getconf($dtyp, $DUMPTYPE_CLIENT_PORT), "12345",
800         "client_port set to 12345");
801
802     $dtyp = lookup_dumptype("mydump-type2");
803     ok($dtyp, "found mydump-type1");
804     is(dumptype_getconf($dtyp, $DUMPTYPE_CLIENT_PORT), "newamanda",
805         "client_port set to \"newamanda\"");
806
807     $dtyp = lookup_dumptype("mydump-type3");
808     ok($dtyp, "found mydump-type1");
809     is(dumptype_getconf($dtyp, $DUMPTYPE_CLIENT_PORT), "67890",
810         "client_port set to \"67890\"");
811 }
812
813 ##
814 # Check property inheritance
815
816 $testconf = Installcheck::Config->new();
817 $testconf->add_application('app1', [
818     'property' => '"prop1" "val1"'
819 ]);
820 $testconf->add_application('app2', [
821     'property' => 'append "prop2" "val2"'
822 ]);
823 $testconf->add_application('app3', [
824     'property' => '"prop3" "val3"'
825 ]);
826 $testconf->add_application('app1a', [
827     'property' => '"prop4" "val4"',
828     'property' => '"prop1" "val1a"',
829     'app1' => undef
830 ]);
831 $testconf->add_application('app2a', [
832     'property' => '"prop5" "val5"',
833     'property' => '"prop2" "val2a"',
834     'app2' => undef
835 ]);
836 $testconf->add_application('app3a', [
837     'property' => '"prop6" "val6"',
838     'app3' => undef,
839     'property' => '"prop7" "val7"'
840 ]);
841 $testconf->add_application('app1b', [
842     'property' => '"prop4" "val4"',
843     'property' => '"prop1" "val1a"',
844     'app1' => undef,
845     'property' => '"prop1" "val1b"',
846 ]);
847 $testconf->add_application('app2b', [
848     'property' => '"prop5" "val5"',
849     'property' => '"prop2" "val2a"',
850     'app2' => undef,
851     'property' => 'append "prop2" "val2b"',
852 ]);
853 $testconf->write();
854
855 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
856 is($cfg_result, $CFGERR_OK, 
857     "application properties inheritance")
858     or diag_config_errors();
859 SKIP: {
860     skip "error loading config", 15 unless $cfg_result == $CFGERR_OK;
861
862     my $app = lookup_application("app1a");
863     ok($app, "found app1a");
864     is(application_name($app), "app1a",
865         "app1a knows its name");
866     my $prop = application_getconf($app, $APPLICATION_PROPERTY);
867     is_deeply($prop, { "prop4" => { priority => 0,
868                                     append   => 0,
869                                     values => [ "val4" ]},
870                        "prop1" => { priority => 0,
871                                     append   => 0,
872                                     values => [ "val1" ] }},
873     "PROPERTY parameter of app1a parsed correctly");
874
875     $app = lookup_application("app2a");
876     ok($app, "found app2a");
877     is(application_name($app), "app2a",
878         "app2a knows its name");
879     $prop = application_getconf($app, $APPLICATION_PROPERTY);
880     is_deeply($prop, { "prop5" => { priority => 0,
881                                     append   => 0,
882                                     values => [ "val5" ]},
883                        "prop2" => { priority => 0,
884                                     append   => 0,
885                                     values => [ "val2a", "val2" ] }},
886     "PROPERTY parameter of app2a parsed correctly");
887
888     $app = lookup_application("app3a");
889     ok($app, "found app3a");
890     is(application_name($app), "app3a",
891         "app3a knows its name");
892     $prop = application_getconf($app, $APPLICATION_PROPERTY);
893     is_deeply($prop, { "prop3" => { priority => 0,
894                                     append   => 0,
895                                     values => [ "val3" ]},
896                        "prop6" => { priority => 0,
897                                     append   => 0,
898                                     values => [ "val6" ] },
899                        "prop7" => { priority => 0,
900                                     append   => 0,
901                                     values => [ "val7" ] }},
902     "PROPERTY parameter of app3a parsed correctly");
903
904     $app = lookup_application("app1b");
905     ok($app, "found app1b");
906     is(application_name($app), "app1b",
907         "app1b knows its name");
908     $prop = application_getconf($app, $APPLICATION_PROPERTY);
909     is_deeply($prop, { "prop4" => { priority => 0,
910                                     append   => 0,
911                                     values => [ "val4" ]},
912                        "prop1" => { priority => 0,
913                                     append   => 0,
914                                     values => [ "val1b" ] }},
915     "PROPERTY parameter of app1b parsed correctly");
916
917     $app = lookup_application("app2b");
918     ok($app, "found app2b");
919     is(application_name($app), "app2b",
920         "app2b knows its name");
921     $prop = application_getconf($app, $APPLICATION_PROPERTY);
922     is_deeply($prop, { "prop5" => { priority => 0,
923                                     append   => 0,
924                                     values => [ "val5" ]},
925                        "prop2" => { priority => 0,
926                                     append   => 1,
927                                     values => [ "val2a", "val2", "val2b" ] }},
928     "PROPERTY parameter of app2b parsed correctly");
929 }
930
931
932 ##
933 # Check getconf_byname and getconf_byname_strs
934
935 $testconf = Installcheck::Config->new();
936 $testconf->add_param('tapedev', '"thats a funny name"');
937 $testconf->add_application('app1', [
938     'comment' => '"one"',
939 ]);
940 $testconf->add_script('scr1', [
941     'comment' => '"one"',
942 ]);
943 # check old names, too
944 $testconf->add_text(<<EOF);
945 define application-tool "app2" {
946     comment "two"
947 }
948 EOF
949 $testconf->add_text(<<EOF);
950 define script-tool "scr2" {
951     comment "two"
952 }
953 EOF
954 $testconf->write();
955
956 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
957 is($cfg_result, $CFGERR_OK,
958     "getconf_byname")
959     or diag_config_errors();
960 SKIP: {
961     skip "error loading config", 7 unless $cfg_result == $CFGERR_OK;
962
963     is(getconf_byname("Tapedev"), "thats a funny name",
964         "getconf_byname for global param");
965     is_deeply([ getconf_byname_strs("Tapedev", 1) ],
966         [ "\"thats a funny name\"" ],
967         "getconf_byname_strs for global param with quotes");
968     is_deeply([ getconf_byname_strs("Tapedev", 0) ],
969         [ "thats a funny name" ],
970         "getconf_byname_strs for global param without quotes");
971
972     # test * and *-tool (the old name)
973     is(getconf_byname("application-tool:app1:comment"), "one",
974         "getconf_byname for appplication-tool param");
975     is(getconf_byname("application:app2:comment"), "two",
976         "getconf_byname for application param");
977     is(getconf_byname("script-tool:scr1:comment"), "one",
978         "getconf_byname for appplication-tool param");
979     is(getconf_byname("script:scr2:comment"), "two",
980         "getconf_byname for script param");
981 }
982
983 my @boolean_vals = (
984     {'val' => '1', 'expected' => 1},
985     {'val' => '0', 'expected' => 0},
986     {'val' => 't', 'expected' => 1},
987     {'val' => 'true', 'expected' => 1},
988     {'val' => 'f', 'expected' => 0},
989     {'val' => 'false', 'expected' => 0},
990     {'val' => 'y', 'expected' => 1},
991     {'val' => 'yes', 'expected' => 1},
992     {'val' => 'n', 'expected' => 0},
993     {'val' => 'no', 'expected' => 0},
994     {'val' => 'on', 'expected' => 1},
995     {'val' => 'off', 'expected' => 0},
996     {'val' => 'oFf', 'expected' => 0},
997     {'val' => 'foo', 'expected' => undef},
998     );
999
1000 for my $bv (@boolean_vals) {
1001     is(string_to_boolean($bv->{'val'}), $bv->{'expected'},
1002         "string_to_boolean('$bv->{'val'}') is right");
1003 }
1004
1005 my @prop_names = (
1006     {'val' => '', 'expected' => ''},
1007     {'val' => 'prop-name', 'expected' => 'prop-name'},
1008     {'val' => 'PRoP-NaME', 'expected' => 'prop-name'},
1009     {'val' => 'prop_name', 'expected' => 'prop-name'},
1010     {'val' => 'FaNCy_ProP', 'expected' => 'fancy-prop'},
1011     {'val' => '_under_', 'expected' => '-under-'},
1012     {'val' => '-dash-', 'expected' => '-dash-'},
1013     {'val' => '-', 'expected' => '-'},
1014     {'val' => '_', 'expected' => '-'},
1015     );
1016
1017 for my $pn (@prop_names) {
1018     is(amandaify_property_name($pn->{'val'}), $pn->{'expected'},
1019        "amandaify_property_name('$pn->{'val'}') is right");
1020 }
1021
1022 $testconf = Installcheck::Config->new();
1023 $testconf->add_param('property', '"PrOP_nAme" "VALUE"');
1024 $testconf->write();
1025 config_init($CONFIG_INIT_EXPLICIT_NAME, "TESTCONF");
1026 my $properties = getconf($CNF_PROPERTY);
1027
1028 @prop_names = (
1029     {'val' => 'prop-name'},
1030     {'val' => 'PRoP-NaME'},
1031     {'val' => 'prop_name'},
1032     {'val' => 'PROP_NAME'},
1033     {'val' => 'PRoP-NaME'},
1034     {'val' => 'prop_name'},
1035     );
1036
1037 for my $pn (@prop_names) {
1038     is_deeply($properties->{$pn->{'val'}}->{values}, [ "VALUE" ]);
1039 }
1040