#!/usr/bin/perl

# Buchan Milne <bgmilne@mandriva.org> 20051213
# Script to monitor OpenLDAP performance and sync-replication status via
# Hobbit (may also still work with BigBrother).
#
# 1)Install Net::LDAP (perl-ldap) and Date::Manip (perl-DateManip)
# 2)Run as task from hobbitserver.cfg (or BBEXT in bbdef.sh for BigBrother)
# 3)Use ncv in hobbit to collect data:
#   -add "ol=ncv" to TEST2RRD, and "ncv" to GRAPHS in hobbitserver.cfg
#   -add NCV_ol="ReadWaiters:GAUGE,WriteWaiters:GAUGE,CurrentConnections:GAUGE"
#    to hobbitserver.cfg
# 4)Add something like the contents of hobbitgraph-ol.cfg to hobbitgraph.cfg
# 5)Ensure monitor backend in OpenLDAP 2.1 or later is configured for 
#   anonymous access from the display server
# 6)Add "ol" to hosts in bb-hosts to monitor with this script

use strict;
use Net::LDAP;
use Date::Manip;
my ($slave,$bbhost,$ldap_message,$ldap_entry,$monitor,$tmp,$content,$status,$base);
my (@slaves,@values);
my %ldap;
my (%ldap_perf,%performance,%master);
my $service = "ol";
my @slave_states = ("in sync", "not a sync-repl slave", "could not check master", "out of sync");
my @state_colours = ("OK", "NONE", "NONE", "WARN","ERR");
my @state_names = ("green", "clear", "clear", "yellow","red");

if ( $ENV{'BBHOME'} ) {
       # Use bbhostgrep if available (hobbit), otherwise use a hardcoded list
       # of servers:

       if ( -f "$ENV{'BBHOME'}/bin/bbhostgrep" ) {
               print "DEBUG Found bbhostgrep\n" if $ENV{DEBUG};
               open (BBHOSTGREP, $ENV{BBHOME} . "/bin/bbhostgrep " . $service . " |") or die $*;
               while (<BBHOSTGREP>) {
                       my ($junk,$host);
                       ($junk,$host) = split ' ';
                       print "DEBUG Adding $host to server list\n" if $ENV{DEBUG};
                       push @slaves, $host;
               }
	       close BBHOSTGREP;
       } else {
               print "DEBUG Couldn't find bbhostgrep\n" if $ENV{DEBUG};
               #@slaves = ('master.domain.com', 'ldap1.domain.com', 'ldap2.domain.com');
               @slaves = ('localhost');
       }
	@state_colours = ("&green", "&clear", "&clear", "&yellow","&red");
} else {
	@slaves = @ARGV;
}

foreach $slave (@slaves) {
	%master = ();
	$status = 0;
	$content = "";
	print "DEBUG Using $slave\n" if $ENV{DEBUG};
	$bbhost = $slave;
	$bbhost =~ s/\./\,/g;

	if ($ldap{$slave} ne undef or $ldap{$slave} = Net::LDAP->new( $slave, async=> 1 ) ) {
		print "DEBUG Binding to $slave\n" if $ENV{DEBUG};
		$ldap_message = $ldap{$slave}->bind;

		# Get monitor context
		$ldap_message = $ldap{$slave}->search(
			base	=>	'',
			scope	=>	'base',
			filter	=>	'(objectclass=*)',
			attrs	=>	['monitorContext']
		);
		$ldap_message->code && die $ldap_message->error;
		$ldap_entry = $ldap_message->entry(0);
		$monitor = $ldap_entry->get_value('monitorContext');
		#$ldap_message = $ldap->root_dse();
		#$ldap_message->code && die $ldap_message->error;
		#$monitor = $ldap_message->get_value('monitorContext');
		print "DEBUG Monitor context for $slave: $monitor\n" if $ENV{DEBUG};

		%performance = &get_perf($slave,$monitor,$ldap{$slave});
		$content = "\n";
		foreach ( sort keys %performance ) {
			$content .= sprintf "%s : %d\n",$_,$performance{$_};
		}

		# Get databases that have a master
		$ldap_message = $ldap{$slave}->search(
			base	=>	$monitor,
			scope	=>	'sub',
			filter	=>	'(&(namingContexts=*)(MonitorUpdateRef=*))',
			attrs	=>	[ 'monitorupdateref', 'namingContexts' ]
		);

		if ( $ldap_message->count == 0 ) {
			$content .= "$state_colours[0] Not a syncrepl slave\n";
		} else {
			$content .= "Replication status of slave databases\n";
		}

		for $ldap_entry ( $ldap_message->all_entries ) {
			$master{$ldap_entry->get_value('namingContexts')} = $ldap_entry->get_value('monitorupdateref');	

		}
		for ( keys ( %master )) {
			my ($slave_status,$msg) = &check_slave($slave,$master{$_},$_,\%ldap);
			$content .= sprintf("%s %s %s %s\n", $state_colours[$slave_status],$_,$slave_states[$slave_status],$msg);
			$status = ($status lt $slave_status) ? $slave_status : $status;
		}	

	} else {
		
		$status = 4;
		$content =  "$@\n";
	}
	if ( $ENV{BBHOME} eq "" ) {
		print $state_colours[$status] . " $slave " . `date`;
		print $content;
	} else {
		my $line = "status " . $bbhost . "." . $service . " " . $state_names[$status] . " " .  `date` . "\n";
		$line .= $content;
		if ( $ENV{BB} ) {
		system ("$ENV{BB}", "$ENV{BBDISP}", "$line");
		} else {
			print "$line";
		}
	}
}

