From 06e91a18f48ddf1cae6cbc9946688dea6a161425 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Sat, 11 Jul 2026 10:36:01 +0000
Subject: [PATCH] [#730] Fix import/export context leak on failed initializeRemote validation (#731)

---
 opendj-server-legacy/src/test/java/org/opends/server/replication/InitOnLineTest.java |   72 +++++++++++++++++++++++++++++++++++
 1 files changed, 71 insertions(+), 1 deletions(-)

diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/InitOnLineTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/InitOnLineTest.java
index e2d0836..64f1695 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/replication/InitOnLineTest.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/InitOnLineTest.java
@@ -656,6 +656,7 @@
       }
 
       // Launch in S1 the task that will initialize S2
+      waitForRemoteReplicas(server2ID);
       addTask(taskInitTargetS2, ResultCode.SUCCESS, null);
 
       // Signal RS we just entered the full update status
@@ -714,6 +715,7 @@
       }
 
       // Launch in S1 the task that will initialize S2
+      waitForRemoteReplicas(server2ID, server3ID);
       addTask(taskInitTargetAll, ResultCode.SUCCESS, null);
 
       // Tests that entries have been received by S2
@@ -1004,6 +1006,7 @@
 
       // Launch in S1 the task that will initialize S2
       log(testCase + " add task " + Thread.currentThread());
+      waitForRemoteReplicas(server2ID);
       addTask(taskInitTargetS2, ResultCode.SUCCESS, null);
 
       log(testCase + " " + server2.getServerId() + " wait target " + Thread.currentThread());
@@ -1027,6 +1030,47 @@
     }
   }
 
+  /**
+   * Tests that an InitializeTarget task failing its validation (the remote
+   * replica is unknown to the domain) does not leave the import/export
+   * context acquired (issue #730): the domain used to reject every subsequent
+   * total update as a simultaneous import/export.
+   */
+  @Test(enabled=true)
+  public void initializeTargetUnknownRemote() throws Exception
+  {
+    String testCase = "initializeTargetUnknownRemote";
+    log("Starting " + testCase);
+    try
+    {
+      replServer1 = createReplicationServer(replServer1ID, testCase);
+      connectServer1ToReplServer(replServer1ID);
+      addTestEntriesToDB();
+
+      // DS(42424) is unknown in the topology: the task must fail to start...
+      Entry taskInitTargetUnknown = TestCaseUtils.makeEntry(
+          "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+          "objectclass: top",
+          "objectclass: ds-task",
+          "objectclass: ds-task-initialize-remote-replica",
+          "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask",
+          "ds-task-initialize-domain-dn: " + EXAMPLE_DN,
+          "ds-task-initialize-replica-server-id: " + 42424);
+      addTask(taskInitTargetUnknown, ResultCode.SUCCESS, null);
+      waitTaskState(taskInitTargetUnknown, STOPPED_BY_ERROR, 20000, null);
+
+      // ...but must not leave the import/export context acquired
+      assertFalse(replDomain.ieRunning(),
+          "ReplicationDomain: Import/Export is not expected to be running after a failed InitializeTarget");
+
+      log("Successfully ending " + testCase);
+    }
+    finally
+    {
+      afterTest(testCase);
+    }
+  }
+
   private void waitForInitializeTargetMsg(String testCase,
       ReplicationBroker server) throws Exception
   {
@@ -1293,9 +1337,30 @@
    * Disconnect broker and remove entries from the local DB
    * @param testCase The name of the test case.
    */
+  /**
+   * Waits until the local replication domain sees the provided replicas in
+   * its topology view, so that an InitializeTarget task does not race the
+   * topology propagation and fail to start with "the remote directory server
+   * DS(x) is unknown" - which, combined with the import/export context leak
+   * (issue #730), used to poison the domain for the rest of the test class.
+   */
+  private void waitForRemoteReplicas(Integer... serverIds) throws Exception
+  {
+    for (int serverId : serverIds)
+    {
+      for (int i = 0; i < 100 && !replDomain.getReplicaInfos().containsKey(serverId); i++)
+      {
+        sleep(100);
+      }
+      assertTrue(replDomain.getReplicaInfos().containsKey(serverId),
+          "DS(" + serverId + ") is not known to the local replication domain");
+    }
+  }
+
   private void afterTest(String testCase) throws Exception
   {
     // Check that the domain has completed the import/export task.
+    boolean ieStillRunning = false;
     if (replDomain != null)
     {
       // race condition could cause the main thread to reach
@@ -1309,7 +1374,10 @@
         }
         sleep(500);
       }
-      assertFalse(replDomain.ieRunning(), "ReplicationDomain: Import/Export is not expected to be running");
+      // asserted only after the cleanup below: failing before it would leak
+      // the domain config, the brokers and the replication servers into the
+      // following tests and cascade the failure over the whole class
+      ieStillRunning = replDomain.ieRunning();
     }
     // Remove domain config
     super.cleanConfigEntries();
@@ -1330,6 +1398,8 @@
 
     Arrays.fill(replServerPort, 0);
     log("Successfully cleaned " + testCase);
+
+    assertFalse(ieStillRunning, "ReplicationDomain: Import/Export is not expected to be running");
   }
 
   /**

--
Gitblit v1.10.0