/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
* Portions Copyright 2013 ForgeRock AS
*/
package org.opends.server.replication.plugin;
import static org.opends.server.TestCaseUtils.*;
import static org.testng.Assert.*;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.IsolationPolicy;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.replication.protocol.ModifyDNMsg;
import org.opends.server.types.*;
import org.testng.annotations.Test;
/**
* Test the naming conflict resolution code.
*/
@SuppressWarnings("javadoc")
public class NamingConflictTest extends ReplicationTestCase
{
private static final AtomicBoolean SHUTDOWN = new AtomicBoolean(false);
/**
* Test for issue 3402 : test, that a modrdn that is older than an other
* modrdn but that is applied later is ignored.
*
* In this test, the local server act both as an LDAP server and
* a replicationServer that are inter-connected.
*
* The test creates an other session to the replicationServer using
* directly the ReplicationBroker API.
* It then uses this session to simulate conflicts and therefore
* test the naming conflict resolution code.
*/
@Test(enabled=true)
public void simultaneousModrdnConflict() throws Exception
{
TestCaseUtils.initializeTestBackend(true);
final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
LDAPReplicationDomain domain =
MultimasterReplication.createNewDomain(conf, queue);
domain.start();
try
{
/*
* Create a Change number generator to generate new ChangeNumbers
* when we need to send operations messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
String parentUUID = getEntryUUID(DN.decode(TEST_ROOT_DN_STRING));
Entry entry = TestCaseUtils.entryFromLdifString(
"dn: cn=simultaneousModrdnConflict, "+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n"
+ "street: 17984 Thirteenth Street\n"
+ "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n");
TestCaseUtils.addEntry(entry);
String entryUUID = getEntryUUID(entry.getDN());
// generate two consecutive ChangeNumber that will be used in backward order
ChangeNumber cn1 = gen.newChangeNumber();
ChangeNumber cn2 = gen.newChangeNumber();
ModifyDNMsg modDnMsg = new ModifyDNMsg(
entry.getDN().toNormalizedString(), cn2,
entryUUID, parentUUID, false,
TEST_ROOT_DN_STRING,
"uid=simultaneous2");
// Put the message in the replay queue
domain.processUpdate(modDnMsg, SHUTDOWN);
// Make the domain replay the change from the replay queue
domain.replay(queue.take().getUpdateMessage(), SHUTDOWN);
// This MODIFY DN uses an older DN and should therefore be cancelled
// at replay time.
modDnMsg = new ModifyDNMsg(
entry.getDN().toNormalizedString(), cn1,
entryUUID, parentUUID, false,
TEST_ROOT_DN_STRING,
"uid=simulatneouswrong");
// Put the message in the replay queue
domain.processUpdate(modDnMsg, SHUTDOWN);
// Make the domain replay the change from the replay queue
// and resolve conflict
domain.replay(queue.take().getUpdateMessage(), SHUTDOWN);
// Expect the conflict resolution
assertFalse(DirectoryServer.entryExists(entry.getDN()),
"The modDN conflict was not resolved as expected.");
}
finally
{
MultimasterReplication.deleteDomain(baseDn);
}
}
/**
* Test that when a previous conflict is resolved because
* a delete operation has removed one of the conflicting entries
* the other conflicting entry is correctly renamed to its
* original name.
*/
@Test(enabled=true)
public void conflictCleaningDelete() throws Exception
{
TestCaseUtils.initializeTestBackend(true);
final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
LDAPReplicationDomain domain =
MultimasterReplication.createNewDomain(conf, queue);
domain.start();
try
{
/*
* Create a Change number generator to generate new ChangeNumbers
* when we need to send operations messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
String entryldif =
"dn: cn=conflictCleaningDelete, "+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n"
+ "street: 17984 Thirteenth Street\n"
+ "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n";
Entry entry = TestCaseUtils.entryFromLdifString(entryldif);
// Add the first entry
TestCaseUtils.addEntry(entry);
String parentUUID = getEntryUUID(DN.decode(TEST_ROOT_DN_STRING));
ChangeNumber cn1 = gen.newChangeNumber();
// Now try to add the same entry with same DN but a different
// unique ID though the replication
AddMsg addMsg =
new AddMsg(cn1,
entry.getDN().toNormalizedString(),
"c9cb8c3c-615a-4122-865d-50323aaaed48", parentUUID,
entry.getObjectClasses(), entry.getUserAttributes(),
null);
// Put the message in the replay queue
domain.processUpdate(addMsg, SHUTDOWN);
// Make the domain replay the change from the replay queue
domain.replay(queue.take().getUpdateMessage(), SHUTDOWN);
// Now delete the first entry that was added at the beginning
TestCaseUtils.deleteEntry(entry.getDN());
// Expect the conflict resolution : the second entry should now
// have been renamed with the original DN.
Entry resultEntry = DirectoryServer.getEntry(entry.getDN());
assertNotNull(resultEntry, "The conflict was not cleared");
assertEquals(getEntryUUID(resultEntry.getDN()),
"c9cb8c3c-615a-4122-865d-50323aaaed48",
"The wrong entry has been renamed");
assertNull(resultEntry.getAttribute(LDAPReplicationDomain.DS_SYNC_CONFLICT));
}
finally
{
MultimasterReplication.deleteDomain(baseDn);
}
}
/**
* Test that when a previous conflict is resolved because
* a MODDN operation has removed one of the conflicting entries
* the other conflicting entry is correctly renamed to its
* original name.
*
* @throws Exception if the test fails.
*/
@Test(enabled=true)
public void conflictCleaningMODDN() throws Exception
{
TestCaseUtils.initializeTestBackend(true);
final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
LDAPReplicationDomain domain =
MultimasterReplication.createNewDomain(conf, queue);
domain.start();
try
{
/*
* Create a Change number generator to generate new ChangeNumbers
* when we need to send operations messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
String entryldif =
"dn: cn=conflictCleaningDelete, "+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n"
+ "street: 17984 Thirteenth Street\n"
+ "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n";
Entry entry = TestCaseUtils.entryFromLdifString(entryldif);
// Add the first entry
TestCaseUtils.addEntry(entry);
String parentUUID = getEntryUUID(DN.decode(TEST_ROOT_DN_STRING));
ChangeNumber cn1 = gen.newChangeNumber();
// Now try to add the same entry with same DN but a different
// unique ID though the replication
AddMsg addMsg =
new AddMsg(cn1,
entry.getDN().toNormalizedString(),
"c9cb8c3c-615a-4122-865d-50323aaaed48", parentUUID,
entry.getObjectClasses(), entry.getUserAttributes(),
null);
// Put the message in the replay queue
domain.processUpdate(addMsg, SHUTDOWN);
// Make the domain replay the change from the replay queue
domain.replay(queue.take().getUpdateMessage(), SHUTDOWN);
// Now delete the first entry that was added at the beginning
InternalClientConnection conn =
InternalClientConnection.getRootConnection();
ModifyDNOperation modDNOperation =
conn.processModifyDN(entry.getDN(), RDN.decode("cn=foo"), false);
assertEquals(modDNOperation.getResultCode(), ResultCode.SUCCESS);
// Expect the conflict resolution : the second entry should now
// have been renamed with the original DN.
Entry resultEntry = DirectoryServer.getEntry(entry.getDN());
assertNotNull(resultEntry, "The conflict was not cleared");
assertEquals(getEntryUUID(resultEntry.getDN()),
"c9cb8c3c-615a-4122-865d-50323aaaed48",
"The wrong entry has been renamed");
assertNull(resultEntry.getAttribute(LDAPReplicationDomain.DS_SYNC_CONFLICT));
}
finally
{
MultimasterReplication.deleteDomain(baseDn);
}
}
/**
* Tests for issue 3891
* S1 S2
* ADD uid=xx,ou=parent,... [SUBTREE] DEL ou=parent, ...
*
* 1/ removeParentConflict1 (on S1)
* - t1(cn1) ADD uid=xx,ou=parent,...
* - t2(cn2) replay SUBTREE DEL ou=parent, ....
* => No conflict : expect the parent entry & subtree to be deleted
*
* 2/ removeParentConflict2 (on S1)
* - t1(cn1) ADD uid=xx,ou=parent,...
* - replay t2(cn2) DEL ou=parent, ....
* => Conflict and no automatic resolution: expect
* - the child entry to be renamed under root entry
* - the parent entry to be deleted
*
* 3/ removeParentConflict3 (on S2)
* - t2(cn2) DEL or SUBTREE DEL ou=parent, ....
* - t1(cn1) replay ADD uid=xx,ou=parent,...
* => Conflict and no automatic resolution: expect
* - the child entry to be renamed under root entry
*
*/
@Test(enabled=true)
public void removeParentConflict1() throws Exception
{
TestCaseUtils.initializeTestBackend(true);
final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
LDAPReplicationDomain domain =
MultimasterReplication.createNewDomain(conf, queue);
domain.start();
try
{
/*
* Create a Change number generator to generate new ChangeNumbers
* when we need to send operations messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
Entry parentEntry = TestCaseUtils.entryFromLdifString(
"dn: ou=rpConflict, "+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organizationalUnit\n");
Entry childEntry = TestCaseUtils.entryFromLdifString(
"dn: cn=child, ou=rpConflict,"+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n"
+ "street: 17984 Thirteenth Street\n"
+ "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n");
TestCaseUtils.addEntry(parentEntry);
TestCaseUtils.addEntry(childEntry);
String parentUUID = getEntryUUID(parentEntry.getDN());
ChangeNumber cn2 = gen.newChangeNumber();
DeleteMsg delMsg = new DeleteMsg(
parentEntry.getDN().toNormalizedString(),
cn2,
parentUUID);
delMsg.setSubtreeDelete(true);
// Put the message in the replay queue
domain.processUpdate(delMsg, SHUTDOWN);
// Make the domain replay the change from the replay queue
domain.replay(queue.take().getUpdateMessage(), SHUTDOWN);
// Expect the subtree to be deleted and no conflict entry created
assertFalse(DirectoryServer.entryExists(parentEntry.getDN()),
"DEL subtree on parent was not processed as expected.");
assertFalse(DirectoryServer.entryExists(parentEntry.getDN()),
"DEL subtree on parent was not processed as expected.");
}
finally
{
MultimasterReplication.deleteDomain(baseDn);
}
}
@Test(enabled=true)
public void removeParentConflict2() throws Exception
{
TestCaseUtils.initializeTestBackend(true);
final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
LDAPReplicationDomain domain =
MultimasterReplication.createNewDomain(conf, queue);
domain.start();
try
{
/*
* Create a Change number generator to generate new ChangeNumbers
* when we need to send operations messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
Entry parentEntry = TestCaseUtils.entryFromLdifString(
"dn: ou=rpConflict, "+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organizationalUnit\n");
Entry childEntry = TestCaseUtils.entryFromLdifString(
"dn: cn=child, ou=rpConflict,"+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n"
+ "street: 17984 Thirteenth Street\n"
+ "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n");
TestCaseUtils.addEntry(parentEntry);
TestCaseUtils.addEntry(childEntry);
assertTrue(DirectoryServer.entryExists(parentEntry.getDN()),
"Parent entry expected to exist.");
assertTrue(DirectoryServer.entryExists(childEntry.getDN()),
"Child entry expected to be exist.");
String parentUUID = getEntryUUID(parentEntry.getDN());
String childUUID = getEntryUUID(childEntry.getDN());
ChangeNumber cn2 = gen.newChangeNumber();
DeleteMsg delMsg = new DeleteMsg(
parentEntry.getDN().toNormalizedString(),
cn2,
parentUUID);
// NOT SUBTREE
// Put the message in the replay queue
domain.processUpdate(delMsg, SHUTDOWN);
// Make the domain replay the change from the replay queue
domain.replay(queue.take().getUpdateMessage(), SHUTDOWN);
// Expect the parent entry to be deleted
assertTrue(!DirectoryServer.entryExists(parentEntry.getDN()),
"Parent entry expected to be deleted : " + parentEntry.getDN());
// Expect the child entry to be moved as conflict entry under the root
// entry of the suffix
DN childDN = DN.decode("entryuuid="+childUUID+
"+cn=child,o=test");
assertTrue(DirectoryServer.entryExists(childDN),
"Child entry conflict exist with DN="+childDN);
}
finally
{
MultimasterReplication.deleteDomain(baseDn);
}
}
@Test(enabled=true)
public void removeParentConflict3() throws Exception
{
TestCaseUtils.initializeTestBackend(true);
final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
LDAPReplicationDomain domain =
MultimasterReplication.createNewDomain(conf, queue);
domain.start();
try
{
/*
* Create a Change number generator to generate new ChangeNumbers
* when we need to send operations messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
Entry parentEntry = TestCaseUtils.entryFromLdifString(
"dn: ou=rpConflict, "+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organizationalUnit\n");
Entry childEntry = TestCaseUtils.entryFromLdifString(
"dn: cn=child, ou=rpConflict,"+ TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n"
+ "street: 17984 Thirteenth Street\n"
+ "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n");
TestCaseUtils.addEntry(parentEntry);
String parentUUID = getEntryUUID(parentEntry.getDN());
TestCaseUtils.deleteEntry(parentEntry);
ChangeNumber cn1 = gen.newChangeNumber();
// Create and publish an update message to add the child entry.
String childUUID = "44444444-4444-4444-4444-444444444444";
AddMsg addMsg = new AddMsg(
cn1,
childEntry.getDN().toString(),
childUUID,
parentUUID,
childEntry.getObjectClassAttribute(),
childEntry.getAttributes(),
new ArrayList<Attribute>());
// Put the message in the replay queue
domain.processUpdate(addMsg, SHUTDOWN);
// Make the domain replay the change from the replay queue
domain.replay(queue.take().getUpdateMessage(), SHUTDOWN);
// Expect the parent entry to be deleted
assertFalse(DirectoryServer.entryExists(parentEntry.getDN()),
"Parent entry exists ");
// Expect the child entry to be moved as conflict entry under the root
// entry of the suffix
DN childDN = DN.decode("entryuuid="+childUUID+
"+cn=child,o=test");
assertTrue(DirectoryServer.entryExists(childDN),
"Child entry conflict exist with DN="+childDN);
}
finally
{
MultimasterReplication.deleteDomain(baseDn);
}
}
}