From e86f702f8e11d85eb7fcbde8de64657a4e308a21 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Fri, 11 Sep 2026 13:06:49 +0000
Subject: [PATCH] [#966] Tell the import and restore task listeners the task is over on every path (#969)
---
opendj-server-legacy/src/test/java/org/opends/server/tasks/TestImportAndExport.java | 79 +++++++++++++++++++
opendj-server-legacy/src/main/java/org/opends/server/tasks/ImportTask.java | 68 ++++++++++------
opendj-server-legacy/src/main/java/org/opends/server/tasks/RestoreTask.java | 49 +++++++-----
3 files changed, 149 insertions(+), 47 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tasks/ImportTask.java b/opendj-server-legacy/src/main/java/org/opends/server/tasks/ImportTask.java
index 5d69679..aaebe55 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tasks/ImportTask.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tasks/ImportTask.java
@@ -627,21 +627,29 @@
// and to take appropriate actions.
DirectoryServer.notifyImportBeginning(backend, importConfig);
- // Disable the backend.
+ /*
+ * From here the listeners must be told that the import is over whichever way this
+ * method returns: a listener which took something offline when it began - a
+ * replication domain disables itself - gets no other chance to put it back.
+ */
+ boolean backendDisabled = false;
+ boolean successful = false;
try
{
- TaskUtils.disableBackend(backend.getBackendID());
- }
- catch (DirectoryException e)
- {
- logger.traceException(e);
+ // Disable the backend.
+ try
+ {
+ TaskUtils.disableBackend(backend.getBackendID());
+ backendDisabled = true;
+ }
+ catch (DirectoryException e)
+ {
+ logger.traceException(e);
- logger.error(e.getMessageObject());
- return TaskState.STOPPED_BY_ERROR;
- }
+ logger.error(e.getMessageObject());
+ return TaskState.STOPPED_BY_ERROR;
+ }
- try
- {
// Acquire an exclusive lock for the backend.
try
{
@@ -665,12 +673,12 @@
try
{
backend.importLDIF(importConfig, DirectoryServer.getInstance().getServerContext());
+ successful = true;
}
catch (DirectoryException de)
{
logger.traceException(de);
- DirectoryServer.notifyImportEnded(backend, importConfig, false);
if (de.getResultCode().equals(DirectoryServer.getCoreConfigManager().getServerErrorResultCode()))
{
logger.error(ERR_LDIFIMPORT_ERROR_DURING_IMPORT.get(de.getMessageObject()));
@@ -685,7 +693,6 @@
{
logger.traceException(e);
- DirectoryServer.notifyImportEnded(backend, importConfig, false);
logger.error(ERR_LDIFIMPORT_ERROR_DURING_IMPORT, getExceptionMessage(e));
return TaskState.STOPPED_BY_ERROR;
}
@@ -713,23 +720,32 @@
}
finally
{
- // Enable the backend.
- try
+ // Enable the backend, if it was this task which disabled it.
+ boolean backendLeftDisabled = false;
+ if (backendDisabled)
{
- TaskUtils.enableBackend(backend.getBackendID());
- // It is necessary to retrieve the backend structure again
- // because disabling and enabling it again may have resulted
- // in a new backend being registered to the server.
- backend = getServerContext().getBackendConfigManager().getLocalBackendById(backend.getBackendID());
- }
- catch (DirectoryException e)
- {
- logger.traceException(e);
+ try
+ {
+ TaskUtils.enableBackend(backend.getBackendID());
+ // It is necessary to retrieve the backend structure again
+ // because disabling and enabling it again may have resulted
+ // in a new backend being registered to the server.
+ backend = getServerContext().getBackendConfigManager().getLocalBackendById(backend.getBackendID());
+ }
+ catch (DirectoryException e)
+ {
+ logger.traceException(e);
- logger.error(e.getMessageObject());
+ logger.error(e.getMessageObject());
+ backendLeftDisabled = true;
+ }
+ }
+ // Notified once, after the backend is back, so that a listener can read it again.
+ DirectoryServer.notifyImportEnded(backend, importConfig, successful);
+ if (backendLeftDisabled)
+ {
return TaskState.STOPPED_BY_ERROR;
}
- DirectoryServer.notifyImportEnded(backend, importConfig, true);
}
// Clean up after the import by closing the import config.
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tasks/RestoreTask.java b/opendj-server-legacy/src/main/java/org/opends/server/tasks/RestoreTask.java
index c75a6e2..8568d35 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tasks/RestoreTask.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tasks/RestoreTask.java
@@ -13,6 +13,7 @@
*
* Copyright 2006-2008 Sun Microsystems, Inc.
* Portions Copyright 2014-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.tasks;
@@ -262,25 +263,32 @@
// and to take appropriate actions.
DirectoryServer.notifyRestoreBeginning(backend, restoreConfig);
- // Disable the backend.
- if ( !verifyOnly)
- {
- try
- {
- TaskUtils.disableBackend(backendID);
- } catch (DirectoryException e)
- {
- logger.traceException(e);
-
- logger.error(e.getMessageObject());
- return TaskState.STOPPED_BY_ERROR;
- }
- }
-
- // From here we must make sure to re-enable the backend before returning.
+ /*
+ * From here the listeners must be told that the restore is over whichever way this
+ * method returns: a listener which took something offline when it began - a
+ * replication domain disables itself - gets no other chance to put it back.
+ */
+ boolean backendDisabled = false;
boolean errorsEncountered = false;
try
{
+ // Disable the backend. From here the finally below re-enables it before returning.
+ if ( !verifyOnly)
+ {
+ try
+ {
+ TaskUtils.disableBackend(backendID);
+ backendDisabled = true;
+ } catch (DirectoryException e)
+ {
+ logger.traceException(e);
+
+ logger.error(e.getMessageObject());
+ errorsEncountered = true;
+ return TaskState.STOPPED_BY_ERROR;
+ }
+ }
+
// Acquire an exclusive lock for the backend.
if (verifyOnly || lockBackend(backend))
{
@@ -294,13 +302,11 @@
}
catch (DirectoryException de)
{
- DirectoryServer.notifyRestoreEnded(backend, restoreConfig, false);
logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), de.getMessageObject());
errorsEncountered = true;
}
catch (Exception e)
{
- DirectoryServer.notifyRestoreEnded(backend, restoreConfig, false);
logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), getExceptionMessage(e));
errorsEncountered = true;
}
@@ -317,8 +323,8 @@
}
finally
{
- // Enable the backend.
- if (! verifyOnly)
+ // Enable the backend, if it was this task which disabled it.
+ if (backendDisabled)
{
try
{
@@ -335,7 +341,8 @@
errorsEncountered = true;
}
}
- DirectoryServer.notifyRestoreEnded(backend, restoreConfig, true);
+ // Notified once, after the backend is back, so that a listener can read it again.
+ DirectoryServer.notifyRestoreEnded(backend, restoreConfig, !errorsEncountered);
}
if (errorsEncountered)
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestImportAndExport.java b/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestImportAndExport.java
index 00467aa..f54a243 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestImportAndExport.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestImportAndExport.java
@@ -22,9 +22,11 @@
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.TestCaseUtils;
+import org.opends.server.api.LocalBackend;
import org.opends.server.api.TestTaskListener;
import org.opends.server.backends.task.TaskState;
import org.opends.server.core.AddOperation;
+import org.opends.server.core.BackendConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Entry;
import org.forgerock.opendj.ldap.schema.ObjectClass;
@@ -383,6 +385,83 @@
}
/**
+ * An import which cannot disable its backend must still tell the import task listeners
+ * that the import is over: a listener which took something offline when the import began
+ * - a replication domain disables itself - has no other chance to put it back.
+ */
+ @Test
+ public void testImportEndsWhenTheBackendCannotBeDisabled() throws Exception
+ {
+ /*
+ * A backend registered at runtime has no entry in cn=config, and disabling a backend
+ * is a modification of that entry, so TaskUtils.disableBackend() cannot do it.
+ */
+ final String backendID = "importTaskUnconfiguredBackend";
+ TestCaseUtils.initializeMemoryBackend(backendID, "dc=unconfigured,dc=com", true);
+ try
+ {
+ int importBeginCount = TestTaskListener.importBeginCount.get();
+ int importEndCount = TestTaskListener.importEndCount.get();
+
+ Entry taskEntry = TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-import",
+ "ds-task-class-name: org.opends.server.tasks.ImportTask",
+ "ds-task-import-backend-id: " + backendID,
+ "ds-task-import-ldif-file: " + ldifFile.getPath());
+
+ testTask(taskEntry, TaskState.STOPPED_BY_ERROR, 60);
+
+ assertEquals(TestTaskListener.importBeginCount.get(), importBeginCount + 1);
+ assertEquals(TestTaskListener.importEndCount.get(), importEndCount + 1);
+ }
+ finally
+ {
+ removeMemoryBackend(backendID);
+ }
+ }
+
+ /**
+ * A failed import must notify the listeners exactly once, as its beginning was notified
+ * once: a replication domain enabled a second time reloads and rewinds its replication
+ * state for nothing.
+ */
+ @Test
+ public void testFailedImportEndsOnlyOnce() throws Exception
+ {
+ int importBeginCount = TestTaskListener.importBeginCount.get();
+ int importEndCount = TestTaskListener.importEndCount.get();
+
+ // A directory can be read, so the task accepts it, but it cannot be read as LDIF.
+ Entry taskEntry = TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-import",
+ "ds-task-class-name: org.opends.server.tasks.ImportTask",
+ "ds-task-import-backend-id: userRoot",
+ "ds-task-import-ldif-file: " + ldifFile.getParent());
+
+ testTask(taskEntry, TaskState.STOPPED_BY_ERROR, 60);
+
+ assertEquals(TestTaskListener.importBeginCount.get(), importBeginCount + 1);
+ assertEquals(TestTaskListener.importEndCount.get(), importEndCount + 1);
+ }
+
+ private void removeMemoryBackend(String backendID) throws Exception
+ {
+ BackendConfigManager backendConfigManager = TestCaseUtils.getServerContext().getBackendConfigManager();
+ LocalBackend<?> backend = backendConfigManager.getLocalBackendById(backendID);
+ if (backend != null)
+ {
+ backend.finalizeBackend();
+ backendConfigManager.deregisterLocalBackend(backend);
+ }
+ }
+
+ /**
* Add a task definition and check that it completes with the expected state.
* @param taskEntry The task entry.
* @param resultCode The expected result code of the task add.
--
Gitblit v1.10.0