/*
* 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 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011 ForgeRock AS
*/
package org.opends.server.replication;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
import org.opends.server.TestCaseUtils;
import org.opends.messages.Message;
import org.opends.messages.Category;
import org.opends.messages.Severity;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.extensions.DummyAlertHandler;
import org.opends.server.plugins.ShortCircuitPlugin;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.replication.protocol.HeartbeatThread;
import org.opends.server.replication.protocol.ModifyDNMsg;
import org.opends.server.replication.protocol.ModifyMsg;
import org.opends.server.replication.protocol.OperationContext;
import org.opends.server.replication.protocol.ReplicationMsg;
import org.opends.server.schema.DirectoryStringSyntax;
import org.opends.server.types.*;
import org.opends.server.util.StaticUtils;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.opends.server.TestCaseUtils.*;
import org.opends.server.util.TimeThread;
/**
* Test synchronization of update operations on the directory server and through
* the replication server broker interface.
*/
public class UpdateOperationTest extends ReplicationTestCase
{
/**
* An entry with a entryUUID
*/
private Entry personWithUUIDEntry;
private Entry personWithSecondUniqueID;
private Entry user3Entry;
private String user3dn;
private String user3UUID;
private String baseUUID;
private String user1dn;
private String user1entrysecondUUID;
private String user1entryUUID;
/**
* A "person" entry
*/
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;
int domainSid = 55;
/**
* Set up the environment for performing the tests in this Class.
*
* @throws Exception
* If the environment could not be set up.
*/
@BeforeClass
@Override
public void setUp() throws Exception
{
super.setUp();
// Create necessary backend top level entry
String topEntry = "dn: ou=People," + TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organizationalUnit\n"
+ "entryUUID: 11111111-1111-1111-1111-111111111111\n";
addEntry(TestCaseUtils.entryFromLdifString(topEntry));
baseUUID = getEntryUUID(DN.decode("ou=People," + TEST_ROOT_DN_STRING));
// find a free port for the replicationServer
ServerSocket socket = TestCaseUtils.bindFreePort();
replServerPort = socket.getLocalPort();
socket.close();
// replication server
String replServerLdif =
"dn: cn=Replication Server, " + SYNCHRO_PLUGIN_DN + "\n"
+ "objectClass: top\n"
+ "objectClass: ds-cfg-replication-server\n"
+ "cn: Replication Server\n"
+ "ds-cfg-replication-port: " + replServerPort + "\n"
+ "ds-cfg-replication-db-directory: UpdateOperationTest\n"
+ "ds-cfg-replication-server-id: 107\n";
replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif);
// suffix synchronized
String testName = "updateOperationTest";
String synchroServerLdif =
"dn: cn=" + testName + ", cn=domains, " + SYNCHRO_PLUGIN_DN + "\n"
+ "objectClass: top\n"
+ "objectClass: ds-cfg-replication-domain\n"
+ "cn: " + testName + "\n"
+ "ds-cfg-base-dn: ou=People," + TEST_ROOT_DN_STRING + "\n"
+ "ds-cfg-replication-server: localhost:" + replServerPort + "\n"
+ "ds-cfg-server-id: "+ domainSid +"\n"
+ "ds-cfg-receive-status: true\n";
synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif);
String personLdif = "dn: uid=user.1,ou=People," + TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "homePhone: 951-245-7634\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "mobile: 027-085-0537\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n" + "pager: 508-763-4246\n"
+ "street: 17984 Thirteenth Street\n"
+ "telephoneNumber: 216-564-6748\n" + "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n";
personEntry = TestCaseUtils.entryFromLdifString(personLdif);
/*
* The 2 entries defined in the following code are used for the naming
* conflict resolution test (called namingConflicts)
* They must have the same DN but different entryUUID.
*/
user1entryUUID = "33333333-3333-3333-3333-333333333333";
user1entrysecondUUID = "22222222-2222-2222-2222-222222222222";
user1dn = "uid=user1,ou=People," + TEST_ROOT_DN_STRING;
String entryWithUUIDldif = "dn: "+ user1dn + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "homePhone: 951-245-7634\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "mobile: 027-085-0537\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n" + "pager: 508-763-4246\n"
+ "street: 17984 Thirteenth Street\n"
+ "telephoneNumber: 216-564-6748\n" + "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n"
+ "entryUUID: " + user1entryUUID + "\n";
personWithUUIDEntry = TestCaseUtils.entryFromLdifString(entryWithUUIDldif);
String entryWithSecondUUID = "dn: "+ user1dn + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "homePhone: 951-245-7634\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "mobile: 027-085-0537\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n" + "pager: 508-763-4246\n"
+ "street: 17984 Thirteenth Street\n"
+ "telephoneNumber: 216-564-6748\n" + "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n"
+ "entryUUID: "+ user1entrysecondUUID + "\n";
personWithSecondUniqueID =
TestCaseUtils.entryFromLdifString(entryWithSecondUUID);
user3UUID = "44444444-4444-4444-4444-444444444444";
user3dn = "uid=user3,ou=People," + TEST_ROOT_DN_STRING;
String user3LDIFEntry = "dn: "+ user3dn + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "homePhone: 951-245-7634\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "mobile: 027-085-0537\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.3@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n" + "pager: 508-763-4246\n"
+ "street: 17984 Thirteenth Street\n"
+ "telephoneNumber: 216-564-6748\n" + "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n"
+ "entryUUID: " + user3UUID + "\n";
user3Entry = TestCaseUtils.entryFromLdifString(user3LDIFEntry);
domain1dn = "dc=domain1,ou=People," + TEST_ROOT_DN_STRING;
domain2dn = "dc=domain2,dc=domain1,ou=People," + TEST_ROOT_DN_STRING;
domain3dn = "dc=domain3,dc=domain1,ou=People," + TEST_ROOT_DN_STRING;
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 database
*
*/
private ChangeNumber addEntry(Entry entry) throws Exception
{
AddOperationBasis addOp = new AddOperationBasis(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
entry.getUserAttributes(), entry.getOperationalAttributes());
addOp.setInternalOperation(true);
addOp.run();
assertEquals(addOp.getResultCode(), ResultCode.SUCCESS);
assertNotNull(getEntry(entry.getDN(), 1000, true));
return OperationContext.getChangeNumber((Operation) addOp);
}
/**
* Delete an entry in the database
*
*/
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.
* @throws Exception
*/
@Test(enabled=false)
public void toggleReceiveStatus() throws Exception
{
logError(Message.raw(Category.SYNC, Severity.INFORMATION,
"Starting synchronization test : toggleReceiveStatus"));
// Clean replication server database from previous run
cleanUpReplicationServersDB();
final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
/*
* Open a session to the replicationServer using the broker API.
* This must use a different serverId to that of the directory server.
*/
ReplicationBroker broker =
openReplicationSession(baseDn, 2, 100, replServerPort, 1000, true);
try
{
/*
* Create a Change number generator to generate new changenumbers
* when we need to send operation messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(2, 0);
// Disable the directory server receive status.
setReceiveStatus(synchroServerEntry.getDN().toString(), false);
// Create and publish an update message to add an entry.
AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN().toString(),
user1entryUUID,
baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
broker.publish(addMsg);
Entry resultEntry;
// Check that the entry has not been created in the directory server.
resultEntry = getEntry(personWithUUIDEntry.getDN(), 1000, true);
assertNull(resultEntry,
"The replication message was replayed while the server "
+ "receive status was disabled");
// Enable the directory server receive status.
setReceiveStatus(synchroServerEntry.getDN().toString(), true);
// Create and publish another update message to add an entry.
addMsg = new AddMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN().toString(),
user1entryUUID,
baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
broker.publish(addMsg);
// Check that the entry has been created in the directory server.
resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, true);
assertNotNull(resultEntry,
"The replication message was not replayed after the server "
+ "receive status was enabled");
// Delete the entries to clean the database.
DeleteMsg delMsg =
new DeleteMsg(personWithUUIDEntry.getDN().toString(),
gen.newChangeNumber(), user1entryUUID);
broker.publish(delMsg);
resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, false);
// Check that the delete operation has been applied.
assertNull(resultEntry,
"The DELETE replication message was not replayed");
}
finally
{
broker.stop();
}
}
/**
* Tests whether the synchronization provider fails over when it loses
* the heartbeat from the replication server.
*/
@Test(groups = "slow")
public void lostHeartbeatFailover() throws Exception
{
logError(Message.raw(Category.SYNC, Severity.INFORMATION,
"Starting replication test : lostHeartbeatFailover"));
// Clean replication server database from previous run
cleanUpReplicationServersDB();
final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
/*
* Open a session to the replicationServer using the broker API.
* This must use a different serverId to that of the directory server.
*/
ReplicationBroker broker =
openReplicationSession(baseDn, 2, 100, replServerPort, 1000, true);
try
{
/*
* Create a Change number generator to generate new changenumbers
* when we need to send operation messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(2, 0);
// Create and publish an update message to add an entry.
AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN().toString(),
user1entryUUID,
baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
broker.publish(addMsg);
Entry resultEntry;
// Check that the entry has been created in the directory server.
resultEntry = getEntry(personWithUUIDEntry.getDN(), 30000, true);
assertNotNull(resultEntry,
"The ADD replication message was not replayed");
// Send a first modify operation message.
List<Modification> mods = generatemods("telephonenumber", "01 02 45");
ModifyMsg modMsg = new ModifyMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN(), mods,
user1entryUUID);
broker.publish(modMsg);
// Check that the modify has been replayed.
boolean found = checkEntryHasAttribute(personWithUUIDEntry.getDN(),
"telephonenumber", "01 02 45", 10000, true);
if (!found)
{
fail("The first modification was not replayed.");
}
// Simulate loss of heartbeats.
HeartbeatThread.setHeartbeatsDisabled(true);
Thread.sleep(3000);
HeartbeatThread.setHeartbeatsDisabled(false);
// Send a second modify operation message.
mods = generatemods("description", "Description was changed");
modMsg = new ModifyMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN(), mods,
user1entryUUID);
broker.publish(modMsg);
// Check that the modify has been replayed.
found = checkEntryHasAttribute(personWithUUIDEntry.getDN(),
"description", "Description was changed",
10000, true);
if (!found)
{
fail("The second modification was not replayed.");
}
// Delete the entries to clean the database.
DeleteMsg delMsg =
new DeleteMsg(personWithUUIDEntry.getDN().toString(),
gen.newChangeNumber(), user1entryUUID);
broker.publish(delMsg);
resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, false);
// Check that the delete operation has been applied.
assertNull(resultEntry,
"The DELETE replication message was not replayed");
}
finally
{
broker.stop();
}
}
/**
* Tests the modify conflict resolution code.
* In this test, the local server acts 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 modify conflict resolution code.
*/
@Test(enabled=true, groups="slow")
public void modifyConflicts()
throws Exception
{
final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
final DN dn1 = DN.decode("cn=test1," + baseDn.toString());
final AttributeType attrType =
DirectoryServer.getAttributeType("displayname");
final AttributeType entryuuidType =
DirectoryServer.getAttributeType("entryuuid");
String monitorAttr = "resolved-modify-conflicts";
// Clean replication server database from previous run
cleanUpReplicationServersDB();
/*
* Open a session to the replicationServer using the broker API.
* This must use a different serverId to that of the directory server.
*/
ReplicationBroker broker =
openReplicationSession(baseDn, 2, 100, replServerPort, 1000, true);
try
{
// Add the first test entry.
TestCaseUtils.addEntry(
"dn: cn=test1," + baseDn.toString(),
"displayname: Test1",
"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).iterator().next().getValue().toString();
// A change on a first server.
long changeTime = TimeThread.getTime();
ChangeNumber t1 = new ChangeNumber(changeTime, 0, 3);
// A change on a second server.
changeTime++;
ChangeNumber t2 = new ChangeNumber(changeTime, 0, 4);
// 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 = Attributes.create(attrType, "B");
Modification mod = new Modification(ModificationType.REPLACE, attr);
List<Modification> mods = new ArrayList<Modification>(1);
mods.add(mod);
ModifyMsg modMsg = new ModifyMsg(t2, dn1, mods, entryuuid);
broker.publish(modMsg);
Thread.sleep(2000);
// Replay an add of a value A at time t1 on a first server.
attr = Attributes.create(attrType, "A");
mod = new Modification(ModificationType.ADD, attr);
mods = new ArrayList<Modification>(1);
mods.add(mod);
modMsg = new ModifyMsg(t1, dn1, mods, entryuuid);
broker.publish(modMsg);
Thread.sleep(2000);
// Read the entry to see how the conflict was resolved.
entry = DirectoryServer.getEntry(dn1);
attrs = entry.getAttribute(attrType);
String attrValue1 =
attrs.get(0).iterator().next().getValue().toString();
// 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.
changeTime++;
t1 = new ChangeNumber(changeTime, 0, 3);
// A change on a second server.
changeTime++;
t2 = new ChangeNumber(changeTime, 0, 4);
// 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 = Attributes.empty(attrType);
mod = new Modification(ModificationType.DELETE, attr);
mods = new ArrayList<Modification>(1);
mods.add(mod);
modMsg = new ModifyMsg(t2, dn1, mods, entryuuid);
broker.publish(modMsg);
Thread.sleep(2000);
// Replay a replace of a value A at time t1 on a first server.
attr = Attributes.create(attrType, "A");
mod = new Modification(ModificationType.REPLACE, attr);
mods = new ArrayList<Modification>(1);
mods.add(mod);
modMsg = new ModifyMsg(t1, dn1, mods, entryuuid);
broker.publish(modMsg);
Thread.sleep(2000);
// Read the entry to see how the conflict was resolved.
entry = DirectoryServer.getEntry(dn1);
attrs = entry.getAttribute(attrType);
// there should not be a value (delete at time t2)
assertNull(attrs);
assertEquals(getMonitorDelta(), 1);
}
finally
{
broker.stop();
}
}
/**
* Tests the naming conflict resolution code.
* 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, groups="slow")
public void namingConflicts() throws Exception
{
logError(Message.raw(Category.SYNC, Severity.INFORMATION,
"Starting replication test : namingConflicts"));
final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
String resolvedMonitorAttr = "resolved-naming-conflicts";
String unresolvedMonitorAttr = "unresolved-naming-conflicts";
// Clean replication server database from previous run
cleanUpReplicationServersDB();
/*
* Open a session to the replicationServer using the ReplicationServer broker API.
* This must use a serverId different from the LDAP server ID
*/
ReplicationBroker broker =
openReplicationSession(baseDn, 2, 100, replServerPort, 1000, true);
try
{
/*
* Create a Change number generator to generate new changenumbers
* when we need to send operations messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator( 2, 0);
/*
* Test that the conflict resolution code is able to find entries
* that have been renamed by an other master.
* To simulate this, create an entry with a given UUID and a given DN
* then send a modify operation using another DN but the same UUID.
* Finally check that the modify operation has been applied.
*/
// create the entry with a given DN
AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN().toString(),
user1entryUUID,
baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
broker.publish(addMsg);
// Check that the entry has been created in the local DS.
Entry resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, true);
assertNotNull(resultEntry,
"The send ADD replication message was not applied");
// send a modify operation with the correct unique ID but another DN
List<Modification> mods = generatemods("telephonenumber", "01 02 45");
ModifyMsg modMsg = new ModifyMsg(gen.newChangeNumber(),
DN.decode("cn=something,ou=People," + TEST_ROOT_DN_STRING), mods,
user1entryUUID);
updateMonitorCount(baseDn, resolvedMonitorAttr);
int AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(modMsg);
// check that the modify has been applied as if the entry had been renamed.
boolean found = checkEntryHasAttribute(personWithUUIDEntry.getDN(),
"telephonenumber", "01 02 45", 10000, true);
if (found == false)
fail("The modification has not been correctly replayed.");
assertEquals(getMonitorDelta(), 1);
// check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
/*
* Test that modify conflict resolution is able to detect that
* because there is a conflict between a MODIFYDN and a MODIFY,
* when a MODIFY is replayed the attribute that is being modified is
* now the RDN of the entry and therefore should not be deleted.
*/
// send a modify operation attempting to replace the RDN entry
// with a new value
mods = generatemods("uid", "AnotherUid");
modMsg = new ModifyMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN(), mods,
user1entryUUID);
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(modMsg);
// check that the modify has been applied.
found = checkEntryHasAttribute(personWithUUIDEntry.getDN(),
"uid", "AnotherUid", 10000, true);
if (found == false)
fail("The modification has not been correctly replayed.");
assertEquals(getMonitorDelta(), 1);
/*
* Test that the conflict resolution code is able to detect
* that an entry has been renamed and that a new entry has
* been created with the same DN but another entry UUID
* To simulate this, create and entry with a given UUID and a given DN
* then send a modify operation using the same DN but another UUID.
* Finally check that the modify operation has not been applied to the
* entry with the given DN.
*/
// create the entry with a given DN and unique ID
addMsg = new AddMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN().toString(),
user1entryUUID, baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
broker.publish(addMsg);
// Check that the entry has been created in the local DS.
resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, true);
assertNotNull(resultEntry,
"The ADD replication message was not applied");
// send a modify operation with a wrong unique ID but the same DN
mods = generatemods("telephonenumber", "02 01 03 05");
modMsg = new ModifyMsg(gen.newChangeNumber(),
DN.decode(user1dn), mods, "10000000-9abc-def0-1234-1234567890ab");
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(modMsg);
// check that the modify has not been applied
Thread.sleep(2000);
found = checkEntryHasAttribute(personWithUUIDEntry.getDN(),
"telephonenumber", "02 01 03 05", 10000, false);
if (found == true)
fail("The modification has been replayed while it should not.");
assertEquals(getMonitorDelta(), 1);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
/*
* Test that the conflict resolution code is able to find entries
* that have been renamed by an other master.
* To simulate this, send a delete operation using another DN but
* the same UUID has the entry that has been used in the tests above.
* Finally check that the delete operation has been applied.
*/
// send a delete operation with a wrong dn but the unique ID of the entry
// used above
DeleteMsg delMsg =
new DeleteMsg("cn=anotherdn,ou=People," + TEST_ROOT_DN_STRING,
gen.newChangeNumber(), user1entryUUID);
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(delMsg);
// check that the delete operation has been applied
resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, false);
assertNull(resultEntry,
"The DELETE replication message was not replayed");
assertEquals(getMonitorDelta(), 1);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
/*
* Test that two adds with the same DN but a different unique ID result
* cause a conflict and result in the second entry to be renamed.
*/
// create an entry with a given DN and unique ID
addMsg = new AddMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN().toString(),
user1entryUUID, baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
broker.publish(addMsg);
// Check that the entry has been created in the local DS.
resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, true);
assertNotNull(resultEntry,
"The ADD replication message was not applied");
// create an entry with the same DN and another unique ID
addMsg = new AddMsg(gen.newChangeNumber(),
personWithSecondUniqueID.getDN().toString(),
user1entrysecondUUID, baseUUID,
personWithSecondUniqueID.getObjectClassAttribute(),
personWithSecondUniqueID.getAttributes(), new ArrayList<Attribute>());
updateMonitorCount(baseDn, unresolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(addMsg);
// Check that the entry has been renamed and created in the local DS.
resultEntry = getEntry(
DN.decode("entryuuid=" + user1entrysecondUUID +" + " + user1dn),
10000, true);
assertNotNull(resultEntry,
"The ADD replication message was not applied");
assertEquals(getMonitorDelta(), 1);
assertConflictAttribute(resultEntry);
// Check that there was an administrative alert generated
// because the conflict has not been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount+1,
"An alert was not generated when resolving conflicts");
// delete the entries to clean the database.
delMsg =
new DeleteMsg(personWithUUIDEntry.getDN().toString(),
gen.newChangeNumber(), user1entryUUID);
broker.publish(delMsg);
delMsg =
new DeleteMsg(personWithSecondUniqueID.getDN().toString(),
gen.newChangeNumber(), user1entrysecondUUID);
broker.publish(delMsg);
resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, false);
resultEntry = getEntry(personWithSecondUniqueID.getDN(), 10000, false);
// check that the delete operation has been applied
assertNull(resultEntry,
"The DELETE replication message was not replayed");
/*
* Check that and added entry is correctly added below it's
* parent entry when this parent entry has been renamed.
*
* Simulate this by trying to add an entry below a DN that does not
* exist but with a parent ID that exist.
*/
addMsg = new AddMsg(gen.newChangeNumber(),
"uid=new person,o=nothere,o=below,ou=People," + TEST_ROOT_DN_STRING,
user1entryUUID,
baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(addMsg);
// Check that the entry has been created in the local DS.
resultEntry = getEntry(
DN.decode("uid=new person,ou=People," + TEST_ROOT_DN_STRING), 10000, true);
assertNotNull(resultEntry,
"The ADD replication message was not applied");
assertEquals(getMonitorDelta(), 1);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
/*
* Check that when replaying delete the naming conflict code
* verify that the unique ID op the replayed operation is
* the same as the unique ID of the entry with the given DN
*
* To achieve this send a delete operation with a correct DN
* but a wrong unique ID.
*/
delMsg =
new DeleteMsg("uid=new person,ou=People," + TEST_ROOT_DN_STRING,
gen.newChangeNumber(), "11111111-9abc-def0-1234-1234567890ab");
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(delMsg);
resultEntry = getEntry(
DN.decode("uid=new person,ou=People," + TEST_ROOT_DN_STRING), 10000, true);
// check that the delete operation has not been applied
assertNotNull(resultEntry,
"The DELETE replication message was replayed when it should not");
assertEquals(getMonitorDelta(), 1);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
/*
* Check that when replaying modify dn operations, the conflict
* resolution code is able to find the new DN of the parent entry
* if it has been renamed on another master.
*
* To simulate this try to rename an entry below an entry that does
* not exist but giving the unique ID of an existing entry.
*/
ModifyDNMsg modDnMsg = new ModifyDNMsg(
"uid=new person,ou=People," + TEST_ROOT_DN_STRING, gen.newChangeNumber(),
user1entryUUID, baseUUID, false,
"uid=wrong, ou=people," + TEST_ROOT_DN_STRING,
"uid=newrdn");
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(modDnMsg);
resultEntry = getEntry(
DN.decode("uid=newrdn,ou=People," + TEST_ROOT_DN_STRING), 10000, true);
// check that the operation has been correctly relayed
assertNotNull(resultEntry,
"The modify dn was not or badly replayed");
assertEquals(getMonitorDelta(), 1);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
/*
* same test but by giving a bad entry DN
*/
modDnMsg = new ModifyDNMsg(
"uid=wrong,ou=People," + TEST_ROOT_DN_STRING, gen.newChangeNumber(),
user1entryUUID, null, false, null, "uid=reallynewrdn");
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(modDnMsg);
resultEntry = getEntry(
DN.decode("uid=reallynewrdn,ou=People," + TEST_ROOT_DN_STRING), 10000, true);
// check that the operation has been correctly relayed
assertNotNull(resultEntry,
"The modify dn was not or badly replayed");
assertEquals(getMonitorDelta(), 1);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
/*
* Check that conflicting entries are renamed when a
* modifyDN is done with the same DN as an entry added on another server.
*/
// add a second entry
addMsg = new AddMsg(gen.newChangeNumber(),
user1dn,
user1entrysecondUUID,
baseUUID,
personWithSecondUniqueID.getObjectClassAttribute(),
personWithSecondUniqueID.getAttributes(), new ArrayList<Attribute>());
broker.publish(addMsg);
// check that the second entry has been added
resultEntry = getEntry(DN.decode(user1dn), 10000, true);
// check that the add operation has been applied
assertNotNull(resultEntry, "The add operation was not replayed");
// try to rename the first entry
modDnMsg = new ModifyDNMsg(user1dn, gen.newChangeNumber(),
user1entrysecondUUID, baseUUID, false,
baseDn.toString(), "uid=reallynewrdn");
updateMonitorCount(baseDn, unresolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(modDnMsg);
// check that the second entry has been renamed
resultEntry = getEntry(
DN.decode("entryUUID = " + user1entrysecondUUID + "+uid=reallynewrdn," +
"ou=People," + TEST_ROOT_DN_STRING), 10000, true);
assertNotNull(resultEntry, "The modifyDN was not or incorrectly replayed");
assertEquals(getMonitorDelta(), 1);
assertConflictAttribute(resultEntry);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount+1,
"An alert was not generated when resolving conflicts");
// delete the entries to clean the database
delMsg =
new DeleteMsg("entryUUID = " + user1entrysecondUUID + "+" +
DN.decode(user1dn).getRDN().toString() +
",ou=People," + TEST_ROOT_DN_STRING,
gen.newChangeNumber(), user1entrysecondUUID);
broker.publish(delMsg);
resultEntry = getEntry(
DN.decode("entryUUID = " + user1entrysecondUUID + "+" +
DN.decode(user1dn).getRDN().toString() +
",ou=People," + TEST_ROOT_DN_STRING), 10000, false);
// check that the delete operation has been applied
assertNull(resultEntry,
"The DELETE replication message was not replayed");
delMsg =
new DeleteMsg("uid=reallynewrdn,ou=People," + TEST_ROOT_DN_STRING,
gen.newChangeNumber(), user1entryUUID);
broker.publish(delMsg);
resultEntry = getEntry(
DN.decode("uid=reallynewrdn,ou=People," + TEST_ROOT_DN_STRING), 10000, false);
// check that the delete operation has been applied
assertNull(resultEntry,
"The DELETE replication message was not replayed");
/*
* When replaying add operations it is possible that the parent entry has
* been renamed before and that another entry have taken the former dn of
* the parent entry. In such case the replication replay code should
* detect that the parent has been renamed and should add the entry below
* the new dn of the parent (thus changing the original dn with which the
* entry had been created)
*
* Steps
* - create parent entry 1 with baseDn1
* - create Add Msg for user1 with parent entry 1 UUID
* - MODDN parent entry 1 to baseDn2 in the LDAP server
* - add new parent entry 2 with baseDn1
* - publish msg
* - check that the Dn has been changed to baseDn2 in the msg received
*/
// - create parent entry 1 with baseDn1
String[] topEntries = new String[1];
topEntries[0] = "dn: ou=baseDn1,"+baseDn+"\n" + "objectClass: top\n"
+ "objectClass: organizationalUnit\n"
+ "entryUUID: 55555555-5555-5555-5555-555555555555\n";
Entry entry;
for (String entryStr : topEntries)
{
entry = TestCaseUtils.entryFromLdifString(entryStr);
AddOperationBasis addOp = new AddOperationBasis(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
entry.getUserAttributes(), entry.getOperationalAttributes());
addOp.setInternalOperation(true);
addOp.run();
}
resultEntry = getEntry(
DN.decode("ou=baseDn1,"+baseDn), 10000, true);
assertNotNull(resultEntry,
"Entry not added: ou=baseDn1,"+baseDn);
// - create Add Msg for user1 with parent entry 1 UUID
addMsg = new AddMsg(gen.newChangeNumber(),
"uid=new person,ou=baseDn1,"+baseDn,
user1entryUUID,
getEntryUUID(DN.decode("ou=baseDn1,"+baseDn)),
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
// - MODDN parent entry 1 to baseDn2 in the LDAP server
ModifyDNOperationBasis modDNOp = new ModifyDNOperationBasis(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null,
DN.decode("ou=baseDn1,"+baseDn),
RDN.decode("ou=baseDn2"), true,
baseDn);
modDNOp.run();
resultEntry = getEntry(
DN.decode("ou=baseDn2,"+baseDn), 10000, true);
assertNotNull(resultEntry,
"Entry not moved from ou=baseDn1,"+baseDn+" to ou=baseDn2,"+baseDn);
// - add new parent entry 2 with baseDn1
String p2 = "dn: ou=baseDn1,"+baseDn+"\n" + "objectClass: top\n"
+ "objectClass: organizationalUnit\n"
+ "entryUUID: 66666666-6666-6666-6666-666666666666\n";
entry = TestCaseUtils.entryFromLdifString(p2);
AddOperationBasis addOp = new AddOperationBasis(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
entry.getUserAttributes(), entry.getOperationalAttributes());
addOp.setInternalOperation(true);
addOp.run();
// - publish msg
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(addMsg);
// - check that the DN has been changed to baseDn2
resultEntry = getEntry(
DN.decode("uid=new person,ou=baseDn1,"+baseDn), 10000, false);
assertNull(resultEntry,
"The ADD replication message was applied under ou=baseDn1,"+baseDn);
resultEntry = getEntry(
DN.decode("uid=new person,ou=baseDn2,"+baseDn), 10000, true);
assertNotNull(resultEntry,
"The ADD replication message was NOT applied under ou=baseDn2,"+baseDn);
assertEquals(getMonitorDelta(), 1);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
//
// 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 children : domain2 and domain3
addEntry(domain1);
ChangeNumber olderCn = gen.newChangeNumber();
Thread.sleep(1000);
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," + TEST_ROOT_DN_STRING);
DN conflictDomain3dn = DN.decode(
"entryUUID = " + domain3uid + "+dc=domain3,ou=people," + TEST_ROOT_DN_STRING);
updateMonitorCount(baseDn, unresolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
// delete domain1
delMsg = new DeleteMsg(domain1dn, olderCn, 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,
LDAPReplicationDomain.DS_SYNC_CONFLICT, domain2dn, 1000, true));
assertTrue(checkEntryHasAttribute(conflictDomain3dn,
LDAPReplicationDomain.DS_SYNC_CONFLICT, domain3dn, 1000, true));
// check that unresolved conflict count has been incremented
assertEquals(getMonitorDelta(), 1);
// Check that an administrative alert was generated
// because the conflict has not been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount+2,
"An alert was incorrectly generated when resolving conflicts");
// delete the resulting entries for the next test
delEntry(conflictDomain2dn);
delEntry(conflictDomain3dn);
//
// Check that when a delete is replayed over an entry which has child
// those child are also deleted
//
// add domain1 entry with 2 children : domain2 and domain3
addEntry(domain1);
domain1uid = getEntryUUID(DN.decode(domain1dn));
addEntry(domain2);
domain2uid = getEntryUUID(DN.decode(domain2dn));
ChangeNumber addCn = addEntry(domain3);
gen.adjust(addCn);
domain3uid = getEntryUUID(DN.decode(domain3dn));
updateMonitorCount(baseDn, unresolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
// 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 as conflicting
String confDomain2dn = "entryuuid="+domain2uid+"+dc=domain2,ou=people,"+TEST_ROOT_DN_STRING;
String confDomain3dn = "entryuuid="+domain3uid+"+dc=domain3,ou=people,"+TEST_ROOT_DN_STRING;
assertTrue(DirectoryServer.entryExists(DN.decode(confDomain2dn)),
"The conflicting entry exist for domain2" + confDomain2dn);
assertTrue(DirectoryServer.entryExists(DN.decode(confDomain3dn)),
"The conflicting entry exist for domain3" + confDomain3dn);
// check that unresolved conflict count has been incremented
assertEquals(getMonitorDelta(), 1);
delEntry(DN.decode(confDomain2dn));
delEntry(DN.decode(confDomain3dn));
//
// 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,
LDAPReplicationDomain.DS_SYNC_CONFLICT, domain2dn, 1000, true));
// check that unresolved conflict count has been incremented
assertEquals(getMonitorDelta(), 1);
// Check that when an entry is deleted on a first master and
// renamed on a second master and the rename is replayed last
// this is correctly detected as a resolved conflict.
// To simulate this simply try a modifyDN on a non existent uid.
modDnMsg = new ModifyDNMsg(
"uid=new person,ou=People," + TEST_ROOT_DN_STRING, gen.newChangeNumber(),
"33343333-3533-3633-3373-333333833333", baseUUID, false,
"uid=wrong, ou=people," + TEST_ROOT_DN_STRING,
"uid=newrdn");
updateMonitorCount(baseDn, resolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
broker.publish(modDnMsg);
// unfortunately it is difficult to check that the operation
// did not do anything.
// The only thing we can check is that resolved naming conflict counter
// has correctly been incremented.
int count = 0;
while ((count<2000) && getMonitorDelta() == 0)
{
// it is possible that the update has not yet been applied
// wait a short time and try again.
Thread.sleep(100);
count++;
}
// if the monitor counter did not get incremented after 200sec
// then something got wrong.
assertTrue(count < 200);
// Check that there was no administrative alert generated
// because the conflict has been automatically resolved.
assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
"An alert was incorrectly generated when resolving conflicts");
/*
* Check that a conflict is detected when an entry is
* moved below an entry that does not exist.
*/
updateMonitorCount(baseDn, unresolvedMonitorAttr);
AlertCount = DummyAlertHandler.getAlertCount();
modDnMsg = new ModifyDNMsg(
"uid=new person,ou=People," + TEST_ROOT_DN_STRING, gen.newChangeNumber(),
"33333333-3333-3333-3333-333333333333",
"12343333-3533-3633-3333-333333833333" , false,
"uid=wrong, ou=people," + TEST_ROOT_DN_STRING,
"uid=newrdn");
broker.publish(modDnMsg);
count = 0;
while ((count<2000) && getMonitorDelta() == 0)
{
// it is possible that the update has not yet been applied
// wait a short time and try again.
Thread.sleep(100);
count++;
}
// if the monitor counter did not get incremented after 200sec
// then something got wrong.
assertTrue(count < 200);
// check that the entry have been correctly marked as conflicting.
assertTrue(checkEntryHasAttribute(
DN.decode("uid=new person,ou=baseDn2,"+baseDn),
LDAPReplicationDomain.DS_SYNC_CONFLICT,
"uid=newrdn,ou=baseDn2,ou=People," + TEST_ROOT_DN_STRING, 1000, true));
}
finally
{
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()
{
return new Object[][] { { false }, {true} };
}
private void cleanupTest() {
try
{
classCleanUp();
setUp();
} catch (Exception e)
{
fail("Test cleanup failed: " + e.getClass().getName() + " : " +
e.getMessage() + " : " + StaticUtils.stackTraceToSingleLineString(e));
}
}
/**
* Tests done using directly the ReplicationBroker interface.
*/
@Test(enabled=true, dataProvider="assured")
public void updateOperations(boolean assured) throws Exception
{
logError(Message.raw(
Category.SYNC, Severity.INFORMATION,
"Starting replication test : updateOperations " + assured));
// Cleanup from previous run
cleanupTest();
final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
ReplicationBroker broker =
openReplicationSession(baseDn, 27, 100, replServerPort, 2000, true);
try {
ChangeNumberGenerator gen = new ChangeNumberGenerator( 27, 0);
/*
* Test that operations done on this server are sent to the
* replicationServer and forwarded to our replicationServer broker session.
*/
// Create an Entry (add operation)
Entry tmp = personEntry.duplicate(false);
AddOperationBasis addOp = new AddOperationBasis(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, tmp.getDN(),
tmp.getObjectClasses(), tmp.getUserAttributes(),
tmp.getOperationalAttributes());
addOp.run();
assertTrue(DirectoryServer.entryExists(personEntry.getDN()),
"The Add Entry operation failed");
if (ResultCode.SUCCESS.equals(addOp.getResultCode()))
{
// Check if the client has received the msg
ReplicationMsg msg = broker.receive();
assertTrue(msg instanceof AddMsg,
"The received replication message is not an ADD msg : " + msg);
AddMsg addMsg = (AddMsg) msg;
Operation receivedOp = addMsg.createOperation(connection);
assertTrue(OperationType.ADD.compareTo(receivedOp.getOperationType()) == 0,
"The received replication message is not an ADD msg : " + addMsg);
assertEquals(DN.decode(addMsg.getDn()),personEntry.getDN(),
"The received ADD replication message is not for the excepted DN : " + addMsg);
}
// Modify the entry
List<Modification> mods = generatemods("telephonenumber", "01 02 45");
ModifyOperationBasis modOp = new ModifyOperationBasis(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, personEntry.getDN(), mods);
modOp.setInternalOperation(true);
modOp.run();
// See if the client has received the msg
ReplicationMsg msg = broker.receive();
assertTrue(msg instanceof ModifyMsg,
"The received replication message is not a MODIFY msg : " + msg);
ModifyMsg modMsg = (ModifyMsg) msg;
modMsg.createOperation(connection);
assertTrue(DN.decode(modMsg.getDn()).compareTo(personEntry.getDN()) == 0,
"The received MODIFY replication message is not for the excepted DN : " + modMsg);
// Modify the entry DN
DN newDN = DN.decode("uid= new person,ou=People," + TEST_ROOT_DN_STRING) ;
ModifyDNOperationBasis modDNOp = new ModifyDNOperationBasis(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, personEntry.getDN(), RDN
.decode("uid=new person"), true, DN
.decode("ou=People," + TEST_ROOT_DN_STRING));
modDNOp.run();
assertTrue(DirectoryServer.entryExists(newDN),
"The MOD_DN operation didn't create the new person entry");
assertFalse(DirectoryServer.entryExists(personEntry.getDN()),
"The MOD_DN operation didn't delete the old person entry");
// See if the client has received the msg
msg = broker.receive();
assertTrue(msg instanceof ModifyDNMsg,
"The received replication message is not a MODIFY DN msg : " + msg);
ModifyDNMsg moddnMsg = (ModifyDNMsg) msg;
moddnMsg.createOperation(connection);
assertTrue(DN.decode(moddnMsg.getDn()).compareTo(personEntry.getDN()) == 0,
"The received MODIFY_DN message is not for the excepted DN : " + moddnMsg);
// Delete the entry
DeleteOperationBasis delOp = new DeleteOperationBasis(connection,
InternalClientConnection.nextOperationID(), InternalClientConnection
.nextMessageID(), null, DN
.decode("uid= new person,ou=People," + TEST_ROOT_DN_STRING));
delOp.run();
assertFalse(DirectoryServer.entryExists(newDN),
"Unable to delete the new person Entry");
// See if the client has received the msg
msg = broker.receive();
assertTrue(msg instanceof DeleteMsg,
"The received replication message is not a MODIFY DN msg : " + msg);
DeleteMsg delMsg = (DeleteMsg) msg;
delMsg.createOperation(connection);
assertTrue(DN.decode(delMsg.getDn()).compareTo(DN
.decode("uid= new person,ou=People," + TEST_ROOT_DN_STRING)) == 0,
"The received DELETE message is not for the excepted DN : " + delMsg);
/*
* Now check that when we send message to the ReplicationServer
* and that they are received and correctly replayed by the server.
*
* Start by testing the Add message reception
*/
AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN().toString(),
user1entryUUID, baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
if (assured)
addMsg.setAssured(true);
broker.publish(addMsg);
/*
* Check that the entry has been created in the local DS.
*/
Entry resultEntry = getEntry(personWithUUIDEntry.getDN(), 10000, true);
assertNotNull(resultEntry,
"The send ADD replication message was not applied for "+personWithUUIDEntry.getDN().toString());
/*
* Test the reception of Modify Msg
*/
modMsg = new ModifyMsg(gen.newChangeNumber(), personWithUUIDEntry.getDN(),
mods, user1entryUUID);
if (assured)
modMsg.setAssured(true);
broker.publish(modMsg);
boolean found = checkEntryHasAttribute(personWithUUIDEntry.getDN(),
"telephonenumber", "01 02 45", 10000, true);
if (found == false)
fail("The modification has not been correctly replayed.");
// Test that replication is able to add attribute that do
// not exist in the schema.
List<Modification> invalidMods = generatemods("badattribute", "value");
modMsg = new ModifyMsg(gen.newChangeNumber(), personWithUUIDEntry.getDN(),
invalidMods, user1entryUUID);
if (assured)
modMsg.setAssured(true);
broker.publish(modMsg);
found = checkEntryHasAttribute(
personWithUUIDEntry.getDN(), "badattribute", "value", 10000, true);
if (found == false)
fail("The modification has not been correctly replayed.");
/*
* Test the Reception of Modify Dn Msg
*/
moddnMsg = new ModifyDNMsg(personWithUUIDEntry.getDN().toString(),
gen.newChangeNumber(),
user1entryUUID, null,
true, null, "uid= new person");
if (assured)
moddnMsg.setAssured(true);
broker.publish(moddnMsg);
resultEntry = getEntry(
DN.decode("uid= new person,ou=People," + TEST_ROOT_DN_STRING), 10000, true);
assertNotNull(resultEntry,
"The modify DN replication message was not applied");
/*
* Test the Reception of Delete Msg
*/
delMsg = new DeleteMsg("uid= new person,ou=People," + TEST_ROOT_DN_STRING,
gen.newChangeNumber(), user1entryUUID);
if (assured)
delMsg.setAssured(true);
broker.publish(delMsg);
resultEntry = getEntry(
DN.decode("uid= new person,ou=People," + TEST_ROOT_DN_STRING), 10000, false);
assertNull(resultEntry,
"The DELETE replication message was not replayed");
}
finally
{
broker.stop();
}
}
/**
* Test case for
* [Issue 635] NullPointerException when trying to access non existing entry.
*/
@Test(enabled=true)
public void deleteNoSuchObject() throws Exception
{
logError(Message.raw(Category.SYNC, Severity.INFORMATION,
"Starting replication test : deleteNoSuchObject"));
// Clean replication server database from previous run
cleanUpReplicationServersDB();
DN dn = DN.decode("cn=No Such Object,ou=People," + TEST_ROOT_DN_STRING);
DeleteOperationBasis op =
new DeleteOperationBasis(connection,
InternalClientConnection.nextOperationID(),
InternalClientConnection.nextMessageID(), null,
dn);
op.run();
assertEquals(op.getResultCode(), ResultCode.NO_SUCH_OBJECT);
}
/**
* Test case for
* [Issue 798] break infinite loop when problems with naming resolution
* conflict.
*/
@Test(enabled=true)
public void infiniteReplayLoop() throws Exception
{
logError(Message.raw(Category.SYNC, Severity.INFORMATION,
"Starting replication test : infiniteReplayLoop"));
final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
// Clean replication server database from previous run
cleanUpReplicationServersDB();
Thread.sleep(2000);
ReplicationBroker broker =
openReplicationSession(baseDn, 11, 100, replServerPort, 1000, true);
try
{
ChangeNumberGenerator gen = new ChangeNumberGenerator( 11, 0);
// Create a test entry.
String personLdif = "dn: uid=user.2,ou=People," + TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.2\n"
+ "homePhone: 951-245-7634\n"
+ "description: This is the description for Aaccf Amar.\n"
+ "st: NC\n"
+ "mobile: 027-085-0537\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n" + "pager: 508-763-4246\n"
+ "street: 17984 Thirteenth Street\n"
+ "telephoneNumber: 216-564-6748\n" + "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n";
Entry tmp = TestCaseUtils.entryFromLdifString(personLdif);
AddOperationBasis addOp =
new AddOperationBasis(connection,
InternalClientConnection.nextOperationID(),
InternalClientConnection.nextMessageID(),
null, tmp.getDN(), tmp.getObjectClasses(),
tmp.getUserAttributes(),
tmp.getOperationalAttributes());
addOp.run();
assertEquals(addOp.getResultCode(), ResultCode.SUCCESS);
long initialCount = getMonitorAttrValue(baseDn, "replayed-updates");
// Get the UUID of the test entry.
Entry resultEntry = getEntry(tmp.getDN(), 1, true);
AttributeType uuidType = DirectoryServer.getAttributeType("entryuuid");
String uuid =
resultEntry.getAttributeValue(uuidType,
DirectoryStringSyntax.DECODER);
// Register a short circuit that will fake a no-such-object result code
// on a delete. This will cause a replication replay loop.
ShortCircuitPlugin.registerShortCircuit(OperationType.DELETE,
"PreParse", 32);
try
{
// Publish a delete message for this test entry.
DeleteMsg delMsg = new DeleteMsg(tmp.getDN().toString(),
gen.newChangeNumber(),
uuid);
broker.publish(delMsg);
// Wait for the operation to be replayed.
long endTime = System.currentTimeMillis() + 5000;
while (getMonitorAttrValue(baseDn, "replayed-updates") == initialCount &&
System.currentTimeMillis() < endTime)
{
Thread.sleep(100);
}
}
finally
{
ShortCircuitPlugin.deregisterShortCircuit(OperationType.DELETE,
"PreParse");
}
// If the replication replay loop was detected and broken then the
// counter will still be updated even though the replay was unsuccessful.
if (getMonitorAttrValue(baseDn, "replayed-updates") == initialCount)
{
fail("Operation was not replayed");
}
}
finally
{
broker.stop();
}
}
/**
* Enable or disable the receive status of a synchronization provider.
*
* @param syncConfigDN The DN of the synchronization provider configuration
* entry.
* @param enable Specifies whether the receive status should be enabled
* or disabled.
*/
private static void setReceiveStatus(String syncConfigDN, boolean enable)
{
ArrayList<ByteString> valueList = new ArrayList<ByteString>(1);
if (enable)
{
valueList.add(ByteString.valueOf("TRUE"));
}
else
{
valueList.add(ByteString.valueOf("FALSE"));
}
LDAPAttribute a = new LDAPAttribute("ds-cfg-receive-status", valueList);
LDAPModification m = new LDAPModification(ModificationType.REPLACE, a);
ArrayList<RawModification> modList = new ArrayList<RawModification>(1);
modList.add(m);
InternalClientConnection conn =
InternalClientConnection.getRootConnection();
ByteString rawEntryDN =
ByteString.valueOf(syncConfigDN);
ModifyOperation internalModify = conn.processModify(rawEntryDN, modList);
ResultCode resultCode = internalModify.getResultCode();
if (resultCode != ResultCode.SUCCESS)
{
throw new RuntimeException("Cannot set receive status");
}
}
/**
* Test that the ReplicationDomain (plugin inside LDAP server) adjust
* its internal change number generator to the last change number
* received. Steps:
* - create a domain with the current date in the CN generator
* - make it receive an update with a CN in the future
* - do a local operation replicated on that domain
* - check that the update generated for that operation has a CN in the
* future.
* @throws Exception
*/
@Test(enabled=true)
public void CNGeneratorAdjust() throws Exception
{
int serverId = 88;
logError(Message.raw(Category.SYNC, Severity.INFORMATION,
"Starting synchronization test : CNGeneratorAdjust"));
final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
// Clean replication server database from previous run
cleanUpReplicationServersDB();
/*
* Open a session to the replicationServer using the broker API.
* This must use a different serverId to that of the directory server.
*/
ReplicationBroker broker =
openReplicationSession(baseDn, serverId, 100, replServerPort, 1000, true);
try
{
/*
* Create a Change number generator to generate new changenumbers
* when we need to send operation messages to the replicationServer.
*/
long inTheFutur = System.currentTimeMillis() + (3600 * 1000);
ChangeNumberGenerator gen = new ChangeNumberGenerator(serverId, inTheFutur);
// Create and publish an update message to add an entry.
AddMsg addMsg = new AddMsg(
gen.newChangeNumber(),
user3dn.toString(),
user3UUID,
baseUUID,
user3Entry.getObjectClassAttribute(),
user3Entry.getAttributes(),
new ArrayList<Attribute>());
broker.publish(addMsg);
Entry resultEntry;
// Check that the entry has not been created in the directory server.
resultEntry = getEntry(user3Entry.getDN(), 1000, true);
assertNotNull(resultEntry, "The entry has not been created");
// Modify the entry
List<Modification> mods = generatemods("telephonenumber", "01 02 45");
ModifyOperationBasis modOp = new ModifyOperationBasis(
connection,
InternalClientConnection.nextOperationID(),
InternalClientConnection.nextMessageID(),
null,
user3Entry.getDN(),
mods);
modOp.setInternalOperation(true);
modOp.run();
// See if the client has received the msg
ReplicationMsg msg = broker.receive();
assertTrue(msg instanceof ModifyMsg,
"The received replication message is not a MODIFY msg");
ModifyMsg modMsg = (ModifyMsg) msg;
assertEquals(addMsg.getChangeNumber().getTimeSec(),
modMsg.getChangeNumber().getTimeSec(),
"The MOD timestamp should have been adjusted to the ADD one");
// Delete the entries to clean the database.
DeleteMsg delMsg =
new DeleteMsg(
user3Entry.getDN().toString(),
gen.newChangeNumber(),
user3UUID);
broker.publish(delMsg);
// Check that the delete operation has been applied.
resultEntry = getEntry(user3Entry.getDN(), 10000, false);
assertNull(resultEntry,
"The DELETE replication message was not replayed");
}
finally
{
broker.stop();
}
}
}