ITS#9186 Implement result code counters

This commit is contained in:
Nadezhda Ivanova 2025-01-29 16:26:10 +02:00
parent 7a8d72b02b
commit 086296d5fa
15 changed files with 1305 additions and 0 deletions

View file

@ -11,3 +11,4 @@ OLcfgCt{Oc|At}:8 datamorph
OLcfgCt{Oc|At}:9 variant
OLcfgCt{Oc|At}:10 alias
OLcfgCt{Oc|At}:11 dsaschema
OLcfgCt{Oc|At}:12 resultstats

View file

@ -0,0 +1,2 @@
servers
clients

View file

@ -0,0 +1,80 @@
# $OpenLDAP$
# This work is part of OpenLDAP Software <http://www.openldap.org/>.
#
# Copyright 1998-2024 The OpenLDAP Foundation.
# Copyright 2024 Symas Corp. 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 the file LICENSE in the
# top-level directory of the distribution or, alternatively, at
# <http://www.OpenLDAP.org/license.html>.
LDAP_SRC = ../../..
LDAP_BUILD = $(LDAP_SRC)
LDAP_INC = -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)/servers/slapd
LDAP_LIB = $(LDAP_BUILD)/libraries/libldap/libldap.la \
$(LDAP_BUILD)/libraries/liblber/liblber.la
SRCDIR = ./
PLAT = UNIX
NT_LIB = -L$(LDAP_BUILD)/servers/slapd -lslapd
NT_LDFLAGS = -no-undefined -avoid-version
UNIX_LDFLAGS = -version-info $(LTVER)
LIBTOOL = $(LDAP_BUILD)/libtool
INSTALL = /usr/bin/install
CC = gcc
OPT = -g -O0
DEFS = -DSLAPD_OVER_RESULTSTATS=SLAPD_MOD_DYNAMIC
INCS = $(LDAP_INC)
LIBS = $($(PLAT)_LIB) $(LDAP_LIB)
LD_FLAGS = $(LDFLAGS) $($(PLAT)_LDFLAGS) -rpath $(moduledir) -module
PROGRAMS = resultstats.la
MANPAGES = slapo_resultstats.5
LTVER = 0:0:0
CLEAN = *.o *.lo *.la .libs
prefix=/usr/local
exec_prefix=$(prefix)
ldap_subdir=/openldap
libdir=$(exec_prefix)/lib
libexecdir=$(exec_prefix)/libexec
moduledir = $(libexecdir)$(ldap_subdir)
mandir = $(exec_prefix)/share/man
man5dir = $(mandir)/man5
.SUFFIXES: .c .o .lo
.c.lo:
$(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(OPT) $(CPPFLAGS) $(DEFS) $(INCS) -c $<
all: $(PROGRAMS)
d :=
sp :=
dir := tests
include $(dir)/Rules.mk
resultstats.la: resultstats.lo
$(LIBTOOL) --mode=link $(CC) $(LD_FLAGS) -o $@ $? $(LIBS)
clean:
rm -rf $(CLEAN)
install: install-lib install-man
install-lib: $(PROGRAMS)
mkdir -p $(DESTDIR)$(moduledir)
for p in $(PROGRAMS) ; do \
$(LIBTOOL) --mode=install cp $$p $(DESTDIR)$(moduledir) ; \
done
install-man: $(MANPAGES)
mkdir -p $(DESTDIR)$(man5dir)
$(INSTALL) -m 644 $(MANPAGES) $(DESTDIR)$(man5dir)

View file

@ -0,0 +1,515 @@
/* resultstats.c - gather result code statistics per operation */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2005-2025 The OpenLDAP Foundation.
* Copyright 2025 Symas Corp. All Rights Reserved.
* 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 the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* ACKNOWLEDGEMENTS:
* This work was developed by Nadezhda Ivanova for Symas Corp. for inclusion
* in OpenLDAP Software.
*/
#include "portable.h"
#ifdef SLAPD_OVER_RESULTSTATS
#include <stdio.h>
#include <ac/unistd.h>
#include <ac/string.h>
#include <ac/ctype.h>
#include <ac/socket.h>
#include "slap.h"
#include "slap-config.h"
#include "lutil.h"
#include "back-monitor/back-monitor.h"
static slap_overinst resultstats;
#define STATS_SIZE LDAP_OTHER+2
static struct resultstats_ops_t {
struct berval rdn;
struct berval nrdn;
} resultstats_op[] = {
{ BER_BVC( "cn=Bind" ), BER_BVC( "cn=bind" ), },
{ BER_BVC( "cn=Unbind" ), BER_BVC( "cn=unbind" ), },
{ BER_BVC( "cn=Search" ), BER_BVC( "cn=search" ), },
{ BER_BVC( "cn=Compare" ), BER_BVC( "cn=compare" ), },
{ BER_BVC( "cn=Modify" ), BER_BVC( "cn=modify" ), },
{ BER_BVC( "cn=Modrdn" ), BER_BVC( "cn=modrdn" ), },
{ BER_BVC( "cn=Add" ), BER_BVC( "cn=add" ), },
{ BER_BVC( "cn=Delete" ), BER_BVC( "cn=delete" ), },
{ BER_BVC( "cn=Abandon" ), BER_BVC( "cn=abandon" ), },
{ BER_BVC( "cn=Extended" ), BER_BVC( "cn=extended" ), },
{ BER_BVNULL, BER_BVNULL }
};
typedef struct resultstats_t {
uintptr_t stats[ SLAP_OP_LAST ][ STATS_SIZE ];
struct berval monitor_ndn;
struct berval rslt_rdn;
struct berval mss_ndn;
monitor_subsys_t *mss;
} resultstats_t;
static int resultstats_monitor_db_init( void );
static int resultstats_monitor_db_open( BackendDB *be );
static int resultstats_monitor_db_close( BackendDB *be );
//static int resultstats_monitor_db_destroy( BackendDB *be );
static AttributeDescription *ad_olmResultCodeStat;
static ObjectClass *oc_olmResultStatOperation;
static ObjectClass *oc_monitorContainer;
static struct {
char *desc;
ObjectClass **ocp;
} s_oc[] = {
{ "( OLcfgCtOc:12.1 "
"NAME ( 'olmResultStatOperation' ) "
"SUP monitoredObject "
"MAY ( "
"olmResultCodeStat"
" ) )",
&oc_olmResultStatOperation },
{ NULL }
};
static struct {
char *desc;
AttributeDescription **adp;
} s_ad[] = {
{ "( OLcfgCtAt:12.1 "
"NAME 'olmResultCodeStat' "
"DESC 'Number of times an LDAP code result has been sent for this operation type' "
"EQUALITY integerMatch "
"ORDERING integerOrderingMatch "
"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
&ad_olmResultCodeStat },
{ NULL }
};
static AttributeDescription *ads[ STATS_SIZE ];
static int
resultstats_monitor_ops_update(
Operation *op,
SlapReply *rs,
Entry *e,
void *priv )
{
uintptr_t *stats = (uintptr_t *)priv;
int i, rc;
for ( i = 0; i < STATS_SIZE; i++ ) {
Attribute *a;
AttributeDescription *ad = NULL;
char name_buf[ BUFSIZ ];
char val_buf[ BUFSIZ ];
const char *text;
struct berval bv;
unsigned long int value = __atomic_load_n( &stats[i], __ATOMIC_RELAXED );
if ( value == 0 ) {
continue;
}
/* TODO This should be optimised by maintaining the attributes in the entry sorted by code,
and avoid searching through the full list every time */
ad = ads[i];
if ( ad == NULL ) {
if ( i <= LDAP_OTHER )
snprintf( name_buf, sizeof( name_buf ), "olmResultCodeStat;x-resultcode-%d", i );
else
snprintf( name_buf, sizeof( name_buf ), "olmResultCodeStat;x-resultcode-more");
rc = slap_str2ad( name_buf, &ad, &text );
if ( rc != 0 ) {
Debug( LDAP_DEBUG_ANY, "resultstats_monitor_ops_update: "
"unable to find attribute description %s \n ", name_buf );
return 0;
}
}
ads[i] = ad;
bv.bv_val = val_buf;
bv.bv_len = snprintf( val_buf, sizeof( val_buf ), "%lu", value );
a = attr_find( e->e_attrs, ad );
if ( a != NULL ) {
ber_bvreplace( &a->a_vals[ 0 ], &bv );
} else {
attr_merge_one( e, ad, &bv, NULL );
}
}
return SLAP_CB_CONTINUE;
}
static int
resultstats_monitor_initialize( void )
{
static int resultstats_monitor_initialized = 0;
int code, i;
if ( backend_info( "monitor" ) == NULL ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_initialize: resultstats overlay requires cn=monitor");
return -1;
}
if ( resultstats_monitor_initialized++ ) {
return 0;
}
ad_define_option( "x-resultcode-", __FILE__, __LINE__ );
for ( i = 0; s_ad[i].desc != NULL; i++ ) {
code = register_at( s_ad[i].desc, s_ad[i].adp, 0 );
if ( code ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_initialize: register_at #%d failed\n", i );
return code;
}
}
for ( i = 0; s_oc[i].desc != NULL; i++ ) {
code = register_oc( s_oc[i].desc, s_oc[i].ocp, 0 );
if ( code ) {
Debug( LDAP_DEBUG_ANY,
"resultstat_monitor_initialize: register_oc #%d failed\n", i );
return code;
}
}
oc_monitorContainer = oc_find( "monitorContainer" );
if ( !oc_monitorContainer ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_initialize: failed to find objectClass (monitorContainer)\n" );
return 5;
}
return 0;
}
static int
resultstats_monitor_register_entries( monitor_extra_t *mbe,
resultstats_t *rslt,
monitor_subsys_t *ms,
Entry *parent )
{
int i, rc;
Entry *e;
for ( i = 0; i < SLAP_OP_LAST; i++ ) {
monitor_callback_t *cb;
e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn,
&resultstats_op[i].rdn,
oc_olmResultStatOperation, NULL, NULL );
if ( e == NULL ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_register_entries: "
"unable to create entry \"%s,%s\"\n",
resultstats_op[i].rdn.bv_val,
rslt->monitor_ndn.bv_val );
return( -1 );
}
cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
cb->mc_update = resultstats_monitor_ops_update;
cb->mc_private = (void *)rslt->stats[i];
rc = mbe->register_entry( e, cb, ms, 0 );
if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_register_entries: "
"unable to register entry \"%s\" for monitoring\n",
e->e_name.bv_val );
ch_free( cb );
entry_free( e );
return rc;
}
entry_free( e );
}
return 0;
}
static int
resultstats_monitor_db_init( void )
{
return resultstats_monitor_initialize();
}
static int
resultstats_monitor_mss_init(
BackendDB *be,
monitor_subsys_t *ms )
{
slap_overinst *on = (slap_overinst *)ms->mss_private;
resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
monitor_extra_t *mbe;
Entry *parent;
int rc;
assert( be != NULL );
mbe = (monitor_extra_t *) be->bd_info->bi_extra;
parent = mbe->entry_stub( &rslt->monitor_ndn, &rslt->monitor_ndn,
&rslt->rslt_rdn, oc_monitorContainer, NULL, NULL );
if ( parent == NULL ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_mss_init: "
"unable to create entry \"%s,%s\"\n",
rslt->rslt_rdn.bv_val,
rslt->monitor_ndn.bv_val );
return( -1 );
}
ber_dupbv( &ms->mss_dn, &parent->e_name );
ber_dupbv( &ms->mss_ndn, &parent->e_nname );
ber_dupbv( &ms->mss_rdn, &rslt->rslt_rdn );
rc = mbe->register_entry( parent, NULL, ms, MONITOR_F_PERSISTENT_CH );
if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_mss_init: "
"unable to register entry \"%s,%s\"\n",
ms->mss_rdn.bv_val,
ms->mss_ndn.bv_val );
entry_free( parent );
return ( -1 );
}
rc = resultstats_monitor_register_entries( mbe, rslt, ms, parent );
entry_free( parent );
return rc;
}
static int
resultstats_monitor_mss_destroy (
BackendDB *be,
monitor_subsys_t *ms )
{
if ( ms->mss_ndn.bv_len > 0 ) {
ch_free( ms->mss_ndn.bv_val );
}
if ( ms->mss_dn.bv_len > 0 ) {
ch_free( ms->mss_dn.bv_val );
}
return 0;
}
static int
resultstats_monitor_db_open( BackendDB *be )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
BackendInfo *bi;
int rc = 0;
monitor_extra_t *mbe;
Entry * parent;
/* check if monitor is configured and usable */
bi = backend_info( "monitor" );
if ( !bi || !bi->bi_extra ) {
return -1;
}
mbe = bi->bi_extra;
/* don't bother if monitor is not configured */
if ( !mbe->is_configured() ) {
Debug( LDAP_DEBUG_CONFIG, "resultstats_monitor_db_open: "
"monitoring disabled; "
"configure monitor database to enable\n" );
return -1;
}
BER_BVZERO( &rslt->monitor_ndn );
if ( ( rc = mbe->register_overlay( be, on, &rslt->monitor_ndn ) ) != 0 ) {
return rc;
}
parent = mbe->entry_stub( &rslt->monitor_ndn, &rslt->monitor_ndn,
&rslt->rslt_rdn, oc_monitorContainer, NULL, NULL );
if ( parent == NULL ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_mss_init: "
"unable to create entry \"%s,%s\"\n",
rslt->rslt_rdn.bv_val,
rslt->monitor_ndn.bv_val );
return( -1 );
}
ber_dupbv( &rslt->mss_ndn, &parent->e_nname );
/* Check if the subsystem already exsists. This can happen if the overlay
has previously added and removed. For now it is safe to assume
that the dn will be unique, as databases cannot be removed.
This should be re-done when we enable database removal and fix monitor
so that subsystems can be unregistered */
rslt->mss = monitor_back_get_subsys_by_dn( &rslt->mss_ndn, 0 );
if ( rslt->mss == NULL ) {
/* this will leak at monitor_db_destroy, but it can't be helped */
rslt->mss = (monitor_subsys_t *)ch_calloc( 1, sizeof( monitor_subsys_t ) );
rslt->mss->mss_name = "Result code statistics";
rslt->mss->mss_flags = MONITOR_F_PERSISTENT_CH;
rslt->mss->mss_open = resultstats_monitor_mss_init;
rslt->mss->mss_destroy = resultstats_monitor_mss_destroy;
rslt->mss->mss_private = on;
if ( mbe->register_subsys_late( rslt->mss ) ) {
Debug( LDAP_DEBUG_ANY,
"resultsats_monitor_db_open: "
"failed to register result statistics subsystem" );
return -1;
}
} else {
rslt->mss->mss_private = on;
rc = mbe->register_entry( parent, NULL, rslt->mss, MONITOR_F_PERSISTENT_CH );
if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_ANY,
"resultstats_monitor_db_open: "
"unable to register entry \"%s,%s\"\n",
rslt->mss->mss_rdn.bv_val,
rslt->mss->mss_ndn.bv_val );
entry_free( parent );
return ( -1 );
}
rc = resultstats_monitor_register_entries( mbe, rslt, rslt->mss, parent );
}
entry_free( parent );
return rc;
}
static int
resultstats_monitor_db_close( BackendDB *be )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
monitor_extra_t *mbe;
BackendInfo *mi = backend_info( "monitor" );
if ( mi && mi->bi_extra ) {
int i;
mbe = mi->bi_extra;
for ( i = 0; i < SLAP_OP_LAST; i++ ) {
struct berval bv;
char buf[ BACKMONITOR_BUFSIZE ];
bv.bv_len = snprintf( buf, sizeof( buf ), "%s,%s",
resultstats_op[i].nrdn.bv_val,
rslt->mss_ndn.bv_val );
bv.bv_val = buf;
mbe->unregister_entry( &bv );
}
mbe->unregister_entry( &rslt->mss_ndn );
}
if ( !BER_BVISNULL(&rslt->mss_ndn) ) {
ch_free( rslt->mss_ndn.bv_val );
BER_BVZERO( &rslt->mss_ndn );
}
/* Make sure this does not point to a non-existent overlay instance */
rslt->mss->mss_private = NULL;
return 0;
}
static int
resultstats_response( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
int code, opidx;
/* skip internal ops */
if ( rs->sr_type != REP_RESULT || op->o_do_not_cache ) {
return SLAP_CB_CONTINUE;
}
code = slap_map_api2result( rs );
if ( code > LDAP_OTHER )
code = LDAP_OTHER+1;
opidx = slap_req2op( op->o_tag );
__atomic_fetch_add( &(rslt->stats[opidx][code]), 1, __ATOMIC_RELAXED );
return SLAP_CB_CONTINUE;
}
static int
resultstats_db_init( BackendDB *be, ConfigReply *cr )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
resultstats_t *rslt;
rslt = (resultstats_t *)ch_calloc( 1, sizeof( resultstats_t ) );
on->on_bi.bi_private = (void *)rslt;
ber_str2bv( "cn=Result Stats", 0, 1,
&rslt->rslt_rdn );
return resultstats_monitor_db_init();
}
static int
resultstats_db_open( BackendDB *be, ConfigReply *cr )
{
return resultstats_monitor_db_open( be );
}
static int
resultstats_db_destroy( BackendDB *be, ConfigReply *cr )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
if ( rslt->rslt_rdn.bv_len > 0 ) {
ch_free( rslt->rslt_rdn.bv_val );
}
ch_free( rslt );
return 0;
}
static int
resultstats_db_close( BackendDB *be, ConfigReply *cr )
{
return resultstats_monitor_db_close( be );
}
int
resultstats_initialize( void )
{
int code;
resultstats.on_bi.bi_type = "resultstats";
resultstats.on_bi.bi_db_init = resultstats_db_init;
resultstats.on_bi.bi_db_open = resultstats_db_open;
resultstats.on_bi.bi_db_destroy = resultstats_db_destroy;
resultstats.on_bi.bi_db_close = resultstats_db_close;
resultstats.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
resultstats.on_response = resultstats_response;
code = resultstats_monitor_initialize();
if ( code != 0)
return code;
return overlay_register( &resultstats );
}
#if SLAPD_OVER_RESULTSTATS == SLAPD_MOD_DYNAMIC
int
init_module( int argc, char *argv[] )
{
return resultstats_initialize();
}
#endif /* SLAPD_OVER_RESULTSTATS == SLAPD_MOD_DYNAMIC */
#endif /* SLAPD_OVER_RESULTSTATS */

