/*
* 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 org.opends.messages.Category;
import org.opends.messages.Message;
import org.opends.messages.Severity;
import org.opends.server.TestCaseUtils;
import org.opends.server.backends.MemoryBackend;
import org.opends.server.backends.task.TaskState;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.common.ServerStatus;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.protocol.*;
import org.opends.server.replication.server.ReplServerFakeConfiguration;
import org.opends.server.replication.server.ReplicationBackend;
import org.opends.server.replication.server.ReplicationServer;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.tasks.LdifFileWriter;
import org.opends.server.types.*;
import org.opends.server.util.StaticUtils;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.io.File;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.*;
import static org.opends.server.TestCaseUtils.TEST_BACKEND_ID;
import static org.opends.server.TestCaseUtils.TEST_ROOT_DN_STRING;
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.server.util.StaticUtils.stackTraceToSingleLineString;
import static org.testng.Assert.*;
/**
* Tests contained here:
*
* - testSingleRS : test generation ID setting with different servers and one
* Replication server.
*
* - testMultiRS : tests generation ID propagation with more than one
* Replication server.
*
*/
public class GenerationIdTest extends ReplicationTestCase
{
// The tracer object for the debug logger
private static final DebugTracer TRACER = getTracer();
private static final String baseDnStr = TEST_ROOT_DN_STRING;
private static final String testName = "generationIdTest";
private static final int WINDOW_SIZE = 10;
private static final int server1ID = 1;
private static final int server2ID = 2;
private static final int server3ID = 3;
private static final int changelog1ID = 11;
private static final int changelog2ID = 12;
private static final int changelog3ID = 13;
private DN baseDn;
private ReplicationBroker broker2 = null;
private ReplicationBroker broker3 = null;
private ReplicationServer replServer1 = null;
private ReplicationServer replServer2 = null;
private ReplicationServer replServer3 = null;
private boolean emptyOldChanges = true;
private Entry taskInitRemoteS2;
private String[] updatedEntries;
private static int[] replServerPort = new int[20];
/**
* A makeldif template used to create some test entries.
*/
private static String diff = "";
private static String[] template = new String[] {
"define suffix=" + baseDnStr,
"define maildomain=example.com",
"define numusers=11",
"",
"branch: [suffix]",
"",
"branch: ou=People,[suffix]",
"subordinateTemplate: person:[numusers]",
"",
"template: person",
"rdnAttr: uid",
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"givenName: <first>",
"sn: <last>",
"cn: {givenName} {sn}",
"initials: {givenName:1}<random:chars:" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ:1>{sn:1}",
"employeeNumber: <sequential:0>",
"uid: user.{employeeNumber}",
"mail: {uid}@[maildomain]",
"userPassword: password",
"telephoneNumber: <random:telephone>",
"homePhone: <random:telephone>",
"pager: <random:telephone>",
"mobile: <random:telephone>",
"street: <random:numeric:5> <file:streets> Street",
"l: <file:cities>",
"st: <file:states>",
"postalCode: <random:numeric:5>",
"postalAddress: {cn}${street}${l}, {st} {postalCode}",
"description: This is the description for {cn} " + diff,
""};
private void debugInfo(String s)
{
logError(Message.raw(Category.SYNC, Severity.NOTICE, "** TEST **" + s));
if (debugEnabled())
{
TRACER.debugInfo("** TEST **" + s);
}
}
protected void debugInfo(String message, Exception e)
{
debugInfo(message + stackTraceToSingleLineString(e));
}
/**
* Set up the environment for performing the tests in this Class.
*
* @throws Exception
* If the environment could not be set up.
*/
@Override
@BeforeClass
public void setUp() throws Exception
{
super.setUp();
baseDn = DN.decode(baseDnStr);
updatedEntries = newLDIFEntries();
// Synchro suffix
synchroServerEntry = null;
taskInitRemoteS2 = 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: " + server2ID);
}
// Tests that entries have been written in the db
private int testEntriesInDb()
{
debugInfo("TestEntriesInDb");
short found = 0;
for (String entry : updatedEntries)
{
int dns = entry.indexOf("dn: ");
int dne = entry.indexOf(TestCaseUtils.TEST_ROOT_DN_STRING);
String dn = entry.substring(dns + 4,
dne + TestCaseUtils.TEST_ROOT_DN_STRING.length());
debugInfo("Search Entry: " + dn);
DN entryDN = null;
try
{
entryDN = DN.decode(dn);
}
catch(Exception e)
{
debugInfo("TestEntriesInDb/" + e);
}
try
{
Entry resultEntry = getEntry(entryDN, 1000, true);
if (resultEntry==null)
{
debugInfo("Entry not found <" + dn + ">");
}
else
{
debugInfo("Entry found <" + dn + ">");
found++;
}
}
catch(Exception e)
{
debugInfo("TestEntriesInDb/", e);
}
}
return found;
}
/*
* Creates entries necessary to the test.
*/
private String[] newLDIFEntries()
{
return new String[]{
"dn: " + baseDn + "\n"
+ "objectClass: top\n"
+ "objectClass: organization\n"
+ "entryUUID: 21111111-1111-1111-1111-111111111111\n"
+ "\n",
"dn: ou=People," + baseDn + "\n"
+ "objectClass: top\n"
+ "objectClass: organizationalUnit\n"
+ "entryUUID: 21111111-1111-1111-1111-111111111112\n"
+ "\n",
"dn: cn=Fiona Jensen,ou=people," + baseDn + "\n"
+ "objectclass: top\n"
+ "objectclass: person\n"
+ "objectclass: organizationalPerson\n"
+ "objectclass: inetOrgPerson\n"
+ "cn: Fiona Jensen\n"
+ "sn: Jensen\n"
+ "uid: fiona\n"
+ "telephonenumber: +1 408 555 1212\n"
+ "entryUUID: 21111111-1111-1111-1111-111111111113\n"
+ "\n",
"dn: cn=Robert Langman,ou=people," + baseDn + "\n"
+ "objectclass: top\n"
+ "objectclass: person\n"
+ "objectclass: organizationalPerson\n"
+ "objectclass: inetOrgPerson\n"
+ "cn: Robert Langman\n"
+ "sn: Langman\n"
+ "uid: robert\n"
+ "telephonenumber: +1 408 555 1213\n"
+ "entryUUID: 21111111-1111-1111-1111-111111111114\n"
+ "\n"
};
}
private int receiveImport(ReplicationBroker broker, int serverID,
String[] updatedEntries)
{
// Expect the broker to receive the entries
ReplicationMsg msg;
short entriesReceived = -100;
while (true)
{
try
{
debugInfo("Broker " + serverID + " Wait for entry or done msg");
msg = broker.receive();
if (msg == null)
break;
if (msg instanceof InitializeTargetMsg)
{
debugInfo("Broker " + serverID + " receives InitializeTargetMessage ");
entriesReceived = 0;
}
else if (msg instanceof EntryMsg)
{
EntryMsg em = (EntryMsg)msg;
debugInfo("Broker " + serverID + " receives entry " + new String(em.getEntryBytes()));
entriesReceived++;
}
else if (msg instanceof DoneMsg)
{
debugInfo("Broker " + serverID + " receives done ");
break;
}
else if (msg instanceof ErrorMsg)
{
ErrorMsg em = (ErrorMsg)msg;
debugInfo("Broker " + serverID + " receives ERROR "
+ em.toString());
break;
}
else
{
debugInfo("Broker " + serverID + " receives and trashes " + msg);
}
}
catch(Exception e)
{
debugInfo("receiveUpdatedEntries" + stackTraceToSingleLineString(e));
}
}
if (updatedEntries != null)
{
assertTrue(entriesReceived == updatedEntries.length,
" Received entries("+entriesReceived +
") == Expected entries("+updatedEntries.length+")");
}
return entriesReceived;
}
/**
* Creates a new replicationServer.
* @param changelogId The serverID of the replicationServer to create.
* @param all Specifies whether to connect the created replication
* server to the other replication servers in the test.
* @return The new created replication server.
*/
private ReplicationServer createReplicationServer(int changelogId,
boolean all, String testCase)
{
SortedSet<String> servers = new TreeSet<String>();
try
{
if (all)
{
if (changelogId != changelog1ID)
servers.add("localhost:" + getChangelogPort(changelog1ID));
if (changelogId != changelog2ID)
servers.add("localhost:" + getChangelogPort(changelog2ID));
if (changelogId != changelog3ID)
servers.add("localhost:" + getChangelogPort(changelog3ID));
}
int chPort = getChangelogPort(changelogId);
String chDir = "generationIdTest"+changelogId+testCase+"Db";
ReplServerFakeConfiguration conf =
new ReplServerFakeConfiguration(chPort, chDir, 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 replication Server ID
*/
private void connectServer1ToChangelog(int changeLogID)
{
// Connect DS to the replicationServer
try
{
// suffix synchronized
String synchroServerLdif =
"dn: cn=" + testName + ", cn=domains," + SYNCHRO_PLUGIN_DN + "\n"
+ "objectClass: top\n"
+ "objectClass: ds-cfg-replication-domain\n"
+ "cn: " + testName + "\n"
+ "ds-cfg-base-dn: " + baseDnStr + "\n"
+ "ds-cfg-replication-server: localhost:"
+ getChangelogPort(changeLogID)+"\n"
+ "ds-cfg-server-id: " + server1ID + "\n"
+ "ds-cfg-receive-status: true\n"
+ "ds-cfg-window-size: " + WINDOW_SIZE;
// Must be no connection already done or disconnectFromReplServer should
// have been called
assertTrue(synchroServerEntry == null);
synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif);
DirectoryServer.getConfigHandler().addEntry(synchroServerEntry, null);
assertNotNull(DirectoryServer.getConfigEntry(synchroServerEntry.getDN()),
"Unable to add the synchronized server");
configEntryList.add(synchroServerEntry.getDN());
int waitCo=0;
LDAPReplicationDomain doToco=null;
while(waitCo<50)
{
doToco =
LDAPReplicationDomain.retrievesReplicationDomain(baseDn);
if ((doToco!=null) && (doToco.isConnected()))
break;
Thread.sleep(waitCo * 200);
waitCo++;
}
assertNotNull(doToco);
assertTrue(doToco.isConnected(), "not connected after #attempt="+waitCo);
debugInfo("ReplicationDomain: Import/Export is running ? " + doToco.ieRunning());
}
catch(Exception e)
{
debugInfo("connectToReplServer", e);
fail("connectToReplServer", e);
}
}
/*
* Disconnect DS from the replicationServer
*/
private void disconnectFromReplServer(int changelogID)
{
try
{
// suffix synchronized
String synchroServerStringDN = "cn=" + testName + ", cn=domains," +
SYNCHRO_PLUGIN_DN;
// Must have called connectServer1ToChangelog previously
assertTrue(synchroServerEntry != null);
DN synchroServerDN = DN.decode(synchroServerStringDN);
Entry ecle;
ecle = DirectoryServer.getConfigHandler().getEntry(
DN.decode("cn=external changelog," + synchroServerStringDN));
if (ecle!=null)
{
DirectoryServer.getConfigHandler().deleteEntry(ecle.getDN(), null);
}
DirectoryServer.getConfigHandler().deleteEntry(synchroServerDN, null);
assertTrue(DirectoryServer.getConfigEntry(synchroServerEntry.getDN()) ==
null,
"Unable to delete the synchronized domain");
synchroServerEntry = null;
configEntryList.remove(configEntryList.indexOf(synchroServerDN));
LDAPReplicationDomain replDomainToDis = null;
try
{
int waitCo=0;
while(waitCo<30)
{
replDomainToDis =
LDAPReplicationDomain.retrievesReplicationDomain(baseDn);
Thread.sleep(200);
waitCo++;
}
assert(replDomainToDis==null);
}
catch (DirectoryException e)
{
// success
debugInfo("disconnectFromReplServer:" + changelogID, e);
}
}
catch(Exception e)
{
fail("disconnectFromReplServer", 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];
}
protected static final String REPLICATION_GENERATION_ID =
"ds-sync-generation-id";
private long readGenIdFromSuffixRootEntry()
{
long genId=-1;
try
{
Entry resultEntry = getEntry(baseDn, 1000, true);
if (resultEntry==null)
{
debugInfo("Entry not found <" + baseDn + ">");
}
else
{
debugInfo("Entry found <" + baseDn + ">");
AttributeType synchronizationGenIDType =
DirectoryServer.getAttributeType(REPLICATION_GENERATION_ID);
List<Attribute> attrs =
resultEntry.getAttribute(synchronizationGenIDType);
if (attrs != null)
{
Attribute attr = attrs.get(0);
if (attr.size() == 1)
{
genId =
Long.decode(attr.iterator().next().getValue().toString());
}
}
}
}
catch(Exception e)
{
fail("Exception raised in readGenId", e);
}
return genId;
}
private void performLdifImport()
{
try
{
// Create a temporary test LDIF file.
/*
A temporary LDIF file containing some test entries.
*/
File ldifFile = File.createTempFile("import-test", ".ldif");
String resourcePath = DirectoryServer.getInstanceRoot() + File.separator +
"config" + File.separator + "MakeLDIF";
LdifFileWriter.makeLdif(ldifFile.getPath(), resourcePath, template);
// Launch import of the Ldif file on the memory test backend
// Note: we do not use a task here as import task does not work on memory
// backend: it disables then re-enables backend which leads to backend
// object instance lost and this is not accepttable for a backend with
// non persistent data
LDIFImportConfig importConfig =
new LDIFImportConfig(ldifFile.getAbsolutePath());
MemoryBackend memoryBackend =
(MemoryBackend) DirectoryServer.getBackend(TEST_BACKEND_ID);
memoryBackend.importLDIF(importConfig);
}
catch(Exception e)
{
fail("Could not perform ldif import on memory test backend: "
+ e.getMessage());
}
}
private String createEntry(UUID uid)
{
String user2dn = "uid=user"+uid+",ou=People," + baseDnStr;
return "dn: " + user2dn + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "homePhone: 951-245-7634\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "mobile: 027-085-0537\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar2\n" + "l: Rockford\n" + "pager: 508-763-4246\n"
+ "street: 17984 Thirteenth Street\n"
+ "telephoneNumber: 216-564-6748\n" + "employeeNumber: 2\n"
+ "sn: Amar2\n" + "givenName: Aaccf2\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n";
}
static protected ReplicationMsg createAddMsg()
{
Entry personWithUUIDEntry = null;
String user1entryUUID;
String baseUUID = null;
String user1dn;
/*
* Create a Change number generator to generate new changenumbers
* when we need to send operation messages to the replicationServer.
*/
ChangeNumberGenerator gen = new ChangeNumberGenerator(2, 0);
user1entryUUID = "33333333-3333-3333-3333-333333333333";
user1dn = "uid=user1,ou=People," + baseDnStr;
String entryWithUUIDldif = "dn: "+ user1dn + "\n"
+ "objectClass: top\n" + "objectClass: person\n"
+ "objectClass: organizationalPerson\n"
+ "objectClass: inetOrgPerson\n" + "uid: user.1\n"
+ "homePhone: 951-245-7634\n"
+ "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
+ "mobile: 027-085-0537\n"
+ "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ "$Rockford, NC 85762\n" + "mail: user.1@example.com\n"
+ "cn: Aaccf Amar\n" + "l: Rockford\n" + "pager: 508-763-4246\n"
+ "street: 17984 Thirteenth Street\n"
+ "telephoneNumber: 216-564-6748\n" + "employeeNumber: 1\n"
+ "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
+ "userPassword: password\n" + "initials: AA\n"
+ "entryUUID: " + user1entryUUID + "\n";
try
{
personWithUUIDEntry = TestCaseUtils.entryFromLdifString(entryWithUUIDldif);
}
catch(Exception e)
{
fail(e.getMessage());
}
// Create and publish an update message to add an entry.
return new AddMsg(gen.newChangeNumber(),
personWithUUIDEntry.getDN().toString(),
user1entryUUID,
baseUUID,
personWithUUIDEntry.getObjectClassAttribute(),
personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
}
/*
* Check that the expected number of changes are in the replication
* server database.
*/
private void checkChangelogSize(int expectedCount)
{
try
{
SearchFilter filter =
SearchFilter.createFilterFromString("(objectclass=*)");
InternalSearchOperation searchOperation =
connection.processSearch(DN.decode("dc=replicationchanges"),
SearchScope.SUBORDINATE_SUBTREE,
filter);
if (debugEnabled())
{
if (searchOperation.getSearchEntries().size() != expectedCount)
{
for (SearchResultEntry sre : searchOperation.getSearchEntries())
{
debugInfo("Entry found: " + sre.toLDIFString());
}
}
}
assertEquals(searchOperation.getSearchEntries().size(), expectedCount);
}
catch(Exception e)
{
}
}
/**
* SingleRS tests basic features of generationID
* with one single Replication Server.
*
* @throws Exception
*/
@Test(enabled=false)
public void testSingleRS() throws Exception
{
String testCase = "testSingleRS";
debugInfo("Starting "+ testCase + " debugEnabled:" + debugEnabled());
debugInfo(testCase + " Clearing DS1 backend");
// Special test were we want to test with an empty backend. So free it
// for this special test.
TestCaseUtils.initializeTestBackend(false);
try
{
long rgenId;
long genId;
replServer1 = createReplicationServer(changelog1ID, false, testCase);
// To search the replication server db later in these tests, we need
// to attach the search backend to the replication server just created.
ReplicationBackend b =
(ReplicationBackend)DirectoryServer.getBackend("replicationChanges");
b.setServer(replServer1);
//===========================================================
debugInfo(testCase + " ** TEST ** Empty backend");
debugInfo(testCase + " Configuring DS1 to replicate to RS1(" + changelog1ID + ") on an empty backend");
connectServer1ToChangelog(changelog1ID);
debugInfo(testCase + " Expect genId to be not retrievable from suffix root entry");
genId = readGenIdFromSuffixRootEntry();
assertEquals(genId,-1);
debugInfo(testCase + " Expect genId to be set in memory on the replication " +
" server side (not wrote on disk/db since no change occurred).");
rgenId = replServer1.getGenerationId(baseDn.toNormalizedString());
assertEquals(rgenId, EMPTY_DN_GENID);
// Clean for next test
debugInfo(testCase + " Unconfiguring DS1 to replicate to RS1(" + changelog1ID + ")");
disconnectFromReplServer(changelog1ID);
//===========================================================
debugInfo(testCase + " ** TEST ** Non empty backend");
debugInfo(testCase + " Adding test entries to DS");
this.addTestEntriesToDB(updatedEntries);
debugInfo(testCase + " Configuring DS1 to replicate to RS1(" + changelog1ID + ") on a non empty backend");
connectServer1ToChangelog(changelog1ID);
debugInfo(testCase + " Test that the generationId is written in the DB in the root entry on DS1");
genId = readGenIdFromSuffixRootEntry();
assertTrue(genId != -1);
assertTrue(genId != EMPTY_DN_GENID);
debugInfo(testCase + " Test that the generationId is set on RS1");
rgenId = replServer1.getGenerationId(baseDn.toNormalizedString());
assertEquals(genId, rgenId);
//===========================================================
debugInfo(testCase + " ** TEST ** DS2 connection to RS1 with bad genID");
try
{
broker2 = openReplicationSession(baseDn,
server2ID, 100, getChangelogPort(changelog1ID),
1000, !emptyOldChanges, genId+1);
}
catch(SocketException se)
{
fail("DS2 with bad genID failed to connect to RS1.");
}
//===========================================================
debugInfo(testCase + " ** TEST ** DS3 connection to RS1 with good genID");
try
{
broker3 = openReplicationSession(baseDn,
server3ID, 100, getChangelogPort(changelog1ID), 1000, !emptyOldChanges, genId);
}
catch(SocketException se)
{
fail("Broker connection is expected to be accepted.");
}
//===========================================================
debugInfo(testCase + " ** TEST ** DS2 (bad genID) changes must be ignored.");
broker2.publish(createAddMsg());
try
{
broker3.receive();
fail("No update message is supposed to be received here.");
}
catch(SocketTimeoutException e)
{
// This is the expected result
// Note that timeout should be lower than RS montoring publisher period
// so that timeout occurs
}
//===========================================================
debugInfo(testCase + " ** TEST ** The part of the topology with the right gen ID should work well");
// Now create a change that must be replicated
String ent1[] = { createEntry(UUID.randomUUID()) };
this.addTestEntriesToDB(ent1);
// Verify that RS1 does contain the change related to this ADD.
Thread.sleep(500);
checkChangelogSize(1);
// Verify that DS3 receives this change
try
{
ReplicationMsg msg = broker3.receive();
debugInfo("Broker 3 received expected update msg" + msg);
}
catch(SocketTimeoutException e)
{
fail("Update message is supposed to be received.");
}
//===========================================================
debugInfo(testCase + " ** TEST ** Persistence of the generation ID in RS1");
long genIdBeforeShut =
replServer1.getGenerationId(baseDn.toNormalizedString());
debugInfo("Shutdown replServer1");
broker2.stop();
broker2 = null;
broker3.stop();
broker3 = null;
replServer1.remove();
replServer1 = null;
debugInfo("Create again replServer1");
replServer1 = createReplicationServer(changelog1ID, false, testCase);
// To search the replication server db later in these tests, we need
// to attach the search backend to the replication server just created.
b = (ReplicationBackend)DirectoryServer.getBackend("replicationChanges");
b.setServer(replServer1);
debugInfo("Delay to allow DS to reconnect to replServer1");
long genIdAfterRestart =
replServer1.getGenerationId(baseDn.toNormalizedString());
debugInfo("Aft restart / replServer.genId=" + genIdAfterRestart);
assertTrue(replServer1!=null, "Replication server creation failed.");
assertTrue(genIdBeforeShut == genIdAfterRestart,
"generationId is expected to have the same value" +
" after replServer1 restart. Before : " + genIdBeforeShut +
" after : " + genIdAfterRestart);
// By the way also verify that no change occurred on the replication server db
// and still contain the ADD submitted initially.
Thread.sleep(500);
checkChangelogSize(1);
//===============================================================
debugInfo(testCase + " ** TEST ** Import with new data set + reset will"+
" spread a new gen ID on the topology, verify DS1 and RS1");
try
{
debugInfo("Create again broker2");
broker2 = openReplicationSession(baseDn,
server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges, genId);
assertTrue(broker2.isConnected(), "Broker2 failed to connect to replication server");
debugInfo("Create again broker3");
broker3 = openReplicationSession(baseDn,
server3ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges, genId);
assertTrue(broker3.isConnected(), "Broker3 failed to connect to replication server");
}
catch(SocketException se)
{
fail("Broker connection is expected to be accepted.");
}
debugInfo("Launch on-line import on DS1");
long oldGenId = genId;
genId=-1;
disconnectFromReplServer(changelog1ID);
performLdifImport();
connectServer1ToChangelog(changelog1ID);
debugInfo("Create Reset task on DS1 to propagate the new gen ID as the reference");
Entry taskReset = TestCaseUtils.makeEntry(
"dn: ds-task-id=resetgenid"+genId+ UUID.randomUUID() +
",cn=Scheduled Tasks,cn=Tasks",
"objectclass: top",
"objectclass: ds-task",
"objectclass: ds-task-reset-generation-id",
"ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask",
"ds-task-reset-generation-id-domain-base-dn: " + baseDnStr);
addTask(taskReset, ResultCode.SUCCESS, null);
waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null);
// Broker 2 and 3 should receive 1 change status message to order them
// to enter the bad gen id status
ChangeStatusMsg csMsg = (ChangeStatusMsg)waitForSpecificMsg(broker2,
ChangeStatusMsg.class.getName());
if (csMsg.getRequestedStatus() != ServerStatus.BAD_GEN_ID_STATUS)
{
fail("Broker 2 connection is expected to receive 1 ChangeStatusMsg" +
" to enter the bad gen id status"
+ csMsg);
}
csMsg = (ChangeStatusMsg)waitForSpecificMsg(broker3,
ChangeStatusMsg.class.getName());
if (csMsg.getRequestedStatus() != ServerStatus.BAD_GEN_ID_STATUS)
{
fail("Broker 2 connection is expected to receive 1 ChangeStatusMsg" +
" to enter the bad gen id status"
+ csMsg);
}
debugInfo("DS1 root entry must contain the new gen ID");
genId = readGenIdFromSuffixRootEntry();
assertTrue(genId != -1, "DS is expected to have a new genID computed " +
" after on-line import but genId=" + genId);
assertTrue(genId != oldGenId, "The new genID after import and reset of genID "
+ "is expected to be diffrent from previous one");
debugInfo("RS1 must have the new gen ID");
rgenId = replServer1.getGenerationId(baseDn.toNormalizedString());
assertEquals(genId, rgenId, "DS and replServer are expected to have same genId.");
debugInfo("RS1 must have been cleared since it has not the proper generation ID");
checkChangelogSize(0);
assertTrue(!replServer1.getReplicationServerDomain(
baseDn.toNormalizedString(), false).
isDegradedDueToGenerationId(server1ID),
"Expecting that DS1 status in RS1 is : not in bad gen id.");
//===============================================================
debugInfo(testCase + " ** TEST ** Previous test set a new gen ID on the "+
"topology, verify degradation of DS2 and DS3");
assertTrue(replServer1.getReplicationServerDomain(
baseDn.toNormalizedString(), false).
isDegradedDueToGenerationId(server2ID),
"Expecting that DS2 with old gen ID is in bad gen id from RS1");
assertTrue(replServer1.getReplicationServerDomain(
baseDn.toNormalizedString(), false).
isDegradedDueToGenerationId(server3ID),
"Expecting that DS3 with old gen ID is in bad gen id from RS1");
debugInfo("Add entries to DS1, update should not be sent to DS2 and DS3 that are in bad gen id");
String[] ent3 = { createEntry(UUID.randomUUID()) };
this.addTestEntriesToDB(ent3);
debugInfo("RS1 must have stored that update.");
Thread.sleep(500);
checkChangelogSize(1);
try
{
ReplicationMsg msg = broker2.receive();
fail("No update message is supposed to be received by broker2 in bad gen id. " + msg);
} catch(SocketTimeoutException e) { /* expected */ }
try
{
ReplicationMsg msg = broker3.receive();
fail("No update message is supposed to be received by broker3 in bad gen id. " + msg);
} catch(SocketTimeoutException e) { /* expected */ }
debugInfo("DS2 is publishing a change and RS1 must ignore this change, DS3 must not receive it.");
AddMsg emsg = (AddMsg)createAddMsg();
broker2.publish(emsg);
// Updates count in RS1 must stay unchanged = to 1
Thread.sleep(500);
checkChangelogSize(1);
try
{
ReplicationMsg msg = broker3.receive();
fail("No update message is supposed to be received by broker3 in bad gen id. "+ msg);
} catch(SocketTimeoutException e) { /* expected */ }
//===============================================================
debugInfo(testCase + " ** TEST ** Previous test put DS2 and DS3 in bad gen id, "+
" now simulates \"dsreplication initialize \"by doing a TU+reset " +
" from DS1 to DS2 and DS3, verify NON degradation of DS2 and DS3");
// In S1 launch the total update to initialize S2
addTask(taskInitRemoteS2, ResultCode.SUCCESS, null);
// S2 should be re-initialized and have a new valid genId
// Signal that we just entered the full update status
broker2.signalStatusChange(ServerStatus.FULL_UPDATE_STATUS);
int receivedEntriesNb = this.receiveImport(broker2, server2ID, null);
debugInfo("broker2 has been initialized from DS with #entries=" + receivedEntriesNb);
broker2.stop();
// Simulates the broker restart at the end of the import
broker2 = openReplicationSession(baseDn,
server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges, genId);
broker3.stop();
// Simulates the broker restart at the end of the import
broker3 = openReplicationSession(baseDn,
server3ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges, genId);
debugInfo("Adding reset task to DS1");
taskReset = TestCaseUtils.makeEntry(
"dn: ds-task-id=resetgenid"+ UUID.randomUUID() +
",cn=Scheduled Tasks,cn=Tasks",
"objectclass: top",
"objectclass: ds-task",
"objectclass: ds-task-reset-generation-id",
"ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask",
"ds-task-reset-generation-id-domain-base-dn: " + baseDnStr);
addTask(taskReset, ResultCode.SUCCESS, null);
waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null);
debugInfo("Verify that RS1 has still the right genID");
assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), rgenId);
// Updates count in RS1 must stay unchanged = to 1
Thread.sleep(500);
checkChangelogSize(1);
debugInfo("Verifying that DS2 is not in bad gen id any more");
assertTrue(!replServer1.getReplicationServerDomain(
baseDn.toNormalizedString(), false).
isDegradedDueToGenerationId(server2ID),
"Expecting that DS2 is not in bad gen id from RS1");
debugInfo("Verifying that DS3 is not in bad gen id any more");
assertTrue(!replServer1.getReplicationServerDomain(
baseDn.toNormalizedString(), false).
isDegradedDueToGenerationId(server3ID),
"Expecting that DS3 is not in bad gen id from RS1");
debugInfo("Verify that DS2 receives the add message stored in RS1 DB");
try
{
ReplicationMsg msg = broker2.receive();
assertTrue(msg instanceof AddMsg, "Excpected to receive an AddMsg but received: " + msg);
}
catch(SocketTimeoutException e)
{
fail("The msg stored in RS1 DB is expected to be received by DS2)");
}
debugInfo("Verify that DS3 receives the add message stored in RS1 DB");
try
{
ReplicationMsg msg = broker3.receive();
assertTrue(msg instanceof AddMsg, "Excpected to receive an AddMsg but received: " + msg);
}
catch(SocketTimeoutException e)
{
fail("The msg stored in RS1 DB is expected to be received by DS3)");
}
debugInfo("DS2 is publishing a change and RS1 must store this change, DS3 must receive it.");
emsg = (AddMsg)createAddMsg();
broker2.publish(emsg);
Thread.sleep(500);
checkChangelogSize(2);
try
{
ReplicationMsg msg = broker3.receive();
/* expected */
AddMsg rcvmsg = (AddMsg)msg;
assertEquals(rcvmsg.getChangeNumber(), emsg.getChangeNumber());
}
catch(SocketTimeoutException e)
{
fail("The msg send by DS2 is expected to be received by DS3)");
}
//===============================================================
debugInfo(testCase + " ** TEST ** General cleaning");
debugInfo("Disconnect DS from replServer1 (required in order to DEL entries).");
disconnectFromReplServer(changelog1ID);
debugInfo("Successfully ending " + testCase);
}
catch(Exception e)
{
fail(testCase + " Exception:"+ e.getMessage() + " " +
stackTraceToSingleLineString(e));
} finally
{
postTest();
}
}
/**
/**
* testMultiRS tests basic features of generationID
* with more than one Replication Server.
* The following test focus on:
* - genId checking across multiple starting RS (replication servers)
* - genId setting propagation from one RS to the others
* - genId reset propagation from one RS to the others
*/
@Test(enabled=false)
public void testMultiRS(int i) throws Exception
{
String testCase = "testMultiRS";
long genId;
debugInfo("Starting " + testCase);
try
{
// Special test were we want to test with an empty backend. So free it
// for this special test.
TestCaseUtils.initializeTestBackend(false);
debugInfo("Creating 3 RS");
replServer1 = createReplicationServer(changelog1ID, true, testCase);
replServer2 = createReplicationServer(changelog2ID, true, testCase);
replServer3 = createReplicationServer(changelog3ID, true, testCase);
debugInfo("Connecting DS to replServer1");
connectServer1ToChangelog(changelog1ID);
debugInfo("Expect genId are set in all replServers.");
int waitRes=0;
while(waitRes<100)
{
if ((replServer1.getGenerationId(baseDn.toNormalizedString())==EMPTY_DN_GENID)
&& (replServer2.getGenerationId(baseDn.toNormalizedString())==EMPTY_DN_GENID)
&& (replServer3.getGenerationId(baseDn.toNormalizedString())==EMPTY_DN_GENID))
break;
waitRes++;
Thread.sleep(100);
}
assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID,
" in replServer1");
assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID,
" in replServer2");
assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID,
" in replServer3");
debugInfo("Disconnect DS from replServer1.");
disconnectFromReplServer(changelog1ID);
waitRes=0;
while(waitRes<100)
{
if ((replServer1.getGenerationId(baseDn.toNormalizedString())==-1)
&& (replServer2.getGenerationId(baseDn.toNormalizedString())==-1)
&& (replServer3.getGenerationId(baseDn.toNormalizedString())==-1))
break;
waitRes++;
Thread.sleep(100);
}
debugInfo(
"Expect genIds to be resetted in all servers to -1 as no more DS in topo - after 10 sec");
assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), -1);
assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), -1);
assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), -1);
debugInfo("Add entries to DS");
this.addTestEntriesToDB(updatedEntries);
debugInfo("Connecting DS to replServer2");
connectServer1ToChangelog(changelog2ID);
debugInfo(
"Expect genIds to be set in all servers based on the added entries.");
genId = readGenIdFromSuffixRootEntry();
assertTrue(genId != -1);
waitRes=0;
while(waitRes<100)
{
if ((replServer1.getGenerationId(baseDn.toNormalizedString())==genId)
&& (replServer2.getGenerationId(baseDn.toNormalizedString())==genId)
&& (replServer3.getGenerationId(baseDn.toNormalizedString())==genId))
break;
waitRes++;
Thread.sleep(100);
}
assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId);
assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId);
assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId);
debugInfo("Connecting broker2 to replServer3 with a good genId");
try
{
broker2 = openReplicationSession(baseDn,
server2ID, 100, getChangelogPort(changelog3ID),
1000, !emptyOldChanges, genId);
Thread.sleep(1000);
} catch (SocketException se)
{
fail("Broker connection is expected to be accepted.");
}
debugInfo(
"Expecting that broker2 is not in bad gen id since it has a correct genId");
assertTrue(!replServer1.getReplicationServerDomain(baseDn.toNormalizedString(), false).
isDegradedDueToGenerationId(server2ID));
debugInfo("Disconnecting DS from replServer1");
disconnectFromReplServer(changelog1ID);
debugInfo("Verifying that all replservers genIds have been reset.");
debugInfo(
"Expect all genIds to keep their value since broker2 is still connected.");
waitRes=0;
while(waitRes<100)
{
if ((replServer1.getGenerationId(baseDn.toNormalizedString())==genId)
&& (replServer2.getGenerationId(baseDn.toNormalizedString())==genId)
&& (replServer3.getGenerationId(baseDn.toNormalizedString())==genId))
break;
waitRes++;
Thread.sleep(100);
}
assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId);
assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId);
assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId);
debugInfo("Connecting broker3 to replServer1 with a bad genId");
try
{
long badgenId = 1;
broker3 = openReplicationSession(baseDn,
server3ID, 100, getChangelogPort(changelog1ID),
1000, !emptyOldChanges, badgenId);
Thread.sleep(1000);
} catch (SocketException se)
{
fail("Broker connection is expected to be accepted.");
}
debugInfo(
"Expecting that broker3 is in bad gen id since it has a bad genId");
assertTrue(replServer1.getReplicationServerDomain(baseDn.toNormalizedString(), false).
isDegradedDueToGenerationId(server3ID));
int found = testEntriesInDb();
assertEquals(found, updatedEntries.length,
" Entries present in DB :" + found +
" Expected entries :" + updatedEntries.length);
debugInfo("Connecting DS to replServer1.");
connectServer1ToChangelog(changelog1ID);
debugInfo("Adding reset task to DS.");
Entry taskReset = TestCaseUtils.makeEntry(
"dn: ds-task-id=resetgenid" + UUID.randomUUID() +
",cn=Scheduled Tasks,cn=Tasks",
"objectclass: top",
"objectclass: ds-task",
"objectclass: ds-task-reset-generation-id",
"ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask",
"ds-task-reset-generation-id-domain-base-dn: " + baseDnStr);
addTask(taskReset, ResultCode.SUCCESS, null);
waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null);
Thread.sleep(500);
debugInfo("Verifying that all replservers genIds have been reset.");
genId = readGenIdFromSuffixRootEntry();
assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId);
assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId);
assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId);
debugInfo("Adding reset task to DS." + genId);
taskReset = TestCaseUtils.makeEntry(
"dn: ds-task-id=resetgenid" + UUID.randomUUID() +
",cn=Scheduled Tasks,cn=Tasks",
"objectclass: top",
"objectclass: ds-task",
"objectclass: ds-task-reset-generation-id",
"ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask",
"ds-task-reset-generation-id-domain-base-dn: " + baseDnStr,
"ds-task-reset-generation-id-new-value: -1");
addTask(taskReset, ResultCode.SUCCESS, null);
waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null);
debugInfo("Verifying that all replservers genIds have been reset.");
waitRes=0;
while(waitRes<100)
{
readGenIdFromSuffixRootEntry();
if ((replServer1.getGenerationId(baseDn.toNormalizedString())==-1)
&& (replServer2.getGenerationId(baseDn.toNormalizedString())==-1)
&& (replServer3.getGenerationId(baseDn.toNormalizedString())==-1))
break;
waitRes++;
Thread.sleep(100);
}
assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), -1, "test"+i);
assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), -1, "test"+i);
assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), -1, "test"+i);
debugInfo(
"Disconnect DS from replServer1 (required in order to DEL entries).");
disconnectFromReplServer(changelog1ID);
debugInfo("Successfully ending " + testCase);
} finally
{
postTest();
}
}
/**
* Disconnect broker and remove entries from the local DB
*/
protected void postTest()
{
debugInfo("Post test cleaning.");
// Clean brokers
if (broker2 != null)
broker2.stop();
broker2 = null;
if (broker3 != null)
broker3.stop();
broker3 = null;
if (replServer1 != null)
{
replServer1.clearDb();
replServer1.remove();
StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(),
replServer1.getDbDirName()));
replServer1 = null;
}
if (replServer2 != null)
{
replServer2.clearDb();
replServer2.remove();
StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(),
replServer2.getDbDirName()));
replServer2 = null;
}
if (replServer3 != null)
{
replServer3.clearDb();
replServer3.remove();
StaticUtils.recursiveDelete(new File(DirectoryServer.getInstanceRoot(),
replServer3.getDbDirName()));
replServer3 = null;
}
super.cleanRealEntries();
// Clean replication server ports
for (int i = 0; i < replServerPort.length; i++)
{
replServerPort[i] = 0;
}
debugInfo("Clearing DS backend");
try
{
TestCaseUtils.initializeTestBackend(false);
} catch (Exception ex)
{debugInfo("postTest(): error cleaning memory backend: " + ex);}
}
/**
* Test generationID saving when the root entry does not exist
* at the moment when the replication is enabled.
* @throws Exception
*/
@Test(enabled=false, groups="slow")
public void testServerStop() throws Exception
{
String testCase = "testServerStop";
debugInfo("Starting "+ testCase + " debugEnabled:" + debugEnabled());
debugInfo(testCase + " Clearing DS1 backend");
// Special test were we want to test with an empty backend. So free it
// for this special test.
TestCaseUtils.initializeTestBackend(false);
try
{
long genId;
replServer1 = createReplicationServer(changelog1ID, false, testCase);
/*
* Test : empty replicated backend
* Check : nothing is broken - no generationId generated
*/
// Connect DS to RS with no data
// Read generationId - should be not retrievable since no entry
debugInfo(testCase + " Connecting DS1 to replServer1(" + changelog1ID + ")");
connectServer1ToChangelog(changelog1ID);
debugInfo(testCase + " Expect genId attribute to be not retrievable");
genId = readGenIdFromSuffixRootEntry();
assertEquals(genId,-1);
this.addTestEntriesToDB(updatedEntries);
debugInfo(testCase + " Expect genId attribute to be retrievable");
genId = readGenIdFromSuffixRootEntry();
assertEquals(genId, EMPTY_DN_GENID);
disconnectFromReplServer(changelog1ID);
}
finally
{
postTest();
debugInfo("Successfully ending " + testCase);
}
}
/**
* Loop opening sessions to the Replication Server
* to check that it handle correctly disconnection and reconnection.
*/
@Test(enabled=false, groups="slow")
public void testLoop() throws Exception
{
String testCase = "testLoop";
debugInfo("Starting "+ testCase + " debugEnabled:" + debugEnabled());
long rgenId;
// Special test were we want to test with an empty backend. So free it
// for this special test.
TestCaseUtils.initializeTestBackend(false);
replServer1 = createReplicationServer(changelog1ID, false, testCase);
replServer1.clearDb();
ReplicationBroker broker = null;
try
{
for (int i=0; i< 5; i++)
{
long generationId = 1000+i;
broker = openReplicationSession(baseDn,
server2ID, 100, getChangelogPort(changelog1ID),
1000, !emptyOldChanges, generationId);
debugInfo(testCase + " Expect genId to be set in memory on the replication " +
" server side even if not wrote on disk/db since no change occurred.");
rgenId = replServer1.getGenerationId(baseDn.toNormalizedString());
assertEquals(rgenId, generationId);
broker.stop();
broker = null;
Thread.sleep(2000); // Let time to RS to clear info about previous connection
}
} finally
{
if (broker != null)
broker.stop();
postTest();
}
}
/**
* This is used to make sure that the 3 tests are run in the
* specified order since this is necessary.
*/
@Test(enabled=true, groups="slow")
public void generationIdTest() throws Exception
{
testSingleRS();
testMultiRS(0);
testServerStop();
testLoop();
}
}