/* * 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 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2013 ForgeRock AS */ package org.opends.server.replication; import java.io.File; import static org.opends.server.config.ConfigConstants.ATTR_TASK_COMPLETION_TIME; import static org.opends.server.config.ConfigConstants.ATTR_TASK_INITIALIZE_DONE; import static org.opends.server.config.ConfigConstants.ATTR_TASK_INITIALIZE_LEFT; import static org.opends.server.config.ConfigConstants.ATTR_TASK_LOG_MESSAGES; import static org.opends.server.config.ConfigConstants.ATTR_TASK_STATE; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.messages.ReplicationMessages.*; import static org.opends.messages.TaskMessages.*; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.net.ServerSocket; import java.util.ArrayList; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import java.util.UUID; import java.net.SocketTimeoutException; import org.opends.server.TestCaseUtils; import org.opends.server.backends.task.TaskState; import org.opends.server.core.AddOperationBasis; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.debug.DebugTracer; import org.opends.messages.Category; import org.opends.messages.Message; import org.opends.messages.Severity; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.replication.service.ReplicationBroker; import org.opends.server.replication.common.ServerStatus; import org.opends.server.replication.plugin.LDAPReplicationDomain; import org.opends.server.replication.protocol.DoneMsg; import org.opends.server.replication.protocol.EntryMsg; import org.opends.server.replication.protocol.ErrorMsg; import org.opends.server.replication.protocol.InitializeRequestMsg; import org.opends.server.replication.protocol.InitializeTargetMsg; import org.opends.server.replication.protocol.ReplicationMsg; import org.opends.server.replication.protocol.RoutableMsg; import org.opends.server.replication.server.ReplServerFakeConfiguration; import org.opends.server.replication.server.ReplicationServer; import org.opends.server.schema.DirectoryStringSyntax; import org.opends.server.types.AttributeType; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.ResultCode; import org.opends.server.types.SearchFilter; import org.opends.server.types.SearchScope; import org.opends.server.util.Base64; import org.opends.server.util.StaticUtils; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** * Tests contained here: * * Initialize Test Cases <=> Pull entries * --------------------- * InitializeImport : Tests the import in the target DS. * Creates a task on current DS and makes a broker simulates DS2 sending entries. * InitializeExport : Tests the export from the source DS * A broker simulates DS2 pulling entries from current DS. * * Initialize Target Test Cases <=> Push entries * ---------------------------- * InitializeTargetExport : Tests the export from the source DS * Creates a task on current DS and makes broker simulates DS2 receiving entries * InitializeTargetImport : Test the import in the target DS * A broker simulates DS2 receiving entries from current DS. * * InitializeTargetConfigErrors : Tests configuration errors of the * InitializeTarget task */ public class InitOnLineTest extends ReplicationTestCase { /** * The tracer object for the debug logger */ private static final DebugTracer TRACER = getTracer(); private static final int WINDOW_SIZE = 10; /** * A "person" entry */ protected Entry taskInitFromS2; protected Entry taskInitTargetS2; protected Entry taskInitTargetAll; protected String[] updatedEntries; private static final int server1ID = 1; private static final int server2ID = 2; private static final int server3ID = 3; private static final int changelog1ID = 8; private static final int changelog2ID = 9; private static final int changelog3ID = 10; private static final String EXAMPLE_DN = "dc=example,dc=com"; private static int[] replServerPort = new int[20]; private DN baseDn; ReplicationBroker server2 = null; ReplicationBroker server3 = null; ReplicationServer changelog1 = null; ReplicationServer changelog2 = null; ReplicationServer changelog3 = null; boolean emptyOldChanges = true; LDAPReplicationDomain replDomain = null; int initWindow = 100; private void log(String s) { logError(Message.raw(Category.SYNC, Severity.NOTICE, "InitOnLineTests/" + s)); if (debugEnabled()) { TRACER.debugInfo(s); } } protected void log(String message, Exception e) { log(message + stackTraceToSingleLineString(e)); } /** * Set up the environment for performing the tests in this Class. * * @throws Exception * If the environment could not be set up. */ @BeforeClass @Override public void setUp() throws Exception { super.setUp(); log("Setup: debugEnabled:" + debugEnabled()); // This test suite depends on having the schema available. baseDn = DN.decode(EXAMPLE_DN); // This test uses import tasks which do not work with memory backend // (like the test backend we use in every tests): backend is disabled then // re-enabled and this clears the backend reference and thus the underlying // data. So for this particular test, we use a classical backend. Let's // clear it. LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); // For most tests, a limited number of entries is enough updatedEntries = newLDIFEntries(2); // Create an internal connection in order to provide operations // to DS to populate the db - connection = InternalClientConnection.getRootConnection(); synchroServerEntry = null; replServerEntry = null; taskInitFromS2 = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-from-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTask", "ds-task-initialize-domain-dn: " + EXAMPLE_DN, "ds-task-initialize-replica-server-id: " + server2ID); taskInitTargetS2 = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", "ds-task-initialize-domain-dn: " + EXAMPLE_DN, "ds-task-initialize-replica-server-id: " + server2ID); taskInitTargetAll = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", "ds-task-initialize-domain-dn: " + EXAMPLE_DN, "ds-task-initialize-replica-server-id: all"); } // Tests that entries have been written in the db private void testEntriesInDb() { log("TestEntriesInDb"); short found = 0; for (String entry : updatedEntries) { int dns = entry.indexOf("dn: "); int dne = entry.indexOf(EXAMPLE_DN); String dn = entry.substring(dns+4,dne+EXAMPLE_DN.length()); log("Search Entry: " + dn); DN entryDN = null; try { entryDN = DN.decode(dn); } catch(Exception e) { log("TestEntriesInDb/" + e); } try { Entry resultEntry = getEntry(entryDN, 1000, true); if (resultEntry==null) { log("Entry not found <" + dn + ">"); } else { log("Entry found <" + dn + ">"); found++; } } catch(Exception e) { log("TestEntriesInDb/", e); } } assertEquals(found, updatedEntries.length, " Entries present in DB :" + found + " Expected entries :" + updatedEntries.length); } /** * Wait a task to be completed and check the expected state and expected * stats. * @param taskEntry The task to process. * @param expectedState The expected state for this task. * @param expectedLeft The expected number of entries still to be processed. * @param expectedDone The expected number of entries to be processed. */ private void waitTaskCompleted(Entry taskEntry, TaskState expectedState, long expectedLeft, long expectedDone) { log("waitTaskCompleted " + taskEntry.toLDIFString()); try { // FIXME - Factorize with TasksTestCase // Wait until the task completes. int timeout = 2000; AttributeType completionTimeType = DirectoryServer.getAttributeType( ATTR_TASK_COMPLETION_TIME.toLowerCase()); SearchFilter filter = SearchFilter.createFilterFromString("(objectclass=*)"); Entry resultEntry = null; String completionTime = null; long startMillisecs = System.currentTimeMillis(); do { InternalSearchOperation searchOperation = connection.processSearch(taskEntry.getDN(), SearchScope.BASE_OBJECT, filter); try { resultEntry = searchOperation.getSearchEntries().getFirst(); } catch (Exception e) { // FIXME How is this possible? Must be issue 858. fail("Task entry was not returned from the search."); continue; } completionTime = resultEntry.getAttributeValue(completionTimeType, DirectoryStringSyntax.DECODER); if (completionTime == null) { if (System.currentTimeMillis() - startMillisecs > 1000*timeout) { break; } Thread.sleep(100); } } while (completionTime == null); if (completionTime == null) { fail("The task had not completed after " + timeout + " seconds."); } // Check that the task state is as expected. AttributeType taskStateType = DirectoryServer.getAttributeType(ATTR_TASK_STATE.toLowerCase()); String stateString = resultEntry.getAttributeValue(taskStateType, DirectoryStringSyntax.DECODER); TaskState taskState = TaskState.fromString(stateString); assertEquals(taskState, expectedState, "The task completed in an unexpected state"); // Check that the task contains some log messages. AttributeType logMessagesType = DirectoryServer.getAttributeType( ATTR_TASK_LOG_MESSAGES.toLowerCase()); ArrayList<String> logMessages = new ArrayList<String>(); resultEntry.getAttributeValues(logMessagesType, DirectoryStringSyntax.DECODER, logMessages); if (taskState != TaskState.COMPLETED_SUCCESSFULLY && logMessages.isEmpty()) { fail("No log messages were written to the task entry on a failed task"); } try { // Check that the task state is as expected. taskStateType = DirectoryServer.getAttributeType(ATTR_TASK_INITIALIZE_LEFT, true); stateString = resultEntry.getAttributeValue(taskStateType, DirectoryStringSyntax.DECODER); assertEquals(Long.decode(stateString).longValue(),expectedLeft, "The number of entries to process is not correct."); // Check that the task state is as expected. taskStateType = DirectoryServer.getAttributeType(ATTR_TASK_INITIALIZE_DONE, true); stateString = resultEntry.getAttributeValue(taskStateType, DirectoryStringSyntax.DECODER); assertEquals(Long.decode(stateString).longValue(),expectedDone, "The number of entries processed is not correct."); } catch(Exception e) { fail("Exception"+ e.getMessage()+e.getStackTrace()); } } catch(Exception e) { fail("Exception"+ e.getMessage()+e.getStackTrace()); } } /** * Add to the current DB the entries necessary to the test. */ private void addTestEntriesToDB() { try { for (String ldifEntry : updatedEntries) { Entry entry = TestCaseUtils.entryFromLdifString(ldifEntry); addTestEntryToDB(entry); // They will be removed at the end of the test entryList.addLast(entry.getDN()); } log("addTestEntriesToDB : " + updatedEntries.length + " successfully added to DB"); } catch(Exception e) { fail("addEntries Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); } } private void addTestEntryToDB(Entry entry) { try { AddOperationBasis addOp = new AddOperationBasis(connection, InternalClientConnection.nextOperationID(), InternalClientConnection .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(), entry.getUserAttributes(), entry.getOperationalAttributes()); addOp.setInternalOperation(true); addOp.run(); if (addOp.getResultCode() != ResultCode.SUCCESS) { log("addEntry: Failed" + addOp.getResultCode()); } // They will be removed at the end of the test entryList.addLast(entry.getDN()); } catch(Exception e) { fail("addTestEntryToDB Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); } } /* * Creates entries necessary to the test. */ private String[] newLDIFEntries(int entriesCnt) { // It is relevant to test ReplLDIFInputStream // and ReplLDIFOutputStream with big entries char bigAttributeValue[] = new char[30240]; for (int i=0; i<bigAttributeValue.length; i++) bigAttributeValue[i] = Integer.toString(i).charAt(0); String[] entries = new String[entriesCnt + 2]; String filler = "000000000000000000000000000000000000"; entries[0] = "dn: " + EXAMPLE_DN + "\n" + "objectClass: top\n" + "objectClass: domain\n" + "dc: example\n" + "entryUUID: 21111111-1111-1111-1111-111111111111\n" + "\n"; entries[1] = "dn: ou=People," + EXAMPLE_DN + "\n" + "objectClass: top\n" + "objectClass: organizationalUnit\n" + "ou: People\n" + "entryUUID: 21111111-1111-1111-1111-111111111112\n" + "\n"; for (int i=0; i<entriesCnt; i++) { String useri="0000"+i; entries[i+2] = "dn: cn="+useri+",ou=people," + EXAMPLE_DN + "\n" + "objectclass: top\n" + "objectclass: person\n" + "objectclass: organizationalPerson\n" + "objectclass: inetOrgPerson\n" + "cn: "+useri+"_cn"+"\n" + "sn: "+useri+"_sn"+"\n" + "uid: "+useri+"_uid"+"\n" + "description:: "+ Base64.encode( new String(bigAttributeValue).getBytes())+"\n" + "entryUUID: 21111111-1111-1111-1111-"+useri+ filler.substring(0, 12-useri.length())+"\n" + "\n"; } return entries; } /** * Broker will send the entries to a server. * @param broker The broker that will send the entries. * @param senderID The serverID of this broker. * @param destinationServerID The target server. * @param requestorID The initiator server. */ private void makeBrokerPublishEntries(ReplicationBroker broker, int senderID, int destinationServerID, int requestorID) { // Send entries try { RoutableMsg initTargetMessage = new InitializeTargetMsg( EXAMPLE_DN, server2ID, destinationServerID, requestorID, updatedEntries.length, initWindow); broker.publish(initTargetMessage); int cnt = 0; for (String entry : updatedEntries) { log("Broker will publish 1 entry: bytes:"+ entry.length()); EntryMsg entryMsg = new EntryMsg(senderID, destinationServerID, entry.getBytes(), ++cnt); broker.publish(entryMsg); } DoneMsg doneMsg = new DoneMsg(senderID, destinationServerID); broker.publish(doneMsg); log("Broker " + senderID + " published entries"); } catch(Exception e) { fail("makeBrokerPublishEntries Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); } } void receiveUpdatedEntries(ReplicationBroker broker, int serverID, String[] updatedEntries) { // Expect the broker to receive the entries ReplicationMsg msg; short entriesReceived = 0; while (true) { try { log("Broker " + serverID + " Wait for entry or done msg"); msg = broker.receive(); if (msg == null) break; if (msg instanceof InitializeTargetMsg) { log("Broker " + serverID + " receives InitializeTargetMessage "); entriesReceived = 0; } else if (msg instanceof EntryMsg) { EntryMsg em = (EntryMsg)msg; log("Broker " + serverID + " receives entry " + new String(em.getEntryBytes())); entriesReceived+=countEntryLimits(em.getEntryBytes()); } else if (msg instanceof DoneMsg) { log("Broker " + serverID + " receives done "); break; } else if (msg instanceof ErrorMsg) { ErrorMsg em = (ErrorMsg)msg; log("Broker " + serverID + " receives ERROR " + " " + em.getDetails()); break; } else { log("Broker " + serverID + " receives and trashes " + msg); } } catch (SocketTimeoutException e) { log("SocketTimeoutException while waiting for entries" + stackTraceToSingleLineString(e)); } catch(Exception e) { log("receiveUpdatedEntries" + stackTraceToSingleLineString(e)); } } assertTrue(entriesReceived == updatedEntries.length, " Received entries("+entriesReceived + ") == Expected entries("+updatedEntries.length+")"); broker.setGenerationID(EMPTY_DN_GENID); broker.reStart(true); try { Thread.sleep(500); } catch(Exception e) {} } /** * Count the number of entries in the provided byte[]. * This is based on the hypothesis that the entries are separated * by a "\n\n" String. * * @param entryBytes * @return The number of entries in the provided byte[]. */ private int countEntryLimits(byte[] entryBytes) { int entryCount = 0; int count = 0; while (count<=entryBytes.length-2) { if ((entryBytes[count] == '\n') && (entryBytes[count+1] == '\n')) { entryCount++; count++; } count++; } return entryCount; } /** * Creates a new replicationServer. * @param changelogId The serverID of the replicationServer to create. * @return The new replicationServer. */ private ReplicationServer createChangelogServer(int changelogId, String testCase) { SortedSet<String> servers = new TreeSet<String>(); try { if (changelogId != changelog1ID) servers.add("localhost:" + getChangelogPort(changelog1ID)); if (changelogId != changelog2ID) servers.add("localhost:" + getChangelogPort(changelog2ID)); if (changelogId != changelog3ID) servers.add("localhost:" + getChangelogPort(changelog3ID)); ReplServerFakeConfiguration conf = new ReplServerFakeConfiguration( getChangelogPort(changelogId), "initOnlineTest" + getChangelogPort(changelogId) + testCase + "Db", 0, changelogId, 0, 100, servers); ReplicationServer replicationServer = new ReplicationServer(conf); Thread.sleep(1000); return replicationServer; } catch (Exception e) { fail("createChangelog" + stackTraceToSingleLineString(e)); } return null; } /** * Create a synchronized suffix in the current server providing the * replication Server ID. * @param changelogID */ private void connectServer1ToChangelog(int changelogID) { connectServer1ToChangelog(changelogID, 0); } private void connectServer1ToChangelog(int changelogID, int heartbeat) { // Connect DS to the replicationServer try { // suffix synchronized String testName = "initOnLineTest"; String synchroServerLdif = "dn: cn=" + testName + ", cn=domains," + SYNCHRO_PLUGIN_DN + "\n" + "objectClass: top\n" + "objectClass: ds-cfg-synchronization-provider\n" + "objectClass: ds-cfg-replication-domain\n" + "cn: " + testName + "\n" + "ds-cfg-base-dn: " + EXAMPLE_DN + "\n" + "ds-cfg-replication-server: localhost:" + getChangelogPort(changelogID)+"\n" + "ds-cfg-server-id: " + server1ID + "\n" + "ds-cfg-receive-status: true\n" + (heartbeat>0?"ds-cfg-heartbeat-interval: "+heartbeat+" ms\n":"") + "ds-cfg-window-size: " + WINDOW_SIZE; // Clear the backend LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif); DirectoryServer.getConfigHandler().addEntry(synchroServerEntry, null); assertNotNull(DirectoryServer.getConfigEntry(synchroServerEntry.getDN()), "Unable to add the synchronized server"); configEntryList.add(synchroServerEntry.getDN()); replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn); assertTrue(!replDomain.ieRunning(), "ReplicationDomain: Import/Export is not expected to be running"); } catch(Exception e) { log("connectServer1ToChangelog", e); fail("connectServer1ToChangelog", e); } } private int getChangelogPort(int changelogID) { if (replServerPort[changelogID] == 0) { try { // Find a free port for the replicationServer ServerSocket socket = TestCaseUtils.bindFreePort(); replServerPort[changelogID] = socket.getLocalPort(); socket.close(); } catch(Exception e) { fail("Cannot retrieve a free port for replication server." + e.getMessage()); } } return replServerPort[changelogID]; } /** * Tests the import side of the Initialize task * Test steps : * - create a task 'InitFromS2' in S1 * - make S2 export its entries * - test that S1 has successfully imported the entries and completed the task. * * TODO: Error case: make S2 crash/disconnect in the middle of the export * and test that, on S1 side, the task ends with an error. * State of the backend on S1 partially initialized: ? */ @Test(enabled=true, groups="slow") public void initializeImport() throws Exception { String testCase = "initializeImport "; log("Starting "+testCase); try { changelog1 = createChangelogServer(changelog1ID, testCase); // Connect DS to the replicationServer connectServer1ToChangelog(changelog1ID); if (server2 == null) server2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); // Thread.sleep(2000); // In S1 launch the total update addTask(taskInitFromS2, ResultCode.SUCCESS, null); // S2 should receive init msg ReplicationMsg msg; msg = server2.receive(); if (!(msg instanceof InitializeRequestMsg)) { fail(testCase + " Message received by S2 is of unexpected class" + msg); } InitializeRequestMsg initMsg = (InitializeRequestMsg)msg; // S2 publishes entries to S1 makeBrokerPublishEntries(server2, server2ID, initMsg.getSenderID(), initMsg.getSenderID()); // Wait for task (import) completion in S1 waitTaskCompleted(taskInitFromS2, TaskState.COMPLETED_SUCCESSFULLY, 0, updatedEntries.length); // Test import result in S1 testEntriesInDb(); log("Successfully ending " + testCase); } catch(Exception e) { fail(testCase + " Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); } finally { afterTest(testCase); } } /** * Tests the export side of the Initialize task * Test steps : * - add entries in S1, make S2 publish InitRequest * - test that S1 has successfully exported the entries (by receiving them * on S2 side). */ @Test(enabled=true, groups="slow") public void initializeExport() throws Exception { String testCase = "initializeExport"; log("Starting "+testCase); try { changelog1 = createChangelogServer(changelog1ID, testCase); // Connect DS to the replicationServer connectServer1ToChangelog(changelog1ID); addTestEntriesToDB(); if (server2 == null) server2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); // Not needed anymore since OpenReplicationSession // checks for session establishment ? // Thread.sleep(3000); InitializeRequestMsg initMsg = new InitializeRequestMsg(EXAMPLE_DN, server2ID, server1ID, 100); server2.publish(initMsg); // Signal RS we just entered the full update status server2.signalStatusChange(ServerStatus.FULL_UPDATE_STATUS); receiveUpdatedEntries(server2, server2ID, updatedEntries); log("Successfully ending " + testCase); } finally { afterTest(testCase); } } /** * Tests the import side of the InitializeTarget task * Test steps : * - add entries in S1 and create a task 'InitTargetS2' in S1 * - wait task completed * - test that S2 has successfully received the entries */ @Test(enabled=true, groups="slow") public void initializeTargetExport() throws Exception { String testCase = "initializeTargetExport"; log("Starting " + testCase); try { changelog1 = createChangelogServer(changelog1ID, testCase); // Creates config to synchronize suffix connectServer1ToChangelog(changelog1ID); // Add in S1 the entries to be exported addTestEntriesToDB(); // S1 is the server we are running in, S2 is simulated by a broker if (server2 == null) server2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); // Thread.sleep(1000); // Launch in S1 the task that will initialize S2 addTask(taskInitTargetS2, ResultCode.SUCCESS, null); // Signal RS we just entered the full update status server2.signalStatusChange(ServerStatus.FULL_UPDATE_STATUS); // Tests that entries have been received by S2 receiveUpdatedEntries(server2, server2ID, updatedEntries); // Wait for task completion waitTaskState(taskInitTargetS2, TaskState.COMPLETED_SUCCESSFULLY, null); log("Successfully ending " + testCase); } finally { afterTest(testCase); } } /** * Tests the import side of the InitializeTarget task * Test steps : * - addEntries in S1, create a task 'InitAll' in S1 * - wait task completed on S1 * - test that S2 and S3 have successfully imported the entries. * * TODO: Error case: make S1 crash in the middle of the export and test that * the task ends with an error. State of the backend on both S2 and S3: ? * * TODO: Error case: make S2 crash in the middle of the import and test what?? */ @Test(enabled=true, groups="slow") public void initializeTargetExportAll() throws Exception { String testCase = "initializeTargetExportAll"; log("Starting " + testCase); try { changelog1 = createChangelogServer(changelog1ID, testCase); // Creates config to synchronize suffix connectServer1ToChangelog(changelog1ID); // Add in S1 the entries to be exported addTestEntriesToDB(); // S1 is the server we are running in, S2 and S3 are simulated by brokers if (server2 == null) server2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); if (server3 == null) server3 = openReplicationSession(DN.decode(EXAMPLE_DN), server3ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); // Thread.sleep(1000); // Launch in S1 the task that will initialize S2 addTask(taskInitTargetAll, ResultCode.SUCCESS, null); // Tests that entries have been received by S2 // Signal RS we just entered the full update status server2.signalStatusChange(ServerStatus.FULL_UPDATE_STATUS); server3.signalStatusChange(ServerStatus.FULL_UPDATE_STATUS); receiveUpdatedEntries(server2, server2ID, updatedEntries); receiveUpdatedEntries(server3, server3ID, updatedEntries); // Wait for task completion waitTaskState(taskInitTargetAll, TaskState.COMPLETED_SUCCESSFULLY, null); log("Successfully ending " + testCase); } finally { afterTest(testCase); } } /** * Tests the import side of the InitializeTarget task */ @Test(enabled=true, groups="slow") public void initializeTargetImport() throws Exception { String testCase = "initializeTargetImport"; try { log("Starting " + testCase + " debugEnabled:" + debugEnabled()); // Start SS changelog1 = createChangelogServer(changelog1ID, testCase); // S1 is the server we are running in, S2 is simulated by a broker if (server2==null) server2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); // Creates config to synchronize suffix connectServer1ToChangelog(changelog1ID); // S2 publishes entries to S1 makeBrokerPublishEntries(server2, server2ID, server1ID, server2ID); // wait until the replication domain has expected generationID // this should indicate that the import occurred correctly. final long EXPECTED_GENERATION_ID = 52955L; long readGenerationId = -1L; for (int count = 0; count < 120; count++) { readGenerationId = replDomain.getGenerationID(); if ( readGenerationId == EXPECTED_GENERATION_ID) break; log(testCase + " genId=" + readGenerationId); Thread.sleep(1000); } if (readGenerationId != EXPECTED_GENERATION_ID) { fail(testCase + " Import success waited longer than expected \n" + TestCaseUtils.threadStacksToString()); } // Test that entries have been imported in S1 testEntriesInDb(); log("Successfully ending " + testCase); } catch(Exception e) { fail(testCase + " Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); } finally { afterTest(testCase); } } /** * Tests the import side of the InitializeTarget task */ @Test(enabled=false) public void initializeTargetConfigErrors() throws Exception { String testCase = "InitializeTargetConfigErrors"; try { log("Starting " + testCase); // Invalid domain base dn Entry taskInitTarget = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", "ds-task-initialize-domain-dn: foo", "ds-task-initialize-remote-replica-server-id: " + server2ID); addTask(taskInitTarget, ResultCode.INVALID_DN_SYNTAX, ERR_TASK_INITIALIZE_INVALID_DN.get()); // Domain base dn not related to any domain taskInitTarget = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", "ds-task-initialize-domain-dn: dc=foo", "ds-task-initialize-remote-replica-server-id: " + server2ID); addTask(taskInitTarget, ResultCode.OTHER, ERR_NO_MATCHING_DOMAIN.get("")); // Invalid scope // createTask(taskInitTargetS2); // Scope containing a serverID absent from the domain // createTask(taskInitTargetS2); log("Successfully ending " + testCase); } catch(Exception e) { fail(testCase + " Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); } finally { afterTest(testCase); } } /** * Tests the import side of the InitializeTarget task */ @Test(enabled=true) public void initializeConfigErrors() throws Exception { String testCase = "initializeConfigErrors"; try { log("Starting " + testCase); // Start SS changelog1 = createChangelogServer(changelog1ID, testCase); // Creates config to synchronize suffix connectServer1ToChangelog(changelog1ID); // Invalid domain base dn Entry taskInit = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-from-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTask", "ds-task-initialize-domain-dn: dc=foo", "ds-task-initialize-replica-server-id: " + server2ID); addTask(taskInit, ResultCode.INVALID_DN_SYNTAX, ERR_NO_MATCHING_DOMAIN.get("dc=foo")); // Domain base dn not related to any domain taskInit = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-from-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTask", "ds-task-initialize-domain-dn: dc=foo", "ds-task-initialize-replica-server-id: " + server2ID); addTask(taskInit, ResultCode.INVALID_DN_SYNTAX, ERR_NO_MATCHING_DOMAIN.get("dc=foo")); // Invalid Source taskInit = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-from-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTask", "ds-task-initialize-domain-dn: " + baseDn, "ds-task-initialize-replica-server-id: -3"); addTask(taskInit, ResultCode.OTHER, ERR_INVALID_IMPORT_SOURCE.get(baseDn.toNormalizedString(), Integer.toString(server1ID),"-3","")); // Scope containing a serverID absent from the domain // createTask(taskInitTargetS2); log("Successfully ending " + testCase); } catch(Exception e) { fail(testCase + " Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); } finally { afterTest(testCase); } } @Test(enabled=false) public void initializeTargetBroken() throws Exception { String testCase = "InitializeTargetBroken"; fail(testCase + " NYI"); } @Test(enabled=false) public void initializeBroken() throws Exception { String testCase = "InitializeBroken"; fail(testCase + " NYI"); } /* * TestReplServerInfos tests that in a topology with more * than one replication server, in each replication server * is stored the list of LDAP servers connected to each * replication server of the topology, thanks to the * ReplServerInfoMessage(s) exchanged by the replication * servers. */ @Test(enabled=true, groups="slow") public void testReplServerInfos() throws Exception { String testCase = "testReplServerInfos"; log("Starting " + testCase); ReplicationBroker broker2 = null; ReplicationBroker broker3 = null; try { // Create the Repl Servers changelog1 = createChangelogServer(changelog1ID, testCase); changelog2 = createChangelogServer(changelog2ID, testCase); changelog3 = createChangelogServer(changelog3ID, testCase); // Connects lDAP1 to replServer1 connectServer1ToChangelog(changelog1ID); // Connects lDAP2 to replServer2 broker2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges); // Connects lDAP3 to replServer2 broker3 = openReplicationSession(DN.decode(EXAMPLE_DN), server3ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges); // Check that the list of connected LDAP servers is correct // in each replication servers List<String> l1 = changelog1.getReplicationServerDomain( baseDn.toNormalizedString(), false). getConnectedLDAPservers(); assertEquals(l1.size(), 1); assertEquals(l1.get(0), String.valueOf(server1ID)); List<String> l2; l2 = changelog2.getReplicationServerDomain( baseDn.toNormalizedString(), false).getConnectedLDAPservers(); assertEquals(l2.size(), 2); assertTrue(l2.contains(String.valueOf(server2ID))); assertTrue(l2.contains(String.valueOf(server3ID))); List<String> l3; l3 = changelog3.getReplicationServerDomain( baseDn.toNormalizedString(), false).getConnectedLDAPservers(); assertEquals(l3.size(), 0); // Test updates broker3.stop(); Thread.sleep(1000); l2 = changelog2.getReplicationServerDomain( baseDn.toNormalizedString(), false).getConnectedLDAPservers(); assertEquals(l2.size(), 1); assertEquals(l2.get(0), String.valueOf(server2ID)); broker3 = openReplicationSession(DN.decode(EXAMPLE_DN), server3ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges); broker2.stop(); Thread.sleep(1000); l2 = changelog2.getReplicationServerDomain( baseDn.toNormalizedString(), false).getConnectedLDAPservers(); assertEquals(l2.size(), 1); assertEquals(l2.get(0), String.valueOf(server3ID)); // TODO Test ReplicationServerDomain.getDestinationServers method. log("Successfully ending " + testCase); } finally { if (broker2 != null) broker2.stop(); if (broker3 != null) broker3.stop(); afterTest(testCase); } } @Test(enabled=true, groups="slow") public void initializeTargetExportMultiSS() throws Exception { String testCase = "initializeTargetExportMultiSS"; try { log("Starting " + testCase); // Create 2 changelogs changelog1 = createChangelogServer(changelog1ID, testCase); changelog2 = createChangelogServer(changelog2ID, testCase); // Creates config to synchronize suffix connectServer1ToChangelog(changelog1ID); // Add in S1 the entries to be exported addTestEntriesToDB(); // S1 is the server we are running in, S2 is simulated by a broker // connected to changelog2 if (server2 == null) { log(testCase + " Will connect server 2 to " + changelog2ID); server2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges); } // Thread.sleep(1000); // Launch in S1 the task that will initialize S2 log(testCase + " add task " + Thread.currentThread()); addTask(taskInitTargetS2, ResultCode.SUCCESS, null); log(testCase + " " + server2.getServerId() + " wait target " + Thread.currentThread()); ReplicationMsg msgrcv; do { msgrcv = server2.receive(); log(testCase + " " + server2.getServerId() + " receives " + msgrcv); } while(!(msgrcv instanceof InitializeTargetMsg)); assertTrue(msgrcv instanceof InitializeTargetMsg, msgrcv.getClass().getCanonicalName()); // Signal RS we just entered the full update status log(testCase + " change status"); server2.signalStatusChange(ServerStatus.FULL_UPDATE_STATUS); // Tests that entries have been received by S2 log(testCase + " receive entries"); receiveUpdatedEntries(server2, server2ID, updatedEntries); // Wait for task completion log(testCase + " wait task completed"); waitTaskState(taskInitTargetS2, TaskState.COMPLETED_SUCCESSFULLY, null); log("Successfully ending " + testCase); } catch(Exception e) { log(testCase + e.getLocalizedMessage()); } finally { afterTest(testCase); } } @Test(enabled=true, groups="slow") public void initializeExportMultiSS() throws Exception { String testCase = "initializeExportMultiSS"; log("Starting "+testCase); try { // Create 2 changelogs changelog1 = createChangelogServer(changelog1ID, testCase); Thread.sleep(1000); changelog2 = createChangelogServer(changelog2ID, testCase); Thread.sleep(1000); // Connect DS to the replicationServer 1 connectServer1ToChangelog(changelog1ID); // Put entries in DB log(testCase + " Will add entries"); addTestEntriesToDB(); // Connect a broker acting as server 2 to Repl Server 2 if (server2 == null) { log(testCase + " Will connect server 2 to " + changelog2ID); server2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges, changelog1.getGenerationId(baseDn.toNormalizedString())); } // Connect a broker acting as server 3 to Repl Server 3 log(testCase + " Will create replServer " + changelog3ID); changelog3 = createChangelogServer(changelog3ID, testCase); Thread.sleep(500); if (server3 == null) { log(testCase + " Will connect server 3 to " + changelog3ID); server3 = openReplicationSession(DN.decode(EXAMPLE_DN), server3ID, 100, getChangelogPort(changelog3ID), 1000, emptyOldChanges, changelog1.getGenerationId(baseDn.toNormalizedString())); } // Thread.sleep(500); // S3 sends init request log(testCase + " server 3 Will send reqinit to " + server1ID); InitializeRequestMsg initMsg = new InitializeRequestMsg(EXAMPLE_DN, server3ID, server1ID, 100); server3.publish(initMsg); // S3 should receive target, entries & done log(testCase + " Wait for InitializeTargetMsg"); ReplicationMsg msgrcv = null; do { msgrcv = server3.receive(); log(testCase + " receives "+ msgrcv); } while (!(msgrcv instanceof InitializeTargetMsg)); assertTrue(msgrcv instanceof InitializeTargetMsg,msgrcv.getClass().getCanonicalName() + msgrcv); // Signal RS we just entered the full update status server3.signalStatusChange(ServerStatus.FULL_UPDATE_STATUS); log(testCase + " Will verify server 3 has received expected entries"); receiveUpdatedEntries(server3, server3ID, updatedEntries); log("Successfully ending " + testCase); } finally { afterTest(testCase); } } @Test(enabled=false) public void initializeNoSource() throws Exception { String testCase = "initializeNoSource"; log("Starting "+testCase); try { // Start Replication Server changelog1 = createChangelogServer(changelog1ID, testCase); // Creates config to synchronize suffix connectServer1ToChangelog(changelog1ID); // Test 1 Entry taskInit = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-from-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTask", "ds-task-initialize-domain-dn: " + baseDn, "ds-task-initialize-replica-server-id: " + 20); addTask(taskInit, ResultCode.SUCCESS, null); waitTaskState(taskInit, TaskState.STOPPED_BY_ERROR, ERR_NO_REACHABLE_PEER_IN_THE_DOMAIN.get( baseDn.toString(), "20")); // Test 2 taskInit = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-from-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTask", "ds-task-initialize-domain-dn: " + baseDn, "ds-task-initialize-replica-server-id: " + server1ID); addTask(taskInit, ResultCode.OTHER, ERR_INVALID_IMPORT_SOURCE.get( baseDn.toNormalizedString(), Integer.toString(server1ID),"20","")); if (replDomain != null) { assertTrue(!replDomain.ieRunning(), "ReplicationDomain: Import/Export is not expected to be running"); } log("Successfully ending " + testCase); } finally { afterTest(testCase); } } @Test(enabled=false) public void initializeTargetNoTarget() throws Exception { String testCase = "initializeTargetNoTarget" + baseDn; log("Starting "+testCase); try { // Start SS changelog1 = createChangelogServer(changelog1ID, testCase); // Creates config to synchronize suffix connectServer1ToChangelog(changelog1ID); // Put entries in DB addTestEntriesToDB(); Entry taskInit = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", "ds-task-initialize-domain-dn: " + baseDn, "ds-task-initialize-replica-server-id: " + 0); addTask(taskInit, ResultCode.SUCCESS, null); waitTaskState(taskInit, TaskState.STOPPED_BY_ERROR, ERR_NO_REACHABLE_PEER_IN_THE_DOMAIN.get(baseDn.toString(), "0")); if (replDomain != null) { assertTrue(!replDomain.ieRunning(), "ReplicationDomain: Import/Export is not expected to be running"); } log("Successfully ending " + testCase); } finally { afterTest(testCase); } } @Test(enabled=false) public void initializeStopped() throws Exception { String testCase = "InitializeStopped"; fail(testCase + " NYI"); } @Test(enabled=false) public void initializeTargetStopped() throws Exception { String testCase = "InitializeTargetStopped"; fail(testCase + " NYI"); } @Test(enabled=false) public void initializeCompressed() throws Exception { String testCase = "InitializeStopped"; fail(testCase + " NYI"); } @Test(enabled=false) public void initializeTargetEncrypted() throws Exception { String testCase = "InitializeTargetCompressed"; fail(testCase + " NYI"); } @Test(enabled = false) public void initializeSimultaneous() throws Exception { String testCase = "initializeSimultaneous"; try { // Start SS changelog1 = createChangelogServer(changelog1ID, testCase); // Connect a broker acting as server 2 to changelog2 if (server2 == null) { server2 = openReplicationSession(DN.decode(EXAMPLE_DN), server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); } // Creates config to synchronize suffix connectServer1ToChangelog(changelog1ID); Entry taskInit = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-from-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTask", "ds-task-initialize-domain-dn: " + baseDn, "ds-task-initialize-replica-server-id: " + server2ID); addTask(taskInit, ResultCode.SUCCESS, null); Thread.sleep(3000); Entry taskInit2 = TestCaseUtils.makeEntry( "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks", "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-initialize-from-remote-replica", "ds-task-class-name: org.opends.server.tasks.InitializeTask", "ds-task-initialize-domain-dn: " + baseDn, "ds-task-initialize-replica-server-id: " + server2ID); // Second task is expected to be rejected addTask(taskInit2, ResultCode.SUCCESS, null); waitTaskState(taskInit2, TaskState.STOPPED_BY_ERROR, ERR_SIMULTANEOUS_IMPORT_EXPORT_REJECTED.get()); // First task is stilll running waitTaskState(taskInit, TaskState.RUNNING, null); // External request is supposed to be rejected // Now tests error in the middle of an import // S2 sends init request ErrorMsg msg = new ErrorMsg(server1ID, 1, Message.EMPTY); server2.publish(msg); waitTaskState(taskInit, TaskState.STOPPED_BY_ERROR, null); log("Successfully ending " + testCase); } finally { afterTest(testCase); } } /** * Disconnect broker and remove entries from the local DB * @param testCase The name of the test case. */ protected void afterTest(String testCase) { // Check that the domain has completed the import/export task. if (replDomain != null) { // race condition could cause the main thread to reach // this code before the task is fully completed. // in those cases, loop for a while waiting for completion. for (int i = 0; i< 10; i++) { if (replDomain.ieRunning()) { try { Thread.sleep(500); } catch (InterruptedException e) { } } else { break; } } assertTrue(!replDomain.ieRunning(), "ReplicationDomain: Import/Export is not expected to be running"); } // Remove domain config super.cleanConfigEntries(); replDomain = null; // Clean brokers if (server2 != null) { server2.stop(); TestCaseUtils.sleep(100); // give some time to the broker to disconnect // from the replicationServer. server2 = null; } if (server3 != null) { server3.stop(); TestCaseUtils.sleep(100); // give some time to the broker to disconnect // from the replicationServer. server3 = null; } super.cleanRealEntries(); if (changelog1 != null) { changelog1.clearDb(); changelog1.remove(); StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(), changelog1.getDbDirName())); changelog1 = null; } if (changelog2 != null) { changelog2.clearDb(); changelog2.remove(); StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(), changelog2.getDbDirName())); changelog2 = null; } if (changelog3 != null) { changelog3.clearDb(); changelog3.remove(); StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(), changelog3.getDbDirName())); changelog3 = null; } // Clean replication server ports for (int i = 0; i < replServerPort.length; i++) { replServerPort[i] = 0; } log("Successfully cleaned " + testCase); } /** * Clean up the environment. * * @throws Exception If the environment could not be set up. */ @AfterClass @Override public void classCleanUp() throws Exception { callParanoiaCheck = false; super.classCleanUp(); // Clear the backend LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); paranoiaCheck(); } }