View file

@ -0,0 +1,96 @@
.TH SLAPO-RESULTSTATS 5 "RELEASEDATE" "OpenLDAP LDVERSION"
.\" Copyright 2005-2025 The OpenLDAP Foundation All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.\" $OpenLDAP$
.SH NAME
slapo\-resultstats \- Operation result statistics for slapd
.SH SYNOPSIS
ETCDIR/slapd.conf
.SH DESCRIPTION
The
.B resultstats
overlay adds per-operation counters for LDAP operation results and
exposes them via
.B cn=monitor.
Result codes from
.B LdapSuccess(0)
to
.B LdapOther(80)
are counted separately as individual values. Codes higher than that are counted
together.
.P
The overlay can be configured on a particular database, or on the front-end,
or both. When configured on the front-end, it will count operations
processed by all databases. When configured on a particular databases,
only operations processed by that database will be counted. Monitoring
must be enabled for the database (mdb databases have monitoring on by default),
and
.B cn=monitor
bust be configured.
.P
.B Note:
Bind request responses will be counted for the database where
the identity used is located. For example, binding with the rootdn of
the database will be counted for that database, but anonymous binds
will be counted only at the front-end level. Failed schema checks
(such as undefinedAttributeType) cannot be counted at all, since they
happen before a database is chosen.
.P
When enabled, the resultstats overlay will add a
.B cn=Result Stats
container under the database entry in cn=monitor, and under it entries
for all types of LDAP operations. The statistic is exposed via the
.B olmResultCodeStat
attribute with the
.B x-resultcode-<number>
tag. Errors higher than 80 will be exposed under the
.B x-resultcode-more
tag. Only result codes with counters higher than 0
will be displayed.
.SH EXAPLES
This is an example entry for search requests, where we have 1 successful operation and one that returned
.B NoSuchObject(32)
.P
.nf
dn: cn=Search,cn=Result Stats,cn=database 2,cn=databases,cn=monitor
objectClass: olmResultStatOperation
olmResultCodeStat;x-resultcode-0: 1
olmResultCodeStat;x-resultcode-32: 1
entryDN: cn=Search,cn=Result Stats,cn=database 2,cn=databases,cn=monitor
.SH CONFIGURATION
This overlay does not have any configuration directives.
.SH Configuration Example
.nf
moduleload resultstats.la
database config
rootdn cn=config
rootpw password
#resultstats configured on the front-end
#entries will appear under cn=Result Stats,cn=database 0,cn=databases,cn=monitor
overlay resultstats
database monitor
database mdb
suffix dc=example,dc=com
rootdn cn=admin,dc=example,dc=com
rootpw password
#resultstats configured on this database
#entries will appear under cn=Result Stats,cn=database 2,cn=databases,cn=monitor
overlay resultstats
.SH FILES
.TP
ETCDIR/slapd.conf
default slapd configuration file
.SH SEE ALSO
.BR slapd.conf (5),
.BR slapd\-monitor (5),
.SH AUTHOR
Nadezhda Ivanova, on behalf of Symas Corp.

