From 3f4deb91789189521d577457bd6da27de8fd75b1 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 07 Oct 2026 08:31:10 +0000
Subject: [PATCH] [#1153] Parse the whole DN string, and build or split DN strings through DN instead of string operations (#1171)
---
opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 289 insertions(+), 0 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java
index 370f28c..5367bdd 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java
@@ -12,6 +12,7 @@
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Portions Copyright 2013-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.tools.upgrade;
@@ -24,6 +25,7 @@
import static javax.security.auth.callback.TextOutputCallback.*;
import static org.forgerock.util.Utils.joinAsString;
import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.schema.SchemaConstants.EMR_DN_NAME;
import static org.opends.server.tools.upgrade.FileManager.copyRecursively;
import static org.opends.server.tools.upgrade.UpgradeUtils.*;
import static org.opends.server.util.StaticUtils.*;
@@ -32,9 +34,11 @@
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
+import java.io.PrintStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -67,6 +71,7 @@
import org.forgerock.opendj.ldif.LDIFEntryReader;
import org.opends.server.backends.pluggable.spi.TreeName;
import org.opends.server.tools.RebuildIndex;
+import org.opends.server.tools.VerifyIndex;
import org.opends.server.util.BuildVersion;
import org.opends.server.util.ChangeOperationType;
import org.opends.server.util.StaticUtils;
@@ -662,6 +667,290 @@
}
/**
+ * Creates a task that verifies, at the end of the upgrade, the equality indexes of the attributes whose values
+ * are compared as DNs, and rebuilds them under each base DN where they do not match the entries. Only an index
+ * that is missing a key needs the rebuild: the verification computes the keys of every entry with the matching
+ * rules of the upgraded server, and finds those that were computed differently, or not at all, by the previous
+ * version. Unlike {@link #rebuildIndexesNamed}, a backend whose indexes match its entries is only read.
+ *
+ * @param summary
+ * A message describing why the indexes are verified and asking whether to do it at the end of the
+ * upgrade.
+ * @return The verify and rebuild task.
+ */
+ static UpgradeTask verifyAndRebuildDNEqualityIndexes(final LocalizableMessage summary)
+ {
+ return new AbstractUpgradeTask()
+ {
+ private boolean isATaskToPerform;
+
+ @Override
+ public void prepare(UpgradeContext context) throws ClientException
+ {
+ Upgrade.needToRunPostUpgradePhase();
+ // Requires answer from the user. Verifying only reads, so it is done unless the user declines.
+ isATaskToPerform = context.confirmYN(summary, YES) == YES;
+ }
+
+ @Override
+ public void postUpgrade(final UpgradeContext context) throws ClientException
+ {
+ if (!isATaskToPerform)
+ {
+ postponePostUpgrade(context);
+ return;
+ }
+ if (isRebuildAllIndexesTaskAccepted || indexesToRebuild.contains("." + EMR_DN_NAME))
+ {
+ // These indexes are rebuilt at the end of the upgrade anyway.
+ return;
+ }
+
+ final Map<String, Set<String>> attributesPerBackend;
+ try
+ {
+ attributesPerBackend = getDNEqualityIndexedAttributesPerBackend();
+ }
+ catch (IOException e)
+ {
+ throw new ClientException(ReturnCode.ERROR_UNEXPECTED, ERR_UPGRADE_READING_CONF_FILE.get(e.getMessage()), e);
+ }
+ verifyAndRebuildOrWarn(context, configFile.getAbsolutePath(),
+ getDNEqualityIndexesToVerify(attributesPerBackend, getBaseDNsPerBackendsFromConfig()), true,
+ UpgradeLog::getPrintStream);
+ }
+
+ @Override
+ public void postponePostUpgrade(UpgradeContext context) throws ClientException
+ {
+ if (!isRebuildAllIndexesIsPresent)
+ {
+ context.notify(INFO_UPGRADE_VERIFY_DN_EQUALITY_INDEXES_DECLINED.get(), TextOutputCallback.WARNING);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.valueOf(summary);
+ }
+ };
+ }
+
+ /**
+ * Returns the base DNs whose DN equality indexes are verified, with the attributes of these indexes: those of the
+ * backend holding each base DN. A backend whose base DNs are unknown is left out.
+ *
+ * @param attributesPerBackend
+ * The attributes with a DN equality index, per backend ID.
+ * @param baseDNsPerBackend
+ * The base DNs, per backend ID.
+ * @return The attributes whose indexes are verified, per base DN.
+ */
+ static Map<String, Set<String>> getDNEqualityIndexesToVerify(final Map<String, Set<String>> attributesPerBackend,
+ final Map<String, Set<String>> baseDNsPerBackend)
+ {
+ final Map<String, Set<String>> attributesPerBaseDN = new TreeMap<>();
+ for (final Map.Entry<String, Set<String>> backend : attributesPerBackend.entrySet())
+ {
+ final Set<String> baseDNs = baseDNsPerBackend.get(backend.getKey());
+ if (baseDNs != null)
+ {
+ for (final String baseDN : baseDNs)
+ {
+ attributesPerBaseDN.put(baseDN, backend.getValue());
+ }
+ }
+ }
+ return attributesPerBaseDN;
+ }
+
+ /** Opens the stream the tools write to for a base DN: the rebuild closes it. */
+ interface ToolOutput
+ {
+ /**
+ * Opens the stream.
+ *
+ * @return The stream the tools write to.
+ * @throws ClientException
+ * If the stream cannot be opened.
+ */
+ PrintStream open() throws ClientException;
+ }
+
+ /**
+ * Verifies the DN equality indexes under each base DN, and rebuilds them if needed, as
+ * {@link #verifyAndRebuildOrWarn(UpgradeContext, String, String, Set, boolean, PrintStream)} does for one base DN.
+ * Once a rebuild has failed, the indexes of the next base DNs are neither verified nor rebuilt, and the user is
+ * told which ones were left: the cause of the failure, such as a full temporary directory, would most likely make
+ * their rebuild fail too, after it had deleted them.
+ *
+ * @param context
+ * The upgrade context, which is notified of the outcome.
+ * @param configFilePath
+ * The path of the configuration file.
+ * @param indexesPerBaseDN
+ * The attributes whose indexes are verified, and rebuilt if needed, per base DN.
+ * @param initializeServer
+ * Whether the tools have to initialize the server components.
+ * @param output
+ * Opens the stream the tools write to, once per base DN.
+ * @throws ClientException
+ * If a rebuild fails, or if the outcome cannot be reported.
+ */
+ static void verifyAndRebuildOrWarn(final UpgradeContext context, final String configFilePath,
+ final Map<String, Set<String>> indexesPerBaseDN, final boolean initializeServer, final ToolOutput output)
+ throws ClientException
+ {
+ ClientException failedRebuild = null;
+ for (final Map.Entry<String, Set<String>> baseDN : indexesPerBaseDN.entrySet())
+ {
+ if (failedRebuild != null)
+ {
+ context.notify(WARN_UPGRADE_VERIFY_DN_EQUALITY_INDEXES_SKIPPED.get(
+ joinAsString(", ", baseDN.getValue()), baseDN.getKey()), WARNING);
+ continue;
+ }
+ final PrintStream out = output.open();
+ try
+ {
+ verifyAndRebuildOrWarn(context, configFilePath, baseDN.getKey(), baseDN.getValue(), initializeServer, out);
+ }
+ catch (final ClientException e)
+ {
+ failedRebuild = e;
+ }
+ finally
+ {
+ // A rebuild closes it too: closing it again does nothing
+ close(out);
+ }
+ }
+ if (failedRebuild != null)
+ {
+ throw failedRebuild;
+ }
+ }
+
+ /** What {@link #verifyAndRebuildIndexes} found. */
+ enum IndexVerification
+ {
+ /** The indexes match the entries. */
+ CONSISTENT,
+ /** The indexes did not match the entries, and were rebuilt. */
+ REBUILT,
+ /** The indexes could not be verified, as when their backend cannot be read: they were left as they were. */
+ NOT_VERIFIED
+ }
+
+ /**
+ * Verifies the provided DN equality indexes under a base DN, and rebuilds them if needed, as
+ * {@link #verifyAndRebuildIndexes} does, then tells the user the outcome. When the indexes cannot be verified, the
+ * user is warned instead of the upgrade failing: they were left as they were, and a backend that cannot be read
+ * now, such as a JDBC or Cassandra backend whose database is down, is left to the administrator. A rebuild that
+ * fails still fails the upgrade, as it may have left the indexes untrusted.
+ *
+ * @param context
+ * The upgrade context, which is notified of the outcome.
+ * @param configFilePath
+ * The path of the configuration file.
+ * @param baseDN
+ * The base DN to verify.
+ * @param attributes
+ * The attributes whose indexes are verified, and rebuilt if needed.
+ * @param initializeServer
+ * Whether the tools have to initialize the server components.
+ * @param out
+ * The stream the tools write to.
+ * @throws ClientException
+ * If the rebuild fails, or if the outcome cannot be reported.
+ */
+ static void verifyAndRebuildOrWarn(final UpgradeContext context, final String configFilePath, final String baseDN,
+ final Set<String> attributes, final boolean initializeServer, final PrintStream out) throws ClientException
+ {
+ final String indexes = joinAsString(", ", attributes);
+ final ProgressNotificationCallback pnc = new ProgressNotificationCallback(
+ INFORMATION, INFO_UPGRADE_VERIFY_DN_EQUALITY_INDEXES_STARTS.get(indexes, baseDN), 25);
+ context.notifyProgress(pnc);
+ final IndexVerification verification;
+ try
+ {
+ verification = verifyAndRebuildIndexes(configFilePath, baseDN, attributes, initializeServer, out);
+ }
+ catch (final ClientException e)
+ {
+ context.notifyProgress(pnc.setProgress(-100));
+ throw e;
+ }
+ context.notifyProgress(pnc.setProgress(100));
+ switch (verification)
+ {
+ case CONSISTENT:
+ context.notify(INFO_UPGRADE_VERIFY_DN_EQUALITY_INDEXES_CONSISTENT.get(indexes, baseDN));
+ break;
+ case REBUILT:
+ context.notify(INFO_UPGRADE_VERIFY_DN_EQUALITY_INDEXES_INCONSISTENT.get(indexes, baseDN));
+ break;
+ default:
+ context.notify(WARN_UPGRADE_VERIFY_DN_EQUALITY_INDEXES_FAILED.get(indexes, baseDN), WARNING);
+ break;
+ }
+ }
+
+ /**
+ * Verifies the provided indexes under a base DN with the verify-index tool, and rebuilds them with the
+ * rebuild-index tool when it reports any error. Indexes that cannot be verified are not rebuilt: whatever keeps
+ * them from being read would most likely make the rebuild fail too, after it has deleted them. The backend must
+ * not be in use by a running server.
+ *
+ * @param configFilePath
+ * The path of the configuration file.
+ * @param baseDN
+ * The base DN to verify.
+ * @param attributes
+ * The attributes whose indexes are verified, and rebuilt if needed.
+ * @param initializeServer
+ * Whether the tools have to initialize the server components.
+ * @param out
+ * The stream the tools write to.
+ * @return What the verification found.
+ * @throws ClientException
+ * If the rebuild fails.
+ */
+ static IndexVerification verifyAndRebuildIndexes(final String configFilePath, final String baseDN,
+ final Collection<String> attributes, final boolean initializeServer, final PrintStream out)
+ throws ClientException
+ {
+ final List<String> args = new ArrayList<>();
+ args.add("--configFile");
+ args.add(configFilePath);
+ args.add("--baseDN");
+ args.add(baseDN);
+ for (final String attribute : attributes)
+ {
+ args.add("--index");
+ args.add(attribute);
+ }
+
+ logger.debug(INFO_UPGRADE_REBUILD_INDEX_ARGUMENTS, args);
+ final long errors = VerifyIndex.countIndexErrors(args.toArray(new String[0]), initializeServer, out);
+ if (errors < 0)
+ {
+ return IndexVerification.NOT_VERIFIED;
+ }
+ if (errors == 0)
+ {
+ return IndexVerification.CONSISTENT;
+ }
+
+ if (new RebuildIndex().rebuildIndexesWithinMultipleBackends(initializeServer, out, args) != 0)
+ {
+ throw new ClientException(ReturnCode.ERROR_UNEXPECTED, ERR_UPGRADE_PERFORMING_POST_TASKS_FAIL.get());
+ }
+ return IndexVerification.REBUILT;
+ }
+
+ /**
* This task is processed at the end of the upgrade, rebuilding indexes. If a
* rebuild all indexes has been registered before, it takes the flag
* relatively to single rebuild index.
--
Gitblit v1.10.0