From 1d4ca4f33e9297c44c6f7d140efcd6c44d9092b0 Mon Sep 17 00:00:00 2001 From: Nathan Kinder Date: Fri, 12 Dec 2008 20:36:00 +0000 Subject: Resolves: 250535 Summary: Make repl-monitor.pl a template to pass in correct perl environment. --- ldap/admin/src/scripts/repl-monitor.pl | 995 ----------------------------- ldap/admin/src/scripts/repl-monitor.pl.in | 997 ++++++++++++++++++++++++++++++ 2 files changed, 997 insertions(+), 995 deletions(-) delete mode 100755 ldap/admin/src/scripts/repl-monitor.pl create mode 100755 ldap/admin/src/scripts/repl-monitor.pl.in (limited to 'ldap/admin/src/scripts') diff --git a/ldap/admin/src/scripts/repl-monitor.pl b/ldap/admin/src/scripts/repl-monitor.pl deleted file mode 100755 index f5709e91..00000000 --- a/ldap/admin/src/scripts/repl-monitor.pl +++ /dev/null @@ -1,995 +0,0 @@ -#!/usr/bin/env perl -# BEGIN COPYRIGHT BLOCK -# 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; version 2 of the License. -# -# 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. -# -# In addition, as a special exception, Red Hat, Inc. gives You the additional -# right to link the code of this Program with code not covered under the GNU -# General Public License ("Non-GPL Code") and to distribute linked combinations -# including the two, subject to the limitations in this paragraph. Non-GPL Code -# permitted under this exception must only link to the code of this Program -# through those well defined interfaces identified in the file named EXCEPTION -# found in the source code files (the "Approved Interfaces"). The files of -# Non-GPL Code may instantiate templates or use macros or inline functions from -# the Approved Interfaces without causing the resulting work to be covered by -# the GNU General Public License. Only Red Hat, Inc. may make changes or -# additions to the list of Approved Interfaces. You must obey the GNU General -# Public License in all respects for all of the Program code and other code used -# in conjunction with the Program except the Non-GPL Code covered by this -# exception. If you modify this file, you may extend this exception to your -# version of the file, but you are not obligated to do so. If you do not wish to -# provide this exception without modification, you must delete this exception -# statement from your version and license this file solely under the GPL without -# exception. -# -# -# Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. -# Copyright (C) 2007 Red Hat, Inc. -# All rights reserved. -# END COPYRIGHT BLOCK -############################################################################## -# -# FILE: repl-monitor.pl -# -# SYNOPSIS: -# repl-monitor.pl -f configuration-file [-h host] [-p port] [-r] -# [-u refresh-url] [-t refresh-interval] -# -# repl-monitor.pl -v -# -# DESCRIPTION: -# Given an LDAP replication "supplier" server, crawl over all the ldap -# servers via direct or indirect replication agreements. -# For each master replica discovered, display the maxcsn of the master -# and the replication status of all its lower level replicas. -# All output is in HTML. -# -# OPTIONS: -# -f configuration-file -# The configuration file contains the sections for the connection -# parameters, the server alias, and the thresholds for different colors -# when display the time lags between consumers and master. -# If the Admin Server is running on Windows, the configuration-file -# name may have format "D:/opt/replmon.conf". -# -# The connection parameter section consists of the section name -# followed by one of more connection parameter entries: -# -# [connection] -# host:port:binddn:bindpwd:bindcert -# host:port=shadowport:binddn:bindpwd:bindcert -# ... -# -# where host:port default (*:*) to that in a replication agreement, -# binddn default (*) to "cn=Directory Manager", and bindcert is the -# pathname of cert db if you want the script to connect to the server -# via SSL. If bindcert is omitted, the connection will be simple -# bind. -# "port=shadowport" means to use shadowport instead of port if port -# is specified in the replication agreement. This is useful when -# for example, ssl port is specified in a replication agreement, -# but you can't access the cert db from the machine where this -# script is running. So you could let the script to map the ssl -# port to a non-ssl port and use the simple bind. -# -# A server may have a dedicated or a share entry in the connection -# section. The script will find out the most matched entry for a given -# server. For example, if all the ldap servers except host1 share the -# same binddn and bindpassword, the connection section then just need -# two entries: -# -# [connection] -# *:*:binddn:bindpassword: -# host1:*:binddn:bindpassword: -# -# If a host:port is assigned an alias, then the alias instead of -# host:port will be displayed in The output file. Each host:port -# can have only one alias. But each alias may be used by more than -# one host:port. -# -# [alias] -# alias = host:port -# ... -# -# CSN time lags between masters and consumers might be displayed in -# different colors based on their range. The thresholds for different -# colors may be specified in color section: -# -# [color] -# lowmark (in minutes) = color -# ... -# -# If the color section or color entry is missing, the default color -# set is: green for [0-5) minutes lag, yellow [5-60), and red 60 and more. -# -# -h host -# Initial replication supplier's host. Default to the current host. -# -# -p port -# Initial replication supplier's port. Default to 389. -# -# -r If specified, -r causes the routine to be entered without printing -# HTML header information. This is suitable when making multiple calls -# to this routine (e.g. when specifying multiple, different, "unrelated" -# supplier servers) and expecting a single HTML output. -# -# -t refresh-interval -# Specify the refresh interval in seconds. This option has to be -# jointly used with option -u. -# -# -u refresh-url -# The output HTML file may invoke a CGI program periodically. If -# this CGI program in turn calls this script, the effect is that -# the output HTML file would automatically refresh itself. This -# is useful for continuing monitoring. See also option -t. -# -# -v Print out the version of this script -# -# DIAGNOSTICS: -# There are several ways to invoke this script if you got error -# "Can't locate Mozilla/LDAP/Conn.pm in @INC", or -# "usage: Undefined variable": -# -# 0. Prerequisite: NSPR, NSS, Mozilla LDAP C SDK, PerLDAP -# -# 1. Run this perl script via repl-monitor, which sets up LD_LIBRARY_PATH -# $ repl-monitor -# -# 2. If 1 does not work, try invoking this script as follows. -# Assuming contains Mozilla/LDAP: -# perl -I repl-monitor.pl -# -############################################################################# -# enable the use of our bundled perldap with our bundled ldapsdk libraries -# all of this nonsense can be omitted if the mozldapsdk and perldap are -# installed in the operating system locations (e.g. /usr/lib /usr/lib/perl5) -# this script is always invoked by repl-monitor-cgi.pl, which sets all of these -# If using this script standalone, be sure to set the shared lib path and -# the path to the perldap modules. - -$usage = "\nusage: $0 -f configuration-file [-h host] [-p port] [-r] [-u refresh-url] [-t refresh-interval]\n\nor : $0 -v\n"; - -use Getopt::Std; # parse command line arguments -use Mozilla::LDAP::Conn; # LDAP module for Perl -use Mozilla::LDAP::Utils qw(normalizeDN); # LULU, utilities. -use Mozilla::LDAP::API qw(:api :ssl :apiv3 :constant); # Direct access to C API -use Time::Local; # to convert GMT Z strings to localtime - -# -# Global variables -# -$product = "Directory Server Replication Monitor"; -$version = "Version 1.0"; -# -# ldap servers given or discovered from the replication agreements: -# @servers = (host:port=shadowport:binddn:password:cert_db) -# -# entries read from the connection section of the configuration file: -# @allconnections = (host:port=shadowport:binddn:password:cert_db) -# -# aliases of ldap servers read from the configuration file: -# %allaliases{$host:$port}= (alias) -# -# replicas discovered on all ldap servers -# @allreplicas = (server#:replicaroot:replicatype:serverid:replicadn) -# -# ruvs retrieved from all replicas -# @allruvs{replica#:masterid} = (rawcsn:decimalcsn;mon/day/year hh:mi:ss) -# -# agreements discovered on all ldap supplier servers: -# @allagreements = (supplier_replica#:consumer#:conntype:schedule:status) -# the array may take another format after the consumer replicas are located: -# @allagreements = (supplier_replica#:consumer_replica#:conntype:schedule:status) -# - -#main -{ - # turn off buffered I/O - $| = 1; - - # Check for legal options - if (!getopts('h:p:f:ru:t:v')) { - print $usage; - exit -1; - } - - if ($opt_v) { - print "$product - $version\n"; - exit; - } - - $interval = $opt_t; - $interval = 300 if ( !$interval || $interval <= 0 ); - - # Get current date/time - $nowraw = localtime(); - ($wday, $mm, $dd, $tt, $yy) = split(/ /, $nowraw); - $now = "$wday $mm $dd $yy $tt"; - - # if no -r (Reenter and skip html header), print html header - if (!$opt_r) { - # print the HTML header - &print_html_header; - } else { - # print separator for new replication set - print "

