confchange.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // 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. package raftpb
  15. import (
  16. "fmt"
  17. "strconv"
  18. "strings"
  19. "github.com/gogo/protobuf/proto"
  20. )
  21. // ConfChangeI abstracts over ConfChangeV2 and (legacy) ConfChange to allow
  22. // treating them in a unified manner.
  23. type ConfChangeI interface {
  24. AsV2() ConfChangeV2
  25. AsV1() (ConfChange, bool)
  26. }
  27. // MarshalConfChange calls Marshal on the underlying ConfChange or ConfChangeV2
  28. // and returns the result along with the corresponding EntryType.
  29. func MarshalConfChange(c ConfChangeI) (EntryType, []byte, error) {
  30. var typ EntryType
  31. var ccdata []byte
  32. var err error
  33. if ccv1, ok := c.AsV1(); ok {
  34. typ = EntryConfChange
  35. ccdata, err = ccv1.Marshal()
  36. } else {
  37. ccv2 := c.AsV2()
  38. typ = EntryConfChangeV2
  39. ccdata, err = ccv2.Marshal()
  40. }
  41. return typ, ccdata, err
  42. }
  43. // AsV2 returns a V2 configuration change carrying out the same operation.
  44. func (c ConfChange) AsV2() ConfChangeV2 {
  45. return ConfChangeV2{
  46. Changes: []ConfChangeSingle{{
  47. Type: c.Type,
  48. NodeID: c.NodeID,
  49. }},
  50. Context: c.Context,
  51. }
  52. }
  53. // AsV1 returns the ConfChange and true.
  54. func (c ConfChange) AsV1() (ConfChange, bool) {
  55. return c, true
  56. }
  57. // AsV2 is the identity.
  58. func (c ConfChangeV2) AsV2() ConfChangeV2 { return c }
  59. // AsV1 returns ConfChange{} and false.
  60. func (c ConfChangeV2) AsV1() (ConfChange, bool) { return ConfChange{}, false }
  61. // EnterJoint returns two bools. The second bool is true if and only if this
  62. // config change will use Joint Consensus, which is the case if it contains more
  63. // than one change or if the use of Joint Consensus was requested explicitly.
  64. // The first bool can only be true if second one is, and indicates whether the
  65. // Joint State will be left automatically.
  66. func (c *ConfChangeV2) EnterJoint() (autoLeave bool, ok bool) {
  67. // NB: in theory, more config changes could qualify for the "simple"
  68. // protocol but it depends on the config on top of which the changes apply.
  69. // For example, adding two learners is not OK if both nodes are part of the
  70. // base config (i.e. two voters are turned into learners in the process of
  71. // applying the conf change). In practice, these distinctions should not
  72. // matter, so we keep it simple and use Joint Consensus liberally.
  73. if c.Transition != ConfChangeTransitionAuto || len(c.Changes) > 1 {
  74. // Use Joint Consensus.
  75. var autoLeave bool
  76. switch c.Transition {
  77. case ConfChangeTransitionAuto:
  78. autoLeave = true
  79. case ConfChangeTransitionJointImplicit:
  80. autoLeave = true
  81. case ConfChangeTransitionJointExplicit:
  82. default:
  83. panic(fmt.Sprintf("unknown transition: %+v", c))
  84. }
  85. return autoLeave, true
  86. }
  87. return false, false
  88. }
  89. // LeaveJoint is true if the configuration change leaves a joint configuration.
  90. // This is the case if the ConfChangeV2 is zero, with the possible exception of
  91. // the Context field.
  92. func (c *ConfChangeV2) LeaveJoint() bool {
  93. cpy := *c
  94. cpy.Context = nil
  95. return proto.Equal(&cpy, &ConfChangeV2{})
  96. }
  97. // ConfChangesFromString parses a Space-delimited sequence of operations into a
  98. // slice of ConfChangeSingle. The supported operations are:
  99. // - vn: make n a voter,
  100. // - ln: make n a learner,
  101. // - rn: remove n, and
  102. // - un: update n.
  103. func ConfChangesFromString(s string) ([]ConfChangeSingle, error) {
  104. var ccs []ConfChangeSingle
  105. toks := strings.Split(strings.TrimSpace(s), " ")
  106. if toks[0] == "" {
  107. toks = nil
  108. }
  109. for _, tok := range toks {
  110. if len(tok) < 2 {
  111. return nil, fmt.Errorf("unknown token %s", tok)
  112. }
  113. var cc ConfChangeSingle
  114. switch tok[0] {
  115. case 'v':
  116. cc.Type = ConfChangeAddNode
  117. case 'l':
  118. cc.Type = ConfChangeAddLearnerNode
  119. case 'r':
  120. cc.Type = ConfChangeRemoveNode
  121. case 'u':
  122. cc.Type = ConfChangeUpdateNode
  123. default:
  124. return nil, fmt.Errorf("unknown input: %s", tok)
  125. }
  126. id, err := strconv.ParseUint(tok[1:], 10, 64)
  127. if err != nil {
  128. return nil, err
  129. }
  130. cc.NodeID = id
  131. ccs = append(ccs, cc)
  132. }
  133. return ccs, nil
  134. }
  135. // ConfChangesToString is the inverse to ConfChangesFromString.
  136. func ConfChangesToString(ccs []ConfChangeSingle) string {
  137. var buf strings.Builder
  138. for i, cc := range ccs {
  139. if i > 0 {
  140. buf.WriteByte(' ')
  141. }
  142. switch cc.Type {
  143. case ConfChangeAddNode:
  144. buf.WriteByte('v')
  145. case ConfChangeAddLearnerNode:
  146. buf.WriteByte('l')
  147. case ConfChangeRemoveNode:
  148. buf.WriteByte('r')
  149. case ConfChangeUpdateNode:
  150. buf.WriteByte('u')
  151. default:
  152. buf.WriteString("unknown")
  153. }
  154. fmt.Fprintf(&buf, "%d", cc.NodeID)
  155. }
  156. return buf.String()
  157. }