My AJP connector is sick!!!

Today, I had a problem between my Apache balancer and my Tomcat cluster. A virtual machine that was hosting two Tomcats was frozen. Something was wrong in my config and the Apache didn't show these Tomcats in error in the jk_status web interface. After a few minutes, I realized that the uriworkermap file was not configure on 'balancer' but directly on 'tomcat1' (one of the two Tomcats that were down).

So, I concluded that the Apache jk doesn't do any test on a worker if it isn't mapped with an uri.

But let'go back to my virtual machine that was frozen. It was not completely dead and a telnet 8010 was responding. Thus, my first conclusion was that I had a problem with my Apache. BAD WAY!!! The telnet was not efficient. I demonstrated that with a tiny perl script found on the net made to simulate an ajp ping.

You can use the script as this : time ./ajp_ping.pl server:8009

Many thanks to the author of this script. I don't have the link to his post anymore.

Here is the script content :

#!/usr/bin/perl -w

use warnings;
use strict;

use Socket;

my ($remote, $port) = split /:/, shift @ARGV, 2;

if (! $remote) {
$remote = 'localhost';
}
print "remote = $remote\n";

if (! $port) {
$port = 8009;
}
print "port = $port\n";

my ($iaddr, $paddr, $proto);

# If the port has anything other than numbers, we're assuming it is an
# /etc/services name.
if ($port =~ /\D/) {
$port = getservbyname $port, 'tcp' ;
}

die "Bad port, stopped" unless $port;
print "port = $port\n";

$iaddr = inet_aton($remote) || die "No host: $remote, stopped";
print "iaddr = $iaddr\n";

$paddr = sockaddr_in($port, $iaddr) || die "sockaddr: $!, stopped";
print "paddr = $paddr\n";

# Grab the number for TCP out of /etc/protocols.
$proto = getprotobyname 'tcp' ;
print "proto = $proto\n";

my $sock;
# PF_INET and SOCK_STREAM are constants imported by the Socket module. They
# are the same as what is defined in sys/socket.h.
socket $sock, PF_INET, SOCK_STREAM, $proto || die "socket: $!, stopped";
print "sock = $sock\n";

print "BEFORE CONNECT\n";
connect $sock, $paddr || die "connect: $!, stopped";
print "AFTER CONNECT\n";

# This is the ping packet. For detailed documentation, see
# http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
# I stole the exact byte sequence from
# http://sourceforge.net/project/shownotes.php?group_id=128058&release_id=438456
# instead of fully understanding the packet structure.
my $ping = pack 'C5' # Format template.
, 0x12, 0x34 # Magic number for server->container packets.
, 0x00, 0x01 # 2 byte int length of payload.
, 0x0A # Type of packet. 10 = CPing.
;

my @ping_values = unpack 'C5', $ping;
print "ping_values = " , join ' ', @ping_values , "\n";

# This is the expected pong packet. That is, this is what Tomcat sends back
# to indicate that it is operating OK.
my $expected = pack 'C5' # Format template.
, 0x41, 0x42 # Magic number for container->server packets.
, 0x00, 0x01 # 2 byte int length of payload.
, 0x09 # Type of packet. 9 = CPong reply.
;

syswrite $sock, $ping || die "syswrite: $!, stopped";

my $pong;
$pong = 'empty';
print "BEFORE READ\n";
sysread $sock, $pong, 5 || die "read: $!, stopped";
print "AFTER READ\n";

my @pong_values = unpack 'C5', $pong;
print "pong_values = " , join ' ', @pong_values , "\n";

close $sock || die "close: $!, stopped";

exit 0;