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