aboutsummaryrefslogtreecommitdiffstats
path: root/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/StateVersionTracker.java
blob: 518361f18fccbd33cef7c97d1c7eef7ce297b42e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.clustercontroller.core;

import com.yahoo.vdslib.state.ClusterState;
import com.yahoo.vespa.clustercontroller.core.hostinfo.HostInfo;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Keeps track of the active cluster state and handles the transition edges between
 * one state to the next. In particular, it ensures that states have strictly increasing
 * version numbers.
 *
 * Wraps ClusterStateView to ensure its knowledge of available nodes stays up to date.
 */
public class StateVersionTracker {

    // We always increment the version _before_ publishing, so the effective first cluster
    // state version when starting from 1 will be 2. This matches legacy behavior and a bunch
    // of existing tests expect it.
    private int currentVersion = 1;
    private int lastZooKeeperVersion = 0;

    // The lowest published distribution bit count for the lifetime of this controller.
    // TODO this mirrors legacy behavior, but should be moved into stable ZK state.
    private int lowestObservedDistributionBits = 16;

    private ClusterState currentUnversionedState = ClusterState.emptyState();
    private AnnotatedClusterState latestCandidateState = AnnotatedClusterState.emptyState();
    private AnnotatedClusterState currentClusterState = latestCandidateState;

    private final MetricUpdater metricUpdater;
    private ClusterStateView clusterStateView;

    private final LinkedList<ClusterStateHistoryEntry> clusterStateHistory = new LinkedList<>();
    private int maxHistoryEntryCount = 50;

    StateVersionTracker(final MetricUpdater metricUpdater) {
        this.metricUpdater = metricUpdater;
        clusterStateView = ClusterStateView.create(currentUnversionedState, metricUpdater);
    }

    void setVersionRetrievedFromZooKeeper(final int version) {
        this.currentVersion = Math.max(1, version);
        this.lastZooKeeperVersion = this.currentVersion;
    }

    /**
     * Sets limit on how many cluster states can be kept in the in-memory queue. Once
     * the list exceeds this limit, the oldest state is repeatedly removed until the limit
     * is no longer exceeded.
     *
     * Takes effect upon the next invocation of promoteCandidateToVersionedState().
     */
    void setMaxHistoryEntryCount(final int maxHistoryEntryCount) {
        this.maxHistoryEntryCount = maxHistoryEntryCount;
    }

    int getCurrentVersion() {
        return this.currentVersion;
    }

    boolean hasReceivedNewVersionFromZooKeeper() {
        return currentVersion <= lastZooKeeperVersion;
    }

    int getLowestObservedDistributionBits() {
        return lowestObservedDistributionBits;
    }

    AnnotatedClusterState getAnnotatedVersionedClusterState() {
        return currentClusterState;
    }

    public ClusterState getVersionedClusterState() {
        return currentClusterState.getClusterState();
    }

    public void updateLatestCandidateState(final AnnotatedClusterState candidate) {
        assert(latestCandidateState.getClusterState().getVersion() == 0);
        latestCandidateState = candidate;
    }

    /**
     * Returns the last state provided to updateLatestCandidateState, which _may or may not_ be
     * a published state. Primary use case for this function is a caller which is interested in
     * changes that may not be reflected in the published state. The best example of this would
     * be node state changes when a cluster is marked as Down.
     */
    public AnnotatedClusterState getLatestCandidateState() {
        return latestCandidateState;
    }

    public List<ClusterStateHistoryEntry> getClusterStateHistory() {
        return Collections.unmodifiableList(clusterStateHistory);
    }

    boolean candidateChangedEnoughFromCurrentToWarrantPublish() {
        return !currentUnversionedState.similarToIgnoringInitProgress(latestCandidateState.getClusterState());
    }

    void promoteCandidateToVersionedState(final long currentTimeMs) {
        final int newVersion = currentVersion + 1;
        updateStatesForNewVersion(latestCandidateState, newVersion);
        currentVersion = newVersion;

        recordCurrentStateInHistoryAtTime(currentTimeMs);
    }

    private void updateStatesForNewVersion(final AnnotatedClusterState newState, final int newVersion) {
        currentClusterState = new AnnotatedClusterState(
                newState.getClusterState().clone(), // Because we mutate version below
                newState.getClusterStateReason(),
                newState.getNodeStateReasons());
        currentClusterState.getClusterState().setVersion(newVersion);
        currentUnversionedState = newState.getClusterState().clone();
        lowestObservedDistributionBits = Math.min(
                lowestObservedDistributionBits,
                newState.getClusterState().getDistributionBitCount());
        // TODO should this take place in updateLatestCandidateState instead? I.e. does it require a consolidated state?
        clusterStateView = ClusterStateView.create(currentClusterState.getClusterState(), metricUpdater);
    }

    private void recordCurrentStateInHistoryAtTime(final long currentTimeMs) {
        clusterStateHistory.addFirst(new ClusterStateHistoryEntry(
                currentClusterState.getClusterState(), currentTimeMs));
        while (clusterStateHistory.size() > maxHistoryEntryCount) {
            clusterStateHistory.removeLast();
        }
    }

    void handleUpdatedHostInfo(final Map<Integer, String> hostnames, final NodeInfo node, final HostInfo hostInfo) {
        // TODO the wiring here isn't unit tested. Need mockable integration points.
        clusterStateView.handleUpdatedHostInfo(hostnames, node, hostInfo);
    }

}