View file

@ -0,0 +1,4 @@
progs
schema
testdata
testrun

View file

@ -0,0 +1,23 @@
sp := $(sp).x
dirstack_$(sp) := $(d)
d := $(dir)
.PHONY: test
CLEAN += clients servers tests/progs tests/schema tests/testdata tests/testrun
test: all clients servers tests/progs
test:
cd tests; \
SRCDIR=$(abspath $(LDAP_SRC)) \
LDAP_BUILD=$(abspath $(LDAP_BUILD)) \
TOPDIR=$(abspath $(SRCDIR)) \
LIBTOOL=$(abspath $(LIBTOOL)) \
$(abspath $(SRCDIR))/tests/run test001-resultstats
servers clients tests/progs:
ln -s $(abspath $(LDAP_BUILD))/$@ $@
d := $(dirstack_$(sp))
sp := $(basename $(sp))

View file

@ -0,0 +1,33 @@
# provider slapd config -- for testing
# $OpenLDAP$
include @SCHEMADIR@/core.schema
include @SCHEMADIR@/cosine.schema
include @SCHEMADIR@/inetorgperson.schema
pidfile @TESTDIR@/slapd.m.pid
argsfile @TESTDIR@/slapd.m.args
#mod#modulepath ../servers/slapd/back-@BACKEND@/
#mod#moduleload back_@BACKEND@.la
moduleload ../resultstats.la
pidfile @TESTDIR@/slapd.1.pid
#######################################################################
# database definitions
#######################################################################
database @BACKEND@
suffix "dc=example,dc=com"
directory @TESTDIR@/db.1.a
# root or superuser
rootdn "cn=Manager,dc=example,dc=com"
rootpw secret
database config
include @TESTDIR@/configpw.conf
database monitor

