Bug 7391: Fix out-of-order LeaderStateChange events
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / main / java / org / opendaylight / controller / cluster / raft / FollowerLogInformationImpl.java
1 /*
2  * Copyright (c) 2014 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
9 package org.opendaylight.controller.cluster.raft;
10
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Stopwatch;
13 import java.util.concurrent.TimeUnit;
14 import javax.annotation.Nonnull;
15 import javax.annotation.Nullable;
16 import org.opendaylight.controller.cluster.raft.behaviors.LeaderInstallSnapshotState;
17
18 /**
19  * Implementation of the FollowerLogInformation interface.
20  *
21  * @author Moiz Raja
22  * @author Thomas Pantelis
23  */
24 public class FollowerLogInformationImpl implements FollowerLogInformation {
25     private final Stopwatch stopwatch = Stopwatch.createUnstarted();
26
27     private final RaftActorContext context;
28
29     private long nextIndex;
30
31     private long matchIndex;
32
33     private long lastReplicatedIndex = -1L;
34
35     private final Stopwatch lastReplicatedStopwatch = Stopwatch.createUnstarted();
36
37     private short payloadVersion = -1;
38
39     // Assume the HELIUM_VERSION version initially for backwards compatibility until we obtain the follower's
40     // actual version via AppendEntriesReply. Although we no longer support the Helium version, a pre-Boron
41     // follower will not have the version field in AppendEntriesReply so it will be set to 0 which is
42     // HELIUM_VERSION.
43     private short raftVersion = RaftVersions.HELIUM_VERSION;
44
45     private final PeerInfo peerInfo;
46
47     private LeaderInstallSnapshotState installSnapshotState;
48
49     /**
50      * Constructs an instance.
51      *
52      * @param peerInfo the associated PeerInfo of the follower.
53      * @param matchIndex the initial match index.
54      * @param context the RaftActorContext.
55      */
56     public FollowerLogInformationImpl(PeerInfo peerInfo, long matchIndex, RaftActorContext context) {
57         this.nextIndex = context.getCommitIndex();
58         this.matchIndex = matchIndex;
59         this.context = context;
60         this.peerInfo = Preconditions.checkNotNull(peerInfo);
61     }
62
63     @Override
64     public long incrNextIndex() {
65         return nextIndex++;
66     }
67
68     @Override
69     public long decrNextIndex() {
70         return nextIndex--;
71     }
72
73     @Override
74     public boolean setNextIndex(long nextIndex) {
75         if (this.nextIndex != nextIndex) {
76             this.nextIndex = nextIndex;
77             return true;
78         }
79
80         return false;
81     }
82
83     @Override
84     public long incrMatchIndex() {
85         return matchIndex++;
86     }
87
88     @Override
89     public boolean setMatchIndex(long matchIndex) {
90         if (this.matchIndex != matchIndex) {
91             this.matchIndex = matchIndex;
92             return true;
93         }
94
95         return false;
96     }
97
98     @Override
99     public String getId() {
100         return peerInfo.getId();
101     }
102
103     @Override
104     public long getNextIndex() {
105         return nextIndex;
106     }
107
108     @Override
109     public long getMatchIndex() {
110         return matchIndex;
111     }
112
113     @Override
114     public boolean isFollowerActive() {
115         if (peerInfo.getVotingState() == VotingState.VOTING_NOT_INITIALIZED) {
116             return false;
117         }
118
119         long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
120         return stopwatch.isRunning()
121                 && elapsed <= context.getConfigParams().getElectionTimeOutInterval().toMillis();
122     }
123
124     @Override
125     public void markFollowerActive() {
126         if (stopwatch.isRunning()) {
127             stopwatch.reset();
128         }
129         stopwatch.start();
130     }
131
132     @Override
133     public void markFollowerInActive() {
134         if (stopwatch.isRunning()) {
135             stopwatch.stop();
136         }
137     }
138
139     @Override
140     public long timeSinceLastActivity() {
141         return stopwatch.elapsed(TimeUnit.MILLISECONDS);
142     }
143
144     @Override
145     public boolean okToReplicate() {
146         if (peerInfo.getVotingState() == VotingState.VOTING_NOT_INITIALIZED) {
147             return false;
148         }
149
150         // Return false if we are trying to send duplicate data before the heartbeat interval
151         if (getNextIndex() == lastReplicatedIndex && lastReplicatedStopwatch.elapsed(TimeUnit.MILLISECONDS)
152                 < context.getConfigParams().getHeartBeatInterval().toMillis()) {
153             return false;
154         }
155
156         resetLastReplicated();
157         return true;
158     }
159
160     private void resetLastReplicated() {
161         lastReplicatedIndex = getNextIndex();
162         if (lastReplicatedStopwatch.isRunning()) {
163             lastReplicatedStopwatch.reset();
164         }
165         lastReplicatedStopwatch.start();
166     }
167
168     @Override
169     public short getPayloadVersion() {
170         return payloadVersion;
171     }
172
173     @Override
174     public void setPayloadVersion(short payloadVersion) {
175         this.payloadVersion = payloadVersion;
176     }
177
178     @Override
179     public short getRaftVersion() {
180         return raftVersion;
181     }
182
183     @Override
184     public void setRaftVersion(short raftVersion) {
185         this.raftVersion = raftVersion;
186     }
187
188     @Override
189     @Nullable
190     public LeaderInstallSnapshotState getInstallSnapshotState() {
191         return installSnapshotState;
192     }
193
194     @Override
195     public void setLeaderInstallSnapshotState(@Nonnull LeaderInstallSnapshotState state) {
196         if (this.installSnapshotState == null) {
197             this.installSnapshotState = Preconditions.checkNotNull(state);
198         }
199     }
200
201     @Override
202     public void clearLeaderInstallSnapshotState() {
203         installSnapshotState = null;
204     }
205
206     @Override
207     public String toString() {
208         return "FollowerLogInformationImpl [id=" + getId() + ", nextIndex=" + nextIndex + ", matchIndex=" + matchIndex
209                 + ", lastReplicatedIndex=" + lastReplicatedIndex + ", votingState=" + peerInfo.getVotingState()
210                 + ", stopwatch=" + stopwatch.elapsed(TimeUnit.MILLISECONDS) + ", followerTimeoutMillis="
211                 + context.getConfigParams().getElectionTimeOutInterval().toMillis() + "]";
212     }
213 }