mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00

When a leader removes itself, it will retain its leadership but not accept new proposals, making the range effectively stuck until manual intervention triggers a campaign event. This commit documents the behavior. It does not correct it yet.
177 lines
4.1 KiB
Go
177 lines
4.1 KiB
Go
// 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 rafttest
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/cockroachdb/datadriven"
|
|
)
|
|
|
|
// Handle is the entrypoint for data-driven interaction testing. Commands and
|
|
// parameters are parsed from the supplied TestData. Errors during data parsing
|
|
// are reported via the supplied *testing.T; errors from the raft nodes and the
|
|
// storage engine are reported to the output buffer.
|
|
func (env *InteractionEnv) Handle(t *testing.T, d datadriven.TestData) string {
|
|
env.Output.Reset()
|
|
var err error
|
|
switch d.Cmd {
|
|
case "_breakpoint":
|
|
// This is a helper case to attach a debugger to when a problem needs
|
|
// to be investigated in a longer test file. In such a case, add the
|
|
// following stanza immediately before the interesting behavior starts:
|
|
//
|
|
// _breakpoint:
|
|
// ----
|
|
// ok
|
|
//
|
|
// and set a breakpoint on the `case` above.
|
|
case "add-nodes":
|
|
// Example:
|
|
//
|
|
// add-nodes <number-of-nodes-to-add> voters=(1 2 3) learners=(4 5) index=2 content=foo
|
|
err = env.handleAddNodes(t, d)
|
|
case "campaign":
|
|
// Example:
|
|
//
|
|
// campaign <id-of-candidate>
|
|
err = env.handleCampaign(t, d)
|
|
case "compact":
|
|
// Example:
|
|
//
|
|
// compact <id> <new-first-index>
|
|
err = env.handleCompact(t, d)
|
|
case "deliver-msgs":
|
|
// Deliver the messages for a given recipient.
|
|
//
|
|
// Example:
|
|
//
|
|
// deliver-msgs <idx>
|
|
err = env.handleDeliverMsgs(t, d)
|
|
case "process-ready":
|
|
// Example:
|
|
//
|
|
// process-ready 3
|
|
err = env.handleProcessReady(t, d)
|
|
case "log-level":
|
|
// Set the log level. NONE disables all output, including from the test
|
|
// harness (except errors).
|
|
//
|
|
// Example:
|
|
//
|
|
// log-level WARN
|
|
err = env.handleLogLevel(t, d)
|
|
case "raft-log":
|
|
// Print the Raft log.
|
|
//
|
|
// Example:
|
|
//
|
|
// raft-log 3
|
|
err = env.handleRaftLog(t, d)
|
|
case "stabilize":
|
|
// Deliver messages to and run process-ready on the set of IDs until
|
|
// no more work is to be done.
|
|
//
|
|
// Example:
|
|
//
|
|
// stabilize 1 4
|
|
err = env.handleStabilize(t, d)
|
|
case "status":
|
|
// Print Raft status.
|
|
//
|
|
// Example:
|
|
//
|
|
// status 5
|
|
err = env.handleStatus(t, d)
|
|
case "tick-heartbeat":
|
|
// Tick a heartbeat interval.
|
|
//
|
|
// Example:
|
|
//
|
|
// tick-heartbeat 3
|
|
err = env.handleTickHeartbeat(t, d)
|
|
case "propose":
|
|
// Propose an entry.
|
|
//
|
|
// Example:
|
|
//
|
|
// propose 1 foo
|
|
err = env.handlePropose(t, d)
|
|
case "propose-conf-change":
|
|
// Propose a configuration change.
|
|
//
|
|
// Example:
|
|
//
|
|
// propose-conf-change transition=explicit
|
|
// v1 v3 l4 r5
|
|
//
|
|
// Example:
|
|
//
|
|
// propose-conf-change v1=true
|
|
// v5
|
|
err = env.handleProposeConfChange(t, d)
|
|
default:
|
|
err = fmt.Errorf("unknown command")
|
|
}
|
|
if err != nil {
|
|
env.Output.WriteString(err.Error())
|
|
}
|
|
// NB: the highest log level suppresses all output, including that of the
|
|
// handlers. This comes in useful during setup which can be chatty.
|
|
// However, errors are always logged.
|
|
if env.Output.Len() == 0 {
|
|
return "ok"
|
|
}
|
|
if env.Output.Lvl == len(lvlNames)-1 {
|
|
if err != nil {
|
|
return err.Error()
|
|
}
|
|
return "ok (quiet)"
|
|
}
|
|
return env.Output.String()
|
|
}
|
|
|
|
func firstAsInt(t *testing.T, d datadriven.TestData) int {
|
|
t.Helper()
|
|
n, err := strconv.Atoi(d.CmdArgs[0].Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return n
|
|
}
|
|
|
|
func firstAsNodeIdx(t *testing.T, d datadriven.TestData) int {
|
|
t.Helper()
|
|
n := firstAsInt(t, d)
|
|
return n - 1
|
|
}
|
|
|
|
func ints(t *testing.T, d datadriven.TestData) []int {
|
|
var ints []int
|
|
for i := 0; i < len(d.CmdArgs); i++ {
|
|
if len(d.CmdArgs[i].Vals) != 0 {
|
|
continue
|
|
}
|
|
n, err := strconv.Atoi(d.CmdArgs[i].Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ints = append(ints, n)
|
|
}
|
|
return ints
|
|
}
|