123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- // 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 confchange
- import (
- "fmt"
- "math/rand"
- "reflect"
- "testing"
- "testing/quick"
- pb "go.etcd.io/etcd/raft/raftpb"
- "go.etcd.io/etcd/raft/tracker"
- )
- // TestConfChangeQuick uses quickcheck to verify that simple and joint config
- // changes arrive at the same result.
- func TestConfChangeQuick(t *testing.T) {
- cfg := &quick.Config{
- MaxCount: 1000,
- }
- // Log the first couple of runs to give some indication of things working
- // as intended.
- const infoCount = 5
- runWithJoint := func(c *Changer, ccs []pb.ConfChangeSingle) error {
- cfg, prs, err := c.EnterJoint(false /* autoLeave */, ccs...)
- if err != nil {
- return err
- }
- // Also do this with autoLeave on, just to check that we'd get the same
- // result.
- cfg2a, prs2a, err := c.EnterJoint(true /* autoLeave */, ccs...)
- if err != nil {
- return err
- }
- cfg2a.AutoLeave = false
- if !reflect.DeepEqual(cfg, cfg2a) || !reflect.DeepEqual(prs, prs2a) {
- return fmt.Errorf("cfg: %+v\ncfg2a: %+v\nprs: %+v\nprs2a: %+v",
- cfg, cfg2a, prs, prs2a)
- }
- c.Tracker.Config = cfg
- c.Tracker.Progress = prs
- cfg2b, prs2b, err := c.LeaveJoint()
- if err != nil {
- return err
- }
- // Reset back to the main branch with autoLeave=false.
- c.Tracker.Config = cfg
- c.Tracker.Progress = prs
- cfg, prs, err = c.LeaveJoint()
- if err != nil {
- return err
- }
- if !reflect.DeepEqual(cfg, cfg2b) || !reflect.DeepEqual(prs, prs2b) {
- return fmt.Errorf("cfg: %+v\ncfg2b: %+v\nprs: %+v\nprs2b: %+v",
- cfg, cfg2b, prs, prs2b)
- }
- c.Tracker.Config = cfg
- c.Tracker.Progress = prs
- return nil
- }
- runWithSimple := func(c *Changer, ccs []pb.ConfChangeSingle) error {
- for _, cc := range ccs {
- cfg, prs, err := c.Simple(cc)
- if err != nil {
- return err
- }
- c.Tracker.Config, c.Tracker.Progress = cfg, prs
- }
- return nil
- }
- type testFunc func(*Changer, []pb.ConfChangeSingle) error
- wrapper := func(invoke testFunc) func(setup initialChanges, ccs confChanges) (*Changer, error) {
- return func(setup initialChanges, ccs confChanges) (*Changer, error) {
- tr := tracker.MakeProgressTracker(10)
- c := &Changer{
- Tracker: tr,
- LastIndex: 10,
- }
- if err := runWithSimple(c, setup); err != nil {
- return nil, err
- }
- err := invoke(c, ccs)
- return c, err
- }
- }
- var n int
- f1 := func(setup initialChanges, ccs confChanges) *Changer {
- c, err := wrapper(runWithSimple)(setup, ccs)
- if err != nil {
- t.Fatal(err)
- }
- if n < infoCount {
- t.Log("initial setup:", Describe(setup...))
- t.Log("changes:", Describe(ccs...))
- t.Log(c.Tracker.Config)
- t.Log(c.Tracker.Progress)
- }
- n++
- return c
- }
- f2 := func(setup initialChanges, ccs confChanges) *Changer {
- c, err := wrapper(runWithJoint)(setup, ccs)
- if err != nil {
- t.Fatal(err)
- }
- return c
- }
- err := quick.CheckEqual(f1, f2, cfg)
- if err == nil {
- return
- }
- cErr, ok := err.(*quick.CheckEqualError)
- if !ok {
- t.Fatal(err)
- }
- t.Error("setup:", Describe(cErr.In[0].([]pb.ConfChangeSingle)...))
- t.Error("ccs:", Describe(cErr.In[1].([]pb.ConfChangeSingle)...))
- t.Errorf("out1: %+v\nout2: %+v", cErr.Out1, cErr.Out2)
- }
- type confChangeTyp pb.ConfChangeType
- func (confChangeTyp) Generate(rand *rand.Rand, _ int) reflect.Value {
- return reflect.ValueOf(confChangeTyp(rand.Intn(4)))
- }
- type confChanges []pb.ConfChangeSingle
- func genCC(num func() int, id func() uint64, typ func() pb.ConfChangeType) []pb.ConfChangeSingle {
- var ccs []pb.ConfChangeSingle
- n := num()
- for i := 0; i < n; i++ {
- ccs = append(ccs, pb.ConfChangeSingle{Type: typ(), NodeID: id()})
- }
- return ccs
- }
- func (confChanges) Generate(rand *rand.Rand, _ int) reflect.Value {
- num := func() int {
- return 1 + rand.Intn(9)
- }
- id := func() uint64 {
- // Note that num() >= 1, so we're never returning 1 from this method,
- // meaning that we'll never touch NodeID one, which is special to avoid
- // voterless configs altogether in this test.
- return 1 + uint64(num())
- }
- typ := func() pb.ConfChangeType {
- return pb.ConfChangeType(rand.Intn(len(pb.ConfChangeType_name)))
- }
- return reflect.ValueOf(genCC(num, id, typ))
- }
- type initialChanges []pb.ConfChangeSingle
- func (initialChanges) Generate(rand *rand.Rand, _ int) reflect.Value {
- num := func() int {
- return 1 + rand.Intn(5)
- }
- id := func() uint64 { return uint64(num()) }
- typ := func() pb.ConfChangeType {
- return pb.ConfChangeAddNode
- }
- // NodeID one is special - it's in the initial config and will be a voter
- // always (this is to avoid uninteresting edge cases where the simple conf
- // changes can't easily make progress).
- ccs := append([]pb.ConfChangeSingle{{Type: pb.ConfChangeAddNode, NodeID: 1}}, genCC(num, id, typ)...)
- return reflect.ValueOf(ccs)
- }
|