From a718498694fa4a84f1ad2c2940af7fb68eeb1d2c Mon Sep 17 00:00:00 2001 From: Stephen L Johnson Date: Fri, 22 Oct 1999 01:04:01 +0000 Subject: [PATCH] Initial import --- src/lib/Spong/Ack.pm | 152 ++++++++++++++ src/lib/Spong/AckList.pm | 124 +++++++++++ src/lib/Spong/History.pm | 113 ++++++++++ src/lib/Spong/HistoryList.pm | 156 ++++++++++++++ src/lib/Spong/HostList.pm | 316 ++++++++++++++++++++++++++++ src/lib/Spong/Info.pm | 125 ++++++++++++ src/lib/Spong/Service.pm | 386 +++++++++++++++++++++++++++++++++++ src/lib/Spong/ServiceList.pm | 194 ++++++++++++++++++ 8 files changed, 1566 insertions(+) create mode 100755 src/lib/Spong/Ack.pm create mode 100755 src/lib/Spong/AckList.pm create mode 100755 src/lib/Spong/History.pm create mode 100755 src/lib/Spong/HistoryList.pm create mode 100755 src/lib/Spong/HostList.pm create mode 100755 src/lib/Spong/Info.pm create mode 100755 src/lib/Spong/Service.pm create mode 100755 src/lib/Spong/ServiceList.pm diff --git a/src/lib/Spong/Ack.pm b/src/lib/Spong/Ack.pm new file mode 100755 index 0000000..fd38fb5 --- /dev/null +++ b/src/lib/Spong/Ack.pm @@ -0,0 +1,152 @@ +#!/usr/local/bin/perl +# +# This object represents an acknowledgment. When a problem happens, you can +# associate one of these objects with the host to let operators or other staff +# know that you are aware of the problem, and what you are doing about it. +# +# host - string FQDN of the machine its associated with +# start - time() that the ack was created +# end - time() that the ack will be over +# contact - arbitrary contact info +# services - arbitrary service names (sep by commas is standard) +# message - arbitrary message +# +# + new() - constructor (sets instance vars to arguments passed in) +# + gets/sets() - magical set/get functions (autoloaded based on func name) +# + display() - output format and view +# +# History: +# (1) Cleaned up (Ed July 30, 1997); + +package Spong::Ack; + +# Constructor. This expects all of its instance vars to be passed in when the +# object is created. See above for specific information on the variables. +# This object is dependent on the AckList object for loading the information +# about it from the database. + +sub new { + my( $class, $host, $start, $end, $contact, $services, $message ) = @_; + my $self = {}; + + $self->{'host'} = $host; + $self->{'start'} = $start; + $self->{'end'} = $end; + $self->{'contact'} = $contact; + $self->{'services'} = $services; + $self->{'message'} = $message; + + bless $self; + return $self; +} + + +# Get/Set methods, nothing fancy here... + +sub host { my $var = 'host'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub start { my $var = 'start'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub end { my $var = 'end'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub contact { my $var = 'contact'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub services { my $var = 'services'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub message { my $var = 'message'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } + + +# Display summary. Does both text and html, doesn't make any calls to +# sub-objects or other helper objects, just spits out the data that it has. +# +# brief: Just shows name of services and time ack is over. +# standard: Shows detailed record of service information (contact, message) +# full: (same as standard) + +sub display { + my( $self, $type, $view ) = @_; + + if( $type eq "text" ) { return $self->display_text( $view ); } + if( $type eq "html" ) { return $self->display_html( $view ); } +} + +sub display_text { + my( $self, $format ) = @_; + + if( $format eq "brief" ) { + print $self->host(), "-", $self->services(), "-", $self->end(); + } elsif( $format eq "standard" ) { + my( $d1, $m1, $y1 ) = (localtime( $self->end()))[3,4,5]; + my( $d2, $m2, $y2 ) = (localtime())[3,4,5]; + my $shost = $main::HOSTS{$self->host()}->{'display_name'}; + my $date; + my $message = $self->message(); + + $shost = (split( /\./, $self->host() ))[0] unless $shost; + print substr( $shost, 0, 12 ), " "x(14-length(substr($shost, 0, 12))); + + print substr( $self->services(), 0, 7 ); + print " "x(9-length(substr( $self->services(), 0, 7 ))); + + if( $d1 == $d2 && $m1 == $m2 && $y1 == $y2 ) { + $date = POSIX::strftime( "%H:%M", localtime($self->end()) ); + } else { + $date = POSIX::strftime( "%D", localtime($self->end()) ); + } + print $date, " "x(12-length($date)); + + $message =~ s/[\n]*$//; + print substr( $message, 0, 45 ); + } elsif( $format eq "full" ) { + $message = $self->message(); + + print "Service(s): ", $self->services(), "\n"; + print "Ack. Until: ", scalar localtime( $self->end() ), "\n"; + print "Ack. By: ", $self->contact(), "\n"; + + if( $message ne "" ) { + $message =~ s/[\n]*$/\n/; print "Message: $message"; } + } elsif( $format eq "special" ) { + print $self->host(), ":", $self->services(), ":", $self->end(); + } +} + +sub display_html { + my( $self, $format ) = @_; + + if( $format eq "brief" ) { + print "\n"; + print "\n"; + print "
"; + print $self->services(); + print ""; + print scalar localtime( $self->end() ); + print "
\n"; + + } elsif( $format eq "standard" || $format eq "full" ) { + my $message = $self->message(); + my $id = $self->host() . "-" . $self->services() . "-" . $self->end(); + + print "\n"; + print ""; + print "\n"; + print "\n"; + + print "\n"; + print "\n"; + + print "\n"; + print "\n"; + + if( $message ne "" ) { + print "\n"; + print "\n"; + } + print "
"; + print "Delete
Service(s): ", $self->services(), "
Ack. Until: ", scalar localtime( $self->end() ); + print "
Ack. By: ", $self->contact(), "
Message: $message
\n"; + } +} + +1; diff --git a/src/lib/Spong/AckList.pm b/src/lib/Spong/AckList.pm new file mode 100755 index 0000000..786e094 --- /dev/null +++ b/src/lib/Spong/AckList.pm @@ -0,0 +1,124 @@ +#!/usr/local/bin/perl +# +# This object represents an list of acknowledgments associated with a host. It +# knows how to load the Acks associated with a given host (since there can be +# more then one). Acks do not know how to load themselves, they must be +# brought to life from an AckList object. +# +# host - string FQDN of the machine its associated with +# acks - list of Ack objects +# +# + new() - constructor (sets instance vars to arguments passed in) +# + gets/sets() - magical set/get functions (autoloaded based on func name) +# + display() - output format and view +# +# History: +# (1) Cleaned up (Ed July 30, 1997); + +use Spong::Ack; + +package Spong::AckList; + +# Constructor. This requires the FQDN of the host that the Acks are associated +# with. If there are no Acks associated with that host, then undef is returned +# otherwise the AckList object is returned and the list of Acks are loaded. + +sub new { + my( $class, $host ) = @_; + my( @list, $file, $message ); + my $self = {}; + + + if( $host ) { + # Go through the list of acknowledgments for this host and add Ack + # objects for each one. + + opendir( DIR, "$main::SPONGDB/$host/acks" ); + while( defined( $file = readdir( DIR ) ) ) { + if( $file =~ /^\d+-(\d+)-(\d+)$/ ) { + my( $s, $e ) = ($1, $2); + if( time() >= $s && time() <= $e ) { + $message = ""; + open( FILE, "$main::SPONGDB/$host/acks/$file" ); + my $header = ; chomp $header; + my( $user, $serv ) = ( $header =~ /^(\S+) (.*)$/ ); + while( ) { $message .= $_; } + close( FILE ); + + push(@list, + Spong::Ack->new( $host, $s, $e, $user, $serv, $message )); + } + } + } + closedir( DIR ); + + # If we found any, then continue setting up and return self, otherwise + # return undef. + + if( $#list >= 0 ) { + $self->{'host'} = $host; + $self->{'acks'} = \@list; + bless $self; + return $self; + } else { + return undef; + } + } else { + $self->{'host'} = $host; + $self->{'acks'} = \@list; + + bless $self; + return $self; + } +} + +# Get/Set methods, acks() is a little fancy in that it return a list rather +# then just the list reference (easier for others to deal with). + +sub host { my $var = 'host'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub acks { my $var = 'acks'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return @{$_[0]->{$var}}; } + + +# Used to add acknowledgements to the list. Typically used in the case where +# you want to show acknowledgements on a variety of hosts and you start with +# a blank list. + +sub add_acks { + my( $self, $add ) = @_; + + if( ref( $add ) eq "Spong::AckList" ) { + foreach( $add->acks() ) { + push( @{$self->{'acks'}}, $_ ); + } + } elsif( ref( $add ) eq "Spong::Ack" ) { + push( @{$self->{'acks'}}, $add ); + } +} + + +# Pretty simple display, just iterate over the Acks and tell each of them to +# display themselves in the way that we were told. + +sub display { + my( $self, $format, $view ) = @_; + + if( $view eq "standard" && $format eq "text" ) { + print "Host Service Until Message\n"; + print "------------ ------- ---------- "; + print "------------------------------------------\n"; + } + + sub bytime { return $b->end() <=> $a->end(); } + + foreach( sort bytime $self->acks() ) { + $_->display( $format, $view ); + + if( $format eq "text" ) { print "\n"; } + if( $format eq "html" ) { print "

