DependencyTest.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
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 2007-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.replication;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.TestCaseUtils;
import org.opends.server.backends.MemoryBackend;
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.common.CSNGenerator;
import org.opends.server.replication.plugin.DomainFakeCfg;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.plugin.MultimasterReplication;
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.replication.protocol.ModifyMsg;
import org.opends.server.replication.server.ReplServerFakeConfiguration;
import org.opends.server.replication.server.ReplicationServer;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.types.Attributes;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.opends.server.TestCaseUtils.*;
import static org.opends.server.core.DirectoryServer.*;
import static org.opends.server.util.CollectionUtils.*;
import static org.testng.Assert.*;
/**
* Test that the dependencies are computed correctly when replaying
* sequences of operations that requires to follow a given order
* such as : ADD an entry, ADD a children entry.
*/
@SuppressWarnings("javadoc")
public class DependencyTest extends ReplicationTestCase
{
private final long CLEAN_DB_GENERATION_ID = 7883L;
private DN TEST_ROOT_DN;
@BeforeClass
public void setup() throws Exception
{
TEST_ROOT_DN = DN.valueOf(TEST_ROOT_DN_STRING);
}
/**
* Check that a sequence of dependents adds and mods is correctly ordered:
* Using a deep dit :
* TEST_ROOT_DN_STRING
* |
* dc=dependency1
* |
* dc=dependency2
* |
* dc=dependency3
* |
*
* |
* dc=dependencyN
* This test sends a sequence of interleaved ADD operations and MODIFY
* operations to build such a dit.
*
* Then test that the sequence of Delete necessary to remove
* all those entries is also correctly ordered.
*/
@Test(enabled=true, groups="slow")
public void addModDelDependencyTest() throws Exception
{
ReplicationServer replServer = null;
LDAPReplicationDomain domain = null;
DN baseDN = TEST_ROOT_DN;
int brokerId = 2;
int serverId = 1;
int replServerId = 81;
int addSequenceLength = 30;
cleanDB();
try
{
/*
* FIRST PART :
* Check that a sequence of dependent ADD is correctly ordered.
*
* - Create replication server
* - Send sequence of ADD messages to the replication server
* - Configure replication server
* - check that the last entry has been correctly added
*/
Entry entry = TestCaseUtils.entryFromLdifString(
"dn:" + TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organization\n"
+ "entryuuid: " + stringUID(1) + "\n");
replServer = newReplicationServer(replServerId, addSequenceLength * 5 + 100, "dependencyTestAddModDelDependencyTestDb");
ReplicationBroker broker = openReplicationSession(
baseDN, brokerId, 1000, replServer.getReplicationPort(), 1000, CLEAN_DB_GENERATION_ID);
Thread.sleep(2000);
// send a sequence of add operation
DN addDN = TEST_ROOT_DN;
CSNGenerator gen = new CSNGenerator(brokerId, 0L);
int sequence;
for (sequence = 1; sequence<=addSequenceLength; sequence ++)
{
entry.removeAttribute(getAttributeType("entryuuid"));
entry.addAttribute(Attributes.create("entryuuid", stringUID(sequence+1)),
new LinkedList<ByteString>());
addDN = DN.valueOf("dc=dependency" + sequence + "," + addDN);
broker.publish(addMsg(addDN, entry, sequence + 1, sequence, gen));
broker.publish(modifyMsg(addDN, sequence + 1, generatemods("description", "test"), gen));
}
// configure and start replication of TEST_ROOT_DN_STRING on the server
domain = startNewLDAPReplicationDomain(replServer, baseDN, serverId, 100000);
// check that last entry in sequence got added.
Entry lastEntry = getEntry(addDN, 30000, true);
assertNotNull(lastEntry,
"The last entry of the ADD sequence was not added.");
// Check that all the modify have been replayed
// (all the entries should have a description).
addDN = TEST_ROOT_DN;
for (sequence = 1; sequence<=addSequenceLength; sequence ++)
{
addDN = DN.valueOf("dc=dependency" + sequence + "," + addDN);
checkEntryHasAttributeValue(
addDN, "description", "test", 10, "The modification was not replayed on entry " + addDN);
}
/*
* SECOND PART
*
* Now check that the dependencies between delete are correctly
* managed.
*
* disable the domain while we publish the delete message to
* to replication server so that when we enable it it receives the
* delete operation in bulk.
*/
domain.disable();
Thread.sleep(2000); // necessary because disable does not wait
// for full termination of all threads. (issue 1571)
DN deleteDN = addDN;
while (sequence-->1)
{
broker.publish(delMsg(deleteDN, sequence + 1, gen));
deleteDN = deleteDN.parent();
}
domain.enable();
// check that entry just below the base entry was deleted.
// (we can't delete the base entry because some other tests might
// have added other children)
DN node1 = DN.valueOf("dc=dependency1," + TEST_ROOT_DN_STRING);
Entry baseEntry = getEntry(node1, 30000, false);
assertNull(baseEntry,
"The last entry of the DEL sequence was not deleted.");
}
finally
{
remove(replServer);
if (domain != null)
{
MultimasterReplication.deleteDomain(baseDN);
}
}
}
private AddMsg addMsg(DN addDN, Entry entry, int uniqueId, int parentId, CSNGenerator gen)
{
return new AddMsg(gen.newCSN(), addDN, stringUID(uniqueId), stringUID(parentId),
entry.getObjectClassAttribute(), entry.getAttributes(), null);
}
private ModifyMsg modifyMsg(DN dn, int entryUUID, List<Modification> mods, CSNGenerator gen)
{
return new ModifyMsg(gen.newCSN(), dn, mods, stringUID(entryUUID));
}
private DeleteMsg delMsg(DN delDN, int entryUUID, CSNGenerator gen)
{
return new DeleteMsg(delDN, gen.newCSN(), stringUID(entryUUID));
}
private ModifyDNMsg modDNMsg(DN dn, String newRDN, int entryUUID, int newSuperiorEntryUUID, CSNGenerator gen)
{
return new ModifyDNMsg(dn, gen.newCSN(), stringUID(entryUUID), stringUID(newSuperiorEntryUUID), true, null, newRDN);
}
/**
* Check the dependency between moddn and delete operation
* when an entry is renamed to a new dn and then deleted.
* Disabled: need investigations to fix random failures
*/
@Test(enabled=false)
public void moddnDelDependencyTest() throws Exception
{
ReplicationServer replServer = null;
LDAPReplicationDomain domain = null;
DN baseDN = TEST_ROOT_DN;
int brokerId = 2;
int serverId = 1;
int replServerId = 82;
cleanDB();
try
{
// Create replication server, replication domain and broker.
Entry entry = TestCaseUtils.entryFromLdifString(
"dn:" + TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organization\n");
CSNGenerator gen = new CSNGenerator(brokerId, 0L);
int renamedEntryUuid = 100;
replServer = newReplicationServer(replServerId, 200, "dependencyTestModdnDelDependencyTestDb");
// configure and start replication of TEST_ROOT_DN_STRING on the server
Thread.sleep(2000);
domain = startNewLDAPReplicationDomain(replServer, baseDN, serverId, 100000);
ReplicationBroker broker = openReplicationSession(
baseDN, brokerId, 1000, replServer.getReplicationPort(), 1000, CLEAN_DB_GENERATION_ID);
// add an entry to play with.
entry.removeAttribute(getAttributeType("entryuuid"));
entry.addAttribute(Attributes.create("entryuuid",
stringUID(renamedEntryUuid)),
new LinkedList<ByteString>());
DN addDN = DN.valueOf("dc=moddndel" + "," + TEST_ROOT_DN_STRING);
broker.publish(addMsg(addDN, entry, renamedEntryUuid, 1, gen));
// check that the entry was correctly added
checkEntryHasAttributeValue(addDN, "entryuuid", stringUID(renamedEntryUuid), 30, "The initial entry add failed");
// disable the domain to make sure that the messages are all sent in a row.
domain.disable();
// rename and delete the entry.
broker.publish(modDNMsg(addDN, "dc=new_name", renamedEntryUuid, 1, gen));
DN delDN = DN.valueOf("dc=new_name" + "," + TEST_ROOT_DN_STRING);
broker.publish(delMsg(delDN, renamedEntryUuid, gen));
// enable back the domain to trigger message replay.
domain.enable();
// check that entry does not exist anymore.
checkEntryHasNoSuchAttributeValue(DN.valueOf("dc=new_name" + "," + TEST_ROOT_DN_STRING), "entryuuid",
stringUID(renamedEntryUuid), 30, "The delete dependencies was not correctly enforced");
}
finally
{
remove(replServer);
if (domain != null)
{
MultimasterReplication.deleteDomain(baseDN);
}
}
}
/**
* Clean the database and replace with a single entry.
*
* @throws FileNotFoundException
* @throws IOException
* @throws Exception
*/
private void cleanDB() throws FileNotFoundException, IOException, Exception
{
// Clear backend
TestCaseUtils.initializeTestBackend(false);
// Create top entry with uuid
Entry topEntry = TestCaseUtils.entryFromLdifString(
"dn:" + TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organization\n"
+ "o: test\n"
+ "entryuuid: " + stringUID(1) + "\n");
MemoryBackend memoryBackend = (MemoryBackend) DirectoryServer.getBackend(TEST_BACKEND_ID);
memoryBackend.addEntry(topEntry, null);
}
/**
* Check that after a sequence of add/del/add done on the same DN
* the second entry is in the database.
* The unique id of the entry is used to check that the correct entry
* has been added.
* To increase the risks of failures a loop of add/del/add is done.
*/
@Test(enabled=true, groups="slow")
public void addDelAddDependencyTest() throws Exception
{
ReplicationServer replServer = null;
LDAPReplicationDomain domain = null;
DN baseDN = TEST_ROOT_DN;
int brokerId = 2;
int serverId = 1;
int replServerId = 83;
int addSequenceLength = 30;
cleanDB();
try
{
Entry entry = TestCaseUtils.entryFromLdifString(
"dn:" + TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organization\n");
replServer = newReplicationServer(replServerId, 5 * addSequenceLength + 100, "dependencyTestAddDelAddDependencyTestDb");
ReplicationBroker broker = openReplicationSession(
baseDN, brokerId, 100, replServer.getReplicationPort(), 1000, CLEAN_DB_GENERATION_ID);
// send a sequence of add/del/add operations
CSNGenerator gen = new CSNGenerator(brokerId, 0L);
int sequence;
for (sequence = 1; sequence<=addSequenceLength; sequence ++)
{
// add the entry a first time
entry.removeAttribute(getAttributeType("entryuuid"));
entry.addAttribute(Attributes.create("entryuuid", stringUID(sequence+1)),
new LinkedList<ByteString>());
DN addDN = DN.valueOf("dc=dependency" + sequence + "," + TEST_ROOT_DN_STRING);
broker.publish(addMsg(addDN, entry, sequence + 1, 1, gen));
broker.publish(delMsg(addDN, sequence + 1, gen));
// add again the entry with a new entryuuid.
entry.removeAttribute(getAttributeType("entryuuid"));
entry.addAttribute(Attributes.create("entryuuid", stringUID(sequence+1025)),
new LinkedList<ByteString>());
broker.publish(addMsg(addDN, entry, sequence + 1025, 1, gen));
}
domain = startNewLDAPReplicationDomain(replServer, baseDN, serverId, -1);
// check that all entries have been deleted and added
// again by checking that they do have the correct entryuuid
for (sequence = 1; sequence<=addSequenceLength; sequence ++)
{
String addDn = "dc=dependency" + sequence + "," + TEST_ROOT_DN_STRING;
checkEntryHasAttributeValue(DN.valueOf(addDn), "entryuuid", stringUID(sequence + 1025), 30,
"The second add was not replayed on entry " + addDn);
}
for (sequence = 1; sequence<=addSequenceLength; sequence ++)
{
DN deleteDN = DN.valueOf("dc=dependency" + sequence + "," + TEST_ROOT_DN_STRING);
broker.publish(delMsg(deleteDN, sequence + 1025, gen));
}
// check that the database was cleaned successfully
DN node1 = DN.valueOf("dc=dependency1," + TEST_ROOT_DN_STRING);
Entry baseEntry = getEntry(node1, 30000, false);
assertNull(baseEntry,
"The entry were not removed succesfully after test completion.");
}
finally
{
remove(replServer);
if (domain != null)
{
MultimasterReplication.deleteDomain(baseDN);
}
}
}
private ReplicationServer newReplicationServer(int replServerId, int windowSize, String dirName) throws Exception
{
int replServerPort = TestCaseUtils.findFreePort();
ReplServerFakeConfiguration conf = new ReplServerFakeConfiguration(
replServerPort, dirName, replicationDbImplementation, 0, replServerId, 0, windowSize, null);
return new ReplicationServer(conf);
}
private LDAPReplicationDomain startNewLDAPReplicationDomain(ReplicationServer replServer, DN baseDN, int serverId,
int heartBeatInterval) throws ConfigException
{
SortedSet<String> replServers = newTreeSet("localhost:" + replServer.getReplicationPort());
DomainFakeCfg domainConf = new DomainFakeCfg(baseDN, serverId, replServers);
if (heartBeatInterval > 0)
{
domainConf.setHeartbeatInterval(heartBeatInterval);
}
LDAPReplicationDomain domain = MultimasterReplication.createNewDomain(domainConf);
domain.start();
return domain;
}
/**
* Check that the dependency of moddn operation are working by
* issuing a set of Add operation followed by a modrdn of the added entry.
*/
@Test(enabled=true, groups="slow")
public void addModdnDependencyTest() throws Exception
{
ReplicationServer replServer = null;
LDAPReplicationDomain domain = null;
DN baseDN = TEST_ROOT_DN;
int brokerId = 2;
int serverId = 1;
int replServerId = 84;
int addSequenceLength = 30;
cleanDB();
try
{
Entry entry = TestCaseUtils.entryFromLdifString(
"dn:" + TEST_ROOT_DN_STRING + "\n"
+ "objectClass: top\n"
+ "objectClass: organization\n");
replServer = newReplicationServer(replServerId, 5 * addSequenceLength + 100, "dependencyTestAddModdnDependencyTestDb");
ReplicationBroker broker = openReplicationSession(
baseDN, brokerId, 100, replServer.getReplicationPort(), 1000, CLEAN_DB_GENERATION_ID);
DN addDN = TEST_ROOT_DN;
CSNGenerator gen = new CSNGenerator(brokerId, 0L);
// send a sequence of add/modrdn operations
int sequence;
for (sequence = 1; sequence<=addSequenceLength; sequence ++)
{
// add the entry
entry.removeAttribute(getAttributeType("entryuuid"));
entry.addAttribute(Attributes.create("entryuuid", stringUID(sequence+1)),
new LinkedList<ByteString>());
addDN = DN.valueOf("dc=dependency" + sequence + "," + TEST_ROOT_DN_STRING);
broker.publish(addMsg(addDN, entry, sequence + 1, 1, gen));
// rename the entry
broker.publish(modDNMsg(addDN, "dc=new_dep" + sequence, sequence + 1, 1, gen));
}
// configure and start replication of TEST_ROOT_DN_STRING on the server
domain = startNewLDAPReplicationDomain(replServer, baseDN, serverId, -1);
// check that all entries have been renamed
for (sequence = 1; sequence<=addSequenceLength; sequence ++)
{
addDN = DN.valueOf("dc=new_dep" + sequence + "," + TEST_ROOT_DN_STRING);
Entry baseEntry = getEntry(addDN, 30000, true);
assertNotNull(baseEntry,
"The rename was not applied correctly on :" + addDN);
}
// delete the entries to clean the database.
for (sequence = 1; sequence<=addSequenceLength; sequence ++)
{
addDN = DN.valueOf("dc=new_dep" + sequence + "," + TEST_ROOT_DN_STRING);
broker.publish(delMsg(addDN, sequence + 1, gen));
}
}
finally
{
remove(replServer);
if (domain != null)
{
MultimasterReplication.deleteDomain(baseDN);
}
}
}
/**
* Builds and return a uuid from an integer.
* This methods assume that unique integers are used and does not make any
* unicity checks. It is only responsible for generating a uid with a
* correct syntax.
*/
private String stringUID(int i)
{
return String.format("11111111-1111-1111-1111-%012x", i);
}
}