[#1025] Stop a restore which cannot lock its backend instead of completing it - #1027
Conversation
…end instead of completing it RestoreTask guarded the restore with `if (verifyOnly || lockBackend(backend))` and had no else branch: when the backend lock was refused the whole restore block was skipped, errorsEncountered stayed false, the backend was re-enabled, the listeners were told the restore succeeded and the task ended in COMPLETED_SUCCESSFULLY - the restore CLI exited 0 right after printing the lock error. A refused lock now ends the task in STOPPED_BY_ERROR through the finally which re-enables the backend and reports the failure to the listeners, the way BackupTask and ImportTask already treat it. Fixes OpenIdentityPlatform#1025.
ad67ade to
3e600a7
Compare
|
@maximthomas rebased onto |
maximthomas
left a comment
There was a problem hiding this comment.
praise: The change is exactly where the bug is and no wider.
RestoreTask.runTask:294-298— the refused lock now takes the roadBackupTask/ImportTaskand thedisableBackendcatch at:283-289already take:errorsEncountered = true; return TaskState.STOPPED_BY_ERROR, through thefinallywhich re-enables the backend and callsnotifyRestoreEnded(..., false).git diff -wshows +7/−3 inRestoreTask.java; the rest is de-indentation, as the description says.TestBackupAndRestore.testRestoreEndsInErrorWhenTheBackendCannotBeLocked:266-277— the shared lock is taken and released in atry/finallyaround the task, so the refcountLockFileManagerkeeps foruserRootis balanced for the classes after it in the same JVM.- CI run 34607225854 is the head's (
headSha 3e600a73); the five ubuntu failsafe cells are green.
suggestion (non-blocking): The !verifyOnly && arm of the rewritten guard is unpinned — no test in the repo schedules a verify-only restore.
opendj-server-legacy/src/main/java/org/opends/server/tasks/RestoreTask.java:294
git grep -i 'restore-verify-only\|verifyOnly' 3e600a73 outside src/main hits only the schema; every restore a test schedules has verifyOnly=false, on which !verifyOnly && !lockBackend(backend) and the mutant !lockBackend(backend) behave identically, so the mutant survives the whole suite by construction. Under it an online restore --verifyOnly skips disableBackend (:276), the enabled backend keeps the shared lock BackendConfigManager took on enable, LockFileManager.acquireExclusiveLock:198 refuses it, and every verify-only restore ends STOPPED_BY_ERROR. The gap predates this PR (verifyOnly || lockBackend(backend) was as unpinned), but the description's "restore --verifyOnly is unaffected: it never takes the lock" now rests on reading alone.
/** A verify-only restore never takes the backend lock: the backend stays enabled and holds its own shared lock. */
@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);
}
}Pin: with the mutant if (!lockBackend(backend)) the enabled backend's own shared lock refuses the exclusive one and the case fails with expected [COMPLETED_SUCCESSFULLY] but found [STOPPED_BY_ERROR].
nitpick (non-blocking): The javadoc says the refused-lock road ends "exactly as when the restore itself fails"; the task state differs between the two roads.
opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java:220-223
A refused lock returns STOPPED_BY_ERROR (RestoreTask.java:294-298); a throwing restoreBackup sets errorsEncountered without returning (:306-317) and the task ends COMPLETED_WITH_ERRORS (:350-352). Only the listener call, notifyRestoreEnded(..., false), is the same on both roads, and the test body itself expects the state the restore-failure road never yields.
/**
* 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.
*/…fused backend lock takes The guard reads `!verifyOnly && !lockBackend(backend)`, but no test in the repo schedules a verify-only restore: on every restore a test does schedule the mutant `!lockBackend(backend)` behaves identically, so that arm was carried by reading alone. testVerifyOnlyRestoreDoesNotTakeTheBackendLock backs userRoot up into a temporary directory and verifies that backup with `ds-task-restore-verify-only: true`. A verify-only restore leaves the backend enabled, so the backend keeps the shared lock BackendConfigManager took when it was enabled and an exclusive lock request is refused over it: under the mutant the case ends `expected [COMPLETED_SUCCESSFULLY] but found [STOPPED_BY_ERROR]`. The javadoc of the refused-lock test also named an end the other road never reaches: a refused lock returns STOPPED_BY_ERROR, a throwing restoreBackup ends COMPLETED_WITH_ERRORS, and only the notifyRestoreEnded(..., false) call is common to both.
|
Both points taken, round 2 is suggestion — the Measured, not read:
Run B also shows what your point says: The case carries a two-line javadoc rather than your one-liner, naming why the exclusive lock would nitpick — "exactly as when the restore itself fails". Correct, the two roads end differently: a Run on the round's head: |
maximthomas
left a comment
There was a problem hiding this comment.
praise: Both claims of the description are now measured, not read.
TestBackupAndRestore.testVerifyOnlyRestoreDoesNotTakeTheBackendLock:298-322kills the acquire-arm mutant: withif (!lockBackend(backend))atRestoreTask.runTask:294the case is red withexpected [COMPLETED_SUCCESSFULLY] but found [STOPPED_BY_ERROR]and the other 13 cases stay green — the table in the description, reproduced (14 ran, 1 failed).testRestoreEndsInErrorWhenTheBackendCannotBeLocked:271is red withRestoreTask.javaat the basef559b090and everything else at the head:expected [STOPPED_BY_ERROR] but found [COMPLETED_SUCCESSFULLY], 14 ran, 1 failed — round 1's open item, closed by a run.RestoreTask.javais unchanged since3e600a73; the head's CI is green on every cell, the five ubuntu failsafe cells included.
Fixes #1025.
Rebased onto
masterafter #969 (e86f702f8e) landed; the branch is the single commit3e600a73a1, its content unchanged from the originalad67ade1e9(same patch-id).The bug
RestoreTask.runTask()guarded the restore withif (verifyOnly || lockBackend(backend))and had noelse. When the backend lock was refused - another process holds it, or the lock file cannot be created - the whole restore block was skipped,errorsEncounteredstayedfalse, thefinallyre-enabled the backend and told the listeners the restore succeeded, and the task returnedgetFinalTaskState(), i.e.COMPLETED_SUCCESSFULLY. TherestoreCLI printed theERR_RESTOREDB_CANNOT_LOCK_BACKENDline from the task log and exited 0 right after it.The change
A refused lock ends the task the way
BackupTaskandImportTaskalready end theirs, and the way #969 already ends a faileddisableBackend():The return passes through the
finally, which re-enables the backend and notifiesprocessRestoreEnd(..., false). The rest of theRestoreTaskhunk is the de-indentation of the formerifbody -?w=1shows the five real lines.restore --verifyOnlyis unaffected: it never takes the lock, pinned by the second test below.Test
TestBackupAndRestore.testRestoreEndsInErrorWhenTheBackendCannotBeLocked- self-contained: it backsuserRootup into a temporary directory, takes a shared lock on the backend's lock file and restores from that backup.LockFileManagerreference-counts shared locks, so the task'sdisableBackend()releases the backend's own reference but not the test's, and the exclusive lock the restore needs is refused withERR_FILELOCKER_LOCK_EXCLUSIVE_REJECTED_BY_SHARED. Asserted:STOPPED_BY_ERROR, one begin and one end notification,successful == falseat the end notification, anduserRootregistered again afterwards.Before the change the test fails with
expected [STOPPED_BY_ERROR] but found [COMPLETED_SUCCESSFULLY], the task log carrying the refused-lock error. It also runs the restore half of #969'sfinally- thebackendDisabledre-enable and the!errorsEncounterednotification - which the review of #969 noted no test executed.TestBackupAndRestore.testVerifyOnlyRestoreDoesNotTakeTheBackendLock- added in review round 2, pins the!verifyOnlyarm of the guard: it backsuserRootup and verifies that backup withds-task-restore-verify-only: true. A verify-only restore never disables the backend, so the backend keeps the shared lockBackendConfigManagertook when it was enabled and an exclusive lock request is refused over it. With the mutantif (!lockBackend(backend))the case fails withexpected [COMPLETED_SUCCESSFULLY] but found [STOPPED_BY_ERROR], whiletestRestoreEndsInErrorWhenTheBackendCannotBeLockedstays green under that same mutant - which is why the arm needed a case of its own.Run:
TestBackupAndRestore14/14,TestImportAndExport14/14,PrivilegeTestCase185/185,ReSyncTest2/2,LDIFBackendTestCase22/22.