UpdateOperationTest.java revision 28215a00c6a6c49ab982f51dee97d501ce954ad3
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
/**
* Test synchronization of update operations on the directory server and through
* the replication server broker interface.
*/
@SuppressWarnings("javadoc")
public class UpdateOperationTest extends ReplicationTestCase
{
/** An entry with a entryUUID. */
private Entry personWithUUIDEntry;
private Entry personWithSecondUniqueID;
private Entry user3Entry;
private String user1entrysecondUUID;
private String user1entryUUID;
/** A "person" entry. */
private Entry personEntry;
private int replServerPort;
private String domain1uid;
private String domain2uid;
private String domain3uid;
private int domainSid = 55;
/** Set up the environment for performing the tests in this Class. */
{
super.setUp();
// Create necessary backend top level entry
"dn: " + baseDN,
"objectClass: top",
"objectClass: organizationalUnit",
"entryUUID: 11111111-1111-1111-1111-111111111111");
// replication server
+ "objectClass: top\n"
+ "objectClass: ds-cfg-replication-server\n"
+ "cn: Replication Server\n"
+ "ds-cfg-replication-db-directory: UpdateOperationTest\n"
+ "ds-cfg-replication-server-id: 107\n";
// suffix synchronized
+ "objectClass: top\n"
+ "objectClass: ds-cfg-replication-domain\n"
+ "ds-cfg-receive-status: true\n";
}
{
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"uid: user.1",
"homePhone: 951-245-7634",
"description: This is the description for Aaccf Amar.",
"st: NC",
"mobile: 027-085-0537",
"postalAddress: Aaccf Amar$17984 Thirteenth Street $Rockford, NC 85762",
"mail: user.1@example.com",
"cn: Aaccf Amar",
"l: Rockford",
"pager: 508-763-4246",
"street: 17984 Thirteenth Street",
"telephoneNumber: 216-564-6748",
"employeeNumber: 1",
"sn: Amar",
"givenName: Aaccf",
"postalCode: 85762",
"userPassword: password",
"initials: AA");
/*
* 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";
"dn: " + user1dn,
"objectClass: top", "objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson", "uid: user.1",
"homePhone: 951-245-7634",
"description: This is the description for Aaccf Amar.", "st: NC",
"mobile: 027-085-0537",
"postalAddress: Aaccf Amar$17984 Thirteenth Street $Rockford, NC 85762", "mail: user.1@example.com",
"cn: Aaccf Amar", "l: Rockford", "pager: 508-763-4246",
"street: 17984 Thirteenth Street",
"telephoneNumber: 216-564-6748", "employeeNumber: 1",
"sn: Amar", "givenName: Aaccf", "postalCode: 85762",
"userPassword: password", "initials: AA",
"dn: "+ user1dn,
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"uid: user.1",
"homePhone: 951-245-7634",
"description: This is the description for Aaccf Amar.",
"st: NC",
"mobile: 027-085-0537",
"postalAddress: Aaccf Amar$17984 Thirteenth Street $Rockford, NC 85762",
"mail: user.1@example.com",
"cn: Aaccf Amar",
"l: Rockford",
"pager: 508-763-4246",
"street: 17984 Thirteenth Street",
"telephoneNumber: 216-564-6748",
"employeeNumber: 1",
"sn: Amar",
"givenName: Aaccf",
"postalCode: 85762",
"userPassword: password",
"initials: AA",
"entryUUID: "+ user1entrysecondUUID);
user3UUID = "44444444-4444-4444-4444-444444444444";
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"uid: user.1",
"homePhone: 951-245-7634",
"description: This is the description for Aaccf Amar.",
"st: NC",
"mobile: 027-085-0537",
"postalAddress: Aaccf Amar$17984 Thirteenth Street $Rockford, NC 85762",
"mail: user.3@example.com",
"cn: Aaccf Amar",
"l: Rockford",
"pager: 508-763-4246",
"street: 17984 Thirteenth Street",
"telephoneNumber: 216-564-6748",
"employeeNumber: 1",
"sn: Amar",
"givenName: Aaccf",
"postalCode: 85762",
"userPassword: password",
"initials: AA",
"entryUUID: " + user3UUID);
"dn:" + domain1dn,
"objectClass:domain",
"dc:domain1");
"dn:" + domain2dn,
"objectClass:domain",
"dc:domain2");
"dn:" + domain3dn,
"objectClass:domain",
"dc:domain3");
}
/** Add an entry in the database. */
{
}
/** Delete an entry in the database. */
{
}
/**
* Tests whether the synchronization provider receive status can be disabled
* then re-enabled.
*/
public void toggleReceiveStatus() throws Exception
{
testSetUp("toggleReceiveStatus");
/*
* Open a session to the replicationServer using the broker API.
* This must use a different serverId to that of the directory server.
*/
final int serverId = 2;
try
{
// Disable the directory server receive status.
// Create and publish an update message to add an entry.
"The replication message was replayed while it should not have been: "
+ "the server receive status was disabled");
// Enable the directory server receive status.
"The replication message was not replayed while it should have been: "
+ "the server receive status was reenabled");
// Delete the entries to clean the database.
"The DELETE replication message was not replayed");
}
finally
{
}
}
{
}
/**
* Tests whether the synchronization provider fails over when it loses
* the heartbeat from the replication server.
*/
public void lostHeartbeatFailover() throws Exception
{
testSetUp("lostHeartbeatFailover");
/*
* Open a session to the replicationServer using the broker API.
* This must use a different serverId to that of the directory server.
*/
int serverId = 2;
try
{
// Create and publish an update message to add an entry.
"The ADD replication message was not replayed");
// Send a first modify operation message.
// Check that the modify has been replayed.
"The first modification was not replayed.");
// Simulate loss of heartbeats.
HeartbeatThread.setHeartbeatsDisabled(false);
// Send a second modify operation message.
// Check that the modify has been replayed.
checkEntryHasAttributeValue(personWithUUIDEntry.getName(), "description", "Description was changed", 10,
"The second modification was not replayed.");
// Delete the entries to clean the database.
"The DELETE replication message was not replayed");
}
finally
{
}
}
/**
* 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.
*/
public void modifyConflicts() throws Exception
{
testSetUp("modifyConflicts");
final AttributeType attrType =
final AttributeType entryuuidType =
/*
* Open a session to the replicationServer using the broker API.
* This must use a different serverId to that of the directory server.
*/
try
{
// Add the first test entry.
"dn: cn=test1," + baseDN,
"displayname: Test1",
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"cn: test1",
"sn: test");
// Read the entry back to get its UUID.
// A change on a first server.
// A change on a second server.
changeTime++;
// Simulate the ordering t2:replace:B followed by t1:add:A that
// Replay a replace of a value B at time t2 on a second server.
// Replay an add of a value A at time t1 on a first server.
// Read the entry to see how the conflict was resolved.
// the value should be the last (time t2) value added
// Simulate the ordering t2:delete:displayname followed by
// t1:replace:displayname
// A change on a first server.
changeTime++;
// A change on a second server.
changeTime++;
// Simulate the ordering t2:delete:displayname followed by t1:replace:A
// Replay an delete of attribute displayname at time t2 on a second server.
// Replay a replace of a value A at time t1 on a first server.
// Read the entry to see how the conflict was resolved.
// there should not be a value (delete at time t2)
}
finally
{
}
}
/**
* 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.
*/
public void namingConflicts() throws Exception
{
testSetUp("namingConflicts");
/*
* Open a session to the replicationServer using the ReplicationServer broker API.
* This must use a serverId different from the LDAP server ID
*/
final int serverId = 2;
try
{
/*
* 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
// Check that the entry has been created in the local DS.
"The send ADD replication message was not applied");
// send a modify operation with the correct unique ID but another DN
// check that the modify has been applied as if the entry had been renamed.
"The modification has not been correctly replayed.");
/*
* 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
// check that the modify has been applied.
"The modification has not been correctly replayed.");
/*
* 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
// Check that the entry has been created in the local DS.
"The ADD replication message was not applied");
// send a modify operation with a wrong unique ID but the same DN
// check that the modify has not been applied
checkEntryHasNoSuchAttributeValue(personWithUUIDEntry.getName(), "telephonenumber", "02 01 03 05", 10,
"The modification has been replayed while it should not.");
/*
* 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
// check that the delete operation has been applied
"The DELETE replication message was not replayed");
/*
* 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
// Check that the entry has been created in the local DS.
"The ADD replication message was not applied");
// create an entry with the same DN and another unique ID
// Check that the entry has been renamed and created in the local DS.
// delete the entries to clean the database.
"The DELETE replication message was not replayed");
"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.
*/
// Check that the entry has been created in the local DS.
"The ADD replication message was not applied");
/*
* 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.
*/
// check that the delete operation has not been applied
"The DELETE replication message was replayed when it should not");
/*
* 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.
*/
user1entryUUID, baseUUID, false,
"uid=wrong, " + baseDN,
"uid=newrdn");
// check that the operation has been correctly relayed
"The modify dn was not or badly replayed");
/* same test but by giving a bad entry DN */
// check that the operation has been correctly relayed
"The modify dn was not or badly replayed");
/*
* 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
// check that the second entry has been added
"The add operation was not replayed");
// try to rename the first entry
user1entrysecondUUID, baseUUID, false,
// check that the second entry has been renamed
// delete the entries to clean the database
"The DELETE replication message was not replayed");
"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
"dn: " + baseDN1,
"objectClass: top",
"objectClass: organizationalUnit",
"entryUUID: 55555555-5555-5555-5555-555555555555"));
"Entry not added: " + baseDN1);
// - create Add Msg for user1 with parent entry 1 UUID
// - MODDN parent entry 1 to baseDn2 in the LDAP server
baseDN);
// - add new parent entry 2 with baseDn1
"dn: " + baseDN1,
"objectClass: top",
"objectClass: organizationalUnit",
"entryUUID: 66666666-6666-6666-6666-666666666666"));
// - publish msg
// - check that the DN has been changed to baseDn2
"The ADD replication message was applied under " + baseDN1);
"The ADD replication message was NOT applied under " + baseDN2);
// 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
// delete domain1
// check that the domain1 has correctly been deleted
"The DELETE replication message was not replayed");
// check that domain2 and domain3 have been renamed
"The conflicting entries were not created");
"The conflicting entries were not created");
// check that the 2 conflicting entries have been correctly marked
// check that unresolved conflict count has been incremented
// delete the resulting entries for the next test
// 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
// delete domain1
// check that the domain1 has correctly been deleted
"The DELETE replication message was not replayed");
// check that domain2 and domain3 have been renamed as conflicting
"The conflicting entry exist for domain2" + conflictDomain2dn);
"The conflicting entry exist for domain3" + conflictDomain3dn);
// check that unresolved conflict count has been incremented
// 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
// check that conflict entry was created
"The conflicting entries were not created");
// check that the entry have been correctly marked as conflicting.
// check that unresolved conflict count has been incremented
// 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(
"33343333-3533-3633-3373-333333833333", baseUUID, false,
"uid=wrong, " + baseDN,
"uid=newrdn");
// 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.
/*
* Check that a conflict is detected when an entry is moved below an entry that does not exist.
*/
modDnMsg = new ModifyDNMsg(
"33333333-3333-3333-3333-333333333333",
"12343333-3533-3633-3333-333333833333" , false,
"uid=wrong, " + baseDN,
"uid=newrdn");
// check that the entry have been correctly marked as conflicting.
}
finally
{
}
}
{
// if the monitor counter did not get incremented after 200sec
// then something got wrong.
.toTimer();
{
{
return null;
}
});
}
/**
* Check that there was an administrative alert generated because the conflict
* has not been automatically resolved.
*/
{
"An alert was not generated when resolving conflicts");
}
/**
* Check that there was no administrative alert generated because the conflict
* has been automatically resolved.
*/
private void assertConflictAutomaticallyResolved(int expectedAlertCount)
{
"Expected no new alert to be generated when automatically resolving conflicts");
}
/**
* 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.
*/
{
}
public Object[][] getAssuredFlag()
{
return new Object[][] { { false }, {true} };
}
private void cleanupTest() throws Exception
{
classCleanUp();
setUp();
}
/** Tests done using directly the ReplicationBroker interface. */
{
testSetUp("updateOperations");
// Cleanup from previous run
cleanupTest();
final int serverId = 27;
try {
/*
* Test that operations done on this server are sent to the
* replicationServer and forwarded to our replicationServer broker session.
*/
// Create an Entry (add operation)
"The Add Entry operation failed");
// Modify the entry
// Modify the entry DN
"The MOD_DN operation didn't create the new person entry");
"The MOD_DN operation didn't delete the old person entry");
// Delete the entry
"Unable to delete the new person Entry");
/*
* 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
*/
/*
* Check that the entry has been created in the local DS.
*/
/*
* Test the reception of Modify Msg
*/
"The modification has not been correctly replayed.");
// Test that replication is able to add attribute that do
// not exist in the schema.
"The modification has not been correctly replayed.");
/*
* Test the Reception of Modify Dn Msg
*/
true, null, "uid= new person");
"The modify DN replication message was not applied");
/*
* Test the Reception of Delete Msg
*/
"The DELETE replication message was not replayed");
}
finally
{
}
}
{
"The received replication message is not of corrct type. msg : " + opMsg);
+ " replication message is not for the expected DN : " + opMsg);
}
{
{
return OperationType.ADD;
}
{
return OperationType.DELETE;
}
{
return OperationType.MODIFY;
}
else if (msg instanceof ModifyDNMsg)
{
return OperationType.MODIFY_DN;
}
}
/** Test case for [Issue 635] NullPointerException when trying to access non existing entry. */
public void deleteNoSuchObject() throws Exception
{
testSetUp("deleteNoSuchObject");
}
/** Test case for [Issue 798] break infinite loop when problems with naming resolution conflict. */
public void infiniteReplayLoop() throws Exception
{
testSetUp("infiniteReplayLoop");
int serverId = 11;
try
{
// Create a test entry.
"dn: uid=user.2," + baseDN,
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"uid: user.2",
"homePhone: 951-245-7634",
"description: This is the description for Aaccf Amar.",
"st: NC",
"mobile: 027-085-0537",
"postalAddress: Aaccf Amar$17984 Thirteenth Street $Rockford, NC 85762",
"mail: user.1@example.com",
"cn: Aaccf Amar",
"l: Rockford",
"pager: 508-763-4246",
"street: 17984 Thirteenth Street",
"telephoneNumber: 216-564-6748",
"employeeNumber: 1",
"sn: Amar",
"givenName: Aaccf",
"postalCode: 85762",
"userPassword: password",
"initials: AA");
// Get the UUID of the test entry.
// Register a short circuit that will fake a no-such-object result code
// on a delete. This will cause a replication replay loop.
try
{
// Publish a delete message for this test entry.
// Wait for the operation to be replayed.
.toTimer();
{
{
return null;
}
});
}
finally
{
}
}
finally
{
}
}
/**
* 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.
*/
{
}
/**
* Test that the ReplicationDomain (plugin inside LDAP server) adjust
* its internal CSN generator to the last CSN received. Steps:
* - create a domain with the current date in the CSN generator
* - make it receive an update with a CSN in the future
* - do a local operation replicated on that domain
* - check that the update generated for that operation has a CSN in the future.
*/
public void csnGeneratorAdjust() throws Exception
{
testSetUp("csnGeneratorAdjust");
/*
* Open a session to the replicationServer using the broker API.
* This must use a different serverId to that of the directory server.
*/
final int serverId = 88;
try
{
// Create and publish an update message to add an entry.
// Check that the entry has not been created in the directory server.
"The entry has not been created");
// Modify the entry
// See if the client has received the msg
"The MOD timestamp should have been adjusted to the ADD one");
// Delete the entries to clean the database.
// Check that the delete operation has been applied.
"The DELETE replication message was not replayed");
}
finally
{
}
}
/**
* Consumes all the messages sent to this broker. This is useful at the start
* of a test to avoid leftover messages from previous test runs.
*/
{
try
{
while (true)
{
}
}
{
// this is expected to happen when there will not be any more messages to
// consume from the socket
}
{
}
}
}