From 898a1a99285c09ce20b0394aafbe30fa81d16675 Mon Sep 17 00:00:00 2001
From: Jean-Noël Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Tue, 29 Sep 2015 09:06:26 +0000
Subject: [PATCH] OPENDJ-1192 Modify request replay failures

---
 opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyReplayTest.java |  399 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 399 insertions(+), 0 deletions(-)

diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyReplayTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyReplayTest.java
new file mode 100644
index 0000000..0d35339
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyReplayTest.java
@@ -0,0 +1,399 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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 2015 ForgeRock AS
+ */
+package org.opends.server.replication.plugin;
+
+import static org.forgerock.opendj.ldap.ModificationType.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+import static org.opends.server.util.CollectionUtils.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ModificationType;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.common.CSN;
+import org.opends.server.replication.protocol.ModifyContext;
+import org.opends.server.replication.protocol.OperationContext;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.operation.PreOperationModifyOperation;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Tests the modify replay logic.
+ * <p>
+ * It produces series of changes and replay them out of order by generating all possible
+ * permutations. The goal is to end up in the same final state whatever the order.
+ */
+@SuppressWarnings("javadoc")
+public class ModifyReplayTest extends ReplicationTestCase
+{
+  private static final String ATTRIBUTE_NAME = "displayName";
+  private static final String SYNCHIST = "ds-sync-hist";
+
+  private Entry entry;
+
+  private static class Mod
+  {
+    private final int time;
+    private final Modification modification;
+
+    private Mod(ModificationType modType, int t)
+    {
+      this(newModification(modType), t);
+    }
+
+    private Mod(ModificationType modType, String value, int t)
+    {
+      this(newModification(modType, value), t);
+    }
+
+    private Mod(Modification modification, int time)
+    {
+      this.modification = modification;
+      this.time = time;
+    }
+
+    private PreOperationModifyOperation toOperation()
+    {
+      final ModifyContext value = new ModifyContext(new CSN(0, time, 0), null);
+
+      PreOperationModifyOperation op = mock(PreOperationModifyOperation.class);
+      when(op.getModifications()).thenReturn(newArrayList(modification));
+      when(op.getAttachment(eq(OperationContext.SYNCHROCONTEXT))).thenReturn(value);
+      return op;
+    }
+
+    /** Implemented to get a nice display for each tests in Eclipse UI. */
+    @Override
+    public String toString()
+    {
+      String modType = modification.getModificationType().toString().toUpperCase();
+      Iterator<ByteString> it = modification.getAttribute().iterator();
+      String attrValue = it.hasNext() ? "\"" + it.next().toString() + "\" " : "";
+      return modType + " " + attrValue + "t" + time;
+    }
+  }
+
+  private static Object[][] generatePermutations(Object[][] scenarios)
+  {
+    List<Object[]> results = new ArrayList<Object[]>();
+    for (Object[] scenario : scenarios)
+    {
+      generate((Object[]) scenario[0], (Attribute) scenario[1], results);
+    }
+
+    return results.toArray(new Object[results.size()][]);
+  }
+
+  private static void generate(Object[] array, Attribute dsSyncHist, List<Object[]> results)
+  {
+    generate(array.length, array, dsSyncHist, results);
+  }
+
+  private static void generate(int n, Object[] array, Attribute dsSyncHist, List<Object[]> results)
+  {
+    if (n == 1)
+    {
+      results.add(new Object[] { Arrays.asList(Arrays.copyOf(array, array.length)), dsSyncHist, });
+      return;
+    }
+
+    for (int i = 0; i < n - 1; i += 1)
+    {
+      generate(n - 1, array, dsSyncHist, results);
+      if (n % 2 == 0)
+      {
+        swap(array, i, n - 1);
+      }
+      else
+      {
+        swap(array, 0, n - 1);
+      }
+    }
+    generate(n - 1, array, dsSyncHist, results);
+  }
+
+  private static <E> void swap(E[] array, int i, int j)
+  {
+    E tmp = array[i];
+    array[i] = array[j];
+    array[j] = tmp;
+  }
+
+  @DataProvider
+  public Object[][] add_data()
+  {
+    // @formatter:off
+    return generatePermutations(new Object[][] {
+      { mods(new Mod(ADD, "X", 1)),                                 dsSyncHist(1, ":add:X"), },
+      { mods(new Mod(ADD, "X", 1), new Mod(ADD, "X", 2)),           dsSyncHist(2, ":add:X"), },
+      { mods(new Mod(ADD, "X", 1), new Mod(ADD, "Y", 2)),           dsSyncHist(2, ":add:Y"), },
+      { mods(new Mod(ADD, "X", 1), new Mod(DELETE, "X", 2)),        dsSyncHist(2, ":attrDel"), },
+      { mods(new Mod(ADD, "X", 1), new Mod(DELETE, "Y", 2)),        dsSyncHist(1, ":add:X"), },
+      { mods(new Mod(ADD, "X", 1), new Mod(DELETE, 2)),             dsSyncHist(2, ":attrDel"), },
+      { mods(new Mod(ADD, "X", 1), new Mod(REPLACE, "X", 2)),       dsSyncHist(2, ":repl:X"), },
+      { mods(new Mod(ADD, "X", 1), new Mod(REPLACE, "Y", 2)),       dsSyncHist(2, ":repl:Y"), },
+      { mods(new Mod(ADD, "X", 1), new Mod(REPLACE, 2)),            dsSyncHist(2, ":attrDel"), }
+    });
+    // @formatter:off
+  }
+
+  @DataProvider
+  public Object[][] delete_noInitialValue_data()
+  {
+    // @formatter:off
+    return generatePermutations(new Object[][] {
+        { mods(new Mod(DELETE, "X", 1)),                            dsSyncHist(1, ":attrDel"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(ADD, "X", 2)),      dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(ADD, "Y", 2)),      dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(DELETE, "X", 2)),   dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(DELETE, "Y", 2)),   dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(DELETE, 2)),        dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(REPLACE, "X", 2)),  dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(REPLACE, "Y", 2)),  dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(REPLACE, 2)),       dsSyncHist(2, ":attrDel"), },
+
+        { mods(new Mod(DELETE, 1)),                                 dsSyncHist(1, ":attrDel"), },
+        { mods(new Mod(DELETE, 1), new Mod(ADD, "X", 2)),           dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(DELETE, 1), new Mod(ADD, "Y", 2)),           dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(DELETE, 1), new Mod(DELETE, "X", 2)),        dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, 1), new Mod(DELETE, "Y", 2)),        dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, 1), new Mod(DELETE, 2)),             dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, 1), new Mod(REPLACE, "X", 2)),       dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(DELETE, 1), new Mod(REPLACE, "Y", 2)),       dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(DELETE, 1), new Mod(REPLACE, 2)),            dsSyncHist(2, ":attrDel"), },
+    });
+    // @formatter:off
+  }
+
+  @DataProvider
+  public Object[][] delete_initialValueX_data()
+  {
+    // @formatter:off
+    return generatePermutations(new Object[][] {
+        { mods(new Mod(DELETE, "X", 1)),                            dsSyncHist(1, ":attrDel"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(ADD, "X", 2)),      dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(ADD, "Y", 2)),      dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(DELETE, "X", 2)),   dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(DELETE, "Y", 2)),   dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(DELETE, 2)),        dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(REPLACE, "X", 2)),  dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(REPLACE, "Y", 2)),  dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(DELETE, "X", 1), new Mod(REPLACE, 2)),       dsSyncHist(2, ":attrDel"), },
+
+        { mods(new Mod(DELETE, "Y", 1)),                            dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(DELETE, "Y", 1), new Mod(ADD, "X", 2)),      dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(DELETE, "Y", 1), new Mod(ADD, "Y", 2)),      dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(DELETE, "Y", 1), new Mod(DELETE, "X", 2)),   dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(DELETE, "Y", 1), new Mod(DELETE, "Y", 2)),   dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(DELETE, "Y", 1), new Mod(DELETE, 2)),        dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, "Y", 1), new Mod(REPLACE, "X", 2)),  dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(DELETE, "Y", 1), new Mod(REPLACE, "Y", 2)),  dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(DELETE, "Y", 1), new Mod(REPLACE, 2)),       dsSyncHist(2, ":attrDel"), },
+
+        { mods(new Mod(DELETE, 1)),                                 dsSyncHist(1, ":attrDel"), },
+        { mods(new Mod(DELETE, 1), new Mod(ADD, "X", 2)),           dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(DELETE, 1), new Mod(ADD, "Y", 2)),           dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(DELETE, 1), new Mod(DELETE, "X", 2)),        dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, 1), new Mod(DELETE, "Y", 2)),        dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, 1), new Mod(DELETE, 2)),             dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(DELETE, 1), new Mod(REPLACE, "X", 2)),       dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(DELETE, 1), new Mod(REPLACE, "Y", 2)),       dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(DELETE, 1), new Mod(REPLACE, 2)),            dsSyncHist(2, ":attrDel"), },
+    });
+    // @formatter:off
+  }
+
+  @DataProvider
+  public Object[][] replace_noInitialValue_data()
+  {
+    // @formatter:off
+    return generatePermutations(new Object[][] {
+        { mods(new Mod(REPLACE, "X", 1)),                           dsSyncHist(1, ":repl:X"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(ADD, "X", 2)),     dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(ADD, "Y", 2)),     dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(DELETE, "X", 2)),  dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(DELETE, "Y", 2)),  dsSyncHist(1, ":repl:X"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(DELETE, 2)),       dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(REPLACE, "X", 2)), dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(REPLACE, "Y", 2)), dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(REPLACE, 2)),      dsSyncHist(2, ":attrDel"), },
+
+        { mods(new Mod(REPLACE, 1)),                                dsSyncHist(1, ":attrDel"), },
+        { mods(new Mod(REPLACE, 1), new Mod(ADD, "X", 2)),          dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(REPLACE, 1), new Mod(ADD, "Y", 2)),          dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(REPLACE, 1), new Mod(DELETE, "X", 2)),       dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(REPLACE, 1), new Mod(DELETE, "Y", 2)),       dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(REPLACE, 1), new Mod(DELETE, 2)),            dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(REPLACE, 1), new Mod(REPLACE, "X", 2)),      dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(REPLACE, 1), new Mod(REPLACE, "Y", 2)),      dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(REPLACE, 1), new Mod(REPLACE, 2)),           dsSyncHist(2, ":attrDel"), },
+    });
+    // @formatter:off
+  }
+
+  @DataProvider
+  public Object[][] replace_initialValueX_data()
+  {
+    // @formatter:off
+    return generatePermutations(new Object[][] {
+        { mods(new Mod(REPLACE, "X", 1)),                           dsSyncHist(1, ":repl:X"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(ADD, "X", 2)),     dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(ADD, "Y", 2)),     dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(DELETE, "X", 2)),  dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(DELETE, "Y", 2)),  dsSyncHist(1, ":add:X"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(DELETE, 2)),       dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(REPLACE, "X", 2)), dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(REPLACE, "Y", 2)), dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(REPLACE, "X", 1), new Mod(REPLACE, 2)),      dsSyncHist(2, ":attrDel"), },
+
+        { mods(new Mod(REPLACE, "Y", 1)),                           dsSyncHist(1, ":repl:Y"), },
+        { mods(new Mod(REPLACE, "Y", 1), new Mod(ADD, "X", 2)),     dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(REPLACE, "Y", 1), new Mod(ADD, "Y", 2)),     dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(REPLACE, "Y", 1), new Mod(DELETE, "X", 2)),  dsSyncHist(1, ":repl:Y"), },
+        { mods(new Mod(REPLACE, "Y", 1), new Mod(DELETE, "Y", 2)),  dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(REPLACE, "Y", 1), new Mod(DELETE, 2)),       dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(REPLACE, "Y", 1), new Mod(REPLACE, "X", 2)), dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(REPLACE, "Y", 1), new Mod(REPLACE, "Y", 2)), dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(REPLACE, "Y", 1), new Mod(REPLACE, 2)),      dsSyncHist(2, ":attrDel"), },
+
+        { mods(new Mod(REPLACE, 1)),                                dsSyncHist(1, ":attrDel"), },
+        { mods(new Mod(REPLACE, 1), new Mod(ADD, "X", 2)),          dsSyncHist(2, ":add:X"), },
+        { mods(new Mod(REPLACE, 1), new Mod(ADD, "Y", 2)),          dsSyncHist(2, ":add:Y"), },
+        { mods(new Mod(REPLACE, 1), new Mod(DELETE, "X", 2)),       dsSyncHist(1, ":attrDel"), },
+        { mods(new Mod(REPLACE, 1), new Mod(DELETE, "Y", 2)),       dsSyncHist(1, ":attrDel"), },
+        { mods(new Mod(REPLACE, 1), new Mod(DELETE, 2)),            dsSyncHist(2, ":attrDel"), },
+        { mods(new Mod(REPLACE, 1), new Mod(REPLACE, "X", 2)),      dsSyncHist(2, ":repl:X"), },
+        { mods(new Mod(REPLACE, 1), new Mod(REPLACE, "Y", 2)),      dsSyncHist(2, ":repl:Y"), },
+        { mods(new Mod(REPLACE, 1), new Mod(REPLACE, 2)),           dsSyncHist(2, ":attrDel"), },
+    });
+    // @formatter:off
+  }
+
+  @Test(dataProvider = "add_data", enabled=false)
+  public void add_noInitialValue(List<Mod> mods, Attribute expectedDsSyncHist) throws Exception
+  {
+    noValue(); // also covers the initialValue("X"); case
+    replay(mods, expectedDsSyncHist);
+  }
+
+  @Test(dataProvider = "delete_noInitialValue_data", enabled=false)
+  public void delete_noInitialValue(List<Mod> mods, Attribute expectedDsSyncHist) throws Exception
+  {
+    noValue();
+    replay(mods, expectedDsSyncHist);
+  }
+
+  @Test(dataProvider = "delete_initialValueX_data", enabled=false)
+  public void delete_initialValueX(List<Mod> mods, Attribute expectedDsSyncHist) throws Exception
+  {
+    initialValue("X");
+    replay(mods, expectedDsSyncHist);
+  }
+
+  @Test(dataProvider = "replace_noInitialValue_data", enabled=false)
+  public void replace_noInitialValue(List<Mod> mods, Attribute expectedDsSyncHist) throws Exception
+  {
+    noValue();
+    replay(mods, expectedDsSyncHist);
+  }
+
+  @Test(dataProvider = "replace_initialValueX_data", enabled=false)
+  public void replace_initialValueX(List<Mod> mods, Attribute expectedDsSyncHist) throws Exception
+  {
+    initialValue("X");
+    replay(mods, expectedDsSyncHist);
+  }
+
+  private void noValue() throws Exception
+  {
+    entry = TestCaseUtils.makeEntry(
+        "dn: uid=test.user",
+        "objectClass: top",
+        "objectClass: person",
+        "objectClass: organizationalPerson",
+        "objectClass: inetOrgPerson",
+        "uid: test.user",
+        "givenName: Test",
+        "sn: User",
+        "cn: Test User",
+        "userPassword: password");
+  }
+
+  private void initialValue(String attrValue) throws Exception
+  {
+    noValue();
+    entry.applyModification(newModification(REPLACE, attrValue));
+  }
+
+  private void replay(List<Mod> mods, Attribute expectedDsSyncHist) throws DirectoryException
+  {
+    for (Mod op : mods)
+    {
+      EntryHistorical entryHistorical = EntryHistorical.newInstanceFromEntry(entry);
+      entryHistorical.replayOperation(op.toOperation(), entry);
+      entry.applyModification(new Modification(REPLACE, entryHistorical.encodeAndPurge()));
+    }
+
+    AttributeType attrType = expectedDsSyncHist.getAttributeType();
+    Attribute actual = entry.getExactAttribute(attrType, Collections.<String> emptySet());
+
+    Assert.assertEquals(actual, expectedDsSyncHist, "wrong final value for ds-sync-hist attribute");
+  }
+
+  private static Object[] mods(Mod... mods)
+  {
+    return mods;
+  }
+
+  private static Object dsSyncHist(int t, String partialDsSyncHist)
+  {
+    String value = ATTRIBUTE_NAME + ":000000000000000000000000000" + t + partialDsSyncHist;
+    return Attributes.create(SYNCHIST, value);
+  }
+
+  private static Modification newModification(ModificationType modType)
+  {
+    return new Modification(modType, Attributes.empty(ATTRIBUTE_NAME));
+  }
+
+  private static Modification newModification(ModificationType modType, String value)
+  {
+    return new Modification(modType, Attributes.create(ATTRIBUTE_NAME, value));
+  }
+}

--
Gitblit v1.10.0