/* * 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-2009 Sun Microsystems, Inc. * Portions Copyright 2011 ForgeRock AS */ package org.opends.server.replication.plugin; import org.opends.server.util.StaticUtils; import org.opends.server.core.DirectoryServer; import java.io.File; import java.io.IOException; import java.net.ServerSocket; import java.util.SortedSet; import java.util.TreeSet; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import org.opends.server.types.DirectoryException; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import org.opends.messages.Category; import org.opends.messages.Message; import org.opends.messages.Severity; import org.opends.server.TestCaseUtils; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.replication.ReplicationTestCase; import org.opends.server.replication.server.ReplServerFakeConfiguration; import org.opends.server.replication.server.ReplicationServer; import org.opends.server.types.DN; import org.testng.annotations.Test; import static org.testng.Assert.*; import static org.opends.server.TestCaseUtils.*; /** * Some real connections from clients that should end up with a server with * the right groupid if available. */ public class GroupIdHandshakeTest extends ReplicationTestCase { private static final int DS1_ID = 1; private static final int DS2_ID = 2; private static final int RS1_ID = 61; private static final int RS2_ID = 62; private static final int RS3_ID = 63; private int rs1Port = -1; private int rs2Port = -1; private int rs3Port = -1; private LDAPReplicationDomain rd1 = null; private LDAPReplicationDomain rd2 = null; private ReplicationServer rs1 = null; private ReplicationServer rs2 = null; private ReplicationServer rs3 = null; // The tracer object for the debug logger private static final DebugTracer TRACER = getTracer(); private void debugInfo(String s) { logError(Message.raw(Category.SYNC, Severity.NOTICE, s)); if (debugEnabled()) { TRACER.debugInfo("** TEST **" + s); } } private void debugInfo(String message, Exception e) { debugInfo(message + stackTraceToSingleLineString(e)); } private void initTest() { rs1Port = -1; rs2Port = -1; rs3Port = -1; rd1 = null; rd2 = null; rs1 = null; rs2 = null; rs3 = null; findFreePorts(); } private void endTest() { if (rd1 != null) { rd1.shutdown(); rd1 = null; } if (rd2 != null) { rd2.shutdown(); rd2 = null; } try { // Clear any reference to a domain in synchro plugin MultimasterReplication.deleteDomain(DN.decode(TEST_ROOT_DN_STRING)); } catch (DirectoryException ex) { fail("Error deleting reference to domain: " + TEST_ROOT_DN_STRING); } if (rs1 != null) { rs1.clearDb(); rs1.remove(); StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(), rs1.getDbDirName())); rs1 = null; } if (rs2 != null) { rs2.clearDb(); rs2.remove(); StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(), rs2.getDbDirName())); rs2 = null; } if (rs3 != null) { rs3.clearDb(); rs3.remove(); StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(), rs3.getDbDirName())); rs3 = null; } rs1Port = -1; rs2Port = -1; rs3Port = -1; } /** * Check connection of the provided replication domain to the provided * replication server. Waits for connection to be ok up to secTimeout seconds * before failing. */ private void checkConnection(int secTimeout, int dsId, int rsId, String msg) { int rsPort = -1; LDAPReplicationDomain rd = null; switch (dsId) { case DS1_ID: rd = rd1; break; case DS2_ID: rd = rd2; break; default: fail("Unknown replication domain server id."); } switch (rsId) { case RS1_ID: rsPort = rs1Port; break; case RS2_ID: rsPort = rs2Port; break; case RS3_ID: rsPort = rs3Port; break; default: fail("Unknown replication server id."); } int nSec = 0; // Go out of the loop only if connection is verified or if timeout occurs while (true) { // Test connection boolean connected = rd.isConnected(); int rdPort = -1; boolean rightPort = false; if (connected) { String serverStr = rd.getReplicationServer(); int index = serverStr.lastIndexOf(':'); if ((index == -1) || (index >= serverStr.length())) fail("Enable to find port number in: " + serverStr); String rdPortStr = serverStr.substring(index + 1); try { rdPort = (new Integer(rdPortStr)).intValue(); } catch (Exception e) { fail("Enable to get an int from: " + rdPortStr); } if (rdPort == rsPort) rightPort = true; } if (connected && rightPort) { // Connection verified debugInfo("checkConnection: connection from domain " + dsId + " to" + " replication server " + rsId + " obtained after " + nSec + " seconds."); return; } // Sleep 1 second try { Thread.sleep(1000); } catch (InterruptedException ex) { fail("Error sleeping " + stackTraceToSingleLineString(ex)); } nSec++; if (nSec > secTimeout) { // Timeout reached, end with error fail("checkConnection: could not verify connection from domain " + dsId + " to replication server " + rsId + " after " + secTimeout + " seconds." + " Domain connected: " + connected + ", connection port: " + rdPort + " (should be: " + rsPort + "). [" + msg + "]"); } } } /** * Find needed free TCP ports. */ private void findFreePorts() { try { ServerSocket socket1 = TestCaseUtils.bindFreePort(); ServerSocket socket2 = TestCaseUtils.bindFreePort(); ServerSocket socket3 = TestCaseUtils.bindFreePort(); rs1Port = socket1.getLocalPort(); rs2Port = socket2.getLocalPort(); rs3Port = socket3.getLocalPort(); socket1.close(); socket2.close(); socket3.close(); } catch (IOException e) { fail("Unable to determinate some free ports " + stackTraceToSingleLineString(e)); } } /** * Creates the list of servers to represent the RS topology matching the * passed test case. */ private SortedSet<String> createRSListForTestCase(String testCase) { SortedSet<String> replServers = new TreeSet<String>(); if (testCase.equals("testRSWithSameGroupIds")) { // 2 servers used for this test case. replServers.add("localhost:" + rs1Port); replServers.add("localhost:" + rs2Port); } else if (testCase.equals("testRSWithManyGroupIds")) { // 3 servers used for this test case. replServers.add("localhost:" + rs1Port); replServers.add("localhost:" + rs2Port); replServers.add("localhost:" + rs3Port); } else fail("Unknown test case: " + testCase); return replServers; } /** * Creates a new ReplicationServer. */ private ReplicationServer createReplicationServer(int serverId, int groupId, String testCase) { SortedSet<String> replServers = new TreeSet<String>(); try { int port = -1; if (serverId == RS1_ID) { port = rs1Port; if (testCase.equals("testRSWithSameGroupIds")) { // 2 servers used for this test case. replServers.add("localhost:" + rs2Port); } else if (testCase.equals("testRSWithManyGroupIds")) { // 3 servers used for this test case. replServers.add("localhost:" + rs2Port); replServers.add("localhost:" + rs3Port); } else fail("Unknown test case: " + testCase); } else if (serverId == RS2_ID) { port = rs2Port; if (testCase.equals("testRSWithSameGroupIds")) { // 2 servers used for this test case. replServers.add("localhost:" + rs1Port); } else if (testCase.equals("testRSWithManyGroupIds")) { // 3 servers used for this test case. replServers.add("localhost:" + rs1Port); replServers.add("localhost:" + rs3Port); } else fail("Unknown test case: " + testCase); } else if (serverId == RS3_ID) { port = rs3Port; if (testCase.equals("testRSWithManyGroupIds")) { // 3 servers used for this test case. replServers.add("localhost:" + rs2Port); replServers.add("localhost:" + rs3Port); } else fail("Invalid test case: " + testCase); } else { fail("Unknown replication server id."); } String dir = "groupIdHandshakeTest" + serverId + testCase + "Db"; ReplServerFakeConfiguration conf = new ReplServerFakeConfiguration(port, dir, 0, serverId, 0, 100, replServers, groupId, 1000, 5000); ReplicationServer replicationServer = new ReplicationServer(conf); return replicationServer; } catch (Exception e) { fail("createReplicationServer " + stackTraceToSingleLineString(e)); } return null; } /** * Creates a new ReplicationDomain. */ private LDAPReplicationDomain createReplicationDomain(int serverId, int groupId, String testCase) { SortedSet<String> replServers = null; try { replServers = createRSListForTestCase(testCase); DN baseDn = DN.decode(TEST_ROOT_DN_STRING); DomainFakeCfg domainConf = new DomainFakeCfg(baseDn, serverId, replServers, groupId); LDAPReplicationDomain replicationDomain = MultimasterReplication.createNewDomain(domainConf); replicationDomain.start(); return replicationDomain; } catch (Exception e) { fail("createReplicationDomain " + stackTraceToSingleLineString(e)); } return null; } /** * Connections with RSs that have the same group ids: * * Full topo is: * - RS1 with GID=1 * - RS2 with GID=1 * - DS1 with GID=1 * - DS2 with GID=2 * Scenario is: * - Start RS1 and RS2 with both GID=1 * - Start DS1 with GID=1 (should connect to a RS with his GID) * - Start DS2 with GID=2 (should connect with a RS with wrong GID as only * GID=1 is available) * * @throws Exception If a problem occurred */ @Test public void testRSWithSameGroupIds() throws Exception { String testCase = "testRSWithSameGroupIds"; debugInfo("Starting " + testCase); initTest(); try { /** * Start RS1 and RS2 with both GID=1 */ // Create and start RS1 rs1 = createReplicationServer(RS1_ID, 1, testCase); // Create and start RS2 rs2 = createReplicationServer(RS2_ID, 1, testCase); /** * Start DS1 with GID=1 (should connect to a RS with his GID) */ // Start DS1 rd1 = createReplicationDomain(DS1_ID, 1, testCase); assertTrue(rd1.isConnected()); /** * Start DS2 with GID=2 (should connect with a RS with wrong GID as only * GID=1 is available */ // Start DS2 rd2 = createReplicationDomain(DS2_ID, 2, testCase); assertTrue(rd2.isConnected()); } finally { endTest(); } } /** * Test connection algorithm, focusing on group ids. Also test the mechanism * that polls replication servers to know if a RS with the right group id * becomes available. * * Full topo is: * - RS1 with GID=1 * - RS2 with GID=2 * - RS3 with GID=3 * - DS1 with GID=2 * - DS2 with GID=3 * Scenario is: * - Start RS1 with GID=1 and RS2 with GID=2 * - Start DS1 with GID=2, should connect to RS2 with GID=2 * - Start DS2 with GID=3, should connect to either RS1 or RS2 (no GID=3 * available) * - Start RS3 with GID=3, DS2 with GID=3 should detect server with his GID * and connect to RS3 * - Stop RS2 and RS3, both DS1 and DS2 should failover to RS1 with GID=1 * (not their group id) * - Restart RS2 and RS3, DS1 should reconnect to RS2 (with GID=2, his GID) * and DS2 should connect to RS3 (with GID=3, his GID) * - Change group id of DS1 and DS2 to 1 : they should reconnect to RS1 * - Change group id of RS3 to 1 * - Change group id of RS1 to 3: DS1 and DS2 should reconnect to RS3 * - Change group id of DS1 and DS2 to 3 : they should reconnect to RS1 * @throws Exception If a problem occurred */ @Test (groups = "slow") public void testRSWithManyGroupIds() throws Exception { String testCase = "testRSWithManyGroupIds"; debugInfo("Starting " + testCase); initTest(); try { /** * Start RS1 with GID=1 and RS2 with GID=2 */ // Create and start RS1 rs1 = createReplicationServer(RS1_ID, 1, testCase); // Create and start RS2 rs2 = createReplicationServer(RS2_ID, 2, testCase); /** * Start DS1 with GID=2, should connect to RS2 with GID=2 */ // Start DS1 rd1 = createReplicationDomain(DS1_ID, 2, testCase); checkConnection(30, DS1_ID, RS2_ID, "Start DS1 with GID=2, should connect to RS2 with GID=2"); /** * Start DS2 with GID=3, should connect to either RS1 or RS2 (no GID=3 * available) */ // Start DS2 rd2 = createReplicationDomain(DS2_ID, 3, testCase); assertTrue(rd2.isConnected()); /** * Start RS3 with GID=3, DS2 with GID=3 should detect server with his GID * and connect to RS3 */ // Create and start RS3 rs3 = createReplicationServer(RS3_ID, 3, testCase); // Sleep to insure start is done and DS2 has time to detect to server // arrival and reconnect checkConnection(30, DS2_ID, RS3_ID, "Start RS3 with GID=3, DS2 with GID=3 should detect server with his GID and connect to RS3"); /** * Stop RS2 and RS3, both DS1 and DS2 should failover to RS1 with GID=1 * (not their group id) */ // Simulate RS2 failure rs2.remove(); // Simulate RS3 failure rs3.remove(); // Sleep to insure shutdowns are ok and DS1 and DS2 reconnect to RS1 checkConnection(30, DS1_ID, RS1_ID, "Stop RS2 and RS3, DS1 should failover to RS1 with GID=1"); checkConnection(30, DS2_ID, RS1_ID, "Stop RS2 and RS3, DS2 should failover to RS1 with GID=1"); /** * Restart RS2 and RS3, DS1 should reconnect to RS2 (with GID=2, his GID) * and DS2 should reconnect to RS3 (with GID=3, his GID) */ // RS2 restart rs2 = createReplicationServer(RS2_ID, 2, testCase); // RS3 restart rs3 = createReplicationServer(RS3_ID, 3, testCase); // Sleep to insure restarts are ok and DS1 and DS2 reconnect to the RS with // their group id checkConnection(30, DS1_ID, RS2_ID, "Restart RS2 and RS3, DS1 should reconnect to RS2 (with GID=2, his GID)"); checkConnection(30, DS2_ID, RS3_ID, "Restart RS2 and RS3, DS2 should reconnect to RS3 (with GID=3, his GID)"); // // ENTERING CHANGE CONFIG TEST PART // /** * Change group id of DS1 and DS2 to 1 and see them reconnect to RS1 */ SortedSet<String> replServers = createRSListForTestCase(testCase); DN baseDn = DN.decode(TEST_ROOT_DN_STRING); DomainFakeCfg domainConfWithNewGid = new DomainFakeCfg(baseDn, DS1_ID, replServers, 1); rd1.applyConfigurationChange(domainConfWithNewGid); domainConfWithNewGid = new DomainFakeCfg(baseDn, DS2_ID, replServers, 1); rd2.applyConfigurationChange(domainConfWithNewGid); checkConnection(30, DS1_ID, RS1_ID, "Change GID of DS1 to 1, it should reconnect to RS1 with GID=1"); checkConnection(30, DS2_ID, RS1_ID, "Change GID of DS2 to 1, it should reconnect to RS1 with GID=1"); /** * Change group id of RS3 to 1 */ SortedSet<String> otherReplServers = new TreeSet<String>(); otherReplServers.add("localhost:" + rs1Port); otherReplServers.add("localhost:" + rs2Port); String dir = "groupIdHandshakeTest" + RS3_ID + testCase + "Db"; ReplServerFakeConfiguration rsConfWithNewGid = new ReplServerFakeConfiguration(rs3Port, dir, 0, RS3_ID, 0, 100, otherReplServers, 1, 1000, 5000); rs3.applyConfigurationChange(rsConfWithNewGid); /** * Change group id of RS1 to 3: DS1 and DS2 should reconnect to RS3 */ otherReplServers = new TreeSet<String>(); otherReplServers.add("localhost:" + rs2Port); otherReplServers.add("localhost:" + rs3Port); dir = "groupIdHandshakeTest" + RS1_ID + testCase + "Db"; rsConfWithNewGid = new ReplServerFakeConfiguration(rs1Port, dir, 0, RS1_ID, 0, 100, otherReplServers, 3, 1000, 5000); rs1.applyConfigurationChange(rsConfWithNewGid); checkConnection(30, DS1_ID, RS3_ID, "Change GID of RS3 to 1 and RS1 to 3, DS1 should reconnect to RS3 with GID=1"); checkConnection(30, DS2_ID, RS3_ID, "Change GID of RS3 to 1 and RS1 to 3, DS2 should reconnect to RS3 with GID=1"); /** * Change group id of DS1 and DS2 to 3 : they should reconnect to RS1 */ domainConfWithNewGid = new DomainFakeCfg(baseDn, DS1_ID, replServers, 3); rd1.applyConfigurationChange(domainConfWithNewGid); domainConfWithNewGid = new DomainFakeCfg(baseDn, DS2_ID, replServers, 3); rd2.applyConfigurationChange(domainConfWithNewGid); checkConnection(30, DS1_ID, RS1_ID, "Change GID of DS1 to 3, it should reconnect to RS1 with GID=3"); checkConnection(30, DS2_ID, RS1_ID, "Change GID of DS2 to 3, it should reconnect to RS1 with GID=3"); } finally { endTest(); } } }