From beda50cfdba870c781fd25d2bae9c2a08c9d3eba Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 24 Jun 2026 15:59:47 +0300 Subject: [PATCH 1/2] Add CI smoke tests for the addrate/authrate/modrate/searchrate tools The four LDAP load-testing tools shipped in opendj-ldap-toolkit are built and packaged on every CI run but were never executed, so a regression in their launcher scripts, classpath, or argument parsing could ship undetected. Run each tool against the freshly set-up server (LDAPS 1636, --trustAll) in the "Test on Unix" and "Test on Windows" steps, right after the existing 5000-user verification while the server is still online: - searchrate: subtree search by random uid=user.N - authrate: direct binds as the sample users (userPassword=password) - modrate: description replace on random users - addrate: add/delete with a small inline MakeLDIF template (collision-free uid=addrate.N, FIFO delete + purge on exit so the sample-user count is preserved) Concurrency is via -c connections with -t 1 (multi-thread mode would require --noRebind); authrate takes no -t. Pass/fail is by exit code: Unix relies on set -eo pipefail, Windows adds explicit $LASTEXITCODE checks since the step runs in PowerShell. --- .github/workflows/build.yml | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58927fe9e8..3aaa8df139 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,6 +92,28 @@ jobs: opendj-server-legacy/target/package/opendj/bin/status --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --trustAll opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 5000 + # --- Load-testing tools smoke tests (server online, 5000 sample users) --- + opendj-server-legacy/target/package/opendj/bin/searchrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "ou=people,dc=example,dc=com" --searchScope sub -c 4 -t 1 -m 2000 -i 1 -S -g "rand(0,4999)" "(uid=user.%d)" 1.1 + opendj-server-legacy/target/package/opendj/bin/authrate --hostname localhost --port 1636 --useSSL --trustAll -D "uid=user.%d,ou=people,dc=example,dc=com" -w password -c 4 -m 2000 -i 1 -S -g "rand(0,4999)" + opendj-server-legacy/target/package/opendj/bin/modrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -b "uid=user.%d,ou=people,dc=example,dc=com" -c 4 -t 1 -m 2000 -i 1 -S -g "rand(0,4999)" "description:modrate load test" + cat > /tmp/addrate.template <<'EOF' + define suffix=dc=example,dc=com + + branch: ou=People,[suffix] + subordinateTemplate: person + + template: person + rdnAttr: uid + objectClass: top + objectClass: person + objectClass: organizationalPerson + objectClass: inetOrgPerson + uid: addrate. + cn: AddRate {uid} + sn: AddRate + userPassword: password + EOF + opendj-server-legacy/target/package/opendj/bin/addrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -c 4 -t 1 -m 2000 -i 1 -S -C fifo -s 100 /tmp/addrate.template opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template opendj-server-legacy/target/package/opendj/bin/stop-ds @@ -255,6 +277,32 @@ jobs: opendj-server-legacy\target\package\opendj\bat\status.bat --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --trustAll opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | find /c '"dn:"' | findstr "5000" + # --- Load-testing tools smoke tests (server online, 5000 sample users) --- + opendj-server-legacy\target\package\opendj\bat\searchrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "ou=people,dc=example,dc=com" --searchScope sub -c 4 -t 1 -m 2000 -i 1 -S -g "rand(0,4999)" "(uid=user.%d)" 1.1 + if ($LASTEXITCODE -ne 0) { throw "searchrate failed with exit code $LASTEXITCODE" } + opendj-server-legacy\target\package\opendj\bat\authrate.bat --hostname localhost --port 1636 --useSSL --trustAll -D "uid=user.%d,ou=people,dc=example,dc=com" -w password -c 4 -m 2000 -i 1 -S -g "rand(0,4999)" + if ($LASTEXITCODE -ne 0) { throw "authrate failed with exit code $LASTEXITCODE" } + opendj-server-legacy\target\package\opendj\bat\modrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -b "uid=user.%d,ou=people,dc=example,dc=com" -c 4 -t 1 -m 2000 -i 1 -S -g "rand(0,4999)" "description:modrate load test" + if ($LASTEXITCODE -ne 0) { throw "modrate failed with exit code $LASTEXITCODE" } + @' + define suffix=dc=example,dc=com + + branch: ou=People,[suffix] + subordinateTemplate: person + + template: person + rdnAttr: uid + objectClass: top + objectClass: person + objectClass: organizationalPerson + objectClass: inetOrgPerson + uid: addrate. + cn: AddRate {uid} + sn: AddRate + userPassword: password + '@ | Set-Content -Encoding ascii addrate.template + opendj-server-legacy\target\package\opendj\bat\addrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -c 4 -t 1 -m 2000 -i 1 -S -C fifo -s 100 addrate.template + if ($LASTEXITCODE -ne 0) { throw "addrate failed with exit code $LASTEXITCODE" } opendj-server-legacy\target\package\opendj\bat\dsconfig.bat create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll opendj-server-legacy\target\package\opendj\bat\makeldif.bat -o test.ldif -c suffix=dc=example2,dc=com opendj-server-legacy\target\package\opendj\config\MakeLDIF\example.template opendj-server-legacy\target\package\opendj\bat\stop-ds.bat From 1ba25825d8104e42f5df1729561955800640d729 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 24 Jun 2026 18:42:32 +0300 Subject: [PATCH 2/2] Fix %d substitution in rate-tool launchers on Windows and show table output in CI The searchrate/authrate/modrate/addrate .bat launchers delegated to _client-script.bat via "call ... %*". CALL performs an extra percent-expansion pass that strips the lone '%' from a Java format token, so on Windows "%d" was turned into "d" before reaching the JVM. As a result the rate tools received "uid=user.d" instead of a generated number: every bind/modify failed, searches matched nothing, and modrate ran unbounded until the runner exhausted its ephemeral ports ("Connect Error: Address already in use"). Chain to _client-script.bat without CALL so "%*" is expanded only once and "%d" reaches the tool intact. Only the four rate tools are affected (they are the only ones passing Java format strings via -g); the shared launcher is untouched. CI rate-tool smoke tests (.github/workflows/build.yml): - drop -S so all four tools print the bordered table instead of CSV; - use a plain "%d" on both Unix and Windows (the launcher fix removes the need for the fragile "%%d" escaping); - remove the spaces from the modrate modification string so the trailing argument survives batch re-parsing; - print an echo / Write-Host label before each tool so the log clearly delimits each block. --- .github/workflows/build.yml | 24 ++++++++++++------- .../src/main/assembly/bat/addrate.bat | 5 +++- .../src/main/assembly/bat/authrate.bat | 6 +++-- .../src/main/assembly/bat/modrate.bat | 5 +++- .../src/main/assembly/bat/searchrate.bat | 5 +++- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3aaa8df139..d15fd417ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,9 +93,13 @@ jobs: opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 5000 # --- Load-testing tools smoke tests (server online, 5000 sample users) --- - opendj-server-legacy/target/package/opendj/bin/searchrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "ou=people,dc=example,dc=com" --searchScope sub -c 4 -t 1 -m 2000 -i 1 -S -g "rand(0,4999)" "(uid=user.%d)" 1.1 - opendj-server-legacy/target/package/opendj/bin/authrate --hostname localhost --port 1636 --useSSL --trustAll -D "uid=user.%d,ou=people,dc=example,dc=com" -w password -c 4 -m 2000 -i 1 -S -g "rand(0,4999)" - opendj-server-legacy/target/package/opendj/bin/modrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -b "uid=user.%d,ou=people,dc=example,dc=com" -c 4 -t 1 -m 2000 -i 1 -S -g "rand(0,4999)" "description:modrate load test" + echo "===== searchrate =====" + opendj-server-legacy/target/package/opendj/bin/searchrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "ou=people,dc=example,dc=com" --searchScope sub -c 4 -t 1 -m 2000 -i 1 -g "rand(0,4999)" "(uid=user.%d)" 1.1 + echo "===== authrate =====" + opendj-server-legacy/target/package/opendj/bin/authrate --hostname localhost --port 1636 --useSSL --trustAll -D "uid=user.%d,ou=people,dc=example,dc=com" -w password -c 4 -m 2000 -i 1 -g "rand(0,4999)" + echo "===== modrate =====" + opendj-server-legacy/target/package/opendj/bin/modrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -b "uid=user.%d,ou=people,dc=example,dc=com" -c 4 -t 1 -m 2000 -i 1 -g "rand(0,4999)" "description:modrate-load-test" + echo "===== addrate =====" cat > /tmp/addrate.template <<'EOF' define suffix=dc=example,dc=com @@ -113,7 +117,7 @@ jobs: sn: AddRate userPassword: password EOF - opendj-server-legacy/target/package/opendj/bin/addrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -c 4 -t 1 -m 2000 -i 1 -S -C fifo -s 100 /tmp/addrate.template + opendj-server-legacy/target/package/opendj/bin/addrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -c 4 -t 1 -m 2000 -i 1 -C fifo -s 100 /tmp/addrate.template opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template opendj-server-legacy/target/package/opendj/bin/stop-ds @@ -278,12 +282,16 @@ jobs: opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | find /c '"dn:"' | findstr "5000" # --- Load-testing tools smoke tests (server online, 5000 sample users) --- - opendj-server-legacy\target\package\opendj\bat\searchrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "ou=people,dc=example,dc=com" --searchScope sub -c 4 -t 1 -m 2000 -i 1 -S -g "rand(0,4999)" "(uid=user.%d)" 1.1 + Write-Host "===== searchrate =====" + opendj-server-legacy\target\package\opendj\bat\searchrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "ou=people,dc=example,dc=com" --searchScope sub -c 4 -t 1 -m 2000 -i 1 -g "rand(0,4999)" "(uid=user.%d)" 1.1 if ($LASTEXITCODE -ne 0) { throw "searchrate failed with exit code $LASTEXITCODE" } - opendj-server-legacy\target\package\opendj\bat\authrate.bat --hostname localhost --port 1636 --useSSL --trustAll -D "uid=user.%d,ou=people,dc=example,dc=com" -w password -c 4 -m 2000 -i 1 -S -g "rand(0,4999)" + Write-Host "===== authrate =====" + opendj-server-legacy\target\package\opendj\bat\authrate.bat --hostname localhost --port 1636 --useSSL --trustAll -D "uid=user.%d,ou=people,dc=example,dc=com" -w password -c 4 -m 2000 -i 1 -g "rand(0,4999)" if ($LASTEXITCODE -ne 0) { throw "authrate failed with exit code $LASTEXITCODE" } - opendj-server-legacy\target\package\opendj\bat\modrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -b "uid=user.%d,ou=people,dc=example,dc=com" -c 4 -t 1 -m 2000 -i 1 -S -g "rand(0,4999)" "description:modrate load test" + Write-Host "===== modrate =====" + opendj-server-legacy\target\package\opendj\bat\modrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -b "uid=user.%d,ou=people,dc=example,dc=com" -c 4 -t 1 -m 2000 -i 1 -g "rand(0,4999)" "description:modrate-load-test" if ($LASTEXITCODE -ne 0) { throw "modrate failed with exit code $LASTEXITCODE" } + Write-Host "===== addrate =====" @' define suffix=dc=example,dc=com @@ -301,7 +309,7 @@ jobs: sn: AddRate userPassword: password '@ | Set-Content -Encoding ascii addrate.template - opendj-server-legacy\target\package\opendj\bat\addrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -c 4 -t 1 -m 2000 -i 1 -S -C fifo -s 100 addrate.template + opendj-server-legacy\target\package\opendj\bat\addrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -c 4 -t 1 -m 2000 -i 1 -C fifo -s 100 addrate.template if ($LASTEXITCODE -ne 0) { throw "addrate failed with exit code $LASTEXITCODE" } opendj-server-legacy\target\package\opendj\bat\dsconfig.bat create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll opendj-server-legacy\target\package\opendj\bat\makeldif.bat -o test.ldif -c suffix=dc=example2,dc=com opendj-server-legacy\target\package\opendj\config\MakeLDIF\example.template diff --git a/opendj-ldap-toolkit/src/main/assembly/bat/addrate.bat b/opendj-ldap-toolkit/src/main/assembly/bat/addrate.bat index 5d557fed62..02081debe8 100644 --- a/opendj-ldap-toolkit/src/main/assembly/bat/addrate.bat +++ b/opendj-ldap-toolkit/src/main/assembly/bat/addrate.bat @@ -13,10 +13,13 @@ rem Header, with the fields enclosed by brackets [] replaced by your own identif rem information: "Portions Copyright [year] [name of copyright owner]". rem rem Copyright 2014 ForgeRock AS. +rem Portions Copyright 2026 3A Systems, LLC setlocal set OPENDJ_INVOKE_CLASS="com.forgerock.opendj.ldap.tools.AddRate" set SCRIPT_NAME=addrate -call "%~dp0\..\lib\_client-script.bat" %* +rem Chain (no CALL) so a literal '%' in arguments (e.g. the "%d" of a Java +rem format string used by -g) is not stripped by CALL's extra expansion pass. +"%~dp0\..\lib\_client-script.bat" %* diff --git a/opendj-ldap-toolkit/src/main/assembly/bat/authrate.bat b/opendj-ldap-toolkit/src/main/assembly/bat/authrate.bat index a111923a5f..a7db1388eb 100644 --- a/opendj-ldap-toolkit/src/main/assembly/bat/authrate.bat +++ b/opendj-ldap-toolkit/src/main/assembly/bat/authrate.bat @@ -13,10 +13,12 @@ rem Header, with the fields enclosed by brackets [] replaced by your own identif rem information: "Portions Copyright [year] [name of copyright owner]". rem rem Copyright 2010 Sun Microsystems, Inc. - +rem Portions Copyright 2026 3A Systems, LLC setlocal set OPENDJ_INVOKE_CLASS="com.forgerock.opendj.ldap.tools.AuthRate" set SCRIPT_NAME=authrate -call "%~dp0\..\lib\_client-script.bat" %* +rem Chain (no CALL) so a literal '%' in arguments (e.g. the "%d" of a Java +rem format string used by -g) is not stripped by CALL's extra expansion pass. +"%~dp0\..\lib\_client-script.bat" %* diff --git a/opendj-ldap-toolkit/src/main/assembly/bat/modrate.bat b/opendj-ldap-toolkit/src/main/assembly/bat/modrate.bat index cc4c18e371..c3e2fc66f1 100644 --- a/opendj-ldap-toolkit/src/main/assembly/bat/modrate.bat +++ b/opendj-ldap-toolkit/src/main/assembly/bat/modrate.bat @@ -13,10 +13,13 @@ rem Header, with the fields enclosed by brackets [] replaced by your own identif rem information: "Portions Copyright [year] [name of copyright owner]". rem rem Copyright 2009 Sun Microsystems, Inc. +rem Portions Copyright 2026 3A Systems, LLC setlocal set OPENDJ_INVOKE_CLASS="com.forgerock.opendj.ldap.tools.ModRate" set SCRIPT_NAME=modrate -call "%~dp0\..\lib\_client-script.bat" %* +rem Chain (no CALL) so a literal '%' in arguments (e.g. the "%d" of a Java +rem format string used by -g) is not stripped by CALL's extra expansion pass. +"%~dp0\..\lib\_client-script.bat" %* diff --git a/opendj-ldap-toolkit/src/main/assembly/bat/searchrate.bat b/opendj-ldap-toolkit/src/main/assembly/bat/searchrate.bat index ffc9203825..e400fc2be2 100644 --- a/opendj-ldap-toolkit/src/main/assembly/bat/searchrate.bat +++ b/opendj-ldap-toolkit/src/main/assembly/bat/searchrate.bat @@ -13,10 +13,13 @@ rem Header, with the fields enclosed by brackets [] replaced by your own identif rem information: "Portions Copyright [year] [name of copyright owner]". rem rem Copyright 2009 Sun Microsystems, Inc. +rem Portions Copyright 2026 3A Systems, LLC setlocal set OPENDJ_INVOKE_CLASS="com.forgerock.opendj.ldap.tools.SearchRate" set SCRIPT_NAME=searchrate -call "%~dp0\..\lib\_client-script.bat" %* +rem Chain (no CALL) so a literal '%' in arguments (e.g. the "%d" of a Java +rem format string used by -g) is not stripped by CALL's extra expansion pass. +"%~dp0\..\lib\_client-script.bat" %*