View file

@ -0,0 +1,99 @@
dn: dc=example,dc=com
objectClass: top
objectClass: organization
objectClass: domainRelatedObject
objectClass: dcObject
dc: example
l: Anytown, Michigan
st: Michigan
o: Example, Inc.
o: EX
o: Ex.
description: The Example, Inc. at Anytown
postalAddress: Example, Inc. $ 535 W. William St. $ Anytown, MI 48109 $ US
telephoneNumber: +1 313 555 1817
associatedDomain: example.com
dn: ou=people,dc=example,dc=com
objectClass: organizationalUnit
ou: people
description: All domain members
dn: cn=user01,ou=people,dc=example,dc=com
cn: user01
objectClass: inetOrgPerson
userPassword:: UEBzc3cwcmQ=
roomNumber: 101
carLicense: 1234ha
sn: user01
mobile: 12345678
mobile: 987654321
ou: people
preferredLanguage: English
description: This is user user01
dn: cn=user02,ou=people,dc=example,dc=com
cn: user02
objectClass: inetOrgPerson
userPassword:: UEBzc3cwcmQ=
roomNumber: 102
carLicense: 1234hb
sn: user02
mobile: 12345678
mobile: 987654321
ou: people
preferredLanguage: English
description: This is user user02
dn: cn=user03,ou=people,dc=example,dc=com
cn: user03
objectClass: inetOrgPerson
userPassword:: UEBzc3cwcmQ=
roomNumber: 103
carLicense: 1234hc
sn: user03
mobile: 12345678
mobile: 987654321
ou: people
preferredLanguage: English
description: This is user user03
dn: cn=user04,ou=people,dc=example,dc=com
cn: user04
objectClass: inetOrgPerson
userPassword:: UEBzc3cwcmQ=
roomNumber: 104
carLicense: 1234ha
sn: user04
mobile: 12345678
mobile: 987654321
ou: people
preferredLanguage: English
description: This is user user04
dn: cn=user05,ou=people,dc=example,dc=com
cn: user05
objectClass: inetOrgPerson
userPassword:: UEBzc3cwcmQ=
roomNumber: 105
carLicense: 1234hb
sn: user05
mobile: 12345678
mobile: 987654321
ou: people
preferredLanguage: English
description: This is user user05
dn: cn=user06,ou=people,dc=example,dc=com
cn: user06
objectClass: inetOrgPerson
userPassword:: UEBzc3cwcmQ=
roomNumber: 106
carLicense: 1234hc
sn: user06
mobile: 12345678
mobile: 987654321
ou: people
preferredLanguage: English
description: This is user user06

