[Date Prev][Date Next]
[Chronological]
[Thread]
[Top]
Re: (delta-)syncrepl and nagios
On Mon, 2006-02-06 at 14:41 -0500, Aaron Richton wrote:
> That's been on my todo list for over a year now. (So I'll join in the
> request for a copy if there is such a script!)
>
> If anybody does write this, it's important to note that something that
> strictly compares contextcsns is likely useless (I think it would just be
> a false positive disaster). Replication doesn't happen instantly; there
> should be some sort of configurable threshold for "csns should be within
> <time>".
>
>
> I've been meaning to ask the list: how many of you check up on your slaves
> from a consistency perspective? What do you do? (contextcsn is the
> approach I've wanted to take. Every time I get annoyed enough to write a
> nagios plugin, I notice that everything is in sync and defer it...)
>
I wrote a very generic python script with exhaustive comments/debugging.
It can be modified to be used as a Nagios script plugin.
To view a description of the script:
$ pydoc ldapSynchCheck
To view the help:
$ ./ldapSynchCheck.py -h
Hope this helps.
Any comments are welcome.
Sam
#!/usr/local/bin/python
import os
import sys
import getpass
import ldap
import time
import datetime
import re
import logging
from optparse import OptionParser
__doc__ = """
This script checks if a consumer is in synch with its provider based on
the contextCSN. The check can be performed for several consumers.
If a threshold is specified, the consumer and its provider is considered
in synch if the difference in contextCSN value is less than the threshold
value.
"""
def getUserPassword():
"""
Get user password
This function returns the password (string)
"""
# get password input
password = getpass.getpass('Password: ')
return password
def create_logger(application, verbose=None, logfile=None):
"""
Create logger instance to log to console and/or to file
application - program's name (string)
verbose - verbose logging (boolean)
logfile - log file name (string)
This function returns a logger instance
"""
if verbose:
lowestseverity = logging.DEBUG
else:
lowestseverity = logging.INFO
# Create logger
logger = logging.getLogger(application)
logger.setLevel(lowestseverity)
# Create console handler and set level to lowestseverity
ch = logging.StreamHandler()
ch.setLevel(lowestseverity)
# Create formatter
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# Add formatter to console handler
ch.setFormatter(formatter)
# Add console handler to logger
logger.addHandler(ch)
if logfile:
# Create file handler and set level to debug
fh = logging.FileHandler(logfile)
fh.setLevel(lowestseverity)
# Add formatter to console handler and file handler
fh.setFormatter(formatter)
# Add file handler to logger
logger.addHandler(fh)
return logger
def ldap_connect(ldapuri, logger=None, binddn=""):
"""
Perform LDAP connection and synchronous simple bind operation
ldapuri - URI referring to the LDAP server (string)
binddn - Distinguished Name used to bind (string)
logger - Logger object instance
This function returns an LDAPobject instance if successful,
None if failure
"""
# Set debugging level
ldap.set_option(ldap.OPT_DEBUG_LEVEL, 0)
ldap_trace_level = 0 # If non-zero a trace output of LDAP calls is generated.
ldap_trace_file = sys.stderr
# Create LDAPObject instance
if logger: logger.debug("Connecting to %s" % ldapuri)
conn = ldap.initialize(ldapuri,
trace_level=ldap_trace_level,
trace_file=ldap_trace_file)
# Set LDAP protocol version used
if logger: logger.debug("LDAP protocol version 3")
conn.protocol_version=ldap.VERSION3
# Perform synchronous simple bind operation
if binddn:
password = getUserPassword()
if logger: logger.debug("Binding with %s" % binddn)
else:
if logger: logger.debug("Binding anonymously")
password = "";
try:
conn.bind_s(binddn, password, ldap.AUTH_SIMPLE)
return conn
except ldap.LDAPError, error_message:
if logger: logger.error("LDAP bind failed. %s" % error_message)
return None
def ldap_search(ldapobj, basedn, scope, filter, attrlist):
"""
Perform LDAP synchronous search operation
ldapobj - LDAP object instance
basedn - LDAP base dn (string)
scope - LDAP search scope (integer)
filter - LDAP filter (string)
attrlist - LDAP attributes (list)
This function returns a result set (list),
None if no attribute was found
"""
result_set = (ldapobj.search_s(basedn, scope, filter, attrlist))
return result_set
def get_contextCSN(ldapobj, basedn, logger=None):
"""
Retrieve contextCSN attribute value for the suffix dn
ldapobj - LDAP object instance
basedn - LDAP base dn (string)
This function returns the contextCSN value (string),
None if no value was found
"""
result_list = ldap_search(ldapobj, basedn, ldap.SCOPE_BASE, '(objectclass=*)', ['contextCSN'])
if result_list[0][1].has_key('contextCSN'):
if logger: logger.debug("contextCSN = %s" % result_list[0][1]['contextCSN'][0] )
return result_list[0][1]['contextCSN'][0]
else:
if logger: logger.error("No contextCSN was found")
return None
def contextCSN_to_datetime(contextCSN):
"""
Convert contextCSN string (YYYYmmddHHMMMSSZ#...) to datetime object
contextCSN - Timestamp in YYYYmmddHHMMSSZ#... format (string)
This function returns a datetime object instance
"""
gentime = re.sub('Z.*$','',contextCSN)
return datetime.datetime.fromtimestamp(time.mktime(time.strptime(gentime,"%Y%m%d%H%M%S")))
def threshold_to_datetime(threshold):
"""
Convert threshold in seconds to datetime object
threshold - seconds (integer)
This function returns a datetime object instance
"""
nbdays, nbseconds = divmod(threshold, 86400)
return datetime.timedelta(days=nbdays, seconds=nbseconds)
def is_insynch(provldapobj, consldapobj, basedn, threshold=None, logger=None):
"""
Check if the consumer is in synch with the provider within the threshold
provldapobj - Provider LDAP object instance
consldapobj - Consumer LDAP object instance
basedn - LDAP base dn (string)
threshold - limit above which provider and consumer are not considered
in synch (int)
This function returns False if the provider and the consumer is not
in synch, True if in synch within the threshold
"""
if logger: logger.debug("Retrieving Provider contextCSN")
provcontextCSN = get_contextCSN(provldapobj, basedn, logger)
if logger: logger.debug("Retrieving Consumer contextCSN")
conscontextCSN = get_contextCSN(consldapobj, basedn, logger)
if (provcontextCSN and conscontextCSN):
if (provcontextCSN == conscontextCSN):
if logger: logger.info(" Provider and consumer exactly in SYNCH")
return True
else:
delta = contextCSN_to_datetime(provcontextCSN) - contextCSN_to_datetime(conscontextCSN)
if threshold:
maxdelta = threshold_to_datetime(eval(threshold))
if logger: logger.debug("Threshold is %s" % maxdelta)
if (abs(delta) <= maxdelta):
if logger:
logger.info(" Consumer in SYNCH within threshold")
logger.info(" Delta is %s" % delta)
return True
else:
if logger: logger.info(" Consumer NOT in SYNCH within threshold")
else:
if logger: logger.info(" Consumer NOT in SYNCH")
if logger: logger.info(" Delta is %s" % delta)
else:
if logger: logger.error(" Check failed: at least one contextCSN value is missing")
return False
def main():
usage = "\n " + sys.argv[0] + """ [options] providerLDAPURI consumerLDAPURI ...
This script takes at least two arguments:
- providerLDAPURI is the provider LDAP URI (as defined in RFC2255)
- consumerLDAPURI is the consumer LDAP URI (as defined in RFC2255)
Additional consumer LDAP URIs can be specified.
"""
parser = OptionParser(usage=usage)
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
default=False,
help="""Enable more verbose output""")
parser.add_option("-q", "--quiet", dest="quiet", action="store_true",
default=False,
help="""Disable console and file logging""")
parser.add_option("-l", "--logfile", dest="logfile", default=re.sub("\.[^\.]*$","",sys.argv[0]) + '.log',
help="""Log the actions of this script to this file
[ default : %default ]""")
parser.add_option("-D", "--binddn",
dest="binddn", default="",
help="""Use the Distinguished Name to bind [default:
anonymous]. You will be prompted to enter the
associated password.""")
parser.add_option("-b", "--basedn",
dest="basedn", default="dc=amnh,dc=org",
help="LDAP base dn [default: %default].")
parser.add_option("-t", "--threshold", dest="threshold",
default=None,
help="""Threshold value in seconds""")
(options, args) = parser.parse_args()
if not options.quiet:
# Create the logger object to log to console and/or file
logger = create_logger(os.path.basename(sys.argv[0]), options.verbose, options.logfile)
else:
logger = None
if logger: logger.info("Provider is: %s" % re.sub("^.*\/\/", "", args[0]))
ldapprov = ldap_connect(args.pop(0), logger, options.binddn)
if ldapprov:
for consumer in args:
if logger: logger.info("Checking if consumer %s is in SYNCH with provider" % re.sub("^.*\/\/", "", consumer))
ldapcons = ldap_connect(consumer, logger, options.binddn)
if ldapcons:
is_insynch(ldapprov, ldapcons, options.basedn, options.threshold, logger)
ldapcons.unbind_s()
ldapprov.unbind_s()
if __name__ == '__main__':
main()