diff --git a/tests/progs/Makefile.in b/tests/progs/Makefile.in index bbef24a62f..765762d6b4 100644 --- a/tests/progs/Makefile.in +++ b/tests/progs/Makefile.in @@ -14,12 +14,12 @@ ## . PROGRAMS = slapd-tester slapd-search slapd-read slapd-addel slapd-modrdn \ - slapd-modify slapd-bind slapd-mtread ldif-filter + slapd-modify slapd-bind slapd-mtread ldif-filter slapd-watcher SRCS = slapd-common.c \ slapd-tester.c slapd-search.c slapd-read.c slapd-addel.c \ slapd-modrdn.c slapd-modify.c slapd-bind.c slapd-mtread.c \ - ldif-filter.c + ldif-filter.c slapd-watcher.c LDAP_INCDIR= ../../include LDAP_LIBDIR= ../../libraries @@ -62,3 +62,5 @@ ldif-filter: ldif-filter.o $(XLIBS) slapd-mtread: slapd-mtread.o $(OBJS) $(XLIBS) $(LTLINK) -o $@ slapd-mtread.o $(OBJS) $(LIBS) +slapd-watcher: slapd-watcher.o $(OBJS) $(XLIBS) + $(LTLINK) -o $@ slapd-watcher.o $(OBJS) $(LIBS) diff --git a/tests/progs/slapd-common.c b/tests/progs/slapd-common.c index 774c35eb41..535cd5fb1b 100644 --- a/tests/progs/slapd-common.c +++ b/tests/progs/slapd-common.c @@ -35,6 +35,7 @@ /* global vars */ pid_t pid; +int debug; /* static vars */ static char progname[ BUFSIZ ]; @@ -311,7 +312,6 @@ tester_config_opt( struct tester_conn_args *config, char opt, char *optarg ) case 'd': { - int debug; if ( lutil_atoi( &debug, optarg ) != 0 ) { return -1; } diff --git a/tests/progs/slapd-common.h b/tests/progs/slapd-common.h index e1ad5a6160..33d2b3e965 100644 --- a/tests/progs/slapd-common.h +++ b/tests/progs/slapd-common.h @@ -87,5 +87,6 @@ extern void tester_config_finish( struct tester_conn_args *config ); extern void tester_init_ld( LDAP **ldp, struct tester_conn_args *conf, int flags ); extern pid_t pid; +extern int debug; #endif /* SLAPD_COMMON_H */ diff --git a/tests/progs/slapd-watcher.c b/tests/progs/slapd-watcher.c new file mode 100644 index 0000000000..0f0ec37bf3 --- /dev/null +++ b/tests/progs/slapd-watcher.c @@ -0,0 +1,725 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1999-2020 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include + +#include "ac/stdlib.h" +#include "ac/time.h" + +#include "ac/ctype.h" +#include "ac/param.h" +#include "ac/socket.h" +#include "ac/string.h" +#include "ac/unistd.h" +#include "ac/wait.h" +#include "ac/time.h" + +#include "ldap.h" +#include "lutil.h" +#include "lutil_ldap.h" +#include "lber_pvt.h" +#include "ldap_pvt.h" + +#include "slapd-common.h" + +#define HAS_MONITOR 1 +#define HAS_BASE 2 +#define HAS_ENTRIES 4 +#define HAS_SREPL 8 + +#define MONFILTER "(objectClass=monitorOperation)" + +#define SLAP_SYNC_SID_MAX 4095 + +typedef enum { + SLAP_OP_BIND = 0, + SLAP_OP_UNBIND, + SLAP_OP_SEARCH, + SLAP_OP_COMPARE, + SLAP_OP_MODIFY, + SLAP_OP_MODRDN, + SLAP_OP_ADD, + SLAP_OP_DELETE, + SLAP_OP_ABANDON, + SLAP_OP_EXTENDED, + SLAP_OP_LAST +} slap_op_t; + +struct opname { + struct berval rdn; + char *display; +} opnames[] = { + { BER_BVC("cn=Bind"), "Bind" }, + { BER_BVC("cn=Unbind"), "Unbind" }, + { BER_BVC("cn=Search"), "Search" }, + { BER_BVC("cn=Compare"), "Compare" }, + { BER_BVC("cn=Modify"), "Modify" }, + { BER_BVC("cn=Modrdn"), "ModDN" }, + { BER_BVC("cn=Add"), "Add" }, + { BER_BVC("cn=Delete"), "Delete" }, + { BER_BVC("cn=Abandon"), "Abandon" }, + { BER_BVC("cn=Extended"), "Extended" }, + { BER_BVNULL, NULL } +}; + +typedef struct counters { + struct timeval time; + unsigned long entries; + unsigned long ops[SLAP_OP_LAST]; +} counters; + +typedef struct csns { + int num; + int *sids; + struct berval *vals; + struct timeval *tvs; +} csns; + +typedef struct activity { + time_t active; + time_t idle; + time_t maxlag; + time_t lag; +} activity; + +typedef struct server { + char *url; + LDAP *ld; + int flags; + int sid; + struct berval monitorbase; + char *monitorfilter; + counters c_prev; + counters c_curr; + csns csn_prev; + csns csn_curr; + activity *times; +} server; + +static void +usage( char *name, char opt ) +{ + if ( opt ) { + fprintf( stderr, "%s: unable to handle option \'%c\'\n\n", + name, opt ); + } + + fprintf( stderr, "usage: %s " + "[-D [ -w ]] " + "[-d ] " + "[-O ] " + "[-R ] " + "[-U [-X ]] " + "[-x | -Y ] " + "[-i ] " + "[-s ] " + "[-b ] URI[...]\n", + name ); + exit( EXIT_FAILURE ); +} + +struct berval base; +int interval = 10; +int numservers; +server *servers; +char *monfilter; + +struct berval at_namingContexts = BER_BVC("namingContexts"); +struct berval at_monitorOpCompleted = BER_BVC("monitorOpCompleted"); +struct berval at_olmMDBEntries = BER_BVC("olmMDBEntries"); +struct berval at_contextCSN = BER_BVC("contextCSN"); + +void timestamp(time_t *tt) +{ + struct tm *tm = gmtime(tt); + printf("%d-%02d-%02d %02d:%02d:%02d", + tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + +void deltat(time_t *tt) +{ + struct tm *tm = gmtime(tt); + if (tm->tm_mday-1) + printf("%02d+", tm->tm_mday-1); + printf("%02d:%02d:%02d", + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + +static char *clearscreen = "\033[H\033[2J"; + +void display() +{ + int i, j; + struct timeval now; + struct tm *tm; + time_t now_t; + + gettimeofday(&now, NULL); + now_t = now.tv_sec; + printf("%s", clearscreen); + timestamp(&now_t); + printf("\n"); + + for (i=0; i servers[i].times[j].maxlag) + servers[i].times[j].maxlag = deltatt; + } else { + servers[i].times[j].lag = 0; + printf(", sync'd"); + } + if (servers[i].times[j].maxlag) { + printf(", max delta "); + deltat( &servers[i].times[j].maxlag ); + } + } + } + } + } + } + printf("\n"); + } + if ( servers[i].csn_prev.num != servers[i].csn_curr.num ) { + servers[i].csn_prev.sids = realloc(servers[i].csn_prev.sids, + servers[i].csn_curr.num * sizeof(int)); + servers[i].csn_prev.vals = realloc(servers[i].csn_prev.vals, + servers[i].csn_curr.num * sizeof(struct berval)); + servers[i].csn_prev.tvs = realloc(servers[i].csn_prev.tvs, + servers[i].csn_curr.num * sizeof(struct timeval)); + for (j=servers[i].csn_prev.num; j < servers[i].csn_curr.num; j++) { + BER_BVZERO( &servers[i].csn_prev.vals[j] ); + } + servers[i].csn_prev.num = servers[i].csn_curr.num; + for (j=0; jops[op] = strtoul( bvals[0].bv_val, NULL, 0 ); + done = 1; + } + if ( bvals ) { + ber_memfree( bvals ); + bvals = NULL; + } + if ( done ) + break; + } + ber_free( ber, 0 ); + e = ldap_next_entry( ld, e ); + if ( !e ) + break; + ldap_get_dn_ber( ld, e, &ber, &dn ); + op++; + } while ( op < SLAP_OP_LAST ); +} + +int +slap_parse_csn_sid( struct berval *csnp ) +{ + char *p, *q; + struct berval csn = *csnp; + int i; + + p = ber_bvchr( &csn, '#' ); + if ( !p ) + return -1; + p++; + csn.bv_len -= p - csn.bv_val; + csn.bv_val = p; + + p = ber_bvchr( &csn, '#' ); + if ( !p ) + return -1; + p++; + csn.bv_len -= p - csn.bv_val; + csn.bv_val = p; + + q = ber_bvchr( &csn, '#' ); + if ( !q ) + return -1; + + csn.bv_len = q - p; + + i = strtol( p, &q, 16 ); + if ( p == q || q != p + csn.bv_len || i < 0 || i > SLAP_SYNC_SID_MAX ) { + i = -1; + } + + return i; +} + +void get_csns( + csns *c, + struct berval *bvs +) +{ + int i; + + for (i=0; bvs[i].bv_val; i++) ; + if ( c->num != i ) { + int j; + c->vals = realloc( c->vals, i*sizeof(struct berval)); + c->sids = realloc( c->sids, i*sizeof(int)); + c->tvs = realloc( c->tvs, i*sizeof(struct timeval)); + for (j=c->num; jvals[j] ); + } + } + c->num = i; + for (i=0; inum; i++) { + struct lutil_tm tm; + struct lutil_timet tt; + ber_bvreplace( &c->vals[i], &bvs[i] ); + c->sids[i] = slap_parse_csn_sid( &bvs[i] ); + lutil_parsetime(c->vals[i].bv_val, &tm); + c->tvs[i].tv_usec = tm.tm_usec; + lutil_tm2time( &tm, &tt ); + c->tvs[i].tv_sec = tt.tt_sec; + } +} + +int +main( int argc, char **argv ) +{ + int i, rc, *msg1, *msg2; + char **sids = NULL; + struct tester_conn_args *config; + + config = tester_init( "slapd-watcher", TESTER_TESTER ); + config->authmethod = LDAP_AUTH_SIMPLE; + + while ( ( i = getopt( argc, argv, "D:O:R:U:X:Y:b:d:i:s:w:x" ) ) != EOF ) + { + switch ( i ) { + case 'b': /* base DN for contextCSN lookups */ + ber_str2bv( optarg, 0, 0, &base ); + break; + + case 'i': + interval = atoi(optarg); + break; + + case 's': + sids = ldap_str2charray( optarg, "," ); + break; + + default: + if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) + break; + + usage( argv[0], i ); + break; + } + } + + tester_config_finish( config ); + + /* don't clear the screen if debug is enabled */ + if (debug) + clearscreen = "\n\n"; + + numservers = argc - optind; + if ( !numservers ) + usage( argv[0], 0 ); + + if ( sids ) { + for (i=0; sids[i]; i++ ); + if ( i != numservers ) { + fprintf(stderr, "Number of sids doesn't equal number of server URLs\n"); + exit( EXIT_FAILURE ); + } + } + + argv += optind; + argc -= optind; + servers = calloc( numservers, sizeof(server)); + + if ( base.bv_val ) { + monfilter = "(|(entryDN:dnOneLevelMatch:=cn=Databases,cn=Monitor)" MONFILTER ")"; + } else { + monfilter = MONFILTER; + } + + if ( numservers > 1 ) { + for ( i=0; iuri = argv[i]; + tester_init_ld( &servers[i].ld, config, 0 ); + servers[i].flags = 0; + { + char *attrs[] = { at_namingContexts.bv_val, at_monitorOpCompleted.bv_val, + at_olmMDBEntries.bv_val, NULL }; + LDAPMessage *res = NULL, *e = NULL; + BerElement *ber = NULL; + LDAP *ld = servers[i].ld; + struct berval dn, bv, *bvals, **bvp = &bvals; + int j; + + rc = ldap_search_ext_s( ld, "cn=monitor", LDAP_SCOPE_SUBTREE, monfilter, + attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res ); + switch(rc) { + case LDAP_SIZELIMIT_EXCEEDED: + case LDAP_TIMELIMIT_EXCEEDED: + case LDAP_SUCCESS: + gettimeofday( &servers[i].c_curr.time, 0 ); + servers[i].flags |= HAS_MONITOR; + for ( e = ldap_first_entry( ld, res ); e; e = ldap_next_entry( ld, e )) { + ldap_get_dn_ber( ld, e, &ber, &dn ); + if ( !strncasecmp( dn.bv_val, "cn=Database", sizeof("cn=Database")-1 ) || + !strncasecmp( dn.bv_val, "cn=Frontend", sizeof("cn=Frontend")-1 )) { + int matched = 0; + for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp ); + rc == LDAP_SUCCESS; + rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) { + if ( bv.bv_val == NULL ) break; + if (!ber_bvcmp( &bv, &at_namingContexts ) && bvals ) { + for (j=0; bvals[j].bv_val; j++) { + if ( !ber_bvstrcasecmp( &base, &bvals[j] )) { + matched = 1; + break; + } + } + if (!matched) { + ber_memfree( bvals ); + bvals = NULL; + break; + } + } + if (!ber_bvcmp( &bv, &at_olmMDBEntries )) { + ber_dupbv( &servers[i].monitorbase, &dn ); + servers[i].flags |= HAS_ENTRIES; + servers[i].c_curr.entries = strtoul( bvals[0].bv_val, NULL, 0 ); + } + ber_memfree( bvals ); + bvals = NULL; + } + } else if (!strncasecmp( dn.bv_val, opnames[0].rdn.bv_val, + opnames[0].rdn.bv_len )) { + get_counters( ld, e, ber, &servers[i].c_curr ); + break; + } + if ( ber ) + ber_free( ber, 0 ); + } + break; + + case LDAP_NO_SUCH_OBJECT: + /* no cn=monitor */ + break; + + default: + tester_ldap_error( ld, "ldap_search_ext_s(cn=Monitor)", NULL ); + exit( EXIT_FAILURE ); + } + ldap_msgfree( res ); + if ( base.bv_val ) { + char *attr2[] = { at_contextCSN.bv_val, NULL }; + rc = ldap_search_ext_s( ld, base.bv_val, LDAP_SCOPE_BASE, "(objectClass=*)", + attr2, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res ); + switch(rc) { + case LDAP_SUCCESS: + e = ldap_first_entry( ld, res ); + if ( e ) { + servers[i].flags |= HAS_BASE; + ldap_get_dn_ber( ld, e, &ber, &dn ); + for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp ); + rc == LDAP_SUCCESS; + rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) { + int done = 0; + if ( bv.bv_val == NULL ) break; + if ( bvals ) { + if ( !ber_bvcmp( &bv, &at_contextCSN )) { + get_csns( &servers[i].csn_curr, bvals ); + done = 1; + } + ber_memfree( bvals ); + bvals = NULL; + if ( done ) + break; + } + } + } + ldap_msgfree( res ); + break; + + default: + tester_ldap_error( ld, "ldap_search_ext_s(baseDN)", NULL ); + exit( EXIT_FAILURE ); + } + } + } + } + + for (i=0; i