123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- // Copyright 2019 The etcd Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package confchange
- import (
- pb "go.etcd.io/etcd/raft/raftpb"
- "go.etcd.io/etcd/raft/tracker"
- )
- // toConfChangeSingle 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.
- func toConfChangeSingle(cs pb.ConfState) (out []pb.ConfChangeSingle, in []pb.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) 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.
- for _, id := range cs.VotersOutgoing {
- // If there are outgoing voters, first add them one by one so that the
- // (non-joint) config has them all.
- out = append(out, pb.ConfChangeSingle{
- Type: pb.ConfChangeAddNode,
- NodeID: id,
- })
- }
- // 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 := range cs.VotersOutgoing {
- in = append(in, pb.ConfChangeSingle{
- Type: pb.ConfChangeRemoveNode,
- NodeID: id,
- })
- }
- // Then we'll add the incoming voters and learners.
- for _, id := range cs.Voters {
- in = append(in, pb.ConfChangeSingle{
- Type: pb.ConfChangeAddNode,
- NodeID: id,
- })
- }
- for _, id := range cs.Learners {
- in = append(in, pb.ConfChangeSingle{
- Type: pb.ConfChangeAddLearnerNode,
- NodeID: id,
- })
- }
- // Same for LearnersNext; these are nodes we want to be learners but which
- // are currently voters in the outgoing config.
- for _, id := range cs.LearnersNext {
- in = append(in, pb.ConfChangeSingle{
- Type: pb.ConfChangeAddLearnerNode,
- NodeID: id,
- })
- }
- return out, in
- }
- func chain(chg Changer, ops ...func(Changer) (tracker.Config, tracker.ProgressMap, error)) (tracker.Config, tracker.ProgressMap, error) {
- for _, op := range ops {
- cfg, prs, err := op(chg)
- if err != nil {
- return tracker.Config{}, nil, err
- }
- chg.Tracker.Config = cfg
- chg.Tracker.Progress = prs
- }
- return chg.Tracker.Config, chg.Tracker.Progress, nil
- }
- // Restore takes a Changer (which must represent an empty configuration), and
- // runs a sequence of changes enacting the configuration described in the
- // ConfState.
- //
- // TODO(tbg) it's silly that this takes a Changer. Unravel this by making sure
- // the Changer only needs a ProgressMap (not a whole Tracker) at which point
- // this can just take LastIndex and MaxInflight directly instead and cook up
- // the results from that alone.
- func Restore(chg Changer, cs pb.ConfState) (tracker.Config, tracker.ProgressMap, error) {
- outgoing, incoming := toConfChangeSingle(cs)
- var ops []func(Changer) (tracker.Config, tracker.ProgressMap, error)
- if len(outgoing) == 0 {
- // No outgoing config, so just apply the incoming changes one by one.
- for _, cc := range incoming {
- cc := cc // loop-local copy
- ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
- return chg.Simple(cc)
- })
- }
- } else {
- // The ConfState describes a joint configuration.
- //
- // First, apply all of the changes of the outgoing config one by one, so
- // that it temporarily becomes the incoming active config. For example,
- // if the config is (1 2 3)&(2 3 4), this will establish (2 3 4)&().
- for _, cc := range outgoing {
- cc := cc // loop-local copy
- ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
- return chg.Simple(cc)
- })
- }
- // Now enter the joint state, which rotates the above additions into the
- // outgoing config, and adds the incoming config in. Continuing the
- // example above, we'd get (1 2 3)&(2 3 4), i.e. the incoming operations
- // would be removing 2,3,4 and then adding in 1,2,3 while transitioning
- // into a joint state.
- ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
- return chg.EnterJoint(cs.AutoLeave, incoming...)
- })
- }
- return chain(chg, ops...)
- }
|