From cfe6d3c911c21e5e7b1091fe0802251fe055854b Mon Sep 17 00:00:00 2001
From: coulbeck <coulbeck@localhost>
Date: Fri, 09 Feb 2007 20:33:01 +0000
Subject: [PATCH] Minor synchronization code changes:
---
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java | 107 +------------
opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java | 4
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java | 3
opends/src/server/org/opends/server/synchronization/plugin/AttrInfo.java | 32 +++
opends/src/server/org/opends/server/synchronization/plugin/Historical.java | 13 -
opends/src/server/org/opends/server/synchronization/plugin/HistVal.java | 11
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java | 177 +++++++++++++++++++++
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java | 93 +++++++++++
8 files changed, 314 insertions(+), 126 deletions(-)
diff --git a/opends/src/server/org/opends/server/synchronization/plugin/AttrInfo.java b/opends/src/server/org/opends/server/synchronization/plugin/AttrInfo.java
index 1f899de..90c7d91 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/AttrInfo.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/AttrInfo.java
@@ -37,7 +37,7 @@
* This classes is used to store historical information.
* One object of this type is created for each attribute that was changed in
* the entry.
- * It allows to record the last time a givene value was added, the last
+ * It allows to record the last time a given value was added, the last
* time a given value was deleted and the last time the whole attribute was
* deleted.
*/
@@ -130,8 +130,14 @@
{
if (this.valuesInfo != null)
this.valuesInfo.clear();
- deleteTime = CN;
- lastUpdateTime = CN;
+ if (CN.newer(deleteTime))
+ {
+ deleteTime = CN;
+ }
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
}
/**
@@ -144,7 +150,10 @@
ValueInfo info = new ValueInfo(val, null, CN);
this.valuesInfo.remove(info);
this.valuesInfo.add(info);
- lastUpdateTime = CN;
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
}
/**
@@ -160,7 +169,10 @@
ValueInfo info = new ValueInfo(val, null, CN);
this.valuesInfo.remove(info);
this.valuesInfo.add(info);
- lastUpdateTime = CN;
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
}
}
@@ -175,7 +187,10 @@
ValueInfo info = new ValueInfo(val, CN, null);
this.valuesInfo.remove(info);
valuesInfo.add(info);
- lastUpdateTime = CN;
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
}
/**
@@ -192,7 +207,10 @@
ValueInfo info = new ValueInfo(val, CN, null);
this.valuesInfo.remove(info);
valuesInfo.add(info);
- lastUpdateTime = CN;
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
}
}
diff --git a/opends/src/server/org/opends/server/synchronization/plugin/HistVal.java b/opends/src/server/org/opends/server/synchronization/plugin/HistVal.java
index ed4c600..579f025 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/HistVal.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/HistVal.java
@@ -26,7 +26,6 @@
*/
package org.opends.server.synchronization.plugin;
-import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -40,7 +39,7 @@
/**
- * This Class is used to encode/decode hsitorical information
+ * This Class is used to encode/decode historical information
* from the String form to the internal usable form.
*
* @author Gilles Bellaton
@@ -51,7 +50,7 @@
private String attrString;
private AttributeValue attributeValue;
private ChangeNumber cn;
- private Set<String> options = null;
+ private LinkedHashSet<String> options;
private HistKey histKey;
private String stringValue;
@@ -84,10 +83,10 @@
*/
String[] token = strVal.split(":", 4);
+ options = new LinkedHashSet<String>();
if (token[0].contains(";"))
{
String[] optionsToken = token[0].split(";");
- options = new HashSet<String>();
int index = 1;
while (index < optionsToken.length)
{
@@ -153,7 +152,7 @@
}
/**
- * Get the options.
+ * Get the options or an empty set if there are no options.
* @return Returns the options.
*/
public Set<String> getOptions()
@@ -186,7 +185,7 @@
*/
public Modification generateMod()
{
- Attribute attr = new Attribute(attrType);
+ Attribute attr = new Attribute(attrType, attrString, options, null);
Modification mod;
if (histKey != HistKey.DELATTR)
{
diff --git a/opends/src/server/org/opends/server/synchronization/plugin/Historical.java b/opends/src/server/org/opends/server/synchronization/plugin/Historical.java
index 1831ef3..df26d97 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/Historical.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/Historical.java
@@ -34,6 +34,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.HashSet;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DirectoryServer;
@@ -141,10 +142,6 @@
Modification m = (Modification) modsIterator.next();
Attribute modAttr = m.getAttribute();
Set<String> options = modAttr.getOptions();
- if (options.isEmpty())
- {
- options = null;
- }
AttributeType type = modAttr.getAttributeType();
AttrInfoWithOptions attrInfoWithOptions = attributesInfo.get(type);
AttrInfo attrInfo = null;
@@ -263,10 +260,6 @@
return;
}
Set<String> options = modAttr.getOptions();
- if (options.isEmpty())
- {
- options = null;
- }
AttributeType type = modAttr.getAttributeType();
AttrInfoWithOptions attrInfoWithOptions = attributesInfo.get(type);
AttrInfo attrInfo;
@@ -477,7 +470,7 @@
*/
private boolean hasConflict(AttrInfo info, ChangeNumber newChange)
{
- // if I've already seen a change that is more recetn than the one
+ // if I've already seen a change that is more recent than the one
// that is currently being processed, then there is
// a potential conflict
if (ChangeNumber.compare(newChange, moreRecentChangenumber) <= 0)
@@ -758,7 +751,7 @@
List<Attribute> hist = entry.getAttribute(historicalAttrType);
Historical histObj = new Historical();
AttributeType lastAttrType = null;
- Set<String> lastOptions = null;
+ Set<String> lastOptions = new HashSet<String>();
AttrInfo attrInfo = null;
AttrInfoWithOptions attrInfoWithOptions = null;
diff --git a/opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java b/opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java
index 42c03eb..99e318c 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java
@@ -1423,6 +1423,8 @@
else
return true;
}
+
+ // TODO log a message for the repair tool.
return true;
}
@@ -1506,7 +1508,7 @@
/*
* This entry is the base dn of the backend.
* It is quite weird that the operation result be NO_SUCH_OBJECT.
- * There is notthing more we can do except TODO log a
+ * There is nothing more we can do except TODO log a
* message for the repair tool to look at this problem.
*/
return true;
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
index 180dfdb..4ad583d 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
@@ -28,25 +28,38 @@
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.NoSuchElementException;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
import org.opends.server.DirectoryServerTestCase;
import org.opends.server.TestCaseUtils;
+import org.opends.server.schema.IntegerSyntax;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.synchronization.common.ServerState;
import org.opends.server.synchronization.plugin.ChangelogBroker;
import org.opends.server.synchronization.plugin.MultimasterSynchronization;
import org.opends.server.synchronization.plugin.PersistentServerState;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
+import org.opends.server.types.ByteStringFactory;
+import org.opends.server.types.SearchScope;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.LockManager;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeValue;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
@@ -295,4 +308,84 @@
entryList.add(synchroServerEntry.getDN());
}
+ /**
+ * Retrieve the number of replayed updates for a given synchronization
+ * domain from the monitor entry.
+ * @return The number of replayed updates.
+ * @throws Exception If an error occurs.
+ */
+ protected long getReplayedUpdatesCount(DN syncDN) throws Exception
+ {
+ String monitorFilter =
+ "(&(cn=synchronization*)(base-dn=" + syncDN + "))";
+
+ InternalSearchOperation op;
+ op = connection.processSearch(
+ ByteStringFactory.create("cn=monitor"),
+ SearchScope.SINGLE_LEVEL,
+ LDAPFilter.decode(monitorFilter));
+ SearchResultEntry entry = op.getSearchEntries().getFirst();
+
+ AttributeType attrType =
+ DirectoryServer.getDefaultAttributeType("replayed-updates");
+ return entry.getAttributeValue(attrType, IntegerSyntax.DECODER).longValue();
+ }
+
+ /**
+ * Check that the entry with the given dn has the given valueString value
+ * for the given attrTypeStr attribute type.
+ */
+ protected boolean checkEntryHasAttribute(DN dn, String attrTypeStr,
+ String valueString, int timeout, boolean hasAttribute) throws Exception
+ {
+ boolean found;
+ int count = timeout/100;
+ if (count<1)
+ count=1;
+
+ do
+ {
+ Entry newEntry;
+ Lock lock = null;
+ for (int j=0; j < 3; j++)
+ {
+ 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)
+ fail("The entry " + dn +
+ " has incorrectly been deleted from the database.");
+ List<Attribute> tmpAttrList = newEntry.getAttribute(attrTypeStr);
+ Attribute tmpAttr = tmpAttrList.get(0);
+
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(attrTypeStr, true);
+ found = tmpAttr.hasValue(new AttributeValue(attrType, valueString));
+
+ }
+ finally
+ {
+ LockManager.unlock(dn, lock);
+ }
+
+ if (found != hasAttribute)
+ Thread.sleep(100);
+ } while ((--count > 0) && (found != hasAttribute));
+ return found;
+ }
+
}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
index 39284d0..031de12 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
@@ -38,7 +38,6 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.plugins.ShortCircuitPlugin;
import org.opends.server.schema.DirectoryStringSyntax;
-import org.opends.server.schema.IntegerSyntax;
import org.opends.server.synchronization.common.ChangeNumberGenerator;
import org.opends.server.synchronization.plugin.ChangelogBroker;
import org.opends.server.synchronization.protocol.AddMsg;
@@ -55,8 +54,6 @@
import org.opends.server.core.Operation;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
-import org.opends.server.protocols.internal.InternalSearchOperation;
-import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.types.*;
@@ -117,9 +114,9 @@
+ "objectClass: organizationalUnit\n"
+ "entryUUID: 11111111-1111-1111-1111-111111111111\n";
Entry entry;
- for (int i = 0; i < topEntries.length; i++)
+ for (String entryStr : topEntries)
{
- entry = TestCaseUtils.entryFromLdifString(topEntries[i]);
+ entry = TestCaseUtils.entryFromLdifString(entryStr);
AddOperation addOp = new AddOperation(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
@@ -321,7 +318,7 @@
logError(ErrorLogCategory.SYNCHRONIZATION,
ErrorLogSeverity.NOTICE,
"Starting synchronization test : lostHeartbeatFailover" , 1);
-
+
cleanEntries();
final DN baseDn = DN.decode("ou=People,dc=example,dc=com");
@@ -745,9 +742,9 @@
+ "objectClass: organizationalUnit\n"
+ "entryUUID: 55555555-5555-5555-5555-555555555555\n";
Entry entry;
- for (int i = 0; i < topEntries.length; i++)
+ for (String entryStr : topEntries)
{
- entry = TestCaseUtils.entryFromLdifString(topEntries[i]);
+ entry = TestCaseUtils.entryFromLdifString(entryStr);
AddOperation addOp = new AddOperation(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
@@ -785,9 +782,9 @@
"Entry not moved from ou=baseDn1,"+baseDn+" to ou=baseDn2,"+baseDn);
// - add new parent entry 2 with baseDn1
- String p2 = new String("dn: ou=baseDn1,"+baseDn+"\n" + "objectClass: top\n"
- + "objectClass: organizationalUnit\n"
- + "entryUUID: 66666666-6666-6666-6666-666666666666\n");
+ String p2 = "dn: ou=baseDn1,"+baseDn+"\n" + "objectClass: top\n"
+ + "objectClass: organizationalUnit\n"
+ + "entryUUID: 66666666-6666-6666-6666-666666666666\n";
entry = TestCaseUtils.entryFromLdifString(p2);
AddOperation addOp = new AddOperation(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
@@ -1020,9 +1017,6 @@
}
}
- /**
- * @return
- */
private List<Modification> generatemods(String attrName, String attrValue)
{
AttributeType attrType =
@@ -1037,63 +1031,6 @@
}
/**
- * Check that the entry with the given dn has the given valueString value
- * for the given attrTypeStr attribute type.
- */
- private boolean checkEntryHasAttribute(DN dn, String attrTypeStr,
- String valueString, int timeout, boolean hasAttribute) throws Exception
- {
- boolean found;
- int count = timeout/100;
- if (count<1)
- count=1;
-
- do
- {
- Entry newEntry;
- Lock lock = null;
- for (int j=0; j < 3; j++)
- {
- lock = LockManager.lockRead(dn);
- if (lock != null)
- {
- break;
- }
- }
-
- if (lock == null)
- {
- throw new Exception("could not lock entry " + dn);
- }
-
- try
- {
- newEntry = DirectoryServer.getEntry(personWithUUIDEntry.getDN());
-
-
- if (newEntry == null)
- fail("The entry " + personWithUUIDEntry.getDN() +
- " has incorrectly been deleted from the database.");
- List<Attribute> tmpAttrList = newEntry.getAttribute(attrTypeStr);
- Attribute tmpAttr = tmpAttrList.get(0);
-
- AttributeType attrType =
- DirectoryServer.getAttributeType(attrTypeStr, true);
- found = tmpAttr.hasValue(new AttributeValue(attrType, valueString));
-
- }
- finally
- {
- LockManager.unlock(dn, lock);
- }
-
- if (found != hasAttribute)
- Thread.sleep(100);
- } while ((--count > 0) && (found != hasAttribute));
- return found;
- }
-
- /**
* Get the entryUUID for a given DN.
*
* @throws Exception if the entry does not exist or does not have
@@ -1272,7 +1209,7 @@
assertEquals(addOp.getResultCode(), ResultCode.SUCCESS);
entryList.add(tmp.getDN());
- long initialCount = getReplayedUpdatesCount();
+ long initialCount = getReplayedUpdatesCount(baseDn);
// Get the UUID of the test entry.
Entry resultEntry = getEntry(tmp.getDN(), 1, true);
@@ -1295,7 +1232,7 @@
// Wait for the operation to be replayed.
long endTime = System.currentTimeMillis() + 5000;
- while (getReplayedUpdatesCount() == initialCount &&
+ while (getReplayedUpdatesCount(baseDn) == initialCount &&
System.currentTimeMillis() < endTime)
{
Thread.sleep(100);
@@ -1309,7 +1246,7 @@
// If the synchronization replay loop was detected and broken then the
// counter will still be updated even though the replay was unsuccessful.
- if (getReplayedUpdatesCount() == initialCount)
+ if (getReplayedUpdatesCount(baseDn) == initialCount)
{
fail("Synchronization operation was not replayed");
}
@@ -1321,28 +1258,6 @@
}
/**
- * Retrieve the number of replayed updates from the monitor entry.
- * @return The number of replayed updates.
- * @throws Exception If an error occurs.
- */
- private long getReplayedUpdatesCount() throws Exception
- {
- String monitorFilter =
- "(&(cn=synchronization*)(base-dn=ou=People,dc=example,dc=com))";
-
- InternalSearchOperation op;
- op = connection.processSearch(
- ByteStringFactory.create("cn=monitor"),
- SearchScope.SINGLE_LEVEL,
- LDAPFilter.decode(monitorFilter));
- SearchResultEntry entry = op.getSearchEntries().getFirst();
-
- AttributeType attrType =
- DirectoryServer.getDefaultAttributeType("replayed-updates");
- return entry.getAttributeValue(attrType, IntegerSyntax.DECODER).longValue();
- }
-
- /**
* Enable or disable the receive status of a synchronization provider.
*
* @param syncConfigDN The DN of the synchronization provider configuration
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java
index fdf922f..3a1c112 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java
@@ -28,18 +28,24 @@
package org.opends.server.synchronization.plugin;
import org.opends.server.synchronization.SynchronizationTestCase;
+import org.opends.server.synchronization.protocol.ModifyMsg;
+import org.opends.server.synchronization.common.ChangeNumber;
import org.opends.server.TestCaseUtils;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.tools.LDAPModify;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.Attribute;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.AttributeType;
import org.opends.server.core.DirectoryServer;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeClass;
import static org.testng.Assert.assertEquals;
import java.util.List;
+import java.util.ArrayList;
/**
* Tests the Historical class.
@@ -122,10 +128,12 @@
"sn: Amar",
"givenName: Aaccf",
"userPassword: password",
- "description: Initial description"
+ "description: Initial description",
+ "displayName: 1"
);
// Modify the test entry to give it some history.
+ // Test both single and multi-valued attributes.
String path = TestCaseUtils.createTempFile(
"dn: uid=user.1,o=test",
@@ -136,6 +144,12 @@
"-",
"replace: description",
"description: replaced description",
+ "-",
+ "add: displayName",
+ "displayName: 2",
+ "-",
+ "delete: displayName",
+ "displayName: 1",
"-"
);
@@ -150,17 +164,172 @@
assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+ args[9] = TestCaseUtils.createTempFile(
+ "dn: uid=user.1,o=test",
+ "changetype: modify",
+ "replace: displayName",
+ "displayName: 2",
+ "-"
+ );
+
+ assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
// Read the entry back to get its history operational attribute.
DN dn = DN.decode("uid=user.1,o=test");
Entry entry = DirectoryServer.getEntry(dn);
+ List<Attribute> attrs = entry.getAttribute(Historical.historicalAttrType);
+ Attribute before = attrs.get(0);
+
// Check that encoding and decoding preserves the history information.
Historical hist = Historical.load(entry);
Attribute after = hist.encode();
- List<Attribute> attrs = entry.getAttribute(Historical.historicalAttrType);
- Attribute before = attrs.get(0);
-
assertEquals(after, before);
}
+
+ /**
+ * The scenario for this test case is that two modify operations occur at
+ * two different servers at nearly the same time, each operation adding a
+ * different value for a single-valued attribute. Synchronization then
+ * replays the operations and we expect the conflict to be resolved on both
+ * servers by keeping whichever value was actually added first.
+ * For the unit test, we employ a single directory server. We use the
+ * broker API to simulate the ordering that would happen on the first server
+ * on one entry, and the reverse ordering that would happen on the
+ * second server on a different entry. Confused yet?
+ * @throws Exception If the test fails.
+ */
+ @Test(enabled=false, groups="slow")
+ public void conflictSingleValue()
+ throws Exception
+ {
+ final DN dn1 = DN.decode("cn=test1,o=test");
+ final DN dn2 = DN.decode("cn=test2,o=test");
+ final DN baseDn = DN.decode("o=test");
+ final AttributeType attrType =
+ DirectoryServer.getAttributeType("displayname");
+ final AttributeType entryuuidType =
+ DirectoryServer.getAttributeType("entryuuid");
+
+ /*
+ * Open a session to the changelog server using the broker API.
+ * This must use a different serverId to that of the directory server.
+ */
+ ChangelogBroker broker =
+ openChangelogSession(baseDn, (short) 2, 100, 8989, 1000, true);
+
+
+ // Clear the backend.
+ TestCaseUtils.initializeTestBackend(true);
+
+ // Add the first test entry.
+ TestCaseUtils.addEntry(
+ "dn: cn=test1,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: test1",
+ "sn: test"
+ );
+
+ // Read the entry back to get its UUID.
+ Entry entry = DirectoryServer.getEntry(dn1);
+ List<Attribute> attrs = entry.getAttribute(entryuuidType);
+ String entryuuid =
+ attrs.get(0).getValues().iterator().next().getStringValue();
+
+ // Add the second test entry.
+ TestCaseUtils.addEntry(
+ "dn: cn=test2,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: test2",
+ "sn: test",
+ "description: Description"
+ );
+
+ // Read the entry back to get its UUID.
+ entry = DirectoryServer.getEntry(dn2);
+ attrs = entry.getAttribute(entryuuidType);
+ String entryuuid2 =
+ attrs.get(0).getValues().iterator().next().getStringValue();
+
+ // A change on a first server.
+ ChangeNumber t1 = new ChangeNumber(1, (short) 0, (short) 3);
+
+ // A change on a second server.
+ ChangeNumber t2 = new ChangeNumber(2, (short) 0, (short) 4);
+
+ // Simulate the ordering t1:add:A followed by t2:add:B that would
+ // happen on one server.
+
+ // Replay an add of a value A at time t1 on a first server.
+ Attribute attr = new Attribute(attrType.getNormalizedPrimaryName(), "A");
+ Modification mod = new Modification(ModificationType.ADD, attr);
+ publishModify(broker, t1, dn1, entryuuid, mod);
+
+ // It would be nice to avoid these sleeps.
+ // We need to preserve the replay order but the order could be changed
+ // due to the multi-threaded nature of the synchronization replay.
+ // Putting a sentinel value in the modification is not foolproof since
+ // the operation might not get replayed at all.
+ Thread.sleep(2000);
+
+ // Replay an add of a value B at time t2 on a second server.
+ attr = new Attribute(attrType.getNormalizedPrimaryName(), "B");
+ mod = new Modification(ModificationType.ADD, attr);
+ publishModify(broker, t2, dn1, entryuuid, mod);
+
+ Thread.sleep(2000);
+
+ // Simulate the reverse ordering t2:add:B followed by t1:add:A that
+ // would happen on the other server.
+
+ t1 = new ChangeNumber(3, (short) 0, (short) 3);
+ t2 = new ChangeNumber(4, (short) 0, (short) 4);
+
+ // Replay an add of a value B at time t2 on a second server.
+ attr = new Attribute(attrType.getNormalizedPrimaryName(), "B");
+ mod = new Modification(ModificationType.ADD, attr);
+ publishModify(broker, t2, dn2, entryuuid2, mod);
+
+ Thread.sleep(2000);
+
+ // Replay an add of a value A at time t1 on a first server.
+ attr = new Attribute(attrType.getNormalizedPrimaryName(), "A");
+ mod = new Modification(ModificationType.ADD, attr);
+ publishModify(broker, t1, dn2, entryuuid2, mod);
+
+ Thread.sleep(2000);
+
+ // Read the first entry to see how the conflict was resolved.
+ entry = DirectoryServer.getEntry(dn1);
+ attrs = entry.getAttribute(attrType);
+ String attrValue1 =
+ attrs.get(0).getValues().iterator().next().getStringValue();
+
+ // Read the second entry to see how the conflict was resolved.
+ entry = DirectoryServer.getEntry(dn2);
+ attrs = entry.getAttribute(attrType);
+ String attrValue2 =
+ attrs.get(0).getValues().iterator().next().getStringValue();
+
+ // The two values should be the first value added.
+ assertEquals(attrValue1, "A");
+ assertEquals(attrValue2, "A");
+ }
+
+ private static
+ void publishModify(ChangelogBroker broker, ChangeNumber changeNum,
+ DN dn, String entryuuid, Modification mod)
+ {
+ List<Modification> mods = new ArrayList<Modification>(1);
+ mods.add(mod);
+ ModifyMsg modMsg = new ModifyMsg(changeNum, dn, mods, entryuuid);
+ broker.publish(modMsg);
+ }
}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java
index f87a450..fd3ea0d 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java
@@ -74,8 +74,7 @@
/**
* Test that conflict between a modify-replace and modify-add for
- * multi-valued attributes@DataProvider(name = "ackMsg") are handled
- * correctly.
+ * multi-valued attributes are handled correctly.
*/
@Test()
public void replaceAndAdd() throws Exception
--
Gitblit v1.10.0