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