From 1afbb2969f0f5bb254f2c777f57803635be23a9e Mon Sep 17 00:00:00 2001
From: gbellato <gbellato@localhost>
Date: Mon, 11 Jun 2007 10:08:13 +0000
Subject: [PATCH] Final part for issue 604 : replication needs to resolve naming conflicts
---
opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java | 25 ++
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java | 190 +++++++++++++--
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java | 32 ++
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java | 458 +++++++++++++++++++++++++++----------
opendj-sdk/opends/resource/schema/02-config.ldif | 3
5 files changed, 546 insertions(+), 162 deletions(-)
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index 21bb051..dd453eb 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/opends/resource/schema/02-config.ldif
@@ -1276,6 +1276,9 @@
NAME 'ds-cfg-file-size-limit'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.380 NAME 'ds-sync-conflict'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ USAGE directoryOperation X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java
index fb4e3cf..85f2e53 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java
@@ -401,6 +401,24 @@
public static final int MSGID_BAD_HISTORICAL =
CATEGORY_MASK_SYNC | SEVERITY_MASK_SEVERE_ERROR | 56;
+ /**
+ * Could not add the conflict attribute to an entry after a conflict was
+ * deteceted.
+ */
+ public static final int MSGID_CANNOT_ADD_CONFLICT_ATTRIBUTE =
+ CATEGORY_MASK_SYNC | SEVERITY_MASK_SEVERE_ERROR | 57;
+
+ /**
+ * Could not rename a conflicting entry.
+ */
+ public static final int MSGID_CANNOT_RENAME_CONFLICT_ENTRY =
+ CATEGORY_MASK_SYNC | SEVERITY_MASK_SEVERE_ERROR | 58;
+
+ /**
+ * Eception durin rename of a conflicting entry.
+ */
+ public static final int MSGID_EXCEPTION_RENAME_CONFLICT_ENTRY =
+ CATEGORY_MASK_SYNC | SEVERITY_MASK_SEVERE_ERROR | 59;
/**
* Register the messages from this class in the core server.
@@ -550,5 +568,12 @@
registerMessage(MSGID_BAD_HISTORICAL,
"Entry %s was containing some unknown historical information,"
+ " This may cause some inconsistency for this entry");
+ registerMessage(MSGID_CANNOT_ADD_CONFLICT_ATTRIBUTE,
+ "A conflict was detected but the conflict information could not be" +
+ "added. Operation : ");
+ registerMessage(MSGID_CANNOT_RENAME_CONFLICT_ENTRY,
+ "An error happened trying the rename a conflicting entry : ");
+ registerMessage(MSGID_EXCEPTION_RENAME_CONFLICT_ENTRY,
+ "An Exception happened when trying the rename a conflicting entry : ");
}
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
index 4275f74..3d76049 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
@@ -75,6 +75,7 @@
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.protocol.AckMessage;
import org.opends.server.replication.protocol.AddContext;
+import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteContext;
import org.opends.server.replication.protocol.DoneMessage;
import org.opends.server.replication.protocol.EntryMessage;
@@ -91,6 +92,9 @@
import org.opends.server.tasks.InitializeTargetTask;
import org.opends.server.tasks.InitializeTask;
import org.opends.server.tasks.TaskUtils;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.DereferencePolicy;
@@ -102,6 +106,7 @@
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
import org.opends.server.types.Operation;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
@@ -122,6 +127,13 @@
implements ConfigurationChangeListener<MultimasterDomainCfg>
{
/**
+ * The attribute used to mark conflicting entries.
+ * The value of this attribute should be the dn that this entry was
+ * supposed to have when it was marked as conflicting.
+ */
+ public static final String DS_SYNC_CONFLICT = "ds-sync-conflict";
+
+ /**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
@@ -1117,10 +1129,11 @@
else if (op instanceof AddOperation)
{
AddOperation newOp = (AddOperation) op;
+ AddMsg addMsg = (AddMsg) msg;
dependency = pendingChanges.checkDependencies(newOp);
if (!dependency)
{
- done = solveNamingConflict(newOp, msg);
+ done = solveNamingConflict(newOp, addMsg);
}
}
else if (op instanceof ModifyDNOperation)
@@ -1165,10 +1178,6 @@
updateError(changeNumber);
}
- else
- {
- numResolvedNamingConflicts.incrementAndGet();
- }
}
catch (ASN1Exception e)
{
@@ -1353,26 +1362,38 @@
if (result == ResultCode.NO_SUCH_OBJECT)
{
/*
- * This error may happen the operation is a modification but
- * the entry had been renamed on a different master in the same time.
+ * The operation is a modification but
+ * the entry has been renamed on a different master in the same time.
* search if the entry has been renamed, and return the new dn
* of the entry.
*/
DN newdn = findEntryDN(entryUid);
if (newdn != null)
{
+ // There is an entry with the same unique id as this modify operation
+ // replay the modify using the current dn of this entry.
msg.setDn(newdn.toString());
+ numResolvedNamingConflicts.incrementAndGet();
return false;
}
else
+ {
+ // This entry does not exist anymore.
+ // It has probably been deleted, stop the processing of this operation
+ numResolvedNamingConflicts.incrementAndGet();
return true;
+ }
}
-
- // TODO log a message for the repair tool.
- return true;
+ else
+ {
+ // The other type of errors can not be caused by naming conflicts.
+ // TODO log a message for the repair tool.
+ return true;
+ }
}
- /** Solve a conflict detected when replaying a delete operation.
+ /**
+ * Solve a conflict detected when replaying a delete operation.
*
* @param op The operation that triggered the conflict detection.
* @param msg The operation that triggered the conflict detection.
@@ -1399,6 +1420,7 @@
* has already done the job.
* In any case, there is is nothing more to do.
*/
+ numResolvedNamingConflicts.incrementAndGet();
return true;
}
else
@@ -1407,6 +1429,7 @@
* This entry has been renamed, replay the delete using its new DN.
*/
msg.setDn(currentDn.toString());
+ numResolvedNamingConflicts.incrementAndGet();
return false;
}
}
@@ -1415,17 +1438,129 @@
/*
* This may happen when we replay a DELETE done on a master
* but children of this entry have been added on another master.
+ *
+ * Rename all the children by adding entryuuid in dn and delete this entry.
+ *
+ * The action taken here must be consistent with the actions
+ * done in the solveNamingConflict(AddOperation) method
+ * when we are adding an entry whose parent entry has already been deleted.
*/
-
- /*
- * TODO : either delete all the childs or rename the child below
- * the top suffix by adding entryuuid in dn and delete this entry.
- */
+ findAndRenameChild(entryUid, op.getEntryDN(), op);
+ numUnresolvedNamingConflicts.incrementAndGet();
+ return false;
}
- return true;
+ else
+ {
+ // The other type of errors can not be caused by naming conflicts.
+ // TODO log a message for the repair tool.
+ return true;
+ }
}
/**
+ * Solve a conflict detected when replaying a Modify DN operation.
+ *
+ * @param op The operation that triggered the conflict detection.
+ * @param msg The operation that triggered the conflict detection.
+ * @return true if the process is completed, false if it must continue.
+ * @throws Exception When the operation is not valid.
+ */
+private boolean solveNamingConflict(ModifyDNOperation op,
+ UpdateMessage msg) throws Exception
+{
+ ResultCode result = op.getResultCode();
+ ModifyDnContext ctx = (ModifyDnContext) op.getAttachment(SYNCHROCONTEXT);
+ String entryUid = ctx.getEntryUid();
+ String newSuperiorID = ctx.getNewParentId();
+
+ /*
+ * four possible cases :
+ * - the modified entry has been renamed
+ * - the new parent has been renamed
+ * - the operation is replayed for the second time.
+ * - the entry has been deleted
+ * action :
+ * - change the target dn and the new parent dn and
+ * restart the operation,
+ * - don't do anything if the operation is replayed.
+ */
+
+ // Construct the new DN to use for the entry.
+ DN entryDN = op.getEntryDN();
+ DN newSuperior = findEntryDN(newSuperiorID);
+ RDN newRDN = op.getNewRDN();
+ DN parentDN;
+
+ if (newSuperior == null)
+ {
+ parentDN = entryDN.getParent();
+ }
+ else
+ {
+ parentDN = newSuperior;
+ }
+
+ if ((parentDN == null) || parentDN.isNullDN())
+ {
+ /* this should never happen
+ * can't solve any conflict in this case.
+ */
+ throw new Exception("operation parameters are invalid");
+ }
+
+ DN newDN = parentDN.concat(newRDN);
+
+ // get the current DN of this entry in the database.
+ DN currentDN = findEntryDN(entryUid);
+
+ // if the newDN and the current DN match then the operation
+ // is a no-op (this was probably a second replay)
+ // don't do anything.
+ if (newDN.equals(currentDN))
+ {
+ numResolvedNamingConflicts.incrementAndGet();
+ return true;
+ }
+
+ if ((result == ResultCode.NO_SUCH_OBJECT) ||
+ (result == ResultCode.OBJECTCLASS_VIOLATION))
+ {
+ /*
+ * The entry or it's new parent has not been found
+ * reconstruct the operation with the DN we just built
+ */
+ ModifyDNMsg modifyDnMsg = (ModifyDNMsg) msg;
+ msg.setDn(currentDN.toString());
+ modifyDnMsg.setNewSuperior(newSuperior.toString());
+ numResolvedNamingConflicts.incrementAndGet();
+ return false;
+ }
+ else if (result == ResultCode.ENTRY_ALREADY_EXISTS)
+ {
+ /*
+ * This may happen when two modifyDn operation
+ * are done on different servers but with the same target DN
+ * add the conflict object class to the entry
+ * and rename it using its entryuuid.
+ */
+ ModifyDNMsg modifyDnMsg = (ModifyDNMsg) msg;
+ markConflictEntry(op, op.getEntryDN(), newDN);
+ modifyDnMsg.setNewRDN(generateConflictRDN(entryUid,
+ modifyDnMsg.getNewRDN()));
+ modifyDnMsg.setNewSuperior(newSuperior.toString());
+ numUnresolvedNamingConflicts.incrementAndGet();
+ return false;
+ }
+ else
+ {
+ // The other type of errors can not be caused by naming conflicts.
+ // TODO log a message for the repair tool.
+ return true;
+ }
+}
+
+
+ /**
* Solve a conflict detected when replaying a ADD operation.
*
* @param op The operation that triggered the conflict detection.
@@ -1434,7 +1569,7 @@
* @throws Exception When the operation is not valid.
*/
private boolean solveNamingConflict(AddOperation op,
- UpdateMessage msg) throws Exception
+ AddMsg msg) throws Exception
{
ResultCode result = op.getResultCode();
AddContext ctx = (AddContext) op.getAttachment(SYNCHROCONTEXT);
@@ -1451,7 +1586,7 @@
{
/*
* This entry is the base dn of the backend.
- * It is quite weird that the operation result be NO_SUCH_OBJECT.
+ * It is quite surprising that the operation result be NO_SUCH_OBJECT.
* There is nothing more we can do except TODO log a
* message for the repair tool to look at this problem.
*/
@@ -1461,15 +1596,28 @@
if (parentDn == null)
{
/*
- * The parent has been deleted, so this entry should not
- * exist don't do the ADD.
+ * The parent has been deleted
+ * rename the entry as a conflicting entry.
+ * The action taken here must be consistent with the actions
+ * done when in the solveNamingConflict(DeleteOperation) method
+ * when we are deleting an entry that have some child entries.
*/
- return true;
+ addConflict(msg);
+
+ msg.setDn(generateConflictRDN(entryUid,
+ op.getEntryDN().getRDN().toString()) + ","
+ + baseDN);
+ // reset the parent uid so that the check done is the handleConflict
+ // phase does not fail.
+ msg.setParentUid(null);
+ numUnresolvedNamingConflicts.incrementAndGet();
+ return false;
}
else
{
RDN entryRdn = DN.decode(msg.getDn()).getRDN();
msg.setDn(entryRdn + "," + parentDn);
+ numResolvedNamingConflicts.incrementAndGet();
return false;
}
}
@@ -1491,132 +1639,158 @@
}
else
{
- addConflict(op);
- msg.setDn(generateConflictDn(entryUid, msg.getDn()));
+ addConflict(msg);
+ msg.setDn(generateConflictRDN(entryUid, msg.getDn()));
+ numUnresolvedNamingConflicts.incrementAndGet();
return false;
}
}
- return true;
- }
-
- /**
- * Solve a conflict detected when replaying a Modify DN operation.
- *
- * @param op The operation that triggered the conflict detection.
- * @param msg The operation that triggered the conflict detection.
- * @return true if the process is completed, false if it must continue.
- * @throws Exception When the operation is not valid.
- */
- private boolean solveNamingConflict(ModifyDNOperation op,
- UpdateMessage msg) throws Exception
- {
- ResultCode result = op.getResultCode();
- ModifyDnContext ctx = (ModifyDnContext) op.getAttachment(SYNCHROCONTEXT);
- String entryUid = ctx.getEntryUid();
- String newSuperiorID = ctx.getNewParentId();
-
- /*
- * four possible cases :
- * - the modified entry has been renamed
- * - the new parent has been renamed
- * - the operation is replayed for the second time.
- * - the entry has been deleted
- * action :
- * - change the target dn and the new parent dn and
- * restart the operation,
- * - don't do anything if the operation is replayed.
- */
-
- // Construct the new DN to use for the entry.
- DN entryDN = op.getEntryDN();
- DN newSuperior = findEntryDN(newSuperiorID);
- RDN newRDN = op.getNewRDN();
- DN parentDN;
-
- if (newSuperior == null)
- {
- parentDN = entryDN.getParent();
- }
else
{
- parentDN = newSuperior;
- }
-
- if ((parentDN == null) || parentDN.isNullDN())
- {
- /* this should never happen
- * can't solve any conflict in this case.
- */
- throw new Exception("operation parameters are invalid");
- }
-
- DN newDN = parentDN.concat(newRDN);
-
- // get the current DN of this entry in the database.
- DN currentDN = findEntryDN(entryUid);
-
- // if the newDN and the current DN match then the operation
- // is a no-op (this was probably a second replay)
- // don't do anything.
- if (newDN.equals(currentDN))
- {
+ // The other type of errors can not be caused by naming conflicts.
+ // TODO log a message for the repair tool.
return true;
}
-
- if ((result == ResultCode.NO_SUCH_OBJECT) ||
- (result == ResultCode.OBJECTCLASS_VIOLATION))
- {
- /*
- * The entry or it's new parent has not been found
- * reconstruct the operation with the DN we just built
- */
- ModifyDNMsg modifyDnMsg = (ModifyDNMsg) msg;
- msg.setDn(currentDN.toString());
- modifyDnMsg.setNewSuperior(newSuperior.toString());
- numUnresolvedNamingConflicts.incrementAndGet();
- return false;
- }
- else if (result == ResultCode.ENTRY_ALREADY_EXISTS)
- {
- /*
- * This may happen when two modifyDn operation
- * are done on different servers but with the same target DN
- * add the conflict object class to the entry
- * and rename it using its entryuuid.
- */
- ModifyDNMsg modifyDnMsg = (ModifyDNMsg) msg;
- generateAddConflictOp(op);
- modifyDnMsg.setNewRDN(generateConflictDn(entryUid,
- modifyDnMsg.getNewRDN()));
- modifyDnMsg.setNewSuperior(newSuperior.toString());
- numUnresolvedNamingConflicts.incrementAndGet();
- return false;
- }
- return true;
}
/**
- * Generate a modification to add the conflict ObjectClass to an entry
+ * Find all the entries below the provided DN and rename them
+ * so that they stay below the baseDn of this replicationDomain and
+ * use the conflicting name and attribute.
+ *
+ * @param entryUid The unique ID of the entry whose child must be renamed.
+ * @param entryDN The DN of the entry whose child must be renamed.
+ * @param conflictOp The Operation that generated the conflict.
+ */
+ private void findAndRenameChild(
+ String entryUid, DN entryDN, Operation conflictOp)
+ {
+ // Find an rename child entries.
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+
+ try
+ {
+ LinkedHashSet<String> attrs = new LinkedHashSet<String>(1);
+ attrs.add(ENTRYUIDNAME);
+
+ SearchFilter ALLMATCH;
+ ALLMATCH = SearchFilter.createFilterFromString("(objectClass=*)");
+ InternalSearchOperation op =
+ conn.processSearch(entryDN, SearchScope.SINGLE_LEVEL,
+ DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false, ALLMATCH,
+ attrs);
+
+ if (op.getResultCode() == ResultCode.SUCCESS)
+ {
+ LinkedList<SearchResultEntry> entries = op.getSearchEntries();
+ if (entries != null)
+ {
+ for (SearchResultEntry entry : entries)
+ {
+ markConflictEntry(conflictOp, entry.getDN(), entryDN);
+ renameConflictEntry(conflictOp, entry.getDN(),
+ Historical.getEntryUuid(entry));
+ }
+ }
+ }
+ else
+ {
+ int msgID = MSGID_CANNOT_RENAME_CONFLICT_ENTRY;
+ String message = getMessage(msgID) + entryDN + " " + conflictOp + " "
+ + op.getResultCode();
+ logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_ERROR,
+ message, msgID);
+ // TODO : log error and information for the REPAIR tool.
+ }
+ } catch (DirectoryException e)
+ {
+ int msgID = MSGID_EXCEPTION_RENAME_CONFLICT_ENTRY;
+ String message = getMessage(msgID) + entryDN + " " + conflictOp + " " + e;
+ logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_ERROR,
+ message, msgID);
+ // TODO log errror and information for the REPAIR tool.
+ }
+ }
+
+
+ /**
+ * Rename an entry that was conflicting so that it stays below the
+ * baseDN of the replicationDomain.
+ *
+ * @param conflictOp The Operation that caused the conflict.
+ * @param dn The DN of the entry to be renamed.
+ * @param uid The uniqueID of the entry to be renamed.
+ */
+ private void renameConflictEntry(Operation conflictOp, DN dn, String uid)
+ {
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+
+ ModifyDNOperation newOp = conn.processModifyDN(
+ dn, generateDeleteConflictDn(uid, dn),false, baseDN);
+
+ if (newOp.getResultCode() != ResultCode.SUCCESS)
+ {
+ int msgID = MSGID_CANNOT_RENAME_CONFLICT_ENTRY;
+ String message = getMessage(msgID) + dn + " " + conflictOp + " "
+ + newOp.getResultCode();
+ logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_ERROR,
+ message, msgID);
+ /*
+ * TODO : REPAIR should log information for the repair tool.
+ */
+ }
+ }
+
+
+ /**
+ * Generate a modification to add the conflict attribute to an entry
* whose Dn is now conflicting with another entry.
*
- * @param op The operation causing the conflict.
+ * @param op The operation causing the conflict.
+ * @param currentDN The current DN of the operation to mark as conflicting.
+ * @param conflictDN The newDn on which the conflict happened.
*/
- private void generateAddConflictOp(ModifyDNOperation op)
+ private void markConflictEntry(Operation op, DN currentDN, DN conflictDN)
{
- // TODO
+ // create new internal modify operation and run it.
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(DS_SYNC_CONFLICT, true);
+ LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
+ values.add(new AttributeValue(attrType, conflictDN.toString()));
+ Attribute attr = new Attribute(attrType, DS_SYNC_CONFLICT, values);
+ List<Modification> mods = new ArrayList<Modification>();
+ Modification mod = new Modification(ModificationType.REPLACE, attr);
+ mods.add(mod);
+ ModifyOperation newOp = conn.processModify(currentDN, mods);
+ if (newOp.getResultCode() != ResultCode.SUCCESS)
+ {
+ int msgID = MSGID_CANNOT_ADD_CONFLICT_ATTRIBUTE;
+ String message = getMessage(msgID) + op + " " + newOp.getResultCode();
+ logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_ERROR,
+ message, msgID);
+ /*
+ * TODO : REPAIR should log information for the repair tool.
+ */
+ }
}
/**
* Add the conflict object class to an entry that could
* not be added because it is conflicting with another entry.
*
- * @param addOp The conflicting Add Operation.
+ * @param addOp The conflicting Add Operation.
+ *
+ * @throws ASN1Exception When an encoding error happenned manipulating the
+ * msg.
*/
- private void addConflict(AddOperation addOp)
+ private void addConflict(AddMsg msg) throws ASN1Exception
{
- /*
- * TODO
- */
+ msg.addAttribute(DS_SYNC_CONFLICT, msg.getDn());
}
/**
@@ -1624,12 +1798,36 @@
*
* @param entryUid The unique identifier of the entry involved in the
* conflict.
- * @param dn Original dn.
- * @return The generated Dn for a conflicting entry.
+ * @param rdn Original rdn.
+ * @return The generated RDN for a conflicting entry.
*/
- private String generateConflictDn(String entryUid, String dn)
+ private String generateConflictRDN(String entryUid, String rdn)
{
- return "entryuuid=" + entryUid + "+" + dn;
+ return "entryuuid=" + entryUid + "+" + rdn;
+ }
+
+ /**
+ * Generate the RDN to use for a conflicting entry whose father was deleted.
+ *
+ * @param entryUid The unique identifier of the entry involved in the
+ * conflict.
+ * @param dn The original DN of the entry.
+ *
+ * @return The generated RDN for a conflicting entry.
+ * @throws DirectoryException
+ */
+ private RDN generateDeleteConflictDn(String entryUid, DN dn)
+ {
+ String newRDN = "entryuuid=" + entryUid + "+" + dn.getRDN();
+ RDN rdn = null;
+ try
+ {
+ rdn = RDN.decode(newRDN);
+ } catch (DirectoryException e)
+ {
+ // cannot happen
+ }
+ return rdn;
}
/**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
index f93bb98..5979842 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
@@ -57,8 +57,8 @@
public class AddMsg extends UpdateMessage
{
private static final long serialVersionUID = -4905520652801395185L;
- private final byte[] encodedAttributes;
- private final String parentUniqueId;
+ private byte[] encodedAttributes;
+ private String parentUniqueId;
/**
* Creates a new AddMessage.
@@ -263,4 +263,32 @@
{
return ("ADD " + getDn() + " " + getChangeNumber());
}
+
+ /**
+ * Add the specified attribute/attribute value in the entry contained
+ * in this AddMsg.
+ *
+ * @param name The name of the attribute to add.
+ * @param value The value of the attribute to add.
+ * @throws ASN1Exception When this Msg is not valid.
+ */
+ public void addAttribute(String name, String value)
+ throws ASN1Exception
+ {
+ RawAttribute newAttr = new LDAPAttribute(name, value);
+ ArrayList<ASN1Element> elems;
+ elems = ASN1Element.decodeElements(encodedAttributes);
+ elems.add(newAttr.encode());
+ encodedAttributes = ASN1Element.encodeValue(elems);
+ }
+
+ /**
+ * Set the parent unique id of this add msg.
+ *
+ * @param uid the parent unique id.
+ */
+ public void setParentUid(String uid)
+ {
+ parentUniqueId = uid;
+ }
}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
index a3d33e4..3b63fc5 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
@@ -41,6 +41,7 @@
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.plugin.ReplicationBroker;
+import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.replication.protocol.HeartbeatThread;
@@ -87,6 +88,15 @@
*/
protected Entry personEntry;
private int replServerPort;
+ private String domain1uid;
+ private String domain2uid;
+ private String domain3uid;
+ private String domain1dn;
+ private String domain2dn;
+ private String domain3dn;
+ private Entry domain1;
+ private Entry domain2;
+ private Entry domain3;
/**
* Set up the environment for performing the tests in this Class.
@@ -111,16 +121,9 @@
topEntries[1] = "dn: ou=People,dc=example,dc=com\n" + "objectClass: top\n"
+ "objectClass: organizationalUnit\n"
+ "entryUUID: 11111111-1111-1111-1111-111111111111\n";
- Entry entry;
for (String entryStr : topEntries)
{
- entry = TestCaseUtils.entryFromLdifString(entryStr);
- AddOperation addOp = new AddOperation(connection,
- InternalClientConnection.nextOperationID(), InternalClientConnection
- .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
- entry.getUserAttributes(), entry.getOperationalAttributes());
- addOp.setInternalOperation(true);
- addOp.run();
+ addEntry(TestCaseUtils.entryFromLdifString(entryStr));
}
baseUUID = getEntryUUID(DN.decode("ou=People,dc=example,dc=com"));
@@ -216,11 +219,53 @@
+ "entryUUID: "+ user1entrysecondUUID + "\n";
personWithSecondUniqueID =
TestCaseUtils.entryFromLdifString(entryWithSecondUUID);
+
+
+ domain1dn = "dc=domain1,ou=People,dc=example,dc=com";
+ domain2dn = "dc=domain2,dc=domain1,ou=People,dc=example,dc=com";
+ domain3dn = "dc=domain3,dc=domain1,ou=People,dc=example,dc=com";
+ domain1 = TestCaseUtils.entryFromLdifString(
+ "dn:" + domain1dn + "\n"
+ + "objectClass:domain\n"
+ + "dc:domain1");
+ domain2 = TestCaseUtils.entryFromLdifString(
+ "dn:" + domain2dn + "\n"
+ + "objectClass:domain\n"
+ + "dc:domain2");
+ domain3 = TestCaseUtils.entryFromLdifString(
+ "dn:" + domain3dn + "\n"
+ + "objectClass:domain\n"
+ + "dc:domain3");
configureReplication();
}
/**
+ * Add an entry in the datatbase
+ *
+ */
+ private void addEntry(Entry entry) throws Exception
+ {
+ AddOperation addOp = new AddOperation(connection,
+ InternalClientConnection.nextOperationID(), InternalClientConnection
+ .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
+ entry.getUserAttributes(), entry.getOperationalAttributes());
+ addOp.setInternalOperation(true);
+ addOp.run();
+ assertNotNull(getEntry(entry.getDN(), 1000, true));
+ }
+
+ /**
+ * Delete an entry in the datatbase
+ *
+ */
+ private void delEntry(DN dn) throws Exception
+ {
+ connection.processDelete(dn);
+ assertNull(getEntry(dn, 1000, true));
+ }
+
+ /**
* Tests whether the synchronization provider receive status can be disabled
* then re-enabled.
* FIXME Enable this test when broker suspend/resume receive are implemented.
@@ -454,7 +499,7 @@
// Simulate the ordering t2:replace:B followed by t1:add:A that
updateMonitorCount(baseDn, monitorAttr);
-
+
// Replay a replace of a value B at time t2 on a second server.
Attribute attr = new Attribute(attrType.getNormalizedPrimaryName(), "B");
Modification mod = new Modification(ModificationType.REPLACE, attr);
@@ -484,7 +529,7 @@
// the value should be the last (time t2) value added
assertEquals(attrValue1, "B");
assertEquals(getMonitorDelta(), 1);
-
+
// Simulate the ordering t2:delete:displayname followed by
// t1:replace:displayname
// A change on a first server.
@@ -495,7 +540,7 @@
// Simulate the ordering t2:delete:displayname followed by t1:replace:A
updateMonitorCount(baseDn, monitorAttr);
-
+
// Replay an delete of attribute displayname at time t2 on a second server.
attr = new Attribute(attrType);
mod = new Modification(ModificationType.DELETE, attr);
@@ -521,9 +566,9 @@
attrs = entry.getAttribute(attrType);
// there should not be a value (delete at time t1)
- assertNull(attrs);
+ assertNull(attrs);
assertEquals(getMonitorDelta(), 1);
-
+
broker.stop();
}
@@ -545,7 +590,8 @@
"Starting replication test : namingConflicts" , 1);
final DN baseDn = DN.decode("ou=People,dc=example,dc=com");
- String monitorAttr = "resolved-naming-conflicts";
+ String resolvedMonitorAttr = "resolved-naming-conflicts";
+ String unresolvedMonitorAttr = "unresolved-naming-conflicts";
/*
* Open a session to the replicationServer using the ReplicationServer broker API.
@@ -588,7 +634,7 @@
ModifyMsg modMsg = new ModifyMsg(gen.newChangeNumber(),
DN.decode("cn=something,ou=People,dc=example,dc=com"), mods,
user1entryUUID);
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, resolvedMonitorAttr);
broker.publish(modMsg);
// check that the modify has been applied as if the entry had been renamed.
@@ -596,7 +642,7 @@
"telephonenumber", "01 02 45", 10000, true);
if (found == false)
fail("The modification has not been correctly replayed.");
- assertEquals(getMonitorDelta(), 1);
+ assertEquals(getMonitorDelta(), 1);
/*
* Test that the conflict resolution code is able to detect
@@ -626,7 +672,7 @@
mods = generatemods("telephonenumber", "02 01 03 05");
modMsg = new ModifyMsg(gen.newChangeNumber(),
DN.decode(user1dn), mods, "10000000-9abc-def0-1234-1234567890ab");
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, resolvedMonitorAttr);
broker.publish(modMsg);
// check that the modify has not been applied
@@ -649,7 +695,7 @@
DeleteMsg delMsg =
new DeleteMsg("cn=anotherdn,ou=People,dc=example,dc=com",
gen.newChangeNumber(), user1entryUUID);
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, resolvedMonitorAttr);
broker.publish(delMsg);
// check that the delete operation has been applied
@@ -684,7 +730,7 @@
user1entrysecondUUID, baseUUID,
personWithSecondUniqueID.getObjectClassAttribute(),
personWithSecondUniqueID.getAttributes(), new ArrayList<Attribute>());
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, unresolvedMonitorAttr);
broker.publish(addMsg);
// Check that the entry has been renamed and created in the local DS.
@@ -693,7 +739,8 @@
10000, true);
assertNotNull(resultEntry,
"The ADD replication message was not applied");
- assertEquals(getMonitorDelta(), 1);
+ assertEquals(getMonitorDelta(), 1);
+ assertConflictAttribute(resultEntry);
// delete the entries to clean the database.
delMsg =
@@ -722,7 +769,7 @@
baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, resolvedMonitorAttr);
broker.publish(addMsg);
// Check that the entry has been renamed and created in the local DS.
@@ -744,7 +791,7 @@
delMsg =
new DeleteMsg("uid=new person,ou=People,dc=example,dc=com",
gen.newChangeNumber(), "11111111-9abc-def0-1234-1234567890ab");
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, resolvedMonitorAttr);
broker.publish(delMsg);
resultEntry = getEntry(
DN.decode("uid=new person,ou=People,dc=example,dc=com"), 10000, true);
@@ -769,7 +816,7 @@
user1entryUUID, baseUUID, false,
"uid=wrong, ou=people,dc=example,dc=com",
"uid=newrdn");
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, resolvedMonitorAttr);
broker.publish(modDnMsg);
resultEntry = getEntry(
@@ -787,7 +834,7 @@
modDnMsg = new ModifyDNMsg(
"uid=wrong,ou=People,dc=example,dc=com", gen.newChangeNumber(),
user1entryUUID, baseUUID, false, null, "uid=reallynewrdn");
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, resolvedMonitorAttr);
broker.publish(modDnMsg);
resultEntry = getEntry(
@@ -810,7 +857,6 @@
baseUUID,
personWithSecondUniqueID.getObjectClassAttribute(),
personWithSecondUniqueID.getAttributes(), new ArrayList<Attribute>());
- updateMonitorCount(baseDn, monitorAttr);
broker.publish(addMsg);
// check that the second entry has been added
@@ -818,23 +864,21 @@
// check that the add operation has been applied
assertNotNull(resultEntry, "The add operation was not replayed");
- assertEquals(getMonitorDelta(), 1);
// try to rename the first entry
modDnMsg = new ModifyDNMsg(user1dn, gen.newChangeNumber(),
user1entrysecondUUID, baseUUID, false,
baseDn.toString(), "uid=reallynewrdn");
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, unresolvedMonitorAttr);
broker.publish(modDnMsg);
// check that the second entry has been renamed
resultEntry = getEntry(
DN.decode("entryUUID = " + user1entrysecondUUID + "+uid=reallynewrdn," +
"ou=People,dc=example,dc=com"), 10000, true);
-
- // check that the delete operation has been applied
assertNotNull(resultEntry, "The modifyDN was not or incorrectly replayed");
assertEquals(getMonitorDelta(), 1);
+ assertConflictAttribute(resultEntry);
// delete the entries to clean the database
delMsg =
@@ -940,7 +984,7 @@
// - publish msg
- updateMonitorCount(baseDn, monitorAttr);
+ updateMonitorCount(baseDn, resolvedMonitorAttr);
broker.publish(addMsg);
// - check that the Dn has been changed to baseDn2
@@ -955,11 +999,97 @@
"The ADD replication message was NOT applied under ou=baseDn2,"+baseDn);
entryList.add(resultEntry.getDN());
assertEquals(getMonitorDelta(), 1);
+
+ //
+ // Check that when a delete is conflicting with Add of some entries
+ // below the deleted entries, the child entry that have been added
+ // before the deleted is replayed gets renamed correctly.
+ //
+
+ // add domain1 entry with 2 childs domain2 and domain3
+ addEntry(domain1);
+ domain1uid = getEntryUUID(DN.decode(domain1dn));
+ addEntry(domain2);
+ domain2uid = getEntryUUID(DN.decode(domain2dn));
+ addEntry(domain3);
+ domain3uid = getEntryUUID(DN.decode(domain3dn));
+ DN conflictDomain2dn = DN.decode(
+ "entryUUID = " + domain2uid + "+dc=domain2,ou=people,dc=example,dc=com");
+ DN conflictDomain3dn = DN.decode(
+ "entryUUID = " + domain3uid + "+dc=domain3,ou=people,dc=example,dc=com");
+
+ updateMonitorCount(baseDn, unresolvedMonitorAttr);
+
+ // delete domain1
+ delMsg = new DeleteMsg(domain1dn, gen.newChangeNumber(), domain1uid);
+ broker.publish(delMsg);
+ // check that the domain1 has correctly been deleted
+ assertNull(getEntry(DN.decode(domain1dn), 10000, false),
+ "The DELETE replication message was not replayed");
+
+ // check that domain2 and domain3 have been renamed
+ assertNotNull(getEntry(conflictDomain2dn, 1000, true),
+ "The conflicting entries were not created");
+ assertNotNull(getEntry(conflictDomain3dn, 1000, true),
+ "The conflicting entries were not created");
+ // check that the 2 conflicting entries have been correctly marked
+ assertTrue(checkEntryHasAttribute(conflictDomain2dn,
+ ReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true));
+ assertTrue(checkEntryHasAttribute(conflictDomain3dn,
+ ReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true));
+
+ // check that unresolved conflict count has been incremented
+ assertEquals(getMonitorDelta(), 1);
+
+ // delete the resulting entries for the next test
+ delEntry(conflictDomain2dn);
+ delEntry(conflictDomain3dn);
+
+ //
+ // Check that when an entry is added on one master below an entry
+ // that is currently deleted on another master, the replay of the
+ // add on the second master cause the added entry to be renamed
+ //
+ addMsg = new AddMsg(gen.newChangeNumber(), domain2dn, domain2uid,
+ domain1uid,
+ domain2.getObjectClassAttribute(),
+ domain2.getAttributes(), new ArrayList<Attribute>());
+ broker.publish(addMsg);
+
+ // check that conflict entry was created
+ assertNotNull(getEntry(conflictDomain2dn, 1000, true),
+ "The conflicting entries were not created");
+
+ // check that the entry have been correctly marked as conflicting.
+ assertTrue(checkEntryHasAttribute(conflictDomain2dn,
+ ReplicationDomain.DS_SYNC_CONFLICT, domain2dn, 1000, true));
+
+ // check that unresolved conflict count has been incremented
+ assertEquals(getMonitorDelta(), 1);
+
broker.stop();
}
+ /**
+ * Check that the given entry does contain the attribute that mark the
+ * entry as conflicting.
+ *
+ * @param entry The entry that needs to be asserted.
+ *
+ * @return A boolean indicating if the entry is correctly marked.
+ */
+ private boolean assertConflictAttribute(Entry entry)
+ {
+ List<Attribute> attrs = entry.getAttribute("ds-sync-confict");
+
+ if (attrs == null)
+ return false;
+ else
+ return true;
+ }
+
@DataProvider(name="assured")
public Object[][] getAssuredFlag()
{
--
Gitblit v1.10.0