\n"; - } - - exit -1 if &validateArgs < 0; - exit if &read_cfg_file ($opt_f) < 0; - - # Start with the given host and port - # The index names in %ld are defined in Mozilla::LDAP::Utils::ldapArgs() - &add_server ("$ld{host}:$ld{port}:$ld{bind}:$ld{pswd}:$ld{cert}"); - - $serveridx = 0; - while ($serveridx <= $#servers) { - if (&get_replicas ($serveridx) != 0 && $serveridx == 0) { - my ($host, $port, $binddn) = split (/:/, $servers[0]); - print("Login to $host:$port as \"$binddn\" failed\n"); - exit; - } - $serveridx++; - } - - &find_consumer_replicas; - &process_suppliers; - - # All done! - well, for the current invokation only - # print "\n"; - exit; -} - -sub validateArgs -{ - my ($rc) = 0; - - %ld = Mozilla::LDAP::Utils::ldapArgs(); - - if (!$opt_v && !$opt_f) { - print "

Error: Missing configuration file.\n"; - print "

If you need help on the configuration file, Please go back and click the Help button.\n"; - #print $usage; # Don't show usage in CGI - $rc = -1; - } - elsif (!$opt_h) { - chop ($ld{"host"} = `hostname`); - } - - return $rc; -} - -sub read_cfg_file -{ - my ($fn) = @_; - unless (open(CFGFILEHANDLE, $fn)) { - print "

Error: Can't open \"$fn\": $!.\n"; - print "

If you need help on the configuration file, Please go back and click the Help button.\n"; - return -1; - } - $section = 0; - while () { - next if (/^\s*\#/ || /^\s*$/); - chop ($_); - if (m/^\[(.*)\]/) { - $section = $1; - } - else { - if ( $section =~ /conn/i ) { - push (@allconnections, $_); - } - elsif ( $section =~ /alias/i ) { - m/^\s*(\S.*)\s*=\s*(\S+)/; - $allaliases {$2} = $1; - } - elsif ( $section =~ /color/i ) { - m/^\s*(-?\d+)\s*=\s*(\S+)/; - $allcolors {$1} = $2; - } - } - } - if ( ! keys (%allcolors) ) { - $allcolors {0} = "#ccffcc"; #apple green - $allcolors {5} = "#ffffcc"; #cream yellow - $allcolors {60} = "#ffcccc"; #pale pink - } - @colorkeys = sort (keys (%allcolors)); - close (CFGFILEHANDLE); - return 0; -} - -sub get_replicas -{ - my ($serveridx) = @_; - my ($conn, $host, $port, $shadowport, $binddn, $bindpwd, $bindcert); - my ($others); - my ($replica, $replicadn); - my ($ruv, $replicaroot, $replicatype, $serverid, $masterid, $maxcsn); - my ($type, $flag, $i); - my ($myridx, $ridx, $cidx); - - # - # Bind to the server - # - ($host, $port, $binddn, $bindpwd, $bindcert) = split (/:/, "$servers[$serveridx]", 5); - - ($port, $shadowport) = split (/=/, $port); - $shadowport = $port if !$shadowport; - - $conn = new Mozilla::LDAP::Conn ($host, $shadowport, "$binddn", $bindpwd, $bindcert); - - return -1 if (!$conn); - - # - # Get all the replica on the server - # - $myridx = $#allreplicas + 1; - $replica = $conn->search ("cn=mapping tree,cn=config", - "sub", - "(objectClass=nsDS5Replica)", 0, - qw(nsDS5ReplicaRoot nsDS5ReplicaType nsDS5Flags nsDS5ReplicaId)); - while ($replica) { - $replicadn = $replica->getDN; - $replicaroot = normalizeDN ($replica->{nsDS5ReplicaRoot}[0]); - $type = $replica->{nsDS5ReplicaType}[0]; - $flag = $replica->{nsDS5Flags}[0]; - $serverid = $replica->{nsDS5ReplicaId}[0]; - - # flag = 0: change log is not created - # type = 2: read only replica - # type = 3: updatable replica - $replicatype = $flag == 0 ? "consumer" : ($type == 2 ? "hub" : "master"); - - push (@allreplicas, "$serveridx:$replicaroot:$replicatype:$serverid:$replicadn"); - - $replica = $conn->nextEntry (); - } - - # - # Get ruv for each replica - # - for ($ridx = $myridx; $ridx <= $#allreplicas; $ridx++) { - - $replicaroot = $1 if ($allreplicas[$ridx] =~ /^\d+:([^:]*)/); - # do a one level search with nsuniqueid in the filter - this will force the use of the - # nsuniqueid index instead of the entry dn index, which seems to be unreliable in - # heavily loaded servers - $ruv = $conn->search($replicaroot, "one", - "(&(nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff)(objectClass=nsTombstone))", - 0, qw(nsds50ruv nsruvReplicaLastModified)); - next if !$ruv; # this should be an error case . . . - - for ($ruv->getValues('nsds50ruv')) { - if (m/\{replica\s+(\d+).+?\}\s*\S+\s*(\S+)/i) { - $masterid = $1; - $maxcsn = &to_decimal_csn ($2); - $allruvs {"$ridx:$masterid"} = "$2:$maxcsn"; - } - } - - for ($ruv->getValues('nsruvReplicaLastModified')) { - if (m/\{replica\s+(\d+).+?\}\s*(\S+)/i) { - $masterid = $1; - $lastmodifiedat = hex($2); - my ($sec, $min, $hour, $mday, $mon, $year) = localtime ($lastmodifiedat); - $mon++; - $year += 1900; - $hour = "0".$hour if ($hour < 10); - $min = "0".$min if ($min < 10); - $sec = "0".$sec if ($sec < 10); - $allruvs {"$ridx:$masterid"} .= ";$mon/$mday/$year $hour:$min:$sec"; - } - } - } - - # - # Get all agreements for each supplier replica - # - for ($ridx = $myridx; $ridx <= $#allreplicas; $ridx++) { - $_ = $allreplicas[$ridx]; - - # Skip consumers - next if m/:consumer:/i; - - m/:([^:]*)$/; - $replicadn = $1; - my @attrlist = qw(cn nsds5BeginReplicaRefresh nsds5replicaUpdateInProgress - nsds5ReplicaLastInitStatus nsds5ReplicaLastInitStart - nsds5ReplicaLastInitEnd nsds5replicaReapActive - nsds5replicaLastUpdateStart nsds5replicaLastUpdateEnd - nsds5replicaChangesSentSinceStartup nsds5replicaLastUpdateStatus - nsds5ReplicaHost - nsds5ReplicaPort nsDS5ReplicaBindMethod nsds5ReplicaUpdateSchedule); - $agreement = $conn->search("$replicadn", "sub", "(objectClass=nsDS5ReplicationAgreement)", - 0, @attrlist); - while ($agreement) { - - my %agmt = (); - # Push consumer to server stack if we have not already - $host = ($agreement->getValues('nsDS5ReplicaHost'))[0]; - $port = ($agreement->getValues('nsDS5ReplicaPort'))[0]; - $cidx = &add_server ("$host:$port"); - - for (@attrlist) { - $agmt{$_} = ($agreement->getValues($_))[0]; - } - if ($agmt{nsDS5ReplicaBindMethod} =~ /simple/i) { - $agmt{nsDS5ReplicaBindMethod} = 'n'; - } - if (!$agmt{nsds5ReplicaUpdateSchedule} || - ($agmt{nsds5ReplicaUpdateSchedule} eq '0000-2359 0123456') || - ($agmt{nsds5ReplicaUpdateSchedule} eq '*') || - ($agmt{nsds5ReplicaUpdateSchedule} eq '* *')) { - $agmt{nsds5ReplicaUpdateSchedule} = 'always in sync'; - } - - $agmt{ridx} = $ridx; - $agmt{cidx} = $cidx; - push @allagreements, \%agmt; - - $agreement = $conn->nextEntry (); - } - } - - $conn->close; - - return 0; -} - -# -# Initially, the agreements have consumer host:port info instead of -# replica info. This routine will find the consumer replica info -# -sub find_consumer_replicas -{ - my ($m_ridx); # index of master's replica - my ($s_ridx); # index of supplier's replica - my ($c_ridx); # index of consumer's replica - my ($c_sidx); # index of consumer server - my ($remainder); # - my ($s_replicaroot); # supplier replica root - my ($c_replicaroot); # consumer replica root - my ($j, $val); - - # - # Loop through every agreement defined on the current supplier replica - # - foreach (@allagreements) { - $s_ridx = $_->{ridx}; - $c_sidx = $_->{cidx}; - $s_replicaroot = $1 if ($allreplicas[$s_ridx] =~ /^\d+:([^:]*)/); - $c_replicaroot = ""; - - # $c_ridx will be assigned to -$c_sidx - # if the condumer is not accessible - # $c_sidx will not be zero since it's - # not the first server. - $c_ridx = -$c_sidx; # $c_sidx will not be zero - - # Loop through consumer's replicas and find - # the counter part for the current supplier - # replica - for ($j = 0; $j <= $#allreplicas; $j++) { - - # Get a replica on consumer - # I'm not sure what's going on here, but possibly could be made - # much simpler with normalizeDN and/or ldap_explode_dn - if ($allreplicas[$j] =~ /^$c_sidx:([^:]*)/) { - $val = $1; - - # We need to find out the consumer - # replica that matches the supplier - # replicaroot most. - if ($s_replicaroot =~ /^.*$val$/i && - length ($val) >= length ($c_replicaroot)) { - $c_ridx = $j; - - # Avoid case-sensitive comparison - last if (length($s_replicaroot) == length($val)); - $c_replicaroot = $val; - } - } - } - $_->{ridx} = $s_ridx; - $_->{cidx} = $c_ridx; - } -} - -sub process_suppliers -{ - my ($ridx, $mid, $maxcsn); - - $mid = ""; - - $last_sidx = -1; # global variable for print html page - - for ($ridx = 0; $ridx <= $#allreplicas; $ridx++) { - - # Skip consumers and hubs - next if $allreplicas[$ridx] !~ /:master:(\d+):/i; - $mid = $1; - - # Skip replicas without agreements defined yet - next if (! grep {$_->{ridx} == $ridx} @allagreements); - - $maxcsn = &print_master_header ($ridx, $mid); - if ( "$maxcsn" != "none" ) { - &print_consumer_header (); - &print_consumers ($ridx, $mid); - } - &print_supplier_end; - } - - if ($mid eq "") { - print "

