BUG 2185: Expand the scope of sync status to cover a slow follower
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / main / java / org / opendaylight / controller / cluster / raft / behaviors / SyncStatusTracker.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
9 package org.opendaylight.controller.cluster.raft.behaviors;
10
11 import akka.actor.ActorRef;
12 import com.google.common.base.Preconditions;
13 import org.opendaylight.controller.cluster.raft.base.messages.FollowerInitialSyncUpStatus;
14
15 /**
16  * The SyncStatusTracker tracks if a Follower is in sync with any given Leader or not
17  * When an update is received from the Leader and the update happens to be the first update
18  * from that Leader then the SyncStatusTracker will not mark the Follower as not in-sync till the
19  * Followers commitIndex matches the commitIndex that the Leader sent in it's very first update.
20  * Subsequently when an update is received the tracker will consider the Follower to be out of
21  * sync if it is behind by 'syncThreshold' commits.
22  */
23 public class SyncStatusTracker {
24
25     private static final boolean IN_SYNC = true;
26     private static final boolean NOT_IN_SYNC = false;
27     private static final boolean FORCE_STATUS_CHANGE = true;
28
29     private final String id;
30     private String syncedLeaderId = null;
31     private final ActorRef actor;
32     private final int syncThreshold;
33     private boolean syncStatus = false;
34     private long minimumExpectedIndex = -2L;
35
36     public SyncStatusTracker(ActorRef actor, String id, int syncThreshold) {
37         this.actor = Preconditions.checkNotNull(actor, "actor should not be null");
38         this.id = Preconditions.checkNotNull(id, "id should not be null");
39         Preconditions.checkArgument(syncThreshold >= 0, "syncThreshold should be greater than or equal to 0");
40         this.syncThreshold = syncThreshold;
41     }
42
43     public void update(String leaderId, long leaderCommit, long commitIndex){
44         leaderId = Preconditions.checkNotNull(leaderId, "leaderId should not be null");
45
46         if(!leaderId.equals(syncedLeaderId)){
47             minimumExpectedIndex = leaderCommit;
48             changeSyncStatus(NOT_IN_SYNC, FORCE_STATUS_CHANGE);
49             syncedLeaderId = leaderId;
50             return;
51         }
52
53         if((leaderCommit - commitIndex) > syncThreshold){
54             changeSyncStatus(NOT_IN_SYNC);
55         } else if((leaderCommit - commitIndex) <= syncThreshold && commitIndex >= minimumExpectedIndex) {
56             changeSyncStatus(IN_SYNC);
57         }
58     }
59
60     private void changeSyncStatus(boolean newSyncStatus){
61         changeSyncStatus(newSyncStatus, !FORCE_STATUS_CHANGE);
62     }
63
64     private void changeSyncStatus(boolean newSyncStatus, boolean forceStatusChange){
65         if(syncStatus == newSyncStatus && !forceStatusChange){
66             return;
67         }
68         actor.tell(new FollowerInitialSyncUpStatus(newSyncStatus, id), ActorRef.noSender());
69         syncStatus = newSyncStatus;
70     }
71 }