/*
* 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 2014-2015 ForgeRock AS.
*/
package org.opends.server.replication.server.changelog.file;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.assertj.core.data.MapEntry;
import org.forgerock.util.time.TimeService;
import org.opends.server.DirectoryServerTestCase;
import org.opends.server.TestCaseUtils;
import org.opends.server.replication.common.CSN;
import org.opends.server.replication.common.CSNGenerator;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.replication.server.ChangelogState;
import org.opends.server.replication.server.ReplicationServer;
import org.opends.server.replication.server.changelog.api.ChangeNumberIndexRecord;
import org.opends.server.replication.server.changelog.api.ChangelogException;
import org.opends.server.types.DN;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.TimeThread;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.opends.server.replication.server.changelog.file.ReplicationEnvironment.*;
@SuppressWarnings("javadoc")
public class ReplicationEnvironmentTest extends DirectoryServerTestCase
{
private static final int SERVER_ID_1 = 1;
private static final int SERVER_ID_2 = 2;
private static final String DN1_AS_STRING = "cn=test1,dc=company.com";
private static final String DN2_AS_STRING = "cn=te::st2,dc=company.com";
private static final String DN3_AS_STRING = "cn=test3,dc=company.com";
private static final String TEST_DIRECTORY_CHANGELOG = "test-output/changelog";
@BeforeClass
public void setUp() throws Exception
{
// This test suite depends on having the schema available for DN decoding.
TestCaseUtils.startFakeServer();
}
@AfterClass
public void tearDown() throws Exception
{
TestCaseUtils.shutdownFakeServer();
}
@AfterMethod
public void cleanTestChangelogDirectory()
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
if (rootPath.exists())
{
StaticUtils.recursiveDelete(rootPath);
}
}
@Test
public void testReadChangelogStateWithSingleDN() throws Exception
{
Log<Long,ChangeNumberIndexRecord> cnDB = null;
Log<CSN,UpdateMsg> replicaDB = null, replicaDB2 = null;
try
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
final DN domainDN = DN.valueOf(DN1_AS_STRING);
ReplicationEnvironment environment = createReplicationEnv(rootPath);
cnDB = environment.getOrCreateCNIndexDB();
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1);
replicaDB2 = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_2, 1);
final ChangelogState state = environment.readOnDiskChangelogState();
assertThat(state.getDomainToServerIds()).containsKeys(domainDN);
assertThat(state.getDomainToServerIds().get(domainDN)).containsOnly(SERVER_ID_1, SERVER_ID_2);
assertThat(state.getDomainToGenerationId()).containsExactly(MapEntry.entry(domainDN, 1L));
assertThat(state.isEqualTo(environment.getChangelogState())).isTrue();
}
finally
{
StaticUtils.close(cnDB, replicaDB, replicaDB2);
}
}
@Test
public void testReadChangelogStateWithMultipleDN() throws Exception
{
Log<Long,ChangeNumberIndexRecord> cnDB = null;
List<Log<CSN,UpdateMsg>> replicaDBs = new ArrayList<>();
try
{
File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
List<DN> domainDNs = Arrays.asList(DN.valueOf(DN1_AS_STRING), DN.valueOf(DN2_AS_STRING), DN.valueOf(DN3_AS_STRING));
ReplicationEnvironment environment = createReplicationEnv(rootPath);
cnDB = environment.getOrCreateCNIndexDB();
for (int i = 0; i <= 2 ; i++)
{
for (int j = 1; j <= 10; j++)
{
// 3 domains, 10 server id each, generation id is different for each domain
replicaDBs.add(environment.getOrCreateReplicaDB(domainDNs.get(i), j, i+1));
}
}
final ChangelogState state = environment.readOnDiskChangelogState();
assertThat(state.getDomainToServerIds()).containsKeys(domainDNs.get(0), domainDNs.get(1), domainDNs.get(2));
for (int i = 0; i <= 2 ; i++)
{
assertThat(state.getDomainToServerIds().get(domainDNs.get(i))).containsOnly(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
assertThat(state.getDomainToGenerationId()).containsOnly(
MapEntry.entry(domainDNs.get(0), 1L),
MapEntry.entry(domainDNs.get(1), 2L),
MapEntry.entry(domainDNs.get(2), 3L));
assertThat(state.isEqualTo(environment.getChangelogState())).isTrue();
}
finally
{
StaticUtils.close(cnDB);
StaticUtils.close(replicaDBs);
}
}
@Test
public void testReadChangelogStateWithReplicaOffline() throws Exception
{
Log<Long,ChangeNumberIndexRecord> cnDB = null;
Log<CSN,UpdateMsg> replicaDB = null;
try
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
final DN domainDN = DN.valueOf(DN1_AS_STRING);
ReplicationEnvironment environment = createReplicationEnv(rootPath);
cnDB = environment.getOrCreateCNIndexDB();
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1);
// put server id 1 offline
CSN offlineCSN = new CSN(TimeThread.getTime(), 0, SERVER_ID_1);
environment.notifyReplicaOffline(domainDN, offlineCSN);
final ChangelogState state = environment.readOnDiskChangelogState();
assertThat(state.getOfflineReplicas().getSnapshot())
.containsExactly(MapEntry.entry(domainDN, Arrays.asList(offlineCSN)));
assertThat(state.isEqualTo(environment.getChangelogState())).isTrue();
}
finally
{
StaticUtils.close(cnDB, replicaDB);
}
}
@Test(expectedExceptions=ChangelogException.class)
public void testReadChangelogStateWithReplicaOfflineStateFileCorrupted() throws Exception
{
Log<Long,ChangeNumberIndexRecord> cnDB = null;
Log<CSN,UpdateMsg> replicaDB = null;
try
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
final DN domainDN = DN.valueOf(DN1_AS_STRING);
ReplicationEnvironment environment = createReplicationEnv(rootPath);
cnDB = environment.getOrCreateCNIndexDB();
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1);
File offlineStateFile = new File(environment.getServerIdPath("1", 1), REPLICA_OFFLINE_STATE_FILENAME);
offlineStateFile.createNewFile();
environment.readOnDiskChangelogState();
}
finally
{
StaticUtils.close(cnDB, replicaDB);
}
}
@Test
public void testReadChangelogStateWithReplicaOfflineSentTwice() throws Exception
{
Log<Long,ChangeNumberIndexRecord> cnDB = null;
Log<CSN,UpdateMsg> replicaDB = null;
try
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
final DN domainDN = DN.valueOf(DN1_AS_STRING);
ReplicationEnvironment environment = createReplicationEnv(rootPath);
cnDB = environment.getOrCreateCNIndexDB();
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1);
// put server id 1 offline twice
CSNGenerator csnGenerator = new CSNGenerator(SERVER_ID_1, 100);
environment.notifyReplicaOffline(domainDN, csnGenerator.newCSN());
CSN lastOfflineCSN = csnGenerator.newCSN();
environment.notifyReplicaOffline(domainDN, lastOfflineCSN);
final ChangelogState state = environment.readOnDiskChangelogState();
assertThat(state.getOfflineReplicas().getSnapshot())
.containsExactly(MapEntry.entry(domainDN, Arrays.asList(lastOfflineCSN)));
assertThat(state.isEqualTo(environment.getChangelogState())).isTrue();
}
finally
{
StaticUtils.close(cnDB, replicaDB);
}
}
@Test
public void testReadChangelogStateWithReplicaOfflineThenReplicaOnline() throws Exception
{
Log<Long,ChangeNumberIndexRecord> cnDB = null;
Log<CSN,UpdateMsg> replicaDB = null;
try
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
final DN domainDN = DN.valueOf(DN1_AS_STRING);
ReplicationEnvironment environment = createReplicationEnv(rootPath);
cnDB = environment.getOrCreateCNIndexDB();
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1);
// put server id 1 offline
environment.notifyReplicaOffline(domainDN, new CSN(TimeThread.getTime(), 0, SERVER_ID_1));
// put server id 1 online again
environment.notifyReplicaOnline(domainDN, SERVER_ID_1);
final ChangelogState state = environment.readOnDiskChangelogState();
assertThat(state.getOfflineReplicas()).isEmpty();
assertThat(state.isEqualTo(environment.getChangelogState())).isTrue();
}
finally
{
StaticUtils.close(cnDB, replicaDB);
}
}
@Test
public void testCreateThenReadChangelogStateWithReplicaOffline() throws Exception
{
Log<Long,ChangeNumberIndexRecord> cnDB = null;
Log<CSN,UpdateMsg> replicaDB = null;
try
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
final DN domainDN = DN.valueOf(DN1_AS_STRING);
ReplicationEnvironment environment = createReplicationEnv(rootPath);
cnDB = environment.getOrCreateCNIndexDB();
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1);
CSN offlineCSN = new CSN(TimeThread.getTime(), 0, SERVER_ID_1);
environment.notifyReplicaOffline(domainDN, offlineCSN);
final ChangelogState state = environment.readOnDiskChangelogState();
assertThat(state.getDomainToServerIds()).containsKeys(domainDN);
assertThat(state.getDomainToServerIds().get(domainDN)).containsOnly(SERVER_ID_1);
assertThat(state.getDomainToGenerationId()).containsExactly(MapEntry.entry(domainDN, 1L));
assertThat(state.getOfflineReplicas().getSnapshot())
.containsExactly(MapEntry.entry(domainDN, Arrays.asList(offlineCSN)));
assertThat(state.isEqualTo(environment.getChangelogState())).isTrue();
}
finally
{
StaticUtils.close(cnDB, replicaDB);
}
}
@Test(expectedExceptions=ChangelogException.class)
public void testMissingDomainDirectory() throws Exception
{
Log<Long,ChangeNumberIndexRecord> cnDB = null;
Log<CSN,UpdateMsg> replicaDB = null, replicaDB2 = null;
try
{
File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
DN domainDN = DN.valueOf(DN1_AS_STRING);
ReplicationEnvironment environment = createReplicationEnv(rootPath);
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1);
replicaDB2 = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_2, 1);
// delete the domain directory created for the 2 replica DBs to break the
// consistency with domain state file
StaticUtils.recursiveDelete(new File(rootPath, "1.dom"));
environment.readOnDiskChangelogState();
}
finally
{
StaticUtils.close(cnDB, replicaDB, replicaDB2);
}
}
private ReplicationEnvironment createReplicationEnv(File rootPath) throws ChangelogException
{
ReplicationServer unusedReplicationServer = null;
return new ReplicationEnvironment(rootPath.getAbsolutePath(), unusedReplicationServer, TimeService.SYSTEM);
}
@Test
public void testLastRotationTimeRetrievalWithNoRotationFile() throws Exception
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
TimeService time = mock(TimeService.class);
when(time.now()).thenReturn(100L);
ReplicationEnvironment environment = new ReplicationEnvironment(rootPath.getAbsolutePath(), null, time);
assertThat(environment.getCnIndexDBLastRotationTime()).isEqualTo(100L);
}
@Test
public void testLastRotationTimeRetrievalWithRotationFile() throws Exception
{
final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
final TimeService time = mock(TimeService.class);
when(time.now()).thenReturn(100L, 200L);
ReplicationEnvironment environment = new ReplicationEnvironment(rootPath.getAbsolutePath(), null, time);
Log<Long,ChangeNumberIndexRecord> cnIndexDB = environment.getOrCreateCNIndexDB();
try {
environment.notifyLogFileRotation(cnIndexDB);
// check runtime change of last rotation time is effective
// this should also persist the time in a file, but this is checked later in the test
assertThat(environment.getCnIndexDBLastRotationTime()).isEqualTo(200L);
}
finally
{
cnIndexDB.close();
environment.shutdown();
}
// now check last rotation time is correctly read from persisted file when re-creating environment
when(time.now()).thenReturn(0L);
environment = new ReplicationEnvironment(rootPath.getAbsolutePath(), null, time);
assertThat(environment.getCnIndexDBLastRotationTime()).isEqualTo(200L);
}
}