/* * 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.interfaces.replication.QuorumConfiguration; import c5db.interfaces.replication.ReplicatorLog; import c5db.replication.generated.LogEntry; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import static c5db.log.OLogEntryOracle.QuorumConfigurationWithSeqNum; import static java.lang.Math.max; /** * Implementation of ReplicatorLog which delegates to an OLog. Each replicator instance has * a ReplicatorLog associated with it. The Mooring implementation allows each of these to * communicate to the same OLog behind the scenes. For instance, since the OLog API requires specifying * quorumId for most operations, whereas the ReplicatorLog does not "know about" quorumId, * Mooring bridges the gap by explicitly tracking quorumId and providing it on delegated calls. * <p> * Mooring also caches the current term and the last index (log sequence number) so that in * most cases these never need to access OLog. */ public class Mooring implements ReplicatorLog { private static final int LOG_TIMEOUT = 10; // seconds private final OLog log; private final String quorumId; private long currentTerm; private long lastIndex; private QuorumConfiguration lastQuorumConfig = QuorumConfiguration.EMPTY; private long lastQuorumConfigIndex = 0; Mooring(OLog log, String quorumId) throws IOException { this.quorumId = quorumId; this.log = log; try { // TODO maybe move this from the constructor to an 'open' method log.openAsync(quorumId) .get(LOG_TIMEOUT, TimeUnit.SECONDS); currentTerm = log.getLastTerm(quorumId); lastIndex = log.getNextSeqNum(quorumId) - 1; setQuorumConfigFromLog(); } catch (Exception e) { throw new IOException(e); } } @Override public ListenableFuture<Boolean> logEntries(List<LogEntry> entries) { if (entries.isEmpty()) { throw new IllegalArgumentException("Mooring#logEntries: empty entry list"); } List<OLogEntry> oLogEntries = new ArrayList<>(); for (LogEntry entry : entries) { if (isAConfigurationEntry(entry)) { lastQuorumConfig = QuorumConfiguration.fromProtostuff(entry.getQuorumConfiguration()); lastQuorumConfigIndex = entry.getIndex(); } oLogEntries.add(OLogEntry.fromProtostuff(entry)); } updateCachedTermAndIndex(oLogEntries); return log.logEntries(oLogEntries, quorumId); } @Override public ListenableFuture<List<LogEntry>> getLogEntries(long start, long end) { return Futures.transform(log.getLogEntries(start, end, quorumId), Mooring::toProtostuffMessages); } @Override public long getLogTerm(long index) { return log.getLogTerm(index, quorumId); } @Override public long getLastTerm() { return currentTerm; } @Override public long getLastIndex() { return lastIndex; } @Override public ListenableFuture<Boolean> truncateLog(long entryIndex) { if (entryIndex <= 0) { throw new IllegalArgumentException("Mooring#truncateLog"); } lastIndex = max(entryIndex - 1, 0); currentTerm = log.getLogTerm(lastIndex, quorumId); ListenableFuture<Boolean> truncateFuture = log.truncateLog(entryIndex, quorumId); setQuorumConfigFromLog(); return truncateFuture; } @Override public QuorumConfiguration getLastConfiguration() { return lastQuorumConfig; } @Override public long getLastConfigurationIndex() { return lastQuorumConfigIndex; } private void setQuorumConfigFromLog() { final QuorumConfigurationWithSeqNum configFromLog = log.getLastQuorumConfig(quorumId); lastQuorumConfig = configFromLog.quorumConfiguration; lastQuorumConfigIndex = configFromLog.seqNum; } private static List<LogEntry> toProtostuffMessages(List<OLogEntry> entries) { return Lists.transform(entries, OLogEntry::toProtostuff); } private void updateCachedTermAndIndex(List<OLogEntry> entriesToLog) { long size = entriesToLog.size(); if (size > 0) { final OLogEntry lastEntry = entriesToLog.get(entriesToLog.size() - 1); currentTerm = lastEntry.getElectionTerm(); lastIndex = lastEntry.getSeqNum(); } } private boolean isAConfigurationEntry(LogEntry entry) { return entry.getQuorumConfiguration() != null; } }