The server is not a master or it has no replication agreement\n"; - } -} - -sub print_master_header -{ - my ($ridx, $mid) = @_; - my ($myruv) = $allruvs {"$ridx:$mid"}; - my ($maxcsnval) = split ( /;/, "$myruv" ); - my ($maxcsn) = &to_string_csn ($maxcsnval); - my ($sidx, $replicaroot, $replicatype, $serverid) = split (/:/, $allreplicas[$ridx]); - - # Print the master name - if ( $last_sidx != $sidx ) { - my ($ldapurl) = &get_ldap_url ($sidx, $sidx); - &print_legend if ( $last_sidx < 0); - print "


\n"; - print "\n

\n"; - print "Master:  $ldapurl
\n"; - $last_sidx = $sidx; - } - - # Print the current replica info onthe master - print "\n

\n"; - - print "\n\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; -} - -sub print_consumers -{ - my ($m_ridx, $mid) = @_; - my ($ignore, $m_replicaroot) = split (/:/, $allreplicas[$m_ridx]); - my (@consumers, @ouragreements, @myagreements); - my ($s_ridx, $c_ridx, $conntype, $schedule, $status); - my ($c_maxcsn_str, $lag, $markcolor); - my ($c_replicaroot, $c_replicatype); - my ($first_entry); - my ($nrows); - my ($found); - - undef @ouragreements; - - # Collect all the consumer replicas for the current master replica - push (@consumers, $m_ridx); - foreach (@consumers) { - $s_ridx = $_; - for (@allagreements) { - next if ($_->{ridx} != $s_ridx); - $c_ridx = $_->{cidx}; - next if $c_ridx == $m_ridx; - push @ouragreements, $_; - $found = 0; - foreach (@consumers) { - if ($_ == $c_ridx) { - $found = 1; - last; - } - } - push (@consumers, $c_ridx) if !$found; - } - } - - # Print each consumer replica - my ($myruv) = $allruvs {"$m_ridx:$mid"}; - my ($m_maxcsn) = split ( /;/, "$myruv" ); - foreach (@consumers) { - $c_ridx = $_; - next if $c_ridx == $m_ridx; - - if ($c_ridx >= 0) { - $myruv = $allruvs {"$c_ridx:$mid"}; - ($c_maxcsn, $c_lastmodified) = split ( /;/, "$myruv" ); - ($c_maxcsn_str, $lag, $markcolor) = &cacl_time_lag ($m_maxcsn, $c_maxcsn); - $c_maxcsn_str =~ s/ /\/; - ($c_sidx, $c_replicaroot, $c_replicatype) = split (/:/, $allreplicas[$c_ridx]); - $c_replicaroot = "same as master" if $m_replicaroot eq $c_replicaroot; - } - else { - # $c_ridx is actually -$c_sidx when c is not available - $c_sidx = -$c_ridx; - $c_maxcsn_str = "_"; - $lag = "n/a"; - $markcolor = red; - $c_replicaroot = "_"; - $c_replicatype = "_"; - } - - $nrows = 0; - foreach (@ouragreements) { - next if ($_->{cidx} != $c_ridx); - $nrows++; - } - - $first_entry = 1; - foreach (@ouragreements) { - next if ($_->{cidx} != $c_ridx); - $s_ridx = $_->{ridx}; - $conntype = $_->{nsDS5ReplicaBindMethod}; - $status = $_->{nsds5replicaLastUpdateStatus}; - $schedule = $_->{nsds5ReplicaUpdateSchedule}; - $s_sidx = $1 if $allreplicas [$s_ridx] =~ /^(\d+):/; - $s_ldapurl = &get_ldap_url ($s_sidx, "n/a"); - - # Print out the consumer's replica and ruvs - print "\n\n"; - if ($first_entry) { - $first_entry = 0; - $c_ldapurl = &get_ldap_url ($c_sidx, $conntype); - print "\n"; - print "\n"; - print "\n"; - print "\n"; - } - print "\n"; - my $changecount = $_->{nsds5replicaChangesSentSinceStartup}; - if ( $changecount =~ /^$mid:(\d+)\/(\d+) / || $changecount =~ / $mid:(\d+)\/(\d+) / ) { - $changecount = "$1 / $2"; - } - elsif ( $changecount =~ /^(\d+)$/ ) { - $changecount = $changecount . " / " . "$_->{nsds5replicaChangesSkippedSinceStartup}"; - } - else { - $changecount = "0 / 0"; - } - print "\n"; - my $redfontstart = ""; - my $redfontend = ""; - if ($status =~ /error/i) { - $redfontstart = ""; - $redfontend = ""; - } - elsif ($status =~ /^(\d+) /) { - if ( $1 != 0 ) { - # warning - $redfontstart = ""; - $redfontend = ""; - } - } - print "\n"; - print "\n"; - print "\n"; - if ( $schedule =~ /always/i ) { - print "\n"; - } - else { - my ($ndays, @days); - $schedule =~ /(\d\d)(\d\d)-(\d\d)(\d\d) (\d+)/; - print "\n"; - $ndays = $5; - $ndays =~ s/(\d)/$1,/g; - @days = (Sun,Mon,Tue,Wed,Thu,Fri,Sat)[eval $ndays]; - print "\n"; - } - print "\n"; - } - } -} - -sub cacl_time_lag -{ - my ($s_maxcsn, $c_maxcsn) = @_; - my ($markcolor); - my ($csn_str); - my ($s_tm, $c_tm, $lag_tm, $lag_str, $hours, $minute); - - $csn_str = &to_string_csn ($c_maxcsn); - - if ($s_maxcsn && !$c_maxcsn) { - $lag_str = "- ?:??:??"; - $markcolor = &get_color (36000); # assume consumer has big latency - } - elsif (!$s_maxcsn && $c_maxcsn) { - $lag_str = "+ ?:??:??"; - $markcolor = &get_color (1); # consumer is ahead of supplier - } - elsif ($s_maxcsn le $c_maxcsn) { - $lag_str = "0:00:00"; - $markcolor = &get_color (0); - } - else { - my ($rawcsn, $decimalcsn) = split (/:/, $s_maxcsn); - ($s_tm) = split(/ /, $decimalcsn); - - ($rawcsn, $decimalcsn) = split (/:/, $c_maxcsn); - ($c_tm) = split(/ /, $decimalcsn); - if ($s_tm > $c_tm) { - $lag_tm = $s_tm - $c_tm; - $lag_str = "- "; - $markcolor = &get_color ($lag_tm); - } - else { - $lag_tm = $c_tm - $s_tm; - $lag_str = "+ "; - $markcolor = $allcolors{ $colorkeys[0] }; # no delay - } - $hours = int ($lag_tm / 3600); - $lag_str .= "$hours:"; - - $lag_tm = $lag_tm % 3600; - $minutes = int ($lag_tm / 60); - $minutes = "0".$minutes if ($minutes < 10); - $lag_str .= "$minutes:"; - - $lag_tm = $lag_tm % 60; - $lag_tm = "0".$lag_tm if ($lag_tm < 10); - $lag_str .= "$lag_tm"; - } - return ($csn_str, $lag_str, $markcolor); -} - -# -# The subroutine would append a new entry to the end of -# @servers if the host and port are new to @servers. -# -sub add_server -{ - my ($host, $port, $binddn, $bindpwd, $bindcert) = split (/:/, "@_"); - my ($shadowport) = $port; - my ($domainpattern) = '\.[^:]+'; - my ($i); - - # Remove the domain name from the host name - my ($hostnode) = $host; - $hostnode = $1 if $host =~ /^(\w+)\./; - - # new host:port - if ($binddn eq "" || $bindpwd eq "" && $bindcert eq "") { - # - # Look up connection parameter in the order of - # host:port - # host:* - # *:port - # *:* - # - my (@myconfig, $h, $p, $d, $w, $c); - (@myconfig = grep (/^$hostnode($domainpattern)*:$port\D/i, @allconnections)) || - (@myconfig = grep (/^$hostnode($domainpattern)*:\*:/i, @allconnections)) || - (@myconfig = grep (/^\*:$port\D/, @allconnections)) || - (@myconfig = grep (/^\*:\*\D/, @allconnections)); - if ($#myconfig >= 0) { - ($h, $p, $d, $w, $c) = split (/:/, $myconfig[0]); - ($p, $shadowport) = split (/=/, $p); - $p = "" if $p eq "*"; - $c = "" if $c eq "*"; - } - if ($binddn eq "" || $binddn eq "*") { - if ($d eq "" || $d eq "*") { - $binddn = "cn=Directory Manager"; - } - else { - $binddn = $d; - } - } - $bindpwd = $w if ($bindpwd eq "" || $bindpwd eq "*"); - $bindcert = $c if ($bindcert eq "" || $bindcert eq "*"); - } - - for ($i = 0; $i <= $#servers; $i++) { - return $i if ($servers[$i] =~ /$hostnode($domainpattern)*:\d*=$shadowport\D/i); - } - - if ($shadowport) { - push (@servers, "$host:$port=$shadowport:$binddn:$bindpwd:$bindcert"); - } else { - push (@servers, "$host:$port:$binddn:$bindpwd:$bindcert"); - } - return $i; -} - -sub get_ldap_url -{ - my ($sidx, $conntype) = @_; - my ($host, $port) = split(/:/, $servers[$sidx]); - my ($shadowport); - ($port, $shadowport) = split (/=/, $port); - my ($protocol, $ldapurl); - - if ($port eq 636 && $conntype eq "0" || $conntype =~ /SSL/i) { - $protocol = ldaps; - } - else { - $protocol = ldap; - } - my ($instance) = $allaliases { "$host:$port" }; - $instance = "$host:$port" if !$instance; - if ($conntype eq "n/a") { - $ldapurl = $instance; - } - else { - $ldapurl = "$instance"; - } - return $ldapurl; -} - -sub to_decimal_csn -{ - my ($maxcsn) = @_; - if (!$maxcsn || $maxcsn eq "") { - return "none"; - } - - my ($tm, $seq, $masterid, $subseq) = unpack("a8 a4 a4 a4", $maxcsn); - - $tm = hex($tm); - $seq = hex($seq); - $masterid = hex($masterid); - $subseq = hex($subseq); - - return "$tm $seq $masterid $subseq"; -} - -sub to_string_csn -{ - my ($rawcsn, $decimalcsn) = split(/:/, "@_"); - if (!$rawcsn || $rawcsn eq "") { - return "none"; - } - my ($tm, $seq, $masterid, $subseq) = split(/ /, $decimalcsn); - my ($sec, $min, $hour, $mday, $mon, $year) = localtime($tm); - $mon++; - $year += 1900; - foreach ($sec, $min, $hour, $mday, $mon) { - $_ = "0".$_ if ($_ < 10); - } - my ($csnstr) = "$mon/$mday/$year $hour:$min:$sec"; - $csnstr .= " $seq $subseq" if ( $seq != 0 || $subseq != 0 ); - return "$rawcsn ($csnstr)"; -} - -sub get_color -{ - my ($lag_minute) = @_; - $lag_minute /= 60; - my ($color) = $allcolors { $colorkeys[0] }; - foreach (@colorkeys) { - last if ($lag_minute < $_); - $color = $allcolors {$_}; - } - return $color; -} - -# subroutine to remove escaped encoding - -sub unescape -{ - #my ($_) = @_; - tr/+/ /; - s/%(..)/pack("c",hex($1))/ge; - $_; -} - -sub print_html_header -{ - # print the HTML header - - print "Content-type: text/html\n\n"; - print "\n"; - print "Replication Status\n"; - # print "\n"; - print "\n\n"; - - if ($opt_u) { - print "\n"; - } - - print "
\n"; - print "Replica ID: "; - print "$serverid\n"; - - print "Replica Root: "; - print "$replicaroot\n"; - - print "Max CSN: "; - print "$maxcsn\n"; - - return $maxcsn; -} - -sub print_consumer_header -{ - #Print the header of consumer - print "\n
ReceiverTime LagMax CSNLast Modify TimeSupplierSent/SkippedUpdate StatusUpdate StartedUpdate EndedScheduleSSL?
$c_ldapurl
Type: $c_replicatype
$lag
$c_maxcsn_str$c_lastmodified
$s_ldapurl
$changecount$redfontstart$status$redfontend", &format_z_time($_->{nsds5replicaLastUpdateStart}), "", &format_z_time($_->{nsds5replicaLastUpdateEnd}), "$schedule$1:$2-$3:$4@days$conntype
\n"; - print "\n"; - print "
$now"; - print "Directory Server Replication Status\n"; - - if ($opt_u) { - print "
(This page updates every $interval seconds)\n"; - } - - print "
$version"; - print "
\n"; -} - -sub print_legend -{ - my ($nlegends) = $#colorkeys + 1; - print "\n

Time Lag Legend:

\n"; - print "\n\n"; - my ($i, $j); - for ($i = 0; $i < $nlegends - 1; $i++) { - $j = $colorkeys[$i]; - print "\n\n"; - } - $j = $colorkeys[$i]; - print "\n\n"; - print "\n\n"; - print "
within $colorkeys[$i+1] min
over $colorkeys[$i] min
server n/a

\n"; -} - -sub print_supplier_end -{ - print "\n"; -} - -# given a string in generalized time format, convert to ascii time -sub format_z_time -{ - my $zstr = shift; - return "n/a" if (! $zstr); - my ($year, $mon, $day, $hour, $min, $sec) = - ($zstr =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/); - my $time = timegm($sec, $min, $hour, $day, ($mon-1), $year); - ($sec, $min, $hour, $day, $mon, $year) = localtime($time); - $mon++; - $year += 1900; - foreach ($sec, $min, $hour, $day, $mon) { - $_ = "0".$_ if ($_ < 10); - } - - return "$mon/$day/$year $hour:$min:$sec"; -} diff --git a/ldap/admin/src/scripts/repl-monitor.pl.in b/ldap/admin/src/scripts/repl-monitor.pl.in new file mode 100755 index 00000000..4ae9bf4b --- /dev/null +++ b/ldap/admin/src/scripts/repl-monitor.pl.in @@ -0,0 +1,997 @@ +#!@perlexec@ +# BEGIN COPYRIGHT BLOCK +# 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; version 2 of the License. +# +# 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. +# +# In addition, as a special exception, Red Hat, Inc. gives You the additional +# right to link the code of this Program with code not covered under the GNU +# General Public License ("Non-GPL Code") and to distribute linked combinations +# including the two, subject to the limitations in this paragraph. Non-GPL Code +# permitted under this exception must only link to the code of this Program +# through those well defined interfaces identified in the file named EXCEPTION +# found in the source code files (the "Approved Interfaces"). The files of +# Non-GPL Code may instantiate templates or use macros or inline functions from +# the Approved Interfaces without causing the resulting work to be covered by +# the GNU General Public License. Only Red Hat, Inc. may make changes or +# additions to the list of Approved Interfaces. You must obey the GNU General +# Public License in all respects for all of the Program code and other code used +# in conjunction with the Program except the Non-GPL Code covered by this +# exception. If you modify this file, you may extend this exception to your +# version of the file, but you are not obligated to do so. If you do not wish to +# provide this exception without modification, you must delete this exception +# statement from your version and license this file solely under the GPL without +# exception. +# +# +# Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +# Copyright (C) 2007 Red Hat, Inc. +# All rights reserved. +# END COPYRIGHT BLOCK +############################################################################## +# +# FILE: repl-monitor.pl +# +# SYNOPSIS: +# repl-monitor.pl -f configuration-file [-h host] [-p port] [-r] +# [-u refresh-url] [-t refresh-interval] +# +# repl-monitor.pl -v +# +# DESCRIPTION: +# Given an LDAP replication "supplier" server, crawl over all the ldap +# servers via direct or indirect replication agreements. +# For each master replica discovered, display the maxcsn of the master +# and the replication status of all its lower level replicas. +# All output is in HTML. +# +# OPTIONS: +# -f configuration-file +# The configuration file contains the sections for the connection +# parameters, the server alias, and the thresholds for different colors +# when display the time lags between consumers and master. +# If the Admin Server is running on Windows, the configuration-file +# name may have format "D:/opt/replmon.conf". +# +# The connection parameter section consists of the section name +# followed by one of more connection parameter entries: +# +# [connection] +# host:port:binddn:bindpwd:bindcert +# host:port=shadowport:binddn:bindpwd:bindcert +# ... +# +# where host:port default (*:*) to that in a replication agreement, +# binddn default (*) to "cn=Directory Manager", and bindcert is the +# pathname of cert db if you want the script to connect to the server +# via SSL. If bindcert is omitted, the connection will be simple +# bind. +# "port=shadowport" means to use shadowport instead of port if port +# is specified in the replication agreement. This is useful when +# for example, ssl port is specified in a replication agreement, +# but you can't access the cert db from the machine where this +# script is running. So you could let the script to map the ssl +# port to a non-ssl port and use the simple bind. +# +# A server may have a dedicated or a share entry in the connection +# section. The script will find out the most matched entry for a given +# server. For example, if all the ldap servers except host1 share the +# same binddn and bindpassword, the connection section then just need +# two entries: +# +# [connection] +# *:*:binddn:bindpassword: +# host1:*:binddn:bindpassword: +# +# If a host:port is assigned an alias, then the alias instead of +# host:port will be displayed in The output file. Each host:port +# can have only one alias. But each alias may be used by more than +# one host:port. +# +# [alias] +# alias = host:port +# ... +# +# CSN time lags between masters and consumers might be displayed in +# different colors based on their range. The thresholds for different +# colors may be specified in color section: +# +# [color] +# lowmark (in minutes) = color +# ... +# +# If the color section or color entry is missing, the default color +# set is: green for [0-5) minutes lag, yellow [5-60), and red 60 and more. +# +# -h host +# Initial replication supplier's host. Default to the current host. +# +# -p port +# Initial replication supplier's port. Default to 389. +# +# -r If specified, -r causes the routine to be entered without printing +# HTML header information. This is suitable when making multiple calls +# to this routine (e.g. when specifying multiple, different, "unrelated" +# supplier servers) and expecting a single HTML output. +# +# -t refresh-interval +# Specify the refresh interval in seconds. This option has to be +# jointly used with option -u. +# +# -u refresh-url +# The output HTML file may invoke a CGI program periodically. If +# this CGI program in turn calls this script, the effect is that +# the output HTML file would automatically refresh itself. This +# is useful for continuing monitoring. See also option -t. +# +# -v Print out the version of this script +# +# DIAGNOSTICS: +# There are several ways to invoke this script if you got error +# "Can't locate Mozilla/LDAP/Conn.pm in @INC", or +# "usage: Undefined variable": +# +# 0. Prerequisite: NSPR, NSS, Mozilla LDAP C SDK, PerLDAP +# +# 1. Run this perl script via repl-monitor, which sets up LD_LIBRARY_PATH +# $ repl-monitor +# +# 2. If 1 does not work, try invoking this script as follows. +# Assuming contains Mozilla/LDAP: +# perl -I repl-monitor.pl +# +############################################################################# +# enable the use of our bundled perldap with our bundled ldapsdk libraries +# all of this nonsense can be omitted if the mozldapsdk and perldap are +# installed in the operating system locations (e.g. /usr/lib /usr/lib/perl5) +# this script is always invoked by repl-monitor-cgi.pl, which sets all of these +# If using this script standalone, be sure to set the shared lib path and +# the path to the perldap modules. + +use lib qw(@perlpath@); + +$usage = "\nusage: $0 -f configuration-file [-h host] [-p port] [-r] [-u refresh-url] [-t refresh-interval]\n\nor : $0 -v\n"; + +use Getopt::Std; # parse command line arguments +use Mozilla::LDAP::Conn; # LDAP module for Perl +use Mozilla::LDAP::Utils qw(normalizeDN); # LULU, utilities. +use Mozilla::LDAP::API qw(:api :ssl :apiv3 :constant); # Direct access to C API +use Time::Local; # to convert GMT Z strings to localtime + +# +# Global variables +# +$product = "Directory Server Replication Monitor"; +$version = "Version 1.0"; +# +# ldap servers given or discovered from the replication agreements: +# @servers = (host:port=shadowport:binddn:password:cert_db) +# +# entries read from the connection section of the configuration file: +# @allconnections = (host:port=shadowport:binddn:password:cert_db) +# +# aliases of ldap servers read from the configuration file: +# %allaliases{$host:$port}= (alias) +# +# replicas discovered on all ldap servers +# @allreplicas = (server#:replicaroot:replicatype:serverid:replicadn) +# +# ruvs retrieved from all replicas +# @allruvs{replica#:masterid} = (rawcsn:decimalcsn;mon/day/year hh:mi:ss) +# +# agreements discovered on all ldap supplier servers: +# @allagreements = (supplier_replica#:consumer#:conntype:schedule:status) +# the array may take another format after the consumer replicas are located: +# @allagreements = (supplier_replica#:consumer_replica#:conntype:schedule:status) +# + +#main +{ + # turn off buffered I/O + $| = 1; + + # Check for legal options + if (!getopts('h:p:f:ru:t:v')) { + print $usage; + exit -1; + } + + if ($opt_v) { + print "$product - $version\n"; + exit; + } + + $interval = $opt_t; + $interval = 300 if ( !$interval || $interval <= 0 ); + + # Get current date/time + $nowraw = localtime(); + ($wday, $mm, $dd, $tt, $yy) = split(/ /, $nowraw); + $now = "$wday $mm $dd $yy $tt"; + + # if no -r (Reenter and skip html header), print html header + if (!$opt_r) { + # print the HTML header + &print_html_header; + } else { + # print separator for new replication set + print "

\n"; + } + + exit -1 if &validateArgs < 0; + exit if &read_cfg_file ($opt_f) < 0; + + # Start with the given host and port + # The index names in %ld are defined in Mozilla::LDAP::Utils::ldapArgs() + &add_server ("$ld{host}:$ld{port}:$ld{bind}:$ld{pswd}:$ld{cert}"); + + $serveridx = 0; + while ($serveridx <= $#servers) { + if (&get_replicas ($serveridx) != 0 && $serveridx == 0) { + my ($host, $port, $binddn) = split (/:/, $servers[0]); + print("Login to $host:$port as \"$binddn\" failed\n"); + exit; + } + $serveridx++; + } + + &find_consumer_replicas; + &process_suppliers; + + # All done! - well, for the current invokation only + # print "\n"; + exit; +} + +sub validateArgs +{ + my ($rc) = 0; + + %ld = Mozilla::LDAP::Utils::ldapArgs(); + + if (!$opt_v && !$opt_f) { + print "

