blob: a3ddff62fd0dffd2c744df4640d3fd8828df877f [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001// Copyright 2019 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package raftpb
16
17import (
18 "fmt"
19 "strconv"
20 "strings"
21
22 "github.com/gogo/protobuf/proto"
23)
24
25// ConfChangeI abstracts over ConfChangeV2 and (legacy) ConfChange to allow
26// treating them in a unified manner.
27type ConfChangeI interface {
28 AsV2() ConfChangeV2
29 AsV1() (ConfChange, bool)
30}
31
32// MarshalConfChange calls Marshal on the underlying ConfChange or ConfChangeV2
33// and returns the result along with the corresponding EntryType.
34func MarshalConfChange(c ConfChangeI) (EntryType, []byte, error) {
35 var typ EntryType
36 var ccdata []byte
37 var err error
38 if c == nil {
39 // A nil data unmarshals into an empty ConfChangeV2 and has the benefit
40 // that appendEntry can never refuse it based on its size (which
41 // registers as zero).
42 typ = EntryConfChangeV2
43 ccdata = nil
44 } else if ccv1, ok := c.AsV1(); ok {
45 typ = EntryConfChange
46 ccdata, err = ccv1.Marshal()
47 } else {
48 ccv2 := c.AsV2()
49 typ = EntryConfChangeV2
50 ccdata, err = ccv2.Marshal()
51 }
52 return typ, ccdata, err
53}
54
55// AsV2 returns a V2 configuration change carrying out the same operation.
56func (c ConfChange) AsV2() ConfChangeV2 {
57 return ConfChangeV2{
58 Changes: []ConfChangeSingle{{
59 Type: c.Type,
60 NodeID: c.NodeID,
61 }},
62 Context: c.Context,
63 }
64}
65
66// AsV1 returns the ConfChange and true.
67func (c ConfChange) AsV1() (ConfChange, bool) {
68 return c, true
69}
70
71// AsV2 is the identity.
72func (c ConfChangeV2) AsV2() ConfChangeV2 { return c }
73
74// AsV1 returns ConfChange{} and false.
75func (c ConfChangeV2) AsV1() (ConfChange, bool) { return ConfChange{}, false }
76
77// EnterJoint returns two bools. The second bool is true if and only if this
78// config change will use Joint Consensus, which is the case if it contains more
79// than one change or if the use of Joint Consensus was requested explicitly.
80// The first bool can only be true if second one is, and indicates whether the
81// Joint State will be left automatically.
82func (c ConfChangeV2) EnterJoint() (autoLeave bool, ok bool) {
83 // NB: in theory, more config changes could qualify for the "simple"
84 // protocol but it depends on the config on top of which the changes apply.
85 // For example, adding two learners is not OK if both nodes are part of the
86 // base config (i.e. two voters are turned into learners in the process of
87 // applying the conf change). In practice, these distinctions should not
88 // matter, so we keep it simple and use Joint Consensus liberally.
89 if c.Transition != ConfChangeTransitionAuto || len(c.Changes) > 1 {
90 // Use Joint Consensus.
91 var autoLeave bool
92 switch c.Transition {
93 case ConfChangeTransitionAuto:
94 autoLeave = true
95 case ConfChangeTransitionJointImplicit:
96 autoLeave = true
97 case ConfChangeTransitionJointExplicit:
98 default:
99 panic(fmt.Sprintf("unknown transition: %+v", c))
100 }
101 return autoLeave, true
102 }
103 return false, false
104}
105
106// LeaveJoint is true if the configuration change leaves a joint configuration.
107// This is the case if the ConfChangeV2 is zero, with the possible exception of
108// the Context field.
109func (c ConfChangeV2) LeaveJoint() bool {
110 // NB: c is already a copy.
111 c.Context = nil
112 return proto.Equal(&c, &ConfChangeV2{})
113}
114
115// ConfChangesFromString parses a Space-delimited sequence of operations into a
116// slice of ConfChangeSingle. The supported operations are:
117// - vn: make n a voter,
118// - ln: make n a learner,
119// - rn: remove n, and
120// - un: update n.
121func ConfChangesFromString(s string) ([]ConfChangeSingle, error) {
122 var ccs []ConfChangeSingle
123 toks := strings.Split(strings.TrimSpace(s), " ")
124 if toks[0] == "" {
125 toks = nil
126 }
127 for _, tok := range toks {
128 if len(tok) < 2 {
129 return nil, fmt.Errorf("unknown token %s", tok)
130 }
131 var cc ConfChangeSingle
132 switch tok[0] {
133 case 'v':
134 cc.Type = ConfChangeAddNode
135 case 'l':
136 cc.Type = ConfChangeAddLearnerNode
137 case 'r':
138 cc.Type = ConfChangeRemoveNode
139 case 'u':
140 cc.Type = ConfChangeUpdateNode
141 default:
142 return nil, fmt.Errorf("unknown input: %s", tok)
143 }
144 id, err := strconv.ParseUint(tok[1:], 10, 64)
145 if err != nil {
146 return nil, err
147 }
148 cc.NodeID = id
149 ccs = append(ccs, cc)
150 }
151 return ccs, nil
152}
153
154// ConfChangesToString is the inverse to ConfChangesFromString.
155func ConfChangesToString(ccs []ConfChangeSingle) string {
156 var buf strings.Builder
157 for i, cc := range ccs {
158 if i > 0 {
159 buf.WriteByte(' ')
160 }
161 switch cc.Type {
162 case ConfChangeAddNode:
163 buf.WriteByte('v')
164 case ConfChangeAddLearnerNode:
165 buf.WriteByte('l')
166 case ConfChangeRemoveNode:
167 buf.WriteByte('r')
168 case ConfChangeUpdateNode:
169 buf.WriteByte('u')
170 default:
171 buf.WriteString("unknown")
172 }
173 fmt.Fprintf(&buf, "%d", cc.NodeID)
174 }
175 return buf.String()
176}