"; } + } +} + + +1; diff --git a/src/lib/Spong/History.pm b/src/lib/Spong/History.pm new file mode 100755 index 0000000..89deb77 --- /dev/null +++ b/src/lib/Spong/History.pm @@ -0,0 +1,113 @@ +#!/usr/local/bin/perl +# +# This object represents an event that has happened that a record should be +# kept for. The current case is when a service changes state (color change). +# An record representing that change is written to the database so that you +# can see a record of these events over time. +# +# host - string FQDN of the machine its associated with +# type - type of history event (status change, etc...) +# time - time (time() format) that the event occurred +# service - service name this history is associated with +# color - color of the service at time history event was created +# other - summary information about the history event +# +# + new() - constructor (sets instance vars to arguments passed in) +# + gets/sets() - magical set/get functions (autoloaded based on func name) +# + display() - output format and view +# +# History: +# (1) Cleaned up (Ed July 31, 1997); + +package Spong::History; + +# Constructor. A simple constructor that just sets all of the instance vars +# according to what was passed in. All vars should be set on creation. + +sub new { + my( $class, $host, $type, $time, $service, $color, $other ) = @_; + my $self = {}; + + $self->{'host'} = $host; + $self->{'type'} = $type; + $self->{'time'} = $time; + $self->{'service'} = $service; + $self->{'color'} = $color; + $self->{'other'} = $other; + + bless $self; + return $self; +} + +# Get/Set methods, nothing fancy here... + +sub host { my $var = 'host'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub type { my $var = 'type'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub time { my $var = 'time'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub service { my $var = 'service'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub color { my $var = 'color'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub other { my $var = 'other'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } + + +# Display summary. Does both text and html, doesn't make any calls to +# sub-objects or other helper objects, just spits out the data that it has. +# This is a little different in that there are not different views of the +# data. The views come into play with the HistoryList in that more days worth +# of history is shown with the more detailed views. + +sub display { + my( $self, $type, $view ) = @_; + + if( $type eq "text" ) { return $self->display_text( $view ); } + if( $type eq "html" ) { return $self->display_html( $view ); } +} + +sub display_text { + my( $self, $view ) = @_; + my( $min, $hour ) = (localtime($self->time()))[1,2]; + my $shost = $main::HOSTS{$self->host()}->{'display_name'}; + + $shost = (split( /\./, $self->host() ))[0] unless $shost; + + print $self->color(), " "x(7-length($self->color())); + print sprintf( "%2.2d:%2.2d ", $hour, $min ); + print substr( $shost, 0, 12 ), " "x(14-length(substr($shost, 0, 12))); + print substr( $self->service(), 0, 5 ); + print " "x(7-length(substr( $self->service(), 0, 5 ))); + print $self->other(), "\n"; +} + +sub display_html { + my( $self, $view ) = @_; + my( $min, $hour ) = (localtime($self->time()))[1,2]; + my $shost = $main::HOSTS{$self->host()}->{'display_name'}; + my $color = $self->color(); + my $service = $self->service(); + my $other = $self->other(); + + $shost = (split( /\./, $self->host() ))[0] unless $shost; + + print ""; + + if( $main::WWW_USE_IMAGES == 1 ) { + print "$color"; + } else { + print ""; + print "
"; + print " 
"; + } + + print ""; + printf ( "%2.2d:%2.2d", $hour, $min ); + print "$shost \n"; + print "$service\n"; + print "$other\n"; +} + +1; diff --git a/src/lib/Spong/HistoryList.pm b/src/lib/Spong/HistoryList.pm new file mode 100755 index 0000000..a48a94c --- /dev/null +++ b/src/lib/Spong/HistoryList.pm @@ -0,0 +1,156 @@ +#!/usr/local/bin/perl +# +# This object represents a list of history events that happen on a given host +# or a set of hosts. HistoryLists are a little unique in that they don't have +# to be associated with one specific host, they can contain events from a +# number of hosts, and they can be populated from an empty container. +# +# host - string FQDN of the machine its associated with +# events - list of history objects. +# +# + new() - constructor (sets instance vars to arguments passed in) +# + gets/sets() - magical set/get functions (autoloaded based on func name) +# + display() - output format and view +# +# + add_history() - used to add history objects to the list +# +# History: +# (1) Cleaned up (Ed July 31, 1997); + +use Spong::History; + +package Spong::HistoryList; + +# Constructor. A simple constructor that will load all the history events +# associated with a particular host (if a hostname is passed in), otherwise +# it just sets up an empty object that expects to be filled by someone else. +# If there are no History events associated with a host, then undef is returned + +sub new { + my( $class, $host ) = @_; + my $self = {}; + my( @list ); + + # If we are given a host name, then look up the events for that host, + # otherwise, just return a shell that will be filled later. + + if( $host ) { + return undef if ! -d "$main::SPONGDB/$host/history"; + + # Go through the current history, and build history objects for each + # item in the history. + + open( FILE, "$main::SPONGDB/$host/history/current" ); + while( ) { + chomp; + if( /^status (\d+) (\S+) (green|yellow|red) (.*)$/ ) { + push( @list, + Spong::History->new( $host, "status", $1, $2, $3, $4 ) ); + } elsif( /^ack (\d+) (\S+) (.*)$/ ) { + push( @list, + Spong::History->new( $host, "ack", $1, $2, "blue", $3 )); + } + } + close( FILE ); + } + + $self->{'host'} = $host; + $self->{'events'} = \@list; + + bless $self; + return $self; +} + + +# Get/Set methods, events() is a little fancy in that it return a list rather +# then just the list reference (easier for others to deal with). + +sub host { my $var = 'host'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub events { my $var = 'events'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return @{$_[0]->{$var}}; } + + +# Used to add history events to the list. Typically used in the case where +# you want to show history events on a variety of hosts and you start with +# a blank list. + +sub add_history { + my( $self, $add ) = @_; + + if( ref( $add ) eq "Spong::HistoryList" ) { + foreach( $add->events() ) { + push( @{$self->{'events'}}, $_ ); + } + } elsif( ref( $add ) eq "Spong::History" ) { + push( @{$self->{'events'}}, $add ); + } +} + + +# Display summary. Does both text and html, does rely on the History object +# to display itself, but this adds to it by showing header information in +# tables, and breaking output up by days. +# +# brief: Shows events that have happened in the last 24 hours +# standard: Shows events that have happened in the last 7 days +# full: Shows events that have happened in the last 30 days + +sub display { + my( $self, $type, $view ) = @_; + + if( $type eq "text" ) { return $self->display_text( $view ); } + if( $type eq "html" ) { return $self->display_html( $view ); } +} + +sub display_text { + my( $self, $format ) = @_; + my( $event, $date ); + my( $day ) = 24*60*60; + + sub bytime { return $b->time() <=> $a->time(); } + + foreach $event ( sort bytime $self->events() ) { + if( ($format eq "brief" && $event->time() > (time() - $day)) || + ($format eq "standard" && $event->time() > (time() - 7*$day)) || + ($format eq "full" && $event->time() > (time() - 31*$day)) ) { + + my $dstr = POSIX::strftime( "%A, %D", localtime($event->time()) ); + if( $dstr ne $date ) { + if( $date ne "" ) { print "\n"; } + print "$dstr\n", "-"x78, "\n"; + $date = $dstr; + } + + $event->display_text(); + } + } +} + +sub display_html { + my( $self, $format ) = @_; + my( $event, $date ); + my( $day ) = 24*60*60; + + sub bytime { return $b->time() <=> $a->time(); } + + foreach $event ( sort bytime $self->events() ) { + if( ($format eq "brief" && $event->time() > (time() - $day)) || + ($format eq "standard" && $event->time() > (time() - 7*$day)) || + ($format eq "full" && $event->time() > (time() - 31*$day)) ) { + + my $dstr = POSIX::strftime( "%A, %D", localtime($event->time()) ); + if( $dstr ne $date ) { + if( $date ne "" ) { print "
"; } + print "$dstr\n


