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

ludovicp
07.55.2010 018234e6ccd27c399d0d1aa17981c8b4f7abbc39
Fix issue #3404. Conflicting entries are now indexed by ds-sync-conflict attribute. This index is only updated when conflicts are detected and searched on for all deletes and modDN operations. Tested successfully against performance regression.
7 files modified
384 ■■■■■ changed files
opends/resource/config/config.ldif 6 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 3 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/LocalDBBackendConfiguration.xml 8 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/replication.properties 2 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/Historical.java 21 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java 163 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/NamingConflictTest.java 181 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -223,6 +223,12 @@
ds-cfg-attribute: ds-sync-hist
ds-cfg-index-type: ordering
dn: ds-cfg-attribute=ds-sync-conflict,cn=Index,ds-cfg-backend-id=userRoot,cn=Backends,cn=config
objectClass: top
objectClass: ds-cfg-local-db-index
ds-cfg-attribute: ds-sync-conflict
ds-cfg-index-type: equality
dn: ds-cfg-attribute=entryUUID,cn=Index,ds-cfg-backend-id=userRoot,cn=Backends,cn=config
objectClass: top
objectClass: ds-cfg-local-db-index
opends/resource/schema/02-config.ldif
@@ -1552,7 +1552,8 @@
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.317
  NAME 'ds-sync-conflict'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  EQUALITY distinguishedNameMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  USAGE directoryOperation
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.319
opends/src/admin/defn/org/opends/server/admin/std/LocalDBBackendConfiguration.xml
@@ -84,6 +84,14 @@
          <adm:value>ds-sync-hist</adm:value>
        </adm:property>
      </adm:default-managed-object>
      <adm:default-managed-object name="ds-sync-conflict">
        <adm:property name="index-type">
          <adm:value>equality</adm:value>
        </adm:property>
        <adm:property name="attribute">
          <adm:value>ds-sync-conflict</adm:value>
        </adm:property>
      </adm:default-managed-object>
    </adm:one-to-many>
    <adm:profile name="ldap">
      <ldap:rdn-sequence>cn=Index</ldap:rdn-sequence>
opends/src/messages/messages/replication.properties
@@ -495,3 +495,5 @@
 translate RUV into state for suffix %s
SEVERE_ERR_RSQUEUE_DIFFERENT_MSGS_WITH_SAME_CN_201=Processing two different \
 changes with same changeNumber=%s. Previous msg=<%s>, New msg=<%s>
SEVERE_ERR_COULD_NOT_SOLVE_CONFLICT_202=Error while trying to solve conflict \
 with DN : %s ERROR : %s
