restore.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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 confchange
  15. import (
  16. pb "go.etcd.io/etcd/raft/raftpb"
  17. "go.etcd.io/etcd/raft/tracker"
  18. )
  19. // toConfChangeSingle translates a conf state into 1) a slice of operations creating
  20. // first the config that will become the outgoing one, and then the incoming one, and
  21. // b) another slice that, when applied to the config resulted from 1), represents the
  22. // ConfState.
  23. func toConfChangeSingle(cs pb.ConfState) (out []pb.ConfChangeSingle, in []pb.ConfChangeSingle) {
  24. // Example to follow along this code:
  25. // voters=(1 2 3) learners=(5) outgoing=(1 2 4 6) learners_next=(4)
  26. //
  27. // This means that before entering the joint config, the configuration
  28. // had voters (1 2 4) and perhaps some learners that are already gone.
  29. // The new set of voters is (1 2 3), i.e. (1 2) were kept around, and (4 6)
  30. // are no longer voters; however 4 is poised to become a learner upon leaving
  31. // the joint state.
  32. // We can't tell whether 5 was a learner before entering the joint config,
  33. // but it doesn't matter (we'll pretend that it wasn't).
  34. //
  35. // The code below will construct
  36. // outgoing = add 1; add 2; add 4; add 6
  37. // incoming = remove 1; remove 2; remove 4; remove 6
  38. // add 1; add 2; add 3;
  39. // add-learner 5;
  40. // add-learner 4;
  41. //
  42. // So, when starting with an empty config, after applying 'outgoing' we have
  43. //
  44. // quorum=(1 2 4 6)
  45. //
  46. // From which we enter a joint state via 'incoming'
  47. //
  48. // quorum=(1 2 3)&&(1 2 4 6) learners=(5) learners_next=(4)
  49. //
  50. // as desired.
  51. for _, id := range cs.VotersOutgoing {
  52. // If there are outgoing voters, first add them one by one so that the
  53. // (non-joint) config has them all.
  54. out = append(out, pb.ConfChangeSingle{
  55. Type: pb.ConfChangeAddNode,
  56. NodeID: id,
  57. })
  58. }
  59. // We're done constructing the outgoing slice, now on to the incoming one
  60. // (which will apply on top of the config created by the outgoing slice).
  61. // First, we'll remove all of the outgoing voters.
  62. for _, id := range cs.VotersOutgoing {
  63. in = append(in, pb.ConfChangeSingle{
  64. Type: pb.ConfChangeRemoveNode,
  65. NodeID: id,
  66. })
  67. }
  68. // Then we'll add the incoming voters and learners.
  69. for _, id := range cs.Voters {
  70. in = append(in, pb.ConfChangeSingle{
  71. Type: pb.ConfChangeAddNode,
  72. NodeID: id,
  73. })
  74. }
  75. for _, id := range cs.Learners {
  76. in = append(in, pb.ConfChangeSingle{
  77. Type: pb.ConfChangeAddLearnerNode,
  78. NodeID: id,
  79. })
  80. }
  81. // Same for LearnersNext; these are nodes we want to be learners but which
  82. // are currently voters in the outgoing config.
  83. for _, id := range cs.LearnersNext {
  84. in = append(in, pb.ConfChangeSingle{
  85. Type: pb.ConfChangeAddLearnerNode,
  86. NodeID: id,
  87. })
  88. }
  89. return out, in
  90. }
  91. func chain(chg Changer, ops ...func(Changer) (tracker.Config, tracker.ProgressMap, error)) (tracker.Config, tracker.ProgressMap, error) {
  92. for _, op := range ops {
  93. cfg, prs, err := op(chg)
  94. if err != nil {
  95. return tracker.Config{}, nil, err
  96. }
  97. chg.Tracker.Config = cfg
  98. chg.Tracker.Progress = prs
  99. }
  100. return chg.Tracker.Config, chg.Tracker.Progress, nil
  101. }
  102. // Restore takes a Changer (which must represent an empty configuration), and
  103. // runs a sequence of changes enacting the configuration described in the
  104. // ConfState.
  105. //
  106. // TODO(tbg) it's silly that this takes a Changer. Unravel this by making sure
  107. // the Changer only needs a ProgressMap (not a whole Tracker) at which point
  108. // this can just take LastIndex and MaxInflight directly instead and cook up
  109. // the results from that alone.
  110. func Restore(chg Changer, cs pb.ConfState) (tracker.Config, tracker.ProgressMap, error) {
  111. outgoing, incoming := toConfChangeSingle(cs)
  112. var ops []func(Changer) (tracker.Config, tracker.ProgressMap, error)
  113. if len(outgoing) == 0 {
  114. // No outgoing config, so just apply the incoming changes one by one.
  115. for _, cc := range incoming {
  116. cc := cc // loop-local copy
  117. ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
  118. return chg.Simple(cc)
  119. })
  120. }
  121. } else {
  122. // The ConfState describes a joint configuration.
  123. //
  124. // First, apply all of the changes of the outgoing config one by one, so
  125. // that it temporarily becomes the incoming active config. For example,
  126. // if the config is (1 2 3)&(2 3 4), this will establish (2 3 4)&().
  127. for _, cc := range outgoing {
  128. cc := cc // loop-local copy
  129. ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
  130. return chg.Simple(cc)
  131. })
  132. }
  133. // Now enter the joint state, which rotates the above additions into the
  134. // outgoing config, and adds the incoming config in. Continuing the
  135. // example above, we'd get (1 2 3)&(2 3 4), i.e. the incoming operations
  136. // would be removing 2,3,4 and then adding in 1,2,3 while transitioning
  137. // into a joint state.
  138. ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
  139. return chg.EnterJoint(cs.AutoLeave, incoming...)
  140. })
  141. }
  142. return chain(chg, ops...)
  143. }