Fix DeleteEntries persisting with wrong index
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / main / java / org / opendaylight / controller / cluster / raft / ReplicatedLogImpl.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.cluster.raft;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.util.Collections;
13 import java.util.List;
14 import java.util.function.Consumer;
15 import org.opendaylight.controller.cluster.raft.persisted.DeleteEntries;
16 import org.opendaylight.controller.cluster.raft.persisted.Snapshot;
17
18 /**
19  * Implementation of ReplicatedLog used by the RaftActor.
20  */
21 final class ReplicatedLogImpl extends AbstractReplicatedLogImpl {
22     private static final int DATA_SIZE_DIVIDER = 5;
23
24     private final RaftActorContext context;
25     private long dataSizeSinceLastSnapshot = 0L;
26
27     private ReplicatedLogImpl(final long snapshotIndex, final long snapshotTerm,
28             final List<ReplicatedLogEntry> unAppliedEntries,
29             final RaftActorContext context) {
30         super(snapshotIndex, snapshotTerm, unAppliedEntries, context.getId());
31         this.context = requireNonNull(context);
32     }
33
34     static ReplicatedLog newInstance(final Snapshot snapshot, final RaftActorContext context) {
35         return new ReplicatedLogImpl(snapshot.getLastAppliedIndex(), snapshot.getLastAppliedTerm(),
36                 snapshot.getUnAppliedEntries(), context);
37     }
38
39     static ReplicatedLog newInstance(final RaftActorContext context) {
40         return new ReplicatedLogImpl(-1L, -1L, Collections.<ReplicatedLogEntry>emptyList(), context);
41     }
42
43     @Override
44     public boolean removeFromAndPersist(final long logEntryIndex) {
45         long adjustedIndex = removeFrom(logEntryIndex);
46         if (adjustedIndex >= 0) {
47             context.getPersistenceProvider().persist(new DeleteEntries(logEntryIndex), NoopProcedure.instance());
48             return true;
49         }
50
51         return false;
52     }
53
54     @Override
55     public boolean shouldCaptureSnapshot(final long logIndex) {
56         final ConfigParams config = context.getConfigParams();
57         final long journalSize = logIndex + 1;
58         final long dataThreshold = context.getTotalMemory() * config.getSnapshotDataThresholdPercentage() / 100;
59
60         return journalSize % config.getSnapshotBatchCount() == 0 || getDataSizeForSnapshotCheck() > dataThreshold;
61     }
62
63     @Override
64     public void captureSnapshotIfReady(final ReplicatedLogEntry replicatedLogEntry) {
65         if (shouldCaptureSnapshot(replicatedLogEntry.getIndex())) {
66             boolean started = context.getSnapshotManager().capture(replicatedLogEntry,
67                     context.getCurrentBehavior().getReplicatedToAllIndex());
68             if (started && !context.hasFollowers()) {
69                 dataSizeSinceLastSnapshot = 0;
70             }
71         }
72     }
73
74     private long getDataSizeForSnapshotCheck() {
75         if (!context.hasFollowers()) {
76             // When we do not have followers we do not maintain an in-memory log
77             // due to this the journalSize will never become anything close to the
78             // snapshot batch count. In fact will mostly be 1.
79             // Similarly since the journal's dataSize depends on the entries in the
80             // journal the journal's dataSize will never reach a value close to the
81             // memory threshold.
82             // By maintaining the dataSize outside the journal we are tracking essentially
83             // what we have written to the disk however since we no longer are in
84             // need of doing a snapshot just for the sake of freeing up memory we adjust
85             // the real size of data by the DATA_SIZE_DIVIDER so that we do not snapshot as often
86             // as if we were maintaining a real snapshot
87             return dataSizeSinceLastSnapshot / DATA_SIZE_DIVIDER;
88         } else {
89             return dataSize();
90         }
91     }
92
93     @Override
94     public boolean appendAndPersist(final ReplicatedLogEntry replicatedLogEntry,
95             final Consumer<ReplicatedLogEntry> callback, final boolean doAsync)  {
96
97         context.getLogger().debug("{}: Append log entry and persist {} ", context.getId(), replicatedLogEntry);
98
99         if (!append(replicatedLogEntry)) {
100             return false;
101         }
102
103         if (doAsync) {
104             context.getPersistenceProvider().persistAsync(replicatedLogEntry,
105                 entry -> persistCallback(entry, callback));
106         } else {
107             context.getPersistenceProvider().persist(replicatedLogEntry, entry -> syncPersistCallback(entry, callback));
108         }
109
110         return true;
111     }
112
113     private void persistCallback(final ReplicatedLogEntry persistedLogEntry,
114             final Consumer<ReplicatedLogEntry> callback) {
115         context.getExecutor().execute(() -> syncPersistCallback(persistedLogEntry, callback));
116     }
117
118     private void syncPersistCallback(final ReplicatedLogEntry persistedLogEntry,
119             final Consumer<ReplicatedLogEntry> callback) {
120         context.getLogger().debug("{}: persist complete {}", context.getId(), persistedLogEntry);
121
122         dataSizeSinceLastSnapshot += persistedLogEntry.size();
123
124         if (callback != null) {
125             callback.accept(persistedLogEntry);
126         }
127     }
128 }