/* * 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()); 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 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()); 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()); 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()); 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 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()); 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); } }