From 7bb5b9a55a8d68f9622ca3ae6bb22b889b0a6a3f Mon Sep 17 00:00:00 2001
From: gbellato <gbellato@localhost>
Date: Mon, 16 Mar 2009 08:06:01 +0000
Subject: [PATCH] Fix for issue 3402 : Replication conflict: fail to resolve double mod_rdn of same entry

---
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java                 |   59 -----
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java |   59 -----
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/NamingConflictTest.java           |  134 +++++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java                 |   59 +++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/TestSynchronousReplayQueue.java   |  223 ++++++++++++++++++++++
 opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java                               |   27 ++
 opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java                                |   16 +
 7 files changed, 456 insertions(+), 121 deletions(-)

diff --git a/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java b/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
index 0abde4a..db16f13 100644
--- a/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
+++ b/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -59,7 +59,7 @@
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.zip.CheckedOutputStream;
@@ -178,7 +178,7 @@
 
   // The update to replay message queue where the listener thread is going to
   // push incoming update messages.
-  private final LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue;
+  private final BlockingQueue<UpdateToReplay> updateToReplayQueue;
   private final AtomicInteger numResolvedNamingConflicts = new AtomicInteger();
   private final AtomicInteger numResolvedModifyConflicts = new AtomicInteger();
   private final AtomicInteger numUnresolvedNamingConflicts =
@@ -301,7 +301,7 @@
    * @throws ConfigException In case of invalid configuration.
    */
   public LDAPReplicationDomain(ReplicationDomainCfg configuration,
-    LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue)
+    BlockingQueue<UpdateToReplay> updateToReplayQueue)
     throws ConfigException
   {
     super(configuration.getBaseDN().toNormalizedString(),
@@ -703,6 +703,16 @@
             ResultCode.NO_SUCH_OBJECT, null);
         }
       }
+      /*
+       * If the object has been renamed more recently than this
+       * operation, cancel the operation.
+       */
+      Historical hist = Historical.load(modifyDNOperation.getOriginalEntry());
+      if (hist.AddedOrRenamedAfter(ctx.getChangeNumber()))
+      {
+        return new SynchronizationProviderResult.StopProcessing(
+            ResultCode.SUCCESS, null);
+      }
     }
     else
     {
diff --git a/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java b/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
index bbc0a82..2afd51d 100644
--- a/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
+++ b/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.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;
 
@@ -33,6 +33,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 
 import org.opends.messages.Message;
@@ -207,6 +208,30 @@
   }
 
   /**
+   * Creates a new domain from its configEntry, do the
+   * necessary initialization and starts it so that it is
+   * fully operational when this method returns.
+   *
+   * @param configuration The entry with the configuration of this domain.
+   * @param queue         The BlockingQueue that this domain will use.
+   *
+   * @return              The domain created.
+   *
+   * @throws ConfigException When the configuration is not valid.
+   */
+  public static LDAPReplicationDomain createNewDomain(
+      ReplicationDomainCfg configuration,
+      BlockingQueue<UpdateToReplay> queue)
+      throws ConfigException
+  {
+    LDAPReplicationDomain domain;
+    domain = new LDAPReplicationDomain(configuration, queue);
+
+    domains.put(domain.getBaseDN(), domain);
+    return domain;
+  }
+
+  /**
    * Deletes a domain.
    * @param dn : the base DN of the domain to delete.
    */
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
index 5723295..a6fd622 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
@@ -1095,4 +1095,63 @@
       fail("addEntries Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e));
     }
   }
+
+  /**
+   *  Get the entryUUID for a given DN.
+   *
+   * @throws Exception if the entry does not exist or does not have
+   *                   an entryUUID.
+   */
+  protected String getEntryUUID(DN dn) throws Exception
+  {
+    Entry newEntry;
+    int count = 10;
+    if (count<1)
+      count=1;
+    String found = null;
+    while ((count> 0) && (found == null))
+    {
+      Thread.sleep(100);
+
+      Lock lock = null;
+      for (int i=0; i < 3; i++)
+      {
+        lock = LockManager.lockRead(dn);
+        if (lock != null)
+        {
+          break;
+        }
+      }
+
+      if (lock == null)
+      {
+        throw new Exception("could not lock entry " + dn);
+      }
+
+      try
+      {
+        newEntry = DirectoryServer.getEntry(dn);
+
+        if (newEntry != null)
+        {
+          List<Attribute> tmpAttrList = newEntry.getAttribute("entryuuid");
+          Attribute tmpAttr = tmpAttrList.get(0);
+
+          for (AttributeValue val : tmpAttr)
+          {
+            found = val.getValue().toString();
+            break;
+          }
+        }
+      }
+      finally
+      {
+        LockManager.unlock(dn, lock);
+      }
+      count --;
+    }
+    if (found == null)
+      throw new Exception("Entry: " + dn + " Could not be found.");
+    return found;
+  }
 }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