opends/src/server/org/opends/server/replication/plugin/Historical.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
@@ -449,6 +449,25 @@
  /**
   * Returns the lastChangeNumber when the entry DN was modified.
   *
   * @return The lastChangeNumber when the entry DN was modified.
   */
  public ChangeNumber getDNDate()
  {
    if (ADDDate == null)
      return MODDNDate;
    if (MODDNDate == null)
      return ADDDate;
    if (MODDNDate.older(ADDDate))
      return MODDNDate;
    else
      return ADDDate;
  }
  /**
   * read the historical information from the entry attribute and
   * load it into the Historical object attached to the entry.
   * @param entry The entry which historical information must be loaded
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -2411,6 +2411,148 @@
        pendingChanges.pushCommittedChanges();
      }
    }
    checkForClearedConflict(op);
  }
  /**
   * Check if the operation that just happened has cleared a conflict :
   * Clearing a conflict happens if the operation has free a DN that
   * for which an other entry was in conflict.
   */
   private void checkForClearedConflict(PostOperationOperation op)
   {
     OperationType type = op.getOperationType();
     if (op.getResultCode() != ResultCode.SUCCESS)
     {
       // those operations cannot have cleared a conflict
       return;
     }
     DN targetDN;
     if (type == OperationType.DELETE)
     {
       targetDN = ((PostOperationDeleteOperation) op).getEntryDN();
     }
     else if (type == OperationType.MODIFY_DN)
     {
       targetDN = ((PostOperationModifyDNOperation) op).getEntryDN();
     }
     else
     {
       return;
     }
    LDAPFilter filter = null;
    try
    {
      filter = LDAPFilter.decode(
          DS_SYNC_CONFLICT + "=" + targetDN.toNormalizedString());
    } catch (LDAPException e)
    {
      // Not possible. We know the filter just above is correct.
    }
     LinkedHashSet<String> attrs = new LinkedHashSet<String>(1);
     attrs.add(Historical.HISTORICALATTRIBUTENAME);
     attrs.add(Historical.ENTRYUIDNAME);
     attrs.add("*");
     InternalSearchOperation searchOp =  conn.processSearch(
       ByteString.valueOf(baseDn.toString()),
       SearchScope.WHOLE_SUBTREE,
       DereferencePolicy.NEVER_DEREF_ALIASES,
       0, 0, false, filter,
       attrs, null);
     LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
     Entry entrytoRename = null;
     ChangeNumber entrytoRenameDate = null;
     for (SearchResultEntry entry : entries)
     {
       Historical history = Historical.load(entry);
       if (entrytoRename == null)
       {
         entrytoRename = entry;
         entrytoRenameDate = history.getDNDate();
       }
       else if (!history.AddedOrRenamedAfter(entrytoRenameDate))
       {
         // this conflict is older than the previous, keep it.
         entrytoRename = entry;
         entrytoRenameDate = history.getDNDate();
       }
     }
     if (entrytoRename != null)
     {
       DN entryDN = entrytoRename.getDN();
       ModifyDNOperationBasis newOp = renameEntry(
           entryDN, targetDN.getRDN(), targetDN.getParent(), false);
       ResultCode res = newOp.getResultCode();
       if (res != ResultCode.SUCCESS)
       {
         Message message =
           ERR_COULD_NOT_SOLVE_CONFLICT.get(entryDN.toString(), res.toString());
           logError(message);
       }
     }
   }
  /**
   * Rename an Entry Using a synchronization, non-replicated operation.
   * This method should be used instead of the InternalConnection methods
   * when the operation that need to be run must be local only and therefore
   * not replicated to the RS.
   *
   * @param targetDN     The DN of the entry to rename.
   * @param newRDN       The new RDN to be used.
   * @param parentDN     The parentDN to be used.
   * @param markConflict A boolean indicating is this entry should be marked
   *                     as a conflicting entry. In such case the
   *                     DS_SYNC_CONFLICT attribute will be added to the entry
   *                     with the value of its original DN.
   *                     If false, the DS_SYNC_CONFLICT attribute will be
   *                     cleared.
   *
   * @return The operation that was run to rename the entry.
   */
  private ModifyDNOperationBasis renameEntry(
      DN targetDN, RDN newRDN, DN parentDN, boolean markConflict)
  {
    InternalClientConnection conn =
      InternalClientConnection.getRootConnection();
    ModifyDNOperationBasis newOp =
       new ModifyDNOperationBasis(
           conn, InternalClientConnection.nextOperationID(),
           InternalClientConnection.nextMessageID(), new ArrayList<Control>(0),
           targetDN, newRDN, false,
           parentDN);
     newOp.setInternalOperation(true);
     newOp.setSynchronizationOperation(true);
     newOp.setDontSynchronize(true);
     if (markConflict)
     {
       AttributeType attrType =
         DirectoryServer.getAttributeType(DS_SYNC_CONFLICT, true);
       Attribute attr = Attributes.create(attrType, AttributeValues.create(
           attrType, targetDN.toString()));
       Modification mod = new Modification(ModificationType.REPLACE, attr);
       newOp.addModification(mod);
     }
     else
     {
       AttributeType attrType =
         DirectoryServer.getAttributeType(DS_SYNC_CONFLICT, true);
       Attribute attr = Attributes.empty(attrType);
       Modification mod = new Modification(ModificationType.DELETE, attr);
       newOp.addModification(mod);
     }
     newOp.run();
    return newOp;
  }
  /**
@@ -3188,7 +3330,6 @@
             * and keep the entry as a conflicting entry,
             */
            conflict = true;
            markConflictEntry(conflictOp, entry.getDN(), entryDN);
            renameConflictEntry(conflictOp, entry.getDN(),
                Historical.getEntryUuid(entry));
          }
