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

gbellato
15.38.2009 75b143a0bff605ca1d48f1fa9571386f95eb0588
Fix for issue 3683

When replication replays a DELETE on an entry that has children,
the replication conflict resolution code incorrectly assumes that
this is a conflict.
This is not correct because it is also possible that the delete
was actually a subtree delete.

This can be fixed by checking the creation date of the child entry.
If it was created before the delete then this is not a conflict and the
delete must be replayed as a subtree delete. : Subtree delete only deletes parent on replica
4 files modified
119 ■■■■■ changed files
opends/src/server/org/opends/server/replication/plugin/Historical.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java 33 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java 36 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/ReplicationDomainTest.java 28 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/Historical.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
@@ -433,6 +433,26 @@
    return builder.toAttribute();
  }
  /**
   * Indicates if the Entry was renamed or added after the ChangeNumber
   * that is given as a parameter.
   *
   * @param cn The ChangeNumber with which the ADD or Rename date must be
   *           compared.
   *
   * @return A boolean indicating if the Entry was renamed or added after
   *                   the ChangeNumber that is given as a parameter.
   */
  public boolean AddedOrRenamedAfter(ChangeNumber cn)
  {
    if (cn.older(ADDDate) || cn.older(MODDNDate))
    {
      return true;
    }
    else
      return false;
  }
  /**
   * read the historical information from the entry attribute and
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -64,6 +64,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.CheckedOutputStream;
import java.util.zip.DataFormatException;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.admin.server.ConfigurationChangeListener;
@@ -75,6 +76,7 @@
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.backends.jeb.BackendImpl;
import org.opends.server.config.ConfigException;
import org.opends.server.controls.SubtreeDeleteControl;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
@@ -1069,6 +1071,8 @@
               * different operation.
               */
              op = msg.createOperation(conn);
              if (op instanceof DeleteOperation)
                op.addRequestControl(new SubtreeDeleteControl());
            }
          }
          else
@@ -1407,9 +1411,11 @@
      * The action taken here must be consistent with the actions
      * done in the solveNamingConflict(AddOperation) method
      * when we are adding an entry whose parent entry has already been deleted.
      *
      */
     findAndRenameChild(entryUid, op.getEntryDN(), op);
     if (findAndRenameChild(entryUid, op.getEntryDN(), op))
     numUnresolvedNamingConflicts.incrementAndGet();
     return false;
   }
   else