Error: Missing configuration file.\n"; + print "

If you need help on the configuration file, Please go back and click the Help button.\n"; + #print $usage; # Don't show usage in CGI + $rc = -1; + } + elsif (!$opt_h) { + chop ($ld{"host"} = `hostname`); + } + + return $rc; +} + +sub read_cfg_file +{ + my ($fn) = @_; + unless (open(CFGFILEHANDLE, $fn)) { + print "

Error: Can't open \"$fn\": $!.\n"; + print "

If you need help on the configuration file, Please go back and click the Help button.\n"; + return -1; + } + $section = 0; + while () { + next if (/^\s*\#/ || /^\s*$/); + chop ($_); + if (m/^\[(.*)\]/) { + $section = $1; + } + else { + if ( $section =~ /conn/i ) { + push (@allconnections, $_); + } + elsif ( $section =~ /alias/i ) { + m/^\s*(\S.*)\s*=\s*(\S+)/; + $allaliases {$2} = $1; + } + elsif ( $section =~ /color/i ) { + m/^\s*(-?\d+)\s*=\s*(\S+)/; + $allcolors {$1} = $2; + } + } + } + if ( ! keys (%allcolors) ) { + $allcolors {0} = "#ccffcc"; #apple green + $allcolors {5} = "#ffffcc"; #cream yellow + $allcolors {60} = "#ffcccc"; #pale pink + } + @colorkeys = sort (keys (%allcolors)); + close (CFGFILEHANDLE); + return 0; +} + +sub get_replicas +{ + my ($serveridx) = @_; + my ($conn, $host, $port, $shadowport, $binddn, $bindpwd, $bindcert); + my ($others); + my ($replica, $replicadn); + my ($ruv, $replicaroot, $replicatype, $serverid, $masterid, $maxcsn); + my ($type, $flag, $i); + my ($myridx, $ridx, $cidx); + + # + # Bind to the server + # + ($host, $port, $binddn, $bindpwd, $bindcert) = split (/:/, "$servers[$serveridx]", 5); + + ($port, $shadowport) = split (/=/, $port); + $shadowport = $port if !$shadowport; + + $conn = new Mozilla::LDAP::Conn ($host, $shadowport, "$binddn", $bindpwd, $bindcert); + + return -1 if (!$conn); + + # + # Get all the replica on the server + # + $myridx = $#allreplicas + 1; + $replica = $conn->search ("cn=mapping tree,cn=config", + "sub", + "(objectClass=nsDS5Replica)", 0, + qw(nsDS5ReplicaRoot nsDS5ReplicaType nsDS5Flags nsDS5ReplicaId)); + while ($replica) { + $replicadn = $replica->getDN; + $replicaroot = normalizeDN ($replica->{nsDS5ReplicaRoot}[0]); + $type = $replica->{nsDS5ReplicaType}[0]; + $flag = $replica->{nsDS5Flags}[0]; + $serverid = $replica->{nsDS5ReplicaId}[0]; + + # flag = 0: change log is not created + # type = 2: read only replica + # type = 3: updatable replica + $replicatype = $flag == 0 ? "consumer" : ($type == 2 ? "hub" : "master"); + + push (@allreplicas, "$serveridx:$replicaroot:$replicatype:$serverid:$replicadn"); + + $replica = $conn->nextEntry (); + } + + # + # Get ruv for each replica + # + for ($ridx = $myridx; $ridx <= $#allreplicas; $ridx++) { + + $replicaroot = $1 if ($allreplicas[$ridx] =~ /^\d+:([^:]*)/); + # do a one level search with nsuniqueid in the filter - this will force the use of the + # nsuniqueid index instead of the entry dn index, which seems to be unreliable in + # heavily loaded servers + $ruv = $conn->search($replicaroot, "one", + "(&(nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff)(objectClass=nsTombstone))", + 0, qw(nsds50ruv nsruvReplicaLastModified)); + next if !$ruv; # this should be an error case . . . + + for ($ruv->getValues('nsds50ruv')) { + if (m/\{replica\s+(\d+).+?\}\s*\S+\s*(\S+)/i) { + $masterid = $1; + $maxcsn = &to_decimal_csn ($2); + $allruvs {"$ridx:$masterid"} = "$2:$maxcsn"; + } + } + + for ($ruv->getValues('nsruvReplicaLastModified')) { + if (m/\{replica\s+(\d+).+?\}\s*(\S+)/i) { + $masterid = $1; + $lastmodifiedat = hex($2); + my ($sec, $min, $hour, $mday, $mon, $year) = localtime ($lastmodifiedat); + $mon++; + $year += 1900; + $hour = "0".$hour if ($hour < 10); + $min = "0".$min if ($min < 10); + $sec = "0".$sec if ($sec < 10); + $allruvs {"$ridx:$masterid"} .= ";$mon/$mday/$year $hour:$min:$sec"; + } + } + } + + # + # Get all agreements for each supplier replica + # + for ($ridx = $myridx; $ridx <= $#allreplicas; $ridx++) { + $_ = $allreplicas[$ridx]; + + # Skip consumers + next if m/:consumer:/i; + + m/:([^:]*)$/; + $replicadn = $1; + my @attrlist = qw(cn nsds5BeginReplicaRefresh nsds5replicaUpdateInProgress + nsds5ReplicaLastInitStatus nsds5ReplicaLastInitStart + nsds5ReplicaLastInitEnd nsds5replicaReapActive + nsds5replicaLastUpdateStart nsds5replicaLastUpdateEnd + nsds5replicaChangesSentSinceStartup nsds5replicaLastUpdateStatus + nsds5ReplicaHost + nsds5ReplicaPort nsDS5ReplicaBindMethod nsds5ReplicaUpdateSchedule); + $agreement = $conn->search("$replicadn", "sub", "(objectClass=nsDS5ReplicationAgreement)", + 0, @attrlist); + while ($agreement) { + + my %agmt = (); + # Push consumer to server stack if we have not already + $host = ($agreement->getValues('nsDS5ReplicaHost'))[0]; + $port = ($agreement->getValues('nsDS5ReplicaPort'))[0]; + $cidx = &add_server ("$host:$port"); + + for (@attrlist) { + $agmt{$_} = ($agreement->getValues($_))[0]; + } + if ($agmt{nsDS5ReplicaBindMethod} =~ /simple/i) { + $agmt{nsDS5ReplicaBindMethod} = 'n'; + } + if (!$agmt{nsds5ReplicaUpdateSchedule} || + ($agmt{nsds5ReplicaUpdateSchedule} eq '0000-2359 0123456') || + ($agmt{nsds5ReplicaUpdateSchedule} eq '*') || + ($agmt{nsds5ReplicaUpdateSchedule} eq '* *')) { + $agmt{nsds5ReplicaUpdateSchedule} = 'always in sync'; + } + + $agmt{ridx} = $ridx; + $agmt{cidx} = $cidx; + push @allagreements, \%agmt; + + $agreement = $conn->nextEntry (); + } + } + + $conn->close; + + return 0; +} + +# +# Initially, the agreements have consumer host:port info instead of +# replica info. This routine will find the consumer replica info +# +sub find_consumer_replicas +{ + my ($m_ridx); # index of master's replica + my ($s_ridx); # index of supplier's replica + my ($c_ridx); # index of consumer's replica + my ($c_sidx); # index of consumer server + my ($remainder); # + my ($s_replicaroot); # supplier replica root + my ($c_replicaroot); # consumer replica root + my ($j, $val); + + # + # Loop through every agreement defined on the current supplier replica + # + foreach (@allagreements) { + $s_ridx = $_->{ridx}; + $c_sidx = $_->{cidx}; + $s_replicaroot = $1 if ($allreplicas[$s_ridx] =~ /^\d+:([^:]*)/); + $c_replicaroot = ""; + + # $c_ridx will be assigned to -$c_sidx + # if the condumer is not accessible + # $c_sidx will not be zero since it's + # not the first server. + $c_ridx = -$c_sidx; # $c_sidx will not be zero + + # Loop through consumer's replicas and find + # the counter part for the current supplier + # replica + for ($j = 0; $j <= $#allreplicas; $j++) { + + # Get a replica on consumer + # I'm not sure what's going on here, but possibly could be made + # much simpler with normalizeDN and/or ldap_explode_dn + if ($allreplicas[$j] =~ /^$c_sidx:([^:]*)/) { + $val = $1; + + # We need to find out the consumer + # replica that matches the supplier + # replicaroot most. + if ($s_replicaroot =~ /^.*$val$/i && + length ($val) >= length ($c_replicaroot)) { + $c_ridx = $j; + + # Avoid case-sensitive comparison + last if (length($s_replicaroot) == length($val)); + $c_replicaroot = $val; + } + } + } + $_->{ridx} = $s_ridx; + $_->{cidx} = $c_ridx; + } +} + +sub process_suppliers +{ + my ($ridx, $mid, $maxcsn); + + $mid = ""; + + $last_sidx = -1; # global variable for print html page + + for ($ridx = 0; $ridx <= $#allreplicas; $ridx++) { + + # Skip consumers and hubs + next if $allreplicas[$ridx] !~ /:master:(\d+):/i; + $mid = $1; + + # Skip replicas without agreements defined yet + next if (! grep {$_->{ridx} == $ridx} @allagreements); + + $maxcsn = &print_master_header ($ridx, $mid); + if ( "$maxcsn" != "none" ) { + &print_consumer_header (); + &print_consumers ($ridx, $mid); + } + &print_supplier_end; + } + + if ($mid eq "") { + print "

