7975a57f2abdea22fe26f86584d79b65cb0dcfca
[debian/amanda] / installcheck / Amanda_ClientService.pl
1 # Copyright (c) 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 => 30;
20 use strict;
21 use warnings;
22
23 use Data::Dumper;
24
25 use lib "@amperldir@";
26 use Amanda::ClientService;
27 use Amanda::Constants;
28 use Amanda::Util;
29 use Amanda::Debug;
30 use Amanda::Config qw( :init );
31 use Amanda::MainLoop;
32 use Socket;
33
34 config_init(0, undef);
35 Amanda::Debug::dbopen('installcheck');
36
37 # test connect_streams
38 {
39     # that these tests assume that DATA_FD_OFFSET and DATA_FD_COUNT have these values
40     is($Amanda::Constants::DATA_FD_OFFSET, 50, "DATA_FD_OFFSET is what I think it is");
41     is($Amanda::Constants::DATA_FD_COUNT, 3, "DATA_FD_COUNT is what I think it is");
42
43     sub test_connect_streams {
44         my ($args, $exp_line, $exp_closed_fds, $exp_streams, $msg) = @_;
45
46         my $cs = Amanda::ClientService->new();
47         $cs->{'_dont_use_real_fds'} = 1;
48         $cs->{'_argv'} = [ 'amandad' ];
49         my $got_line = $cs->connect_streams(@$args);
50
51         is($got_line, $exp_line, "$msg (CONNECT line)");
52
53         is_deeply([ sort @{$cs->{'_would_have_closed_fds'}} ],
54                   [ sort @$exp_closed_fds ],
55                   "$msg (closed fds)");
56
57         # get the named streams and their fd's
58         my %streams;
59         while (@$args) {
60             my $name = shift @$args;
61             my $dirs = shift @$args;
62             $streams{$name} = [ $cs->rfd($name), $cs->wfd($name) ];
63         }
64
65         is_deeply(\%streams, $exp_streams, "$msg (streams)");
66     }
67
68     test_connect_streams(
69         [ 'DATA' => 'rw' ],
70         'CONNECT DATA 50',
71         [ 52, 53, 54, 55 ],
72         { 'DATA' => [ 51, 50 ] },
73         "simple read/write DATA stream");
74
75     test_connect_streams(
76         [ 'DATA' => 'r' ],
77         'CONNECT DATA 50',
78         [ 50, 52, 53, 54, 55 ],
79         { 'DATA' => [ 51, -1 ] },
80         "read-only stream");
81
82     test_connect_streams(
83         [ 'DATA' => 'w' ],
84         'CONNECT DATA 50',
85         [ 51, 52, 53, 54, 55 ],
86         { 'DATA' => [ -1, 50 ] },
87         "write-only stream");
88
89     test_connect_streams(
90         [ 'DATA' => 'rw', 'RD' => 'r', 'WR' => 'w' ],
91         'CONNECT DATA 50 RD 51 WR 52',
92         [ 52, 55 ],
93         { 'DATA' => [ 51, 50 ],
94           'RD' => [ 53, -1 ],
95           'WR' => [ -1, 54 ] },
96         "three streams");
97 }
98
99 # test from_inetd and friends
100 {
101     my $cs;
102
103     $cs = Amanda::ClientService->new();
104     $cs->{'_argv'} = [];
105     ok($cs->from_inetd, "no argv[0] interpreted as a run from inetd");
106
107     $cs = Amanda::ClientService->new();
108     $cs->{'_argv'} = [ 'installcheck' ];
109     ok($cs->from_inetd, "argv[0] = 'installcheck' also interpreted as a run from inetd");
110
111     $cs = Amanda::ClientService->new();
112     $cs->{'_argv'} = [ 'amandad' ];
113     ok(!$cs->from_inetd, "argv[0] = 'amandad' interpreted as a run from amandad");
114
115     $cs = Amanda::ClientService->new();
116     $cs->{'_argv'} = [ 'amandad', 'bsdgre' ];
117     is($cs->amandad_auth, "bsdgre",
118         "argv[1] = 'bsdgre' interpreted as auth");
119
120     $cs = Amanda::ClientService->new();
121     $cs->{'_argv'} = [ 'amandad' ];
122     is($cs->amandad_auth, undef,
123         "amandad_auth interpreted as undef if missing");
124 }
125
126 # test add_connection and half-close operations
127 sub test_connections {
128     my ($finished_cb) = @_;
129
130     my $port;
131     my $cs = Amanda::ClientService->new();
132     $cs->{'_argv'} = [ ];
133
134     my $steps = define_steps
135         cb_ref => \$finished_cb;
136
137     step listen => sub {
138         $port = $cs->connection_listen('FOO', 0);
139
140         $steps->{'fork'}->();
141     };
142
143     step fork => sub {
144         # fork off a child to connect to and write to that port
145         if (fork() == 0) {
146             socket(my $foo, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
147                 or die "error creating connect socket: $!";
148             connect($foo, sockaddr_in($port, inet_aton("127.0.0.1")))
149                 or die "error connecting: $!";
150             my $info = <$foo>;
151             print $foo "GOT[$info]";
152             close($foo);
153             exit(0);
154         } else {
155             $steps->{'accept'}->();
156         }
157     };
158
159     step accept => sub {
160         $cs->connection_accept('FOO', 90, $steps->{'accept_finished'});
161     };
162
163     step accept_finished => sub {
164         # write a message to the fd and read back the result; this is
165         # synchronous
166         my $msg = "HELLO WORLD";
167         Amanda::Util::full_write($cs->wfd('FOO'), $msg, length($msg))
168             or die "full write: $!";
169         $cs->close('FOO', 'w');
170         is($cs->wfd('FOO'), -1,
171             "FOO is closed for writing");
172
173         my $input = Amanda::Util::full_read($cs->rfd('FOO'), 1024);
174         $cs->close('FOO', 'r');
175         is_deeply([ keys %{$cs->{'_streams'}} ], [ 'main' ],
176             "FOO stream is deleted when completely closed");
177
178         is($input, "GOT[HELLO WORLD]",
179             "both directions of the FOO stream work");
180
181         $finished_cb->();
182     };
183 }
184 test_connections(\&Amanda::MainLoop::quit);
185 Amanda::MainLoop::run();
186
187 # check rfd and wfd
188 {
189     my $cs = Amanda::ClientService->new();
190     is($cs->rfd('main'), 0,
191         "main rfd is stdin");
192     is($cs->wfd('main'), 1,
193         "main wfd is stdout");
194     is($cs->wfd('none'), -1,
195         "wfd returns -1 for invalid stream");
196     is($cs->rfd('none'), -1,
197         "rfd returns -1 for invalid stream");
198 }
199
200 # check check_bsd_security
201 {
202     # note that we can't completely test this, because BSD security entails checking
203     # DNS and privileged ports, neither of which are controllable from the installcheck
204     # environment.  However, we can at least call the method.
205
206     my $cs = Amanda::ClientService->new();
207     $cs->{'_argv'} = [ 'installcheck' ]; # basically neuters check_bsd_security
208
209     ok(!$cs->check_bsd_security('main', "USER bart"),
210         "check_bsd_security returns undef");
211 }
212
213 # check parse_req
214 {
215     my $cs = Amanda::ClientService->new();
216     my $req_str;
217
218     # is_deeply doesn't like objects very much
219     sub strip_features {
220         my ($x) = @_;
221         #use Data::Dumper;
222         #print Dumper($x);
223         return $x unless defined $x->{'features'};
224         $x->{'features'} = "featureset";
225         return $x;
226     }
227
228     $req_str = <<ENDREQ;
229 OPTIONS auth=passport;features=f0039;
230 FOO
231 ENDREQ
232     is_deeply(strip_features($cs->parse_req($req_str)), {
233         lines => [ 'OPTIONS auth=passport;features=f0039;', 'FOO' ],
234         options => {
235             auth => 'passport',
236             features => 'f0039',
237         },
238         errors => [],
239         features => "featureset",
240     }, "parse_req parses a request properly");
241
242     $req_str = <<ENDREQ;
243 OPTIONS auth=bsd;no-features;yes=no;
244 ENDREQ
245     is_deeply(strip_features($cs->parse_req($req_str)), {
246         lines => [ 'OPTIONS auth=bsd;no-features;yes=no;' ],
247         options => {
248             auth => 'bsd',
249             yes => 'no',
250             'no-features' => 1,
251         },
252         errors => [],
253         features => undef,
254     }, "parse_req parses a request with boolean options");
255
256     $req_str = <<ENDREQ;
257 OPTIONS turn=left;
258 OPTIONS turn=right;
259 ENDREQ
260     is_deeply(strip_features($cs->parse_req($req_str)), {
261         lines => [ 'OPTIONS turn=left;', 'OPTIONS turn=right;' ],
262         options => {
263             turn => 'left',
264         },
265         errors => [ 'got multiple OPTIONS lines' ],
266         features => undef,
267     }, "parse_req detects multiple OPTIONS lines as an error");
268 }