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