The server is not a master or it has no replication agreement\n"; + } +} + +sub print_master_header +{ + my ($ridx, $mid) = @_; + my ($myruv) = $allruvs {"$ridx:$mid"}; + my ($maxcsnval) = split ( /;/, "$myruv" ); + my ($maxcsn) = &to_string_csn ($maxcsnval); + my ($sidx, $replicaroot, $replicatype, $serverid) = split (/:/, $allreplicas[$ridx]); + + # Print the master name + if ( $last_sidx != $sidx ) { + my ($ldapurl) = &get_ldap_url ($sidx, $sidx); + &print_legend if ( $last_sidx < 0); + print "


\n"; + print "\n

\n"; + print "Master:  $ldapurl
\n"; + $last_sidx = $sidx; + } + + # Print the current replica info onthe master + print "\n

\n"; + + print "\n\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; +} + +sub print_consumers +{ + my ($m_ridx, $mid) = @_; + my ($ignore, $m_replicaroot) = split (/:/, $allreplicas[$m_ridx]); + my (@consumers, @ouragreements, @myagreements); + my ($s_ridx, $c_ridx, $conntype, $schedule, $status); + my ($c_maxcsn_str, $lag, $markcolor); + my ($c_replicaroot, $c_replicatype); + my ($first_entry); + my ($nrows); + my ($found); + + undef @ouragreements; + + # Collect all the consumer replicas for the current master replica + push (@consumers, $m_ridx); + foreach (@consumers) { + $s_ridx = $_; + for (@allagreements) { + next if ($_->{ridx} != $s_ridx); + $c_ridx = $_->{cidx}; + next if $c_ridx == $m_ridx; + push @ouragreements, $_; + $found = 0; + foreach (@consumers) { + if ($_ == $c_ridx) { + $found = 1; + last; + } + } + push (@consumers, $c_ridx) if !$found; + } + } + + # Print each consumer replica + my ($myruv) = $allruvs {"$m_ridx:$mid"}; + my ($m_maxcsn) = split ( /;/, "$myruv" ); + foreach (@consumers) { + $c_ridx = $_; + next if $c_ridx == $m_ridx; + + if ($c_ridx >= 0) { + $myruv = $allruvs {"$c_ridx:$mid"}; + ($c_maxcsn, $c_lastmodified) = split ( /;/, "$myruv" ); + ($c_maxcsn_str, $lag, $markcolor) = &cacl_time_lag ($m_maxcsn, $c_maxcsn); + $c_maxcsn_str =~ s/ /\/; + ($c_sidx, $c_replicaroot, $c_replicatype) = split (/:/, $allreplicas[$c_ridx]); + $c_replicaroot = "same as master" if $m_replicaroot eq $c_replicaroot; + } + else { + # $c_ridx is actually -$c_sidx when c is not available + $c_sidx = -$c_ridx; + $c_maxcsn_str = "_"; + $lag = "n/a"; + $markcolor = red; + $c_replicaroot = "_"; + $c_replicatype = "_"; + } + + $nrows = 0; + foreach (@ouragreements) { + next if ($_->{cidx} != $c_ridx); + $nrows++; + } + + $first_entry = 1; + foreach (@ouragreements) { + next if ($_->{cidx} != $c_ridx); + $s_ridx = $_->{ridx}; + $conntype = $_->{nsDS5ReplicaBindMethod}; + $status = $_->{nsds5replicaLastUpdateStatus}; + $schedule = $_->{nsds5ReplicaUpdateSchedule}; + $s_sidx = $1 if $allreplicas [$s_ridx] =~ /^(\d+):/; + $s_ldapurl = &get_ldap_url ($s_sidx, "n/a"); + + # Print out the consumer's replica and ruvs + print "\n\n"; + if ($first_entry) { + $first_entry = 0; + $c_ldapurl = &get_ldap_url ($c_sidx, $conntype); + print "\n"; + print "\n"; + print "\n"; + print "\n"; + } + print "\n"; + my $changecount = $_->{nsds5replicaChangesSentSinceStartup}; + if ( $changecount =~ /^$mid:(\d+)\/(\d+) / || $changecount =~ / $mid:(\d+)\/(\d+) / ) { + $changecount = "$1 / $2"; + } + elsif ( $changecount =~ /^(\d+)$/ ) { + $changecount = $changecount . " / " . "$_->{nsds5replicaChangesSkippedSinceStartup}"; + } + else { + $changecount = "0 / 0"; + } + print "\n"; + my $redfontstart = ""; + my $redfontend = ""; + if ($status =~ /error/i) { + $redfontstart = ""; + $redfontend = ""; + } + elsif ($status =~ /^(\d+) /) { + if ( $1 != 0 ) { + # warning + $redfontstart = ""; + $redfontend = ""; + } + } + print "\n"; + print "\n"; + print "\n"; + if ( $schedule =~ /always/i ) { + print "\n"; + } + else { + my ($ndays, @days); + $schedule =~ /(\d\d)(\d\d)-(\d\d)(\d\d) (\d+)/; + print "\n"; + $ndays = $5; + $ndays =~ s/(\d)/$1,/g; + @days = (Sun,Mon,Tue,Wed,Thu,Fri,Sat)[eval $ndays]; + print "\n"; + } + print "\n"; + } + } +} + +sub cacl_time_lag +{ + my ($s_maxcsn, $c_maxcsn) = @_; + my ($markcolor); + my ($csn_str); + my ($s_tm, $c_tm, $lag_tm, $lag_str, $hours, $minute); + + $csn_str = &to_string_csn ($c_maxcsn); + + if ($s_maxcsn && !$c_maxcsn) { + $lag_str = "- ?:??:??"; + $markcolor = &get_color (36000); # assume consumer has big latency + } + elsif (!$s_maxcsn && $c_maxcsn) { + $lag_str = "+ ?:??:??"; + $markcolor = &get_color (1); # consumer is ahead of supplier + } + elsif ($s_maxcsn le $c_maxcsn) { + $lag_str = "0:00:00"; + $markcolor = &get_color (0); + } + else { + my ($rawcsn, $decimalcsn) = split (/:/, $s_maxcsn); + ($s_tm) = split(/ /, $decimalcsn); + + ($rawcsn, $decimalcsn) = split (/:/, $c_maxcsn); + ($c_tm) = split(/ /, $decimalcsn); + if ($s_tm > $c_tm) { + $lag_tm = $s_tm - $c_tm; + $lag_str = "- "; + $markcolor = &get_color ($lag_tm); + } + else { + $lag_tm = $c_tm - $s_tm; + $lag_str = "+ "; + $markcolor = $allcolors{ $colorkeys[0] }; # no delay + } + $hours = int ($lag_tm / 3600); + $lag_str .= "$hours:"; + + $lag_tm = $lag_tm % 3600; + $minutes = int ($lag_tm / 60); + $minutes = "0".$minutes if ($minutes < 10); + $lag_str .= "$minutes:"; + + $lag_tm = $lag_tm % 60; + $lag_tm = "0".$lag_tm if ($lag_tm < 10); + $lag_str .= "$lag_tm"; + } + return ($csn_str, $lag_str, $markcolor); +} + +# +# The subroutine would append a new entry to the end of +# @servers if the host and port are new to @servers. +# +sub add_server +{ + my ($host, $port, $binddn, $bindpwd, $bindcert) = split (/:/, "@_"); + my ($shadowport) = $port; + my ($domainpattern) = '\.[^:]+'; + my ($i); + + # Remove the domain name from the host name + my ($hostnode) = $host; + $hostnode = $1 if $host =~ /^(\w+)\./; + + # new host:port + if ($binddn eq "" || $bindpwd eq "" && $bindcert eq "") { + # + # Look up connection parameter in the order of + # host:port + # host:* + # *:port + # *:* + # + my (@myconfig, $h, $p, $d, $w, $c); + (@myconfig = grep (/^$hostnode($domainpattern)*:$port\D/i, @allconnections)) || + (@myconfig = grep (/^$hostnode($domainpattern)*:\*:/i, @allconnections)) || + (@myconfig = grep (/^\*:$port\D/, @allconnections)) || + (@myconfig = grep (/^\*:\*\D/, @allconnections)); + if ($#myconfig >= 0) { + ($h, $p, $d, $w, $c) = split (/:/, $myconfig[0]); + ($p, $shadowport) = split (/=/, $p); + $p = "" if $p eq "*"; + $c = "" if $c eq "*"; + } + if ($binddn eq "" || $binddn eq "*") { + if ($d eq "" || $d eq "*") { + $binddn = "cn=Directory Manager"; + } + else { + $binddn = $d; + } + } + $bindpwd = $w if ($bindpwd eq "" || $bindpwd eq "*"); + $bindcert = $c if ($bindcert eq "" || $bindcert eq "*"); + } + + for ($i = 0; $i <= $#servers; $i++) { + return $i if ($servers[$i] =~ /$hostnode($domainpattern)*:\d*=$shadowport\D/i); + } + + if ($shadowport) { + push (@servers, "$host:$port=$shadowport:$binddn:$bindpwd:$bindcert"); + } else { + push (@servers, "$host:$port:$binddn:$bindpwd:$bindcert"); + } + return $i; +} + +sub get_ldap_url +{ + my ($sidx, $conntype) = @_; + my ($host, $port) = split(/:/, $servers[$sidx]); + my ($shadowport); + ($port, $shadowport) = split (/=/, $port); + my ($protocol, $ldapurl); + + if ($port eq 636 && $conntype eq "0" || $conntype =~ /SSL/i) { + $protocol = ldaps; + } + else { + $protocol = ldap; + } + my ($instance) = $allaliases { "$host:$port" }; + $instance = "$host:$port" if !$instance; + if ($conntype eq "n/a") { + $ldapurl = $instance; + } + else { + $ldapurl = "$instance"; + } + return $ldapurl; +} + +sub to_decimal_csn +{ + my ($maxcsn) = @_; + if (!$maxcsn || $maxcsn eq "") { + return "none"; + } + + my ($tm, $seq, $masterid, $subseq) = unpack("a8 a4 a4 a4", $maxcsn); + + $tm = hex($tm); + $seq = hex($seq); + $masterid = hex($masterid); + $subseq = hex($subseq); + + return "$tm $seq $masterid $subseq"; +} + +sub to_string_csn +{ + my ($rawcsn, $decimalcsn) = split(/:/, "@_"); + if (!$rawcsn || $rawcsn eq "") { + return "none"; + } + my ($tm, $seq, $masterid, $subseq) = split(/ /, $decimalcsn); + my ($sec, $min, $hour, $mday, $mon, $year) = localtime($tm); + $mon++; + $year += 1900; + foreach ($sec, $min, $hour, $mday, $mon) { + $_ = "0".$_ if ($_ < 10); + } + my ($csnstr) = "$mon/$mday/$year $hour:$min:$sec"; + $csnstr .= " $seq $subseq" if ( $seq != 0 || $subseq != 0 ); + return "$rawcsn ($csnstr)"; +} + +sub get_color +{ + my ($lag_minute) = @_; + $lag_minute /= 60; + my ($color) = $allcolors { $colorkeys[0] }; + foreach (@colorkeys) { + last if ($lag_minute < $_); + $color = $allcolors {$_}; + } + return $color; +} + +# subroutine to remove escaped encoding + +sub unescape +{ + #my ($_) = @_; + tr/+/ /; + s/%(..)/pack("c",hex($1))/ge; + $_; +} + +sub print_html_header +{ + # print the HTML header + + print "Content-type: text/html\n\n"; + print "\n"; + print "Replication Status\n"; + # print "\n"; + print "\n\n"; + + if ($opt_u) { + print "\n"; + } + + print "
\n"; + print "Replica ID: "; + print "$serverid\n"; + + print "Replica Root: "; + print "$replicaroot\n"; + + print "Max CSN: "; + print "$maxcsn\n"; + + return $maxcsn; +} + +sub print_consumer_header +{ + #Print the header of consumer + print "\n
ReceiverTime LagMax CSNLast Modify TimeSupplierSent/SkippedUpdate StatusUpdate StartedUpdate EndedScheduleSSL?
$c_ldapurl
Type: $c_replicatype
$lag
$c_maxcsn_str$c_lastmodified
$s_ldapurl
$changecount$redfontstart$status$redfontend", &format_z_time($_->{nsds5replicaLastUpdateStart}), "", &format_z_time($_->{nsds5replicaLastUpdateEnd}), "$schedule$1:$2-$3:$4@days$conntype
\n"; + print "\n"; + print "
$now"; + print "Directory Server Replication Status\n"; + + if ($opt_u) { + print "
(This page updates every $interval seconds)\n"; + } + + print "
$version"; + print "
\n"; +} + +sub print_legend +{ + my ($nlegends) = $#colorkeys + 1; + print "\n

Time Lag Legend:

\n"; + print "\n\n"; + my ($i, $j); + for ($i = 0; $i < $nlegends - 1; $i++) { + $j = $colorkeys[$i]; + print "\n\n"; + } + $j = $colorkeys[$i]; + print "\n\n"; + print "\n\n"; + print "
within $colorkeys[$i+1] min
over $colorkeys[$i] min
server n/a

\n"; +} + +sub print_supplier_end +{ + print "\n"; +} + +# given a string in generalized time format, convert to ascii time +sub format_z_time +{ + my $zstr = shift; + return "n/a" if (! $zstr); + my ($year, $mon, $day, $hour, $min, $sec) = + ($zstr =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/); + my $time = timegm($sec, $min, $hour, $day, ($mon-1), $year); + ($sec, $min, $hour, $day, $mon, $year) = localtime($time); + $mon++; + $year += 1900; + foreach ($sec, $min, $hour, $day, $mon) { + $_ = "0".$_ if ($_ < 10); + } + + return "$mon/$day/$year $hour:$min:$sec"; +} -- cgit