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
141
142
143
144
145
146
147
148
149
150
151
// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0.

use crate::eraftpb::{
    ConfChange, ConfChangeSingle, ConfChangeTransition, ConfChangeType, ConfChangeV2,
};
use std::borrow::Cow;
use std::fmt::Write;

/// Creates a `ConfChangeSingle`.
pub fn new_conf_change_single(node_id: u64, ty: ConfChangeType) -> ConfChangeSingle {
    let mut single = ConfChangeSingle::default();
    single.node_id = node_id;
    single.set_change_type(ty);
    single
}

/// Parses a Space-delimited sequence of operations into a slice of ConfChangeSingle.
/// The supported operations are:
/// - vn: make n a voter,
/// - ln: make n a learner,
/// - rn: remove n
pub fn parse_conf_change(s: &str) -> Result<Vec<ConfChangeSingle>, String> {
    let s = s.trim();
    if s.is_empty() {
        return Ok(vec![]);
    }
    let mut ccs = vec![];
    let splits = s.split_ascii_whitespace();
    for tok in splits {
        if tok.len() < 2 {
            return Err(format!("unknown token {}", tok));
        }
        let mut cc = ConfChangeSingle::default();
        let mut chars = tok.chars();
        cc.set_change_type(match chars.next().unwrap() {
            'v' => ConfChangeType::AddNode,
            'l' => ConfChangeType::AddLearnerNode,
            'r' => ConfChangeType::RemoveNode,
            _ => return Err(format!("unknown token {}", tok)),
        });
        cc.node_id = match chars.as_str().parse() {
            Ok(id) => id,
            Err(e) => return Err(format!("parse token {} fail: {}", tok, e)),
        };
        ccs.push(cc);
    }
    Ok(ccs)
}

/// The inverse to `parse_conf_change`.
pub fn stringify_conf_change(ccs: &[ConfChangeSingle]) -> String {
    let mut s = String::new();
    for (i, cc) in ccs.iter().enumerate() {
        if i > 0 {
            s.push(' ');
        }
        match cc.get_change_type() {
            ConfChangeType::AddNode => s.push('v'),
            ConfChangeType::AddLearnerNode => s.push('l'),
            ConfChangeType::RemoveNode => s.push('r'),
        }
        write!(&mut s, "{}", cc.node_id).unwrap();
    }
    s
}

/// Abstracts over ConfChangeV2 and (legacy) ConfChange to allow
/// treating them in a unified manner.
pub trait ConfChangeI {
    /// Converts conf change to `ConfChangeV2`.
    fn into_v2(self) -> ConfChangeV2;

    /// Gets conf change as `ConfChangeV2`.
    fn as_v2(&self) -> Cow<ConfChangeV2>;

    /// Converts conf change to `ConfChange`.
    ///
    /// `ConfChangeV2` can't be changed back to `ConfChange`.
    fn as_v1(&self) -> Option<&ConfChange>;
}

impl ConfChangeI for ConfChange {
    #[inline]
    fn into_v2(mut self) -> ConfChangeV2 {
        let mut cc = ConfChangeV2::default();
        let single = new_conf_change_single(self.node_id, self.get_change_type());
        cc.mut_changes().push(single);
        cc.set_context(self.take_context());
        cc
    }

    #[inline]
    fn as_v2(&self) -> Cow<ConfChangeV2> {
        Cow::Owned(self.clone().into_v2())
    }

    #[inline]
    fn as_v1(&self) -> Option<&ConfChange> {
        Some(self)
    }
}

impl ConfChangeI for ConfChangeV2 {
    #[inline]
    fn into_v2(self) -> ConfChangeV2 {
        self
    }

    #[inline]
    fn as_v2(&self) -> Cow<ConfChangeV2> {
        Cow::Borrowed(self)
    }

    #[inline]
    fn as_v1(&self) -> Option<&ConfChange> {
        None
    }
}

impl ConfChangeV2 {
    /// Checks if uses Joint Consensus.
    ///
    /// It will return Some if and only if this config change will use Joint Consensus,
    /// which is the case if it contains more than one change or if the use of Joint
    /// Consensus was requested explicitly. The bool indicates whether the Joint State
    /// will be left automatically.
    pub fn enter_joint(&self) -> Option<bool> {
        // NB: in theory, more config changes could qualify for the "simple"
        // protocol but it depends on the config on top of which the changes apply.
        // For example, adding two learners is not OK if both nodes are part of the
        // base config (i.e. two voters are turned into learners in the process of
        // applying the conf change). In practice, these distinctions should not
        // matter, so we keep it simple and use Joint Consensus liberally.
        if self.get_transition() != ConfChangeTransition::Auto || self.changes.len() > 1 {
            match self.get_transition() {
                ConfChangeTransition::Auto | ConfChangeTransition::Implicit => Some(true),
                ConfChangeTransition::Explicit => Some(false),
            }
        } else {
            None
        }
    }

    /// Checks if the configuration change leaves a joint configuration.
    ///
    /// This is the case if the ConfChangeV2 is zero, with the possible exception of
    /// the Context field.
    pub fn leave_joint(&self) -> bool {
        self.get_transition() == ConfChangeTransition::Auto && self.changes.is_empty()
    }
}