@@ -3233,11 +3374,8 @@
   */
  private void renameConflictEntry(Operation conflictOp, DN dn, String uid)
  {
    InternalClientConnection conn =
      InternalClientConnection.getRootConnection();
    ModifyDNOperation newOp = conn.processModifyDN(
        dn, generateDeleteConflictDn(uid, dn),false, baseDn);
    ModifyDNOperation newOp =
      renameEntry(dn, generateDeleteConflictDn(uid, dn), baseDn, true);
    if (newOp.getResultCode() != ResultCode.SUCCESS)
    {
@@ -3275,7 +3413,18 @@
    List<Modification> mods = new ArrayList<Modification>();
    Modification mod = new Modification(ModificationType.REPLACE, attr);
    mods.add(mod);
    ModifyOperation newOp = conn.processModify(currentDN, mods);
    ModifyOperationBasis newOp =
      new ModifyOperationBasis(
          conn, InternalClientConnection.nextOperationID(),
          InternalClientConnection.nextMessageID(), new ArrayList<Control>(0),
          currentDN, mods);
    newOp.setInternalOperation(true);
    newOp.setSynchronizationOperation(true);
    newOp.setDontSynchronize(true);
    newOp.run();
    if (newOp.getResultCode() != ResultCode.SUCCESS)
    {
      // Log information for the repair tool.
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/NamingConflictTest.java
@@ -34,6 +34,8 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.IsolationPolicy;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
@@ -42,9 +44,12 @@
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.types.Attribute;
import org.opends.server.types.DN;
import org.opends.server.types.RDN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@@ -149,6 +154,181 @@
  }
  /**
   * Test that when a previous conflict is resolved because
   * a delete operation has removed one of the conflicting entries
   * the other conflicting entry is correctly renamed to its
   * original name.
   *
   * @throws Exception if the test fails.
   */
  @Test(enabled=true)
  public void conflictCleaningDelete() throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
    DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
    conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
    LDAPReplicationDomain domain =
      MultimasterReplication.createNewDomain(conf, queue);
    domain.start();
    try
    {
      /*
       * Create a Change number generator to generate new ChangeNumbers
       * when we need to send operations messages to the replicationServer.
       */
      ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
      String entryldif =
        "dn: cn=conflictCleaningDelete, "+ TEST_ROOT_DN_STRING + "\n"
        + "objectClass: top\n" + "objectClass: person\n"
        + "objectClass: organizationalPerson\n"
        + "objectClass: inetOrgPerson\n" + "uid: user.1\n"
        + "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
        + "postalAddress: Aaccf Amar$17984 Thirteenth Street"
        + "$Rockford, NC  85762\n" + "mail: user.1@example.com\n"
        + "cn: Aaccf Amar\n" + "l: Rockford\n"
        + "street: 17984 Thirteenth Street\n"
        + "employeeNumber: 1\n"
        + "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
        + "userPassword: password\n" + "initials: AA\n";
      Entry entry = TestCaseUtils.entryFromLdifString(entryldif);
      // Add the first entry
      TestCaseUtils.addEntry(entry);
      String parentUUID = getEntryUUID(DN.decode(TEST_ROOT_DN_STRING));
      ChangeNumber cn1 = gen.newChangeNumber();
      // Now try to add the same entry with same DN but a different
      // unique ID though the replication
      AddMsg  addMsg =
        new AddMsg(cn1,
            entry.getDN().toNormalizedString(),
            "c9cb8c3c-615a-4122-865d-50323aaaed48", parentUUID,
            entry.getObjectClasses(), entry.getUserAttributes(),
            null);
      // Put the message in the replay queue
      domain.processUpdate(addMsg);
      // Make the domain replay the change from the replay queue
      domain.replay(queue.take().getUpdateMessage());
      // Now delete the first entry that was added at the beginning
      TestCaseUtils.deleteEntry(entry.getDN());
      // Expect the conflict resolution : the second entry should now
      // have been renamed with the original DN.
      Entry resultEntry = DirectoryServer.getEntry(entry.getDN());
      assertTrue(resultEntry != null, "The conflict was not cleared");
      assertEquals(getEntryUUID(resultEntry.getDN()),
          "c9cb8c3c-615a-4122-865d-50323aaaed48",
          "The wrong entry has been renamed");
      assertNull(resultEntry.getAttribute(LDAPReplicationDomain.DS_SYNC_CONFLICT));
    }
    finally
    {
      MultimasterReplication.deleteDomain(baseDn);
    }
  }
  /**
   * Test that when a previous conflict is resolved because
   * a MODDN operation has removed one of the conflicting entries
   * the other conflicting entry is correctly renamed to its
   * original name.
   *
   * @throws Exception if the test fails.
   */
  @Test(enabled=true)
  public void conflictCleaningMODDN() throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
    DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
    conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
    LDAPReplicationDomain domain =
      MultimasterReplication.createNewDomain(conf, queue);
    domain.start();
    try
    {
      /*
       * Create a Change number generator to generate new ChangeNumbers
       * when we need to send operations messages to the replicationServer.
       */
      ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
      String entryldif =
        "dn: cn=conflictCleaningDelete, "+ TEST_ROOT_DN_STRING + "\n"
        + "objectClass: top\n" + "objectClass: person\n"
        + "objectClass: organizationalPerson\n"
        + "objectClass: inetOrgPerson\n" + "uid: user.1\n"
        + "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
        + "postalAddress: Aaccf Amar$17984 Thirteenth Street"
        + "$Rockford, NC  85762\n" + "mail: user.1@example.com\n"
        + "cn: Aaccf Amar\n" + "l: Rockford\n"
        + "street: 17984 Thirteenth Street\n"
        + "employeeNumber: 1\n"
        + "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
        + "userPassword: password\n" + "initials: AA\n";
      Entry entry = TestCaseUtils.entryFromLdifString(entryldif);
      // Add the first entry
      TestCaseUtils.addEntry(entry);
      String parentUUID = getEntryUUID(DN.decode(TEST_ROOT_DN_STRING));
      ChangeNumber cn1 = gen.newChangeNumber();
      // Now try to add the same entry with same DN but a different
      // unique ID though the replication
      AddMsg  addMsg =
        new AddMsg(cn1,
            entry.getDN().toNormalizedString(),
            "c9cb8c3c-615a-4122-865d-50323aaaed48", parentUUID,
            entry.getObjectClasses(), entry.getUserAttributes(),
            null);
      // Put the message in the replay queue
      domain.processUpdate(addMsg);
      // Make the domain replay the change from the replay queue
      domain.replay(queue.take().getUpdateMessage());
      // Now delete the first entry that was added at the beginning
      InternalClientConnection conn =
        InternalClientConnection.getRootConnection();
      ModifyDNOperation modDNOperation =
        conn.processModifyDN(entry.getDN(), RDN.decode("cn=foo"), false);
      assertEquals(modDNOperation.getResultCode(), ResultCode.SUCCESS);
      // Expect the conflict resolution : the second entry should now
      // have been renamed with the original DN.
      Entry resultEntry = DirectoryServer.getEntry(entry.getDN());
      assertTrue(resultEntry != null, "The conflict was not cleared");
      assertEquals(getEntryUUID(resultEntry.getDN()),
          "c9cb8c3c-615a-4122-865d-50323aaaed48",
          "The wrong entry has been renamed");
      assertNull(resultEntry.getAttribute(LDAPReplicationDomain.DS_SYNC_CONFLICT));
    }
    finally
    {
      MultimasterReplication.deleteDomain(baseDn);
    }
  }
  /**
   * Tests for issue 3891
   *       S1                                       S2
   *       ADD uid=xx,ou=parent,...          [SUBTREE] DEL ou=parent, ...
@@ -219,7 +399,6 @@
      TestCaseUtils.addEntry(childEntry);
      String parentUUID = getEntryUUID(parentEntry.getDN());
      String childUUID = getEntryUUID(childEntry.getDN());
      ChangeNumber cn2 = gen.newChangeNumber();