\n"; + print "\n"; + $date = $dstr; + } + + $event->display_html(); + } + } + print "
\n"; +} + +1; diff --git a/src/lib/Spong/HostList.pm b/src/lib/Spong/HostList.pm new file mode 100755 index 0000000..110afe9 --- /dev/null +++ b/src/lib/Spong/HostList.pm @@ -0,0 +1,316 @@ +#!/usr/local/bin/perl +# +# This represents a collection of hosts, either a group (including all hosts), +# a single host, or a list of hosts by FDQN name. +# +# name - name of the group the HostList represents +# hostnames - a list of string names of the hosts in the list +# hosthash - a hash of Host objects keyed by hostname. +# +# + new() - constructor (sets instance vars to arguments passed in) +# + gets/sets() - magical set/get functions (autoloaded based on func name) +# + display() - output format and view +# +# + display_problems() - shows information only about hosts that have services +# that indicate a problem +# +# + host - used to get at a specific host by name +# + hosts - used to get back a generic list of hosts that are stored +# + add - adds a specific host to the list. +# +# History: +# (1) Cleaned up (Ed July 31, 1997); + +use Spong::Host; + +package Spong::HostList; + +# This constructor expects one of four different types of options. Either an +# empty string specifying that no actual hosts will be loaded, the string +# "all" to load all the hosts, the string containing a group name that +# represents a list of hosts, or a reference to an array that contains a list +# of hostnames in which case each host will be loaded individually. + +sub new { + my( $class, $group ) = @_; + my( @names, %hosts, $name ) = (); + my $self = {}; + + if( $group eq "" ) { + %hosts = (); + @names = (); + $self->{'name'} = ""; + } elsif( ref( $group ) eq "ARRAY" ) { + foreach $name ( @$group ) { + my $object = Spong::Host->new( $name ); + if( $object ) { $hosts{$name} = $object; push( @names, $name ); } + } + $self->{'name'} = ""; + } else { + if( $group eq "all" ) { + foreach $name ( @main::HOSTS_LIST ) { + my $object = Spong::Host->new( $name ); + if( $object ) { $hosts{$name} = $object; push( @names, $name ); } + } + } else { + if( ref( $main::GROUPS{$group}->{'members'} ) eq "ARRAY" ) { + foreach $name ( @{$main::GROUPS{$group}->{'members'}} ) { + my $object = Spong::Host->new( $name ); + if( $object ) { $hosts{$name} = $object; push( @names, $name );} + } + } + } + + $self->{'name'} = $group; + } + + $self->{'hosthash'} = \%hosts; + $self->{'hostnames'} = [ @names ]; + + bless $self; + return $self; +} + + +# Get/Set methods, hostnames() is a little fancy in that it return a list +# rather then just the list reference (easier for others to deal with). The +# hosthash function returns a reference however. + +sub name { my $var = 'name'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub hosthash { my $var = 'hosthash'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub hostnames { my $var = 'hostnames'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return @{$_[0]->{$var}}; } + +# Some specific functions that get at and manipulate the data in the instance +# vars in more convenient ways + +sub host { + return $_[0]->{'hosthash'}->{$_[1]}; } + +sub hosts { + my $self = shift; + my( @tmp ); + + foreach( $self->hostnames() ) { push( @tmp, $self->host( $_ ) ); } + return @tmp; +} + +sub add { + my( $self, $host ) = @_; + my $name = $host->name(); + + $self->{'hosthash'}->{$name} = $host; + push( @{$self->{'hostnames'}}, $name ); +} + +# Display summary. Does both text and html, does rely on both the Host and +# Service objects for some help. +# +# brief Hostname and "one" color reflecting overall status of the host +# standard Table showing hosts vs services and the state of each service +# full Records for each host showing detailed info about its state + +sub display { + my( $self, $type, $view ) = @_; + + $self->display_text( $view ) if $type eq "text"; + $self->display_html( $view ) if $type eq "html"; +} + +# This displays a summary of all the hosts in this list in a text format +# suitable for displaying on dumb ascii terminals + +sub display_text { + my( $self, $format ) = @_; + + if( $format eq "standard" ) { + my( %services, $host, $service, @names ); + + # Compute the total list of services running on the various hosts, and + # sort them alphabetically (except always put ping first). + +# foreach $host ( $self->hosts() ) { +# foreach $service ( $host->service_names() ) { $services{$service}++;}} + + if( grep( /^ping$/, keys %main::SERVICES ) ) { push( @names, "ping" ); } + foreach $service ( sort keys %main::SERVICES ) { + push( @names, $service ) unless $service eq "ping"; } + + # Print the horizontal axis of the table (names of the services) + + print "-"x30, "-"x(($#names+1)*3), "\n"; + foreach $cnt ( 0..4 ) { + if( $cnt == 0 ) { print "Key: . = green, ? = purple "; } + if( $cnt == 1 ) { print " Y = yellow, R = red "; } + if( $cnt == 2 ) { print " B = blue "; } + if( $cnt == 3 ) { print " "x30; } + if( $cnt == 4 ) { print "Host:", " "x25; } + foreach $service ( @names ) { + if( length($service) - 5 + $cnt >= 0 ) { + print substr( $service, (length($service) - 5 + $cnt), 1 )," "; + } else { + print " "; + } + } + print "\n"; + } + print "-"x30, "-"x(($#names+1)*3), "\n"; + + # Now go through each host, and fill in the table. + + foreach $host ( $self->hosts() ) { + print substr( $host->name(), 0, 29 ); + print " "x(30-length(substr($host->name(), 0, 29))); + foreach $service ( @names ) { + my $servobj = $host->service( $service ); + if( $servobj ) { + $servobj->display_text( "really_brief" ); + } else { + print " "; + } + } + print "\n"; + } + print "\n"; + } elsif( $format eq "full" ) { + + # This goes through each host, and has each one print a textual record + # of the problem it is currently having, this would include a summary + # of the problem services, date/time the problem occurred, and contact + # information (as well as acknowledgments if they are available). + + foreach $host ( $self->hosts() ) { + $host->display_problem( "text" ); print "\n"; + } + } +} + +# This displays a summary of all the hosts in this list in an HTML format +# suitable for displaying on web clients capable of displaying tables. + +sub display_html { + my( $self, $format ) = @_; + + if( $format eq "standard" ) { + my( %services, $host, $service, @names ); + + # Compute the total list of services running on the various hosts, and + # sort them alphabetically (except always put ping first). + + if( grep( /^ping$/, keys %main::SERVICES ) ) { push( @names, "ping" ); } + foreach $service ( sort keys %main::SERVICES ) { + push( @names, $service ) unless $service eq "ping"; } + + # Print the horizontal axis of the table (names of the services) + + print ""; + print "\n"; + foreach $service ( @names ) { + print "\n"; + } + + print "\n\n"; + + # Now go through each host, and fill in the table. + + foreach $host ( $self->hosts() ) { + my $hostname = $host->name(); + my $short = $main::HOSTS{$hostname}->{'display_name'}; + $short = (split( /\./, $hostname ))[0] unless $short; + + print "\n"; + + foreach $service ( @names ) { + my $servobj = $host->service( $service ); + + if( $servobj ) { + my $col = $servobj->color(); + + if( $main::WWW_USE_IMAGES == 1 ) { + print ""; + } else { + if( $main::WWW_USE_IMAGES == 1 ) { + print "\n"; + } else { + print "\n"; + } + } + } + } + print "
Host\n"; + print ""; + print "$service
\n"; + print "$short"; + print "$col"; + print ""; + } else { + print ""; + print ""; + print ""; + print "___"; + } + print " -  
"; + + } elsif( $format eq "full" ) { + + # This goes through each host, and has each one print a textual record + # of the problem it is currently having, this would include a summary + # of the problem services, date/time the problem occurred, and contact + # information (as well as acknowledgments if they are available). + + foreach $host ( $self->hosts() ) { + $host->display_problem( "html" ); print "\n"; + } + } +} + + +# These methods all display summary information about only those machines +# that are currently registering a problem, or that have services that have +# not been updated (showing purple) - it also relays information about +# services that have been acknowledged. +# +# brief Hostname and "one" color reflecting overall status of the host +# standard Table showing hosts vs services and the state of each service +# full Records for each host showing detailed info about the problems + +sub display_problems { + my( $self, $type, $view ) = @_; + my( $badhosts ) = Spong::HostList->new( "" ); + my( $host, $problem, $purple ); + + foreach $host ( $self->hosts() ) { + if( $host->has_problem() ) { + $problem = 1; + $badhosts->add( $host ); + } elsif( $host->has_color( "purple" ) ) { + $purple = 1; + } + } + + if( $problem ) { + $badhosts->display( $type, $view ); + } else { + if( $view eq "full" ) { + if( $purple ) { + print "

