blob: d7e01d26adf1817e57427bd345156264a4b4ed8f [file] [log] [blame]
Willy Tarreau2ea3abb2007-03-25 16:45:16 +02001#!/usr/bin/perl
2###################################################################################################################
3# $Id:: check 20 2007-02-23 14:26:44Z fabrice $
4# $Revision:: 20 $
5###################################################################################################################
6# Authors : Fabrice Dulaunoy <fabrice@dulaunoy.com>
7#
8# Copyright (C) 2006-2007 Fabrice Dulaunoy <fabrice@dulaunoy.com>
9#
10# This program is free software; you can redistribute it and/or modify it
11# under the terms of the GNU General Public License as published by the
12# Free Software Foundation; either version 2 of the License, or (at your
13# option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
14#
15# This program is distributed in the hope that it will be useful, but
16# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18# for more details.
19###################################################################################################################
20#
21###################################################################################################################
22
23use strict;
24
25package MyPackage;
26use Config::General;
27use Getopt::Std;
28use LWP::UserAgent;
29use URI;
30use File::Basename;
31
32# CVS VSERSION
33#my $VERSION = do { my @rev = ( q$Revision: 20 $ =~ /\d+/g ); sprintf "%d." . "%d" x $#rev, @rev };
34# SVN VERSION
35my $VERSION = sprintf "1.%02d", '$Revision: 20 $ ' =~ /(\d+)/;
36
37my %option;
38
39getopts( "vHhc:", \%option );
40
41if ( $option{ h } )
42{
43 print "Usage: $0 [options ...]\n\n";
44 print "Where options include:\n";
45 print "\t -h \t\t\tthis help (what else ?)\n";
46 print "\t -H \t\t\tshow a sample config file\n";
47 print "\t -v \t\t\tprint version and exit\n";
48 print "\t -c file \t\tuse config file (default /etc/check.conf)\n";
49 print "\n\t This is a small program parsing the config file \n";
50 print "\t and checking one or more condition on one or more servers\n";
51 print "\t these condition could be \n";
52 print "\t\t HTTP return code list (with optinal Host Header and optional Basic Authentication) \n";
53 print "\t\t a regex over a HTTP GET (with optinal Host Header and optional Basic Authentication)\n";
54 print "\t\t a regex over a FTP GET ( with optional Basic Authentication)\n";
55 print "\t\t a TCP open port\n";
56 print "\t the result state is an AND over all tests \n";
57 print "\t this result could be \n";
58 print "\t\t a simple HTTP return state (\"200 OK\" or \"503 Service Unavailable\" \n";
59 print "\t\t a HTML page with a status OK or NOK for each test\n";
60 print "\t\t a HTML page with a staus OK or NOK for each test in a row of a TABLE\n";
61 print "\n\t The natural complement of this tools is the poll_check tool\n";
62 print "\t The result code of this tools is designed to fit the HAPROXY requirement (test over a port not related to the WEB server)\n";
63}
64
65if ( $option{ H } )
66{
67 print "\t A sample config file could be:\n";
68 print <<'EOF';
69
70 ###########################################################
71 # listening port ( default 9898 )
72 port 9899
73
74 # on which IP to bind (default 127.0.0.1 ) * = all IP
75 host 10.2.1.1
76
77 # which client addr is allow ( default 127.0.0.0/8 )
78 #cidr_allow = 0.0.0.0/0
79
80 # verbosity from 0 to 4 (default 0 = no log )
81 log_level = 1
82
83 # daemonize (default 0 = no )
84 daemon = 1
85
86 # content put a HTML content after header
87 # (default 0 = no content 1 = html 2 = table )
88 content = 2
89
90 # reparse the config file at each request ( default 0 = no )
91 # only SIGHUP reread the config file)
92 reparse = 1
93
94 # pid_file (default /var/run/check.pid )
95 # $$$ = basename of config file
96 # $$ = PID
97 pid_file=/var/run/CHECK_$$$.pid
98
99 # log_file (default /var/log/check.log )
100 # $$$ = basename of config file
101 # $$ = PID
102 log_file=/var/log/CHECK_$$$.log
103
104 # number of servers to keep running (default = 5)
105 min_servers = 2
106
107 # number of servers to have waiting for requests (default = 2)
108 min_spare_servers = 1
109
110 # maximum number of servers to have waiting for requests (default = 10)
111 max_spare_servers =1
112
113 # number of servers (default = 50)
114 max_servers = 2
115
116
117 ###########################################################
118 # a server to check
119 # type could be get , regex or tcp
120 #
121 # get = do a http or ftp get and check the result code with
122 # the list, coma separated, provided ( default = 200,201 )
123 # hostheader is optional and send to the server if provided
124 #
125 # regex = do a http or ftp get and check the content result
126 # with regex provided
127 # hostheader is optional and send to the server if provided
128 #
129 # tcp = test if the tcp port provided is open
130 #
131 ###########################################################
132
133 <realserver>
134 url=http://127.0.0.1:80/apache2-default/index.html
135 type = get
136 code=200,201
137 hostheader = www.test.com
138 </realserver>
139
140
141 <realserver>
142 url=http://127.0.0.1:82/apache2-default/index.html
143 type = get
144 code=200,201
145 hostheader = www.myhost.com
146 </realserver>
147
148 <realserver>
149 url= http://10.2.2.1
150 type = regex
151 regex= /qdAbm/
152 </realserver>
153
154 <realserver>
155 type = tcp
156 url = 10.2.2.1
157 port =80
158 </realserver>
159
160 <realserver>
161 type = get
162 url = ftp://USER:PASSWORD@10.2.3.1
163 code=200,201
164 </realserver>
165 ###########################################################
166
167
168
169EOF
170
171}
172
173if ( $option{ h } || $option{ H } )
174{
175 exit;
176}
177
178if ( $option{ v } ) { print "$VERSION\n"; exit; }
179
180use vars qw(@ISA);
181use Net::Server::PreFork;
182@ISA = qw(Net::Server::PreFork);
183
184my $port;
185my $host;
186my $reparse;
187my $cidr_allow;
188my $log_level;
189my $log_file;
190my $pid_file;
191my $daemon;
192my $min_servers;
193my $min_spare_servers;
194my $max_spare_servers;
195my $max_servers;
196my $html_content;
197
198my $conf_file = $option{ c } || "/etc/check.conf";
199my $pwd = $ENV{ PWD };
200$conf_file =~ s/^\./$pwd/;
201$conf_file =~ s/^([^\/])/$pwd\/$1/;
202my $basename = basename( $conf_file, ( '.conf' ) );
203my $CONF = parse_conf( $conf_file );
204
205my $reparse_one = 0;
206
207$SIG{ HUP } = sub { $reparse_one = 1; };
208
209my @TEST;
210my $test_list = $CONF->{ realserver };
211if ( ref( $test_list ) eq "ARRAY" )
212{
213 @TEST = @{ $test_list };
214}
215else
216{
217 @TEST = ( $test_list );
218}
219
220my $server = MyPackage->new(
221 {
222 port => $port,
223 host => $host,
224 cidr_allow => $cidr_allow,
225 log_level => $log_level,
226 child_communication => 1,
227 setsid => $daemon,
228 log_file => $log_file,
229 pid_file => $pid_file,
230 min_servers => $min_servers,
231 min_spare_servers => $min_spare_servers,
232 max_spare_servers => $max_spare_servers,
233 max_servers => $max_servers,
234 }
235);
236
237$server->run();
238exit;
239
240sub process_request
241{
242 my $self = shift;
243 if ( $reparse || $reparse_one )
244 {
245 $CONF = parse_conf( $conf_file );
246 }
247 my $result;
248 my @TEST;
249 my $test_list = $CONF->{ realserver };
250
251 if ( ref( $test_list ) eq "ARRAY" )
252 {
253 @TEST = @{ $test_list };
254 }
255 else
256 {
257 @TEST = ( $test_list );
258 }
259
260 my $allow_code;
261 my $test_item;
262 my $html_data;
263 foreach my $test ( @TEST )
264 {
265 my $uri;
266 my $authority;
267 my $URL = $test->{ url };
268 $uri = URI->new( $URL );
269 $authority = $uri->authority;
270
271 if ( exists $test->{ type } )
272 {
273 if ( $test->{ type } =~ /get/i )
274 {
275 my $allow_code = $test->{ code } || '200,201';
276 $test_item++;
277 my $host = $test->{ hostheader } || $authority;
278 my $res = get( $URL, $allow_code, $host );
279 if ( $html_content == 1 )
280 {
281 if ( $res )
282 {
283 $html_data .= "GET OK $URL<br>\r\n";
284 }
285 else
286 {
287 $html_data .= "GET NOK $URL<br>\r\n";
288 }
289 }
290 if ( $html_content == 2 )
291 {
292 if ( $res )
293 {
294 $html_data .= "<tr><td>GET</td><td>OK</td><td>$URL</td></tr>\r\n";
295 }
296 else
297 {
298 $html_data .= "<tr><td>GET</td><td>NOK</td><td>$URL</td></tr>\r\n";
299 }
300 }
301 $result += $res;
302 }
303 if ( $test->{ type } =~ /regex/i )
304 {
305 my $regex = $test->{ regex };
306 $test_item++;
307 my $host = $test->{ hostheader } || $authority;
308 my $res = regex( $URL, $regex, $host );
309 if ( $html_content == 1 )
310 {
311 if ( $res )
312 {
313 $html_data .= "REGEX OK $URL<br>\r\n";
314 }
315 else
316 {
317 $html_data .= "REGEX NOK $URL<br>\r\n";
318 }
319 }
320 if ( $html_content == 2 )
321 {
322 if ( $res )
323 {
324 $html_data .= "<tr><td>REGEX</td><td>OK</td><td>$URL</td></tr>\r\n";
325 }
326 else
327 {
328 $html_data .= "<tr><td>REGEX</td><td>NOK</td><td>$URL</td></tr>\r\n";
329 }
330 }
331 $result += $res;
332 }
333 if ( $test->{ type } =~ /tcp/i )
334 {
335 $test_item++;
336 my $PORT = $test->{ port } || 80;
337 my $res = TCP( $URL, $PORT );
338 if ( $html_content == 1 )
339 {
340 if ( $res )
341 {
342 $html_data .= "TCP OK $URL<br>\r\n";
343 }
344 else
345 {
346 $html_data .= "TCP NOK $URL<br>\r\n";
347 }
348 }
349 if ( $html_content == 2 )
350 {
351 if ( $res )
352 {
353 $html_data .= "<tr><td>TCP</td><td>OK</td><td>$URL</td></tr>\r\n";
354 }
355 else
356 {
357 $html_data .= "<tr><td>TCP</td><td>NOK</td><td>$URL</td></tr>\r\n";
358 }
359 }
360 $result += $res;
361 }
362 }
363 }
364
365 my $len;
366 if ( $html_content == 1 )
367 {
368 $html_data = "\r\n<html><body>\r\n$html_data</body></html>\r\n";
369 $len = ( length( $html_data ) ) - 2;
370 }
371 if ( $html_content == 2 )
372 {
373 $html_data = "\r\n<table align='center' border='1' >\r\n$html_data</table>\r\n";
374 $len = ( length( $html_data ) ) - 2;
375 }
376
377 if ( $result != $test_item )
378 {
379 my $header = "HTTP/1.0 503 Service Unavailable\r\n";
380 if ( $html_content )
381 {
382 $header .= "Content-Length: $len\r\nContent-Type: text/html; charset=iso-8859-1\r\n";
383 }
384 print $header . $html_data;
385 return;
386 }
387 my $header = "HTTP/1.0 200 OK\r\n";
388 if ( $html_content )
389 {
390 $header .= "Content-Length: $len\r\nContent-Type: text/html; charset=iso-8859-1\r\n";
391 }
392 print $header. $html_data;
393}
394
3951;
396
397##########################################################
398##########################################################
399# function to REGEX on a GET from URL
400# arg: uri
401# regex to test (with extra parameter like perl e.g. /\bweb\d{2,3}/i )
402# IP
403# port (optionnal: default=80)
404# ret: 0 if no reply
405# 1 if reply
406##########################################################
407##########################################################
408sub regex
409{
410 my $url = shift;
411 my $regex = shift;
412 my $host = shift;
413
414 $regex =~ /\/(.*)\/(.*)/;
415 my $reg = $1;
416 my $ext = $2;
417 my %options;
418 $options{ 'agent' } = "LB_REGEX_PROBE/$VERSION";
419 $options{ 'timeout' } = 10;
420 my $ua = LWP::UserAgent->new( %options );
421 my $response = $ua->get( $url, "Host" => $host );
422 if ( $response->is_success )
423 {
424 my $html = $response->content;
425 if ( $ext =~ /i/ )
426 {
427 if ( $html =~ /$reg/si )
428 {
429 return 1;
430 }
431 }
432 else
433 {
434 if ( $html =~ /$reg/s )
435 {
436 return 1;
437 }
438 }
439 }
440 return 0;
441}
442
443##########################################################
444##########################################################
445# function to GET an URL (HTTP or FTP) ftp://FTPTest:6ccount4F@brice!@172.29.0.146
446# arg: uri
447# allowed code (comma seaparated)
448# IP
449# port (optionnal: default=80)
450# ret: 0 if not the expected vcode
451# 1 if the expected code is returned
452##########################################################
453##########################################################
454sub get
455{
456 my $url = shift;
457 my $code = shift;
458 my $host = shift;
459
460 $code =~ s/\s*//g;
461 my %codes = map { $_ => $_ } split /,/, $code;
462 my %options;
463 $options{ 'agent' } = "LB_HTTP_PROBE/$VERSION";
464 $options{ 'timeout' } = 10;
465 my $ua = LWP::UserAgent->new( %options );
466 my $response = $ua->get( $url, "Host" => $host );
467 if ( $response->is_success )
468 {
469 my $rc = $response->{ _rc };
470 if ( defined $codes{ $rc } )
471 {
472 return 1;
473 }
474 }
475 return 0;
476}
477
478##########################################################
479##########################################################
480# function to test a port on a host
481# arg: hostip
482# port
483# timeout
484# ret: 0 if not open
485# 1 if open
486##########################################################
487##########################################################
488sub TCP
489{
490 use IO::Socket::PortState qw(check_ports);
491 my $remote_host = shift;
492 my $remote_port = shift;
493 my $timeout = shift;
494
495 my %porthash = ( tcp => { $remote_port => { name => 'to_test', } } );
496 check_ports( $remote_host, $timeout, \%porthash );
497 return $porthash{ tcp }{ $remote_port }{ open };
498}
499
500##############################################
501# parse config file
502# IN: File PATH
503# Out: Ref to a hash with config data
504##############################################
505sub parse_conf
506{
507 my $file = shift;
508
509 my $conf = new Config::General(
510 -ConfigFile => $file,
511 -ExtendedAccess => 1,
512 -AllowMultiOptions => "yes"
513 );
514 my %config = $conf->getall;
515 $port = $config{ port } || 9898;
516 $host = $config{ host } || '127.0.0.1';
517 $reparse = $config{ reparse } || 0;
518 $cidr_allow = $config{ cidr_allow } || '127.0.0.0/8';
519 $log_level = $config{ log_level } || 0;
520 $log_file = $config{ log_file } || "/var/log/check.log";
521 $pid_file = $config{ pid_file } || "/var/run/check.pid";
522 $daemon = $config{ daemon } || 0;
523 $min_servers = $config{ min_servers } || 5;
524 $min_spare_servers = $config{ min_spare_servers } || 2;
525 $max_spare_servers = $config{ max_spare_servers } || 10;
526 $max_servers = $config{ max_servers } || 50;
527 $html_content = $config{ content } || 0;
528
529 $pid_file =~ s/\$\$\$/$basename/g;
530 $pid_file =~ s/\$\$/$$/g;
531 $log_file =~ s/\$\$\$/$basename/g;
532 $log_file =~ s/\$\$/$$/g;
533
534 if ( !( keys %{ $config{ realserver } } ) )
535 {
536 die "No farm to test\n";
537 }
538 return ( \%config );
539}
540