/* * 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 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2012 ForgeRock AS */ package org.opends.server.replication.plugin; import java.io.File; import static org.opends.server.TestCaseUtils.TEST_ROOT_DN_STRING; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.io.IOException; import java.net.ServerSocket; import java.util.LinkedList; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import org.opends.messages.Category; import org.opends.messages.Message; import org.opends.messages.Severity; import org.opends.server.TestCaseUtils; import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.AssuredType; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.replication.ReplicationTestCase; import org.opends.server.replication.common.ChangeNumber; import org.opends.server.replication.protocol.AddMsg; import org.opends.server.replication.protocol.DeleteMsg; import org.opends.server.replication.protocol.LDAPUpdateMsg; import org.opends.server.replication.protocol.ModifyMsg; import org.opends.server.replication.protocol.ReplicationMsg; 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.Attribute; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.ByteString; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import org.opends.server.util.StaticUtils; import org.opends.server.util.TimeThread; import org.testng.annotations.Test; /** * Test the usage of the historical data of the replication. */ public class HistoricalCsnOrderingTest extends ReplicationTestCase { final int serverId = 123; public class TestBroker extends ReplicationBroker { LinkedList<ReplicationMsg> list = null; public TestBroker(LinkedList<ReplicationMsg> list) { super(null, null, null, 0, 0, (long) 0, (long) 0, null, (byte) 0, (long) 0); this.list = list; } public void publishRecovery(ReplicationMsg msg) { list.add(msg); } } /** * Check the basic comparator on the HistoricalCsnOrderingMatchingRule */ @Test() public void basicRuleTest() throws Exception { // Creates a rule HistoricalCsnOrderingMatchingRule r = new HistoricalCsnOrderingMatchingRule(); ChangeNumber del1 = new ChangeNumber(1, 0, 1); ChangeNumber del2 = new ChangeNumber(1, 1, 1); ByteString v1 = ByteString.valueOf("a"+":"+del1.toString()); ByteString v2 = ByteString.valueOf("a"+":"+del2.toString()); int cmp = r.compareValues(v1, v1); assertTrue(cmp == 0); cmp = r.compareValues(v1, v2); assertTrue(cmp == -1); cmp = r.compareValues(v2, v1); assertTrue(cmp == 1); } /** * Test that we can retrieve the entries that were missed by * a replication server and can re-build operations from the historical * informations. */ @Test() public void buildAndPublishMissingChangesOneEntryTest() throws Exception { final int serverId = 123; final DN baseDn = DN.decode(TEST_ROOT_DN_STRING); TestCaseUtils.initializeTestBackend(true); ReplicationServer rs = createReplicationServer(); // Create Replication Server and Domain LDAPReplicationDomain rd1 = createReplicationDomain(serverId); try { long startTime = TimeThread.getTime(); final DN dn1 = DN.decode("cn=test1," + baseDn.toString()); final AttributeType histType = DirectoryServer.getAttributeType(EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); logError(Message.raw(Category.SYNC, Severity.INFORMATION, "Starting replication test : changesCmpTest")); // 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" ); // Perform a first modification to update the historical attribute int resultCode = TestCaseUtils.applyModifications(false, "dn: cn=test1," + baseDn.toString(), "changetype: modify", "add: description", "description: foo"); assertEquals(resultCode, 0); // Read the entry back to get its historical and included changeNumber Entry entry = DirectoryServer.getEntry(dn1); List<Attribute> attrs1 = entry.getAttribute(histType); assertTrue(attrs1 != null); assertTrue(attrs1.isEmpty() != true); String histValue = attrs1.get(0).iterator().next().getValue().toString(); logError(Message.raw(Category.SYNC, Severity.INFORMATION, "First historical value:" + histValue)); // Perform a 2nd modification to update the hist attribute with // a second value resultCode = TestCaseUtils.applyModifications(false, "dn: cn=test1," + baseDn.toString(), "changetype: modify", "add: description", "description: bar"); assertEquals(resultCode, 0); Entry entry2 = DirectoryServer.getEntry(dn1); List<Attribute> attrs2 = entry2.getAttribute(histType); assertTrue(attrs2 != null); assertTrue(attrs2.isEmpty() != true); for (AttributeValue av : attrs2.get(0)) { logError(Message.raw(Category.SYNC, Severity.INFORMATION, "Second historical value:" + av.getValue().toString())); } LinkedList<ReplicationMsg> opList = new LinkedList<ReplicationMsg>(); TestBroker session = new TestBroker(opList); boolean result = rd1.buildAndPublishMissingChanges( new ChangeNumber(startTime, 0, serverId), session); assertTrue(result, "buildAndPublishMissingChanges has failed"); assertEquals(opList.size(), 3, "buildAndPublishMissingChanges should return 3 operations"); assertTrue(opList.getFirst().getClass().equals(AddMsg.class)); // Build a change number from the first modification String hv[] = histValue.split(":"); logError(Message.raw(Category.SYNC, Severity.INFORMATION, hv[1])); ChangeNumber fromChangeNumber = new ChangeNumber(hv[1]); opList = new LinkedList<ReplicationMsg>(); session = new TestBroker(opList); result = rd1.buildAndPublishMissingChanges( fromChangeNumber, session); assertTrue(result, "buildAndPublishMissingChanges has failed"); assertEquals(opList.size(), 1, "buildAndPublishMissingChanges should return 1 operation"); assertTrue(opList.getFirst().getClass().equals(ModifyMsg.class)); } finally { MultimasterReplication.deleteDomain(baseDn); rs.remove(); StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(), rs.getDbDirName())); } } /** * Test that we can retrieve the entries that were missed by * a replication server and can re-build operations from the historical * informations. */ @Test() public void buildAndPublishMissingChangesSeveralEntriesTest() throws Exception { final DN baseDn = DN.decode(TEST_ROOT_DN_STRING); TestCaseUtils.initializeTestBackend(true); ReplicationServer rs = createReplicationServer(); // Create Replication Server and Domain LDAPReplicationDomain rd1 = createReplicationDomain(serverId); long startTime = TimeThread.getTime(); try { logError(Message.raw(Category.SYNC, Severity.INFORMATION, "Starting replication test : changesCmpTest")); // Add 3 entries. String dnTest1 = "cn=test1," + baseDn.toString(); String dnTest2 = "cn=test2," + baseDn.toString(); String dnTest3 = "cn=test3," + baseDn.toString(); TestCaseUtils.addEntry( "dn: " + dnTest3, "displayname: Test1", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "cn: test1", "sn: test" ); TestCaseUtils.addEntry( "dn: " + dnTest1, "displayname: Test1", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "cn: test1", "sn: test" ); TestCaseUtils.deleteEntry(DN.decode(dnTest3)); TestCaseUtils.addEntry( "dn: " + dnTest2, "displayname: Test1", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "cn: test1", "sn: test" ); // Perform modifications on the 2 entries int resultCode = TestCaseUtils.applyModifications(false, "dn: cn=test2," + baseDn.toString(), "changetype: modify", "add: description", "description: foo"); resultCode = TestCaseUtils.applyModifications(false, "dn: cn=test1," + baseDn.toString(), "changetype: modify", "add: description", "description: foo"); assertEquals(resultCode, 0); LinkedList<ReplicationMsg> opList = new LinkedList<ReplicationMsg>(); TestBroker session = new TestBroker(opList); // Call the buildAndPublishMissingChanges and check that this method // correctly generates the 4 operations in the correct order. boolean result = rd1.buildAndPublishMissingChanges( new ChangeNumber(startTime, 0, serverId), session); assertTrue(result, "buildAndPublishMissingChanges has failed"); assertEquals(opList.size(), 5, "buildAndPublishMissingChanges should return 5 operations"); ReplicationMsg msg = opList.removeFirst(); assertTrue(msg.getClass().equals(AddMsg.class)); assertEquals(((LDAPUpdateMsg) msg).getDn(), dnTest1); msg = opList.removeFirst(); assertTrue(msg.getClass().equals(DeleteMsg.class)); assertEquals(((LDAPUpdateMsg) msg).getDn(), dnTest3); msg = opList.removeFirst(); assertTrue(msg.getClass().equals(AddMsg.class)); assertEquals(((LDAPUpdateMsg) msg).getDn(), dnTest2); msg = opList.removeFirst(); assertTrue(msg.getClass().equals(ModifyMsg.class)); assertEquals(((LDAPUpdateMsg) msg).getDn(), dnTest2); msg = opList.removeFirst(); assertTrue(msg.getClass().equals(ModifyMsg.class)); assertEquals(((LDAPUpdateMsg) msg).getDn(), dnTest1); } finally { MultimasterReplication.deleteDomain(baseDn); rs.remove(); } } SortedSet<String> replServers = new TreeSet<String>(); private ReplicationServer createReplicationServer() throws ConfigException { int rsPort; try { ServerSocket socket1 = TestCaseUtils.bindFreePort(); rsPort = socket1.getLocalPort(); socket1.close(); replServers.add("localhost:" + rsPort); ReplServerFakeConfiguration conf = new ReplServerFakeConfiguration(rsPort, "HistoricalCsnOrdering", 0, 1, 0, 100, replServers, 1, 1000, 5000); ReplicationServer replicationServer = new ReplicationServer(conf); replicationServer.clearDb(); return replicationServer; } catch (IOException e) { fail("Unable to determinate some free ports " + stackTraceToSingleLineString(e)); return null; } } private LDAPReplicationDomain createReplicationDomain(int dsId) throws DirectoryException, ConfigException { DN baseDn = DN.decode(TEST_ROOT_DN_STRING); DomainFakeCfg domainConf = new DomainFakeCfg(baseDn, dsId, replServers, AssuredType.NOT_ASSURED, 2, 1, 0, null); LDAPReplicationDomain replicationDomain = MultimasterReplication.createNewDomain(domainConf); replicationDomain.start(); return replicationDomain; } }