" if( $type eq "html" ); + print "No known problems.\n"; + print "" if( $type eq "html" ); + print "

" if( $type eq "html" ); + print "Although some information is out of date which might "; + print "indicate a problem.\n"; + print "" if( $type eq "html" ); + } else { + print "

" if( $type eq "html" ); + print "No current problems.\n"; + print "" if( $type eq "html" ); + } + } + } +} + +1; diff --git a/src/lib/Spong/Info.pm b/src/lib/Spong/Info.pm new file mode 100755 index 0000000..ca87378 --- /dev/null +++ b/src/lib/Spong/Info.pm @@ -0,0 +1,125 @@ +#!/usr/local/bin/perl +# +# This is a simple class which just represents some information that is +# provided by the spong administrator about the hosts that are being +# monitored. The administrator can provide documentation in any number of +# formats depending on how energetic he/she is. He/she can also choose not to +# provide this information at all, and nothing will be displayed. +# +# There is only one Info class associated with each Host, so this class knows +# how to load itself from the database. +# +# host - string FQDN of the machine its associated with +# +# + new() - constructor (sets instance vars to arguments passed in) +# + gets/sets() - magical set/get functions (autoloaded based on func name) +# + display() - output format and view +# +# History: +# (1) Cleaned up (Ed July 31, 1997); + +package Spong::Info; + +# Object creation... Just checks to see if any of the required documentation +# files have been provided by the spong administrator, and if not it returns +# undef rather then a real life object... + +sub new { + my( $class, $host ) = @_; + my $self = {}; + my $ok; + + if( -f "$main::SPONGDB/$host/info/info.txt" ) { $ok = 1; } + if( -f "$main::SPONGDB/$host/info/info.html" ) { $ok = 1; } + + if( -f "$main::SPONGDB/$host/info/info.brief.txt" ) { $ok = 1; } + if( -f "$main::SPONGDB/$host/info/info.standard.txt" ) { $ok = 1; } + if( -f "$main::SPONGDB/$host/info/info.full.txt" ) { $ok = 1; } + + if( -f "$main::SPONGDB/$host/info/info.brief.html" ) { $ok = 1; } + if( -f "$main::SPONGDB/$host/info/info.standard.html" ) { $ok = 1; } + if( -f "$main::SPONGDB/$host/info/info.full.html" ) { $ok = 1; } + + return undef if ! $ok; + + $self->{'host'} = $host; + bless $self; + return $self; +} + + +# Get/Set methods, nothing fancy here... + +sub host { my $var = 'host'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } + + +# Display summary. Does both text and html, doesn't make any calls to +# sub-objects or other helper objects, just spits out the data that it has. +# +# This object is a little different in that it shows the best possible +# documentation based on what is provided. It checks to see if the +# administrator has provided a specific view of documentation, but if it +# doesn't exist then it just keep looking for documentation until it finds +# some to print. + +sub display { + my( $self, $type, $view ) = @_; + + if( $type eq "text" ) { return $self->display_text( $view ); } + if( $type eq "html" ) { return $self->display_html( $view ); } +} + +sub display_text { + my( $self, $format ) = @_; + my $host = $self->host(); + my( @cycle ); + + if( $format eq "brief" ) { + @cycle = ( "brief.txt", "txt", "standard.txt", "full.txt" ); + } elsif( $format eq "standard" ) { + @cycle = ( "standard.txt", "txt", "full.txt", "brief.txt" ); + } elsif( $format eq "full" ) { + @cycle = ( "full.txt", "txt", "brief.txt", "standard.txt" ); + } + + foreach( @cycle ) { + if( -f "$main::SPONGDB/$host/info/info.$_" ) { + open( FILE, "$main::SPONGDB/$host/info/info.$_" ); + while( ) { print $_; } + close( FILE ); + return; + } + } + + print "Only information in HTML is provided.\n"; +} + +sub display_html { + my( $self, $format ) = @_; + my $host = $self->host(); + my( @cycle ); + + if( $format eq "brief" ) { + @cycle = ( "brief.html", "html", "brief.txt", "standard.html", + "standard.txt", "full.html", "full.txt" ); + } elsif( $format eq "standard" ) { + @cycle = ( "standard.html", "html", "standard.txt", "full.html", + "full.txt", "breif.html", "brief.txt" ); + } elsif( $format eq "full" ) { + @cycle = ( "full.html", "html", "full.txt", "brief.html", "brief.txt", + "standard.html", "standard.txt" ); + } + + foreach( @cycle ) { + if( -f "$main::SPONGDB/$host/info/info.$_" ) { + open( FILE, "$main::SPONGDB/$host/info/info.$_" ); + while( ) { print $_; } + close( FILE ); + return; + } + } +} + +1; + diff --git a/src/lib/Spong/Service.pm b/src/lib/Spong/Service.pm new file mode 100755 index 0000000..79b93db --- /dev/null +++ b/src/lib/Spong/Service.pm @@ -0,0 +1,386 @@ +#!/usr/local/bin/perl +# +# This object represents a service that is running on a given machine. It +# contains information such as the state of the service (red,green,etc...), the +# time the service was last updated, summary information about the state, and +# additional information helpful if there is a problem. +# +# host - string FQDN of the machine its associated with +# name - string name of the service +# color - computed color of the service (can be purple or blue) +# rcolor - reported color of the service (only red, yellow, green) +# rtime - reported last updated time of the service (time() format) +# stime - reported start of current status (time() format) +# summary - short summary providing additional info about the service +# message - more detailed information about the service +# +# + new() - constructor (sets instance vars to arguments passed in) +# + gets/sets() - magical set/get functions (autoloaded based on func name) +# + display() - output format and view +# +# + has_problem() - +# +# History: +# (1) Cleaned up (Ed July 31, 1997); + +package Spong::Service; + +# Constructor. This is a lazy constructor in that it doesn't get all it's +# instance vars set at the time of creation. Vars are loaded as they are asked +# for, this gives us a performance gain. This object is dependent on the +# ServiceList object for loading the information about it from the database. + +sub new { + my( $class, $host, $service ) = @_; + my $self = {}; + + # A hack to get around a local problem. + + if( $service eq "procs" ) { $service = "jobs"; } + + $self->{'name'} = $service; + $self->{'host'} = $host; + $self->{'color'} = ""; + $self->{'rcolor'} = ""; + $self->{'rtime'} = ""; + $self->{'summary'} = ""; + $self->{'message'} = ""; + $self->{'stime'} = ""; + + bless $self; + return $self; +} + + +# Get/Set methods, nothing fancy here... + +sub host { my $var = 'host'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } + +sub name { + my $var = 'name'; + + # A hack to get around a local problem. + + if( defined $_[1] ) { + if( $_[1] eq "procs" ) { + $_[0]->{$var} = "jobs"; + } else { + $_[0]->{$var} = $_[1]; + } + } + return $_[0]->{$var}; +} + +sub color { my $var = 'color'; + if( defined $_[1] ) { + $_[0]->{$var} = $_[1]; + } else { + my $name = $_[0]->host(); + + # If there is a problem reported, and we are in this machine's down time + # then don't report it, instead mark it as blue. + + foreach( @{$main::HOSTS{$name}->{'down'}} ) { + my( $day, $shour, $smin, $ehour, $emin ) = + ( /^(.):(\d+):(\d+)-(\d+):(\d+)$/ ); + my( $nmin, $nhour, $nday ) = (localtime(time()))[1,2,6]; + + if( $day eq "*" || $nday eq $day ) { + my $ntotal = $nhour * 60 + $nmin; + my $stotal = $shour * 60 + $smin; + my $etotal = $ehour * 60 + $emin; + + if( $ntotal >= $stotal && $ntotal <= $etotal ) { + return 'blue'; + } + } + } + } + + return $_[0]->{$var}; +} + +sub rcolor { my $var = 'rcolor'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub rtime { my $var = 'rtime'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub stime { my $var = 'stime'; + if( $_[0]->{$var} eq "" ) { $_[0]->summary(); } return $_[0]->{$var}; } + + +# These are both lazy loading functions. If we don't have summary and message +# information already loaded, then go out and grab it from the database and +# return that information to the user. + +sub summary { + my $self = shift; + my $file; + + # This is a local hack to get around the procs/jobs problems, first look + # for the jobs service, if it is not there, look for procs... + + if( $self->name() eq "jobs" ) { + $file = $self->host()."/services/".$self->name()."-".$self->rcolor(); + if( ! -f "$main::SPONGDB/$file" ) { + $file = $self->host()."/services/procs-".$self->rcolor(); + } + } else { + $file = $self->host()."/services/".$self->name()."-".$self->rcolor(); + } + + + if( ! $self->{'summary'} ) { + open( FILE, "$main::SPONGDB/$file" ); + my $header = ; chomp $header; + # If a timestamp is present, read it and set start time + if ($header =~ /^timestamp (\d+) (\d+)/ ) { + $self->{'stime'} = $1; + $header = ; chomp $header; + } + ($self->{'rtime'}, $self->{'summary'}) = ( $header =~ /^(\d+) (.*)$/ ); + while( ) { $self->{'message'} .= $_; } + close( FILE ); + } + + return $self->{'summary'}; +} + +sub message { + my $self = shift; + my $file; + + # This is a local hack to get around the procs/jobs problems, first look + # for the jobs service, if it is not there, look for procs... + + if( $self->name() eq "jobs" ) { + $file = $self->host()."/services/".$self->name()."-".$self->rcolor(); + if( ! -f $file ) { + $file = $self->host()."/services/procs-".$self->rcolor(); + } + } else { + $file = $self->host()."/services/".$self->name()."-".$self->rcolor(); + } + + if( ! $self->{'message'} ) { + open( FILE, "$main::SPONGDB/$file" ); + my $header = ; chomp $header; + # If timestamp header present, read it and set start time + if ($header =~ /^timestamp (\d+) (\d+)/ ) { + $self->{'stime'} = $1; + $header = ; chomp $header; + } + ($self->{'rtime'}, $self->{'summary'}) = ( $header =~ /^(\d+) (.*)$/ ); + while( ) { $self->{'message'} .= $_; } + close( FILE ); + } + + return $self->{'message'}; +} + + +# This returns true or false depending on if there is a problem with the +# service. This allows you to define some smarts into what a problem is. + +sub has_problem { + my $self = shift; + + if( $self->color() eq "red" ) { + return 1; + } else { + return 0; + } +} + + +# Display summary. Does both text and html, doesn't make any calls to +# sub-objects or other helper objects, just spits out the data that it has. +# +# brief: Just show the color of the service (or a colored ball in html) +# standard: Shows a line stating the name/color/updated/summary information +# full: Shows standard information plus any messages the service has + +sub display { + my( $self, $type, $view ) = @_; + + if( $type eq "text" ) { return $self->display_text( $view ); } + if( $type eq "html" ) { return $self->display_html( $view ); } +} + +sub display_text { + my( $self, $format ) = @_; + + if( $format eq "really_brief" ) { + if( $self->color() eq "green" ) { print ". "; } + if( $self->color() eq "purple" ) { print "? "; } + if( $self->color() eq "yellow" ) { print "Y "; } + if( $self->color() eq "red" ) { print "R "; } + if( $self->color() eq "blue" ) { print "B "; } + } elsif( $format eq "brief" ) { + print $self->color(), "\n"; + } elsif( $format eq "standard_table" ) { + my( $d1, $m1, $y1 ) = (localtime( $self->rtime()))[3,4,5]; + my( $d2, $m2, $y2 ) = (localtime())[3,4,5]; + my $name = substr( $self->name(), 0, 7 ); + my $color = $self->color(); + my $summary = $self->summary(); + my $firstline = 1; + + print $name, " "x(8-length($name)); + print $color, " "x(9-length($color)); + + if( $d1 == $d2 && $m1 == $m2 && $y1 == $y2 ) { + $date = POSIX::strftime( "%H:%M", localtime($self->rtime()) ); + } else { + $date = POSIX::strftime( "%D", localtime($self->rtime()) ); + } + print $date, " "x(11-length($date)); + + do { + print " "x30 if ! $firstline++; + print substr( $summary, 0, 50 ), "\n"; + $summary = substr( $summary, 51 ); + } while( length( $summary > 50 ) ); + } elsif( $format eq "standard" ) { + print "Name Color Updated Summary\n"; + print "------- -------- ---------- ", "-"x50, "\n"; + $self->display_text( "standard_table" ); + } elsif( $format eq "full" ) { + $self->display_text( "standard" ); + print "\nStatus unchanged in " . + &format_duration($self->stime(),$self->rtime) . "\n"; + print "-"x78, "\n"; + print $self->message(), "\n"; + } +} + +sub display_html { + my( $self, $format ) = @_; + my $host = $self->host; + my $name = $self->name(); + my $color = $self->color(); + my( $d1, $m1, $y1 ) = (localtime( $self->rtime()))[3,4,5]; + my( $d2, $m2, $y2 ) = (localtime())[3,4,5]; + + if( $format eq "brief" ) { + print "\n"; + + if( $main::WWW_USE_IMAGES == 1 ) { + print "$color"; + } else { + print ""; + print "
"; + print "___
\n"; + } + print "
"; + } elsif( $format eq "standard_table" ) { + print "\n"; + print "$name\n"; + print "\n"; + + if( $main::WWW_USE_IMAGES == 1 ) { + print "\n"; + print "$color"; + } else { + print ""; + print "
"; + print ""; + print "___"; + print "
\n"; + } + + print "\n"; + print ""; + + if( $d1 == $d2 && $m1 == $m2 && $y1 == $y2 ) { + print POSIX::strftime( "%H:%M", localtime($self->rtime()) ), " "; + } else { + print POSIX::strftime( "%D", localtime($self->rtime()) ), " "; + } + + print "\n"; + print "", $self->summary(), "\n"; + } elsif( $format eq "standard" ) { + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + + $self->display_html( "standard_table" ); + + print "
Service UpdatedSummary
\n"; + } elsif( $format eq "full" ) { + print "", $self->host(), "/", $self->name(); + print "\n"; + + # This breaks the generic interactive vs non-interactive model + $self->add_action_bar(); + + print ""; + print ""; + print "
 