@@ -1652,17 +1658,25 @@
   * @param entryDN    The DN of the entry whose child must be renamed.
   * @param conflictOp The Operation that generated the conflict.
   */
  private void findAndRenameChild(
  private boolean findAndRenameChild(
      String entryUid, DN entryDN, Operation conflictOp)
  {
    boolean conflict = false;
    // Find an rename child entries.
    InternalClientConnection conn =
      InternalClientConnection.getRootConnection();
    DeleteContext ctx =
      (DeleteContext) conflictOp.getAttachment(SYNCHROCONTEXT);
    ChangeNumber cn = null;
    if (ctx != null)
      cn = ctx.getChangeNumber();
    try
    {
      LinkedHashSet<String> attrs = new LinkedHashSet<String>(1);
      attrs.add(ENTRYUIDNAME);
      attrs.add(Historical.HISTORICALATTRIBUTENAME);
      SearchFilter ALLMATCH;
      ALLMATCH = SearchFilter.createFilterFromString("(objectClass=*)");
@@ -1678,12 +1692,25 @@
        {
          for (SearchResultEntry entry : entries)
          {
            /*
             * Check the ADD and ModRDN date of the child entry. If it is after
             * the delete date then keep the entry as a conflicting entry,
             * otherwise delete the entry with the operation.
             */
            if (cn != null)
            {
              Historical hist = Historical.load(entry);
              if (hist.AddedOrRenamedAfter(cn))
              {
                conflict = true;
            markConflictEntry(conflictOp, entry.getDN(), entryDN);
            renameConflictEntry(conflictOp, entry.getDN(),
                                Historical.getEntryUuid(entry));
          }
        }
      }
        }
      }
      else
      {
        // log error and information for the REPAIR tool.
@@ -1708,6 +1735,8 @@
      mb.append(e.getLocalizedMessage());
      logError(mb.toMessage());
    }
    return conflict;
  }
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 */
package org.opends.server.replication;
@@ -1120,6 +1120,8 @@
    // add domain1 entry with 2 children : domain2 and domain3
    addEntry(domain1);
    ChangeNumber olderCn = gen.newChangeNumber();
    Thread.sleep(1000);
    domain1uid = getEntryUUID(DN.decode(domain1dn));
    addEntry(domain2);
    domain2uid = getEntryUUID(DN.decode(domain2dn));
@@ -1134,7 +1136,7 @@
    AlertCount = DummyAlertHandler.getAlertCount();
    // delete domain1
    delMsg = new DeleteMsg(domain1dn, gen.newChangeNumber(), domain1uid);
    delMsg = new DeleteMsg(domain1dn, olderCn, domain1uid);
    broker.publish(delMsg);
    // check that the domain1 has correctly been deleted
@@ -1167,6 +1169,36 @@
    delEntry(conflictDomain3dn);
    //
    // Check that when a delete is replayed over an entry which has child
    // those child are also deleted
    //
    // add domain1 entry with 2 children : domain2 and domain3
    addEntry(domain1);
    domain1uid = getEntryUUID(DN.decode(domain1dn));
    addEntry(domain2);
    domain2uid = getEntryUUID(DN.decode(domain2dn));
    addEntry(domain3);
    domain3uid = getEntryUUID(DN.decode(domain3dn));
    updateMonitorCount(baseDn, unresolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    // delete domain1
    delMsg = new DeleteMsg(domain1dn, gen.newChangeNumber(), domain1uid);
    broker.publish(delMsg);
    // check that the domain1 has correctly been deleted
    assertNull(getEntry(DN.decode(domain1dn), 10000, false),
        "The DELETE replication message was not replayed");
    // check that domain2 and domain3 have not been renamed as conflicting
    assertNull(getEntry(conflictDomain2dn, 1000, true),
        "The conflicting entries were not created");
    assertNull(getEntry(conflictDomain3dn, 1000, true),
        "The conflicting entries were not created");
    //
    // Check that when an entry is added on one master below an entry
    // that is currently deleted on another master, the replay of the
    // add on the second master cause the added entry to be renamed
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/ReplicationDomainTest.java
@@ -41,6 +41,7 @@
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.DSInfo;
import org.opends.server.replication.common.RSInfo;
import org.opends.server.replication.common.ServerStatus;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.replication.server.ReplServerFakeConfiguration;
import org.opends.server.replication.server.ReplicationServer;
@@ -128,6 +129,11 @@
        assertTrue(replServerInfo.getGenerationId() == 1);
      }
      for (DSInfo serverInfo : domain1.getDsList())
      {
        assertTrue(serverInfo.getStatus() == ServerStatus.NORMAL_STATUS);
      }
      domain1.setGenerationID(2);
      domain1.resetReplicationLog();
@@ -138,6 +144,28 @@
        // The generation Id of the remote should now be 2
        assertTrue(replServerInfo.getGenerationId() == 2);
      }
      for (DSInfo serverInfo : domain1.getDsList())
      {
        if (serverInfo.getDsId() == 2)
          assertTrue(serverInfo.getStatus() == ServerStatus.BAD_GEN_ID_STATUS);
        else
        {
          assertTrue(serverInfo.getDsId() == 1);
          assertTrue(serverInfo.getStatus() == ServerStatus.NORMAL_STATUS);
        }
      }
      for (DSInfo serverInfo : domain2.getDsList())
      {
        if (serverInfo.getDsId() == 2)
          assertTrue(serverInfo.getStatus() == ServerStatus.BAD_GEN_ID_STATUS);
        else
        {
          assertTrue(serverInfo.getDsId() == 1);
          assertTrue(serverInfo.getStatus() == ServerStatus.NORMAL_STATUS);
        }
      }
    }
    finally
    {