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 15package rafttest 16 17import ( 18 "bufio" 19 "fmt" 20 "strings" 21 "testing" 22 23 "github.com/cockroachdb/datadriven" 24 "go.etcd.io/etcd/raft/raftpb" 25) 26 27func (env *InteractionEnv) handleStabilize(t *testing.T, d datadriven.TestData) error { 28 var idxs []int 29 for _, id := range ints(t, d) { 30 idxs = append(idxs, id-1) 31 } 32 return env.Stabilize(idxs...) 33} 34 35// Stabilize repeatedly runs Ready handling on and message delivery to the set 36// of nodes specified via the idxs slice until reaching a fixed point. 37func (env *InteractionEnv) Stabilize(idxs ...int) error { 38 var nodes []Node 39 for _, idx := range idxs { 40 nodes = append(nodes, env.Nodes[idx]) 41 } 42 if len(nodes) == 0 { 43 nodes = env.Nodes 44 } 45 46 withIndent := func(f func()) { 47 orig := env.Output.Builder 48 env.Output.Builder = &strings.Builder{} 49 f() 50 51 scanner := bufio.NewScanner(strings.NewReader(env.Output.Builder.String())) 52 for scanner.Scan() { 53 orig.WriteString(" " + scanner.Text() + "\n") 54 } 55 env.Output.Builder = orig 56 } 57 58 for { 59 done := true 60 for _, rn := range nodes { 61 if rn.HasReady() { 62 done = false 63 idx := int(rn.Status().ID - 1) 64 fmt.Fprintf(env.Output, "> %d handling Ready\n", idx+1) 65 withIndent(func() { env.ProcessReady(idx) }) 66 } 67 } 68 for _, rn := range nodes { 69 id := rn.Status().ID 70 // NB: we grab the messages just to see whether to print the header. 71 // DeliverMsgs will do it again. 72 if msgs, _ := splitMsgs(env.Messages, id); len(msgs) > 0 { 73 fmt.Fprintf(env.Output, "> %d receiving messages\n", id) 74 withIndent(func() { env.DeliverMsgs(Recipient{ID: id}) }) 75 done = false 76 } 77 } 78 if done { 79 return nil 80 } 81 } 82} 83 84func splitMsgs(msgs []raftpb.Message, to uint64) (toMsgs []raftpb.Message, rmdr []raftpb.Message) { 85 // NB: this method does not reorder messages. 86 for _, msg := range msgs { 87 if msg.To == to { 88 toMsgs = append(toMsgs, msg) 89 } else { 90 rmdr = append(rmdr, msg) 91 } 92 } 93 return toMsgs, rmdr 94} 95