index 0f367a2..798cb28 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
@@ -590,6 +590,7 @@
     broker.stop();
   }
 
+
   /**
    * Tests the naming conflict resolution code.
    * In this test, the local server act both as an LDAP server and
@@ -1500,64 +1501,6 @@
     }
   }
 
-  /**
-   *  Get the entryUUID for a given DN.
-   *
-   * @throws Exception if the entry does not exist or does not have
-   *                   an entryUUID.
-   */
-  private String getEntryUUID(DN dn) throws Exception
-  {
-    Entry newEntry;
-    int count = 10;
-    if (count<1)
-      count=1;
-    String found = null;
-    while ((count> 0) && (found == null))
-    {
-      Thread.sleep(100);
-
-      Lock lock = null;
-      for (int i=0; i < 3; i++)
-      {
-        lock = LockManager.lockRead(dn);
-        if (lock != null)
-        {
-          break;
-        }
-      }
-
-      if (lock == null)
-      {
-        throw new Exception("could not lock entry " + dn);
-      }
-
-      try
-      {
-        newEntry = DirectoryServer.getEntry(dn);
-
-        if (newEntry != null)
-        {
-          List<Attribute> tmpAttrList = newEntry.getAttribute("entryuuid");
-          Attribute tmpAttr = tmpAttrList.get(0);
-
-          for (AttributeValue val : tmpAttr)
-          {
-            found = val.getValue().toString();
-            break;
-          }
-        }
-      }
-      finally
-      {
-        LockManager.unlock(dn, lock);
-      }
-      count --;
-    }
-    if (found == null)
-      throw new Exception("Entry: " + dn + " Could not be found.");
-    return found;
-  }
 
   /**
    * Test case for
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
index b20164a..329e40a 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
@@ -1300,65 +1300,6 @@
   }
 
   /**
-   *  Get the entryUUID for a given DN.
-   *
-   * @throws Exception if the entry does not exist or does not have
-   *                   an entryUUID.
-   */
-  private String getEntryUUID(DN dn) throws Exception
-  {
-    Entry newEntry;
-    int count = 10;
-    if (count<1)
-      count=1;
-    String found = null;
-    while ((count> 0) && (found == null))
-    {
-      Thread.sleep(100);
-
-      Lock lock = null;
-      for (int i=0; i < 3; i++)
-      {
-        lock = LockManager.lockRead(dn);
-        if (lock != null)
-        {
-          break;
-        }
-      }
-
-      if (lock == null)
-      {
-        throw new Exception("could not lock entry " + dn);
-      }
-
-      try
-      {
-        newEntry = DirectoryServer.getEntry(dn);
-
-        if (newEntry != null)
-        {
-          List<Attribute> tmpAttrList = newEntry.getAttribute("entryuuid");
-          Attribute tmpAttr = tmpAttrList.get(0);
-
-          for (AttributeValue val : tmpAttr)
-          {
-            found = val.toString();
-            break;
-          }
-        }
-      }
-      finally
-      {
-        LockManager.unlock(dn, lock);
-      }
-      count --;
-    }
-    if (found == null)
-      throw new Exception("Entry: " + dn + " Could not be found.");
-    return found;
-  }
-
-  /**
    * Tests that a DS receiving an update from a RS in safe read mode effectively
    * sends an ack back (with or without error)
    */
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/NamingConflictTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/NamingConflictTest.java
new file mode 100644
index 0000000..5165a0a
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/NamingConflictTest.java
@@ -0,0 +1,134 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.plugin;
+
+import static org.opends.server.TestCaseUtils.TEST_ROOT_DN_STRING;
+
+import java.util.TreeSet;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.IsolationPolicy;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.ChangeNumberGenerator;
+import org.opends.server.replication.protocol.ModifyDNMsg;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+
+/**
+ * Test the naming conflict resolution code.
+ */
+public class NamingConflictTest extends ReplicationTestCase
+{
+  /**
+   * Test for issue 3402 : test, that a modrdn that is older than an other
+   * modrdn but that is applied later is ignored.
+   *
+   * In this test, the local server act both as an LDAP server and
+   * a replicationServer that are inter-connected.
+   *
+   * The test creates an other session to the replicationServer using
+   * directly the ReplicationBroker API.
+   * It then uses this session to simulate conflicts and therefore
+   * test the naming conflict resolution code.
+   */
+  @Test(enabled=true)
+  public void simultaneousModrdnConflict() 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);
+
+    /*
+     * Create a Change number generator to generate new ChangeNumbers
+     * when we need to send operations messages to the replicationServer.
+     */
+    ChangeNumberGenerator gen = new ChangeNumberGenerator((short) 201, 0);
+
+    String parentUUID = getEntryUUID(DN.decode(TEST_ROOT_DN_STRING));
+
+    Entry entry = TestCaseUtils.entryFromLdifString(
+        "dn: cn=simultaneousModrdnConflict, "+ TEST_ROOT_DN_STRING + "\n"
+        + "objectClass: top\n" + "objectClass: person\n"
+        + "objectClass: organizationalPerson\n"
+        + "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+        + "homePhone: 951-245-7634\n"
+        + "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+        + "mobile: 027-085-0537\n"
+        + "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+        + "$Rockford, NC  85762\n" + "mail: user.1@example.com\n"
+        + "cn: Aaccf Amar\n" + "l: Rockford\n" + "pager: 508-763-4246\n"
+        + "street: 17984 Thirteenth Street\n"
+        + "telephoneNumber: 216-564-6748\n" + "employeeNumber: 1\n"
+        + "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+        + "userPassword: password\n" + "initials: AA\n");
+
+    TestCaseUtils.addEntry(entry);
+    String entryUUID = getEntryUUID(entry.getDN());
+
+    // generate two consecutive ChangeNumber that will be used in backward order
+    ChangeNumber cn1 = gen.newChangeNumber();
+    ChangeNumber cn2 = gen.newChangeNumber();
+
+    ModifyDNMsg  modDnMsg = new ModifyDNMsg(
+        entry.getDN().toNormalizedString(), cn2,
+        entryUUID, parentUUID, false,
+        TEST_ROOT_DN_STRING,
+        "uid=simultaneous2");
+
+    domain.processUpdate(modDnMsg);
+    domain.replay(queue.take().getUpdateMessage());
+
+    // This MODIFY DN uses an older DN and should therefore be cancelled
+    // at replay time.
+    modDnMsg = new ModifyDNMsg(
+        entry.getDN().toNormalizedString(), cn1,
+        entryUUID, parentUUID, false,
+        TEST_ROOT_DN_STRING,
+        "uid=simulatneouswrong");
+
+    domain.processUpdate(modDnMsg);
+    domain.replay(queue.take().getUpdateMessage());
+
+    assertFalse(DirectoryServer.entryExists(entry.getDN()),
+        "The modDN conflict was not resolved as expected.");
+
+    MultimasterReplication.deleteDomain(baseDn);
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/TestSynchronousReplayQueue.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/TestSynchronousReplayQueue.java
new file mode 100644
index 0000000..1b4be59
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/TestSynchronousReplayQueue.java
@@ -0,0 +1,223 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.plugin;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * A very partial implementation of a Blocking queue that should
+ * be used for test purpose only.
+ *
+ * Only the offer and take methods are implemented.
+ *
+ */
+public class TestSynchronousReplayQueue implements BlockingQueue<UpdateToReplay>
+{
+  LinkedList<UpdateToReplay> list = new LinkedList<UpdateToReplay>();
+
+  @Override
+  public boolean add(UpdateToReplay e)
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public boolean contains(Object o)
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public int drainTo(Collection<? super UpdateToReplay> c)
+  {
+    // TODO Auto-generated method stub
+    return 0;
+  }
+
+  @Override
+  public int drainTo(Collection<? super UpdateToReplay> c, int maxElements)
+  {
+    // TODO Auto-generated method stub
+    return 0;
+  }
+
+  @Override
+  public boolean offer(UpdateToReplay e)
+  {
+    list.add(e);
+    return true;
+  }
+
+  @Override
+  public boolean offer(UpdateToReplay e, long timeout, TimeUnit unit)
+      throws InterruptedException
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public UpdateToReplay poll(long timeout, TimeUnit unit)
+      throws InterruptedException
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public void put(UpdateToReplay e) throws InterruptedException
+  {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public int remainingCapacity()
+  {
+    // TODO Auto-generated method stub
+    return 0;
+  }
+
+  @Override
+  public boolean remove(Object o)
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public UpdateToReplay take() throws InterruptedException
+  {
+    return list.pop();
+  }
+
+  @Override
+  public UpdateToReplay element()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public UpdateToReplay peek()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public UpdateToReplay poll()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public UpdateToReplay remove()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public boolean addAll(Collection<? extends UpdateToReplay> c)
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public void clear()
+  {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public boolean containsAll(Collection<?> c)
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public boolean isEmpty()
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public Iterator<UpdateToReplay> iterator()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public boolean removeAll(Collection<?> c)
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public boolean retainAll(Collection<?> c)
+  {
+    // TODO Auto-generated method stub
+    return false;
+  }
+
+  @Override
+  public int size()
+  {
+    // TODO Auto-generated method stub
+    return 0;
+  }
+
+  @Override
+  public Object[] toArray()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public <T> T[] toArray(T[] a)
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+}

--
Gitblit v1.10.0