"; + + print "Updated: "; + print POSIX::strftime( "%H:%M, %D", localtime($self->rtime()) ); + + print "
Duration: "; + print &format_duration($self->stime(),$self->rtime()); + + print "
Summary: ", $self->summary(), "
\n"; + print "


", $self->message(), "
\n"; + } +} + + +# Returns a message that can be sent to the person who is on call for this +# machine which indicates the problem with this machine. + +sub _problem_message { + my $self = shift; + + if( $self->has_problem() ) { + return "Problem: " . $self->name() . ", " . $self->summary(); + } else { + return "No Problem"; + } +} + + +sub add_action_bar { + my( $self ) = shift; + my $name = $self->host(); + my $message = &escape( "Host: $name, " . $self->_problem_message() ); + + print "
"; + print "Connect to Host || "; + print "Acknowledge Problem "; + + if( $main::WWWCONTACT ) { + print "|| "; + print "Contact Help"; + } + + print "
\n"; +} + +sub escape { + my($toencode) = @_; + $toencode=~s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg; + return $toencode; +} + +sub format_duration { + my($stime,$rtime) = @_; + + my $duration = $rtime - $stime; + my $units = "seconds"; + if ($duration > 86400.0) { $duration /= 86400.0; $units = "days"; } + elsif ($duration > 3600.0) { $duration /= 3600.0; $units = "hours"; } + elsif ($duration > 60.0) { $duration /= 60.0; $units = "minutes"; } + + return sprintf("%.2f %s", $duration, $units); +} + +1; diff --git a/src/lib/Spong/ServiceList.pm b/src/lib/Spong/ServiceList.pm new file mode 100755 index 0000000..c2fe484 --- /dev/null +++ b/src/lib/Spong/ServiceList.pm @@ -0,0 +1,194 @@ +#!/usr/local/bin/perl +# +# This class represents a list of services that run on a given host, allowing +# you to query individual services for status information. This knows how to +# load the Service information from the database, and has the smarts to change +# the service colors based on updates times and Acks. +# +# host - string FQDN of the machine its associated with +# service_hash - a hash containing the services org by service names +# +# + new() - constructor (sets instance vars to arguments passed in) +# + gets/sets() - magical set/get functions (autoloaded based on func name) +# + display() - output format and view +# +# + service() - returns the specific Service object based on name +# + services() - returns a list of all the Service objects +# + names() - returns a list of all the Service object names. +# + color() - returns the LCD color of all the services in the list +# +# History: +# (1) Cleaned up (Ed July 31, 1997); + +use Spong::Service; +use Spong::AckList; +use Spong::Ack; + +package Spong::ServiceList; + +# Constructor. This requires the FQDN of the host that the Services are +# associated with. It goes through and loads the information from the database +# and also loads any Acks that might be associated with the host. It then +# computes the colors for each host and builds Service objects for each service +# running on that host. If there are no services running on the host, then +# undef is returned. + +sub new { + my( $class, $host ) = @_; + my( %service_hash, @acks, $service, $ack, $service_name, $got_one ); + my $self = {}; + + return undef if ! -d "$main::SPONGDB/$host/services"; + + # Go through the database, and grab the name and state of each service + # running on this host. + + opendir( DIR, "$main::SPONGDB/$host/services" ); + while( defined( $_ = readdir( DIR ) ) ) { + next unless /^(.*)-(green|yellow|red)$/ ; + my( $name, $color ) = ($1, $2); + + # A hack to get around a local problem. + if( $name eq "procs" ) { $name = "jobs"; } + + $got_one = 1; + my( $rtime ) = (stat( "$main::SPONGDB/$host/services/$_" ))[9]; + my $service = Spong::Service->new( $host, $name ); + + $service->rtime( $rtime ); + if( $rtime < ( time() - (3 * $main::SPONGSLEEP) ) ) { + $service->rcolor( $color ); $service->color( "purple" ); + } else { + $service->rcolor( $color ); $service->color( $color ); + } + $service_hash{$name} = $service; + } + closedir( DIR ); + + return undef unless $got_one; + + # Go through the acknowledgments that we have for this host, and change the + # state information on our Services if needed. + + $acklist = Spong::AckList->new( $host ); + if( $acklist ) { + foreach $ack ( $acklist->acks() ) { + my $ack_services = $ack->services(); + + if( time() >= $ack->start() && time() <= $ack->end() ) { + foreach $service_name ( keys %service_hash ) { + if( $ack_services eq "all" || + $ack_services =~ /\b$service_name\b/ ) { + + $service_hash{$service_name}->color( "blue" ); + } + } + } + } + } + + $self->{'host'} = $host; + $self->{'service_hash'} = \%service_hash; + + bless $self; + return $self; +} + +# Get/Set methods, nothing fancy here... + +sub host { my $var = 'host'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } +sub service_hash { my $var = 'service_hash'; + if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; } + + +# Some utility functions that return information based on instance vars, and +# the state of services that this object holds. + +sub service { return $_[0]->{'service_hash'}->{$_[1]}; } +sub services { return values %{$_[0]->{'service_hash'}}; } +sub names { return keys %{$_[0]->{'service_hash'}}; } + +sub color { + my $self = shift; + my( %state_hash ) = qw( blue 3 red 4 yellow 2 purple 1 green 0 ); + my $state = "green"; + + foreach $service ( $self->services() ) { + if( $state_hash{$service->color()} > $state_hash{$state} ) { + $state = $service->color(); } } + + return $state; +} + + +# Display summary. Does both text and html, it relies on the Service objects +# to print out information about themselves, but this adds a little to by +# printing header information in cases. +# +# brief: Just shows name of services and what color they are +# standard: Shows a table of services (name,color,updated,summary) +# full: Shows standard information about service along with messages +# +# ** full is not implemented yet. + +sub display { + my( $self, $type, $view ) = @_; + + if( $type eq "text" ) { return $self->display_text( $view ); } + if( $type eq "html" ) { return $self->display_html( $view ); } +} + +sub display_text { + my( $self, $format ) = @_; + my( $service ); + + if( $format eq "brief" ) { + foreach $service ( $self->services() ) { + print $service->name, " "x(8-length( $service->name() )); + $service->display_text( "brief" ); + } + } elsif( $format eq "standard" ) { + print "Name Color Updated Summary\n"; + print "------- -------- ---------- ", "-"x50, "\n"; + foreach( $self->services() ) { $_->display_text( "standard_table" ); } + } elsif( $format eq "full" ) { + foreach $service ( $self->services() ) { + $service->display_text( "full" ); + print "\n\n"; + } + } +} + +sub display_html { + my( $self, $format ) = @_; + my( $event, $date ); + + if( $format eq "brief" ) { + print "\n"; + foreach $service ( $self->services() ) { + my $host = $service->host(); + my $name = $service->name(); + + print "\n"; + } + print "
"; + $service->display_html( "brief" ); + print ""; + print " $name"; + print "
\n"; + + } elsif( $format eq "standard" ) { + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + + foreach( $self->services() ) { $_->display_html( "standard_table" ); } + + print "
Service UpdatedSummary
\n"; + } +} + +1; -- 2.30.2