View file

@ -0,0 +1,6 @@
dn: cn=Bind,cn=Result Stats,cn=frontend,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Bind
olmResultCodeStat;x-resultcode-0: 28
olmResultCodeStat;x-resultcode-49: 5

View file

@ -0,0 +1,6 @@
dn: cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Modify
olmResultCodeStat;x-resultcode-0: 5
olmResultCodeStat;x-resultcode-32: 5

View file

@ -0,0 +1,45 @@
# base="cn=Result Stats,cn=database 1,cn=databases,cn=monitor"...
dn: cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: monitorContainer
cn: Result Stats
dn: cn=Bind,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Bind
dn: cn=Unbind,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Unbind
dn: cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Search
dn: cn=Compare,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Compare
dn: cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Modify
dn: cn=Modrdn,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Modrdn
dn: cn=Add,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Add
dn: cn=Delete,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Delete
dn: cn=Abandon,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Abandon
dn: cn=Extended,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Extended

View file

@ -0,0 +1,6 @@
dn: cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
objectClass: olmResultStatOperation
cn: Search
olmResultCodeStat;x-resultcode-0: 5
olmResultCodeStat;x-resultcode-32: 5

View file

@ -0,0 +1,17 @@
#!/bin/sh
## $OpenLDAP$
## This work is part of OpenLDAP Software <http://www.openldap.org/>.
##
## Copyright 1998-2024 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 the file LICENSE in the
## top-level directory of the distribution or, alternatively, at
## <http://www.OpenLDAP.org/license.html>.
TOPSRCDIR="$SRCDIR" OBJDIR="${LDAP_BUILD}" SRCDIR="${SRCDIR}/tests" DEFSDIR="${SRCDIR}/scripts" SCRIPTDIR="${TOPDIR}/tests/scripts" "${LDAP_BUILD}/tests/run" $*

