mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
2 days ago 80481f756d71bd58b4bda627758dcd774e9d5dcd
opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ReplayDuringImportTest.java
@@ -50,6 +50,7 @@
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.types.Entry;
import org.opends.server.types.OperationType;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -66,7 +67,9 @@
 * over that session, so a replay which fails while it is on its way must not restart it.
 * A restart asked for before the total update took the session, and left standing for the
 * length of it, is not run once it is over either: the change it was asked for is gone with
 * the ServerState the import replaced.
 * the ServerState the import replaced. The changes a replay which is unwound had parked as
 * waiting for another one are released on the same terms, and nothing more is done for them
 * (issue #954).
 * <p>
 * The exporter is a broker of this test, so that the test says when the entries arrive: the
 * change is replayed while the import is waiting for them - or, for the request, while the
@@ -293,6 +296,115 @@
  }
  /**
   * The changes a replay which is unwound had parked as waiting for another change are
   * released and nothing more while a total update owns the session (issue #954): no
   * session restart is asked for them - the one it would ask for is refused where it runs,
   * and the request would be spent on it - and they are neither reported as changes the
   * replication server sends again, which it does not before the import has replaced the
   * data, nor counted as processed. That is the road a change a stopping replay thread
   * abandons takes on this domain, and the give-back of the parked changes takes it too.
   * <p>
   * Pinned on the import road because it is the one road with an owner which a test holds
   * open for as long as it needs: the request is on its way until the exporter answers it,
   * and the backend is live meanwhile, so the change which is parked and the replay which
   * is unwound run as they would on any domain. The domain going away, or being disabled,
   * forgets its pending changes a moment after it takes the session and clears every
   * request and every count on its way, so a give-back on that road is a race with the
   * forgetting and leaves nothing to read.
   * <p>
   * The replay is unwound on the thread of this test - it applied its change, and the ack
   * of its delivery runs out of memory - so the parked change is this thread's to give
   * back, and the error which ends a replay thread is caught here instead.
   */
  @Test(timeOut = 120_000)
  public void aParkedChangeGivenBackWhileTheRequestIsOnItsWayIsNotAskedForAgain() throws Exception
  {
    final Entry entry = TestCaseUtils.addEntry(
        "dn: cn=renamedSince," + EXAMPLE_DN,
        "objectClass: top",
        "objectClass: person",
        "cn: renamedSince",
        "sn: renamedSince");
    final String entryUUID = getEntryUUID(entry.getName());
    final String[] exported = exportedEntries();
    // The request is out, and the exporter holds it until the give-back below has run.
    domain.initializeFromRemote(EXPORTER_ID, null);
    assertNotNull(waitForSpecificMsg(exporter, InitializeRequestMsg.class));
    /*
     * The barrier: a change whose replay fails stays listed and uncommitted - the attempts
     * in place end on an entryUUID search which does not run, the way they do in the case
     * above - and stays among the changes the newer ones are checked against, so a change
     * which follows it on the same entry has to wait for it. The restart which would have
     * followed is refused, the total update owning the session, and the search is let
     * through again before anything below reads a monitor.
     */
    final DN movedAway = DN.valueOf("cn=movedAway," + EXAMPLE_DN);
    final CSN failing = gen.newCSN();
    ShortCircuitPlugin.registerShortCircuit(
        OperationType.SEARCH, "PreParse", ResultCode.UNAVAILABLE.intValue());
    try
    {
      replayMsg(new ModifyMsg(failing, movedAway,
          generatemods("description", "the replay of this change fails"), entryUUID));
    }
    finally
    {
      ShortCircuitPlugin.deregisterShortCircuit(OperationType.SEARCH, "PreParse");
    }
    assertFalse(domain.getServerState().cover(failing),
        "the change whose replay fails must stay listed as one which is not in the data");
    // Parked as waiting for it by this thread, which owns it from here on.
    final CSN parked = gen.newCSN();
    replayMsg(new ModifyMsg(parked, movedAway,
        generatemods("description", "the change which was parked as a dependency"), entryUUID));
    assertEquals(getMonitorAttrValue(baseDN, "dependent-changes-size"), 1,
        "a change which waits for one that is not in the data must be parked");
    /*
     * The replay which is unwound while this thread still holds the parked change: its own
     * change is applied and committed, so the give-back on the way out finds the parked
     * change alone. The count is read once the parked change is listed, since a parked
     * change publishes no ack and is not counted until the delivery which replays it is.
     */
    final long processed = getMonitorAttrValue(baseDN, "replayed-updates");
    final CSN unwound = gen.newCSN();
    try
    {
      replayMsg(new ModifyMsgWhoseAckRunsOutOfMemoryOnceApplied(unwound, entry.getName(),
          generatemods("description", "the replay of this change is unwound once it is applied"),
          entryUUID));
      Assert.fail("the replay was not unwound: the ack of the delivery must run out of memory");
    }
    catch (OutOfMemoryError unwinding)
    {
      // The error is the fixture's own, and this is the thread it would have ended.
    }
    assertEquals(getMonitorAttrValue(baseDN, "dependent-changes-size"), 0,
        "the change parked by the replay which was unwound must be given back");
    assertEquals(getMonitorAttrValue(baseDN, "replayed-updates"), processed,
        "a change released while a total update owns the session must not be counted as"
            + " processed: no session sends it again before the import has replaced the data");
    assertThat(errorLogRecordsOf(NOTE_REPLAY_PARKED_CHANGE_GIVEN_BACK.ordinal(), parked))
        .as("the change was reported as one the replication server sends again, which it does"
            + " not before the import has replaced the data")
        .isEmpty();
    assertTrue(domain.isConnected(), "the session the answer to the request arrives over was stopped");
    answerImportRequest(exported.length);
    finishImport(exported);
    for (String ldif : exported)
    {
      final DN dn = dnOf(ldif);
      assertTrue(entryExists(dn), "the import ended before " + dn
          + " arrived: the answer to the request was lost with the session it was made over");
    }
  }
  /**
   * A session restart which stood while the import ran was asked for by a replay thread
   * for a change given back before the total update owned the session, and that change is
   * forgotten with the pending changes when the imported data replaces the ServerState: