541 lines
16 KiB
Plaintext
541 lines
16 KiB
Plaintext
|
#!/usr/bin/perl
|
||
|
###################################################################################################################
|
||
|
# $Id:: check 20 2007-02-23 14:26:44Z fabrice $
|
||
|
# $Revision:: 20 $
|
||
|
###################################################################################################################
|
||
|
# Authors : Fabrice Dulaunoy <fabrice@dulaunoy.com>
|
||
|
#
|
||
|
# Copyright (C) 2006-2007 Fabrice Dulaunoy <fabrice@dulaunoy.com>
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify it
|
||
|
# under the terms of the GNU General Public License as published by the
|
||
|
# Free Software Foundation; either version 2 of the License, or (at your
|
||
|
# option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful, but
|
||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
|
# for more details.
|
||
|
###################################################################################################################
|
||
|
#
|
||
|
###################################################################################################################
|
||
|
|
||
|
use strict;
|
||
|
|
||
|
package MyPackage;
|
||
|
use Config::General;
|
||
|
use Getopt::Std;
|
||
|
use LWP::UserAgent;
|
||
|
use URI;
|
||
|
use File::Basename;
|
||
|
|
||
|
# CVS VSERSION
|
||
|
#my $VERSION = do { my @rev = ( q$Revision: 20 $ =~ /\d+/g ); sprintf "%d." . "%d" x $#rev, @rev };
|
||
|
# SVN VERSION
|
||
|
my $VERSION = sprintf "1.%02d", '$Revision: 20 $ ' =~ /(\d+)/;
|
||
|
|
||
|
my %option;
|
||
|
|
||
|
getopts( "vHhc:", \%option );
|
||
|
|
||
|
if ( $option{ h } )
|
||
|
{
|
||
|
print "Usage: $0 [options ...]\n\n";
|
||
|
print "Where options include:\n";
|
||
|
print "\t -h \t\t\tthis help (what else ?)\n";
|
||
|
print "\t -H \t\t\tshow a sample config file\n";
|
||
|
print "\t -v \t\t\tprint version and exit\n";
|
||
|
print "\t -c file \t\tuse config file (default /etc/check.conf)\n";
|
||
|
print "\n\t This is a small program parsing the config file \n";
|
||
|
print "\t and checking one or more condition on one or more servers\n";
|
||
|
print "\t these condition could be \n";
|
||
|
print "\t\t HTTP return code list (with optinal Host Header and optional Basic Authentication) \n";
|
||
|
print "\t\t a regex over a HTTP GET (with optinal Host Header and optional Basic Authentication)\n";
|
||
|
print "\t\t a regex over a FTP GET ( with optional Basic Authentication)\n";
|
||
|
print "\t\t a TCP open port\n";
|
||
|
print "\t the result state is an AND over all tests \n";
|
||
|
print "\t this result could be \n";
|
||
|
print "\t\t a simple HTTP return state (\"200 OK\" or \"503 Service Unavailable\" \n";
|
||
|
print "\t\t a HTML page with a status OK or NOK for each test\n";
|
||
|
print "\t\t a HTML page with a staus OK or NOK for each test in a row of a TABLE\n";
|
||
|
print "\n\t The natural complement of this tools is the poll_check tool\n";
|
||
|
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";
|
||
|
}
|
||
|
|
||
|
if ( $option{ H } )
|
||
|
{
|
||
|
print "\t A sample config file could be:\n";
|
||
|
print <<'EOF';
|
||
|
|
||
|
###########################################################
|
||
|
# listening port ( default 9898 )
|
||
|
port 9899
|
||
|
|
||
|
# on which IP to bind (default 127.0.0.1 ) * = all IP
|
||
|
host 10.2.1.1
|
||
|
|
||
|
# which client addr is allow ( default 127.0.0.0/8 )
|
||
|
#cidr_allow = 0.0.0.0/0
|
||
|
|
||
|
# verbosity from 0 to 4 (default 0 = no log )
|
||
|
log_level = 1
|
||
|
|
||
|
# daemonize (default 0 = no )
|
||
|
daemon = 1
|
||
|
|
||
|
# content put a HTML content after header
|
||
|
# (default 0 = no content 1 = html 2 = table )
|
||
|
content = 2
|
||
|
|
||
|
# reparse the config file at each request ( default 0 = no )
|
||
|
# only SIGHUP reread the config file)
|
||
|
reparse = 1
|
||
|
|
||
|
# pid_file (default /var/run/check.pid )
|
||
|
# $$$ = basename of config file
|
||
|
# $$ = PID
|
||
|
pid_file=/var/run/CHECK_$$$.pid
|
||
|
|
||
|
# log_file (default /var/log/check.log )
|
||
|
# $$$ = basename of config file
|
||
|
# $$ = PID
|
||
|
log_file=/var/log/CHECK_$$$.log
|
||
|
|
||
|
# number of servers to keep running (default = 5)
|
||
|
min_servers = 2
|
||
|
|
||
|
# number of servers to have waiting for requests (default = 2)
|
||
|
min_spare_servers = 1
|
||
|
|
||
|
# maximum number of servers to have waiting for requests (default = 10)
|
||
|
max_spare_servers =1
|
||
|
|
||
|
# number of servers (default = 50)
|
||
|
max_servers = 2
|
||
|
|
||
|
|
||
|
###########################################################
|
||
|
# a server to check
|
||
|
# type could be get , regex or tcp
|
||
|
#
|
||
|
# get = do a http or ftp get and check the result code with
|
||
|
# the list, coma separated, provided ( default = 200,201 )
|
||
|
# hostheader is optional and send to the server if provided
|
||
|
#
|
||
|
# regex = do a http or ftp get and check the content result
|
||
|
# with regex provided
|
||
|
# hostheader is optional and send to the server if provided
|
||
|
#
|
||
|
# tcp = test if the tcp port provided is open
|
||
|
#
|
||
|
###########################################################
|
||
|
|
||
|
<realserver>
|
||
|
url=http://127.0.0.1:80/apache2-default/index.html
|
||
|
type = get
|
||
|
code=200,201
|
||
|
hostheader = www.test.com
|
||
|
</realserver>
|
||
|
|
||
|
|
||
|
<realserver>
|
||
|
url=http://127.0.0.1:82/apache2-default/index.html
|
||
|
type = get
|
||
|
code=200,201
|
||
|
hostheader = www.myhost.com
|
||
|
</realserver>
|
||
|
|
||
|
<realserver>
|
||
|
url= http://10.2.2.1
|
||
|
type = regex
|
||
|
regex= /qdAbm/
|
||
|
</realserver>
|
||
|
|
||
|
<realserver>
|
||
|
type = tcp
|
||
|
url = 10.2.2.1
|
||
|
port =80
|
||
|
</realserver>
|
||
|
|
||
|
<realserver>
|
||
|
type = get
|
||
|
url = ftp://USER:PASSWORD@10.2.3.1
|
||
|
code=200,201
|
||
|
</realserver>
|
||
|
###########################################################
|
||
|
|
||
|
|
||
|
|
||
|
EOF
|
||
|
|
||
|
}
|
||
|
|
||
|
if ( $option{ h } || $option{ H } )
|
||
|
{
|
||
|
exit;
|
||
|
}
|
||
|
|
||
|
if ( $option{ v } ) { print "$VERSION\n"; exit; }
|
||
|
|
||
|
use vars qw(@ISA);
|
||
|
use Net::Server::PreFork;
|
||
|
@ISA = qw(Net::Server::PreFork);
|
||
|
|
||
|
my $port;
|
||
|
my $host;
|
||
|
my $reparse;
|
||
|
my $cidr_allow;
|
||
|
my $log_level;
|
||
|
my $log_file;
|
||
|
my $pid_file;
|
||
|
my $daemon;
|
||
|
my $min_servers;
|
||
|
my $min_spare_servers;
|
||
|
my $max_spare_servers;
|
||
|
my $max_servers;
|
||
|
my $html_content;
|
||
|
|
||
|
my $conf_file = $option{ c } || "/etc/check.conf";
|
||
|
my $pwd = $ENV{ PWD };
|
||
|
$conf_file =~ s/^\./$pwd/;
|
||
|
$conf_file =~ s/^([^\/])/$pwd\/$1/;
|
||
|
my $basename = basename( $conf_file, ( '.conf' ) );
|
||
|
my $CONF = parse_conf( $conf_file );
|
||
|
|
||
|
my $reparse_one = 0;
|
||
|
|
||
|
$SIG{ HUP } = sub { $reparse_one = 1; };
|
||
|
|
||
|
my @TEST;
|
||
|
my $test_list = $CONF->{ realserver };
|
||
|
if ( ref( $test_list ) eq "ARRAY" )
|
||
|
{
|
||
|
@TEST = @{ $test_list };
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
@TEST = ( $test_list );
|
||
|
}
|
||
|
|
||
|
my $server = MyPackage->new(
|
||
|
{
|
||
|
port => $port,
|
||
|
host => $host,
|
||
|
cidr_allow => $cidr_allow,
|
||
|
log_level => $log_level,
|
||
|
child_communication => 1,
|
||
|
setsid => $daemon,
|
||
|
log_file => $log_file,
|
||
|
pid_file => $pid_file,
|
||
|
min_servers => $min_servers,
|
||
|
min_spare_servers => $min_spare_servers,
|
||
|
max_spare_servers => $max_spare_servers,
|
||
|
max_servers => $max_servers,
|
||
|
}
|
||
|
);
|
||
|
|
||
|
$server->run();
|
||
|
exit;
|
||
|
|
||
|
sub process_request
|
||
|
{
|
||
|
my $self = shift;
|
||
|
if ( $reparse || $reparse_one )
|
||
|
{
|
||
|
$CONF = parse_conf( $conf_file );
|
||
|
}
|
||
|
my $result;
|
||
|
my @TEST;
|
||
|
my $test_list = $CONF->{ realserver };
|
||
|
|
||
|
if ( ref( $test_list ) eq "ARRAY" )
|
||
|
{
|
||
|
@TEST = @{ $test_list };
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
@TEST = ( $test_list );
|
||
|
}
|
||
|
|
||
|
my $allow_code;
|
||
|
my $test_item;
|
||
|
my $html_data;
|
||
|
foreach my $test ( @TEST )
|
||
|
{
|
||
|
my $uri;
|
||
|
my $authority;
|
||
|
my $URL = $test->{ url };
|
||
|
$uri = URI->new( $URL );
|
||
|
$authority = $uri->authority;
|
||
|
|
||
|
if ( exists $test->{ type } )
|
||
|
{
|
||
|
if ( $test->{ type } =~ /get/i )
|
||
|
{
|
||
|
my $allow_code = $test->{ code } || '200,201';
|
||
|
$test_item++;
|
||
|
my $host = $test->{ hostheader } || $authority;
|
||
|
my $res = get( $URL, $allow_code, $host );
|
||
|
if ( $html_content == 1 )
|
||
|
{
|
||
|
if ( $res )
|
||
|
{
|
||
|
$html_data .= "GET OK $URL<br>\r\n";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$html_data .= "GET NOK $URL<br>\r\n";
|
||
|
}
|
||
|
}
|
||
|
if ( $html_content == 2 )
|
||
|
{
|
||
|
if ( $res )
|
||
|
{
|
||
|
$html_data .= "<tr><td>GET</td><td>OK</td><td>$URL</td></tr>\r\n";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$html_data .= "<tr><td>GET</td><td>NOK</td><td>$URL</td></tr>\r\n";
|
||
|
}
|
||
|
}
|
||
|
$result += $res;
|
||
|
}
|
||
|
if ( $test->{ type } =~ /regex/i )
|
||
|
{
|
||
|
my $regex = $test->{ regex };
|
||
|
$test_item++;
|
||
|
my $host = $test->{ hostheader } || $authority;
|
||
|
my $res = regex( $URL, $regex, $host );
|
||
|
if ( $html_content == 1 )
|
||
|
{
|
||
|
if ( $res )
|
||
|
{
|
||
|
$html_data .= "REGEX OK $URL<br>\r\n";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$html_data .= "REGEX NOK $URL<br>\r\n";
|
||
|
}
|
||
|
}
|
||
|
if ( $html_content == 2 )
|
||
|
{
|
||
|
if ( $res )
|
||
|
{
|
||
|
$html_data .= "<tr><td>REGEX</td><td>OK</td><td>$URL</td></tr>\r\n";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$html_data .= "<tr><td>REGEX</td><td>NOK</td><td>$URL</td></tr>\r\n";
|
||
|
}
|
||
|
}
|
||
|
$result += $res;
|
||
|
}
|
||
|
if ( $test->{ type } =~ /tcp/i )
|
||
|
{
|
||
|
$test_item++;
|
||
|
my $PORT = $test->{ port } || 80;
|
||
|
my $res = TCP( $URL, $PORT );
|
||
|
if ( $html_content == 1 )
|
||
|
{
|
||
|
if ( $res )
|
||
|
{
|
||
|
$html_data .= "TCP OK $URL<br>\r\n";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$html_data .= "TCP NOK $URL<br>\r\n";
|
||
|
}
|
||
|
}
|
||
|
if ( $html_content == 2 )
|
||
|
{
|
||
|
if ( $res )
|
||
|
{
|
||
|
$html_data .= "<tr><td>TCP</td><td>OK</td><td>$URL</td></tr>\r\n";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$html_data .= "<tr><td>TCP</td><td>NOK</td><td>$URL</td></tr>\r\n";
|
||
|
}
|
||
|
}
|
||
|
$result += $res;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
my $len;
|
||
|
if ( $html_content == 1 )
|
||
|
{
|
||
|
$html_data = "\r\n<html><body>\r\n$html_data</body></html>\r\n";
|
||
|
$len = ( length( $html_data ) ) - 2;
|
||
|
}
|
||
|
if ( $html_content == 2 )
|
||
|
{
|
||
|
$html_data = "\r\n<table align='center' border='1' >\r\n$html_data</table>\r\n";
|
||
|
$len = ( length( $html_data ) ) - 2;
|
||
|
}
|
||
|
|
||
|
if ( $result != $test_item )
|
||
|
{
|
||
|
my $header = "HTTP/1.0 503 Service Unavailable\r\n";
|
||
|
if ( $html_content )
|
||
|
{
|
||
|
$header .= "Content-Length: $len\r\nContent-Type: text/html; charset=iso-8859-1\r\n";
|
||
|
}
|
||
|
print $header . $html_data;
|
||
|
return;
|
||
|
}
|
||
|
my $header = "HTTP/1.0 200 OK\r\n";
|
||
|
if ( $html_content )
|
||
|
{
|
||
|
$header .= "Content-Length: $len\r\nContent-Type: text/html; charset=iso-8859-1\r\n";
|
||
|
}
|
||
|
print $header. $html_data;
|
||
|
}
|
||
|
|
||
|
1;
|
||
|
|
||
|
##########################################################
|
||
|
##########################################################
|
||
|
# function to REGEX on a GET from URL
|
||
|
# arg: uri
|
||
|
# regex to test (with extra parameter like perl e.g. /\bweb\d{2,3}/i )
|
||
|
# IP
|
||
|
# port (optionnal: default=80)
|
||
|
# ret: 0 if no reply
|
||
|
# 1 if reply
|
||
|
##########################################################
|
||
|
##########################################################
|
||
|
sub regex
|
||
|
{
|
||
|
my $url = shift;
|
||
|
my $regex = shift;
|
||
|
my $host = shift;
|
||
|
|
||
|
$regex =~ /\/(.*)\/(.*)/;
|
||
|
my $reg = $1;
|
||
|
my $ext = $2;
|
||
|
my %options;
|
||
|
$options{ 'agent' } = "LB_REGEX_PROBE/$VERSION";
|
||
|
$options{ 'timeout' } = 10;
|
||
|
my $ua = LWP::UserAgent->new( %options );
|
||
|
my $response = $ua->get( $url, "Host" => $host );
|
||
|
if ( $response->is_success )
|
||
|
{
|
||
|
my $html = $response->content;
|
||
|
if ( $ext =~ /i/ )
|
||
|
{
|
||
|
if ( $html =~ /$reg/si )
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( $html =~ /$reg/s )
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
##########################################################
|
||
|
##########################################################
|
||
|
# function to GET an URL (HTTP or FTP) ftp://FTPTest:6ccount4F@brice!@172.29.0.146
|
||
|
# arg: uri
|
||
|
# allowed code (comma seaparated)
|
||
|
# IP
|
||
|
# port (optionnal: default=80)
|
||
|
# ret: 0 if not the expected vcode
|
||
|
# 1 if the expected code is returned
|
||
|
##########################################################
|
||
|
##########################################################
|
||
|
sub get
|
||
|
{
|
||
|
my $url = shift;
|
||
|
my $code = shift;
|
||
|
my $host = shift;
|
||
|
|
||
|
$code =~ s/\s*//g;
|
||
|
my %codes = map { $_ => $_ } split /,/, $code;
|
||
|
my %options;
|
||
|
$options{ 'agent' } = "LB_HTTP_PROBE/$VERSION";
|
||
|
$options{ 'timeout' } = 10;
|
||
|
my $ua = LWP::UserAgent->new( %options );
|
||
|
my $response = $ua->get( $url, "Host" => $host );
|
||
|
if ( $response->is_success )
|
||
|
{
|
||
|
my $rc = $response->{ _rc };
|
||
|
if ( defined $codes{ $rc } )
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
##########################################################
|
||
|
##########################################################
|
||
|
# function to test a port on a host
|
||
|
# arg: hostip
|
||
|
# port
|
||
|
# timeout
|
||
|
# ret: 0 if not open
|
||
|
# 1 if open
|
||
|
##########################################################
|
||
|
##########################################################
|
||
|
sub TCP
|
||
|
{
|
||
|
use IO::Socket::PortState qw(check_ports);
|
||
|
my $remote_host = shift;
|
||
|
my $remote_port = shift;
|
||
|
my $timeout = shift;
|
||
|
|
||
|
my %porthash = ( tcp => { $remote_port => { name => 'to_test', } } );
|
||
|
check_ports( $remote_host, $timeout, \%porthash );
|
||
|
return $porthash{ tcp }{ $remote_port }{ open };
|
||
|
}
|
||
|
|
||
|
##############################################
|
||
|
# parse config file
|
||
|
# IN: File PATH
|
||
|
# Out: Ref to a hash with config data
|
||
|
##############################################
|
||
|
sub parse_conf
|
||
|
{
|
||
|
my $file = shift;
|
||
|
|
||
|
my $conf = new Config::General(
|
||
|
-ConfigFile => $file,
|
||
|
-ExtendedAccess => 1,
|
||
|
-AllowMultiOptions => "yes"
|
||
|
);
|
||
|
my %config = $conf->getall;
|
||
|
$port = $config{ port } || 9898;
|
||
|
$host = $config{ host } || '127.0.0.1';
|
||
|
$reparse = $config{ reparse } || 0;
|
||
|
$cidr_allow = $config{ cidr_allow } || '127.0.0.0/8';
|
||
|
$log_level = $config{ log_level } || 0;
|
||
|
$log_file = $config{ log_file } || "/var/log/check.log";
|
||
|
$pid_file = $config{ pid_file } || "/var/run/check.pid";
|
||
|
$daemon = $config{ daemon } || 0;
|
||
|
$min_servers = $config{ min_servers } || 5;
|
||
|
$min_spare_servers = $config{ min_spare_servers } || 2;
|
||
|
$max_spare_servers = $config{ max_spare_servers } || 10;
|
||
|
$max_servers = $config{ max_servers } || 50;
|
||
|
$html_content = $config{ content } || 0;
|
||
|
|
||
|
$pid_file =~ s/\$\$\$/$basename/g;
|
||
|
$pid_file =~ s/\$\$/$$/g;
|
||
|
$log_file =~ s/\$\$\$/$basename/g;
|
||
|
$log_file =~ s/\$\$/$$/g;
|
||
|
|
||
|
if ( !( keys %{ $config{ realserver } } ) )
|
||
|
{
|
||
|
die "No farm to test\n";
|
||
|
}
|
||
|
return ( \%config );
|
||
|
}
|
||
|
|