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
// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0.

// TODO: remove following line
#![allow(dead_code)]

use super::changer::Changer;
use crate::eraftpb::{ConfChangeSingle, ConfChangeType, ConfState};
use crate::tracker::ProgressTracker;
use crate::Result;

/// Translates a conf state into 1) a slice of operations creating first the config that
/// will become the outgoing one, and then the incoming one, and b) another slice that,
/// when applied to the config resulted from 1), represents the ConfState.
fn to_conf_change_single(cs: &ConfState) -> (Vec<ConfChangeSingle>, Vec<ConfChangeSingle>) {
    // Example to follow along this code:
    // voters=(1 2 3) learners=(5) outgoing=(1 2 4 6) learners_next=(4)
    //
    // This means that before entering the joint config, the configuration
    // had voters (1 2 4 6) and perhaps some learners that are already gone.
    // The new set of voters is (1 2 3), i.e. (1 2) were kept around, and (4 6)
    // are no longer voters; however 4 is poised to become a learner upon leaving
    // the joint state.
    // We can't tell whether 5 was a learner before entering the joint config,
    // but it doesn't matter (we'll pretend that it wasn't).
    //
    // The code below will construct
    // outgoing = add 1; add 2; add 4; add 6
    // incoming = remove 1; remove 2; remove 4; remove 6
    //            add 1;    add 2;    add 3;
    //            add-learner 5;
    //            add-learner 4;
    //
    // So, when starting with an empty config, after applying 'outgoing' we have
    //
    //   quorum=(1 2 4 6)
    //
    // From which we enter a joint state via 'incoming'
    //
    //   quorum=(1 2 3)&&(1 2 4 6) learners=(5) learners_next=(4)
    //
    // as desired.
    let mut incoming = Vec::new();
    let mut outgoing = Vec::new();
    for id in cs.get_voters_outgoing() {
        // If there are outgoing voters, first add them one by one so that the
        // (non-joint) config has them all.
        outgoing.push(raft_proto::new_conf_change_single(
            *id,
            ConfChangeType::AddNode,
        ));
    }

    // We're done constructing the outgoing slice, now on to the incoming one
    // (which will apply on top of the config created by the outgoing slice).

    // First, we'll remove all of the outgoing voters.
    for id in cs.get_voters_outgoing() {
        incoming.push(raft_proto::new_conf_change_single(
            *id,
            ConfChangeType::RemoveNode,
        ));
    }
    // Then we'll add the incoming voters and learners.
    for id in cs.get_voters() {
        incoming.push(raft_proto::new_conf_change_single(
            *id,
            ConfChangeType::AddNode,
        ));
    }
    for id in cs.get_learners() {
        incoming.push(raft_proto::new_conf_change_single(
            *id,
            ConfChangeType::AddLearnerNode,
        ));
    }
    // Same for LearnersNext; these are nodes we want to be learners but which
    // are currently voters in the outgoing config.
    for id in cs.get_learners_next() {
        incoming.push(raft_proto::new_conf_change_single(
            *id,
            ConfChangeType::AddLearnerNode,
        ));
    }
    (outgoing, incoming)
}

/// Restore takes a Changer (which must represent an empty configuration), and runs a
/// sequence of changes enacting the configuration described in the ConfState.
///
/// TODO(jay) find a way to only take `ProgressMap` instead of a whole tracker.
pub fn restore(tracker: &mut ProgressTracker, next_idx: u64, cs: &ConfState) -> Result<()> {
    let (outgoing, incoming) = to_conf_change_single(cs);
    if outgoing.is_empty() {
        for i in incoming {
            let (cfg, changes) = Changer::new(tracker).simple(&[i])?;
            tracker.apply_conf(cfg, changes, next_idx);
        }
    } else {
        for cc in outgoing {
            let (cfg, changes) = Changer::new(tracker).simple(&[cc])?;
            tracker.apply_conf(cfg, changes, next_idx);
        }
        let (cfg, changes) = Changer::new(tracker).enter_joint(cs.auto_leave, &incoming)?;
        tracker.apply_conf(cfg, changes, next_idx);
    }
    Ok(())
}