/*
* 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 2008-2009 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.replication.plugin;
import static org.opends.server.TestCaseUtils.*;
import static org.testng.Assert.*;
import java.io.IOException;
import java.util.SortedSet;
import java.util.TreeSet;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.TestCaseUtils;
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.opends.server.types.HostPort;
import org.testng.annotations.Test;
/**
* 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;
private LDAPReplicationDomain rd2;
private ReplicationServer rs1;
private ReplicationServer rs2;
private ReplicationServer rs3;
// The tracer object for the debug logger
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private void debugInfo(String s)
{
logger.error(LocalizableMessage.raw(s));
if (logger.isTraceEnabled())
{
logger.trace("** TEST **" + s);
}
}
private void initTest() throws Exception
{
rs1Port = -1;
rs2Port = -1;
rs3Port = -1;
rd1 = null;
rd2 = null;
rs1 = null;
rs2 = null;
rs3 = null;
findFreePorts();
}
private void endTest() throws Exception
{
if (rd1 != null)
{
rd1.shutdown();
rd1 = null;
}
if (rd2 != null)
{
rd2.shutdown();
rd2 = null;
}
// Clear any reference to a domain in synchro plugin
MultimasterReplication.deleteDomain(DN.valueOf(TEST_ROOT_DN_STRING));
remove(rs1, rs2, rs3);
rs1 = rs2 = rs3 = null;
rs1Port = rs2Port = 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)
throws Exception
{
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();
rdPort = HostPort.valueOf(serverStr).getPort();
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
Thread.sleep(1000);
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() throws IOException
{
int[] ports = TestCaseUtils.findFreePorts(3);
int i = 0;
rs1Port = ports[i++];
rs2Port = ports[i++];
rs3Port = ports[i++];
}
/**
* 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<>();
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) throws Exception
{
SortedSet<String> replServers = new TreeSet<>();
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);
return new ReplicationServer(conf);
}
/** Creates a new ReplicationDomain. */
private LDAPReplicationDomain createReplicationDomain(int serverId,
int groupId, String testCase) throws Exception
{
SortedSet<String> replServers = createRSListForTestCase(testCase);
DN baseDn = DN.valueOf(TEST_ROOT_DN_STRING);
DomainFakeCfg domainConf =
new DomainFakeCfg(baseDn, serverId, replServers, groupId);
LDAPReplicationDomain replicationDomain =
MultimasterReplication.createNewDomain(domainConf);
replicationDomain.start();
return replicationDomain;
}
/**
* 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.valueOf(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<>();
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<>();
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();
}
}
}