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