View file

@ -0,0 +1,372 @@
#! /bin/sh
# $OpenLDAP$
## This work is part of OpenLDAP Software <http://www.openldap.org/>.
##
## Copyright 1998-2025 The OpenLDAP Foundation.
## Copyright 2025 Symas Corp.
##
## 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 the file LICENSE in the
## top-level directory of the distribution or, alternatively, at
## <http://www.OpenLDAP.org/license.html>.
##
## ACKNOWLEDGEMENTS:
## This work was developed in 2025 by Nadezhda Ivanova for Symas Corp.
echo "running defines.sh"
. $SRCDIR/scripts/defines.sh
echo ""
rm -rf $TESTDIR
mkdir -p $TESTDIR $DBDIR1
$SLAPPASSWD -g -n >$CONFIGPWF
echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >$TESTDIR/configpw.conf
echo "Starting slapd on TCP/IP port $PORT1..."
. $CONFFILTER $BACKEND < data/slapd.conf > $CONF1
$SLAPD -f $CONF1 -h $URI1 -d $LVL > $LOG1 2>&1 &
PID=$!
if test $WAIT != 0 ; then
echo PID $PID
read foo
fi
KILLPIDS="$PID"
sleep 1
echo "Using ldapsearch to check that slapd is running..."
for i in 0 1 2 3 4 5; do
$LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \
'objectclass=*' > /dev/null 2>&1
RC=$?
if test $RC = 0 ; then
break
fi
echo "Waiting 5 seconds for slapd to start..."
sleep 5
done
if test $RC != 0 ; then
echo "ldapsearch failed ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Using ldapadd to populate the database..."
$LDAPADD -D "$MANAGERDN" -H $URI1 -w $PASSWD < \
data/test001-add.ldif > $TESTOUT 2>&1
RC=$?
if test $RC != 0 ; then
echo "ldapadd failed ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
cat /dev/null > $SEARCHOUT
echo "Searching base=\"$BASEDN\"..."
echo "# searching base=\"$BASEDN\"..." >> $SEARCHOUT
$LDAPSEARCH -S "" -H $URI1 -b "$BASEDN" >> $SEARCHOUT 2>&1
RC=$?
if test $RC != 0 ; then
echo "Search failed ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
#enable the overlay
echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
>> $TESTOUT 2>&1 << EOMODS
dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
objectClass: olcOverlayConfig
EOMODS
RC=$?
if test $RC != 0 ; then
echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Modifying cn=config, enabling resultstats overlay on frontend"
$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
>> $TESTOUT 2>&1 << EOMODS
dn: olcOverlay=resultstats,olcDatabase={-1}frontend,cn=config
objectClass: olcOverlayConfig
EOMODS
RC=$?
if test $RC != 0 ; then
echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
#run successful searches
echo "Performing successful search..."
for i in 1 2 3 4 5; do
$LDAPSEARCH -S "" -H $URI1 -b "cn=user0$i,ou=people,$BASEDN" "(objectClass=*)" '*' \
> /dev/null 2>&1
RC=$?
if test $RC != 0 ; then
echo "Expected successful search ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
done
#verify
RC=$?
if test $RC != 0 ; then
echo "Expected successful search ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
#run noSuchObject searches
echo "Performing incorrect search (noSuchObject).."
for i in 1 2 3 4 5; do
$LDAPSEARCH -S "" -H $URI1 -b "cn=user0$i,$BASEDN" "(objectClass=*)" '*' \
> /dev/null 2>&1
RC=$?
if test $RC != 32 ; then
echo "Expected noSuchObject ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
done
#verify
SEARCHDN="cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
echo "Verifying search statistics..."
cat /dev/null > $SEARCHOUT
echo " base=\"$SEARCHDN\"..."
echo "# base=\"$SEARCHDN\"..." >> $SEARCHOUT
$LDAPSEARCH -H $URI1 \
-b "$SEARCHDN" \
"(objectClass=*)" '*' >> $SEARCHOUT 2>&1
RC=$?
if test $RC != 0 ; then
echo "Unable to read monitor stats ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Filtering ldapsearch results..."
$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
$LDIFFILTER < data/test001-search.ldif > $LDIFFLT
echo "Comparing filter output..."
$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
if test $? != 0 ; then
echo "comparison failed - search statistics incorrect"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit 1
fi
#run successful mods
echo "Performing successful modify..."
for i in 1 2 3 4 5; do
$LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
>> $TESTOUT 2>&1 << EOMODS
dn: cn=user0$i,ou=people,$BASEDN
changetype: modify
replace: roomNumber
roomNumber: 200
EOMODS
RC=$?
if test $RC != 0 ; then
echo "Expected successful modify ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
done
#run incorrect mods
#this will not be counted, as it happens before database choice
echo "Performing incorrect modify (undefinedAttributeType)..."
for i in 1 2 3 4 5; do
$LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
>> $TESTOUT 2>&1 << EOMODS
dn: cn=user0$i,ou=people,$BASEDN
changetype: modify
replace: IsBusy
IsBusy: TRUE
EOMODS
RC=$?
if test $RC != 17 ; then
echo "Expected undefinedAttributeType ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
done
echo "Performing incorrect modify (noSuchObject)..."
for i in 1 2 3 4 5; do
$LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
>> $TESTOUT 2>&1 << EOMODS
dn: cn=user0$i,$BASEDN
changetype: modify
replace: roomNumber
roomNumber: 200
EOMODS
RC=$?
if test $RC != 32 ; then
echo "Expected noSuchObject ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
done
echo "Performing incorrect modify (invalidCredentials)..."
#this will only be counted on the frontend
for i in 1 2 3 4 5; do
$LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w "wrongpassword" \
>> $TESTOUT 2>&1 << EOMODS
dn: cn=user0$i,ou=people,$BASEDN
changetype: modify
replace: roomNumber
roomNumber: 200
EOMODS
RC=$?
if test $RC != 49 ; then
echo "Expected invalidCredentials ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
done
#verify
echo "Verifying modify statistics..."
cat /dev/null > $SEARCHOUT
MODIFYDN="cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
echo " base=\"$MODIFYDN\"..."
echo "# base=\"$MODIFYDN\"..." >> $SEARCHOUT
$LDAPSEARCH -H $URI1 \
-b "$MODIFYDN" \
"(objectClass=*)" '*' >> $SEARCHOUT 2>&1
RC=$?
if test $RC != 0 ; then
echo "Unable to read monitor stats ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Filtering ldapsearch results..."
$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
$LDIFFILTER < data/test001-modify.ldif > $LDIFFLT
echo "Comparing filter output..."
$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
if test $? != 0 ; then
echo "comparison failed - modify statistics incorrect"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit 1
fi
echo "Verifying bind statistics..."
cat /dev/null > $SEARCHOUT
BINDDN="cn=Bind,cn=Result Stats,cn=frontend,cn=databases,cn=monitor"
echo " base=\"$BINDDN\"..."
echo "# base=\"$BINDDN\"..." >> $SEARCHOUT
$LDAPSEARCH -H $URI1 \
-b "$BINDDN" \
"(objectClass=*)" '*' >> $SEARCHOUT 2>&1
RC=$?
if test $RC != 0 ; then
echo "Unable to read monitor stats ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Filtering ldapsearch results..."
$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
$LDIFFILTER < data/test001-bind.ldif > $LDIFFLT
echo "Comparing filter output..."
$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
if test $? != 0 ; then
echo "comparison failed - bind statistics incorrect"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit 1
fi
cat /dev/null > $SEARCHOUT
OVERLAYDN="olcOverlay={0}resultstats,olcDatabase={1}$BACKEND,cn=config"
echo "Modifying cn=config, deleting resultstats overlay on $BACKEND"
$LDAPDELETE -D cn=config -H $URI1 -y $CONFIGPWF $OVERLAYDN\
>> $TESTOUT 2>&1
RC=$?
if test $RC != 0 ; then
echo "ldapdelete failed ($RC)! Unable to delete resultstats overlay"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
>> $TESTOUT 2>&1 << EOMODS
dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
objectClass: olcOverlayConfig
EOMODS
RC=$?
if test $RC != 0 ; then
echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Verifying statistics are reset..."
cat /dev/null > $SEARCHOUT
RESDN="cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
echo " base=\"$RESDN\"..."
echo "# base=\"$RESDN\"..." >> $SEARCHOUT
$LDAPSEARCH -H $URI1 \
-b "$RESDN" \
"(objectClass=*)" '*' >> $SEARCHOUT 2>&1
RC=$?
if test $RC != 0 ; then
echo "Unable to read monitor stats ($RC)!"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Filtering ldapsearch results..."
$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
$LDIFFILTER < data/test001-reset.ldif > $LDIFFLT
echo "Comparing filter output..."
$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
if test $? != 0 ; then
echo "comparison failed - reset statistics incorrect"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit 1
fi
OVERLAYDN="olcOverlay={0}resultstats,olcDatabase={1}$BACKEND,cn=config"
echo "Modifying cn=config, deleting resultstats overlay on $BACKEND"
$LDAPDELETE -D cn=config -H $URI1 -y $CONFIGPWF $OVERLAYDN\
>> $TESTOUT 2>&1
RC=$?
if test $RC != 0 ; then
echo "ldapdelete failed ($RC)! Unable to delete resultstats overlay"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
>> $TESTOUT 2>&1 << EOMODS
dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
objectClass: olcOverlayConfig
EOMODS
RC=$?
if test $RC != 0 ; then
echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
test $KILLSERVERS != no && kill -HUP $KILLPIDS
exit $RC
fi
test $KILLSERVERS != no && kill -HUP $KILLPIDS
echo ">>>>> Test succeeded"
test $KILLSERVERS != no && wait
exit 0