From 7313588e280915ed5ce879761e16ad398488f54c Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 22 Sep 2026 09:22:17 +0000
Subject: [PATCH] [#1025] Stop a restore which cannot lock its backend instead of completing it (#1027)
---
opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java | 110 ++++++++++++++++++++++++++++++++++++
opendj-server-legacy/src/main/java/org/opends/server/tasks/RestoreTask.java | 52 +++++++++--------
2 files changed, 138 insertions(+), 24 deletions(-)
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 8568d35..e678e1d 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
@@ -289,35 +289,39 @@
}
}
- // Acquire an exclusive lock for the backend.
- if (verifyOnly || lockBackend(backend))
+ // Acquire an exclusive lock for the backend. Without it nothing is restored, and the
+ // task must say so rather than complete as if it had.
+ if (!verifyOnly && !lockBackend(backend))
{
- // From here we must make sure to release the backend exclusive lock.
+ errorsEncountered = true;
+ return TaskState.STOPPED_BY_ERROR;
+ }
+
+ // From here we must make sure to release the backend exclusive lock.
+ try
+ {
+ // Perform the restore.
try
{
- // Perform the restore.
- try
- {
- backend.restoreBackup(restoreConfig);
- }
- catch (DirectoryException de)
- {
- logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), de.getMessageObject());
- errorsEncountered = true;
- }
- catch (Exception e)
- {
- logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), getExceptionMessage(e));
- errorsEncountered = true;
- }
+ backend.restoreBackup(restoreConfig);
}
- finally
+ catch (DirectoryException de)
{
- // Release the exclusive lock on the backend.
- if (!verifyOnly && !unlockBackend(backend))
- {
- errorsEncountered = true;
- }
+ logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), de.getMessageObject());
+ errorsEncountered = true;
+ }
+ catch (Exception e)
+ {
+ logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), getExceptionMessage(e));
+ errorsEncountered = true;
+ }
+ }
+ finally
+ {
+ // Release the exclusive lock on the backend.
+ if (!verifyOnly && !unlockBackend(backend))
+ {
+ errorsEncountered = true;
}
}
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java b/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java
index cf64d63..9fe8b2f 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.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;
@@ -20,10 +21,16 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
import org.opends.server.TestCaseUtils;
+import org.opends.server.api.LocalBackend;
+import org.opends.server.api.RestoreTaskListener;
import org.opends.server.backends.task.TaskState;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.LockFileManager;
import org.opends.server.types.Entry;
+import org.opends.server.types.RestoreConfig;
import org.forgerock.opendj.ldap.schema.ObjectClass;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
@@ -210,4 +217,107 @@
}
}
+ /**
+ * A restore which cannot lock its backend has restored nothing and must say so: the task
+ * ends STOPPED_BY_ERROR and the restore task listeners are told the restore failed, as they
+ * are when the restore itself fails.
+ */
+ @Test
+ public void testRestoreEndsInErrorWhenTheBackendCannotBeLocked() throws Exception
+ {
+ File backupDirectory = TestCaseUtils.createTemporaryDirectory("restore-lock");
+ try
+ {
+ testTask(TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-backup",
+ "ds-task-class-name: org.opends.server.tasks.BackupTask",
+ "ds-task-backup-backend-id: userRoot",
+ "ds-backup-directory-path: " + backupDirectory.getPath()),
+ TaskState.COMPLETED_SUCCESSFULLY, 30);
+
+ final AtomicReference<Boolean> restoreSuccessful = new AtomicReference<>();
+ RestoreTaskListener outcome = new RestoreTaskListener()
+ {
+ @Override
+ public void processRestoreBegin(LocalBackend<?> backend, RestoreConfig config)
+ {
+ }
+
+ @Override
+ public void processRestoreEnd(LocalBackend<?> backend, RestoreConfig config, boolean successful)
+ {
+ restoreSuccessful.set(successful);
+ }
+ };
+
+ final int restoreBeginCountStart = restoreBeginCount.get();
+ final int restoreEndCountStart = restoreEndCount.get();
+
+ /*
+ * Hold the backend lock as another shared holder would: the task takes the backend
+ * offline, which releases the backend's own reference but not this one, so the
+ * exclusive lock it needs to restore is refused.
+ */
+ LocalBackend<?> userRoot = TestCaseUtils.getServerContext().getBackendConfigManager().getLocalBackendById("userRoot");
+ String lockFile = LockFileManager.getBackendLockFileName(userRoot);
+ StringBuilder failureReason = new StringBuilder();
+ assertTrue(LockFileManager.acquireSharedLock(lockFile, failureReason), failureReason.toString());
+ DirectoryServer.registerRestoreTaskListener(outcome);
+ try
+ {
+ testTask(TestCaseUtils.makeEntry(restoreTask(
+ "ds-backup-directory-path: " + backupDirectory.getPath())),
+ TaskState.STOPPED_BY_ERROR, 30);
+ }
+ finally
+ {
+ DirectoryServer.deregisterRestoreTaskListener(outcome);
+ LockFileManager.releaseLock(lockFile, new StringBuilder());
+ }
+
+ assertEquals(restoreBeginCount.get(), restoreBeginCountStart + 1);
+ assertEquals(restoreEndCount.get(), restoreEndCountStart + 1);
+ assertEquals(restoreSuccessful.get(), Boolean.FALSE);
+ // The backend the task took offline is back.
+ assertNotNull(TestCaseUtils.getServerContext().getBackendConfigManager().getLocalBackendById("userRoot"));
+ }
+ finally
+ {
+ TestCaseUtils.deleteDirectory(backupDirectory);
+ }
+ }
+
+ /**
+ * A verify-only restore never takes the backend lock: the backend stays enabled and keeps the
+ * shared lock it was given when it was enabled, which an exclusive lock request is refused over.
+ */
+ @Test
+ public void testVerifyOnlyRestoreDoesNotTakeTheBackendLock() throws Exception
+ {
+ File backupDirectory = TestCaseUtils.createTemporaryDirectory("restore-verify-only");
+ try
+ {
+ testTask(TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-backup",
+ "ds-task-class-name: org.opends.server.tasks.BackupTask",
+ "ds-task-backup-backend-id: userRoot",
+ "ds-backup-directory-path: " + backupDirectory.getPath()),
+ TaskState.COMPLETED_SUCCESSFULLY, 30);
+
+ testTask(TestCaseUtils.makeEntry(restoreTask(
+ "ds-backup-directory-path: " + backupDirectory.getPath(),
+ "ds-task-restore-verify-only: true")),
+ TaskState.COMPLETED_SUCCESSFULLY, 30);
+ }
+ finally
+ {
+ TestCaseUtils.deleteDirectory(backupDirectory);
+ }
+ }
}
--
Gitblit v1.10.0