/* * Copyright 2014 WANdisco * * WANdisco licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package c5db.log; import c5db.C5CommonTestUtil; import c5db.interfaces.replication.QuorumConfiguration; import c5db.interfaces.replication.ReplicatorLog; import c5db.util.CheckedConsumer; import c5db.util.WrappingKeySerializingExecutor; import com.google.common.collect.Lists; import org.hamcrest.core.Is; import org.junit.Test; import java.nio.file.Path; import java.util.concurrent.Executors; import static c5db.FutureMatchers.resultsIn; import static c5db.log.LogMatchers.aListOfEntriesWithConsecutiveSeqNums; import static c5db.log.LogTestUtil.makeSingleEntryList; import static c5db.log.LogTestUtil.someConsecutiveEntries; import static c5db.log.ReplicatorLogGenericTestUtil.seqNum; import static c5db.log.ReplicatorLogGenericTestUtil.someData; import static c5db.log.ReplicatorLogGenericTestUtil.term; import static c5db.replication.ReplicatorTestUtil.entries; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; /** * Test the chain Mooring -> QuorumDelegatingLog -> EncodedSequentialLog -> (disk), ensuring that * persisted information can be reconstructed from the information left on disk, so that a replicator * can pick up where it left off after a crash. */ public class LogIntegratedPersistenceTest { private static final String QUORUM_ID = "LogIntegratedPersistenceTest"; private static final int NUM_THREADS = 3; private final Path testDirectory = (new C5CommonTestUtil()).getDataTestDir("olog"); @Test public void anEmptyLogReturnsDefaultValuesForIndexesAndTermsAndConfigurations() throws Exception { withReplicatorLog((log) -> { assertThat(log.getLastIndex(), is(equalTo(0L))); assertThat(log.getLastTerm(), is(equalTo(0L))); assertThat(log.getLastConfigurationIndex(), is(equalTo(0L))); assertThat(log.getLastConfiguration(), is(equalTo(QuorumConfiguration.EMPTY))); }); } @Test public void termAndIndexInformationIsPersisted() throws Exception { withReplicatorLog((log) -> { log.logEntries( entries() .term(77).indexes(1, 2) .term(78).indexes(3, 4).build()); }); withReplicatorLog((log) -> { assertThat(log.getLastIndex(), is(equalTo(4L))); assertThat(log.getLastTerm(), is(equalTo(78L))); assertThat(log.getLogTerm(3), is(equalTo(78L))); assertThat(log.getLogTerm(2), is(equalTo(77L))); }); } @Test public void configurationInformationIsPersisted() throws Exception { final QuorumConfiguration firstConfig = QuorumConfiguration .of(Lists.newArrayList(1L, 2L, 3L)) .getTransitionalConfiguration(Lists.newArrayList(2L, 3L, 4L)); final QuorumConfiguration secondConfig = firstConfig.getCompletedConfiguration(); withReplicatorLog((log) -> { log.logEntries( entries() .term(97).indexes(1, 2) .term(98).configurationAndIndex(firstConfig, 3) .term(99).indexes(4, 5) .term(99).configurationAndIndex(secondConfig, 6).build()); }); withReplicatorLog((log) -> { assertThat(log.getLastConfiguration(), is(equalTo(secondConfig))); assertThat(log.getLastConfigurationIndex(), is(equalTo(6L))); log.truncateLog(seqNum(4)); assertThat(log.getLastConfiguration(), is(equalTo(firstConfig))); assertThat(log.getLastConfigurationIndex(), is(equalTo(3L))); log.truncateLog(seqNum(2)); assertThat(log.getLastConfiguration(), is(equalTo(QuorumConfiguration.EMPTY))); assertThat(log.getLastConfigurationIndex(), is(equalTo(0L))); }); } @Test(expected = RuntimeException.class) public void informationAboutWhatSequenceNumberComesNextIsPersistedSoThatAnIncorrectSeqNumCanBeCaught() throws Exception { withReplicatorLog((log) -> { log.logEntries( entries().term(1).indexes(1, 2, 3).build()); }); withReplicatorLog((log) -> { log.logEntries( entries().term(2).indexes(5, 6, 7).build()); }); } @Test(timeout = 3000) public void headerInformationIsPersistedAfterARollOperation() throws Exception { final QuorumConfiguration configuration = QuorumConfiguration.of(Lists.newArrayList(1L, 2L, 3L)); final long lastTerm = 97; try (OLog oLog = getOLog()) { new Mooring(oLog, QUORUM_ID) .logEntries(entries() .term(lastTerm) .configurationAndIndex(configuration, 1).build()); oLog.roll(QUORUM_ID).get(); } withReplicatorLog((log) -> { assertThat(log.getLastTerm(), is(equalTo(lastTerm))); assertThat(log.getLastConfiguration(), is(equalTo(configuration))); }); } @Test(timeout = 3000) public void rollsKeepingTrackOfTermCorrectly() throws Exception { withOpenOLog((oLog) -> { oLog.logEntries(makeSingleEntryList(seqNum(1), term(17), someData()), QUORUM_ID); oLog.roll(QUORUM_ID); oLog.logEntries(makeSingleEntryList(seqNum(2), term(56), someData()), QUORUM_ID); }); withOpenOLog((oLog) -> { assertThat(oLog.getLastTerm(QUORUM_ID), Is.is(equalTo(56L))); oLog.truncateLog(seqNum(2), QUORUM_ID); assertThat(oLog.getLastTerm(QUORUM_ID), Is.is(equalTo(17L))); }); } @Test(timeout = 3000) public void performsMultiLogFetchesCorrectlyFromPersistence() throws Exception { withOpenOLog((oLog) -> { oLog.logEntries(someConsecutiveEntries(1, 6), QUORUM_ID); oLog.roll(QUORUM_ID); oLog.logEntries(someConsecutiveEntries(6, 11), QUORUM_ID); oLog.roll(QUORUM_ID); oLog.logEntries(someConsecutiveEntries(11, 16), QUORUM_ID); }); withOpenOLog((oLog) -> assertThat(oLog.getLogEntries(3, 15, QUORUM_ID), resultsIn(aListOfEntriesWithConsecutiveSeqNums(3, 15)))); withOpenOLog((oLog) -> assertThat(oLog.getLogEntries(1, 6, QUORUM_ID), resultsIn(aListOfEntriesWithConsecutiveSeqNums(1, 6)))); } private void withReplicatorLog(CheckedConsumer<ReplicatorLog, Exception> useLog) throws Exception { try (OLog oLog = getOLog()) { useLog.accept(new Mooring(oLog, QUORUM_ID)); } } private void withOpenOLog(CheckedConsumer<OLog, Exception> useLog) throws Exception { try (OLog oLog = getOLog()) { oLog.openAsync(QUORUM_ID).get(); useLog.accept(oLog); } } private OLog getOLog() throws Exception { return new QuorumDelegatingLog( new LogFileService(testDirectory), new WrappingKeySerializingExecutor(Executors.newFixedThreadPool(NUM_THREADS)), NavigableMapOLogEntryOracle::new, InMemoryPersistenceNavigator::new); } }