--- projects/cms/source/host/ihost-perl/ihost.pl 2001/01/29 09:19:16 1.8 +++ projects/cms/source/host/ihost-perl/ihost.pl 2002/05/18 18:15:56 1.49 @@ -1,15 +1,33 @@ #!/usr/bin/perl -w +# +# i-scream central monitoring system +# Copyright (C) 2000-2002 i-scream +# +# 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. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + # ----------------------------------------------------------- # Perl i-scream Host. +# http://www.i-scream.org.uk # # An all-in-one script to act as an i-scream host on -# a typical Unix/Linux box. You may adapt the data-gathering -# methods as you see fit. -# - pjm2@ukc.ac.uk +# a typical Unix/Linux box. # -# $Author: pjm2 $ -# $Id: ihost.pl,v 1.8 2001/01/29 09:19:16 pjm2 Exp $ +# $Author: tdb $ +# $Id: ihost.pl,v 1.49 2002/05/18 18:15:56 tdb Exp $ #------------------------------------------------------------ $| = 1; @@ -31,6 +49,12 @@ use vars qw ( $tcp_port $filter_addr $file_list + $fqdn + $pidfile + $retry_wait + $ostype + $key + @data ); if (@ARGV != 2) { @@ -41,265 +65,426 @@ $filter_manager_addr = $ARGV[0]; $filter_manager_port = $ARGV[1]; $seq_no = 1; +$retry_wait = 60; +# work out our platform, if we can. +$ostype = `uname -s`; +chomp $ostype; +$ostype = "unknown" if not defined $ostype; + +# write our PID to a file +# use home dir by default +#$pidfile = $ENV{"HOME"}; +# or drop it in /var/tmp if we can't find HOME +$pidfile = "/var/tmp" if not defined $pidfile; +$pidfile .= "/.ihost.pid"; +&write_pid(); + &tcp_configure(); +&send_tcp_heartbeat(); &send_udp_packet(); $last_udp_time = time; $last_tcp_time = time; while (1) { - if (time >= $last_udp_time + $udp_update_time) { + my($time) = time; + if ($time >= $last_udp_time + $udp_update_time) { &send_udp_packet(); - $last_udp_time = time; + $last_udp_time = $time; } - if (time >= $last_tcp_time + $tcp_update_time) { + if ($time >= $last_tcp_time + $tcp_update_time) { &send_tcp_heartbeat(); - $last_tcp_time = time; + $last_tcp_time = $time; } - `sleep 1`; + my($next_udp) = $udp_update_time - $time + $last_udp_time; + my($next_tcp) = $tcp_update_time - $time + $last_tcp_time; + my($delay); + if ($next_udp < $next_tcp) { + $delay = $next_udp + } + else { + $delay = $next_tcp; + } + sleep $delay; } +# we'll probably never get here... +`rm -f $pidfile`; exit(0); + +#----------------------------------------------------------------------- +# wait_then_retry +# Waits for the period of time specified in $retry_wait, then attempts +# to reconfigure with the server. +#----------------------------------------------------------------------- +sub wait_then_retry() { + print "Will retry configuration with filter manager in $retry_wait seconds.\n"; + sleep $retry_wait; +} + + +#----------------------------------------------------------------------- +# tcp_configure +# Establishes a TCP connection to the specified i-scream filter manager. +# The host then requests details from the server, such as the intervals +# at which to send UDP packets. +#----------------------------------------------------------------------- sub tcp_configure() { - my($sock) = new IO::Socket::INET( - PeerAddr => $filter_manager_addr, - PeerPort => $filter_manager_port, - Proto => 'tcp' - ) or die "Could not perform configuration via TCP: $!\n"; + while (1) { + my($sock) = new IO::Socket::INET( + PeerAddr => $filter_manager_addr, + PeerPort => $filter_manager_port, + Proto => 'tcp' + ) or die "Cannot connect!"; + if (!defined $sock) { + print "IHOST ERROR: Could not connect to $filter_manager_addr:$filter_manager_port.\n"; + print "Please check that there is an i-scream server at this address.\n"; + wait_then_retry(); + next; + } - die "Could not connect to the i-scream filter manager: $!\n" unless $sock; - - # Now run through the configuration process. - my($response); + # Now run through the configuration process... + my($response); - print $sock "STARTCONFIG\n"; - $response = <$sock>; - if (!chop $response eq "OK") { - print "The i-scream server rejected the STARTCONFIG command. Terminated."; - exit(1); - } + print $sock "STARTCONFIG\n"; + $response = <$sock>; + if ($response && !($response eq "OK\n")) { + print "The i-scream server rejected the STARTCONFIG command.\n"; + close($sock); + wait_then_retry(); + next; + } - print "Config started okay.\n"; + print "Config started okay.\n"; - print $sock "LASTMODIFIED\n"; - $response = <$sock>; - chop $response; - $last_modified = $response; + print $sock "LASTMODIFIED\n"; + $response = <$sock>; + if (!$response || $response eq "ERROR\n") { + print "The i-scream server did not provide the LASTMODIFIED value.\n"; + close($sock); + wait_then_retry(); + next; + } + chomp $response; + $last_modified = $response; - print "Config last modified: ". (scalar localtime $last_modified/1000) . "\n"; + print "Config last modified: ". (scalar localtime $last_modified/1000) . "\n"; - print $sock "FILELIST\n"; - $response = <$sock>; - chop $response; - $file_list = $response; + print $sock "FILELIST\n"; + $response = <$sock>; + if (!$response || $response eq "ERROR\n") { + print "The i-scream server did not provide a configuration file list.\n"; + close($sock); + wait_then_retry(); + next; + } + chomp $response; + $file_list = $response; - print "File list obtained: $file_list\n"; + print "File list obtained: $file_list\n"; - print $sock "UDPUpdateTime\n"; - $response = <$sock>; - chop $response; - $udp_update_time = $response; + print $sock "FQDN\n"; + $response = <$sock>; + if (!$response || $response eq "ERROR\n") { + print "The i-scream server did not tell us our FQDN.\n"; + close($sock); + wait_then_retry(); + next; + } + chomp $response; + $fqdn = $response; - print $sock "TCPUpdateTime\n"; - $response = <$sock>; - chop $response; - $tcp_update_time = $response; + print "FQDN returned: $fqdn\n"; + + print $sock "UDPUpdateTime\n"; + $response = <$sock>; + if (!$response || $response eq "ERROR\n") { + print "The i-scream server did not give us a UDPUpdateTime.\n"; + close($sock); + wait_then_retry(); + next; + } + chomp $response; + $udp_update_time = $response; + + print $sock "TCPUpdateTime\n"; + $response = <$sock>; + if (!$response || $response eq "ERROR\n") { + print "The i-scream server did not give us a TCPUpdateTime.\n"; + close($sock); + wait_then_retry(); + next; + } + chomp $response; + $tcp_update_time = $response; - print "UDP packet period: $udp_update_time seconds.\nTCP heartbeat period: $tcp_update_time seconds.\n"; + print "UDP packet period: $udp_update_time seconds.\nTCP heartbeat period: $tcp_update_time seconds.\n"; - print $sock "ENDCONFIG\n"; - $response = <$sock>; - chomp $response; - if (!$response eq "OK") { - print "ENDCONFIG command to server failed. Terminated.\n"; - exit(1); - } + print $sock "ENDCONFIG\n"; + $response = <$sock>; + if ($response && !($response eq "OK\n")) { + print "ENDCONFIG command to server failed. Terminated.\n"; + close($sock); + wait_then_retry(); + next; + } - print "Config ended.\n"; + print "Config ended.\n"; - print $sock "FILTER\n"; - $response = <$sock>; - chop $response; - $response =~ /(.*);(.*);(.*)/; - ($filter_addr, $udp_port, $tcp_port) = ($1, $2, $3); + print $sock "FILTER\n"; + $response = <$sock>; + if (!$response) { + print "Failed: Could not get a filter address from the filter manager.\n"; + close($sock); + wait_then_retry(); + next; + } + chomp $response; + if ($response eq "ERROR") { + print "There are no active configured filters for your host.\n"; + close($sock); + wait_then_retry(); + next; + } + $response =~ /^(.*);(.*);(.*)/; + ($filter_addr, $udp_port, $tcp_port) = ($1, $2, $3); + unless (defined($filter_addr) && defined($udp_port) && defined($tcp_port)) { + print "Failed: Filter address response from server did not make sense: $response\n"; + close($sock); + wait_then_retry(); + next; + } - print "Got filter data ($filter_addr, $udp_port, $tcp_port)\n"; + print "Got filter data ($filter_addr, $udp_port, $tcp_port)\n"; - print $sock "END\n"; - $response = <$sock>; - chop $response; - if ($response eq "OK") { - print "Host successfully configured via TCP.\n" - } - else { - print "The server failed the host configuration on the END command."; - exit(1); - } + print $sock "END\n"; + $response = <$sock>; + if ($response && ($response eq "OK\n")) { + print "Host successfully configured via TCP.\n" + } + else { + print "The server failed the host configuration on the END command.\n"; + close($sock); + wait_then_retry(); + next; + } - close($sock); + close($sock); - print "Configuration finished sucessfully!\n"; + print "Configuration finished sucessfully!\n"; + last; + } + return; } + +#----------------------------------------------------------------------- +# send_udp_packet +# Sends a UDP packet to an i-scream filter. +# The packet contains XML markup describing some of the machine's state. +# Receipt of UDP packets is not guaranteed. +#----------------------------------------------------------------------- sub send_udp_packet() { - my(@statgrab) = `./statgrab.pl`; - my(%packet); - for (my($i) = 0; $i < $#statgrab; $i++) { - $statgrab[$i] =~ /^([^\s]*) (.*)$/; - $packet{$1} = $2; + my($plugins_dir) = "plugins"; + + opendir PLUGINS, $plugins_dir; + my(@plugins) = readdir PLUGINS; + foreach my $plugin (@plugins) { + push @data, `$plugins_dir/$plugin $ostype` if -x "$plugins_dir/$plugin" && -f "$plugins_dir/$plugin"; } + # get some extra data my($date) = time; + my($ip); + $ip = inet_ntoa(scalar(gethostbyname(hostname())) || 'localhost') or $ip = 'localhost'; - my($disk_info) = ""; - my($i) = 0; - while (defined $packet{"packet.disk.p$i.attributes.mount"}) { - $disk_info .= ""; - $disk_info .= qq/$packet{"packet.disk.p$i.attributes.name"}<\/name>/; - $disk_info .= qq/$packet{"packet.disk.p$i.attributes.kbytes"}<\/kbytes>/; - $disk_info .= qq/$packet{"packet.disk.p$i.attributes.used"}<\/used>/; - $disk_info .= qq/$packet{"packet.disk.p$i.attributes.avail"}<\/avail>/; - $disk_info .= qq/$packet{"packet.disk.p$i.attributes.mount"}<\/mount>/; - $disk_info .= ""; - ++$i; - } - $disk_info .= ""; - - my($hostname) = hostname(); - $hostname =~ s/\..*$//g; - `cat /etc/resolv.conf` =~ /domain\s+([^\s]+)/; - my($domainname) = $1; - my($machine_name) = "$hostname.$domainname"; - my($ip) = inet_ntoa(scalar(gethostbyname($hostname)) || 'localhost'); - - # Build the XML packet this way, as we can clearly - # see the structure and contents... I like this ;-) - my($xml) = < - - $packet{"packet.load.load1"} - $packet{"packet.load.load5"} - $packet{"packet.load.load15"} - - - $packet{"packet.os.name"} - $packet{"packet.os.release"} - $packet{"packet.os.platform"} - $packet{"packet.os.sysname"} - $packet{"packet.os.version"} - - - $packet{"packet.users.count"} - $packet{"packet.users.list"} - - - $packet{"packet.processes.total"} - $packet{"packet.processes.sleeping"} - $packet{"packet.processes.zombie"} - $packet{"packet.processes.stopped"} - $packet{"packet.processes.cpu"} - - - $packet{"packet.cpu.idle"} - $packet{"packet.cpu.user"} - $packet{"packet.cpu.kernel"} - $packet{"packet.cpu.iowait"} - $packet{"packet.cpu.swap"} - - - $packet{"packet.memory.real"} - $packet{"packet.memory.free"} - - - $packet{"packet.memory.swap_total"} - $packet{"packet.memory.swap_free"} - - $disk_info - + # sort the data + @data = sort(grep(!/^$/, grep(/^packet\./, @data))); -EOF + # turn the array into some nice XML + my($xml) = &make_xml("", ""); - $xml =~ s/\n\s*//g; - my($sock) = new IO::Socket::INET ( PeerPort => $udp_port, PeerAddr => $filter_addr, Proto => 'udp' - ) or die "Socket: $!\n"; - + ) or die "Could not send UDP: $!\n"; + print $sock $xml or die "Could not send UDP packet: $!\n"; close($sock); $seq_no++; print "-"; + + return; } + +#----------------------------------------------------------------------- +# send_tcp_heartbeat +# Establishes a TCP connection to an i-scream filter. +# The heartbeat is used as a guaranteed "I'm alive" delivery mechanism. +# If we need to reconfigure, then we complete the heartbeat before +# doing so. +#----------------------------------------------------------------------- sub send_tcp_heartbeat() { + my ($doReconfigure) = 0; + my($sock) = new IO::Socket::INET( PeerAddr => $filter_addr, PeerPort => $tcp_port, Proto => 'tcp' - ) or die "Could not perform heartbeat via TCP: $!\n"; + ) or return; + if (!defined $sock) { + print "IHOST WARNING: Failed to deliver a heartbeat to the i-scream filter.\n"; + &tcp_configure(); + return; + } - die "Could not connect to the i-scream filter: $!\n" unless $sock; - # Now run through the configuration process. my($response); print $sock "HEARTBEAT\n"; $response = <$sock>; - chop $response; - if (!$response eq "OK") { + if (!$response eq "OK\n") { close($sock); print "Server gave wrong response to HEARTBEAT: $response\n"; + &tcp_configure(); return; } print $sock "CONFIG\n"; $response = <$sock>; - chop $response; - if (!$response eq "OK") { + if (!$response eq "OK\n") { close($sock); print "Server gave wrong response to CONFIG: $response\n"; + &tcp_configure(); return; } print $sock "$file_list\n"; $response = <$sock>; - chop $response; - if (!$response eq "OK") { + if (!$response eq "OK\n") { close($sock); print "Server gave wrong response to file list: $response\n"; + &tcp_configure(); return; } print $sock "$last_modified\n"; $response = <$sock>; - chop $response; - if ($response eq "ERROR") { + if ($response eq "ERROR\n") { close($sock); - &tcp_configure(); - return; + print "Server configuration changed. Reconfiguring with filter manager.\n"; + $doReconfigure = 1; } - if (!$response eq "OK") { + if (!$response eq "OK\n") { close($sock); print "Server gave wrong response to HEARTBEAT: $response\n"; + &tcp_configure(); return; } + print $sock "KEY\n"; + $key = <$sock>; + print $sock "ENDHEARTBEAT\n"; $response = <$sock>; - chop $response; - if (!$response eq "OK") { + if (!$response eq "OK\n") { close($sock); print "Server gave wrong response to ENDHEARTBEAT: $response\n"; + &tcp_configure(); return; } close($sock); print "^"; + + &tcp_configure() if $doReconfigure; + + return; +} + + +#----------------------------------------------------------------------- +# write_pid +# Writes the PID (process ID) of this instance to $pidfile. +# This is then used by a seperate script to check (and restart) ihost. +#----------------------------------------------------------------------- +sub write_pid() { + open PID, ">$pidfile"; + print PID $$; + close PID; + + return; +} + +#----------------------------------------------------------------------- +# make_xml +# Turns an array of plugins data into an XML string. +#----------------------------------------------------------------------- +sub make_xml() { + my($curlevel, $curline) = @_; + my($xmltemp) = ""; my($curtag) = ""; my($attributes) = ""; + while(1) { + $curline = shift(@data) if $curline eq ""; + return $xmltemp if not defined $curline; + chomp $curline; + # dealing with nest (or attributes) + if($curline =~ /^$curlevel([^\.\s]+\.)/) { + $curtag=$1; + if($curline =~ /^$curlevel$curtag([^\.\s]+)\s+(.*)$/) { + $xmltemp .= &make_xml("$curlevel$curtag", $curline); + } + elsif($curline =~ /^$curlevel$curtag(attributes)\.([^\.\s]+)\s+(.*)$/) { + $attributes .= " $2=\"$3\""; + } + else { + $xmltemp .= &make_xml("$curlevel$curtag", $curline); + } + my($nextline) = $data[0]; chomp $nextline if defined $nextline; + $curtag =~ s/(.*)\./$1/; + if((defined $nextline) && ($nextline =~ /^$curlevel$curtag\./)) { + $curline = ""; + } + else { + $xmltemp = "<$curtag$attributes>$xmltemp" unless $curtag eq ""; + return $xmltemp; + } + } + # dealing with value + elsif($curline =~ /^$curlevel([^\.\s]+)\s+(.*)$/) { + $curtag=$1; + $xmltemp=$2; + my($nextline) = $data[0]; chomp $nextline if defined $nextline; + if(defined $nextline && ($nextline =~ /^$curlevel$curtag\./ || $nextline =~ /^$curlevel$curtag\s+/)) { + $curline = ""; + } + else { + $xmltemp = "<$curtag$attributes>$xmltemp" unless $curtag eq ""; + return $xmltemp; + } + } + # dealing with a null value + elsif($curline =~ /^$curlevel([^\.\s]+)$/) { + # simply adding a space makes the above elsif deal with it :) + # just level with an empty tag in the XML + $curline .= " "; + } + # failing all that, skip the line + else { + $curline = ""; + } + } }