# clean up ldap connections
foreach (keys %ldap) {
	next if not defined ($ldap{$_});
	print "DEBUG Unbinding from $_\n" if $ENV{DEBUG};
	$ldap{$_}->unbind;
}

sub get_contextcsn {
	my ($c_ldapserver,$c_base,$c_ldap) = @_;
	my $c_message;
	my $c_entry;
	my $bound = 'TRUE';

	$c_message = $c_ldap->search(
		base	=>	$c_base,
		scope	=>	'base',
		filter	=>	'(objectclass=*)',
		attrs	=>	['contextCSN']
	);
	$c_message->code && return $c_message->error;
	$c_entry = $c_message->entry(0);
	return $c_entry->get_value('contextCSN');
}

sub get_perf {
	my ($ldapserver,$base,$ldapconn) = @_;
	my ($message,$entry,$dn,$value,$monitortype,$monitorfilter,$firstattrib);
	my $bound =' TRUE';
	my %perf;
	my @monitorattribs;
	
	if ( $ldapconn == undef ) {
		$bound = 'FALSE';
		printf "DEBUG Binding to $ldapserver\n" if $ENV{DEBUG};
		$ldapconn = Net::LDAP->new( $ldapserver, async => 1) or die "$@" unless defined ( $ldapconn);
		$message = $ldapconn->bind;
	}

	# search on monitorcontext for structuralobjectclass
	# OL 2.1 = structuralObjectClass: monitor
	# OL 2.2 and later = structuralObjectClass: monitorServer

	$message = $ldapconn->search(
		base	=>	$base,
		filter	=>	'(objectclass=*)',
		scope	=>	'base',
		attrs	=>	['structuralObjectClass']
	);
	$message->code && die $message->error;
	$monitortype =  $message->entry(0)->get_value('structuralObjectClass');
	print "DEBUG monitor is " . $monitortype . "\n" if $ENV{DEBUG};
	if ( $monitortype eq "monitorServer" ) {
		$monitorfilter = '(&
				   (|
				     (objectclass=monitorCounterObject)
				     (objectclass=monitorOperation)
				     (objectclass=monitorContainer)
				   )
				   (|
				     (monitorcounter=*)
				     (monitorOpInitiated=*)
				   )
				)';
		@monitorattribs = ['monitorCounter', 'monitorOpInitiated', 'monitorOpCompleted', 'dn'];
		$firstattrib = 'monitorCounter';
	} elsif ( $monitortype eq 'monitor' ) {
		$monitorfilter = '(objectclass=monitor)';
		@monitorattribs = ['description', 'dn'];
		$firstattrib = 'description';
	}

	# counters:
	print "DEBUG Searching under $base with filter $monitorfilter on $ldapserver\n" if $ENV{DEBUG};
	$message = $ldapconn->search(
		base	=>	$base,
		scope	=>	'sub',
		filter	=>	$monitorfilter,
		attrs   =>	@monitorattribs
	);
	$message->code && die $message->error;
	foreach $entry ( $message->all_entries ) {
		$dn = $entry->dn;
		next if ($dn =~ m/(cn=(Connections|Backends|Databases|Listeners|Time|TLS|SASL|Log)|(^cn=Monitor))/);
	print "DEBUG got $dn\n" if $ENV{DEBUG};
		$dn =~ s/,$base$//;
		$dn =~ s/cn=//g;
		$dn =~ s/\,/ /g;
		$dn =~ s/ Operations//g;
		$dn =~ s/Operations/Operation/g;
		$dn =~ s/Statistics//g;
		$dn =~ s/ //g;

	print "DEBUG getting $firstattrib for $dn\n" if $ENV{DEBUG};
		$value = $entry->get_value($firstattrib);
		if ( $value eq "" ) {
			$value = $dn . "Initiated";
			$perf{$value} = $entry->get_value('monitorOpInitiated');
			$value = $dn . "Completed";
			$perf{$value} = $entry->get_value('monitorOpCompleted');
		} else {
			$perf{$dn} = abs($value)
		}
		print "DEBUG $dn:\t $value\n" if $ENV{DEBUG};
	}
	print "DEBUG Finished search results\n" if $ENV{DEBUG};
	return %perf;
}

sub check_slave {
	my ($slave,$master,$db,$ldap) = @_;
	if ($ldap{$master{$_}} eq undef) { 
		print "DEBUG Binding to master $master{$_}\n" if $ENV{DEBUG};
		$ldap{$master{$_}} = Net::LDAP->new( $master{$_}, async=> 1) or return (2,"");
		$ldap{$master{$_}}->bind or return (2,"");
	}
	my ($mastercsn,$slavecsn);
	print "DEBUG Checking $master for $db\n" if $ENV{DEBUG};
	$slavecsn = &get_contextcsn($slave,$db,$$ldap{$slave}) or return (1,"");
	$mastercsn = &get_contextcsn($master,$db,$$ldap{$master}) or return (2,"");
	if ($slavecsn == $mastercsn){
		return (0,"");
	} else {
		my $slavetime = $slavecsn;
		my $mastertime = $mastercsn;
		my $time;
		$slavetime =~ s/#.*//g;
		$mastertime =~ s/#.*//g;
		$time = DateCalc($mastertime,$slavetime);
		$time =~ s/^(.)0:0:/$1/;
		$time =~ s/^\+(.*)$/$1 ahead/g;
		$time =~ s/^\-(.*)$/$1 behind/g;
		foreach ("weeks","days") { $time =~s/:/ $_ /;}
		return (3,$time);
	}
}

