mirror of
https://github.com/cubefs/cubefs.git
synced 2026-08-02 02:00:56 +00:00
fix(common): update kvstorev2
with: #22409822 of #22357426 Signed-off-by: xiejian <xiejian3@oppo.com>
This commit is contained in:
parent
b7cade30e6
commit
75bc515d0b
321
blobstore/common/kvstorev2/kvstore.go
Normal file
321
blobstore/common/kvstorev2/kvstore.go
Normal file
@ -0,0 +1,321 @@
|
||||
// Copyright 2023 The Cuber 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 kvstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCF = "default"
|
||||
|
||||
RocksdbLsmKVType = LsmKVType("rocksdb")
|
||||
|
||||
FIFOStyle = CompactionStyle("fifo")
|
||||
LevelStyle = CompactionStyle("level")
|
||||
UniversalStyle = CompactionStyle("universal")
|
||||
|
||||
defaultReadConcurrency = 10
|
||||
defaultReadQueueLen = 10
|
||||
defaultWriteConcurrency = 4
|
||||
defaultWriteQueueLen = 10
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("key not found")
|
||||
ErrKVTypeNotFound = errors.New("kv type not found")
|
||||
)
|
||||
|
||||
type (
|
||||
CF string
|
||||
LsmKVType string
|
||||
CompactionStyle string
|
||||
|
||||
Store interface {
|
||||
NewSnapshot() Snapshot
|
||||
CreateColumn(col CF) error
|
||||
GetAllColumns() []CF
|
||||
CheckColumns(col CF) bool
|
||||
Get(ctx context.Context, col CF, key []byte, opts ...ReadOptFunc) (value ValueGetter, err error)
|
||||
GetRaw(ctx context.Context, col CF, key []byte, opts ...ReadOptFunc) (value []byte, err error)
|
||||
MultiGet(ctx context.Context, col CF, keys [][]byte, opts ...ReadOptFunc) (values []ValueGetter, err error)
|
||||
SetRaw(ctx context.Context, col CF, key []byte, value []byte, opts ...WriteOptFunc) error
|
||||
Delete(ctx context.Context, col CF, key []byte, opts ...WriteOptFunc) error
|
||||
DeleteRange(ctx context.Context, col CF, start, end []byte, opts ...WriteOptFunc) error
|
||||
List(ctx context.Context, col CF, prefix []byte, marker []byte, readOpt ReadOption) ListReader
|
||||
Write(ctx context.Context, batch WriteBatch, opts ...WriteOptFunc) error
|
||||
Read(ctx context.Context, cols []CF, keys [][]byte, opts ...ReadOptFunc) (values []ValueGetter, err error)
|
||||
GetOptionHelper() (helper OptionHelper)
|
||||
NewReadOption() (readOption ReadOption)
|
||||
NewWriteOption() (writeOption WriteOption)
|
||||
NewWriteBatch() (writeBatch WriteBatch)
|
||||
FlushCF(ctx context.Context, col CF) error
|
||||
Stats(ctx context.Context) (Stats, error)
|
||||
Close()
|
||||
}
|
||||
OptionHelper interface {
|
||||
GetOption() Option
|
||||
SetMaxBackgroundJobs(value int) error
|
||||
SetMaxBackgroundCompactions(value int) error
|
||||
SetMaxSubCompactions(value int) error
|
||||
SetMaxOpenFiles(value int) error
|
||||
SetMaxWriteBufferNumber(value int) error
|
||||
SetWriteBufferSize(size int) error
|
||||
SetArenaBlockSize(size int) error
|
||||
SetTargetFileSizeBase(value uint64) error
|
||||
SetMaxBytesForLevelBase(value uint64) error
|
||||
SetLevel0SlowdownWritesTrigger(value int) error
|
||||
SetLevel0StopWritesTrigger(value int) error
|
||||
SetSoftPendingCompactionBytesLimit(value uint64) error
|
||||
SetHardPendingCompactionBytesLimit(value uint64) error
|
||||
SetBlockSize(size int) error
|
||||
SetFIFOCompactionMaxTableFileSize(size int) error
|
||||
SetFIFOCompactionAllow(value bool) error
|
||||
}
|
||||
ReadOption interface {
|
||||
SetSnapShot(snap Snapshot)
|
||||
Close()
|
||||
}
|
||||
ReadOptFunc func(opts *readOpts)
|
||||
|
||||
WriteOption interface {
|
||||
SetSync(value bool)
|
||||
DisableWAL(value bool)
|
||||
Close()
|
||||
}
|
||||
WriteOptFunc func(*writeOpts)
|
||||
|
||||
LruCache interface {
|
||||
GetUsage() uint64
|
||||
GetPinnedUsage() uint64
|
||||
Close()
|
||||
}
|
||||
WriteBufferManager interface {
|
||||
Close()
|
||||
}
|
||||
RateLimiter interface {
|
||||
SetBytesPerSec(value int64)
|
||||
Close()
|
||||
}
|
||||
ListReader interface {
|
||||
ReadNext() (key KeyGetter, val ValueGetter, err error)
|
||||
ReadNextCopy() (key []byte, value []byte, err error)
|
||||
ReadPrev() (key KeyGetter, val ValueGetter, err error)
|
||||
ReadPrevCopy() (key []byte, value []byte, err error)
|
||||
ReadLast() (key KeyGetter, val ValueGetter, err error)
|
||||
SeekToLast()
|
||||
SeekForPrev(key []byte) (err error)
|
||||
Seek(key []byte)
|
||||
SetFilterKey(key []byte)
|
||||
Close()
|
||||
}
|
||||
KeyGetter interface {
|
||||
Key() []byte
|
||||
Close()
|
||||
}
|
||||
ValueGetter interface {
|
||||
Value() []byte
|
||||
Read([]byte) (n int, err error)
|
||||
Size() int
|
||||
Close()
|
||||
}
|
||||
Snapshot interface {
|
||||
Close()
|
||||
}
|
||||
Env interface {
|
||||
SetLowPriorityBackgroundThreads(n int)
|
||||
SetHighPriorityBackgroundThreads(n int)
|
||||
Close()
|
||||
}
|
||||
SstFileManager interface {
|
||||
Close()
|
||||
}
|
||||
WriteBatch interface {
|
||||
Put(col CF, key, value []byte)
|
||||
Delete(col CF, key []byte)
|
||||
DeleteRange(col CF, startKey, endKey []byte)
|
||||
Data() []byte
|
||||
From(data []byte)
|
||||
Count() int
|
||||
Clear()
|
||||
Close()
|
||||
// Iterator()
|
||||
}
|
||||
|
||||
Stats struct {
|
||||
Used uint64
|
||||
MemoryUsage MemoryUsage
|
||||
Level0FileNum uint64
|
||||
WriteSlowdown bool
|
||||
WriteStop bool
|
||||
RunningFlush uint64
|
||||
PendingFlush bool
|
||||
RunningCompaction uint64
|
||||
PendingCompaction bool
|
||||
BackgroundErrors uint64
|
||||
}
|
||||
MemoryUsage struct {
|
||||
BlockCacheUsage uint64
|
||||
IndexAndFilterUsage uint64
|
||||
MemtableUsage uint64
|
||||
BlockPinnedUsage uint64
|
||||
Total uint64
|
||||
}
|
||||
Option struct {
|
||||
Sync bool `json:"sync,omitempty"`
|
||||
DisableWal bool `json:"disable_wal,omitempty"`
|
||||
ColumnFamily []CF `json:"column_family,omitempty"`
|
||||
CreateIfMissing bool `json:"create_if_missingC"`
|
||||
BlockSize int `json:"block_size,omitempty"`
|
||||
BlockCache uint64 `json:"block_cache,omitempty"`
|
||||
EnablePipelinedWrite bool `json:"enable_pipelined_write,omitempty"`
|
||||
MaxBackgroundJobs int `json:"max_background_jobs,omitempty"`
|
||||
MaxBackgroundCompactions int `json:"max_background_compactions,omitempty"`
|
||||
MaxBackgroundFlushes int `json:"max_background_flushes,omitempty"`
|
||||
MaxSubCompactions int `json:"max_sub_compactions,omitempty"`
|
||||
LevelCompactionDynamicLevelBytes bool `json:"level_compaction_dynamic_level_bytes,omitempty"`
|
||||
MaxOpenFiles int `json:"max_open_files,omitempty"`
|
||||
MinWriteBufferNumberToMerge int `json:"min_write_buffer_number_to_merge,omitempty"`
|
||||
MaxWriteBufferNumber int `json:"max_write_buffer_number,omitempty"`
|
||||
WriteBufferSize int `json:"write_buffer_size,omitempty"`
|
||||
ArenaBlockSize int `json:"arena_block_size,omitempty"`
|
||||
TargetFileSizeBase uint64 `json:"target_file_size_base,omitempty"`
|
||||
MaxBytesForLevelBase uint64 `json:"max_bytes_for_level_base,omitempty"`
|
||||
KeepLogFileNum int `json:"keep_log_file_num,omitempty"`
|
||||
MaxLogFileSize int `json:"max_log_file_size,omitempty"`
|
||||
Level0SlowdownWritesTrigger int `json:"level_0_slowdown_writes_trigger,omitempty"`
|
||||
Level0StopWritesTrigger int `json:"level_0_stop_writes_trigger,omitempty"`
|
||||
SoftPendingCompactionBytesLimit uint64 `json:"soft_pending_compaction_bytes_limit,omitempty"`
|
||||
HardPendingCompactionBytesLimit uint64 `json:"hard_pending_compaction_bytes_limit,omitempty"`
|
||||
MaxWalLogSize uint64 `json:"max_wal_log_size,omitempty"`
|
||||
CompactionStyle CompactionStyle `json:"compaction_style,omitempty"`
|
||||
CompactionOptionFIFO CompactionOptionFIFO `json:"compaction_option_fifo,omitempty"`
|
||||
|
||||
Cache LruCache
|
||||
WriteBufferManager WriteBufferManager
|
||||
Env Env
|
||||
SstFileManager SstFileManager
|
||||
HandleError HandleError
|
||||
|
||||
ReadConcurrency int `json:"read_concurrency,omitempty"`
|
||||
ReadQueueLen int `json:"read_queue_len,omitempty"`
|
||||
WriteConcurrency int `json:"write_concurrency,omitempty"`
|
||||
WriteQueueLen int `json:"write_queue_len,omitempty"`
|
||||
}
|
||||
CompactionOptionFIFO struct {
|
||||
MaxTableFileSize int `json:"max_table_file_size,omitempty"`
|
||||
AllowCompaction bool `json:"allow_compaction,omitempty"`
|
||||
}
|
||||
HandleError func(err error)
|
||||
|
||||
readOpts struct {
|
||||
opt ReadOption
|
||||
withNoMerge bool
|
||||
}
|
||||
writeOpts struct {
|
||||
opt WriteOption
|
||||
withNoMerge bool
|
||||
}
|
||||
)
|
||||
|
||||
func NewKVStore(ctx context.Context, path string, lsmType LsmKVType, option *Option) (Store, error) {
|
||||
switch lsmType {
|
||||
case RocksdbLsmKVType:
|
||||
return newRocksdb(ctx, path, option)
|
||||
default:
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
}
|
||||
|
||||
func NewCache(ctx context.Context, lsmType LsmKVType, size uint64) LruCache {
|
||||
switch lsmType {
|
||||
case RocksdbLsmKVType:
|
||||
return newRocksdbLruCache(ctx, size)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewWriteBufferManager(ctx context.Context, lsmType LsmKVType, size uint64) WriteBufferManager {
|
||||
switch lsmType {
|
||||
case RocksdbLsmKVType:
|
||||
return newRocksdbWriteBufferManager(ctx, size)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewEnv(ctx context.Context, lsmType LsmKVType) Env {
|
||||
switch lsmType {
|
||||
case RocksdbLsmKVType:
|
||||
return newRocksdbEnv(ctx)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewSstFileManager(ctx context.Context, lsmType LsmKVType, env Env) SstFileManager {
|
||||
switch lsmType {
|
||||
case RocksdbLsmKVType:
|
||||
return newRocksdbSstFileManager(ctx, env)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithReadOption(opt ReadOption) ReadOptFunc {
|
||||
return func(ro *readOpts) {
|
||||
ro.opt = opt
|
||||
}
|
||||
}
|
||||
|
||||
func WithNoMergeRead() ReadOptFunc {
|
||||
return func(ro *readOpts) {
|
||||
ro.withNoMerge = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithWriteOption(opt WriteOption) WriteOptFunc {
|
||||
return func(wo *writeOpts) {
|
||||
wo.opt = opt
|
||||
}
|
||||
}
|
||||
|
||||
func WithNoMergeWrite() WriteOptFunc {
|
||||
return func(wo *writeOpts) {
|
||||
wo.withNoMerge = true
|
||||
}
|
||||
}
|
||||
|
||||
func (cf CF) String() string {
|
||||
return string(cf)
|
||||
}
|
||||
|
||||
func (ro *readOpts) applyOptions(opts []ReadOptFunc) {
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(ro)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wo *writeOpts) applyOptions(opts []WriteOptFunc) {
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(wo)
|
||||
}
|
||||
}
|
||||
}
|
||||
1428
blobstore/common/kvstorev2/kvstore_mock.go
Normal file
1428
blobstore/common/kvstorev2/kvstore_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
64
blobstore/common/kvstorev2/kvstore_test.go
Normal file
64
blobstore/common/kvstorev2/kvstore_test.go
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright 2023 The Cuber 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 kvstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
//go:generate mockgen -source=./kvstore.go -destination=./kvstore_mock.go -package=kvstore
|
||||
|
||||
func TestNewEngine(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
path, err := genTmpPath()
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(path)
|
||||
opt := new(Option)
|
||||
opt.CreateIfMissing = true
|
||||
eg, err := NewKVStore(ctx, path, RocksdbLsmKVType, opt)
|
||||
require.NoError(t, err)
|
||||
eg.Close()
|
||||
}
|
||||
|
||||
func TestNewWriteBufferManager(t *testing.T) {
|
||||
mgr1 := NewWriteBufferManager(context.TODO(), RocksdbLsmKVType, 1<<10)
|
||||
mgr1.Close()
|
||||
|
||||
mgr2 := NewWriteBufferManager(context.TODO(), "", 1<<10)
|
||||
require.Equal(t, nil, mgr2)
|
||||
}
|
||||
|
||||
func TestNewCache(t *testing.T) {
|
||||
cache1 := NewCache(context.TODO(), RocksdbLsmKVType, 1<<10)
|
||||
cache1.Close()
|
||||
|
||||
cache2 := NewCache(context.TODO(), "", 1<<10)
|
||||
require.Equal(t, nil, cache2)
|
||||
}
|
||||
|
||||
func TestNewEnv(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
NewEnv(ctx, RocksdbLsmKVType)
|
||||
}
|
||||
|
||||
func TestNewSstFileManager(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
env := NewEnv(ctx, RocksdbLsmKVType)
|
||||
NewSstFileManager(ctx, RocksdbLsmKVType, env)
|
||||
}
|
||||
285
blobstore/common/kvstorev2/option_helper.go
Normal file
285
blobstore/common/kvstorev2/option_helper.go
Normal file
@ -0,0 +1,285 @@
|
||||
package kvstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
rdb "github.com/tecbot/gorocksdb"
|
||||
)
|
||||
|
||||
func (oph *optHelper) GetOption() Option {
|
||||
oph.lock.RLock()
|
||||
opt := *oph.opt
|
||||
oph.lock.RUnlock()
|
||||
return opt
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetMaxBackgroundJobs(value int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetDBOptions([]string{"max_background_jobs"}, []string{strconv.Itoa(value)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.MaxBackgroundJobs = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetMaxBackgroundCompactions(value int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetDBOptions([]string{"max_background_compactions"}, []string{strconv.Itoa(value)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.MaxBackgroundCompactions = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetMaxSubCompactions(value int) error {
|
||||
oph.lock.Lock()
|
||||
// todo
|
||||
oph.opt.MaxSubCompactions = value
|
||||
oph.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetMaxOpenFiles(value int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetDBOptions([]string{"max_open_files"}, []string{strconv.Itoa(value)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.MaxOpenFiles = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetMaxWriteBufferNumber(value int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"max_write_buffer_number"}, []string{strconv.Itoa(value)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.MaxWriteBufferNumber = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetWriteBufferSize(size int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"write_buffer_size"}, []string{strconv.Itoa(size)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.WriteBufferSize = size
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetArenaBlockSize(size int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"arena_block_size"}, []string{strconv.Itoa(size)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.ArenaBlockSize = size
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetTargetFileSizeBase(value uint64) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"target_file_size_base"}, []string{strconv.FormatUint(value, 10)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.TargetFileSizeBase = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetMaxBytesForLevelBase(value uint64) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"max_bytes_for_level_base"}, []string{strconv.FormatUint(value, 10)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.MaxBytesForLevelBase = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetLevel0SlowdownWritesTrigger(value int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"level0_slowdown_writes_trigger"}, []string{strconv.Itoa(value)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.Level0SlowdownWritesTrigger = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetLevel0StopWritesTrigger(value int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"level0_stop_writes_trigger"}, []string{strconv.Itoa(value)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.Level0StopWritesTrigger = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetSoftPendingCompactionBytesLimit(value uint64) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"soft_pending_compaction_bytes_limit"}, []string{strconv.FormatUint(value, 10)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.SoftPendingCompactionBytesLimit = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetHardPendingCompactionBytesLimit(value uint64) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions([]string{"hard_pending_compaction_bytes_limit"}, []string{strconv.FormatUint(value, 10)}); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.HardPendingCompactionBytesLimit = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetBlockSize(size int) error {
|
||||
oph.lock.Lock()
|
||||
// todo
|
||||
oph.opt.BlockSize = size
|
||||
oph.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetFIFOCompactionMaxTableFileSize(size int) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
if err := oph.db.SetOptions(formatFIFOCompactionOption("max_table_files_size", strconv.Itoa(size))); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.CompactionOptionFIFO.MaxTableFileSize = size
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oph *optHelper) SetFIFOCompactionAllow(value bool) error {
|
||||
oph.lock.Lock()
|
||||
defer oph.lock.Unlock()
|
||||
v := "false"
|
||||
if value {
|
||||
v = "true"
|
||||
}
|
||||
if err := oph.db.SetOptions(formatFIFOCompactionOption("allow_compaction", v)); err != nil {
|
||||
return err
|
||||
}
|
||||
oph.opt.CompactionOptionFIFO.AllowCompaction = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func genRocksdbOpts(opt *Option) (opts *rdb.Options) {
|
||||
opts = rdb.NewDefaultOptions()
|
||||
blockBaseOpt := rdb.NewDefaultBlockBasedTableOptions()
|
||||
fifoCompactionOpt := rdb.NewDefaultFIFOCompactionOptions()
|
||||
opts.SetCreateIfMissing(opt.CreateIfMissing)
|
||||
if opt.BlockSize > 0 {
|
||||
blockBaseOpt.SetBlockSize(opt.BlockSize)
|
||||
}
|
||||
if opt.Cache != nil {
|
||||
blockBaseOpt.SetBlockCache(opt.Cache.(*lruCache).cache)
|
||||
// blockBaseOpt.SetCacheIndexAndFilterBlocks(true)
|
||||
} else {
|
||||
if opt.BlockCache > 0 {
|
||||
blockBaseOpt.SetBlockCache(rdb.NewLRUCache(opt.BlockCache))
|
||||
}
|
||||
}
|
||||
opts.SetEnablePipelinedWrite(opt.EnablePipelinedWrite)
|
||||
if opt.MaxBackgroundCompactions > 0 {
|
||||
opts.SetMaxBackgroundCompactions(opt.MaxBackgroundCompactions)
|
||||
}
|
||||
if opt.MaxBackgroundFlushes > 0 {
|
||||
opts.SetMaxBackgroundFlushes(opt.MaxBackgroundFlushes)
|
||||
}
|
||||
if opt.MaxSubCompactions > 0 {
|
||||
opts.SetMaxSubCompactions(opt.MaxSubCompactions)
|
||||
}
|
||||
|
||||
opts.SetLevelCompactionDynamicLevelBytes(opt.LevelCompactionDynamicLevelBytes)
|
||||
if opt.MaxOpenFiles > 0 {
|
||||
opts.SetMaxOpenFiles(opt.MaxOpenFiles)
|
||||
}
|
||||
if opt.MinWriteBufferNumberToMerge > 0 {
|
||||
opts.SetMinWriteBufferNumberToMerge(opt.MinWriteBufferNumberToMerge)
|
||||
}
|
||||
if opt.MaxWriteBufferNumber > 0 {
|
||||
opts.SetMaxWriteBufferNumber(opt.MaxWriteBufferNumber)
|
||||
}
|
||||
if opt.WriteBufferSize > 0 {
|
||||
opts.SetWriteBufferSize(opt.WriteBufferSize)
|
||||
}
|
||||
if opt.ArenaBlockSize > 0 {
|
||||
opts.SetArenaBlockSize(opt.ArenaBlockSize)
|
||||
}
|
||||
if opt.TargetFileSizeBase > 0 {
|
||||
opts.SetTargetFileSizeBase(opt.TargetFileSizeBase)
|
||||
}
|
||||
if opt.MaxBytesForLevelBase > 0 {
|
||||
opts.SetMaxBytesForLevelBase(opt.MaxBytesForLevelBase)
|
||||
}
|
||||
if opt.KeepLogFileNum > 0 {
|
||||
opts.SetKeepLogFileNum(opt.KeepLogFileNum)
|
||||
}
|
||||
if opt.MaxLogFileSize > 0 {
|
||||
opts.SetMaxLogFileSize(opt.MaxLogFileSize)
|
||||
}
|
||||
if opt.Level0SlowdownWritesTrigger > 0 {
|
||||
opts.SetLevel0SlowdownWritesTrigger(opt.Level0SlowdownWritesTrigger)
|
||||
}
|
||||
if opt.Level0StopWritesTrigger > 0 {
|
||||
opts.SetLevel0StopWritesTrigger(opt.Level0StopWritesTrigger)
|
||||
}
|
||||
if opt.SoftPendingCompactionBytesLimit > 0 {
|
||||
opts.SetSoftPendingCompactionBytesLimit(opt.SoftPendingCompactionBytesLimit)
|
||||
}
|
||||
if opt.HardPendingCompactionBytesLimit > 0 {
|
||||
opts.SetHardPendingCompactionBytesLimit(opt.HardPendingCompactionBytesLimit)
|
||||
}
|
||||
if len(opt.CompactionStyle) > 0 {
|
||||
switch opt.CompactionStyle {
|
||||
case FIFOStyle:
|
||||
opts.SetCompactionStyle(rdb.FIFOCompactionStyle)
|
||||
case LevelStyle:
|
||||
opts.SetCompactionStyle(rdb.LevelCompactionStyle)
|
||||
case UniversalStyle:
|
||||
opts.SetCompactionStyle(rdb.UniversalCompactionStyle)
|
||||
default:
|
||||
}
|
||||
}
|
||||
if opt.CompactionOptionFIFO.MaxTableFileSize > 0 {
|
||||
fifoCompactionOpt.SetMaxTableFilesSize(uint64(opt.CompactionOptionFIFO.MaxTableFileSize))
|
||||
}
|
||||
if opt.WriteBufferManager != nil {
|
||||
opts.SetWriteBufferManager(opt.WriteBufferManager.(*writeBufferManager).manager)
|
||||
}
|
||||
if opt.MaxWalLogSize > 0 {
|
||||
opts.SetMaxTotalWalSize(opt.MaxWalLogSize)
|
||||
}
|
||||
if opt.Env != nil {
|
||||
opts.SetEnv(opt.Env.(*env).Env)
|
||||
} else {
|
||||
opts.SetEnv(rdb.NewDefaultEnv())
|
||||
}
|
||||
if opt.SstFileManager != nil {
|
||||
opts.SetSstFileManager(opt.SstFileManager.(*sstFileManager).SstFileManager)
|
||||
}
|
||||
|
||||
opts.SetStatsDumpPeriodSec(0)
|
||||
opts.SetStatsPersistPeriodSec(0)
|
||||
opts.SetBlockBasedTableFactory(blockBaseOpt)
|
||||
opts.SetFIFOCompactionOptions(fifoCompactionOpt)
|
||||
opts.SetCreateIfMissingColumnFamilies(true)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func formatFIFOCompactionOption(key, value string) ([]string, []string) {
|
||||
s := fmt.Sprintf("%s=%s;", key, value)
|
||||
return []string{"compaction_options_fifo"}, []string{s}
|
||||
}
|
||||
1202
blobstore/common/kvstorev2/rocksdb.go
Normal file
1202
blobstore/common/kvstorev2/rocksdb.go
Normal file
File diff suppressed because it is too large
Load Diff
549
blobstore/common/kvstorev2/rocksdb_test.go
Normal file
549
blobstore/common/kvstorev2/rocksdb_test.go
Normal file
@ -0,0 +1,549 @@
|
||||
// Copyright 2023 The Cuber 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 kvstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testEg struct {
|
||||
engine Store
|
||||
path string
|
||||
opt *Option
|
||||
}
|
||||
|
||||
func genTmpPath() (string, error) {
|
||||
id := uuid.NewString()
|
||||
path := os.TempDir() + "/" + id
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func newEngine(ctx context.Context, opt *Option) (*testEg, error) {
|
||||
path, err := genTmpPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var _opt *Option
|
||||
if opt != nil {
|
||||
_opt = opt
|
||||
} else {
|
||||
_opt = new(Option)
|
||||
}
|
||||
_opt.CreateIfMissing = true
|
||||
_opt.Sync = true
|
||||
_opt.ReadConcurrency = 10
|
||||
_opt.ReadQueueLen = 10
|
||||
_opt.WriteConcurrency = 10
|
||||
_opt.WriteQueueLen = 10
|
||||
engine, err := newRocksdb(ctx, path, _opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testEg{
|
||||
engine: engine,
|
||||
path: path,
|
||||
opt: _opt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (eg *testEg) close() {
|
||||
eg.engine.Close()
|
||||
os.RemoveAll(eg.path)
|
||||
}
|
||||
|
||||
func Test_openRocksdb(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
path, err := genTmpPath()
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(path)
|
||||
opt := new(Option)
|
||||
opt.CreateIfMissing = true
|
||||
opt.CompactionOptionFIFO = CompactionOptionFIFO{
|
||||
MaxTableFileSize: 1 << 10,
|
||||
AllowCompaction: false,
|
||||
}
|
||||
opt.BlockSize = 1 << 20
|
||||
opt.BlockCache = 1 << 20
|
||||
opt.MaxSubCompactions = 8
|
||||
opt.MaxBackgroundJobs = 8
|
||||
opt.MaxBackgroundCompactions = 8
|
||||
opt.KeepLogFileNum = 10000
|
||||
opt.MaxLogFileSize = 1 << 30
|
||||
opt.ColumnFamily = []CF{"a", "b", "c"}
|
||||
opt.CompactionStyle = FIFOStyle
|
||||
eg, err := newRocksdb(ctx, path, opt)
|
||||
require.NoError(t, err)
|
||||
eg.Close()
|
||||
|
||||
// open with empty path
|
||||
_, err = newRocksdb(ctx, "", opt)
|
||||
require.Equal(t, errors.New("path is empty"), err)
|
||||
// reopen db
|
||||
eg, err = newRocksdb(ctx, path, opt)
|
||||
require.NoError(t, err)
|
||||
eg.Close()
|
||||
// open with wrong cf
|
||||
opt.ColumnFamily = []CF{"a", "b"}
|
||||
_, err = newRocksdb(ctx, path, opt)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestInstance_CreateColumn(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
err = eg.engine.CreateColumn("colA")
|
||||
require.NoError(t, err)
|
||||
cols := eg.engine.GetAllColumns()
|
||||
fmt.Println(cols)
|
||||
}
|
||||
|
||||
func TestInstance_SetGetRaw(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
k := []byte("key1")
|
||||
v := []byte("value1")
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, k, v)
|
||||
require.NoError(t, err)
|
||||
v1, err := eg.engine.GetRaw(ctx, defaultCF, k)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v, v1)
|
||||
v2, err := eg.engine.Get(ctx, defaultCF, k)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v, v2.Value())
|
||||
err = eg.engine.Delete(ctx, defaultCF, k)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, k, v, WithNoMergeWrite())
|
||||
require.NoError(t, err)
|
||||
v1, err = eg.engine.GetRaw(ctx, defaultCF, k)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v, v1)
|
||||
|
||||
err = eg.engine.Delete(ctx, defaultCF, k)
|
||||
require.NoError(t, err)
|
||||
|
||||
v1, err = eg.engine.GetRaw(ctx, defaultCF, k)
|
||||
require.Nil(t, v1)
|
||||
require.Equal(t, ErrNotFound, err)
|
||||
}
|
||||
|
||||
func TestWriteRead(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
col1 := CF("c1")
|
||||
err = eg.engine.CreateColumn(col1)
|
||||
require.Nil(t, err)
|
||||
|
||||
n := 100
|
||||
cfs := make([]CF, n)
|
||||
keys := make([][]byte, n)
|
||||
values := make([][]byte, n)
|
||||
|
||||
batch := eg.engine.NewWriteBatch()
|
||||
for i := 0; i < n; i++ {
|
||||
keyStr := []byte(fmt.Sprintf("k%d", i))
|
||||
valStr := []byte(fmt.Sprintf("v%d", i))
|
||||
cfs[i] = col1
|
||||
keys[i] = keyStr
|
||||
values[i] = valStr
|
||||
if i/2 != 0 {
|
||||
batch.Put(col1, keyStr, valStr)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
if i/2 == 0 {
|
||||
err = eg.engine.SetRaw(ctx, col1, keys[i], values[i])
|
||||
require.Nil(t, err)
|
||||
continue
|
||||
}
|
||||
err = eg.engine.Write(ctx, batch)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
// read
|
||||
vgs, err := eg.engine.Read(ctx, cfs, keys)
|
||||
require.Nil(t, err)
|
||||
for i := range vgs {
|
||||
require.Equal(t, values[i], vgs[i].Value())
|
||||
vgs[i].Close()
|
||||
}
|
||||
|
||||
ro := eg.engine.NewReadOption()
|
||||
defer ro.Close()
|
||||
|
||||
// no merge read
|
||||
vgs, err = eg.engine.Read(ctx, cfs, keys, WithNoMergeRead(), WithReadOption(ro))
|
||||
require.Nil(t, err)
|
||||
for i := range vgs {
|
||||
require.Equal(t, values[i], vgs[i].Value())
|
||||
vgs[i].Close()
|
||||
}
|
||||
|
||||
// multi get
|
||||
vgs, err = eg.engine.MultiGet(ctx, col1, keys)
|
||||
require.Nil(t, err)
|
||||
for i := range vgs {
|
||||
require.Equal(t, values[i], vgs[i].Value())
|
||||
vgs[i].Close()
|
||||
}
|
||||
|
||||
// no merge multi get
|
||||
vgs, err = eg.engine.MultiGet(ctx, col1, keys, WithNoMergeRead(), WithReadOption(ro))
|
||||
require.Nil(t, err)
|
||||
for i := range vgs {
|
||||
require.Equal(t, values[i], vgs[i].Value())
|
||||
vgs[i].Close()
|
||||
}
|
||||
|
||||
batch = eg.engine.NewWriteBatch()
|
||||
batch.DeleteRange(col1, keys[0], keys[n-1])
|
||||
batch.Put(col1, keys[n-1], []byte("new"))
|
||||
err = eg.engine.Write(ctx, batch)
|
||||
require.NoError(t, err)
|
||||
batch.Clear()
|
||||
|
||||
for i := 0; i < n-1; i++ {
|
||||
_, err = eg.engine.GetRaw(ctx, col1, keys[i])
|
||||
require.Equal(t, ErrNotFound, err)
|
||||
}
|
||||
v, err := eg.engine.GetRaw(ctx, col1, keys[n-1], WithNoMergeRead())
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []byte("new"), v)
|
||||
|
||||
for i := range keys {
|
||||
batch.Put(col1, keys[i], values[i])
|
||||
}
|
||||
err = eg.engine.Write(ctx, batch, WithNoMergeWrite())
|
||||
require.Nil(t, err)
|
||||
err = eg.engine.DeleteRange(ctx, col1, keys[0], keys[n-1], WithNoMergeWrite())
|
||||
require.Nil(t, err)
|
||||
|
||||
vg, err := eg.engine.Get(ctx, col1, keys[len(keys)-1], WithNoMergeRead())
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, values[len(keys)-1], vg.Value())
|
||||
|
||||
_, err = eg.engine.Get(ctx, col1, keys[0], WithNoMergeRead())
|
||||
require.Equal(t, ErrNotFound, err)
|
||||
}
|
||||
|
||||
func Test_ShareCache(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
opt1 := new(Option)
|
||||
opt2 := new(Option)
|
||||
cache := NewCache(ctx, RocksdbLsmKVType, 1<<20)
|
||||
defer cache.Close()
|
||||
opt1.Cache = cache
|
||||
opt2.Cache = cache
|
||||
|
||||
eg1, err := newEngine(ctx, opt1)
|
||||
require.NoError(t, err)
|
||||
eg2, err := newEngine(ctx, opt2)
|
||||
require.NoError(t, err)
|
||||
defer eg1.close()
|
||||
defer eg2.close()
|
||||
}
|
||||
|
||||
func Test_ShareWriteBufferManager(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
opt1 := new(Option)
|
||||
opt2 := new(Option)
|
||||
manager := NewWriteBufferManager(ctx, RocksdbLsmKVType, 1<<20)
|
||||
defer manager.Close()
|
||||
opt1.WriteBufferManager = manager
|
||||
opt2.WriteBufferManager = manager
|
||||
|
||||
eg1, err := newEngine(ctx, opt1)
|
||||
require.NoError(t, err)
|
||||
eg2, err := newEngine(ctx, opt2)
|
||||
require.NoError(t, err)
|
||||
defer eg1.close()
|
||||
defer eg2.close()
|
||||
}
|
||||
|
||||
func TestOptHelper_SetGetOpts(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
oph := eg.engine.GetOptionHelper()
|
||||
require.NoError(t, oph.SetMaxBackgroundJobs(10))
|
||||
require.NoError(t, oph.SetMaxBackgroundCompactions(8))
|
||||
require.NoError(t, oph.SetMaxSubCompactions(8))
|
||||
require.NoError(t, oph.SetMaxOpenFiles(5000))
|
||||
require.NoError(t, oph.SetMaxWriteBufferNumber(36))
|
||||
require.NoError(t, oph.SetWriteBufferSize(256<<20))
|
||||
require.NoError(t, oph.SetArenaBlockSize(64<<20))
|
||||
require.NoError(t, oph.SetTargetFileSizeBase(64<<20))
|
||||
require.NoError(t, oph.SetMaxBytesForLevelBase(64<<20))
|
||||
require.NoError(t, oph.SetLevel0StopWritesTrigger(42))
|
||||
require.NoError(t, oph.SetLevel0SlowdownWritesTrigger(42))
|
||||
require.NoError(t, oph.SetSoftPendingCompactionBytesLimit(64<<20))
|
||||
require.NoError(t, oph.SetHardPendingCompactionBytesLimit(128<<20))
|
||||
require.NoError(t, oph.SetBlockSize(4096))
|
||||
require.NoError(t, oph.SetFIFOCompactionMaxTableFileSize(128<<20))
|
||||
require.NoError(t, oph.SetFIFOCompactionAllow(true))
|
||||
require.Equal(t, oph.GetOption(), *eg.opt)
|
||||
}
|
||||
|
||||
func TestInstance_NewReadOption(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
ro := eg.engine.NewReadOption()
|
||||
k := []byte("key1")
|
||||
v := []byte("value1")
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, k, v)
|
||||
require.NoError(t, err)
|
||||
v1, err := eg.engine.Get(ctx, defaultCF, k)
|
||||
require.NoError(t, err)
|
||||
snap := eg.engine.NewSnapshot()
|
||||
ro.SetSnapShot(snap)
|
||||
v2, err := eg.engine.Get(ctx, defaultCF, k, WithReadOption(ro))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v, v1.Value())
|
||||
require.Equal(t, v, v2.Value())
|
||||
ro.Close()
|
||||
}
|
||||
|
||||
func TestValueGetter_Read(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
k := []byte("key")
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, k, []byte("helloworld"))
|
||||
require.NoError(t, err)
|
||||
vg, err := eg.engine.Get(ctx, defaultCF, k)
|
||||
require.NoError(t, err)
|
||||
defer vg.Close()
|
||||
b := make([]byte, vg.Size()/2)
|
||||
n, err := vg.Read(b)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("hello"), b)
|
||||
require.Equal(t, vg.Size()/2, n)
|
||||
n, err = vg.Read(b)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("world"), b)
|
||||
require.Equal(t, vg.Size()/2, n)
|
||||
n, err = vg.Read(b)
|
||||
require.Equal(t, io.EOF, err)
|
||||
require.Equal(t, 0, n)
|
||||
}
|
||||
|
||||
func TestInstance_NewWriteOption(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
wo := eg.engine.NewWriteOption()
|
||||
wo.SetSync(false)
|
||||
wo.DisableWAL(true)
|
||||
k := []byte("key1")
|
||||
v := []byte("value1")
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, k, v, WithWriteOption(wo))
|
||||
require.NoError(t, err)
|
||||
v1, err := eg.engine.Get(ctx, defaultCF, k)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v, v1.Value())
|
||||
wo.Close()
|
||||
}
|
||||
|
||||
func TestInstance_List(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("key1"), []byte("value1"))
|
||||
require.NoError(t, err)
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("word1"), []byte("w1"))
|
||||
require.NoError(t, err)
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("key2"), []byte("value2"))
|
||||
require.NoError(t, err)
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("check"), []byte("0"))
|
||||
require.NoError(t, err)
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("word2"), []byte("w2"))
|
||||
require.NoError(t, err)
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("key3"), []byte("value3"))
|
||||
require.NoError(t, err)
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("word3"), []byte("w3"))
|
||||
require.NoError(t, err)
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("xyz"), []byte("zyx"))
|
||||
require.NoError(t, err)
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, []byte("key4"), []byte("value4"))
|
||||
require.NoError(t, err)
|
||||
|
||||
ls := eg.engine.List(ctx, defaultCF, []byte("word"), nil, nil)
|
||||
ls.SetFilterKey([]byte("check"))
|
||||
ls.Seek([]byte("word2"))
|
||||
kg, vg, err := ls.ReadNext()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("word2"), kg.Key())
|
||||
require.Equal(t, []byte("w2"), vg.Value())
|
||||
kg, vg, err = ls.ReadNext()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("word3"), kg.Key())
|
||||
require.Equal(t, []byte("w3"), vg.Value())
|
||||
|
||||
ls = eg.engine.List(ctx, defaultCF, []byte("key"), nil, nil)
|
||||
ls.SetFilterKey([]byte("check"))
|
||||
|
||||
// prefix read
|
||||
i := 0
|
||||
for {
|
||||
i++
|
||||
kg, vg, err = ls.ReadNext()
|
||||
if kg == nil {
|
||||
i = 0
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("key"+strconv.Itoa(i)), kg.Key())
|
||||
require.Equal(t, []byte("value"+strconv.Itoa(i)), vg.Value())
|
||||
kg.Close()
|
||||
vg.Close()
|
||||
}
|
||||
// ls.SeekToPrefix([]byte("word"))
|
||||
for {
|
||||
i++
|
||||
kg, vg, err = ls.ReadNext()
|
||||
require.NoError(t, err)
|
||||
if kg == nil {
|
||||
break
|
||||
}
|
||||
require.Equal(t, []byte("word"+strconv.Itoa(i)), kg.Key())
|
||||
require.Equal(t, []byte("w"+strconv.Itoa(i)), vg.Value())
|
||||
kg.Close()
|
||||
vg.Close()
|
||||
}
|
||||
ls.Close()
|
||||
// marker read
|
||||
ls = eg.engine.List(ctx, defaultCF, []byte("key"), []byte("key2"), nil)
|
||||
_, v, err := ls.ReadNextCopy()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("value2"), v)
|
||||
|
||||
// read last
|
||||
_, vg, err = ls.ReadLast()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("value4"), vg.Value())
|
||||
require.Equal(t, 6, vg.Size())
|
||||
|
||||
// nil prefix read
|
||||
ls = eg.engine.List(ctx, defaultCF, nil, nil, nil)
|
||||
// nil prefix read last
|
||||
_, vg, err = ls.ReadLast()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("zyx"), vg.Value())
|
||||
require.Equal(t, 3, vg.Size())
|
||||
vg.Close()
|
||||
ls.Close()
|
||||
}
|
||||
|
||||
func TestInstance_Stats(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
eg.engine.FlushCF(ctx, defaultCF)
|
||||
stats, err := eg.engine.Stats(ctx)
|
||||
require.NoError(t, err)
|
||||
fmt.Println(stats.Used/(1<<10), "kb")
|
||||
}
|
||||
|
||||
func TestEnv_SetLowPriorityBackgroundThreads(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
env := NewEnv(ctx, RocksdbLsmKVType)
|
||||
env.SetLowPriorityBackgroundThreads(1)
|
||||
env.Close()
|
||||
}
|
||||
|
||||
func TestSstFileManager_Close(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
mgr := NewSstFileManager(ctx, RocksdbLsmKVType, NewEnv(ctx, RocksdbLsmKVType))
|
||||
mgr.Close()
|
||||
}
|
||||
|
||||
func TestInstance_DeleteRange(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
eg, err := newEngine(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer eg.close()
|
||||
|
||||
keys := [][]byte{[]byte("/k1/a"), []byte("/k1/b"), []byte("/k1/c"), []byte("/k10"), []byte("/k1012"), []byte("/k11")}
|
||||
for _, key := range keys {
|
||||
err = eg.engine.SetRaw(ctx, defaultCF, key, []byte("1"))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
for _, key := range keys {
|
||||
var value ValueGetter
|
||||
value, err = eg.engine.Get(ctx, defaultCF, key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("1"), value.Value())
|
||||
value.Close()
|
||||
}
|
||||
|
||||
start := []byte("/k1/")
|
||||
end := []byte("/k1/")
|
||||
end[len(end)-1]++
|
||||
t.Log("start: ", start, " end: ", end)
|
||||
t.Log("start: ", string(start), " end: ", string(end))
|
||||
err = eg.engine.DeleteRange(ctx, defaultCF, start, end)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, key := range [][]byte{[]byte("/k1/a"), []byte("/k1/b"), []byte("/k1/c")} {
|
||||
_, err := eg.engine.Get(ctx, defaultCF, key)
|
||||
t.Log(key, err)
|
||||
require.Equal(t, ErrNotFound, err)
|
||||
}
|
||||
for _, key := range [][]byte{[]byte("/k10"), []byte("/k1012"), []byte("/k11")} {
|
||||
value, err := eg.engine.Get(ctx, defaultCF, key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("1"), value.Value())
|
||||
value.Close()
|
||||
}
|
||||
}
|
||||
Binary file not shown.
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.17
|
||||
replace (
|
||||
github.com/jacobsa/fuse => ./depends/jacobsa/fuse
|
||||
github.com/spf13/cobra => ./depends/spf13/cobra
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c => github.com/Cloudstriff/gorocksdb v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
2
go.sum
2
go.sum
@ -35,6 +35,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Cloudstriff/gorocksdb v1.0.1 h1:X/53ktTmfJEog2llrTwJZtajH6ph9Uj72gG6A2QeN9c=
|
||||
github.com/Cloudstriff/gorocksdb v1.0.1/go.mod h1:hH4gbgqW1A4ZTPtDC0CxhVmOrzVPiu1AaAbt1AEdxW4=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/Netflix/go-expect v0.0.0-20190729225929-0e00d9168667/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
|
||||
2
vendor/github.com/Shopify/sarama/Dockerfile.kafka
generated
vendored
2
vendor/github.com/Shopify/sarama/Dockerfile.kafka
generated
vendored
@ -1,4 +1,4 @@
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal@sha256:e52fc1de73dc2879516431ff1865e0fb61b1a32f57b6f914bdcddb13c62f84e6
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
54
vendor/github.com/tecbot/gorocksdb/db.go
generated
vendored
54
vendor/github.com/tecbot/gorocksdb/db.go
generated
vendored
@ -674,6 +674,12 @@ func (db *DB) SetOptions(keys, values []string) error {
|
||||
cKeys[i] = C.CString(keys[i])
|
||||
cValues[i] = C.CString(values[i])
|
||||
}
|
||||
defer func() {
|
||||
for i := range cKeys {
|
||||
C.free(unsafe.Pointer(cKeys[i]))
|
||||
C.free(unsafe.Pointer(cValues[i]))
|
||||
}
|
||||
}()
|
||||
|
||||
var cErr *C.char
|
||||
|
||||
@ -691,6 +697,43 @@ func (db *DB) SetOptions(keys, values []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDBOptions dynamically changes options through the SetDBOptions API.
|
||||
func (db *DB) SetDBOptions(keys, values []string) error {
|
||||
num_keys := len(keys)
|
||||
|
||||
if num_keys == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cKeys := make([]*C.char, num_keys)
|
||||
cValues := make([]*C.char, num_keys)
|
||||
for i := range keys {
|
||||
cKeys[i] = C.CString(keys[i])
|
||||
cValues[i] = C.CString(values[i])
|
||||
}
|
||||
defer func() {
|
||||
for i := range cKeys {
|
||||
C.free(unsafe.Pointer(cKeys[i]))
|
||||
C.free(unsafe.Pointer(cValues[i]))
|
||||
}
|
||||
}()
|
||||
|
||||
var cErr *C.char
|
||||
|
||||
C.rocksdb_set_dboptions(
|
||||
db.c,
|
||||
C.int(num_keys),
|
||||
&cKeys[0],
|
||||
&cValues[0],
|
||||
&cErr,
|
||||
)
|
||||
if cErr != nil {
|
||||
defer C.rocksdb_free(unsafe.Pointer(cErr))
|
||||
return errors.New(C.GoString(cErr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LiveFileMetadata is a metadata which is associated with each SST file.
|
||||
type LiveFileMetadata struct {
|
||||
Name string
|
||||
@ -752,6 +795,17 @@ func (db *DB) Flush(opts *FlushOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlushCF triggers a manuel column family flush for the database.
|
||||
func (db *DB) FlushCF(opts *FlushOptions, cf *ColumnFamilyHandle) error {
|
||||
var cErr *C.char
|
||||
C.rocksdb_flush_cf(db.c, opts.c, cf.c, &cErr)
|
||||
if cErr != nil {
|
||||
defer C.rocksdb_free(unsafe.Pointer(cErr))
|
||||
return errors.New(C.GoString(cErr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableFileDeletions disables file deletions and should be used when backup the database.
|
||||
func (db *DB) DisableFileDeletions() error {
|
||||
var cErr *C.char
|
||||
|
||||
57
vendor/github.com/tecbot/gorocksdb/options.go
generated
vendored
57
vendor/github.com/tecbot/gorocksdb/options.go
generated
vendored
@ -918,6 +918,14 @@ func (opts *Options) SetStatsDumpPeriodSec(value uint) {
|
||||
C.rocksdb_options_set_stats_dump_period_sec(opts.c, C.uint(value))
|
||||
}
|
||||
|
||||
// SetStatsPersistPeriodSec sets the stats persist period in seconds.
|
||||
//
|
||||
// If not zero, persist stats to LOG every stats_persist_period_sec
|
||||
// Default: 300 (5 min)
|
||||
func (opts *Options) SetStatsPersistPeriodSec(value uint) {
|
||||
C.rocksdb_options_set_stats_persist_period_sec(opts.c, C.uint(value))
|
||||
}
|
||||
|
||||
// SetAdviseRandomOnOpen specifies whether we will hint the underlying
|
||||
// file system that the file access pattern is random, when a sst file is opened.
|
||||
// Default: true
|
||||
@ -1196,6 +1204,55 @@ func (opts *Options) SetOptimizeFiltersForHits(value bool) {
|
||||
C.rocksdb_options_set_optimize_filters_for_hits(opts.c, C.int(btoi(value)))
|
||||
}
|
||||
|
||||
// SetMaxSubCompactions set max_subcompactions
|
||||
// This value represents the maximum number of threads that will
|
||||
// concurrently perform a compaction job by breaking it into multiple,
|
||||
// smaller ones that are run simultaneously.
|
||||
// Default: 1 (i.e. no subcompactions)
|
||||
//
|
||||
// Dynamically changeable through SetDBOptions() API.
|
||||
func (opts *Options) SetMaxSubCompactions(value int) {
|
||||
C.rocksdb_options_set_max_subcompactions(opts.c, C.uint32_t(value))
|
||||
}
|
||||
|
||||
// SetWriteBufferManager set write_bufffer_manager for the option
|
||||
// The memory usage of memtable will report to this object. The same object
|
||||
// can be passed into multiple DBs and it will track the sum of size of all
|
||||
// the DBs. If the total size of all live memtables of all the DBs exceeds
|
||||
// a limit, a flush will be triggered in the next DB to which the next write
|
||||
// is issued.
|
||||
//
|
||||
// If the object is only passed to one DB, the behavior is the same as
|
||||
// db_write_buffer_size. When write_buffer_manager is set, the value set will
|
||||
// override db_write_buffer_size.
|
||||
//
|
||||
// This feature is disabled by default. Specify a non-zero value
|
||||
// to enable it.
|
||||
//
|
||||
// Default: null
|
||||
func (opts *Options) SetWriteBufferManager(w *WriteBufferManager) {
|
||||
C.rocksdb_options_set_write_buffer_manager(opts.c, w.c)
|
||||
}
|
||||
|
||||
// SetSstFileManager set sst_file_manager for the option
|
||||
// Use to track SST files and control their file deletion rate.
|
||||
//
|
||||
// Features:
|
||||
// - Throttle the deletion rate of the SST files.
|
||||
// - Keep track the total size of all SST files.
|
||||
// - Set a maximum allowed space limit for SST files that when reached
|
||||
// the DB wont do any further flushes or compactions and will set the
|
||||
// background error.
|
||||
// - Can be shared between multiple dbs.
|
||||
// Limitations:
|
||||
// - Only track and throttle deletes of SST files in
|
||||
// first db_path (db_name if db_paths is empty).
|
||||
//
|
||||
// Default: null
|
||||
func (opts *Options) SetSstFileManager(s *SstFileManager) {
|
||||
C.rocksdb_options_set_sst_file_manager(opts.c, s.c)
|
||||
}
|
||||
|
||||
// Destroy deallocates the Options object.
|
||||
func (opts *Options) Destroy() {
|
||||
C.rocksdb_options_destroy(opts.c)
|
||||
|
||||
5
vendor/github.com/tecbot/gorocksdb/ratelimiter.go
generated
vendored
5
vendor/github.com/tecbot/gorocksdb/ratelimiter.go
generated
vendored
@ -24,6 +24,11 @@ func NewNativeRateLimiter(c *C.rocksdb_ratelimiter_t) *RateLimiter {
|
||||
return &RateLimiter{c}
|
||||
}
|
||||
|
||||
// SetBytesPerSecond set rate limiter bytes per second dynamically
|
||||
func (self *RateLimiter) SetBytesPerSecond(val int64) {
|
||||
C.rocksdb_ratelimiter_set_bytes_per_second(self.c, C.int64_t(val))
|
||||
}
|
||||
|
||||
// Destroy deallocates the RateLimiter object.
|
||||
func (self *RateLimiter) Destroy() {
|
||||
C.rocksdb_ratelimiter_destroy(self.c)
|
||||
|
||||
21
vendor/github.com/tecbot/gorocksdb/sst_file_manager.go
generated
vendored
Normal file
21
vendor/github.com/tecbot/gorocksdb/sst_file_manager.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package gorocksdb
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include "rocksdb/c.h"
|
||||
import "C"
|
||||
|
||||
// SstFileManager manager sst file deletion between multiple column families or multiple db
|
||||
type SstFileManager struct {
|
||||
c *C.rocksdb_sst_file_manager_t
|
||||
}
|
||||
|
||||
// NewSstFileManager creates a SstFileManager object.
|
||||
func NewSstFileManager(env *Env) *SstFileManager {
|
||||
return &SstFileManager{c: C.rocksdb_sst_file_manager_create(env.c)}
|
||||
}
|
||||
|
||||
// Destroy deallocates the SstFileManager object.
|
||||
func (w *SstFileManager) Destroy() {
|
||||
C.rocksdb_sst_file_manager_destory(w.c)
|
||||
w.c = nil
|
||||
}
|
||||
21
vendor/github.com/tecbot/gorocksdb/write_buffer_manager.go
generated
vendored
Normal file
21
vendor/github.com/tecbot/gorocksdb/write_buffer_manager.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package gorocksdb
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include "rocksdb/c.h"
|
||||
import "C"
|
||||
|
||||
// WriteBufferManager manager the whole write buffer between multiple column families or multiple db
|
||||
type WriteBufferManager struct {
|
||||
c *C.rocksdb_write_buffer_manager_t
|
||||
}
|
||||
|
||||
// NewWriteBufferManager creates a WriteBufferManager object.
|
||||
func NewWriteBufferManager(bufferSize uint64) *WriteBufferManager {
|
||||
return &WriteBufferManager{c: C.rocksdb_write_buffer_manager_create(C.size_t(bufferSize))}
|
||||
}
|
||||
|
||||
// Destroy deallocates the WriterBufferManager object.
|
||||
func (w *WriteBufferManager) Destroy() {
|
||||
C.rocksdb_write_buffer_manager_destory(w.c)
|
||||
w.c = nil
|
||||
}
|
||||
109
vendor/go.etcd.io/etcd/raft/v3/confchange/datadriven_test.go
generated
vendored
109
vendor/go.etcd.io/etcd/raft/v3/confchange/datadriven_test.go
generated
vendored
@ -1,109 +0,0 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cockroachdb/datadriven"
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
"go.etcd.io/etcd/raft/v3/tracker"
|
||||
)
|
||||
|
||||
func TestConfChangeDataDriven(t *testing.T) {
|
||||
datadriven.Walk(t, "testdata", func(t *testing.T, path string) {
|
||||
tr := tracker.MakeProgressTracker(10)
|
||||
c := Changer{
|
||||
Tracker: tr,
|
||||
LastIndex: 0, // incremented in this test with each cmd
|
||||
}
|
||||
|
||||
// The test files use the commands
|
||||
// - simple: run a simple conf change (i.e. no joint consensus),
|
||||
// - enter-joint: enter a joint config, and
|
||||
// - leave-joint: leave a joint config.
|
||||
// The first two take a list of config changes, which have the following
|
||||
// syntax:
|
||||
// - vn: make n a voter,
|
||||
// - ln: make n a learner,
|
||||
// - rn: remove n, and
|
||||
// - un: update n.
|
||||
datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
|
||||
defer func() {
|
||||
c.LastIndex++
|
||||
}()
|
||||
var ccs []pb.ConfChangeSingle
|
||||
toks := strings.Split(strings.TrimSpace(d.Input), " ")
|
||||
if toks[0] == "" {
|
||||
toks = nil
|
||||
}
|
||||
for _, tok := range toks {
|
||||
if len(tok) < 2 {
|
||||
return fmt.Sprintf("unknown token %s", tok)
|
||||
}
|
||||
var cc pb.ConfChangeSingle
|
||||
switch tok[0] {
|
||||
case 'v':
|
||||
cc.Type = pb.ConfChangeAddNode
|
||||
case 'l':
|
||||
cc.Type = pb.ConfChangeAddLearnerNode
|
||||
case 'r':
|
||||
cc.Type = pb.ConfChangeRemoveNode
|
||||
case 'u':
|
||||
cc.Type = pb.ConfChangeUpdateNode
|
||||
default:
|
||||
return fmt.Sprintf("unknown input: %s", tok)
|
||||
}
|
||||
id, err := strconv.ParseUint(tok[1:], 10, 64)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
cc.NodeID = id
|
||||
ccs = append(ccs, cc)
|
||||
}
|
||||
|
||||
var cfg tracker.Config
|
||||
var prs tracker.ProgressMap
|
||||
var err error
|
||||
switch d.Cmd {
|
||||
case "simple":
|
||||
cfg, prs, err = c.Simple(ccs...)
|
||||
case "enter-joint":
|
||||
var autoLeave bool
|
||||
if len(d.CmdArgs) > 0 {
|
||||
d.ScanArgs(t, "autoleave", &autoLeave)
|
||||
}
|
||||
cfg, prs, err = c.EnterJoint(autoLeave, ccs...)
|
||||
case "leave-joint":
|
||||
if len(ccs) > 0 {
|
||||
err = errors.New("this command takes no input")
|
||||
} else {
|
||||
cfg, prs, err = c.LeaveJoint()
|
||||
}
|
||||
default:
|
||||
return "unknown command"
|
||||
}
|
||||
if err != nil {
|
||||
return err.Error() + "\n"
|
||||
}
|
||||
c.Tracker.Config, c.Tracker.Progress = cfg, prs
|
||||
return fmt.Sprintf("%s\n%s", c.Tracker.Config, c.Tracker.Progress)
|
||||
})
|
||||
})
|
||||
}
|
||||
191
vendor/go.etcd.io/etcd/raft/v3/confchange/quick_test.go
generated
vendored
191
vendor/go.etcd.io/etcd/raft/v3/confchange/quick_test.go
generated
vendored
@ -1,191 +0,0 @@
|
||||
// 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/v3/raftpb"
|
||||
"go.etcd.io/etcd/raft/v3/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)
|
||||
}
|
||||
142
vendor/go.etcd.io/etcd/raft/v3/confchange/restore_test.go
generated
vendored
142
vendor/go.etcd.io/etcd/raft/v3/confchange/restore_test.go
generated
vendored
@ -1,142 +0,0 @@
|
||||
// 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 (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
"go.etcd.io/etcd/raft/v3/tracker"
|
||||
)
|
||||
|
||||
type rndConfChange pb.ConfState
|
||||
|
||||
// Generate creates a random (valid) ConfState for use with quickcheck.
|
||||
func (rndConfChange) Generate(rand *rand.Rand, _ int) reflect.Value {
|
||||
conv := func(sl []int) []uint64 {
|
||||
// We want IDs but the incoming slice is zero-indexed, so add one to
|
||||
// each.
|
||||
out := make([]uint64, len(sl))
|
||||
for i := range sl {
|
||||
out[i] = uint64(sl[i] + 1)
|
||||
}
|
||||
return out
|
||||
}
|
||||
var cs pb.ConfState
|
||||
// NB: never generate the empty ConfState, that one should be unit tested.
|
||||
nVoters := 1 + rand.Intn(5)
|
||||
|
||||
nLearners := rand.Intn(5)
|
||||
// The number of voters that are in the outgoing config but not in the
|
||||
// incoming one. (We'll additionally retain a random number of the
|
||||
// incoming voters below).
|
||||
nRemovedVoters := rand.Intn(3)
|
||||
|
||||
// Voters, learners, and removed voters must not overlap. A "removed voter"
|
||||
// is one that we have in the outgoing config but not the incoming one.
|
||||
ids := conv(rand.Perm(2 * (nVoters + nLearners + nRemovedVoters)))
|
||||
|
||||
cs.Voters = ids[:nVoters]
|
||||
ids = ids[nVoters:]
|
||||
|
||||
if nLearners > 0 {
|
||||
cs.Learners = ids[:nLearners]
|
||||
ids = ids[nLearners:]
|
||||
}
|
||||
|
||||
// Roll the dice on how many of the incoming voters we decide were also
|
||||
// previously voters.
|
||||
//
|
||||
// NB: this code avoids creating non-nil empty slices (here and below).
|
||||
nOutgoingRetainedVoters := rand.Intn(nVoters + 1)
|
||||
if nOutgoingRetainedVoters > 0 || nRemovedVoters > 0 {
|
||||
cs.VotersOutgoing = append([]uint64(nil), cs.Voters[:nOutgoingRetainedVoters]...)
|
||||
cs.VotersOutgoing = append(cs.VotersOutgoing, ids[:nRemovedVoters]...)
|
||||
}
|
||||
// Only outgoing voters that are not also incoming voters can be in
|
||||
// LearnersNext (they represent demotions).
|
||||
if nRemovedVoters > 0 {
|
||||
if nLearnersNext := rand.Intn(nRemovedVoters + 1); nLearnersNext > 0 {
|
||||
cs.LearnersNext = ids[:nLearnersNext]
|
||||
}
|
||||
}
|
||||
|
||||
cs.AutoLeave = len(cs.VotersOutgoing) > 0 && rand.Intn(2) == 1
|
||||
return reflect.ValueOf(rndConfChange(cs))
|
||||
}
|
||||
|
||||
func TestRestore(t *testing.T) {
|
||||
cfg := quick.Config{MaxCount: 1000}
|
||||
|
||||
f := func(cs pb.ConfState) bool {
|
||||
chg := Changer{
|
||||
Tracker: tracker.MakeProgressTracker(20),
|
||||
LastIndex: 10,
|
||||
}
|
||||
cfg, prs, err := Restore(chg, cs)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return false
|
||||
}
|
||||
chg.Tracker.Config = cfg
|
||||
chg.Tracker.Progress = prs
|
||||
|
||||
for _, sl := range [][]uint64{
|
||||
cs.Voters,
|
||||
cs.Learners,
|
||||
cs.VotersOutgoing,
|
||||
cs.LearnersNext,
|
||||
} {
|
||||
sort.Slice(sl, func(i, j int) bool { return sl[i] < sl[j] })
|
||||
}
|
||||
|
||||
cs2 := chg.Tracker.ConfState()
|
||||
// NB: cs.Equivalent does the same "sorting" dance internally, but let's
|
||||
// test it a bit here instead of relying on it.
|
||||
if reflect.DeepEqual(cs, cs2) && cs.Equivalent(cs2) == nil && cs2.Equivalent(cs) == nil {
|
||||
return true // success
|
||||
}
|
||||
t.Errorf(`
|
||||
before: %+#v
|
||||
after: %+#v`, cs, cs2)
|
||||
return false
|
||||
}
|
||||
|
||||
ids := func(sl ...uint64) []uint64 {
|
||||
return sl
|
||||
}
|
||||
|
||||
// Unit tests.
|
||||
for _, cs := range []pb.ConfState{
|
||||
{},
|
||||
{Voters: ids(1, 2, 3)},
|
||||
{Voters: ids(1, 2, 3), Learners: ids(4, 5, 6)},
|
||||
{Voters: ids(1, 2, 3), Learners: ids(5), VotersOutgoing: ids(1, 2, 4, 6), LearnersNext: ids(4)},
|
||||
} {
|
||||
if !f(cs) {
|
||||
t.FailNow() // f() already logged a nice t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
if err := quick.Check(func(cs rndConfChange) bool {
|
||||
return f(pb.ConfState(cs))
|
||||
}, &cfg); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
29
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/joint_autoleave.txt
generated
vendored
29
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/joint_autoleave.txt
generated
vendored
@ -1,29 +0,0 @@
|
||||
# Test the autoleave argument to EnterJoint. It defaults to false in the
|
||||
# datadriven tests. The flag has no associated semantics in this package,
|
||||
# it is simply passed through.
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
|
||||
# Autoleave is reflected in the config.
|
||||
enter-joint autoleave=true
|
||||
v2 v3
|
||||
----
|
||||
voters=(1 2 3)&&(1) autoleave
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
3: StateProbe match=0 next=1
|
||||
|
||||
# Can't enter-joint twice, even if autoleave changes.
|
||||
enter-joint autoleave=false
|
||||
----
|
||||
config is already joint
|
||||
|
||||
leave-joint
|
||||
----
|
||||
voters=(1 2 3)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
3: StateProbe match=0 next=1
|
||||
23
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/joint_idempotency.txt
generated
vendored
23
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/joint_idempotency.txt
generated
vendored
@ -1,23 +0,0 @@
|
||||
# Verify that operations upon entering the joint state are idempotent, i.e.
|
||||
# removing an absent node is fine, etc.
|
||||
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
|
||||
enter-joint
|
||||
r1 r2 r9 v2 v3 v4 v2 v3 v4 l2 l2 r4 r4 l1 l1
|
||||
----
|
||||
voters=(3)&&(1) learners=(2) learners_next=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1 learner
|
||||
3: StateProbe match=0 next=1
|
||||
|
||||
leave-joint
|
||||
----
|
||||
voters=(3) learners=(1 2)
|
||||
1: StateProbe match=0 next=0 learner
|
||||
2: StateProbe match=0 next=1 learner
|
||||
3: StateProbe match=0 next=1
|
||||
24
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/joint_learners_next.txt
generated
vendored
24
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/joint_learners_next.txt
generated
vendored
@ -1,24 +0,0 @@
|
||||
# Verify that when a voter is demoted in a joint config, it will show up in
|
||||
# learners_next until the joint config is left, and only then will the progress
|
||||
# turn into that of a learner, without resetting the progress. Note that this
|
||||
# last fact is verified by `next`, which can tell us which "round" the progress
|
||||
# was originally created in.
|
||||
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
|
||||
enter-joint
|
||||
v2 l1
|
||||
----
|
||||
voters=(2)&&(1) learners_next=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
|
||||
leave-joint
|
||||
----
|
||||
voters=(2) learners=(1)
|
||||
1: StateProbe match=0 next=0 learner
|
||||
2: StateProbe match=0 next=1
|
||||
81
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/joint_safety.txt
generated
vendored
81
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/joint_safety.txt
generated
vendored
@ -1,81 +0,0 @@
|
||||
leave-joint
|
||||
----
|
||||
can't leave a non-joint config
|
||||
|
||||
enter-joint
|
||||
----
|
||||
can't make a zero-voter config joint
|
||||
|
||||
enter-joint
|
||||
v1
|
||||
----
|
||||
can't make a zero-voter config joint
|
||||
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=3
|
||||
|
||||
leave-joint
|
||||
----
|
||||
can't leave a non-joint config
|
||||
|
||||
# Can enter into joint config.
|
||||
enter-joint
|
||||
----
|
||||
voters=(1)&&(1)
|
||||
1: StateProbe match=0 next=3
|
||||
|
||||
enter-joint
|
||||
----
|
||||
config is already joint
|
||||
|
||||
leave-joint
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=3
|
||||
|
||||
leave-joint
|
||||
----
|
||||
can't leave a non-joint config
|
||||
|
||||
# Can enter again, this time with some ops.
|
||||
enter-joint
|
||||
r1 v2 v3 l4
|
||||
----
|
||||
voters=(2 3)&&(1) learners=(4)
|
||||
1: StateProbe match=0 next=3
|
||||
2: StateProbe match=0 next=9
|
||||
3: StateProbe match=0 next=9
|
||||
4: StateProbe match=0 next=9 learner
|
||||
|
||||
enter-joint
|
||||
----
|
||||
config is already joint
|
||||
|
||||
enter-joint
|
||||
v12
|
||||
----
|
||||
config is already joint
|
||||
|
||||
simple
|
||||
l15
|
||||
----
|
||||
can't apply simple config change in joint config
|
||||
|
||||
leave-joint
|
||||
----
|
||||
voters=(2 3) learners=(4)
|
||||
2: StateProbe match=0 next=9
|
||||
3: StateProbe match=0 next=9
|
||||
4: StateProbe match=0 next=9 learner
|
||||
|
||||
simple
|
||||
l9
|
||||
----
|
||||
voters=(2 3) learners=(4 9)
|
||||
2: StateProbe match=0 next=9
|
||||
3: StateProbe match=0 next=9
|
||||
4: StateProbe match=0 next=9 learner
|
||||
9: StateProbe match=0 next=14 learner
|
||||
69
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/simple_idempotency.txt
generated
vendored
69
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/simple_idempotency.txt
generated
vendored
@ -1,69 +0,0 @@
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
|
||||
simple
|
||||
v2
|
||||
----
|
||||
voters=(1 2)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=2
|
||||
|
||||
simple
|
||||
l1
|
||||
----
|
||||
voters=(2) learners=(1)
|
||||
1: StateProbe match=0 next=0 learner
|
||||
2: StateProbe match=0 next=2
|
||||
|
||||
simple
|
||||
l1
|
||||
----
|
||||
voters=(2) learners=(1)
|
||||
1: StateProbe match=0 next=0 learner
|
||||
2: StateProbe match=0 next=2
|
||||
|
||||
simple
|
||||
r1
|
||||
----
|
||||
voters=(2)
|
||||
2: StateProbe match=0 next=2
|
||||
|
||||
simple
|
||||
r1
|
||||
----
|
||||
voters=(2)
|
||||
2: StateProbe match=0 next=2
|
||||
|
||||
simple
|
||||
v3
|
||||
----
|
||||
voters=(2 3)
|
||||
2: StateProbe match=0 next=2
|
||||
3: StateProbe match=0 next=7
|
||||
|
||||
simple
|
||||
r3
|
||||
----
|
||||
voters=(2)
|
||||
2: StateProbe match=0 next=2
|
||||
|
||||
simple
|
||||
r3
|
||||
----
|
||||
voters=(2)
|
||||
2: StateProbe match=0 next=2
|
||||
|
||||
simple
|
||||
r4
|
||||
----
|
||||
voters=(2)
|
||||
2: StateProbe match=0 next=2
|
||||
60
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/simple_promote_demote.txt
generated
vendored
60
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/simple_promote_demote.txt
generated
vendored
@ -1,60 +0,0 @@
|
||||
# Set up three voters for this test.
|
||||
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
|
||||
simple
|
||||
v2
|
||||
----
|
||||
voters=(1 2)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
|
||||
simple
|
||||
v3
|
||||
----
|
||||
voters=(1 2 3)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
3: StateProbe match=0 next=2
|
||||
|
||||
# Can atomically demote and promote without a hitch.
|
||||
# This is pointless, but possible.
|
||||
simple
|
||||
l1 v1
|
||||
----
|
||||
voters=(1 2 3)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
3: StateProbe match=0 next=2
|
||||
|
||||
# Can demote a voter.
|
||||
simple
|
||||
l2
|
||||
----
|
||||
voters=(1 3) learners=(2)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1 learner
|
||||
3: StateProbe match=0 next=2
|
||||
|
||||
# Can atomically promote and demote the same voter.
|
||||
# This is pointless, but possible.
|
||||
simple
|
||||
v2 l2
|
||||
----
|
||||
voters=(1 3) learners=(2)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1 learner
|
||||
3: StateProbe match=0 next=2
|
||||
|
||||
# Can promote a voter.
|
||||
simple
|
||||
v2
|
||||
----
|
||||
voters=(1 2 3)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
3: StateProbe match=0 next=2
|
||||
64
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/simple_safety.txt
generated
vendored
64
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/simple_safety.txt
generated
vendored
@ -1,64 +0,0 @@
|
||||
simple
|
||||
l1
|
||||
----
|
||||
removed all voters
|
||||
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=1
|
||||
|
||||
simple
|
||||
v2 l3
|
||||
----
|
||||
voters=(1 2) learners=(3)
|
||||
1: StateProbe match=0 next=1
|
||||
2: StateProbe match=0 next=2
|
||||
3: StateProbe match=0 next=2 learner
|
||||
|
||||
simple
|
||||
r1 v5
|
||||
----
|
||||
more than one voter changed without entering joint config
|
||||
|
||||
simple
|
||||
r1 r2
|
||||
----
|
||||
removed all voters
|
||||
|
||||
simple
|
||||
v3 v4
|
||||
----
|
||||
more than one voter changed without entering joint config
|
||||
|
||||
simple
|
||||
l1 v5
|
||||
----
|
||||
more than one voter changed without entering joint config
|
||||
|
||||
simple
|
||||
l1 l2
|
||||
----
|
||||
removed all voters
|
||||
|
||||
simple
|
||||
l2 l3 l4 l5
|
||||
----
|
||||
voters=(1) learners=(2 3 4 5)
|
||||
1: StateProbe match=0 next=1
|
||||
2: StateProbe match=0 next=2 learner
|
||||
3: StateProbe match=0 next=2 learner
|
||||
4: StateProbe match=0 next=8 learner
|
||||
5: StateProbe match=0 next=8 learner
|
||||
|
||||
simple
|
||||
r1
|
||||
----
|
||||
removed all voters
|
||||
|
||||
simple
|
||||
r2 r3 r4 r5
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=1
|
||||
23
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/update.txt
generated
vendored
23
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/update.txt
generated
vendored
@ -1,23 +0,0 @@
|
||||
# Nobody cares about ConfChangeUpdateNode, but at least use it once. It is used
|
||||
# by etcd as a convenient way to pass a blob through their conf change machinery
|
||||
# that updates information tracked outside of raft.
|
||||
|
||||
simple
|
||||
v1
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
|
||||
simple
|
||||
v2 u1
|
||||
----
|
||||
voters=(1 2)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
|
||||
simple
|
||||
u1 u2 u3 u1 u2 u3
|
||||
----
|
||||
voters=(1 2)
|
||||
1: StateProbe match=0 next=0
|
||||
2: StateProbe match=0 next=1
|
||||
6
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/zero.txt
generated
vendored
6
vendor/go.etcd.io/etcd/raft/v3/confchange/testdata/zero.txt
generated
vendored
@ -1,6 +0,0 @@
|
||||
# NodeID zero is ignored.
|
||||
simple
|
||||
v1 r0 v0 l0
|
||||
----
|
||||
voters=(1)
|
||||
1: StateProbe match=0 next=0
|
||||
65
vendor/go.etcd.io/etcd/raft/v3/diff_test.go
generated
vendored
65
vendor/go.etcd.io/etcd/raft/v3/diff_test.go
generated
vendored
@ -1,65 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func diffu(a, b string) string {
|
||||
if a == b {
|
||||
return ""
|
||||
}
|
||||
aname, bname := mustTemp("base", a), mustTemp("other", b)
|
||||
defer os.Remove(aname)
|
||||
defer os.Remove(bname)
|
||||
cmd := exec.Command("diff", "-u", aname, bname)
|
||||
buf, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
// do nothing
|
||||
return string(buf)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func mustTemp(pre, body string) string {
|
||||
f, err := ioutil.TempFile("", pre)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = io.Copy(f, strings.NewReader(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.Close()
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
func ltoa(l *raftLog) string {
|
||||
s := fmt.Sprintf("committed: %d\n", l.committed)
|
||||
s += fmt.Sprintf("applied: %d\n", l.applied)
|
||||
for i, e := range l.allEntries() {
|
||||
s += fmt.Sprintf("#%d: %+v\n", i, e)
|
||||
}
|
||||
return s
|
||||
}
|
||||
47
vendor/go.etcd.io/etcd/raft/v3/example_test.go
generated
vendored
47
vendor/go.etcd.io/etcd/raft/v3/example_test.go
generated
vendored
@ -1,47 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
)
|
||||
|
||||
func applyToStore(ents []pb.Entry) {}
|
||||
func sendMessages(msgs []pb.Message) {}
|
||||
func saveStateToDisk(st pb.HardState) {}
|
||||
func saveToDisk(ents []pb.Entry) {}
|
||||
|
||||
func ExampleNode() {
|
||||
c := &Config{}
|
||||
n := StartNode(c, nil)
|
||||
defer n.Stop()
|
||||
|
||||
// stuff to n happens in other goroutines
|
||||
|
||||
// the last known state
|
||||
var prev pb.HardState
|
||||
for {
|
||||
// Ready blocks until there is new state ready.
|
||||
rd := <-n.Ready()
|
||||
if !isHardStateEqual(prev, rd.HardState) {
|
||||
saveStateToDisk(rd.HardState)
|
||||
prev = rd.HardState
|
||||
}
|
||||
|
||||
saveToDisk(rd.Entries)
|
||||
go applyToStore(rd.CommittedEntries)
|
||||
sendMessages(rd.Messages)
|
||||
}
|
||||
}
|
||||
34
vendor/go.etcd.io/etcd/raft/v3/interaction_test.go
generated
vendored
34
vendor/go.etcd.io/etcd/raft/v3/interaction_test.go
generated
vendored
@ -1,34 +0,0 @@
|
||||
// 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 raft_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cockroachdb/datadriven"
|
||||
"go.etcd.io/etcd/raft/v3/rafttest"
|
||||
)
|
||||
|
||||
func TestInteraction(t *testing.T) {
|
||||
// NB: if this test fails, run `go test ./raft -rewrite` and inspect the
|
||||
// diff. Only commit the changes if you understand what caused them and if
|
||||
// they are desired.
|
||||
datadriven.Walk(t, "testdata", func(t *testing.T, path string) {
|
||||
env := rafttest.NewInteractionEnv(nil)
|
||||
datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
|
||||
return env.Handle(t, *d)
|
||||
})
|
||||
})
|
||||
}
|
||||
821
vendor/go.etcd.io/etcd/raft/v3/log_test.go
generated
vendored
821
vendor/go.etcd.io/etcd/raft/v3/log_test.go
generated
vendored
@ -1,821 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
)
|
||||
|
||||
func TestFindConflict(t *testing.T) {
|
||||
previousEnts := []pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}, {Index: 3, Term: 3}}
|
||||
tests := []struct {
|
||||
ents []pb.Entry
|
||||
wconflict uint64
|
||||
}{
|
||||
// no conflict, empty ent
|
||||
{[]pb.Entry{}, 0},
|
||||
// no conflict
|
||||
{[]pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}, {Index: 3, Term: 3}}, 0},
|
||||
{[]pb.Entry{{Index: 2, Term: 2}, {Index: 3, Term: 3}}, 0},
|
||||
{[]pb.Entry{{Index: 3, Term: 3}}, 0},
|
||||
// no conflict, but has new entries
|
||||
{[]pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}, {Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 4}}, 4},
|
||||
{[]pb.Entry{{Index: 2, Term: 2}, {Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 4}}, 4},
|
||||
{[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 4}}, 4},
|
||||
{[]pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 4}}, 4},
|
||||
// conflicts with existing entries
|
||||
{[]pb.Entry{{Index: 1, Term: 4}, {Index: 2, Term: 4}}, 1},
|
||||
{[]pb.Entry{{Index: 2, Term: 1}, {Index: 3, Term: 4}, {Index: 4, Term: 4}}, 2},
|
||||
{[]pb.Entry{{Index: 3, Term: 1}, {Index: 4, Term: 2}, {Index: 5, Term: 4}, {Index: 6, Term: 4}}, 3},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
raftLog := newLog(NewMemoryStorage(), raftLogger)
|
||||
raftLog.append(previousEnts...)
|
||||
|
||||
gconflict := raftLog.findConflict(tt.ents)
|
||||
if gconflict != tt.wconflict {
|
||||
t.Errorf("#%d: conflict = %d, want %d", i, gconflict, tt.wconflict)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUpToDate(t *testing.T) {
|
||||
previousEnts := []pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}, {Index: 3, Term: 3}}
|
||||
raftLog := newLog(NewMemoryStorage(), raftLogger)
|
||||
raftLog.append(previousEnts...)
|
||||
tests := []struct {
|
||||
lastIndex uint64
|
||||
term uint64
|
||||
wUpToDate bool
|
||||
}{
|
||||
// greater term, ignore lastIndex
|
||||
{raftLog.lastIndex() - 1, 4, true},
|
||||
{raftLog.lastIndex(), 4, true},
|
||||
{raftLog.lastIndex() + 1, 4, true},
|
||||
// smaller term, ignore lastIndex
|
||||
{raftLog.lastIndex() - 1, 2, false},
|
||||
{raftLog.lastIndex(), 2, false},
|
||||
{raftLog.lastIndex() + 1, 2, false},
|
||||
// equal term, equal or lager lastIndex wins
|
||||
{raftLog.lastIndex() - 1, 3, false},
|
||||
{raftLog.lastIndex(), 3, true},
|
||||
{raftLog.lastIndex() + 1, 3, true},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
gUpToDate := raftLog.isUpToDate(tt.lastIndex, tt.term)
|
||||
if gUpToDate != tt.wUpToDate {
|
||||
t.Errorf("#%d: uptodate = %v, want %v", i, gUpToDate, tt.wUpToDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
previousEnts := []pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}}
|
||||
tests := []struct {
|
||||
ents []pb.Entry
|
||||
windex uint64
|
||||
wents []pb.Entry
|
||||
wunstable uint64
|
||||
}{
|
||||
{
|
||||
[]pb.Entry{},
|
||||
2,
|
||||
[]pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}},
|
||||
3,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 3, Term: 2}},
|
||||
3,
|
||||
[]pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}, {Index: 3, Term: 2}},
|
||||
3,
|
||||
},
|
||||
// conflicts with index 1
|
||||
{
|
||||
[]pb.Entry{{Index: 1, Term: 2}},
|
||||
1,
|
||||
[]pb.Entry{{Index: 1, Term: 2}},
|
||||
1,
|
||||
},
|
||||
// conflicts with index 2
|
||||
{
|
||||
[]pb.Entry{{Index: 2, Term: 3}, {Index: 3, Term: 3}},
|
||||
3,
|
||||
[]pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 3}, {Index: 3, Term: 3}},
|
||||
2,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
storage := NewMemoryStorage()
|
||||
storage.Append(previousEnts)
|
||||
raftLog := newLog(storage, raftLogger)
|
||||
|
||||
index := raftLog.append(tt.ents...)
|
||||
if index != tt.windex {
|
||||
t.Errorf("#%d: lastIndex = %d, want %d", i, index, tt.windex)
|
||||
}
|
||||
g, err := raftLog.entries(1, noLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("#%d: unexpected error %v", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(g, tt.wents) {
|
||||
t.Errorf("#%d: logEnts = %+v, want %+v", i, g, tt.wents)
|
||||
}
|
||||
if goff := raftLog.unstable.offset; goff != tt.wunstable {
|
||||
t.Errorf("#%d: unstable = %d, want %d", i, goff, tt.wunstable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogMaybeAppend ensures:
|
||||
// If the given (index, term) matches with the existing log:
|
||||
// 1. If an existing entry conflicts with a new one (same index
|
||||
// but different terms), delete the existing entry and all that
|
||||
// follow it
|
||||
// 2. Append any new entries not already in the log
|
||||
//
|
||||
// If the given (index, term) does not match with the existing log:
|
||||
//
|
||||
// return false
|
||||
func TestLogMaybeAppend(t *testing.T) {
|
||||
previousEnts := []pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}, {Index: 3, Term: 3}}
|
||||
lastindex := uint64(3)
|
||||
lastterm := uint64(3)
|
||||
commit := uint64(1)
|
||||
|
||||
tests := []struct {
|
||||
logTerm uint64
|
||||
index uint64
|
||||
committed uint64
|
||||
ents []pb.Entry
|
||||
|
||||
wlasti uint64
|
||||
wappend bool
|
||||
wcommit uint64
|
||||
wpanic bool
|
||||
}{
|
||||
// not match: term is different
|
||||
{
|
||||
lastterm - 1, lastindex, lastindex, []pb.Entry{{Index: lastindex + 1, Term: 4}},
|
||||
0, false, commit, false,
|
||||
},
|
||||
// not match: index out of bound
|
||||
{
|
||||
lastterm, lastindex + 1, lastindex, []pb.Entry{{Index: lastindex + 2, Term: 4}},
|
||||
0, false, commit, false,
|
||||
},
|
||||
// match with the last existing entry
|
||||
{
|
||||
lastterm, lastindex, lastindex, nil,
|
||||
lastindex, true, lastindex, false,
|
||||
},
|
||||
{
|
||||
lastterm, lastindex, lastindex + 1, nil,
|
||||
lastindex, true, lastindex, false, // do not increase commit higher than lastnewi
|
||||
},
|
||||
{
|
||||
lastterm, lastindex, lastindex - 1, nil,
|
||||
lastindex, true, lastindex - 1, false, // commit up to the commit in the message
|
||||
},
|
||||
{
|
||||
lastterm, lastindex, 0, nil,
|
||||
lastindex, true, commit, false, // commit do not decrease
|
||||
},
|
||||
{
|
||||
0, 0, lastindex, nil,
|
||||
0, true, commit, false, // commit do not decrease
|
||||
},
|
||||
{
|
||||
lastterm, lastindex, lastindex, []pb.Entry{{Index: lastindex + 1, Term: 4}},
|
||||
lastindex + 1, true, lastindex, false,
|
||||
},
|
||||
{
|
||||
lastterm, lastindex, lastindex + 1, []pb.Entry{{Index: lastindex + 1, Term: 4}},
|
||||
lastindex + 1, true, lastindex + 1, false,
|
||||
},
|
||||
{
|
||||
lastterm, lastindex, lastindex + 2, []pb.Entry{{Index: lastindex + 1, Term: 4}},
|
||||
lastindex + 1, true, lastindex + 1, false, // do not increase commit higher than lastnewi
|
||||
},
|
||||
{
|
||||
lastterm, lastindex, lastindex + 2, []pb.Entry{{Index: lastindex + 1, Term: 4}, {Index: lastindex + 2, Term: 4}},
|
||||
lastindex + 2, true, lastindex + 2, false,
|
||||
},
|
||||
// match with the the entry in the middle
|
||||
{
|
||||
lastterm - 1, lastindex - 1, lastindex, []pb.Entry{{Index: lastindex, Term: 4}},
|
||||
lastindex, true, lastindex, false,
|
||||
},
|
||||
{
|
||||
lastterm - 2, lastindex - 2, lastindex, []pb.Entry{{Index: lastindex - 1, Term: 4}},
|
||||
lastindex - 1, true, lastindex - 1, false,
|
||||
},
|
||||
{
|
||||
lastterm - 3, lastindex - 3, lastindex, []pb.Entry{{Index: lastindex - 2, Term: 4}},
|
||||
lastindex - 2, true, lastindex - 2, true, // conflict with existing committed entry
|
||||
},
|
||||
{
|
||||
lastterm - 2, lastindex - 2, lastindex, []pb.Entry{{Index: lastindex - 1, Term: 4}, {Index: lastindex, Term: 4}},
|
||||
lastindex, true, lastindex, false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
raftLog := newLog(NewMemoryStorage(), raftLogger)
|
||||
raftLog.append(previousEnts...)
|
||||
raftLog.committed = commit
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wpanic {
|
||||
t.Errorf("%d: panic = %v, want %v", i, true, tt.wpanic)
|
||||
}
|
||||
}
|
||||
}()
|
||||
glasti, gappend := raftLog.maybeAppend(tt.index, tt.logTerm, tt.committed, tt.ents...)
|
||||
gcommit := raftLog.committed
|
||||
|
||||
if glasti != tt.wlasti {
|
||||
t.Errorf("#%d: lastindex = %d, want %d", i, glasti, tt.wlasti)
|
||||
}
|
||||
if gappend != tt.wappend {
|
||||
t.Errorf("#%d: append = %v, want %v", i, gappend, tt.wappend)
|
||||
}
|
||||
if gcommit != tt.wcommit {
|
||||
t.Errorf("#%d: committed = %d, want %d", i, gcommit, tt.wcommit)
|
||||
}
|
||||
if gappend && len(tt.ents) != 0 {
|
||||
gents, err := raftLog.slice(raftLog.lastIndex()-uint64(len(tt.ents))+1, raftLog.lastIndex()+1, noLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.ents, gents) {
|
||||
t.Errorf("#%d: appended entries = %v, want %v", i, gents, tt.ents)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompactionSideEffects ensures that all the log related functionality works correctly after
|
||||
// a compaction.
|
||||
func TestCompactionSideEffects(t *testing.T) {
|
||||
var i uint64
|
||||
// Populate the log with 1000 entries; 750 in stable storage and 250 in unstable.
|
||||
lastIndex := uint64(1000)
|
||||
unstableIndex := uint64(750)
|
||||
lastTerm := lastIndex
|
||||
storage := NewMemoryStorage()
|
||||
for i = 1; i <= unstableIndex; i++ {
|
||||
storage.Append([]pb.Entry{{Term: i, Index: i}})
|
||||
}
|
||||
raftLog := newLog(storage, raftLogger)
|
||||
for i = unstableIndex; i < lastIndex; i++ {
|
||||
raftLog.append(pb.Entry{Term: i + 1, Index: i + 1})
|
||||
}
|
||||
|
||||
ok := raftLog.maybeCommit(lastIndex, lastTerm)
|
||||
if !ok {
|
||||
t.Fatalf("maybeCommit returned false")
|
||||
}
|
||||
raftLog.appliedTo(raftLog.committed)
|
||||
|
||||
offset := uint64(500)
|
||||
storage.Compact(offset)
|
||||
|
||||
if raftLog.lastIndex() != lastIndex {
|
||||
t.Errorf("lastIndex = %d, want %d", raftLog.lastIndex(), lastIndex)
|
||||
}
|
||||
|
||||
for j := offset; j <= raftLog.lastIndex(); j++ {
|
||||
if mustTerm(raftLog.term(j)) != j {
|
||||
t.Errorf("term(%d) = %d, want %d", j, mustTerm(raftLog.term(j)), j)
|
||||
}
|
||||
}
|
||||
|
||||
for j := offset; j <= raftLog.lastIndex(); j++ {
|
||||
if !raftLog.matchTerm(j, j) {
|
||||
t.Errorf("matchTerm(%d) = false, want true", j)
|
||||
}
|
||||
}
|
||||
|
||||
unstableEnts := raftLog.unstableEntries()
|
||||
if g := len(unstableEnts); g != 250 {
|
||||
t.Errorf("len(unstableEntries) = %d, want = %d", g, 250)
|
||||
}
|
||||
if unstableEnts[0].Index != 751 {
|
||||
t.Errorf("Index = %d, want = %d", unstableEnts[0].Index, 751)
|
||||
}
|
||||
|
||||
prev := raftLog.lastIndex()
|
||||
raftLog.append(pb.Entry{Index: raftLog.lastIndex() + 1, Term: raftLog.lastIndex() + 1})
|
||||
if raftLog.lastIndex() != prev+1 {
|
||||
t.Errorf("lastIndex = %d, want = %d", raftLog.lastIndex(), prev+1)
|
||||
}
|
||||
|
||||
ents, err := raftLog.entries(raftLog.lastIndex(), noLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if len(ents) != 1 {
|
||||
t.Errorf("len(entries) = %d, want = %d", len(ents), 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNextEnts(t *testing.T) {
|
||||
snap := pb.Snapshot{
|
||||
Metadata: pb.SnapshotMetadata{Term: 1, Index: 3},
|
||||
}
|
||||
ents := []pb.Entry{
|
||||
{Term: 1, Index: 4},
|
||||
{Term: 1, Index: 5},
|
||||
{Term: 1, Index: 6},
|
||||
}
|
||||
tests := []struct {
|
||||
applied uint64
|
||||
hasNext bool
|
||||
}{
|
||||
{0, true},
|
||||
{3, true},
|
||||
{4, true},
|
||||
{5, false},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
storage := NewMemoryStorage()
|
||||
storage.ApplySnapshot(snap)
|
||||
raftLog := newLog(storage, raftLogger)
|
||||
raftLog.append(ents...)
|
||||
raftLog.maybeCommit(5, 1)
|
||||
raftLog.appliedTo(tt.applied)
|
||||
|
||||
hasNext := raftLog.hasNextEnts()
|
||||
if hasNext != tt.hasNext {
|
||||
t.Errorf("#%d: hasNext = %v, want %v", i, hasNext, tt.hasNext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextEnts(t *testing.T) {
|
||||
snap := pb.Snapshot{
|
||||
Metadata: pb.SnapshotMetadata{Term: 1, Index: 3},
|
||||
}
|
||||
ents := []pb.Entry{
|
||||
{Term: 1, Index: 4},
|
||||
{Term: 1, Index: 5},
|
||||
{Term: 1, Index: 6},
|
||||
}
|
||||
tests := []struct {
|
||||
applied uint64
|
||||
wents []pb.Entry
|
||||
}{
|
||||
{0, ents[:2]},
|
||||
{3, ents[:2]},
|
||||
{4, ents[1:2]},
|
||||
{5, nil},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
storage := NewMemoryStorage()
|
||||
storage.ApplySnapshot(snap)
|
||||
raftLog := newLog(storage, raftLogger)
|
||||
raftLog.append(ents...)
|
||||
raftLog.maybeCommit(5, 1)
|
||||
raftLog.appliedTo(tt.applied)
|
||||
|
||||
nents := raftLog.nextEnts()
|
||||
if !reflect.DeepEqual(nents, tt.wents) {
|
||||
t.Errorf("#%d: nents = %+v, want %+v", i, nents, tt.wents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnstableEnts ensures unstableEntries returns the unstable part of the
|
||||
// entries correctly.
|
||||
func TestUnstableEnts(t *testing.T) {
|
||||
previousEnts := []pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}}
|
||||
tests := []struct {
|
||||
unstable uint64
|
||||
wents []pb.Entry
|
||||
}{
|
||||
{3, nil},
|
||||
{1, previousEnts},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
// append stable entries to storage
|
||||
storage := NewMemoryStorage()
|
||||
storage.Append(previousEnts[:tt.unstable-1])
|
||||
|
||||
// append unstable entries to raftlog
|
||||
raftLog := newLog(storage, raftLogger)
|
||||
raftLog.append(previousEnts[tt.unstable-1:]...)
|
||||
|
||||
ents := raftLog.unstableEntries()
|
||||
if l := len(ents); l > 0 {
|
||||
raftLog.stableTo(ents[l-1].Index, ents[l-1].Term)
|
||||
}
|
||||
if !reflect.DeepEqual(ents, tt.wents) {
|
||||
t.Errorf("#%d: unstableEnts = %+v, want %+v", i, ents, tt.wents)
|
||||
}
|
||||
w := previousEnts[len(previousEnts)-1].Index + 1
|
||||
if g := raftLog.unstable.offset; g != w {
|
||||
t.Errorf("#%d: unstable = %d, want %d", i, g, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitTo(t *testing.T) {
|
||||
previousEnts := []pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}, {Term: 3, Index: 3}}
|
||||
commit := uint64(2)
|
||||
tests := []struct {
|
||||
commit uint64
|
||||
wcommit uint64
|
||||
wpanic bool
|
||||
}{
|
||||
{3, 3, false},
|
||||
{1, 2, false}, // never decrease
|
||||
{4, 0, true}, // commit out of range -> panic
|
||||
}
|
||||
for i, tt := range tests {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wpanic {
|
||||
t.Errorf("%d: panic = %v, want %v", i, true, tt.wpanic)
|
||||
}
|
||||
}
|
||||
}()
|
||||
raftLog := newLog(NewMemoryStorage(), raftLogger)
|
||||
raftLog.append(previousEnts...)
|
||||
raftLog.committed = commit
|
||||
raftLog.commitTo(tt.commit)
|
||||
if raftLog.committed != tt.wcommit {
|
||||
t.Errorf("#%d: committed = %d, want %d", i, raftLog.committed, tt.wcommit)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestStableTo(t *testing.T) {
|
||||
tests := []struct {
|
||||
stablei uint64
|
||||
stablet uint64
|
||||
wunstable uint64
|
||||
}{
|
||||
{1, 1, 2},
|
||||
{2, 2, 3},
|
||||
{2, 1, 1}, // bad term
|
||||
{3, 1, 1}, // bad index
|
||||
}
|
||||
for i, tt := range tests {
|
||||
raftLog := newLog(NewMemoryStorage(), raftLogger)
|
||||
raftLog.append([]pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}}...)
|
||||
raftLog.stableTo(tt.stablei, tt.stablet)
|
||||
if raftLog.unstable.offset != tt.wunstable {
|
||||
t.Errorf("#%d: unstable = %d, want %d", i, raftLog.unstable.offset, tt.wunstable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStableToWithSnap(t *testing.T) {
|
||||
snapi, snapt := uint64(5), uint64(2)
|
||||
tests := []struct {
|
||||
stablei uint64
|
||||
stablet uint64
|
||||
newEnts []pb.Entry
|
||||
|
||||
wunstable uint64
|
||||
}{
|
||||
{snapi + 1, snapt, nil, snapi + 1},
|
||||
{snapi, snapt, nil, snapi + 1},
|
||||
{snapi - 1, snapt, nil, snapi + 1},
|
||||
|
||||
{snapi + 1, snapt + 1, nil, snapi + 1},
|
||||
{snapi, snapt + 1, nil, snapi + 1},
|
||||
{snapi - 1, snapt + 1, nil, snapi + 1},
|
||||
|
||||
{snapi + 1, snapt, []pb.Entry{{Index: snapi + 1, Term: snapt}}, snapi + 2},
|
||||
{snapi, snapt, []pb.Entry{{Index: snapi + 1, Term: snapt}}, snapi + 1},
|
||||
{snapi - 1, snapt, []pb.Entry{{Index: snapi + 1, Term: snapt}}, snapi + 1},
|
||||
|
||||
{snapi + 1, snapt + 1, []pb.Entry{{Index: snapi + 1, Term: snapt}}, snapi + 1},
|
||||
{snapi, snapt + 1, []pb.Entry{{Index: snapi + 1, Term: snapt}}, snapi + 1},
|
||||
{snapi - 1, snapt + 1, []pb.Entry{{Index: snapi + 1, Term: snapt}}, snapi + 1},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
s := NewMemoryStorage()
|
||||
s.ApplySnapshot(pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: snapi, Term: snapt}})
|
||||
raftLog := newLog(s, raftLogger)
|
||||
raftLog.append(tt.newEnts...)
|
||||
raftLog.stableTo(tt.stablei, tt.stablet)
|
||||
if raftLog.unstable.offset != tt.wunstable {
|
||||
t.Errorf("#%d: unstable = %d, want %d", i, raftLog.unstable.offset, tt.wunstable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompaction ensures that the number of log entries is correct after compactions.
|
||||
func TestCompaction(t *testing.T) {
|
||||
tests := []struct {
|
||||
lastIndex uint64
|
||||
compact []uint64
|
||||
wleft []int
|
||||
wallow bool
|
||||
}{
|
||||
// out of upper bound
|
||||
{1000, []uint64{1001}, []int{-1}, false},
|
||||
{1000, []uint64{300, 500, 800, 900}, []int{700, 500, 200, 100}, true},
|
||||
// out of lower bound
|
||||
{1000, []uint64{300, 299}, []int{700, -1}, false},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if tt.wallow {
|
||||
t.Errorf("%d: allow = %v, want %v: %v", i, false, true, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
storage := NewMemoryStorage()
|
||||
for i := uint64(1); i <= tt.lastIndex; i++ {
|
||||
storage.Append([]pb.Entry{{Index: i}})
|
||||
}
|
||||
raftLog := newLog(storage, raftLogger)
|
||||
raftLog.maybeCommit(tt.lastIndex, 0)
|
||||
raftLog.appliedTo(raftLog.committed)
|
||||
|
||||
for j := 0; j < len(tt.compact); j++ {
|
||||
err := storage.Compact(tt.compact[j])
|
||||
if err != nil {
|
||||
if tt.wallow {
|
||||
t.Errorf("#%d.%d allow = %t, want %t", i, j, false, tt.wallow)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(raftLog.allEntries()) != tt.wleft[j] {
|
||||
t.Errorf("#%d.%d len = %d, want %d", i, j, len(raftLog.allEntries()), tt.wleft[j])
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogRestore(t *testing.T) {
|
||||
index := uint64(1000)
|
||||
term := uint64(1000)
|
||||
snap := pb.SnapshotMetadata{Index: index, Term: term}
|
||||
storage := NewMemoryStorage()
|
||||
storage.ApplySnapshot(pb.Snapshot{Metadata: snap})
|
||||
raftLog := newLog(storage, raftLogger)
|
||||
|
||||
if len(raftLog.allEntries()) != 0 {
|
||||
t.Errorf("len = %d, want 0", len(raftLog.allEntries()))
|
||||
}
|
||||
if raftLog.firstIndex() != index+1 {
|
||||
t.Errorf("firstIndex = %d, want %d", raftLog.firstIndex(), index+1)
|
||||
}
|
||||
if raftLog.committed != index {
|
||||
t.Errorf("committed = %d, want %d", raftLog.committed, index)
|
||||
}
|
||||
if raftLog.unstable.offset != index+1 {
|
||||
t.Errorf("unstable = %d, want %d", raftLog.unstable.offset, index+1)
|
||||
}
|
||||
if mustTerm(raftLog.term(index)) != term {
|
||||
t.Errorf("term = %d, want %d", mustTerm(raftLog.term(index)), term)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsOutOfBounds(t *testing.T) {
|
||||
offset := uint64(100)
|
||||
num := uint64(100)
|
||||
storage := NewMemoryStorage()
|
||||
storage.ApplySnapshot(pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: offset}})
|
||||
l := newLog(storage, raftLogger)
|
||||
for i := uint64(1); i <= num; i++ {
|
||||
l.append(pb.Entry{Index: i + offset})
|
||||
}
|
||||
|
||||
first := offset + 1
|
||||
tests := []struct {
|
||||
lo, hi uint64
|
||||
wpanic bool
|
||||
wErrCompacted bool
|
||||
}{
|
||||
{
|
||||
first - 2, first + 1,
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
first - 1, first + 1,
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
first, first,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
first + num/2, first + num/2,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
first + num - 1, first + num - 1,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
first + num, first + num,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
first + num, first + num + 1,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
first + num + 1, first + num + 1,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wpanic {
|
||||
t.Errorf("%d: panic = %v, want %v: %v", i, true, false, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
err := l.mustCheckOutOfBounds(tt.lo, tt.hi)
|
||||
if tt.wpanic {
|
||||
t.Errorf("#%d: panic = %v, want %v", i, false, true)
|
||||
}
|
||||
if tt.wErrCompacted && err != ErrCompacted {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, ErrCompacted)
|
||||
}
|
||||
if !tt.wErrCompacted && err != nil {
|
||||
t.Errorf("#%d: unexpected err %v", i, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerm(t *testing.T) {
|
||||
var i uint64
|
||||
offset := uint64(100)
|
||||
num := uint64(100)
|
||||
|
||||
storage := NewMemoryStorage()
|
||||
storage.ApplySnapshot(pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: offset, Term: 1}})
|
||||
l := newLog(storage, raftLogger)
|
||||
for i = 1; i < num; i++ {
|
||||
l.append(pb.Entry{Index: offset + i, Term: i})
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
index uint64
|
||||
w uint64
|
||||
}{
|
||||
{offset - 1, 0},
|
||||
{offset, 1},
|
||||
{offset + num/2, num / 2},
|
||||
{offset + num - 1, num - 1},
|
||||
{offset + num, 0},
|
||||
}
|
||||
|
||||
for j, tt := range tests {
|
||||
term := mustTerm(l.term(tt.index))
|
||||
if term != tt.w {
|
||||
t.Errorf("#%d: at = %d, want %d", j, term, tt.w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTermWithUnstableSnapshot(t *testing.T) {
|
||||
storagesnapi := uint64(100)
|
||||
unstablesnapi := storagesnapi + 5
|
||||
|
||||
storage := NewMemoryStorage()
|
||||
storage.ApplySnapshot(pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: storagesnapi, Term: 1}})
|
||||
l := newLog(storage, raftLogger)
|
||||
l.restore(pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: unstablesnapi, Term: 1}})
|
||||
|
||||
tests := []struct {
|
||||
index uint64
|
||||
w uint64
|
||||
}{
|
||||
// cannot get term from storage
|
||||
{storagesnapi, 0},
|
||||
// cannot get term from the gap between storage ents and unstable snapshot
|
||||
{storagesnapi + 1, 0},
|
||||
{unstablesnapi - 1, 0},
|
||||
// get term from unstable snapshot index
|
||||
{unstablesnapi, 1},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
term := mustTerm(l.term(tt.index))
|
||||
if term != tt.w {
|
||||
t.Errorf("#%d: at = %d, want %d", i, term, tt.w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlice(t *testing.T) {
|
||||
var i uint64
|
||||
offset := uint64(100)
|
||||
num := uint64(100)
|
||||
last := offset + num
|
||||
half := offset + num/2
|
||||
halfe := pb.Entry{Index: half, Term: half}
|
||||
|
||||
storage := NewMemoryStorage()
|
||||
storage.ApplySnapshot(pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: offset}})
|
||||
for i = 1; i < num/2; i++ {
|
||||
storage.Append([]pb.Entry{{Index: offset + i, Term: offset + i}})
|
||||
}
|
||||
l := newLog(storage, raftLogger)
|
||||
for i = num / 2; i < num; i++ {
|
||||
l.append(pb.Entry{Index: offset + i, Term: offset + i})
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
from uint64
|
||||
to uint64
|
||||
limit uint64
|
||||
|
||||
w []pb.Entry
|
||||
wpanic bool
|
||||
}{
|
||||
// test no limit
|
||||
{offset - 1, offset + 1, noLimit, nil, false},
|
||||
{offset, offset + 1, noLimit, nil, false},
|
||||
{half - 1, half + 1, noLimit, []pb.Entry{{Index: half - 1, Term: half - 1}, {Index: half, Term: half}}, false},
|
||||
{half, half + 1, noLimit, []pb.Entry{{Index: half, Term: half}}, false},
|
||||
{last - 1, last, noLimit, []pb.Entry{{Index: last - 1, Term: last - 1}}, false},
|
||||
{last, last + 1, noLimit, nil, true},
|
||||
|
||||
// test limit
|
||||
{half - 1, half + 1, 0, []pb.Entry{{Index: half - 1, Term: half - 1}}, false},
|
||||
{half - 1, half + 1, uint64(halfe.Size() + 1), []pb.Entry{{Index: half - 1, Term: half - 1}}, false},
|
||||
{half - 2, half + 1, uint64(halfe.Size() + 1), []pb.Entry{{Index: half - 2, Term: half - 2}}, false},
|
||||
{half - 1, half + 1, uint64(halfe.Size() * 2), []pb.Entry{{Index: half - 1, Term: half - 1}, {Index: half, Term: half}}, false},
|
||||
{half - 1, half + 2, uint64(halfe.Size() * 3), []pb.Entry{{Index: half - 1, Term: half - 1}, {Index: half, Term: half}, {Index: half + 1, Term: half + 1}}, false},
|
||||
{half, half + 2, uint64(halfe.Size()), []pb.Entry{{Index: half, Term: half}}, false},
|
||||
{half, half + 2, uint64(halfe.Size() * 2), []pb.Entry{{Index: half, Term: half}, {Index: half + 1, Term: half + 1}}, false},
|
||||
}
|
||||
|
||||
for j, tt := range tests {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wpanic {
|
||||
t.Errorf("%d: panic = %v, want %v: %v", j, true, false, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
g, err := l.slice(tt.from, tt.to, tt.limit)
|
||||
if tt.from <= offset && err != ErrCompacted {
|
||||
t.Fatalf("#%d: err = %v, want %v", j, err, ErrCompacted)
|
||||
}
|
||||
if tt.from > offset && err != nil {
|
||||
t.Fatalf("#%d: unexpected error %v", j, err)
|
||||
}
|
||||
if !reflect.DeepEqual(g, tt.w) {
|
||||
t.Errorf("#%d: from %d to %d = %v, want %v", j, tt.from, tt.to, g, tt.w)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func mustTerm(term uint64, err error) uint64 {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return term
|
||||
}
|
||||
359
vendor/go.etcd.io/etcd/raft/v3/log_unstable_test.go
generated
vendored
359
vendor/go.etcd.io/etcd/raft/v3/log_unstable_test.go
generated
vendored
@ -1,359 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
)
|
||||
|
||||
func TestUnstableMaybeFirstIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
entries []pb.Entry
|
||||
offset uint64
|
||||
snap *pb.Snapshot
|
||||
|
||||
wok bool
|
||||
windex uint64
|
||||
}{
|
||||
// no snapshot
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
false, 0,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{}, 0, nil,
|
||||
false, 0,
|
||||
},
|
||||
// has snapshot
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
true, 5,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
true, 5,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
u := unstable{
|
||||
entries: tt.entries,
|
||||
offset: tt.offset,
|
||||
snapshot: tt.snap,
|
||||
logger: raftLogger,
|
||||
}
|
||||
index, ok := u.maybeFirstIndex()
|
||||
if ok != tt.wok {
|
||||
t.Errorf("#%d: ok = %t, want %t", i, ok, tt.wok)
|
||||
}
|
||||
if index != tt.windex {
|
||||
t.Errorf("#%d: index = %d, want %d", i, index, tt.windex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaybeLastIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
entries []pb.Entry
|
||||
offset uint64
|
||||
snap *pb.Snapshot
|
||||
|
||||
wok bool
|
||||
windex uint64
|
||||
}{
|
||||
// last in entries
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
true, 5,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
true, 5,
|
||||
},
|
||||
// last in snapshot
|
||||
{
|
||||
[]pb.Entry{}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
true, 4,
|
||||
},
|
||||
// empty unstable
|
||||
{
|
||||
[]pb.Entry{}, 0, nil,
|
||||
false, 0,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
u := unstable{
|
||||
entries: tt.entries,
|
||||
offset: tt.offset,
|
||||
snapshot: tt.snap,
|
||||
logger: raftLogger,
|
||||
}
|
||||
index, ok := u.maybeLastIndex()
|
||||
if ok != tt.wok {
|
||||
t.Errorf("#%d: ok = %t, want %t", i, ok, tt.wok)
|
||||
}
|
||||
if index != tt.windex {
|
||||
t.Errorf("#%d: index = %d, want %d", i, index, tt.windex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnstableMaybeTerm(t *testing.T) {
|
||||
tests := []struct {
|
||||
entries []pb.Entry
|
||||
offset uint64
|
||||
snap *pb.Snapshot
|
||||
index uint64
|
||||
|
||||
wok bool
|
||||
wterm uint64
|
||||
}{
|
||||
// term from entries
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
5,
|
||||
true, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
6,
|
||||
false, 0,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
4,
|
||||
false, 0,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
5,
|
||||
true, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
6,
|
||||
false, 0,
|
||||
},
|
||||
// term from snapshot
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
4,
|
||||
true, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
3,
|
||||
false, 0,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
5,
|
||||
false, 0,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
4,
|
||||
true, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{}, 0, nil,
|
||||
5,
|
||||
false, 0,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
u := unstable{
|
||||
entries: tt.entries,
|
||||
offset: tt.offset,
|
||||
snapshot: tt.snap,
|
||||
logger: raftLogger,
|
||||
}
|
||||
term, ok := u.maybeTerm(tt.index)
|
||||
if ok != tt.wok {
|
||||
t.Errorf("#%d: ok = %t, want %t", i, ok, tt.wok)
|
||||
}
|
||||
if term != tt.wterm {
|
||||
t.Errorf("#%d: term = %d, want %d", i, term, tt.wterm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnstableRestore(t *testing.T) {
|
||||
u := unstable{
|
||||
entries: []pb.Entry{{Index: 5, Term: 1}},
|
||||
offset: 5,
|
||||
snapshot: &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
logger: raftLogger,
|
||||
}
|
||||
s := pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 6, Term: 2}}
|
||||
u.restore(s)
|
||||
|
||||
if u.offset != s.Metadata.Index+1 {
|
||||
t.Errorf("offset = %d, want %d", u.offset, s.Metadata.Index+1)
|
||||
}
|
||||
if len(u.entries) != 0 {
|
||||
t.Errorf("len = %d, want 0", len(u.entries))
|
||||
}
|
||||
if !reflect.DeepEqual(u.snapshot, &s) {
|
||||
t.Errorf("snap = %v, want %v", u.snapshot, &s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnstableStableTo(t *testing.T) {
|
||||
tests := []struct {
|
||||
entries []pb.Entry
|
||||
offset uint64
|
||||
snap *pb.Snapshot
|
||||
index, term uint64
|
||||
|
||||
woffset uint64
|
||||
wlen int
|
||||
}{
|
||||
{
|
||||
[]pb.Entry{}, 0, nil,
|
||||
5, 1,
|
||||
0, 0,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
5, 1, // stable to the first entry
|
||||
6, 0,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}, {Index: 6, Term: 1}}, 5, nil,
|
||||
5, 1, // stable to the first entry
|
||||
6, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 6, Term: 2}}, 6, nil,
|
||||
6, 1, // stable to the first entry and term mismatch
|
||||
6, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
4, 1, // stable to old entry
|
||||
5, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
4, 2, // stable to old entry
|
||||
5, 1,
|
||||
},
|
||||
// with snapshot
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
5, 1, // stable to the first entry
|
||||
6, 0,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}, {Index: 6, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
5, 1, // stable to the first entry
|
||||
6, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 6, Term: 2}}, 6, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 5, Term: 1}},
|
||||
6, 1, // stable to the first entry and term mismatch
|
||||
6, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 1}},
|
||||
4, 1, // stable to snapshot
|
||||
5, 1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 2}}, 5, &pb.Snapshot{Metadata: pb.SnapshotMetadata{Index: 4, Term: 2}},
|
||||
4, 1, // stable to old entry
|
||||
5, 1,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
u := unstable{
|
||||
entries: tt.entries,
|
||||
offset: tt.offset,
|
||||
snapshot: tt.snap,
|
||||
logger: raftLogger,
|
||||
}
|
||||
u.stableTo(tt.index, tt.term)
|
||||
if u.offset != tt.woffset {
|
||||
t.Errorf("#%d: offset = %d, want %d", i, u.offset, tt.woffset)
|
||||
}
|
||||
if len(u.entries) != tt.wlen {
|
||||
t.Errorf("#%d: len = %d, want %d", i, len(u.entries), tt.wlen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnstableTruncateAndAppend(t *testing.T) {
|
||||
tests := []struct {
|
||||
entries []pb.Entry
|
||||
offset uint64
|
||||
snap *pb.Snapshot
|
||||
toappend []pb.Entry
|
||||
|
||||
woffset uint64
|
||||
wentries []pb.Entry
|
||||
}{
|
||||
// append to the end
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
[]pb.Entry{{Index: 6, Term: 1}, {Index: 7, Term: 1}},
|
||||
5, []pb.Entry{{Index: 5, Term: 1}, {Index: 6, Term: 1}, {Index: 7, Term: 1}},
|
||||
},
|
||||
// replace the unstable entries
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
[]pb.Entry{{Index: 5, Term: 2}, {Index: 6, Term: 2}},
|
||||
5, []pb.Entry{{Index: 5, Term: 2}, {Index: 6, Term: 2}},
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}}, 5, nil,
|
||||
[]pb.Entry{{Index: 4, Term: 2}, {Index: 5, Term: 2}, {Index: 6, Term: 2}},
|
||||
4, []pb.Entry{{Index: 4, Term: 2}, {Index: 5, Term: 2}, {Index: 6, Term: 2}},
|
||||
},
|
||||
// truncate the existing entries and append
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}, {Index: 6, Term: 1}, {Index: 7, Term: 1}}, 5, nil,
|
||||
[]pb.Entry{{Index: 6, Term: 2}},
|
||||
5, []pb.Entry{{Index: 5, Term: 1}, {Index: 6, Term: 2}},
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 5, Term: 1}, {Index: 6, Term: 1}, {Index: 7, Term: 1}}, 5, nil,
|
||||
[]pb.Entry{{Index: 7, Term: 2}, {Index: 8, Term: 2}},
|
||||
5, []pb.Entry{{Index: 5, Term: 1}, {Index: 6, Term: 1}, {Index: 7, Term: 2}, {Index: 8, Term: 2}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
u := unstable{
|
||||
entries: tt.entries,
|
||||
offset: tt.offset,
|
||||
snapshot: tt.snap,
|
||||
logger: raftLogger,
|
||||
}
|
||||
u.truncateAndAppend(tt.toappend)
|
||||
if u.offset != tt.woffset {
|
||||
t.Errorf("#%d: offset = %d, want %d", i, u.offset, tt.woffset)
|
||||
}
|
||||
if !reflect.DeepEqual(u.entries, tt.wentries) {
|
||||
t.Errorf("#%d: entries = %v, want %v", i, u.entries, tt.wentries)
|
||||
}
|
||||
}
|
||||
}
|
||||
51
vendor/go.etcd.io/etcd/raft/v3/node_bench_test.go
generated
vendored
51
vendor/go.etcd.io/etcd/raft/v3/node_bench_test.go
generated
vendored
@ -1,51 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkOneNode(b *testing.B) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
s := newTestMemoryStorage(withPeers(1))
|
||||
rn := newTestRawNode(1, 10, 1, s)
|
||||
n := newNode(rn)
|
||||
go n.run()
|
||||
|
||||
defer n.Stop()
|
||||
|
||||
n.Campaign(ctx)
|
||||
go func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
n.Propose(ctx, []byte("foo"))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
rd := <-n.Ready()
|
||||
s.Append(rd.Entries)
|
||||
// a reasonable disk sync latency
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
n.Advance()
|
||||
if rd.HardState.Commit == uint64(b.N+1) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
1020
vendor/go.etcd.io/etcd/raft/v3/node_test.go
generated
vendored
1020
vendor/go.etcd.io/etcd/raft/v3/node_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
40
vendor/go.etcd.io/etcd/raft/v3/quorum/bench_test.go
generated
vendored
40
vendor/go.etcd.io/etcd/raft/v3/quorum/bench_test.go
generated
vendored
@ -1,40 +0,0 @@
|
||||
// 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 quorum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkMajorityConfig_CommittedIndex(b *testing.B) {
|
||||
// go test -run - -bench . -benchmem ./raft/quorum
|
||||
for _, n := range []int{1, 3, 5, 7, 9, 11} {
|
||||
b.Run(fmt.Sprintf("voters=%d", n), func(b *testing.B) {
|
||||
c := MajorityConfig{}
|
||||
l := mapAckIndexer{}
|
||||
for i := uint64(0); i < uint64(n); i++ {
|
||||
c[i+1] = struct{}{}
|
||||
l[i+1] = Index(rand.Int63n(math.MaxInt64))
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = c.CommittedIndex(l)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
250
vendor/go.etcd.io/etcd/raft/v3/quorum/datadriven_test.go
generated
vendored
250
vendor/go.etcd.io/etcd/raft/v3/quorum/datadriven_test.go
generated
vendored
@ -1,250 +0,0 @@
|
||||
// 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 quorum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cockroachdb/datadriven"
|
||||
)
|
||||
|
||||
// TestDataDriven parses and executes the test cases in ./testdata/*. An entry
|
||||
// in such a file specifies the command, which is either of "committed" to check
|
||||
// CommittedIndex or "vote" to verify a VoteResult. The underlying configuration
|
||||
// and inputs are specified via the arguments 'cfg' and 'cfgj' (for the majority
|
||||
// config and, optionally, majority config joint to the first one) and 'idx'
|
||||
// (for CommittedIndex) and 'votes' (for VoteResult).
|
||||
//
|
||||
// Internally, the harness runs some additional checks on each test case for
|
||||
// which it is known that the result shouldn't change. For example,
|
||||
// interchanging the majority configurations of a joint quorum must not
|
||||
// influence the result; if it does, this is noted in the test's output.
|
||||
func TestDataDriven(t *testing.T) {
|
||||
datadriven.Walk(t, "testdata", func(t *testing.T, path string) {
|
||||
datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
|
||||
// Two majority configs. The first one is always used (though it may
|
||||
// be empty) and the second one is used iff joint is true.
|
||||
var joint bool
|
||||
var ids, idsj []uint64
|
||||
// The committed indexes for the nodes in the config in the order in
|
||||
// which they appear in (ids,idsj), without repetition. An underscore
|
||||
// denotes an omission (i.e. no information for this voter); this is
|
||||
// different from 0. For example,
|
||||
//
|
||||
// cfg=(1,2) cfgj=(2,3,4) idxs=(_,5,_,7) initializes the idx for voter 2
|
||||
// to 5 and that for voter 4 to 7 (and no others).
|
||||
//
|
||||
// cfgj=zero is specified to instruct the test harness to treat cfgj
|
||||
// as zero instead of not specified (i.e. it will trigger a joint
|
||||
// quorum test instead of a majority quorum test for cfg only).
|
||||
var idxs []Index
|
||||
// Votes. These are initialized similar to idxs except the only values
|
||||
// used are 1 (voted against) and 2 (voted for). This looks awkward,
|
||||
// but is convenient because it allows sharing code between the two.
|
||||
var votes []Index
|
||||
|
||||
// Parse the args.
|
||||
for _, arg := range d.CmdArgs {
|
||||
for i := range arg.Vals {
|
||||
switch arg.Key {
|
||||
case "cfg":
|
||||
var n uint64
|
||||
arg.Scan(t, i, &n)
|
||||
ids = append(ids, n)
|
||||
case "cfgj":
|
||||
joint = true
|
||||
if arg.Vals[i] == "zero" {
|
||||
if len(arg.Vals) != 1 {
|
||||
t.Fatalf("cannot mix 'zero' into configuration")
|
||||
}
|
||||
} else {
|
||||
var n uint64
|
||||
arg.Scan(t, i, &n)
|
||||
idsj = append(idsj, n)
|
||||
}
|
||||
case "idx":
|
||||
var n uint64
|
||||
// Register placeholders as zeroes.
|
||||
if arg.Vals[i] != "_" {
|
||||
arg.Scan(t, i, &n)
|
||||
if n == 0 {
|
||||
// This is a restriction caused by the above
|
||||
// special-casing for _.
|
||||
t.Fatalf("cannot use 0 as idx")
|
||||
}
|
||||
}
|
||||
idxs = append(idxs, Index(n))
|
||||
case "votes":
|
||||
var s string
|
||||
arg.Scan(t, i, &s)
|
||||
switch s {
|
||||
case "y":
|
||||
votes = append(votes, 2)
|
||||
case "n":
|
||||
votes = append(votes, 1)
|
||||
case "_":
|
||||
votes = append(votes, 0)
|
||||
default:
|
||||
t.Fatalf("unknown vote: %s", s)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unknown arg %s", arg.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the two majority configs.
|
||||
c := MajorityConfig{}
|
||||
for _, id := range ids {
|
||||
c[id] = struct{}{}
|
||||
}
|
||||
cj := MajorityConfig{}
|
||||
for _, id := range idsj {
|
||||
cj[id] = struct{}{}
|
||||
}
|
||||
|
||||
// Helper that returns an AckedIndexer which has the specified indexes
|
||||
// mapped to the right IDs.
|
||||
makeLookuper := func(idxs []Index, ids, idsj []uint64) mapAckIndexer {
|
||||
l := mapAckIndexer{}
|
||||
var p int // next to consume from idxs
|
||||
for _, id := range append(append([]uint64(nil), ids...), idsj...) {
|
||||
if _, ok := l[id]; ok {
|
||||
continue
|
||||
}
|
||||
if p < len(idxs) {
|
||||
// NB: this creates zero entries for placeholders that we remove later.
|
||||
// The upshot of doing it that way is to avoid having to specify place-
|
||||
// holders multiple times when omitting voters present in both halves of
|
||||
// a joint config.
|
||||
l[id] = idxs[p]
|
||||
p++
|
||||
}
|
||||
}
|
||||
|
||||
for id := range l {
|
||||
// Zero entries are created by _ placeholders; we don't want
|
||||
// them in the lookuper because "no entry" is different from
|
||||
// "zero entry". Note that we prevent tests from specifying
|
||||
// zero commit indexes, so that there's no confusion between
|
||||
// the two concepts.
|
||||
if l[id] == 0 {
|
||||
delete(l, id)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
{
|
||||
input := idxs
|
||||
if d.Cmd == "vote" {
|
||||
input = votes
|
||||
}
|
||||
if voters := JointConfig([2]MajorityConfig{c, cj}).IDs(); len(voters) != len(input) {
|
||||
return fmt.Sprintf("error: mismatched input (explicit or _) for voters %v: %v",
|
||||
voters, input)
|
||||
}
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
switch d.Cmd {
|
||||
case "committed":
|
||||
l := makeLookuper(idxs, ids, idsj)
|
||||
|
||||
// Branch based on whether this is a majority or joint quorum
|
||||
// test case.
|
||||
if !joint {
|
||||
idx := c.CommittedIndex(l)
|
||||
fmt.Fprint(&buf, c.Describe(l))
|
||||
// These alternative computations should return the same
|
||||
// result. If not, print to the output.
|
||||
if aIdx := alternativeMajorityCommittedIndex(c, l); aIdx != idx {
|
||||
fmt.Fprintf(&buf, "%s <-- via alternative computation\n", aIdx)
|
||||
}
|
||||
// Joining a majority with the empty majority should give same result.
|
||||
if aIdx := JointConfig([2]MajorityConfig{c, {}}).CommittedIndex(l); aIdx != idx {
|
||||
fmt.Fprintf(&buf, "%s <-- via zero-joint quorum\n", aIdx)
|
||||
}
|
||||
// Joining a majority with itself should give same result.
|
||||
if aIdx := JointConfig([2]MajorityConfig{c, c}).CommittedIndex(l); aIdx != idx {
|
||||
fmt.Fprintf(&buf, "%s <-- via self-joint quorum\n", aIdx)
|
||||
}
|
||||
overlay := func(c MajorityConfig, l AckedIndexer, id uint64, idx Index) AckedIndexer {
|
||||
ll := mapAckIndexer{}
|
||||
for iid := range c {
|
||||
if iid == id {
|
||||
ll[iid] = idx
|
||||
} else if idx, ok := l.AckedIndex(iid); ok {
|
||||
ll[iid] = idx
|
||||
}
|
||||
}
|
||||
return ll
|
||||
}
|
||||
for id := range c {
|
||||
iidx, _ := l.AckedIndex(id)
|
||||
if idx > iidx && iidx > 0 {
|
||||
// If the committed index was definitely above the currently
|
||||
// inspected idx, the result shouldn't change if we lower it
|
||||
// further.
|
||||
lo := overlay(c, l, id, iidx-1)
|
||||
if aIdx := c.CommittedIndex(lo); aIdx != idx {
|
||||
fmt.Fprintf(&buf, "%s <-- overlaying %d->%d", aIdx, id, iidx)
|
||||
}
|
||||
lo = overlay(c, l, id, 0)
|
||||
if aIdx := c.CommittedIndex(lo); aIdx != idx {
|
||||
fmt.Fprintf(&buf, "%s <-- overlaying %d->0", aIdx, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s\n", idx)
|
||||
} else {
|
||||
cc := JointConfig([2]MajorityConfig{c, cj})
|
||||
fmt.Fprint(&buf, cc.Describe(l))
|
||||
idx := cc.CommittedIndex(l)
|
||||
// Interchanging the majorities shouldn't make a difference. If it does, print.
|
||||
if aIdx := JointConfig([2]MajorityConfig{cj, c}).CommittedIndex(l); aIdx != idx {
|
||||
fmt.Fprintf(&buf, "%s <-- via symmetry\n", aIdx)
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s\n", idx)
|
||||
}
|
||||
case "vote":
|
||||
ll := makeLookuper(votes, ids, idsj)
|
||||
l := map[uint64]bool{}
|
||||
for id, v := range ll {
|
||||
l[id] = v != 1 // NB: 1 == false, 2 == true
|
||||
}
|
||||
|
||||
if !joint {
|
||||
// Test a majority quorum.
|
||||
r := c.VoteResult(l)
|
||||
fmt.Fprintf(&buf, "%v\n", r)
|
||||
} else {
|
||||
// Run a joint quorum test case.
|
||||
r := JointConfig([2]MajorityConfig{c, cj}).VoteResult(l)
|
||||
// Interchanging the majorities shouldn't make a difference. If it does, print.
|
||||
if ar := JointConfig([2]MajorityConfig{cj, c}).VoteResult(l); ar != r {
|
||||
fmt.Fprintf(&buf, "%v <-- via symmetry\n", ar)
|
||||
}
|
||||
fmt.Fprintf(&buf, "%v\n", r)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unknown command: %s", d.Cmd)
|
||||
}
|
||||
return buf.String()
|
||||
})
|
||||
})
|
||||
}
|
||||
122
vendor/go.etcd.io/etcd/raft/v3/quorum/quick_test.go
generated
vendored
122
vendor/go.etcd.io/etcd/raft/v3/quorum/quick_test.go
generated
vendored
@ -1,122 +0,0 @@
|
||||
// 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 quorum
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
// TestQuick uses quickcheck to heuristically assert that the main
|
||||
// implementation of (MajorityConfig).CommittedIndex agrees with a "dumb"
|
||||
// alternative version.
|
||||
func TestQuick(t *testing.T) {
|
||||
cfg := &quick.Config{
|
||||
MaxCount: 50000,
|
||||
}
|
||||
|
||||
t.Run("majority_commit", func(t *testing.T) {
|
||||
fn1 := func(c memberMap, l idxMap) uint64 {
|
||||
return uint64(MajorityConfig(c).CommittedIndex(mapAckIndexer(l)))
|
||||
}
|
||||
fn2 := func(c memberMap, l idxMap) uint64 {
|
||||
return uint64(alternativeMajorityCommittedIndex(MajorityConfig(c), mapAckIndexer(l)))
|
||||
}
|
||||
if err := quick.CheckEqual(fn1, fn2, cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// smallRandIdxMap returns a reasonably sized map of ids to commit indexes.
|
||||
func smallRandIdxMap(rand *rand.Rand, _ int) map[uint64]Index {
|
||||
// Hard-code a reasonably small size here (quick will hard-code 50, which
|
||||
// is not useful here).
|
||||
size := 10
|
||||
|
||||
n := rand.Intn(size)
|
||||
ids := rand.Perm(2 * n)[:n]
|
||||
idxs := make([]int, len(ids))
|
||||
for i := range idxs {
|
||||
idxs[i] = rand.Intn(n)
|
||||
}
|
||||
|
||||
m := map[uint64]Index{}
|
||||
for i := range ids {
|
||||
m[uint64(ids[i])] = Index(idxs[i])
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
type idxMap map[uint64]Index
|
||||
|
||||
func (idxMap) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
m := smallRandIdxMap(rand, size)
|
||||
return reflect.ValueOf(m)
|
||||
}
|
||||
|
||||
type memberMap map[uint64]struct{}
|
||||
|
||||
func (memberMap) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
m := smallRandIdxMap(rand, size)
|
||||
mm := map[uint64]struct{}{}
|
||||
for id := range m {
|
||||
mm[id] = struct{}{}
|
||||
}
|
||||
return reflect.ValueOf(mm)
|
||||
}
|
||||
|
||||
// This is an alternative implementation of (MajorityConfig).CommittedIndex(l).
|
||||
func alternativeMajorityCommittedIndex(c MajorityConfig, l AckedIndexer) Index {
|
||||
if len(c) == 0 {
|
||||
return math.MaxUint64
|
||||
}
|
||||
|
||||
idToIdx := map[uint64]Index{}
|
||||
for id := range c {
|
||||
if idx, ok := l.AckedIndex(id); ok {
|
||||
idToIdx[id] = idx
|
||||
}
|
||||
}
|
||||
|
||||
// Build a map from index to voters who have acked that or any higher index.
|
||||
idxToVotes := map[Index]int{}
|
||||
for _, idx := range idToIdx {
|
||||
idxToVotes[idx] = 0
|
||||
}
|
||||
|
||||
for _, idx := range idToIdx {
|
||||
for idy := range idxToVotes {
|
||||
if idy > idx {
|
||||
continue
|
||||
}
|
||||
idxToVotes[idy]++
|
||||
}
|
||||
}
|
||||
|
||||
// Find the maximum index that has achieved quorum.
|
||||
q := len(c)/2 + 1
|
||||
var maxQuorumIdx Index
|
||||
for idx, n := range idxToVotes {
|
||||
if n >= q && idx > maxQuorumIdx {
|
||||
maxQuorumIdx = idx
|
||||
}
|
||||
}
|
||||
|
||||
return maxQuorumIdx
|
||||
}
|
||||
481
vendor/go.etcd.io/etcd/raft/v3/quorum/testdata/joint_commit.txt
generated
vendored
481
vendor/go.etcd.io/etcd/raft/v3/quorum/testdata/joint_commit.txt
generated
vendored
@ -1,481 +0,0 @@
|
||||
# No difference between a simple majority quorum and a simple majority quorum
|
||||
# joint with an empty majority quorum. (This is asserted for all datadriven tests
|
||||
# by the framework, so we don't dwell on it more).
|
||||
#
|
||||
# Note that by specifying cfgj explicitly we tell the test harness to treat the
|
||||
# input as a joint quorum and not a majority quorum. If we didn't specify
|
||||
# cfgj=zero the test would pass just the same, but it wouldn't be exercising the
|
||||
# joint quorum path.
|
||||
committed cfg=(1,2,3) cfgj=zero idx=(100,101,99)
|
||||
----
|
||||
idx
|
||||
x> 100 (id=1)
|
||||
xx> 101 (id=2)
|
||||
> 99 (id=3)
|
||||
100
|
||||
|
||||
# Joint nonoverlapping singleton quorums.
|
||||
|
||||
committed cfg=(1) cfgj=(2) idx=(_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
0
|
||||
|
||||
# Voter 1 has 100 committed, 2 nothing. This means we definitely won't commit
|
||||
# past 100.
|
||||
committed cfg=(1) cfgj=(2) idx=(100,_)
|
||||
----
|
||||
idx
|
||||
x> 100 (id=1)
|
||||
? 0 (id=2)
|
||||
0
|
||||
|
||||
# Committed index collapses once both majorities do, to the lower index.
|
||||
committed cfg=(1) cfgj=(2) idx=(13, 100)
|
||||
----
|
||||
idx
|
||||
> 13 (id=1)
|
||||
x> 100 (id=2)
|
||||
13
|
||||
|
||||
# Joint overlapping (i.e. identical) singleton quorum.
|
||||
|
||||
committed cfg=(1) cfgj=(1) idx=(_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
0
|
||||
|
||||
committed cfg=(1) cfgj=(1) idx=(100)
|
||||
----
|
||||
idx
|
||||
> 100 (id=1)
|
||||
100
|
||||
|
||||
|
||||
|
||||
# Two-node config joint with non-overlapping single node config
|
||||
committed cfg=(1,3) cfgj=(2) idx=(_,_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
0
|
||||
|
||||
committed cfg=(1,3) cfgj=(2) idx=(100,_,_)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
0
|
||||
|
||||
# 1 has 100 committed, 2 has 50 (collapsing half of the joint quorum to 50).
|
||||
committed cfg=(1,3) cfgj=(2) idx=(100,_,50)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
x> 50 (id=2)
|
||||
? 0 (id=3)
|
||||
0
|
||||
|
||||
# 2 reports 45, collapsing the other half (to 45).
|
||||
committed cfg=(1,3) cfgj=(2) idx=(100,45,50)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
x> 50 (id=2)
|
||||
> 45 (id=3)
|
||||
45
|
||||
|
||||
# Two-node config with overlapping single-node config.
|
||||
|
||||
committed cfg=(1,2) cfgj=(2) idx=(_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
0
|
||||
|
||||
# 1 reports 100.
|
||||
committed cfg=(1,2) cfgj=(2) idx=(100,_)
|
||||
----
|
||||
idx
|
||||
x> 100 (id=1)
|
||||
? 0 (id=2)
|
||||
0
|
||||
|
||||
# 2 reports 100.
|
||||
committed cfg=(1,2) cfgj=(2) idx=(_,100)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
x> 100 (id=2)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(2) idx=(50,100)
|
||||
----
|
||||
idx
|
||||
> 50 (id=1)
|
||||
x> 100 (id=2)
|
||||
50
|
||||
|
||||
committed cfg=(1,2) cfgj=(2) idx=(100,50)
|
||||
----
|
||||
idx
|
||||
x> 100 (id=1)
|
||||
> 50 (id=2)
|
||||
50
|
||||
|
||||
|
||||
|
||||
# Joint non-overlapping two-node configs.
|
||||
|
||||
committed cfg=(1,2) cfgj=(3,4) idx=(50,_,_,_)
|
||||
----
|
||||
idx
|
||||
xxx> 50 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
? 0 (id=4)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(3,4) idx=(50,_,49,_)
|
||||
----
|
||||
idx
|
||||
xxx> 50 (id=1)
|
||||
? 0 (id=2)
|
||||
xx> 49 (id=3)
|
||||
? 0 (id=4)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(3,4) idx=(50,48,49,_)
|
||||
----
|
||||
idx
|
||||
xxx> 50 (id=1)
|
||||
x> 48 (id=2)
|
||||
xx> 49 (id=3)
|
||||
? 0 (id=4)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(3,4) idx=(50,48,49,47)
|
||||
----
|
||||
idx
|
||||
xxx> 50 (id=1)
|
||||
x> 48 (id=2)
|
||||
xx> 49 (id=3)
|
||||
> 47 (id=4)
|
||||
47
|
||||
|
||||
# Joint overlapping two-node configs.
|
||||
committed cfg=(1,2) cfgj=(2,3) idx=(_,_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(2,3) idx=(100,_,_)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(2,3) idx=(_,100,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
xx> 100 (id=2)
|
||||
? 0 (id=3)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(2,3) idx=(_,100,99)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
xx> 100 (id=2)
|
||||
x> 99 (id=3)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(2,3) idx=(101,100,99)
|
||||
----
|
||||
idx
|
||||
xx> 101 (id=1)
|
||||
x> 100 (id=2)
|
||||
> 99 (id=3)
|
||||
99
|
||||
|
||||
# Joint identical two-node configs.
|
||||
committed cfg=(1,2) cfgj=(1,2) idx=(_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(1,2) idx=(_,40)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
x> 40 (id=2)
|
||||
0
|
||||
|
||||
committed cfg=(1,2) cfgj=(1,2) idx=(41,40)
|
||||
----
|
||||
idx
|
||||
x> 41 (id=1)
|
||||
> 40 (id=2)
|
||||
40
|
||||
|
||||
|
||||
|
||||
# Joint disjoint three-node configs.
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(4,5,6) idx=(_,_,_,_,_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
? 0 (id=4)
|
||||
? 0 (id=5)
|
||||
? 0 (id=6)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(4,5,6) idx=(100,_,_,_,_,_)
|
||||
----
|
||||
idx
|
||||
xxxxx> 100 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
? 0 (id=4)
|
||||
? 0 (id=5)
|
||||
? 0 (id=6)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(4,5,6) idx=(100,_,_,90,_,_)
|
||||
----
|
||||
idx
|
||||
xxxxx> 100 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
xxxx> 90 (id=4)
|
||||
? 0 (id=5)
|
||||
? 0 (id=6)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(4,5,6) idx=(100,99,_,_,_,_)
|
||||
----
|
||||
idx
|
||||
xxxxx> 100 (id=1)
|
||||
xxxx> 99 (id=2)
|
||||
? 0 (id=3)
|
||||
? 0 (id=4)
|
||||
? 0 (id=5)
|
||||
? 0 (id=6)
|
||||
0
|
||||
|
||||
# First quorum <= 99, second one <= 97. Both quorums guarantee that 90 is
|
||||
# committed.
|
||||
committed cfg=(1,2,3) cfgj=(4,5,6) idx=(_,99,90,97,95,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
xxxxx> 99 (id=2)
|
||||
xx> 90 (id=3)
|
||||
xxxx> 97 (id=4)
|
||||
xxx> 95 (id=5)
|
||||
? 0 (id=6)
|
||||
90
|
||||
|
||||
# First quorum collapsed to 92. Second one already had at least 95 committed,
|
||||
# so the result also collapses.
|
||||
committed cfg=(1,2,3) cfgj=(4,5,6) idx=(92,99,90,97,95,_)
|
||||
----
|
||||
idx
|
||||
xx> 92 (id=1)
|
||||
xxxxx> 99 (id=2)
|
||||
x> 90 (id=3)
|
||||
xxxx> 97 (id=4)
|
||||
xxx> 95 (id=5)
|
||||
? 0 (id=6)
|
||||
92
|
||||
|
||||
# Second quorum collapses, but nothing changes in the output.
|
||||
committed cfg=(1,2,3) cfgj=(4,5,6) idx=(92,99,90,97,95,77)
|
||||
----
|
||||
idx
|
||||
xx> 92 (id=1)
|
||||
xxxxx> 99 (id=2)
|
||||
x> 90 (id=3)
|
||||
xxxx> 97 (id=4)
|
||||
xxx> 95 (id=5)
|
||||
> 77 (id=6)
|
||||
92
|
||||
|
||||
|
||||
# Joint overlapping three-node configs.
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(1,4,5) idx=(_,_,_,_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
? 0 (id=4)
|
||||
? 0 (id=5)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(1,4,5) idx=(100,_,_,_,_)
|
||||
----
|
||||
idx
|
||||
xxxx> 100 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
? 0 (id=4)
|
||||
? 0 (id=5)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(1,4,5) idx=(100,101,_,_,_)
|
||||
----
|
||||
idx
|
||||
xxx> 100 (id=1)
|
||||
xxxx> 101 (id=2)
|
||||
? 0 (id=3)
|
||||
? 0 (id=4)
|
||||
? 0 (id=5)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(1,4,5) idx=(100,101,100,_,_)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
xxxx> 101 (id=2)
|
||||
> 100 (id=3)
|
||||
? 0 (id=4)
|
||||
? 0 (id=5)
|
||||
0
|
||||
|
||||
# Second quorum could commit either 98 or 99, but first quorum is open.
|
||||
committed cfg=(1,2,3) cfgj=(1,4,5) idx=(_,100,_,99,98)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
xxxx> 100 (id=2)
|
||||
? 0 (id=3)
|
||||
xxx> 99 (id=4)
|
||||
xx> 98 (id=5)
|
||||
0
|
||||
|
||||
# Additionally, first quorum can commit either 100 or 99
|
||||
committed cfg=(1,2,3) cfgj=(1,4,5) idx=(_,100,99,99,98)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
xxxx> 100 (id=2)
|
||||
xx> 99 (id=3)
|
||||
> 99 (id=4)
|
||||
x> 98 (id=5)
|
||||
98
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(1,4,5) idx=(1,100,99,99,98)
|
||||
----
|
||||
idx
|
||||
> 1 (id=1)
|
||||
xxxx> 100 (id=2)
|
||||
xx> 99 (id=3)
|
||||
> 99 (id=4)
|
||||
x> 98 (id=5)
|
||||
98
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(1,4,5) idx=(100,100,99,99,98)
|
||||
----
|
||||
idx
|
||||
xxx> 100 (id=1)
|
||||
> 100 (id=2)
|
||||
x> 99 (id=3)
|
||||
> 99 (id=4)
|
||||
> 98 (id=5)
|
||||
99
|
||||
|
||||
|
||||
# More overlap.
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(2,3,4) idx=(_,_,_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
? 0 (id=4)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(2,3,4) idx=(_,100,99,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
xxx> 100 (id=2)
|
||||
xx> 99 (id=3)
|
||||
? 0 (id=4)
|
||||
99
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(2,3,4) idx=(98,100,99,_)
|
||||
----
|
||||
idx
|
||||
x> 98 (id=1)
|
||||
xxx> 100 (id=2)
|
||||
xx> 99 (id=3)
|
||||
? 0 (id=4)
|
||||
99
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(2,3,4) idx=(100,100,99,_)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
> 100 (id=2)
|
||||
x> 99 (id=3)
|
||||
? 0 (id=4)
|
||||
99
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(2,3,4) idx=(100,100,99,98)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
> 100 (id=2)
|
||||
x> 99 (id=3)
|
||||
> 98 (id=4)
|
||||
99
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(2,3,4) idx=(100,_,_,101)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
xxx> 101 (id=4)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) cfgj=(2,3,4) idx=(100,99,_,101)
|
||||
----
|
||||
idx
|
||||
xx> 100 (id=1)
|
||||
x> 99 (id=2)
|
||||
? 0 (id=3)
|
||||
xxx> 101 (id=4)
|
||||
99
|
||||
|
||||
# Identical. This is also exercised in the test harness, so it's listed here
|
||||
# only briefly.
|
||||
committed cfg=(1,2,3) cfgj=(1,2,3) idx=(50,45,_)
|
||||
----
|
||||
idx
|
||||
xx> 50 (id=1)
|
||||
x> 45 (id=2)
|
||||
? 0 (id=3)
|
||||
45
|
||||
165
vendor/go.etcd.io/etcd/raft/v3/quorum/testdata/joint_vote.txt
generated
vendored
165
vendor/go.etcd.io/etcd/raft/v3/quorum/testdata/joint_vote.txt
generated
vendored
@ -1,165 +0,0 @@
|
||||
# Empty joint config wins all votes. This isn't used in production. Note that
|
||||
# by specifying cfgj explicitly we tell the test harness to treat the input as
|
||||
# a joint quorum and not a majority quorum.
|
||||
vote cfgj=zero
|
||||
----
|
||||
VoteWon
|
||||
|
||||
# More examples with close to trivial configs.
|
||||
|
||||
vote cfg=(1) cfgj=zero votes=(_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1) cfgj=zero votes=(y)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(1) cfgj=zero votes=(n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1) cfgj=(1) votes=(_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1) cfgj=(1) votes=(y)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(1) cfgj=(1) votes=(n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1) cfgj=(2) votes=(_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1) cfgj=(2) votes=(y,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1) cfgj=(2) votes=(y,y)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(1) cfgj=(2) votes=(y,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1) cfgj=(2) votes=(n,_)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1) cfgj=(2) votes=(n,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1) cfgj=(2) votes=(n,y)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
# Two node configs.
|
||||
|
||||
vote cfg=(1,2) cfgj=(3,4) votes=(_,_,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2) cfgj=(3,4) votes=(y,_,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2) cfgj=(3,4) votes=(y,y,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2) cfgj=(3,4) votes=(y,y,n,_)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2) cfgj=(3,4) votes=(y,y,n,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2) cfgj=(3,4) votes=(y,y,y,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2) cfgj=(3,4) votes=(y,y,y,y)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(1,2) cfgj=(2,3) votes=(_,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2) cfgj=(2,3) votes=(_,n,_)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2) cfgj=(2,3) votes=(y,y,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2) cfgj=(2,3) votes=(y,y,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2) cfgj=(2,3) votes=(y,y,y)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(1,2) cfgj=(1,2) votes=(_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2) cfgj=(1,2) votes=(y,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2) cfgj=(1,2) votes=(y,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2) cfgj=(1,2) votes=(n,_)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2) cfgj=(1,2) votes=(n,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
|
||||
# Simple example for overlapping three node configs.
|
||||
|
||||
vote cfg=(1,2,3) cfgj=(2,3,4) votes=(_,_,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2,3) cfgj=(2,3,4) votes=(_,n,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2,3) cfgj=(2,3,4) votes=(_,n,n,_)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2,3) cfgj=(2,3,4) votes=(_,y,y,_)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(1,2,3) cfgj=(2,3,4) votes=(y,y,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2,3) cfgj=(2,3,4) votes=(y,y,n,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2,3) cfgj=(2,3,4) votes=(y,y,n,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(1,2,3) cfgj=(2,3,4) votes=(y,y,n,y)
|
||||
----
|
||||
VoteWon
|
||||
153
vendor/go.etcd.io/etcd/raft/v3/quorum/testdata/majority_commit.txt
generated
vendored
153
vendor/go.etcd.io/etcd/raft/v3/quorum/testdata/majority_commit.txt
generated
vendored
@ -1,153 +0,0 @@
|
||||
# The empty quorum commits "everything". This is useful for its use in joint
|
||||
# quorums.
|
||||
committed
|
||||
----
|
||||
<empty majority quorum>∞
|
||||
|
||||
|
||||
|
||||
# A single voter quorum is not final when no index is known.
|
||||
committed cfg=(1) idx=(_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
0
|
||||
|
||||
# When an index is known, that's the committed index, and that's final.
|
||||
committed cfg=(1) idx=(12)
|
||||
----
|
||||
idx
|
||||
> 12 (id=1)
|
||||
12
|
||||
|
||||
|
||||
|
||||
|
||||
# With two nodes, start out similarly.
|
||||
committed cfg=(1, 2) idx=(_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
0
|
||||
|
||||
# The first committed index becomes known (for n1). Nothing changes in the
|
||||
# output because idx=12 is not known to be on a quorum (which is both nodes).
|
||||
committed cfg=(1, 2) idx=(12,_)
|
||||
----
|
||||
idx
|
||||
x> 12 (id=1)
|
||||
? 0 (id=2)
|
||||
0
|
||||
|
||||
# The second index comes in and finalize the decision. The result will be the
|
||||
# smaller of the two indexes.
|
||||
committed cfg=(1,2) idx=(12,5)
|
||||
----
|
||||
idx
|
||||
x> 12 (id=1)
|
||||
> 5 (id=2)
|
||||
5
|
||||
|
||||
|
||||
|
||||
|
||||
# No surprises for three nodes.
|
||||
committed cfg=(1,2,3) idx=(_,_,_)
|
||||
----
|
||||
idx
|
||||
? 0 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
0
|
||||
|
||||
committed cfg=(1,2,3) idx=(12,_,_)
|
||||
----
|
||||
idx
|
||||
xx> 12 (id=1)
|
||||
? 0 (id=2)
|
||||
? 0 (id=3)
|
||||
0
|
||||
|
||||
# We see a committed index, but a higher committed index for the last pending
|
||||
# votes could change (increment) the outcome, so not final yet.
|
||||
committed cfg=(1,2,3) idx=(12,5,_)
|
||||
----
|
||||
idx
|
||||
xx> 12 (id=1)
|
||||
x> 5 (id=2)
|
||||
? 0 (id=3)
|
||||
5
|
||||
|
||||
# a) the case in which it does:
|
||||
committed cfg=(1,2,3) idx=(12,5,6)
|
||||
----
|
||||
idx
|
||||
xx> 12 (id=1)
|
||||
> 5 (id=2)
|
||||
x> 6 (id=3)
|
||||
6
|
||||
|
||||
# b) the case in which it does not:
|
||||
committed cfg=(1,2,3) idx=(12,5,4)
|
||||
----
|
||||
idx
|
||||
xx> 12 (id=1)
|
||||
x> 5 (id=2)
|
||||
> 4 (id=3)
|
||||
5
|
||||
|
||||
# c) a different case in which the last index is pending but it has no chance of
|
||||
# swaying the outcome (because nobody in the current quorum agrees on anything
|
||||
# higher than the candidate):
|
||||
committed cfg=(1,2,3) idx=(5,5,_)
|
||||
----
|
||||
idx
|
||||
x> 5 (id=1)
|
||||
> 5 (id=2)
|
||||
? 0 (id=3)
|
||||
5
|
||||
|
||||
# c) continued: Doesn't matter what shows up last. The result is final.
|
||||
committed cfg=(1,2,3) idx=(5,5,12)
|
||||
----
|
||||
idx
|
||||
> 5 (id=1)
|
||||
> 5 (id=2)
|
||||
xx> 12 (id=3)
|
||||
5
|
||||
|
||||
# With all committed idx known, the result is final.
|
||||
committed cfg=(1, 2, 3) idx=(100, 101, 103)
|
||||
----
|
||||
idx
|
||||
> 100 (id=1)
|
||||
x> 101 (id=2)
|
||||
xx> 103 (id=3)
|
||||
101
|
||||
|
||||
|
||||
|
||||
# Some more complicated examples. Similar to case c) above. The result is
|
||||
# already final because no index higher than 103 is one short of quorum.
|
||||
committed cfg=(1, 2, 3, 4, 5) idx=(101, 104, 103, 103,_)
|
||||
----
|
||||
idx
|
||||
x> 101 (id=1)
|
||||
xxxx> 104 (id=2)
|
||||
xx> 103 (id=3)
|
||||
> 103 (id=4)
|
||||
? 0 (id=5)
|
||||
103
|
||||
|
||||
# A similar case which is not final because another vote for >= 103 would change
|
||||
# the outcome.
|
||||
committed cfg=(1, 2, 3, 4, 5) idx=(101, 102, 103, 103,_)
|
||||
----
|
||||
idx
|
||||
x> 101 (id=1)
|
||||
xx> 102 (id=2)
|
||||
xxx> 103 (id=3)
|
||||
> 103 (id=4)
|
||||
? 0 (id=5)
|
||||
102
|
||||
97
vendor/go.etcd.io/etcd/raft/v3/quorum/testdata/majority_vote.txt
generated
vendored
97
vendor/go.etcd.io/etcd/raft/v3/quorum/testdata/majority_vote.txt
generated
vendored
@ -1,97 +0,0 @@
|
||||
# The empty config always announces a won vote.
|
||||
vote
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(1) votes=(_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1) votes=(n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(123) votes=(y)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
|
||||
|
||||
|
||||
vote cfg=(4,8) votes=(_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
# With two voters, a single rejection loses the vote.
|
||||
vote cfg=(4,8) votes=(n,_)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(4,8) votes=(y,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(4,8) votes=(n,y)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(4,8) votes=(y,y)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
|
||||
|
||||
vote cfg=(2,4,7) votes=(_,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(2,4,7) votes=(n,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(2,4,7) votes=(y,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(2,4,7) votes=(n,n,_)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
vote cfg=(2,4,7) votes=(y,n,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(2,4,7) votes=(y,y,_)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(2,4,7) votes=(y,y,n)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(2,4,7) votes=(n,y,n)
|
||||
----
|
||||
VoteLost
|
||||
|
||||
|
||||
|
||||
# Test some random example with seven nodes (why not).
|
||||
vote cfg=(1,2,3,4,5,6,7) votes=(y,y,n,y,_,_,_)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2,3,4,5,6,7) votes=(_,y,y,_,n,y,n)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2,3,4,5,6,7) votes=(y,y,n,y,_,n,y)
|
||||
----
|
||||
VoteWon
|
||||
|
||||
vote cfg=(1,2,3,4,5,6,7) votes=(y,y,_,n,y,n,n)
|
||||
----
|
||||
VotePending
|
||||
|
||||
vote cfg=(1,2,3,4,5,6,7) votes=(y,y,n,y,n,n,n)
|
||||
----
|
||||
VoteLost
|
||||
156
vendor/go.etcd.io/etcd/raft/v3/raft_flow_control_test.go
generated
vendored
156
vendor/go.etcd.io/etcd/raft/v3/raft_flow_control_test.go
generated
vendored
@ -1,156 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
)
|
||||
|
||||
// TestMsgAppFlowControlFull ensures:
|
||||
// 1. msgApp can fill the sending window until full
|
||||
// 2. when the window is full, no more msgApp can be sent.
|
||||
|
||||
func TestMsgAppFlowControlFull(t *testing.T) {
|
||||
r := newTestRaft(1, 5, 1, newTestMemoryStorage(withPeers(1, 2)))
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
|
||||
pr2 := r.prs.Progress[2]
|
||||
// force the progress to be in replicate state
|
||||
pr2.BecomeReplicate()
|
||||
// fill in the inflights window
|
||||
for i := 0; i < r.prs.MaxInflight; i++ {
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}})
|
||||
ms := r.readMessages()
|
||||
if len(ms) != 1 {
|
||||
t.Fatalf("#%d: len(ms) = %d, want 1", i, len(ms))
|
||||
}
|
||||
}
|
||||
|
||||
// ensure 1
|
||||
if !pr2.Inflights.Full() {
|
||||
t.Fatalf("inflights.full = %t, want %t", pr2.Inflights.Full(), true)
|
||||
}
|
||||
|
||||
// ensure 2
|
||||
for i := 0; i < 10; i++ {
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}})
|
||||
ms := r.readMessages()
|
||||
if len(ms) != 0 {
|
||||
t.Fatalf("#%d: len(ms) = %d, want 0", i, len(ms))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMsgAppFlowControlMoveForward ensures msgAppResp can move
|
||||
// forward the sending window correctly:
|
||||
// 1. valid msgAppResp.index moves the windows to pass all smaller or equal index.
|
||||
// 2. out-of-dated msgAppResp has no effect on the sliding window.
|
||||
func TestMsgAppFlowControlMoveForward(t *testing.T) {
|
||||
r := newTestRaft(1, 5, 1, newTestMemoryStorage(withPeers(1, 2)))
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
|
||||
pr2 := r.prs.Progress[2]
|
||||
// force the progress to be in replicate state
|
||||
pr2.BecomeReplicate()
|
||||
// fill in the inflights window
|
||||
for i := 0; i < r.prs.MaxInflight; i++ {
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}})
|
||||
r.readMessages()
|
||||
}
|
||||
|
||||
// 1 is noop, 2 is the first proposal we just sent.
|
||||
// so we start with 2.
|
||||
for tt := 2; tt < r.prs.MaxInflight; tt++ {
|
||||
// move forward the window
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgAppResp, Index: uint64(tt)})
|
||||
r.readMessages()
|
||||
|
||||
// fill in the inflights window again
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}})
|
||||
ms := r.readMessages()
|
||||
if len(ms) != 1 {
|
||||
t.Fatalf("#%d: len(ms) = %d, want 1", tt, len(ms))
|
||||
}
|
||||
|
||||
// ensure 1
|
||||
if !pr2.Inflights.Full() {
|
||||
t.Fatalf("inflights.full = %t, want %t", pr2.Inflights.Full(), true)
|
||||
}
|
||||
|
||||
// ensure 2
|
||||
for i := 0; i < tt; i++ {
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgAppResp, Index: uint64(i)})
|
||||
if !pr2.Inflights.Full() {
|
||||
t.Fatalf("#%d: inflights.full = %t, want %t", tt, pr2.Inflights.Full(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMsgAppFlowControlRecvHeartbeat ensures a heartbeat response
|
||||
// frees one slot if the window is full.
|
||||
func TestMsgAppFlowControlRecvHeartbeat(t *testing.T) {
|
||||
r := newTestRaft(1, 5, 1, newTestMemoryStorage(withPeers(1, 2)))
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
|
||||
pr2 := r.prs.Progress[2]
|
||||
// force the progress to be in replicate state
|
||||
pr2.BecomeReplicate()
|
||||
// fill in the inflights window
|
||||
for i := 0; i < r.prs.MaxInflight; i++ {
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}})
|
||||
r.readMessages()
|
||||
}
|
||||
|
||||
for tt := 1; tt < 5; tt++ {
|
||||
if !pr2.Inflights.Full() {
|
||||
t.Fatalf("#%d: inflights.full = %t, want %t", tt, pr2.Inflights.Full(), true)
|
||||
}
|
||||
|
||||
// recv tt msgHeartbeatResp and expect one free slot
|
||||
for i := 0; i < tt; i++ {
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgHeartbeatResp})
|
||||
r.readMessages()
|
||||
if pr2.Inflights.Full() {
|
||||
t.Fatalf("#%d.%d: inflights.full = %t, want %t", tt, i, pr2.Inflights.Full(), false)
|
||||
}
|
||||
}
|
||||
|
||||
// one slot
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}})
|
||||
ms := r.readMessages()
|
||||
if len(ms) != 1 {
|
||||
t.Fatalf("#%d: free slot = 0, want 1", tt)
|
||||
}
|
||||
|
||||
// and just one slot
|
||||
for i := 0; i < 10; i++ {
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}})
|
||||
ms1 := r.readMessages()
|
||||
if len(ms1) != 0 {
|
||||
t.Fatalf("#%d.%d: len(ms) = %d, want 0", tt, i, len(ms1))
|
||||
}
|
||||
}
|
||||
|
||||
// clear all pending messages.
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgHeartbeatResp})
|
||||
r.readMessages()
|
||||
}
|
||||
}
|
||||
937
vendor/go.etcd.io/etcd/raft/v3/raft_paper_test.go
generated
vendored
937
vendor/go.etcd.io/etcd/raft/v3/raft_paper_test.go
generated
vendored
@ -1,937 +0,0 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
/*
|
||||
This file contains tests which verify that the scenarios described
|
||||
in the raft paper (https://raft.github.io/raft.pdf) are
|
||||
handled by the raft implementation correctly. Each test focuses on
|
||||
several sentences written in the paper. This could help us to prevent
|
||||
most implementation bugs.
|
||||
|
||||
Each test is composed of three parts: init, test and check.
|
||||
Init part uses simple and understandable way to simulate the init state.
|
||||
Test part uses Step function to generate the scenario. Check part checks
|
||||
outgoing messages and state.
|
||||
*/
|
||||
package raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
)
|
||||
|
||||
func TestFollowerUpdateTermFromMessage(t *testing.T) {
|
||||
testUpdateTermFromMessage(t, StateFollower)
|
||||
}
|
||||
func TestCandidateUpdateTermFromMessage(t *testing.T) {
|
||||
testUpdateTermFromMessage(t, StateCandidate)
|
||||
}
|
||||
func TestLeaderUpdateTermFromMessage(t *testing.T) {
|
||||
testUpdateTermFromMessage(t, StateLeader)
|
||||
}
|
||||
|
||||
// testUpdateTermFromMessage tests that if one server’s current term is
|
||||
// smaller than the other’s, then it updates its current term to the larger
|
||||
// value. If a candidate or leader discovers that its term is out of date,
|
||||
// it immediately reverts to follower state.
|
||||
// Reference: section 5.1
|
||||
func testUpdateTermFromMessage(t *testing.T, state StateType) {
|
||||
r := newTestRaft(1, 10, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
switch state {
|
||||
case StateFollower:
|
||||
r.becomeFollower(1, 2)
|
||||
case StateCandidate:
|
||||
r.becomeCandidate()
|
||||
case StateLeader:
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
}
|
||||
|
||||
r.Step(pb.Message{Type: pb.MsgApp, Term: 2})
|
||||
|
||||
if r.Term != 2 {
|
||||
t.Errorf("term = %d, want %d", r.Term, 2)
|
||||
}
|
||||
if r.state != StateFollower {
|
||||
t.Errorf("state = %v, want %v", r.state, StateFollower)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRejectStaleTermMessage tests that if a server receives a request with
|
||||
// a stale term number, it rejects the request.
|
||||
// Our implementation ignores the request instead.
|
||||
// Reference: section 5.1
|
||||
func TestRejectStaleTermMessage(t *testing.T) {
|
||||
called := false
|
||||
fakeStep := func(r *raft, m pb.Message) error {
|
||||
called = true
|
||||
return nil
|
||||
}
|
||||
r := newTestRaft(1, 10, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
r.step = fakeStep
|
||||
r.loadState(pb.HardState{Term: 2})
|
||||
|
||||
r.Step(pb.Message{Type: pb.MsgApp, Term: r.Term - 1})
|
||||
|
||||
if called {
|
||||
t.Errorf("stepFunc called = %v, want %v", called, false)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStartAsFollower tests that when servers start up, they begin as followers.
|
||||
// Reference: section 5.2
|
||||
func TestStartAsFollower(t *testing.T) {
|
||||
r := newTestRaft(1, 10, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
if r.state != StateFollower {
|
||||
t.Errorf("state = %s, want %s", r.state, StateFollower)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderBcastBeat tests that if the leader receives a heartbeat tick,
|
||||
// it will send a MsgHeartbeat with m.Index = 0, m.LogTerm=0 and empty entries
|
||||
// as heartbeat to all followers.
|
||||
// Reference: section 5.2
|
||||
func TestLeaderBcastBeat(t *testing.T) {
|
||||
// heartbeat interval
|
||||
hi := 1
|
||||
r := newTestRaft(1, 10, hi, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
for i := 0; i < 10; i++ {
|
||||
mustAppendEntry(r, pb.Entry{Index: uint64(i) + 1})
|
||||
}
|
||||
|
||||
for i := 0; i < hi; i++ {
|
||||
r.tick()
|
||||
}
|
||||
|
||||
msgs := r.readMessages()
|
||||
sort.Sort(messageSlice(msgs))
|
||||
wmsgs := []pb.Message{
|
||||
{From: 1, To: 2, Term: 1, Type: pb.MsgHeartbeat},
|
||||
{From: 1, To: 3, Term: 1, Type: pb.MsgHeartbeat},
|
||||
}
|
||||
if !reflect.DeepEqual(msgs, wmsgs) {
|
||||
t.Errorf("msgs = %v, want %v", msgs, wmsgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowerStartElection(t *testing.T) {
|
||||
testNonleaderStartElection(t, StateFollower)
|
||||
}
|
||||
func TestCandidateStartNewElection(t *testing.T) {
|
||||
testNonleaderStartElection(t, StateCandidate)
|
||||
}
|
||||
|
||||
// testNonleaderStartElection tests that if a follower receives no communication
|
||||
// over election timeout, it begins an election to choose a new leader. It
|
||||
// increments its current term and transitions to candidate state. It then
|
||||
// votes for itself and issues RequestVote RPCs in parallel to each of the
|
||||
// other servers in the cluster.
|
||||
// Reference: section 5.2
|
||||
// Also if a candidate fails to obtain a majority, it will time out and
|
||||
// start a new election by incrementing its term and initiating another
|
||||
// round of RequestVote RPCs.
|
||||
// Reference: section 5.2
|
||||
func testNonleaderStartElection(t *testing.T, state StateType) {
|
||||
// election timeout
|
||||
et := 10
|
||||
r := newTestRaft(1, et, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
switch state {
|
||||
case StateFollower:
|
||||
r.becomeFollower(1, 2)
|
||||
case StateCandidate:
|
||||
r.becomeCandidate()
|
||||
}
|
||||
|
||||
for i := 1; i < 2*et; i++ {
|
||||
r.tick()
|
||||
}
|
||||
|
||||
if r.Term != 2 {
|
||||
t.Errorf("term = %d, want 2", r.Term)
|
||||
}
|
||||
if r.state != StateCandidate {
|
||||
t.Errorf("state = %s, want %s", r.state, StateCandidate)
|
||||
}
|
||||
if !r.prs.Votes[r.id] {
|
||||
t.Errorf("vote for self = false, want true")
|
||||
}
|
||||
msgs := r.readMessages()
|
||||
sort.Sort(messageSlice(msgs))
|
||||
wmsgs := []pb.Message{
|
||||
{From: 1, To: 2, Term: 2, Type: pb.MsgVote},
|
||||
{From: 1, To: 3, Term: 2, Type: pb.MsgVote},
|
||||
}
|
||||
if !reflect.DeepEqual(msgs, wmsgs) {
|
||||
t.Errorf("msgs = %v, want %v", msgs, wmsgs)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderElectionInOneRoundRPC tests all cases that may happen in
|
||||
// leader election during one round of RequestVote RPC:
|
||||
// a) it wins the election
|
||||
// b) it loses the election
|
||||
// c) it is unclear about the result
|
||||
// Reference: section 5.2
|
||||
func TestLeaderElectionInOneRoundRPC(t *testing.T) {
|
||||
tests := []struct {
|
||||
size int
|
||||
votes map[uint64]bool
|
||||
state StateType
|
||||
}{
|
||||
// win the election when receiving votes from a majority of the servers
|
||||
{1, map[uint64]bool{}, StateLeader},
|
||||
{3, map[uint64]bool{2: true, 3: true}, StateLeader},
|
||||
{3, map[uint64]bool{2: true}, StateLeader},
|
||||
{5, map[uint64]bool{2: true, 3: true, 4: true, 5: true}, StateLeader},
|
||||
{5, map[uint64]bool{2: true, 3: true, 4: true}, StateLeader},
|
||||
{5, map[uint64]bool{2: true, 3: true}, StateLeader},
|
||||
|
||||
// return to follower state if it receives vote denial from a majority
|
||||
{3, map[uint64]bool{2: false, 3: false}, StateFollower},
|
||||
{5, map[uint64]bool{2: false, 3: false, 4: false, 5: false}, StateFollower},
|
||||
{5, map[uint64]bool{2: true, 3: false, 4: false, 5: false}, StateFollower},
|
||||
|
||||
// stay in candidate if it does not obtain the majority
|
||||
{3, map[uint64]bool{}, StateCandidate},
|
||||
{5, map[uint64]bool{2: true}, StateCandidate},
|
||||
{5, map[uint64]bool{2: false, 3: false}, StateCandidate},
|
||||
{5, map[uint64]bool{}, StateCandidate},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
r := newTestRaft(1, 10, 1, newTestMemoryStorage(withPeers(idsBySize(tt.size)...)))
|
||||
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
for id, vote := range tt.votes {
|
||||
r.Step(pb.Message{From: id, To: 1, Term: r.Term, Type: pb.MsgVoteResp, Reject: !vote})
|
||||
}
|
||||
|
||||
if r.state != tt.state {
|
||||
t.Errorf("#%d: state = %s, want %s", i, r.state, tt.state)
|
||||
}
|
||||
if g := r.Term; g != 1 {
|
||||
t.Errorf("#%d: term = %d, want %d", i, g, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFollowerVote tests that each follower will vote for at most one
|
||||
// candidate in a given term, on a first-come-first-served basis.
|
||||
// Reference: section 5.2
|
||||
func TestFollowerVote(t *testing.T) {
|
||||
tests := []struct {
|
||||
vote uint64
|
||||
nvote uint64
|
||||
wreject bool
|
||||
}{
|
||||
{None, 1, false},
|
||||
{None, 2, false},
|
||||
{1, 1, false},
|
||||
{2, 2, false},
|
||||
{1, 2, true},
|
||||
{2, 1, true},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
r := newTestRaft(1, 10, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
r.loadState(pb.HardState{Term: 1, Vote: tt.vote})
|
||||
|
||||
r.Step(pb.Message{From: tt.nvote, To: 1, Term: 1, Type: pb.MsgVote})
|
||||
|
||||
msgs := r.readMessages()
|
||||
wmsgs := []pb.Message{
|
||||
{From: 1, To: tt.nvote, Term: 1, Type: pb.MsgVoteResp, Reject: tt.wreject},
|
||||
}
|
||||
if !reflect.DeepEqual(msgs, wmsgs) {
|
||||
t.Errorf("#%d: msgs = %v, want %v", i, msgs, wmsgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCandidateFallback tests that while waiting for votes,
|
||||
// if a candidate receives an AppendEntries RPC from another server claiming
|
||||
// to be leader whose term is at least as large as the candidate's current term,
|
||||
// it recognizes the leader as legitimate and returns to follower state.
|
||||
// Reference: section 5.2
|
||||
func TestCandidateFallback(t *testing.T) {
|
||||
tests := []pb.Message{
|
||||
{From: 2, To: 1, Term: 1, Type: pb.MsgApp},
|
||||
{From: 2, To: 1, Term: 2, Type: pb.MsgApp},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
r := newTestRaft(1, 10, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
if r.state != StateCandidate {
|
||||
t.Fatalf("unexpected state = %s, want %s", r.state, StateCandidate)
|
||||
}
|
||||
|
||||
r.Step(tt)
|
||||
|
||||
if g := r.state; g != StateFollower {
|
||||
t.Errorf("#%d: state = %s, want %s", i, g, StateFollower)
|
||||
}
|
||||
if g := r.Term; g != tt.Term {
|
||||
t.Errorf("#%d: term = %d, want %d", i, g, tt.Term)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowerElectionTimeoutRandomized(t *testing.T) {
|
||||
SetLogger(discardLogger)
|
||||
defer SetLogger(defaultLogger)
|
||||
testNonleaderElectionTimeoutRandomized(t, StateFollower)
|
||||
}
|
||||
func TestCandidateElectionTimeoutRandomized(t *testing.T) {
|
||||
SetLogger(discardLogger)
|
||||
defer SetLogger(defaultLogger)
|
||||
testNonleaderElectionTimeoutRandomized(t, StateCandidate)
|
||||
}
|
||||
|
||||
// testNonleaderElectionTimeoutRandomized tests that election timeout for
|
||||
// follower or candidate is randomized.
|
||||
// Reference: section 5.2
|
||||
func testNonleaderElectionTimeoutRandomized(t *testing.T, state StateType) {
|
||||
et := 10
|
||||
r := newTestRaft(1, et, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
timeouts := make(map[int]bool)
|
||||
for round := 0; round < 50*et; round++ {
|
||||
switch state {
|
||||
case StateFollower:
|
||||
r.becomeFollower(r.Term+1, 2)
|
||||
case StateCandidate:
|
||||
r.becomeCandidate()
|
||||
}
|
||||
|
||||
time := 0
|
||||
for len(r.readMessages()) == 0 {
|
||||
r.tick()
|
||||
time++
|
||||
}
|
||||
timeouts[time] = true
|
||||
}
|
||||
|
||||
for d := et + 1; d < 2*et; d++ {
|
||||
if !timeouts[d] {
|
||||
t.Errorf("timeout in %d ticks should happen", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowersElectionTimeoutNonconflict(t *testing.T) {
|
||||
SetLogger(discardLogger)
|
||||
defer SetLogger(defaultLogger)
|
||||
testNonleadersElectionTimeoutNonconflict(t, StateFollower)
|
||||
}
|
||||
func TestCandidatesElectionTimeoutNonconflict(t *testing.T) {
|
||||
SetLogger(discardLogger)
|
||||
defer SetLogger(defaultLogger)
|
||||
testNonleadersElectionTimeoutNonconflict(t, StateCandidate)
|
||||
}
|
||||
|
||||
// testNonleadersElectionTimeoutNonconflict tests that in most cases only a
|
||||
// single server(follower or candidate) will time out, which reduces the
|
||||
// likelihood of split vote in the new election.
|
||||
// Reference: section 5.2
|
||||
func testNonleadersElectionTimeoutNonconflict(t *testing.T, state StateType) {
|
||||
et := 10
|
||||
size := 5
|
||||
rs := make([]*raft, size)
|
||||
ids := idsBySize(size)
|
||||
for k := range rs {
|
||||
rs[k] = newTestRaft(ids[k], et, 1, newTestMemoryStorage(withPeers(ids...)))
|
||||
}
|
||||
conflicts := 0
|
||||
for round := 0; round < 1000; round++ {
|
||||
for _, r := range rs {
|
||||
switch state {
|
||||
case StateFollower:
|
||||
r.becomeFollower(r.Term+1, None)
|
||||
case StateCandidate:
|
||||
r.becomeCandidate()
|
||||
}
|
||||
}
|
||||
|
||||
timeoutNum := 0
|
||||
for timeoutNum == 0 {
|
||||
for _, r := range rs {
|
||||
r.tick()
|
||||
if len(r.readMessages()) > 0 {
|
||||
timeoutNum++
|
||||
}
|
||||
}
|
||||
}
|
||||
// several rafts time out at the same tick
|
||||
if timeoutNum > 1 {
|
||||
conflicts++
|
||||
}
|
||||
}
|
||||
|
||||
if g := float64(conflicts) / 1000; g > 0.3 {
|
||||
t.Errorf("probability of conflicts = %v, want <= 0.3", g)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderStartReplication tests that when receiving client proposals,
|
||||
// the leader appends the proposal to its log as a new entry, then issues
|
||||
// AppendEntries RPCs in parallel to each of the other servers to replicate
|
||||
// the entry. Also, when sending an AppendEntries RPC, the leader includes
|
||||
// the index and term of the entry in its log that immediately precedes
|
||||
// the new entries.
|
||||
// Also, it writes the new entry into stable storage.
|
||||
// Reference: section 5.3
|
||||
func TestLeaderStartReplication(t *testing.T) {
|
||||
s := newTestMemoryStorage(withPeers(1, 2, 3))
|
||||
r := newTestRaft(1, 10, 1, s)
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
commitNoopEntry(r, s)
|
||||
li := r.raftLog.lastIndex()
|
||||
|
||||
ents := []pb.Entry{{Data: []byte("some data")}}
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: ents})
|
||||
|
||||
if g := r.raftLog.lastIndex(); g != li+1 {
|
||||
t.Errorf("lastIndex = %d, want %d", g, li+1)
|
||||
}
|
||||
if g := r.raftLog.committed; g != li {
|
||||
t.Errorf("committed = %d, want %d", g, li)
|
||||
}
|
||||
msgs := r.readMessages()
|
||||
sort.Sort(messageSlice(msgs))
|
||||
wents := []pb.Entry{{Index: li + 1, Term: 1, Data: []byte("some data")}}
|
||||
wmsgs := []pb.Message{
|
||||
{From: 1, To: 2, Term: 1, Type: pb.MsgApp, Index: li, LogTerm: 1, Entries: wents, Commit: li},
|
||||
{From: 1, To: 3, Term: 1, Type: pb.MsgApp, Index: li, LogTerm: 1, Entries: wents, Commit: li},
|
||||
}
|
||||
if !reflect.DeepEqual(msgs, wmsgs) {
|
||||
t.Errorf("msgs = %+v, want %+v", msgs, wmsgs)
|
||||
}
|
||||
if g := r.raftLog.unstableEntries(); !reflect.DeepEqual(g, wents) {
|
||||
t.Errorf("ents = %+v, want %+v", g, wents)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderCommitEntry tests that when the entry has been safely replicated,
|
||||
// the leader gives out the applied entries, which can be applied to its state
|
||||
// machine.
|
||||
// Also, the leader keeps track of the highest index it knows to be committed,
|
||||
// and it includes that index in future AppendEntries RPCs so that the other
|
||||
// servers eventually find out.
|
||||
// Reference: section 5.3
|
||||
func TestLeaderCommitEntry(t *testing.T) {
|
||||
s := newTestMemoryStorage(withPeers(1, 2, 3))
|
||||
r := newTestRaft(1, 10, 1, s)
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
commitNoopEntry(r, s)
|
||||
li := r.raftLog.lastIndex()
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("some data")}}})
|
||||
|
||||
for _, m := range r.readMessages() {
|
||||
r.Step(acceptAndReply(m))
|
||||
}
|
||||
|
||||
if g := r.raftLog.committed; g != li+1 {
|
||||
t.Errorf("committed = %d, want %d", g, li+1)
|
||||
}
|
||||
wents := []pb.Entry{{Index: li + 1, Term: 1, Data: []byte("some data")}}
|
||||
if g := r.raftLog.nextEnts(); !reflect.DeepEqual(g, wents) {
|
||||
t.Errorf("nextEnts = %+v, want %+v", g, wents)
|
||||
}
|
||||
msgs := r.readMessages()
|
||||
sort.Sort(messageSlice(msgs))
|
||||
for i, m := range msgs {
|
||||
if w := uint64(i + 2); m.To != w {
|
||||
t.Errorf("to = %x, want %x", m.To, w)
|
||||
}
|
||||
if m.Type != pb.MsgApp {
|
||||
t.Errorf("type = %v, want %v", m.Type, pb.MsgApp)
|
||||
}
|
||||
if m.Commit != li+1 {
|
||||
t.Errorf("commit = %d, want %d", m.Commit, li+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderAcknowledgeCommit tests that a log entry is committed once the
|
||||
// leader that created the entry has replicated it on a majority of the servers.
|
||||
// Reference: section 5.3
|
||||
func TestLeaderAcknowledgeCommit(t *testing.T) {
|
||||
tests := []struct {
|
||||
size int
|
||||
acceptors map[uint64]bool
|
||||
wack bool
|
||||
}{
|
||||
{1, nil, true},
|
||||
{3, nil, false},
|
||||
{3, map[uint64]bool{2: true}, true},
|
||||
{3, map[uint64]bool{2: true, 3: true}, true},
|
||||
{5, nil, false},
|
||||
{5, map[uint64]bool{2: true}, false},
|
||||
{5, map[uint64]bool{2: true, 3: true}, true},
|
||||
{5, map[uint64]bool{2: true, 3: true, 4: true}, true},
|
||||
{5, map[uint64]bool{2: true, 3: true, 4: true, 5: true}, true},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
s := newTestMemoryStorage(withPeers(idsBySize(tt.size)...))
|
||||
r := newTestRaft(1, 10, 1, s)
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
commitNoopEntry(r, s)
|
||||
li := r.raftLog.lastIndex()
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("some data")}}})
|
||||
|
||||
for _, m := range r.readMessages() {
|
||||
if tt.acceptors[m.To] {
|
||||
r.Step(acceptAndReply(m))
|
||||
}
|
||||
}
|
||||
|
||||
if g := r.raftLog.committed > li; g != tt.wack {
|
||||
t.Errorf("#%d: ack commit = %v, want %v", i, g, tt.wack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderCommitPrecedingEntries tests that when leader commits a log entry,
|
||||
// it also commits all preceding entries in the leader’s log, including
|
||||
// entries created by previous leaders.
|
||||
// Also, it applies the entry to its local state machine (in log order).
|
||||
// Reference: section 5.3
|
||||
func TestLeaderCommitPrecedingEntries(t *testing.T) {
|
||||
tests := [][]pb.Entry{
|
||||
{},
|
||||
{{Term: 2, Index: 1}},
|
||||
{{Term: 1, Index: 1}, {Term: 2, Index: 2}},
|
||||
{{Term: 1, Index: 1}},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2, 3))
|
||||
storage.Append(tt)
|
||||
r := newTestRaft(1, 10, 1, storage)
|
||||
r.loadState(pb.HardState{Term: 2})
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("some data")}}})
|
||||
|
||||
for _, m := range r.readMessages() {
|
||||
r.Step(acceptAndReply(m))
|
||||
}
|
||||
|
||||
li := uint64(len(tt))
|
||||
wents := append(tt, pb.Entry{Term: 3, Index: li + 1}, pb.Entry{Term: 3, Index: li + 2, Data: []byte("some data")})
|
||||
if g := r.raftLog.nextEnts(); !reflect.DeepEqual(g, wents) {
|
||||
t.Errorf("#%d: ents = %+v, want %+v", i, g, wents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFollowerCommitEntry tests that once a follower learns that a log entry
|
||||
// is committed, it applies the entry to its local state machine (in log order).
|
||||
// Reference: section 5.3
|
||||
func TestFollowerCommitEntry(t *testing.T) {
|
||||
tests := []struct {
|
||||
ents []pb.Entry
|
||||
commit uint64
|
||||
}{
|
||||
{
|
||||
[]pb.Entry{
|
||||
{Term: 1, Index: 1, Data: []byte("some data")},
|
||||
},
|
||||
1,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{
|
||||
{Term: 1, Index: 1, Data: []byte("some data")},
|
||||
{Term: 1, Index: 2, Data: []byte("some data2")},
|
||||
},
|
||||
2,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{
|
||||
{Term: 1, Index: 1, Data: []byte("some data2")},
|
||||
{Term: 1, Index: 2, Data: []byte("some data")},
|
||||
},
|
||||
2,
|
||||
},
|
||||
{
|
||||
[]pb.Entry{
|
||||
{Term: 1, Index: 1, Data: []byte("some data")},
|
||||
{Term: 1, Index: 2, Data: []byte("some data2")},
|
||||
},
|
||||
1,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
r := newTestRaft(1, 10, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
r.becomeFollower(1, 2)
|
||||
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgApp, Term: 1, Entries: tt.ents, Commit: tt.commit})
|
||||
|
||||
if g := r.raftLog.committed; g != tt.commit {
|
||||
t.Errorf("#%d: committed = %d, want %d", i, g, tt.commit)
|
||||
}
|
||||
wents := tt.ents[:int(tt.commit)]
|
||||
if g := r.raftLog.nextEnts(); !reflect.DeepEqual(g, wents) {
|
||||
t.Errorf("#%d: nextEnts = %v, want %v", i, g, wents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFollowerCheckMsgApp tests that if the follower does not find an
|
||||
// entry in its log with the same index and term as the one in AppendEntries RPC,
|
||||
// then it refuses the new entries. Otherwise it replies that it accepts the
|
||||
// append entries.
|
||||
// Reference: section 5.3
|
||||
func TestFollowerCheckMsgApp(t *testing.T) {
|
||||
ents := []pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}}
|
||||
tests := []struct {
|
||||
term uint64
|
||||
index uint64
|
||||
windex uint64
|
||||
wreject bool
|
||||
wrejectHint uint64
|
||||
wlogterm uint64
|
||||
}{
|
||||
// match with committed entries
|
||||
{0, 0, 1, false, 0, 0},
|
||||
{ents[0].Term, ents[0].Index, 1, false, 0, 0},
|
||||
// match with uncommitted entries
|
||||
{ents[1].Term, ents[1].Index, 2, false, 0, 0},
|
||||
|
||||
// unmatch with existing entry
|
||||
{ents[0].Term, ents[1].Index, ents[1].Index, true, 1, 1},
|
||||
// unexisting entry
|
||||
{ents[1].Term + 1, ents[1].Index + 1, ents[1].Index + 1, true, 2, 2},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2, 3))
|
||||
storage.Append(ents)
|
||||
r := newTestRaft(1, 10, 1, storage)
|
||||
r.loadState(pb.HardState{Commit: 1})
|
||||
r.becomeFollower(2, 2)
|
||||
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgApp, Term: 2, LogTerm: tt.term, Index: tt.index})
|
||||
|
||||
msgs := r.readMessages()
|
||||
wmsgs := []pb.Message{
|
||||
{From: 1, To: 2, Type: pb.MsgAppResp, Term: 2, Index: tt.windex, Reject: tt.wreject, RejectHint: tt.wrejectHint, LogTerm: tt.wlogterm},
|
||||
}
|
||||
if !reflect.DeepEqual(msgs, wmsgs) {
|
||||
t.Errorf("#%d: msgs = %+v, want %+v", i, msgs, wmsgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFollowerAppendEntries tests that when AppendEntries RPC is valid,
|
||||
// the follower will delete the existing conflict entry and all that follow it,
|
||||
// and append any new entries not already in the log.
|
||||
// Also, it writes the new entry into stable storage.
|
||||
// Reference: section 5.3
|
||||
func TestFollowerAppendEntries(t *testing.T) {
|
||||
tests := []struct {
|
||||
index, term uint64
|
||||
ents []pb.Entry
|
||||
wents []pb.Entry
|
||||
wunstable []pb.Entry
|
||||
}{
|
||||
{
|
||||
2, 2,
|
||||
[]pb.Entry{{Term: 3, Index: 3}},
|
||||
[]pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}, {Term: 3, Index: 3}},
|
||||
[]pb.Entry{{Term: 3, Index: 3}},
|
||||
},
|
||||
{
|
||||
1, 1,
|
||||
[]pb.Entry{{Term: 3, Index: 2}, {Term: 4, Index: 3}},
|
||||
[]pb.Entry{{Term: 1, Index: 1}, {Term: 3, Index: 2}, {Term: 4, Index: 3}},
|
||||
[]pb.Entry{{Term: 3, Index: 2}, {Term: 4, Index: 3}},
|
||||
},
|
||||
{
|
||||
0, 0,
|
||||
[]pb.Entry{{Term: 1, Index: 1}},
|
||||
[]pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
0, 0,
|
||||
[]pb.Entry{{Term: 3, Index: 1}},
|
||||
[]pb.Entry{{Term: 3, Index: 1}},
|
||||
[]pb.Entry{{Term: 3, Index: 1}},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2, 3))
|
||||
storage.Append([]pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}})
|
||||
r := newTestRaft(1, 10, 1, storage)
|
||||
r.becomeFollower(2, 2)
|
||||
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgApp, Term: 2, LogTerm: tt.term, Index: tt.index, Entries: tt.ents})
|
||||
|
||||
if g := r.raftLog.allEntries(); !reflect.DeepEqual(g, tt.wents) {
|
||||
t.Errorf("#%d: ents = %+v, want %+v", i, g, tt.wents)
|
||||
}
|
||||
if g := r.raftLog.unstableEntries(); !reflect.DeepEqual(g, tt.wunstable) {
|
||||
t.Errorf("#%d: unstableEnts = %+v, want %+v", i, g, tt.wunstable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderSyncFollowerLog tests that the leader could bring a follower's log
|
||||
// into consistency with its own.
|
||||
// Reference: section 5.3, figure 7
|
||||
func TestLeaderSyncFollowerLog(t *testing.T) {
|
||||
ents := []pb.Entry{
|
||||
{},
|
||||
{Term: 1, Index: 1}, {Term: 1, Index: 2}, {Term: 1, Index: 3},
|
||||
{Term: 4, Index: 4}, {Term: 4, Index: 5},
|
||||
{Term: 5, Index: 6}, {Term: 5, Index: 7},
|
||||
{Term: 6, Index: 8}, {Term: 6, Index: 9}, {Term: 6, Index: 10},
|
||||
}
|
||||
term := uint64(8)
|
||||
tests := [][]pb.Entry{
|
||||
{
|
||||
{},
|
||||
{Term: 1, Index: 1}, {Term: 1, Index: 2}, {Term: 1, Index: 3},
|
||||
{Term: 4, Index: 4}, {Term: 4, Index: 5},
|
||||
{Term: 5, Index: 6}, {Term: 5, Index: 7},
|
||||
{Term: 6, Index: 8}, {Term: 6, Index: 9},
|
||||
},
|
||||
{
|
||||
{},
|
||||
{Term: 1, Index: 1}, {Term: 1, Index: 2}, {Term: 1, Index: 3},
|
||||
{Term: 4, Index: 4},
|
||||
},
|
||||
{
|
||||
{},
|
||||
{Term: 1, Index: 1}, {Term: 1, Index: 2}, {Term: 1, Index: 3},
|
||||
{Term: 4, Index: 4}, {Term: 4, Index: 5},
|
||||
{Term: 5, Index: 6}, {Term: 5, Index: 7},
|
||||
{Term: 6, Index: 8}, {Term: 6, Index: 9}, {Term: 6, Index: 10}, {Term: 6, Index: 11},
|
||||
},
|
||||
{
|
||||
{},
|
||||
{Term: 1, Index: 1}, {Term: 1, Index: 2}, {Term: 1, Index: 3},
|
||||
{Term: 4, Index: 4}, {Term: 4, Index: 5},
|
||||
{Term: 5, Index: 6}, {Term: 5, Index: 7},
|
||||
{Term: 6, Index: 8}, {Term: 6, Index: 9}, {Term: 6, Index: 10},
|
||||
{Term: 7, Index: 11}, {Term: 7, Index: 12},
|
||||
},
|
||||
{
|
||||
{},
|
||||
{Term: 1, Index: 1}, {Term: 1, Index: 2}, {Term: 1, Index: 3},
|
||||
{Term: 4, Index: 4}, {Term: 4, Index: 5}, {Term: 4, Index: 6}, {Term: 4, Index: 7},
|
||||
},
|
||||
{
|
||||
{},
|
||||
{Term: 1, Index: 1}, {Term: 1, Index: 2}, {Term: 1, Index: 3},
|
||||
{Term: 2, Index: 4}, {Term: 2, Index: 5}, {Term: 2, Index: 6},
|
||||
{Term: 3, Index: 7}, {Term: 3, Index: 8}, {Term: 3, Index: 9}, {Term: 3, Index: 10}, {Term: 3, Index: 11},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
leadStorage := newTestMemoryStorage(withPeers(1, 2, 3))
|
||||
leadStorage.Append(ents)
|
||||
lead := newTestRaft(1, 10, 1, leadStorage)
|
||||
lead.loadState(pb.HardState{Commit: lead.raftLog.lastIndex(), Term: term})
|
||||
followerStorage := newTestMemoryStorage(withPeers(1, 2, 3))
|
||||
followerStorage.Append(tt)
|
||||
follower := newTestRaft(2, 10, 1, followerStorage)
|
||||
follower.loadState(pb.HardState{Term: term - 1})
|
||||
// It is necessary to have a three-node cluster.
|
||||
// The second may have more up-to-date log than the first one, so the
|
||||
// first node needs the vote from the third node to become the leader.
|
||||
n := newNetwork(lead, follower, nopStepper)
|
||||
n.send(pb.Message{From: 1, To: 1, Type: pb.MsgHup})
|
||||
// The election occurs in the term after the one we loaded with
|
||||
// lead.loadState above.
|
||||
n.send(pb.Message{From: 3, To: 1, Type: pb.MsgVoteResp, Term: term + 1})
|
||||
|
||||
n.send(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{}}})
|
||||
|
||||
if g := diffu(ltoa(lead.raftLog), ltoa(follower.raftLog)); g != "" {
|
||||
t.Errorf("#%d: log diff:\n%s", i, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVoteRequest tests that the vote request includes information about the candidate’s log
|
||||
// and are sent to all of the other nodes.
|
||||
// Reference: section 5.4.1
|
||||
func TestVoteRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
ents []pb.Entry
|
||||
wterm uint64
|
||||
}{
|
||||
{[]pb.Entry{{Term: 1, Index: 1}}, 2},
|
||||
{[]pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}}, 3},
|
||||
}
|
||||
for j, tt := range tests {
|
||||
r := newTestRaft(1, 10, 1, newTestMemoryStorage(withPeers(1, 2, 3)))
|
||||
r.Step(pb.Message{
|
||||
From: 2, To: 1, Type: pb.MsgApp, Term: tt.wterm - 1, LogTerm: 0, Index: 0, Entries: tt.ents,
|
||||
})
|
||||
r.readMessages()
|
||||
|
||||
for i := 1; i < r.electionTimeout*2; i++ {
|
||||
r.tickElection()
|
||||
}
|
||||
|
||||
msgs := r.readMessages()
|
||||
sort.Sort(messageSlice(msgs))
|
||||
if len(msgs) != 2 {
|
||||
t.Fatalf("#%d: len(msg) = %d, want %d", j, len(msgs), 2)
|
||||
}
|
||||
for i, m := range msgs {
|
||||
if m.Type != pb.MsgVote {
|
||||
t.Errorf("#%d: msgType = %d, want %d", i, m.Type, pb.MsgVote)
|
||||
}
|
||||
if m.To != uint64(i+2) {
|
||||
t.Errorf("#%d: to = %d, want %d", i, m.To, i+2)
|
||||
}
|
||||
if m.Term != tt.wterm {
|
||||
t.Errorf("#%d: term = %d, want %d", i, m.Term, tt.wterm)
|
||||
}
|
||||
windex, wlogterm := tt.ents[len(tt.ents)-1].Index, tt.ents[len(tt.ents)-1].Term
|
||||
if m.Index != windex {
|
||||
t.Errorf("#%d: index = %d, want %d", i, m.Index, windex)
|
||||
}
|
||||
if m.LogTerm != wlogterm {
|
||||
t.Errorf("#%d: logterm = %d, want %d", i, m.LogTerm, wlogterm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVoter tests the voter denies its vote if its own log is more up-to-date
|
||||
// than that of the candidate.
|
||||
// Reference: section 5.4.1
|
||||
func TestVoter(t *testing.T) {
|
||||
tests := []struct {
|
||||
ents []pb.Entry
|
||||
logterm uint64
|
||||
index uint64
|
||||
|
||||
wreject bool
|
||||
}{
|
||||
// same logterm
|
||||
{[]pb.Entry{{Term: 1, Index: 1}}, 1, 1, false},
|
||||
{[]pb.Entry{{Term: 1, Index: 1}}, 1, 2, false},
|
||||
{[]pb.Entry{{Term: 1, Index: 1}, {Term: 1, Index: 2}}, 1, 1, true},
|
||||
// candidate higher logterm
|
||||
{[]pb.Entry{{Term: 1, Index: 1}}, 2, 1, false},
|
||||
{[]pb.Entry{{Term: 1, Index: 1}}, 2, 2, false},
|
||||
{[]pb.Entry{{Term: 1, Index: 1}, {Term: 1, Index: 2}}, 2, 1, false},
|
||||
// voter higher logterm
|
||||
{[]pb.Entry{{Term: 2, Index: 1}}, 1, 1, true},
|
||||
{[]pb.Entry{{Term: 2, Index: 1}}, 1, 2, true},
|
||||
{[]pb.Entry{{Term: 2, Index: 1}, {Term: 1, Index: 2}}, 1, 1, true},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2))
|
||||
storage.Append(tt.ents)
|
||||
r := newTestRaft(1, 10, 1, storage)
|
||||
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgVote, Term: 3, LogTerm: tt.logterm, Index: tt.index})
|
||||
|
||||
msgs := r.readMessages()
|
||||
if len(msgs) != 1 {
|
||||
t.Fatalf("#%d: len(msg) = %d, want %d", i, len(msgs), 1)
|
||||
}
|
||||
m := msgs[0]
|
||||
if m.Type != pb.MsgVoteResp {
|
||||
t.Errorf("#%d: msgType = %d, want %d", i, m.Type, pb.MsgVoteResp)
|
||||
}
|
||||
if m.Reject != tt.wreject {
|
||||
t.Errorf("#%d: reject = %t, want %t", i, m.Reject, tt.wreject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaderOnlyCommitsLogFromCurrentTerm tests that only log entries from the leader’s
|
||||
// current term are committed by counting replicas.
|
||||
// Reference: section 5.4.2
|
||||
func TestLeaderOnlyCommitsLogFromCurrentTerm(t *testing.T) {
|
||||
ents := []pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}}
|
||||
tests := []struct {
|
||||
index uint64
|
||||
wcommit uint64
|
||||
}{
|
||||
// do not commit log entries in previous terms
|
||||
{1, 0},
|
||||
{2, 0},
|
||||
// commit log in current term
|
||||
{3, 3},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2))
|
||||
storage.Append(ents)
|
||||
r := newTestRaft(1, 10, 1, storage)
|
||||
r.loadState(pb.HardState{Term: 2})
|
||||
// become leader at term 3
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
r.readMessages()
|
||||
// propose a entry to current term
|
||||
r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{}}})
|
||||
|
||||
r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgAppResp, Term: r.Term, Index: tt.index})
|
||||
if r.raftLog.committed != tt.wcommit {
|
||||
t.Errorf("#%d: commit = %d, want %d", i, r.raftLog.committed, tt.wcommit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type messageSlice []pb.Message
|
||||
|
||||
func (s messageSlice) Len() int { return len(s) }
|
||||
func (s messageSlice) Less(i, j int) bool { return fmt.Sprint(s[i]) < fmt.Sprint(s[j]) }
|
||||
func (s messageSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func commitNoopEntry(r *raft, s *MemoryStorage) {
|
||||
if r.state != StateLeader {
|
||||
panic("it should only be used when it is the leader")
|
||||
}
|
||||
r.bcastAppend()
|
||||
// simulate the response of MsgApp
|
||||
msgs := r.readMessages()
|
||||
for _, m := range msgs {
|
||||
if m.Type != pb.MsgApp || len(m.Entries) != 1 || m.Entries[0].Data != nil {
|
||||
panic("not a message to append noop entry")
|
||||
}
|
||||
r.Step(acceptAndReply(m))
|
||||
}
|
||||
// ignore further messages to refresh followers' commit index
|
||||
r.readMessages()
|
||||
s.Append(r.raftLog.unstableEntries())
|
||||
r.raftLog.appliedTo(r.raftLog.committed)
|
||||
r.raftLog.stableTo(r.raftLog.lastIndex(), r.raftLog.lastTerm())
|
||||
}
|
||||
|
||||
func acceptAndReply(m pb.Message) pb.Message {
|
||||
if m.Type != pb.MsgApp {
|
||||
panic("type should be MsgApp")
|
||||
}
|
||||
return pb.Message{
|
||||
From: m.To,
|
||||
To: m.From,
|
||||
Term: m.Term,
|
||||
Type: pb.MsgAppResp,
|
||||
Index: m.Index + uint64(len(m.Entries)),
|
||||
}
|
||||
}
|
||||
141
vendor/go.etcd.io/etcd/raft/v3/raft_snap_test.go
generated
vendored
141
vendor/go.etcd.io/etcd/raft/v3/raft_snap_test.go
generated
vendored
@ -1,141 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
)
|
||||
|
||||
var (
|
||||
testingSnap = pb.Snapshot{
|
||||
Metadata: pb.SnapshotMetadata{
|
||||
Index: 11, // magic number
|
||||
Term: 11, // magic number
|
||||
ConfState: pb.ConfState{Voters: []uint64{1, 2}},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestSendingSnapshotSetPendingSnapshot(t *testing.T) {
|
||||
storage := newTestMemoryStorage(withPeers(1))
|
||||
sm := newTestRaft(1, 10, 1, storage)
|
||||
sm.restore(testingSnap)
|
||||
|
||||
sm.becomeCandidate()
|
||||
sm.becomeLeader()
|
||||
|
||||
// force set the next of node 2, so that
|
||||
// node 2 needs a snapshot
|
||||
sm.prs.Progress[2].Next = sm.raftLog.firstIndex()
|
||||
|
||||
sm.Step(pb.Message{From: 2, To: 1, Type: pb.MsgAppResp, Index: sm.prs.Progress[2].Next - 1, Reject: true})
|
||||
if sm.prs.Progress[2].PendingSnapshot != 11 {
|
||||
t.Fatalf("PendingSnapshot = %d, want 11", sm.prs.Progress[2].PendingSnapshot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPendingSnapshotPauseReplication(t *testing.T) {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2))
|
||||
sm := newTestRaft(1, 10, 1, storage)
|
||||
sm.restore(testingSnap)
|
||||
|
||||
sm.becomeCandidate()
|
||||
sm.becomeLeader()
|
||||
|
||||
sm.prs.Progress[2].BecomeSnapshot(11)
|
||||
|
||||
sm.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{Data: []byte("somedata")}}})
|
||||
msgs := sm.readMessages()
|
||||
if len(msgs) != 0 {
|
||||
t.Fatalf("len(msgs) = %d, want 0", len(msgs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotFailure(t *testing.T) {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2))
|
||||
sm := newTestRaft(1, 10, 1, storage)
|
||||
sm.restore(testingSnap)
|
||||
|
||||
sm.becomeCandidate()
|
||||
sm.becomeLeader()
|
||||
|
||||
sm.prs.Progress[2].Next = 1
|
||||
sm.prs.Progress[2].BecomeSnapshot(11)
|
||||
|
||||
sm.Step(pb.Message{From: 2, To: 1, Type: pb.MsgSnapStatus, Reject: true})
|
||||
if sm.prs.Progress[2].PendingSnapshot != 0 {
|
||||
t.Fatalf("PendingSnapshot = %d, want 0", sm.prs.Progress[2].PendingSnapshot)
|
||||
}
|
||||
if sm.prs.Progress[2].Next != 1 {
|
||||
t.Fatalf("Next = %d, want 1", sm.prs.Progress[2].Next)
|
||||
}
|
||||
if !sm.prs.Progress[2].ProbeSent {
|
||||
t.Errorf("ProbeSent = %v, want true", sm.prs.Progress[2].ProbeSent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotSucceed(t *testing.T) {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2))
|
||||
sm := newTestRaft(1, 10, 1, storage)
|
||||
sm.restore(testingSnap)
|
||||
|
||||
sm.becomeCandidate()
|
||||
sm.becomeLeader()
|
||||
|
||||
sm.prs.Progress[2].Next = 1
|
||||
sm.prs.Progress[2].BecomeSnapshot(11)
|
||||
|
||||
sm.Step(pb.Message{From: 2, To: 1, Type: pb.MsgSnapStatus, Reject: false})
|
||||
if sm.prs.Progress[2].PendingSnapshot != 0 {
|
||||
t.Fatalf("PendingSnapshot = %d, want 0", sm.prs.Progress[2].PendingSnapshot)
|
||||
}
|
||||
if sm.prs.Progress[2].Next != 12 {
|
||||
t.Fatalf("Next = %d, want 12", sm.prs.Progress[2].Next)
|
||||
}
|
||||
if !sm.prs.Progress[2].ProbeSent {
|
||||
t.Errorf("ProbeSent = %v, want true", sm.prs.Progress[2].ProbeSent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotAbort(t *testing.T) {
|
||||
storage := newTestMemoryStorage(withPeers(1, 2))
|
||||
sm := newTestRaft(1, 10, 1, storage)
|
||||
sm.restore(testingSnap)
|
||||
|
||||
sm.becomeCandidate()
|
||||
sm.becomeLeader()
|
||||
|
||||
sm.prs.Progress[2].Next = 1
|
||||
sm.prs.Progress[2].BecomeSnapshot(11)
|
||||
|
||||
// A successful msgAppResp that has a higher/equal index than the
|
||||
// pending snapshot should abort the pending snapshot.
|
||||
sm.Step(pb.Message{From: 2, To: 1, Type: pb.MsgAppResp, Index: 11})
|
||||
if sm.prs.Progress[2].PendingSnapshot != 0 {
|
||||
t.Fatalf("PendingSnapshot = %d, want 0", sm.prs.Progress[2].PendingSnapshot)
|
||||
}
|
||||
// The follower entered StateReplicate and the leader send an append
|
||||
// and optimistically updated the progress (so we see 13 instead of 12).
|
||||
// There is something to append because the leader appended an empty entry
|
||||
// to the log at index 12 when it assumed leadership.
|
||||
if sm.prs.Progress[2].Next != 13 {
|
||||
t.Fatalf("Next = %d, want 13", sm.prs.Progress[2].Next)
|
||||
}
|
||||
if n := sm.prs.Progress[2].Inflights.Count(); n != 1 {
|
||||
t.Fatalf("expected an inflight message, got %d", n)
|
||||
}
|
||||
}
|
||||
4853
vendor/go.etcd.io/etcd/raft/v3/raft_test.go
generated
vendored
4853
vendor/go.etcd.io/etcd/raft/v3/raft_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
58
vendor/go.etcd.io/etcd/raft/v3/raftpb/confstate_test.go
generated
vendored
58
vendor/go.etcd.io/etcd/raft/v3/raftpb/confstate_test.go
generated
vendored
@ -1,58 +0,0 @@
|
||||
// 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 raftpb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfState_Equivalent(t *testing.T) {
|
||||
type testCase struct {
|
||||
cs, cs2 ConfState
|
||||
ok bool
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
// Reordered voters and learners.
|
||||
{ConfState{
|
||||
Voters: []uint64{1, 2, 3},
|
||||
Learners: []uint64{5, 4, 6},
|
||||
VotersOutgoing: []uint64{9, 8, 7},
|
||||
LearnersNext: []uint64{10, 20, 15},
|
||||
}, ConfState{
|
||||
Voters: []uint64{1, 2, 3},
|
||||
Learners: []uint64{4, 5, 6},
|
||||
VotersOutgoing: []uint64{7, 9, 8},
|
||||
LearnersNext: []uint64{20, 10, 15},
|
||||
}, true},
|
||||
// Not sensitive to nil vs empty slice.
|
||||
{ConfState{Voters: []uint64{}}, ConfState{Voters: []uint64(nil)}, true},
|
||||
// Non-equivalent voters.
|
||||
{ConfState{Voters: []uint64{1, 2, 3, 4}}, ConfState{Voters: []uint64{2, 1, 3}}, false},
|
||||
{ConfState{Voters: []uint64{1, 4, 3}}, ConfState{Voters: []uint64{2, 1, 3}}, false},
|
||||
// Non-equivalent learners.
|
||||
{ConfState{Voters: []uint64{1, 2, 3, 4}}, ConfState{Voters: []uint64{2, 1, 3}}, false},
|
||||
// Sensitive to AutoLeave flag.
|
||||
{ConfState{AutoLeave: true}, ConfState{}, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
if err := tc.cs.Equivalent(tc.cs2); (err == nil) != tc.ok {
|
||||
t.Fatalf("wanted error: %t, got:\n%s", tc.ok, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
64
vendor/go.etcd.io/etcd/raft/v3/raftpb/raft_test.go
generated
vendored
64
vendor/go.etcd.io/etcd/raft/v3/raftpb/raft_test.go
generated
vendored
@ -1,64 +0,0 @@
|
||||
// Copyright 2021 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 raftpb
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func TestProtoMemorySizes(t *testing.T) {
|
||||
assert := func(size, exp uintptr, name string) {
|
||||
t.Helper()
|
||||
if size != exp {
|
||||
t.Errorf("expected size of %s proto to be %d bytes, found %d bytes", name, exp, size)
|
||||
}
|
||||
}
|
||||
|
||||
if64Bit := func(yes, no uintptr) uintptr {
|
||||
if bits.UintSize == 64 {
|
||||
return yes
|
||||
}
|
||||
return no
|
||||
}
|
||||
|
||||
var e Entry
|
||||
assert(unsafe.Sizeof(e), if64Bit(48, 32), "Entry")
|
||||
|
||||
var sm SnapshotMetadata
|
||||
assert(unsafe.Sizeof(sm), if64Bit(120, 68), "SnapshotMetadata")
|
||||
|
||||
var s Snapshot
|
||||
assert(unsafe.Sizeof(s), if64Bit(144, 80), "Snapshot")
|
||||
|
||||
var m Message
|
||||
assert(unsafe.Sizeof(m), if64Bit(264, 168), "Message")
|
||||
|
||||
var hs HardState
|
||||
assert(unsafe.Sizeof(hs), 24, "HardState")
|
||||
|
||||
var cs ConfState
|
||||
assert(unsafe.Sizeof(cs), if64Bit(104, 52), "ConfState")
|
||||
|
||||
var cc ConfChange
|
||||
assert(unsafe.Sizeof(cc), if64Bit(48, 32), "ConfChange")
|
||||
|
||||
var ccs ConfChangeSingle
|
||||
assert(unsafe.Sizeof(ccs), if64Bit(16, 12), "ConfChangeSingle")
|
||||
|
||||
var ccv2 ConfChangeV2
|
||||
assert(unsafe.Sizeof(ccv2), if64Bit(56, 28), "ConfChangeV2")
|
||||
}
|
||||
1107
vendor/go.etcd.io/etcd/raft/v3/rawnode_test.go
generated
vendored
1107
vendor/go.etcd.io/etcd/raft/v3/rawnode_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
290
vendor/go.etcd.io/etcd/raft/v3/storage_test.go
generated
vendored
290
vendor/go.etcd.io/etcd/raft/v3/storage_test.go
generated
vendored
@ -1,290 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
)
|
||||
|
||||
func TestStorageTerm(t *testing.T) {
|
||||
ents := []pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}}
|
||||
tests := []struct {
|
||||
i uint64
|
||||
|
||||
werr error
|
||||
wterm uint64
|
||||
wpanic bool
|
||||
}{
|
||||
{2, ErrCompacted, 0, false},
|
||||
{3, nil, 3, false},
|
||||
{4, nil, 4, false},
|
||||
{5, nil, 5, false},
|
||||
{6, ErrUnavailable, 0, false},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
s := &MemoryStorage{ents: ents}
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wpanic {
|
||||
t.Errorf("%d: panic = %v, want %v", i, true, tt.wpanic)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
term, err := s.Term(tt.i)
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
if term != tt.wterm {
|
||||
t.Errorf("#%d: term = %d, want %d", i, term, tt.wterm)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageEntries(t *testing.T) {
|
||||
ents := []pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 6}}
|
||||
tests := []struct {
|
||||
lo, hi, maxsize uint64
|
||||
|
||||
werr error
|
||||
wentries []pb.Entry
|
||||
}{
|
||||
{2, 6, math.MaxUint64, ErrCompacted, nil},
|
||||
{3, 4, math.MaxUint64, ErrCompacted, nil},
|
||||
{4, 5, math.MaxUint64, nil, []pb.Entry{{Index: 4, Term: 4}}},
|
||||
{4, 6, math.MaxUint64, nil, []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}}},
|
||||
{4, 7, math.MaxUint64, nil, []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 6}}},
|
||||
// even if maxsize is zero, the first entry should be returned
|
||||
{4, 7, 0, nil, []pb.Entry{{Index: 4, Term: 4}}},
|
||||
// limit to 2
|
||||
{4, 7, uint64(ents[1].Size() + ents[2].Size()), nil, []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}}},
|
||||
// limit to 2
|
||||
{4, 7, uint64(ents[1].Size() + ents[2].Size() + ents[3].Size()/2), nil, []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}}},
|
||||
{4, 7, uint64(ents[1].Size() + ents[2].Size() + ents[3].Size() - 1), nil, []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}}},
|
||||
// all
|
||||
{4, 7, uint64(ents[1].Size() + ents[2].Size() + ents[3].Size()), nil, []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 6}}},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
s := &MemoryStorage{ents: ents}
|
||||
entries, err := s.Entries(tt.lo, tt.hi, tt.maxsize)
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
if !reflect.DeepEqual(entries, tt.wentries) {
|
||||
t.Errorf("#%d: entries = %v, want %v", i, entries, tt.wentries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageLastIndex(t *testing.T) {
|
||||
ents := []pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}}
|
||||
s := &MemoryStorage{ents: ents}
|
||||
|
||||
last, err := s.LastIndex()
|
||||
if err != nil {
|
||||
t.Errorf("err = %v, want nil", err)
|
||||
}
|
||||
if last != 5 {
|
||||
t.Errorf("last = %d, want %d", last, 5)
|
||||
}
|
||||
|
||||
s.Append([]pb.Entry{{Index: 6, Term: 5}})
|
||||
last, err = s.LastIndex()
|
||||
if err != nil {
|
||||
t.Errorf("err = %v, want nil", err)
|
||||
}
|
||||
if last != 6 {
|
||||
t.Errorf("last = %d, want %d", last, 6)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageFirstIndex(t *testing.T) {
|
||||
ents := []pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}}
|
||||
s := &MemoryStorage{ents: ents}
|
||||
|
||||
first, err := s.FirstIndex()
|
||||
if err != nil {
|
||||
t.Errorf("err = %v, want nil", err)
|
||||
}
|
||||
if first != 4 {
|
||||
t.Errorf("first = %d, want %d", first, 4)
|
||||
}
|
||||
|
||||
s.Compact(4)
|
||||
first, err = s.FirstIndex()
|
||||
if err != nil {
|
||||
t.Errorf("err = %v, want nil", err)
|
||||
}
|
||||
if first != 5 {
|
||||
t.Errorf("first = %d, want %d", first, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageCompact(t *testing.T) {
|
||||
ents := []pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}}
|
||||
tests := []struct {
|
||||
i uint64
|
||||
|
||||
werr error
|
||||
windex uint64
|
||||
wterm uint64
|
||||
wlen int
|
||||
}{
|
||||
{2, ErrCompacted, 3, 3, 3},
|
||||
{3, ErrCompacted, 3, 3, 3},
|
||||
{4, nil, 4, 4, 2},
|
||||
{5, nil, 5, 5, 1},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
s := &MemoryStorage{ents: ents}
|
||||
err := s.Compact(tt.i)
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
if s.ents[0].Index != tt.windex {
|
||||
t.Errorf("#%d: index = %d, want %d", i, s.ents[0].Index, tt.windex)
|
||||
}
|
||||
if s.ents[0].Term != tt.wterm {
|
||||
t.Errorf("#%d: term = %d, want %d", i, s.ents[0].Term, tt.wterm)
|
||||
}
|
||||
if len(s.ents) != tt.wlen {
|
||||
t.Errorf("#%d: len = %d, want %d", i, len(s.ents), tt.wlen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageCreateSnapshot(t *testing.T) {
|
||||
ents := []pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}}
|
||||
cs := &pb.ConfState{Voters: []uint64{1, 2, 3}}
|
||||
data := []byte("data")
|
||||
|
||||
tests := []struct {
|
||||
i uint64
|
||||
|
||||
werr error
|
||||
wsnap pb.Snapshot
|
||||
}{
|
||||
{4, nil, pb.Snapshot{Data: data, Metadata: pb.SnapshotMetadata{Index: 4, Term: 4, ConfState: *cs}}},
|
||||
{5, nil, pb.Snapshot{Data: data, Metadata: pb.SnapshotMetadata{Index: 5, Term: 5, ConfState: *cs}}},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
s := &MemoryStorage{ents: ents}
|
||||
snap, err := s.CreateSnapshot(tt.i, cs, data)
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
if !reflect.DeepEqual(snap, tt.wsnap) {
|
||||
t.Errorf("#%d: snap = %+v, want %+v", i, snap, tt.wsnap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageAppend(t *testing.T) {
|
||||
ents := []pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}}
|
||||
tests := []struct {
|
||||
entries []pb.Entry
|
||||
|
||||
werr error
|
||||
wentries []pb.Entry
|
||||
}{
|
||||
{
|
||||
[]pb.Entry{{Index: 1, Term: 1}, {Index: 2, Term: 2}},
|
||||
nil,
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}},
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}},
|
||||
nil,
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}},
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 6}, {Index: 5, Term: 6}},
|
||||
nil,
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 6}, {Index: 5, Term: 6}},
|
||||
},
|
||||
{
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 5}},
|
||||
nil,
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 5}},
|
||||
},
|
||||
// truncate incoming entries, truncate the existing entries and append
|
||||
{
|
||||
[]pb.Entry{{Index: 2, Term: 3}, {Index: 3, Term: 3}, {Index: 4, Term: 5}},
|
||||
nil,
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 5}},
|
||||
},
|
||||
// truncate the existing entries and append
|
||||
{
|
||||
[]pb.Entry{{Index: 4, Term: 5}},
|
||||
nil,
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 5}},
|
||||
},
|
||||
// direct append
|
||||
{
|
||||
[]pb.Entry{{Index: 6, Term: 5}},
|
||||
nil,
|
||||
[]pb.Entry{{Index: 3, Term: 3}, {Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 5}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
s := &MemoryStorage{ents: ents}
|
||||
err := s.Append(tt.entries)
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
if !reflect.DeepEqual(s.ents, tt.wentries) {
|
||||
t.Errorf("#%d: entries = %v, want %v", i, s.ents, tt.wentries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageApplySnapshot(t *testing.T) {
|
||||
cs := &pb.ConfState{Voters: []uint64{1, 2, 3}}
|
||||
data := []byte("data")
|
||||
|
||||
tests := []pb.Snapshot{{Data: data, Metadata: pb.SnapshotMetadata{Index: 4, Term: 4, ConfState: *cs}},
|
||||
{Data: data, Metadata: pb.SnapshotMetadata{Index: 3, Term: 3, ConfState: *cs}},
|
||||
}
|
||||
|
||||
s := NewMemoryStorage()
|
||||
|
||||
//Apply Snapshot successful
|
||||
i := 0
|
||||
tt := tests[i]
|
||||
err := s.ApplySnapshot(tt)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, nil)
|
||||
}
|
||||
|
||||
//Apply Snapshot fails due to ErrSnapOutOfDate
|
||||
i = 1
|
||||
tt = tests[i]
|
||||
err = s.ApplySnapshot(tt)
|
||||
if err != ErrSnapOutOfDate {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, ErrSnapOutOfDate)
|
||||
}
|
||||
}
|
||||
117
vendor/go.etcd.io/etcd/raft/v3/testdata/campaign.txt
generated
vendored
117
vendor/go.etcd.io/etcd/raft/v3/testdata/campaign.txt
generated
vendored
@ -1,117 +0,0 @@
|
||||
log-level info
|
||||
----
|
||||
ok
|
||||
|
||||
add-nodes 3 voters=(1,2,3) index=2
|
||||
----
|
||||
INFO 1 switched to configuration voters=(1 2 3)
|
||||
INFO 1 became follower at term 0
|
||||
INFO newRaft 1 [peers: [1,2,3], term: 0, commit: 2, applied: 2, lastindex: 2, lastterm: 1]
|
||||
INFO 2 switched to configuration voters=(1 2 3)
|
||||
INFO 2 became follower at term 0
|
||||
INFO newRaft 2 [peers: [1,2,3], term: 0, commit: 2, applied: 2, lastindex: 2, lastterm: 1]
|
||||
INFO 3 switched to configuration voters=(1 2 3)
|
||||
INFO 3 became follower at term 0
|
||||
INFO newRaft 3 [peers: [1,2,3], term: 0, commit: 2, applied: 2, lastindex: 2, lastterm: 1]
|
||||
|
||||
campaign 1
|
||||
----
|
||||
INFO 1 is starting a new election at term 0
|
||||
INFO 1 became candidate at term 1
|
||||
INFO 1 received MsgVoteResp from 1 at term 1
|
||||
INFO 1 [logterm: 1, index: 2] sent MsgVote request to 2 at term 1
|
||||
INFO 1 [logterm: 1, index: 2] sent MsgVote request to 3 at term 1
|
||||
|
||||
stabilize
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:0 State:StateCandidate
|
||||
HardState Term:1 Vote:1 Commit:2
|
||||
Messages:
|
||||
1->2 MsgVote Term:1 Log:1/2
|
||||
1->3 MsgVote Term:1 Log:1/2
|
||||
> 2 receiving messages
|
||||
1->2 MsgVote Term:1 Log:1/2
|
||||
INFO 2 [term: 0] received a MsgVote message with higher term from 1 [term: 1]
|
||||
INFO 2 became follower at term 1
|
||||
INFO 2 [logterm: 1, index: 2, vote: 0] cast MsgVote for 1 [logterm: 1, index: 2] at term 1
|
||||
> 3 receiving messages
|
||||
1->3 MsgVote Term:1 Log:1/2
|
||||
INFO 3 [term: 0] received a MsgVote message with higher term from 1 [term: 1]
|
||||
INFO 3 became follower at term 1
|
||||
INFO 3 [logterm: 1, index: 2, vote: 0] cast MsgVote for 1 [logterm: 1, index: 2] at term 1
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:1 Vote:1 Commit:2
|
||||
Messages:
|
||||
2->1 MsgVoteResp Term:1 Log:0/0
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:1 Vote:1 Commit:2
|
||||
Messages:
|
||||
3->1 MsgVoteResp Term:1 Log:0/0
|
||||
> 1 receiving messages
|
||||
2->1 MsgVoteResp Term:1 Log:0/0
|
||||
INFO 1 received MsgVoteResp from 2 at term 1
|
||||
INFO 1 has received 2 MsgVoteResp votes and 0 vote rejections
|
||||
INFO 1 became leader at term 1
|
||||
3->1 MsgVoteResp Term:1 Log:0/0
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateLeader
|
||||
Entries:
|
||||
1/3 EntryNormal ""
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/2 Commit:2 Entries:[1/3 EntryNormal ""]
|
||||
1->3 MsgApp Term:1 Log:1/2 Commit:2 Entries:[1/3 EntryNormal ""]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/2 Commit:2 Entries:[1/3 EntryNormal ""]
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/2 Commit:2 Entries:[1/3 EntryNormal ""]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
Entries:
|
||||
1/3 EntryNormal ""
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/3
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
Entries:
|
||||
1/3 EntryNormal ""
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/3
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/3
|
||||
3->1 MsgAppResp Term:1 Log:0/3
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:3
|
||||
CommittedEntries:
|
||||
1/3 EntryNormal ""
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:3
|
||||
1->3 MsgApp Term:1 Log:1/3 Commit:3
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:3
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/3 Commit:3
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:3
|
||||
CommittedEntries:
|
||||
1/3 EntryNormal ""
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/3
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:3
|
||||
CommittedEntries:
|
||||
1/3 EntryNormal ""
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/3
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/3
|
||||
3->1 MsgAppResp Term:1 Log:0/3
|
||||
152
vendor/go.etcd.io/etcd/raft/v3/testdata/campaign_learner_must_vote.txt
generated
vendored
152
vendor/go.etcd.io/etcd/raft/v3/testdata/campaign_learner_must_vote.txt
generated
vendored
@ -1,152 +0,0 @@
|
||||
# Regression test that verifies that learners can vote. This holds only in the
|
||||
# sense that if a learner is asked to vote, a candidate believes that they are a
|
||||
# voter based on its current config, which may be more recent than that of the
|
||||
# learner. If learners which are actually voters but don't know it yet don't
|
||||
# vote in that situation, the raft group may end up unavailable despite a quorum
|
||||
# of voters (as of the latest config) being available.
|
||||
#
|
||||
# See:
|
||||
# https://github.com/etcd-io/etcd/pull/10998
|
||||
|
||||
# Turn output off during boilerplate.
|
||||
log-level none
|
||||
----
|
||||
ok
|
||||
|
||||
# Bootstrap three nodes.
|
||||
add-nodes 3 voters=(1,2) learners=(3) index=2
|
||||
----
|
||||
ok
|
||||
|
||||
# n1 gets to be leader.
|
||||
campaign 1
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
# Propose a conf change on n1 that promotes n3 to voter.
|
||||
propose-conf-change 1
|
||||
v3
|
||||
----
|
||||
ok
|
||||
|
||||
# Commit and fully apply said conf change. n1 and n2 now consider n3 a voter.
|
||||
stabilize 1 2
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
# Drop all inflight messages to 3. We don't want it to be caught up when it is
|
||||
# asked to vote.
|
||||
deliver-msgs drop=(3)
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
# We now pretend that n1 is dead, and n2 is trying to become leader.
|
||||
|
||||
log-level debug
|
||||
----
|
||||
ok
|
||||
|
||||
campaign 2
|
||||
----
|
||||
INFO 2 is starting a new election at term 1
|
||||
INFO 2 became candidate at term 2
|
||||
INFO 2 received MsgVoteResp from 2 at term 2
|
||||
INFO 2 [logterm: 1, index: 4] sent MsgVote request to 1 at term 2
|
||||
INFO 2 [logterm: 1, index: 4] sent MsgVote request to 3 at term 2
|
||||
|
||||
# Send out the MsgVote requests.
|
||||
process-ready 2
|
||||
----
|
||||
Ready MustSync=true:
|
||||
Lead:0 State:StateCandidate
|
||||
HardState Term:2 Vote:2 Commit:4
|
||||
Messages:
|
||||
2->1 MsgVote Term:2 Log:1/4
|
||||
2->3 MsgVote Term:2 Log:1/4
|
||||
|
||||
# n2 is now campaigning while n1 is down (does not respond). The latest config
|
||||
# has n3 as a voter, but n3 doesn't even have the corresponding conf change in
|
||||
# its log. Still, it casts a vote for n2 which can in turn become leader and
|
||||
# catches up n3.
|
||||
stabilize 3
|
||||
----
|
||||
> 3 receiving messages
|
||||
2->3 MsgVote Term:2 Log:1/4
|
||||
INFO 3 [term: 1] received a MsgVote message with higher term from 2 [term: 2]
|
||||
INFO 3 became follower at term 2
|
||||
INFO 3 [logterm: 1, index: 3, vote: 0] cast MsgVote for 2 [logterm: 1, index: 4] at term 2
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:0 State:StateFollower
|
||||
HardState Term:2 Vote:2 Commit:3
|
||||
Messages:
|
||||
3->2 MsgVoteResp Term:2 Log:0/0
|
||||
|
||||
stabilize 2 3
|
||||
----
|
||||
> 2 receiving messages
|
||||
3->2 MsgVoteResp Term:2 Log:0/0
|
||||
INFO 2 received MsgVoteResp from 3 at term 2
|
||||
INFO 2 has received 2 MsgVoteResp votes and 0 vote rejections
|
||||
INFO 2 became leader at term 2
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:2 State:StateLeader
|
||||
Entries:
|
||||
2/5 EntryNormal ""
|
||||
Messages:
|
||||
2->1 MsgApp Term:2 Log:1/4 Commit:4 Entries:[2/5 EntryNormal ""]
|
||||
2->3 MsgApp Term:2 Log:1/4 Commit:4 Entries:[2/5 EntryNormal ""]
|
||||
> 3 receiving messages
|
||||
2->3 MsgApp Term:2 Log:1/4 Commit:4 Entries:[2/5 EntryNormal ""]
|
||||
DEBUG 3 [logterm: 0, index: 4] rejected MsgApp [logterm: 1, index: 4] from 2
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
Lead:2 State:StateFollower
|
||||
Messages:
|
||||
3->2 MsgAppResp Term:2 Log:1/4 Rejected (Hint: 3)
|
||||
> 2 receiving messages
|
||||
3->2 MsgAppResp Term:2 Log:1/4 Rejected (Hint: 3)
|
||||
DEBUG 2 received MsgAppResp(rejected, hint: (index 3, term 1)) from 3 for index 4
|
||||
DEBUG 2 decreased progress of 3 to [StateProbe match=0 next=4]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
2->3 MsgApp Term:2 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v3, 2/5 EntryNormal ""]
|
||||
> 3 receiving messages
|
||||
2->3 MsgApp Term:2 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v3, 2/5 EntryNormal ""]
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:2 Vote:2 Commit:4
|
||||
Entries:
|
||||
1/4 EntryConfChangeV2 v3
|
||||
2/5 EntryNormal ""
|
||||
CommittedEntries:
|
||||
1/4 EntryConfChangeV2 v3
|
||||
Messages:
|
||||
3->2 MsgAppResp Term:2 Log:0/5
|
||||
INFO 3 switched to configuration voters=(1 2 3)
|
||||
> 2 receiving messages
|
||||
3->2 MsgAppResp Term:2 Log:0/5
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:2 Vote:2 Commit:5
|
||||
CommittedEntries:
|
||||
2/5 EntryNormal ""
|
||||
Messages:
|
||||
2->3 MsgApp Term:2 Log:2/5 Commit:5
|
||||
> 3 receiving messages
|
||||
2->3 MsgApp Term:2 Log:2/5 Commit:5
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:2 Vote:2 Commit:5
|
||||
CommittedEntries:
|
||||
2/5 EntryNormal ""
|
||||
Messages:
|
||||
3->2 MsgAppResp Term:2 Log:0/5
|
||||
> 2 receiving messages
|
||||
3->2 MsgAppResp Term:2 Log:0/5
|
||||
97
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v1_add_single.txt
generated
vendored
97
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v1_add_single.txt
generated
vendored
@ -1,97 +0,0 @@
|
||||
# Run a V1 membership change that adds a single voter.
|
||||
|
||||
# Bootstrap n1.
|
||||
add-nodes 1 voters=(1) index=2
|
||||
----
|
||||
INFO 1 switched to configuration voters=(1)
|
||||
INFO 1 became follower at term 0
|
||||
INFO newRaft 1 [peers: [1], term: 0, commit: 2, applied: 2, lastindex: 2, lastterm: 1]
|
||||
|
||||
campaign 1
|
||||
----
|
||||
INFO 1 is starting a new election at term 0
|
||||
INFO 1 became candidate at term 1
|
||||
INFO 1 received MsgVoteResp from 1 at term 1
|
||||
INFO 1 became leader at term 1
|
||||
|
||||
# Add v2 (with an auto transition).
|
||||
propose-conf-change 1 v1=true
|
||||
v2
|
||||
----
|
||||
ok
|
||||
|
||||
# Pull n2 out of thin air.
|
||||
add-nodes 1
|
||||
----
|
||||
INFO 2 switched to configuration voters=()
|
||||
INFO 2 became follower at term 0
|
||||
INFO newRaft 2 [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]
|
||||
|
||||
# n1 commits the conf change using itself as commit quorum, immediately transitions into
|
||||
# the final config, and catches up n2. Note that it's using an EntryConfChange, not an
|
||||
# EntryConfChangeV2, so this is compatible with nodes that don't know about V2 conf changes.
|
||||
stabilize
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateLeader
|
||||
HardState Term:1 Vote:1 Commit:4
|
||||
Entries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChange v2
|
||||
CommittedEntries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChange v2
|
||||
INFO 1 switched to configuration voters=(1 2)
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChange v2]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChange v2]
|
||||
INFO 2 [term: 0] received a MsgApp message with higher term from 1 [term: 1]
|
||||
INFO 2 became follower at term 1
|
||||
DEBUG 2 [logterm: 0, index: 3] rejected MsgApp [logterm: 1, index: 3] from 1
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
HardState Term:1 Commit:0
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
DEBUG 1 received MsgAppResp(rejected, hint: (index 0, term 0)) from 2 for index 3
|
||||
DEBUG 1 decreased progress of 2 to [StateProbe match=0 next=1]
|
||||
DEBUG 1 [firstindex: 3, commit: 4] sent snapshot[index: 4, term: 1] to 2 [StateProbe match=0 next=1]
|
||||
DEBUG 1 paused sending replication messages to 2 [StateSnapshot match=0 next=1 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
> 2 receiving messages
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
INFO log [committed=0, applied=0, unstable.offset=1, len(unstable.Entries)=0] starts to restore snapshot [index: 4, term: 1]
|
||||
INFO 2 switched to configuration voters=(1 2)
|
||||
INFO 2 [commit: 4, lastindex: 4, lastterm: 1] restored snapshot [index: 4, term: 1]
|
||||
INFO 2 [commit: 4] restored snapshot [index: 4, term: 1]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:4
|
||||
Snapshot Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
DEBUG 1 recovered from needing snapshot, resumed sending replication messages to 2 [StateSnapshot match=4 next=5 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
224
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v1_remove_leader.txt
generated
vendored
224
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v1_remove_leader.txt
generated
vendored
@ -1,224 +0,0 @@
|
||||
# We'll turn this back on after the boilerplate.
|
||||
log-level none
|
||||
----
|
||||
ok
|
||||
|
||||
# Run a V1 membership change that removes the leader.
|
||||
# Bootstrap n1, n2, n3.
|
||||
add-nodes 3 voters=(1,2,3) index=2
|
||||
----
|
||||
ok
|
||||
|
||||
campaign 1
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
log-level debug
|
||||
----
|
||||
ok
|
||||
|
||||
# Start removing n1.
|
||||
propose-conf-change 1 v1=true
|
||||
r1
|
||||
----
|
||||
ok
|
||||
|
||||
# Propose an extra entry which will be sent out together with the conf change.
|
||||
propose 1 foo
|
||||
----
|
||||
ok
|
||||
|
||||
# Send out the corresponding appends.
|
||||
process-ready 1
|
||||
----
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/4 EntryConfChange r1
|
||||
1/5 EntryNormal "foo"
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:3 Entries:[1/4 EntryConfChange r1]
|
||||
1->3 MsgApp Term:1 Log:1/3 Commit:3 Entries:[1/4 EntryConfChange r1]
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:3 Entries:[1/5 EntryNormal "foo"]
|
||||
1->3 MsgApp Term:1 Log:1/4 Commit:3 Entries:[1/5 EntryNormal "foo"]
|
||||
|
||||
# Send response from n2 (which is enough to commit the entries so far next time
|
||||
# n1 runs).
|
||||
stabilize 2
|
||||
----
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:3 Entries:[1/4 EntryConfChange r1]
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:3 Entries:[1/5 EntryNormal "foo"]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/4 EntryConfChange r1
|
||||
1/5 EntryNormal "foo"
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
|
||||
# Put another entry in n1's log.
|
||||
propose 1 bar
|
||||
----
|
||||
ok
|
||||
|
||||
# n1 applies the conf change, so it has now removed itself. But it still has
|
||||
# an uncommitted entry in the log. If the leader unconditionally counted itself
|
||||
# as part of the commit quorum, we'd be in trouble. In the block below, we see
|
||||
# it send out appends to the other nodes for the 'bar' entry.
|
||||
stabilize 1
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/6 EntryNormal "bar"
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:3 Entries:[1/6 EntryNormal "bar"]
|
||||
1->3 MsgApp Term:1 Log:1/5 Commit:3 Entries:[1/6 EntryNormal "bar"]
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:5
|
||||
CommittedEntries:
|
||||
1/4 EntryConfChange r1
|
||||
1/5 EntryNormal "foo"
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:4
|
||||
1->3 MsgApp Term:1 Log:1/6 Commit:4
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:5
|
||||
1->3 MsgApp Term:1 Log:1/6 Commit:5
|
||||
INFO 1 switched to configuration voters=(2 3)
|
||||
|
||||
# n2 responds, n3 doesn't yet. Quorum for 'bar' should not be reached...
|
||||
stabilize 2
|
||||
----
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:3 Entries:[1/6 EntryNormal "bar"]
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:4
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:5
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:1 Vote:1 Commit:5
|
||||
Entries:
|
||||
1/6 EntryNormal "bar"
|
||||
CommittedEntries:
|
||||
1/4 EntryConfChange r1
|
||||
1/5 EntryNormal "foo"
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
INFO 2 switched to configuration voters=(2 3)
|
||||
|
||||
# ... which thankfully is what we see on the leader.
|
||||
stabilize 1
|
||||
----
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
|
||||
# When n3 responds, quorum is reached and everything falls into place.
|
||||
stabilize
|
||||
----
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/3 Commit:3 Entries:[1/4 EntryConfChange r1]
|
||||
1->3 MsgApp Term:1 Log:1/4 Commit:3 Entries:[1/5 EntryNormal "foo"]
|
||||
1->3 MsgApp Term:1 Log:1/5 Commit:3 Entries:[1/6 EntryNormal "bar"]
|
||||
1->3 MsgApp Term:1 Log:1/6 Commit:4
|
||||
1->3 MsgApp Term:1 Log:1/6 Commit:5
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:1 Vote:1 Commit:5
|
||||
Entries:
|
||||
1/4 EntryConfChange r1
|
||||
1/5 EntryNormal "foo"
|
||||
1/6 EntryNormal "bar"
|
||||
CommittedEntries:
|
||||
1/4 EntryConfChange r1
|
||||
1/5 EntryNormal "foo"
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/4
|
||||
3->1 MsgAppResp Term:1 Log:0/5
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
INFO 3 switched to configuration voters=(2 3)
|
||||
> 1 receiving messages
|
||||
3->1 MsgAppResp Term:1 Log:0/4
|
||||
3->1 MsgAppResp Term:1 Log:0/5
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:6
|
||||
CommittedEntries:
|
||||
1/6 EntryNormal "bar"
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:6
|
||||
1->3 MsgApp Term:1 Log:1/6 Commit:6
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:6
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/6 Commit:6
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:6
|
||||
CommittedEntries:
|
||||
1/6 EntryNormal "bar"
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:6
|
||||
CommittedEntries:
|
||||
1/6 EntryNormal "bar"
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
|
||||
# However not all is well. n1 is still leader but unconditionally drops all
|
||||
# proposals on the floor, so we're effectively stuck if it still heartbeats
|
||||
# its followers...
|
||||
propose 1 baz
|
||||
----
|
||||
raft proposal dropped
|
||||
|
||||
tick-heartbeat 1
|
||||
----
|
||||
ok
|
||||
|
||||
# ... which, uh oh, it does.
|
||||
# TODO(tbg): change behavior so that a leader that is removed immediately steps
|
||||
# down, and initiates an optimistic handover.
|
||||
stabilize
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgHeartbeat Term:1 Log:0/0 Commit:6
|
||||
1->3 MsgHeartbeat Term:1 Log:0/0 Commit:6
|
||||
> 2 receiving messages
|
||||
1->2 MsgHeartbeat Term:1 Log:0/0 Commit:6
|
||||
> 3 receiving messages
|
||||
1->3 MsgHeartbeat Term:1 Log:0/0 Commit:6
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
2->1 MsgHeartbeatResp Term:1 Log:0/0
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
3->1 MsgHeartbeatResp Term:1 Log:0/0
|
||||
> 1 receiving messages
|
||||
2->1 MsgHeartbeatResp Term:1 Log:0/0
|
||||
3->1 MsgHeartbeatResp Term:1 Log:0/0
|
||||
408
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v2_add_double_auto.txt
generated
vendored
408
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v2_add_double_auto.txt
generated
vendored
@ -1,408 +0,0 @@
|
||||
# Run a V2 membership change that adds two voters at once and auto-leaves the
|
||||
# joint configuration. (This is the same as specifying an explicit transition
|
||||
# since more than one change is being made atomically).
|
||||
|
||||
# Bootstrap n1.
|
||||
add-nodes 1 voters=(1) index=2
|
||||
----
|
||||
INFO 1 switched to configuration voters=(1)
|
||||
INFO 1 became follower at term 0
|
||||
INFO newRaft 1 [peers: [1], term: 0, commit: 2, applied: 2, lastindex: 2, lastterm: 1]
|
||||
|
||||
campaign 1
|
||||
----
|
||||
INFO 1 is starting a new election at term 0
|
||||
INFO 1 became candidate at term 1
|
||||
INFO 1 received MsgVoteResp from 1 at term 1
|
||||
INFO 1 became leader at term 1
|
||||
|
||||
propose-conf-change 1 transition=auto
|
||||
v2 v3
|
||||
----
|
||||
ok
|
||||
|
||||
# Add two "empty" nodes to the cluster, n2 and n3.
|
||||
add-nodes 2
|
||||
----
|
||||
INFO 2 switched to configuration voters=()
|
||||
INFO 2 became follower at term 0
|
||||
INFO newRaft 2 [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]
|
||||
INFO 3 switched to configuration voters=()
|
||||
INFO 3 became follower at term 0
|
||||
INFO newRaft 3 [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]
|
||||
|
||||
# n1 immediately gets to commit & apply the conf change using only itself. We see that
|
||||
# it starts transitioning out of that joint configuration (though we will only see that
|
||||
# proposal in the next ready handling loop, when it is emitted). We also see that this
|
||||
# is using joint consensus, which it has to since we're carrying out two additions at
|
||||
# once.
|
||||
process-ready 1
|
||||
----
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateLeader
|
||||
HardState Term:1 Vote:1 Commit:4
|
||||
Entries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChangeV2 v2 v3
|
||||
CommittedEntries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChangeV2 v2 v3
|
||||
INFO 1 switched to configuration voters=(1 2 3)&&(1) autoleave
|
||||
INFO initiating automatic transition out of joint configuration voters=(1 2 3)&&(1) autoleave
|
||||
|
||||
# n1 immediately probes n2 and n3.
|
||||
stabilize 1
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/5 EntryConfChangeV2
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2 v3]
|
||||
1->3 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2 v3]
|
||||
|
||||
# First, play out the whole interaction between n1 and n2. We see n1's probe to
|
||||
# n2 get rejected (since n2 needs a snapshot); the snapshot is delivered at which
|
||||
# point n2 switches to the correct config, and n1 catches it up. This notably
|
||||
# includes the empty conf change which gets committed and applied by both and
|
||||
# which transitions them out of their joint configuration into the final one (1 2 3).
|
||||
stabilize 1 2
|
||||
----
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2 v3]
|
||||
INFO 2 [term: 0] received a MsgApp message with higher term from 1 [term: 1]
|
||||
INFO 2 became follower at term 1
|
||||
DEBUG 2 [logterm: 0, index: 3] rejected MsgApp [logterm: 1, index: 3] from 1
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
HardState Term:1 Commit:0
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
DEBUG 1 received MsgAppResp(rejected, hint: (index 0, term 0)) from 2 for index 3
|
||||
DEBUG 1 decreased progress of 2 to [StateProbe match=0 next=1]
|
||||
DEBUG 1 [firstindex: 3, commit: 4] sent snapshot[index: 4, term: 1] to 2 [StateProbe match=0 next=1]
|
||||
DEBUG 1 paused sending replication messages to 2 [StateSnapshot match=0 next=1 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:true
|
||||
> 2 receiving messages
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:true
|
||||
INFO log [committed=0, applied=0, unstable.offset=1, len(unstable.Entries)=0] starts to restore snapshot [index: 4, term: 1]
|
||||
INFO 2 switched to configuration voters=(1 2 3)&&(1) autoleave
|
||||
INFO 2 [commit: 4, lastindex: 4, lastterm: 1] restored snapshot [index: 4, term: 1]
|
||||
INFO 2 [commit: 4] restored snapshot [index: 4, term: 1]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:4
|
||||
Snapshot Index:4 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:true
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
DEBUG 1 recovered from needing snapshot, resumed sending replication messages to 2 [StateSnapshot match=4 next=5 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4 Entries:[1/5 EntryConfChangeV2]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4 Entries:[1/5 EntryConfChangeV2]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/5 EntryConfChangeV2
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:5
|
||||
CommittedEntries:
|
||||
1/5 EntryConfChangeV2
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:5
|
||||
INFO 1 switched to configuration voters=(1 2 3)
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:5
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:5
|
||||
CommittedEntries:
|
||||
1/5 EntryConfChangeV2
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
INFO 2 switched to configuration voters=(1 2 3)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
|
||||
# n3 immediately receives a snapshot in the final configuration.
|
||||
stabilize 1 3
|
||||
----
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2 v3]
|
||||
INFO 3 [term: 0] received a MsgApp message with higher term from 1 [term: 1]
|
||||
INFO 3 became follower at term 1
|
||||
DEBUG 3 [logterm: 0, index: 3] rejected MsgApp [logterm: 1, index: 3] from 1
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
HardState Term:1 Commit:0
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
> 1 receiving messages
|
||||
3->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
DEBUG 1 received MsgAppResp(rejected, hint: (index 0, term 0)) from 3 for index 3
|
||||
DEBUG 1 decreased progress of 3 to [StateProbe match=0 next=1]
|
||||
DEBUG 1 [firstindex: 3, commit: 5] sent snapshot[index: 5, term: 1] to 3 [StateProbe match=0 next=1]
|
||||
DEBUG 1 paused sending replication messages to 3 [StateSnapshot match=0 next=1 paused pendingSnap=5]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->3 MsgSnap Term:1 Log:0/0 Snapshot: Index:5 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
> 3 receiving messages
|
||||
1->3 MsgSnap Term:1 Log:0/0 Snapshot: Index:5 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
INFO log [committed=0, applied=0, unstable.offset=1, len(unstable.Entries)=0] starts to restore snapshot [index: 5, term: 1]
|
||||
INFO 3 switched to configuration voters=(1 2 3)
|
||||
INFO 3 [commit: 5, lastindex: 5, lastterm: 1] restored snapshot [index: 5, term: 1]
|
||||
INFO 3 [commit: 5] restored snapshot [index: 5, term: 1]
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:5
|
||||
Snapshot Index:5 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/5
|
||||
> 1 receiving messages
|
||||
3->1 MsgAppResp Term:1 Log:0/5
|
||||
DEBUG 1 recovered from needing snapshot, resumed sending replication messages to 3 [StateSnapshot match=5 next=6 paused pendingSnap=5]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->3 MsgApp Term:1 Log:1/5 Commit:5
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/5 Commit:5
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/5
|
||||
> 1 receiving messages
|
||||
3->1 MsgAppResp Term:1 Log:0/5
|
||||
|
||||
# Nothing else happens.
|
||||
stabilize
|
||||
----
|
||||
ok
|
||||
|
||||
# Now remove two nodes. What's new here is that the leader will actually have
|
||||
# to go to a quorum to commit the transition into the joint config.
|
||||
|
||||
propose-conf-change 1
|
||||
r2 r3
|
||||
----
|
||||
ok
|
||||
|
||||
# n1 sends out MsgApps.
|
||||
stabilize 1
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/6 EntryConfChangeV2 r2 r3
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:5 Entries:[1/6 EntryConfChangeV2 r2 r3]
|
||||
1->3 MsgApp Term:1 Log:1/5 Commit:5 Entries:[1/6 EntryConfChangeV2 r2 r3]
|
||||
|
||||
# n2, n3 ack them.
|
||||
stabilize 2 3
|
||||
----
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:5 Entries:[1/6 EntryConfChangeV2 r2 r3]
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/5 Commit:5 Entries:[1/6 EntryConfChangeV2 r2 r3]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/6 EntryConfChangeV2 r2 r3
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/6 EntryConfChangeV2 r2 r3
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
|
||||
# n1 gets some more proposals. This is part of a regression test: There used to
|
||||
# be a bug in which these proposals would prompt the leader to transition out of
|
||||
# the same joint state multiple times, which would cause a panic.
|
||||
propose 1 foo
|
||||
----
|
||||
ok
|
||||
|
||||
propose 1 bar
|
||||
----
|
||||
ok
|
||||
|
||||
# n1 switches to the joint config, then initiates a transition into the final
|
||||
# config.
|
||||
stabilize 1
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/7 EntryNormal "foo"
|
||||
1/8 EntryNormal "bar"
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:5 Entries:[1/7 EntryNormal "foo"]
|
||||
1->3 MsgApp Term:1 Log:1/6 Commit:5 Entries:[1/7 EntryNormal "foo"]
|
||||
1->2 MsgApp Term:1 Log:1/7 Commit:5 Entries:[1/8 EntryNormal "bar"]
|
||||
1->3 MsgApp Term:1 Log:1/7 Commit:5 Entries:[1/8 EntryNormal "bar"]
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
3->1 MsgAppResp Term:1 Log:0/6
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:6
|
||||
CommittedEntries:
|
||||
1/6 EntryConfChangeV2 r2 r3
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/8 Commit:6
|
||||
1->3 MsgApp Term:1 Log:1/8 Commit:6
|
||||
INFO 1 switched to configuration voters=(1)&&(1 2 3) autoleave
|
||||
INFO initiating automatic transition out of joint configuration voters=(1)&&(1 2 3) autoleave
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/9 EntryConfChangeV2
|
||||
|
||||
# n2 and n3 also switch to the joint config, and ack the transition out of it.
|
||||
stabilize 2 3
|
||||
----
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:5 Entries:[1/7 EntryNormal "foo"]
|
||||
1->2 MsgApp Term:1 Log:1/7 Commit:5 Entries:[1/8 EntryNormal "bar"]
|
||||
1->2 MsgApp Term:1 Log:1/8 Commit:6
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/6 Commit:5 Entries:[1/7 EntryNormal "foo"]
|
||||
1->3 MsgApp Term:1 Log:1/7 Commit:5 Entries:[1/8 EntryNormal "bar"]
|
||||
1->3 MsgApp Term:1 Log:1/8 Commit:6
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:1 Commit:6
|
||||
Entries:
|
||||
1/7 EntryNormal "foo"
|
||||
1/8 EntryNormal "bar"
|
||||
CommittedEntries:
|
||||
1/6 EntryConfChangeV2 r2 r3
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/7
|
||||
2->1 MsgAppResp Term:1 Log:0/8
|
||||
2->1 MsgAppResp Term:1 Log:0/8
|
||||
INFO 2 switched to configuration voters=(1)&&(1 2 3) autoleave
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:1 Commit:6
|
||||
Entries:
|
||||
1/7 EntryNormal "foo"
|
||||
1/8 EntryNormal "bar"
|
||||
CommittedEntries:
|
||||
1/6 EntryConfChangeV2 r2 r3
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/7
|
||||
3->1 MsgAppResp Term:1 Log:0/8
|
||||
3->1 MsgAppResp Term:1 Log:0/8
|
||||
INFO 3 switched to configuration voters=(1)&&(1 2 3) autoleave
|
||||
|
||||
# n2 and n3 also leave the joint config and the dust settles. We see at the very
|
||||
# end that n1 receives some messages from them that it refuses because it does
|
||||
# not have them in its config any more.
|
||||
stabilize
|
||||
----
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/7
|
||||
2->1 MsgAppResp Term:1 Log:0/8
|
||||
2->1 MsgAppResp Term:1 Log:0/8
|
||||
3->1 MsgAppResp Term:1 Log:0/7
|
||||
3->1 MsgAppResp Term:1 Log:0/8
|
||||
3->1 MsgAppResp Term:1 Log:0/8
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:8
|
||||
CommittedEntries:
|
||||
1/7 EntryNormal "foo"
|
||||
1/8 EntryNormal "bar"
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/8 Commit:7 Entries:[1/9 EntryConfChangeV2]
|
||||
1->3 MsgApp Term:1 Log:1/8 Commit:7 Entries:[1/9 EntryConfChangeV2]
|
||||
1->2 MsgApp Term:1 Log:1/9 Commit:8
|
||||
1->3 MsgApp Term:1 Log:1/9 Commit:8
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/8 Commit:7 Entries:[1/9 EntryConfChangeV2]
|
||||
1->2 MsgApp Term:1 Log:1/9 Commit:8
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/8 Commit:7 Entries:[1/9 EntryConfChangeV2]
|
||||
1->3 MsgApp Term:1 Log:1/9 Commit:8
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:1 Commit:8
|
||||
Entries:
|
||||
1/9 EntryConfChangeV2
|
||||
CommittedEntries:
|
||||
1/7 EntryNormal "foo"
|
||||
1/8 EntryNormal "bar"
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/9
|
||||
2->1 MsgAppResp Term:1 Log:0/9
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:1 Commit:8
|
||||
Entries:
|
||||
1/9 EntryConfChangeV2
|
||||
CommittedEntries:
|
||||
1/7 EntryNormal "foo"
|
||||
1/8 EntryNormal "bar"
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/9
|
||||
3->1 MsgAppResp Term:1 Log:0/9
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/9
|
||||
2->1 MsgAppResp Term:1 Log:0/9
|
||||
3->1 MsgAppResp Term:1 Log:0/9
|
||||
3->1 MsgAppResp Term:1 Log:0/9
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:9
|
||||
CommittedEntries:
|
||||
1/9 EntryConfChangeV2
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/9 Commit:9
|
||||
1->3 MsgApp Term:1 Log:1/9 Commit:9
|
||||
INFO 1 switched to configuration voters=(1)
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/9 Commit:9
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/9 Commit:9
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:9
|
||||
CommittedEntries:
|
||||
1/9 EntryConfChangeV2
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/9
|
||||
INFO 2 switched to configuration voters=(1)
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:9
|
||||
CommittedEntries:
|
||||
1/9 EntryConfChangeV2
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/9
|
||||
INFO 3 switched to configuration voters=(1)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/9
|
||||
raft: cannot step as peer not found
|
||||
3->1 MsgAppResp Term:1 Log:0/9
|
||||
raft: cannot step as peer not found
|
||||
125
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v2_add_double_implicit.txt
generated
vendored
125
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v2_add_double_implicit.txt
generated
vendored
@ -1,125 +0,0 @@
|
||||
# Run a V2 membership change that adds a single voter but explicitly asks for the
|
||||
# use of joint consensus (with auto-leaving).
|
||||
|
||||
# TODO(tbg): also verify that if the leader changes while in the joint state, the
|
||||
# new leader will auto-transition out of the joint state just the same.
|
||||
|
||||
# Bootstrap n1.
|
||||
add-nodes 1 voters=(1) index=2
|
||||
----
|
||||
INFO 1 switched to configuration voters=(1)
|
||||
INFO 1 became follower at term 0
|
||||
INFO newRaft 1 [peers: [1], term: 0, commit: 2, applied: 2, lastindex: 2, lastterm: 1]
|
||||
|
||||
campaign 1
|
||||
----
|
||||
INFO 1 is starting a new election at term 0
|
||||
INFO 1 became candidate at term 1
|
||||
INFO 1 received MsgVoteResp from 1 at term 1
|
||||
INFO 1 became leader at term 1
|
||||
|
||||
propose-conf-change 1 transition=implicit
|
||||
v2
|
||||
----
|
||||
ok
|
||||
|
||||
# Add n2.
|
||||
add-nodes 1
|
||||
----
|
||||
INFO 2 switched to configuration voters=()
|
||||
INFO 2 became follower at term 0
|
||||
INFO newRaft 2 [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]
|
||||
|
||||
# n1 commits the conf change using itself as commit quorum, then starts catching up n2.
|
||||
# When that's done, it starts auto-transitioning out. Note that the snapshots propagating
|
||||
# the joint config have the AutoLeave flag set in their config.
|
||||
stabilize 1 2
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateLeader
|
||||
HardState Term:1 Vote:1 Commit:4
|
||||
Entries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChangeV2 v2
|
||||
CommittedEntries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChangeV2 v2
|
||||
INFO 1 switched to configuration voters=(1 2)&&(1) autoleave
|
||||
INFO initiating automatic transition out of joint configuration voters=(1 2)&&(1) autoleave
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/5 EntryConfChangeV2
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2]
|
||||
INFO 2 [term: 0] received a MsgApp message with higher term from 1 [term: 1]
|
||||
INFO 2 became follower at term 1
|
||||
DEBUG 2 [logterm: 0, index: 3] rejected MsgApp [logterm: 1, index: 3] from 1
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
HardState Term:1 Commit:0
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
DEBUG 1 received MsgAppResp(rejected, hint: (index 0, term 0)) from 2 for index 3
|
||||
DEBUG 1 decreased progress of 2 to [StateProbe match=0 next=1]
|
||||
DEBUG 1 [firstindex: 3, commit: 4] sent snapshot[index: 4, term: 1] to 2 [StateProbe match=0 next=1]
|
||||
DEBUG 1 paused sending replication messages to 2 [StateSnapshot match=0 next=1 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:true
|
||||
> 2 receiving messages
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:true
|
||||
INFO log [committed=0, applied=0, unstable.offset=1, len(unstable.Entries)=0] starts to restore snapshot [index: 4, term: 1]
|
||||
INFO 2 switched to configuration voters=(1 2)&&(1) autoleave
|
||||
INFO 2 [commit: 4, lastindex: 4, lastterm: 1] restored snapshot [index: 4, term: 1]
|
||||
INFO 2 [commit: 4] restored snapshot [index: 4, term: 1]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:4
|
||||
Snapshot Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:true
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
DEBUG 1 recovered from needing snapshot, resumed sending replication messages to 2 [StateSnapshot match=4 next=5 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4 Entries:[1/5 EntryConfChangeV2]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4 Entries:[1/5 EntryConfChangeV2]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/5 EntryConfChangeV2
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:5
|
||||
CommittedEntries:
|
||||
1/5 EntryConfChangeV2
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:5
|
||||
INFO 1 switched to configuration voters=(1 2)
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:5
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:5
|
||||
CommittedEntries:
|
||||
1/5 EntryConfChangeV2
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
INFO 2 switched to configuration voters=(1 2)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
98
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v2_add_single_auto.txt
generated
vendored
98
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v2_add_single_auto.txt
generated
vendored
@ -1,98 +0,0 @@
|
||||
# Run a V2 membership change that adds a single voter in auto mode, which means
|
||||
# that joint consensus is not used but a direct transition into the new config
|
||||
# takes place.
|
||||
|
||||
# Bootstrap n1.
|
||||
add-nodes 1 voters=(1) index=2
|
||||
----
|
||||
INFO 1 switched to configuration voters=(1)
|
||||
INFO 1 became follower at term 0
|
||||
INFO newRaft 1 [peers: [1], term: 0, commit: 2, applied: 2, lastindex: 2, lastterm: 1]
|
||||
|
||||
campaign 1
|
||||
----
|
||||
INFO 1 is starting a new election at term 0
|
||||
INFO 1 became candidate at term 1
|
||||
INFO 1 received MsgVoteResp from 1 at term 1
|
||||
INFO 1 became leader at term 1
|
||||
|
||||
# Add v2 (with an auto transition).
|
||||
propose-conf-change 1
|
||||
v2
|
||||
----
|
||||
ok
|
||||
|
||||
# Pull n2 out of thin air.
|
||||
add-nodes 1
|
||||
----
|
||||
INFO 2 switched to configuration voters=()
|
||||
INFO 2 became follower at term 0
|
||||
INFO newRaft 2 [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]
|
||||
|
||||
# n1 commits the conf change using itself as commit quorum, immediately transitions into
|
||||
# the final config, and catches up n2.
|
||||
stabilize
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateLeader
|
||||
HardState Term:1 Vote:1 Commit:4
|
||||
Entries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChangeV2 v2
|
||||
CommittedEntries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChangeV2 v2
|
||||
INFO 1 switched to configuration voters=(1 2)
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2]
|
||||
INFO 2 [term: 0] received a MsgApp message with higher term from 1 [term: 1]
|
||||
INFO 2 became follower at term 1
|
||||
DEBUG 2 [logterm: 0, index: 3] rejected MsgApp [logterm: 1, index: 3] from 1
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
HardState Term:1 Commit:0
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
DEBUG 1 received MsgAppResp(rejected, hint: (index 0, term 0)) from 2 for index 3
|
||||
DEBUG 1 decreased progress of 2 to [StateProbe match=0 next=1]
|
||||
DEBUG 1 [firstindex: 3, commit: 4] sent snapshot[index: 4, term: 1] to 2 [StateProbe match=0 next=1]
|
||||
DEBUG 1 paused sending replication messages to 2 [StateSnapshot match=0 next=1 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
> 2 receiving messages
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
INFO log [committed=0, applied=0, unstable.offset=1, len(unstable.Entries)=0] starts to restore snapshot [index: 4, term: 1]
|
||||
INFO 2 switched to configuration voters=(1 2)
|
||||
INFO 2 [commit: 4, lastindex: 4, lastterm: 1] restored snapshot [index: 4, term: 1]
|
||||
INFO 2 [commit: 4] restored snapshot [index: 4, term: 1]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:4
|
||||
Snapshot Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
DEBUG 1 recovered from needing snapshot, resumed sending replication messages to 2 [StateSnapshot match=4 next=5 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
206
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v2_add_single_explicit.txt
generated
vendored
206
vendor/go.etcd.io/etcd/raft/v3/testdata/confchange_v2_add_single_explicit.txt
generated
vendored
@ -1,206 +0,0 @@
|
||||
# Run a V2 membership change that adds a single voter but explicitly asks for the
|
||||
# use of joint consensus, including wanting to transition out of the joint config
|
||||
# manually.
|
||||
|
||||
# Bootstrap n1.
|
||||
add-nodes 1 voters=(1) index=2
|
||||
----
|
||||
INFO 1 switched to configuration voters=(1)
|
||||
INFO 1 became follower at term 0
|
||||
INFO newRaft 1 [peers: [1], term: 0, commit: 2, applied: 2, lastindex: 2, lastterm: 1]
|
||||
|
||||
campaign 1
|
||||
----
|
||||
INFO 1 is starting a new election at term 0
|
||||
INFO 1 became candidate at term 1
|
||||
INFO 1 received MsgVoteResp from 1 at term 1
|
||||
INFO 1 became leader at term 1
|
||||
|
||||
# Add v2 with an explicit transition.
|
||||
propose-conf-change 1 transition=explicit
|
||||
v2
|
||||
----
|
||||
ok
|
||||
|
||||
# Pull n2 out of thin air.
|
||||
add-nodes 1
|
||||
----
|
||||
INFO 2 switched to configuration voters=()
|
||||
INFO 2 became follower at term 0
|
||||
INFO newRaft 2 [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]
|
||||
|
||||
# n1 commits the conf change using itself as commit quorum, then starts catching up n2.
|
||||
# Everyone remains in the joint config. Note that the snapshot below has AutoLeave unset.
|
||||
stabilize 1 2
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateLeader
|
||||
HardState Term:1 Vote:1 Commit:4
|
||||
Entries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChangeV2 v2
|
||||
CommittedEntries:
|
||||
1/3 EntryNormal ""
|
||||
1/4 EntryConfChangeV2 v2
|
||||
INFO 1 switched to configuration voters=(1 2)&&(1)
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/3 Commit:4 Entries:[1/4 EntryConfChangeV2 v2]
|
||||
INFO 2 [term: 0] received a MsgApp message with higher term from 1 [term: 1]
|
||||
INFO 2 became follower at term 1
|
||||
DEBUG 2 [logterm: 0, index: 3] rejected MsgApp [logterm: 1, index: 3] from 1
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
HardState Term:1 Commit:0
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/3 Rejected (Hint: 0)
|
||||
DEBUG 1 received MsgAppResp(rejected, hint: (index 0, term 0)) from 2 for index 3
|
||||
DEBUG 1 decreased progress of 2 to [StateProbe match=0 next=1]
|
||||
DEBUG 1 [firstindex: 3, commit: 4] sent snapshot[index: 4, term: 1] to 2 [StateProbe match=0 next=1]
|
||||
DEBUG 1 paused sending replication messages to 2 [StateSnapshot match=0 next=1 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
> 2 receiving messages
|
||||
1->2 MsgSnap Term:1 Log:0/0 Snapshot: Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
INFO log [committed=0, applied=0, unstable.offset=1, len(unstable.Entries)=0] starts to restore snapshot [index: 4, term: 1]
|
||||
INFO 2 switched to configuration voters=(1 2)&&(1)
|
||||
INFO 2 [commit: 4, lastindex: 4, lastterm: 1] restored snapshot [index: 4, term: 1]
|
||||
INFO 2 [commit: 4] restored snapshot [index: 4, term: 1]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:4
|
||||
Snapshot Index:4 Term:1 ConfState:Voters:[1 2] VotersOutgoing:[1] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
DEBUG 1 recovered from needing snapshot, resumed sending replication messages to 2 [StateSnapshot match=4 next=5 paused pendingSnap=4]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/4
|
||||
|
||||
# Check that we're not allowed to change membership again while in the joint state.
|
||||
# This leads to an empty entry being proposed instead (index 5 in the stabilize block
|
||||
# below).
|
||||
propose-conf-change 1
|
||||
v3 v4 v5
|
||||
----
|
||||
INFO 1 ignoring conf change {ConfChangeTransitionAuto [{ConfChangeAddNode 3} {ConfChangeAddNode 4} {ConfChangeAddNode 5}] []} at config voters=(1 2)&&(1): must transition out of joint config first
|
||||
|
||||
# Propose a transition out of the joint config. We'll see this at index 6 below.
|
||||
propose-conf-change 1
|
||||
----
|
||||
ok
|
||||
|
||||
# The group commits the command and everyone switches to the final config.
|
||||
stabilize
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/5 EntryNormal ""
|
||||
1/6 EntryConfChangeV2
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4 Entries:[1/5 EntryNormal ""]
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:4 Entries:[1/6 EntryConfChangeV2]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/4 Commit:4 Entries:[1/5 EntryNormal ""]
|
||||
1->2 MsgApp Term:1 Log:1/5 Commit:4 Entries:[1/6 EntryConfChangeV2]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/5 EntryNormal ""
|
||||
1/6 EntryConfChangeV2
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/5
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:6
|
||||
CommittedEntries:
|
||||
1/5 EntryNormal ""
|
||||
1/6 EntryConfChangeV2
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:5
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:6
|
||||
INFO 1 switched to configuration voters=(1 2)
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:5
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:6
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:6
|
||||
CommittedEntries:
|
||||
1/5 EntryNormal ""
|
||||
1/6 EntryConfChangeV2
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
INFO 2 switched to configuration voters=(1 2)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
2->1 MsgAppResp Term:1 Log:0/6
|
||||
|
||||
# Check that trying to transition out again won't do anything.
|
||||
propose-conf-change 1
|
||||
----
|
||||
INFO 1 ignoring conf change {ConfChangeTransitionAuto [] []} at config voters=(1 2): not in joint state; refusing empty conf change
|
||||
|
||||
# Finishes work for the empty entry we just proposed.
|
||||
stabilize
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/7 EntryNormal ""
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:6 Entries:[1/7 EntryNormal ""]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/6 Commit:6 Entries:[1/7 EntryNormal ""]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
1/7 EntryNormal ""
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/7
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/7
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Vote:1 Commit:7
|
||||
CommittedEntries:
|
||||
1/7 EntryNormal ""
|
||||
Messages:
|
||||
1->2 MsgApp Term:1 Log:1/7 Commit:7
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:1 Log:1/7 Commit:7
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:7
|
||||
CommittedEntries:
|
||||
1/7 EntryNormal ""
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:1 Log:0/7
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:1 Log:0/7
|
||||
767
vendor/go.etcd.io/etcd/raft/v3/testdata/probe_and_replicate.txt
generated
vendored
767
vendor/go.etcd.io/etcd/raft/v3/testdata/probe_and_replicate.txt
generated
vendored
@ -1,767 +0,0 @@
|
||||
# This test creates a complete Raft log configuration and demonstrates how a
|
||||
# leader probes and replicates to each of its followers. The log configuration
|
||||
# constructed is almost[*] identical to the one present in Figure 7 of the raft
|
||||
# paper (https://raft.github.io/raft.pdf), which looks like:
|
||||
#
|
||||
# 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
# n1: [1][1][1][4][4][5][5][6][6][6]
|
||||
# n2: [1][1][1][4][4][5][5][6][6]
|
||||
# n3: [1][1][1][4]
|
||||
# n4: [1][1][1][4][4][5][5][6][6][6][6]
|
||||
# n5: [1][1][1][4][4][5][5][6][7][7][7][7]
|
||||
# n6: [1][1][1][4][4][4][4]
|
||||
# n7: [1][1][1][2][2][2][3][3][3][3][3]
|
||||
#
|
||||
# Once in this state, we then elect node 1 as the leader and stabilize the
|
||||
# entire raft group. This demonstrates how a newly elected leader probes for
|
||||
# matching indexes, overwrites conflicting entries, and catches up all
|
||||
# followers.
|
||||
#
|
||||
# [*] the only differences are:
|
||||
# 1. n5 is given a larger uncommitted log tail, which is used to demonstrate a
|
||||
# follower-side probing optimization.
|
||||
# 2. the log indexes are shifted by 10 in this test because add-nodes wants to
|
||||
# start with an index > 1.
|
||||
#
|
||||
|
||||
|
||||
# Set up the log configuration. This is mostly unintersting, but the order of
|
||||
# each leadership change and the nodes that are allowed to hear about them is
|
||||
# very important. Most readers of this test can skip this section.
|
||||
log-level none
|
||||
----
|
||||
ok
|
||||
|
||||
## Start with seven nodes.
|
||||
add-nodes 7 voters=(1,2,3,4,5,6,7) index=10
|
||||
----
|
||||
ok
|
||||
|
||||
## Create term 1 entries.
|
||||
campaign 1
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 1 prop_1_12
|
||||
----
|
||||
ok
|
||||
|
||||
propose 1 prop_1_13
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
## Create term 2 entries.
|
||||
campaign 2
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 2
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
stabilize 6
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
stabilize 2 5 7
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 2 prop_2_15
|
||||
----
|
||||
ok
|
||||
|
||||
propose 2 prop_2_16
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 2 7
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
deliver-msgs drop=(1,2,3,4,5,6,7)
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
## Create term 3 entries.
|
||||
campaign 7
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 7
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
stabilize 1 2 3 4 5 6
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
stabilize 7
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 7 prop_3_18
|
||||
----
|
||||
ok
|
||||
|
||||
propose 7 prop_3_19
|
||||
----
|
||||
ok
|
||||
|
||||
propose 7 prop_3_20
|
||||
----
|
||||
ok
|
||||
|
||||
propose 7 prop_3_21
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 7
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
deliver-msgs drop=(1,2,3,4,5,6,7)
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
## Create term 4 entries.
|
||||
campaign 6
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 1 2 3 4 5 6
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 6 prop_4_15
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 1 2 4 5 6
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 6 prop_4_16
|
||||
----
|
||||
ok
|
||||
|
||||
propose 6 prop_4_17
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 6
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
deliver-msgs drop=(1,2,3,4,5,6,7)
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
## Create term 5 entries.
|
||||
campaign 5
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 1 2 4 5
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 5 prop_5_17
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 1 2 4 5
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
deliver-msgs drop=(1,2,3,4,5,6,7)
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
## Create term 6 entries.
|
||||
campaign 4
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 1 2 4 5
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 4 prop_6_19
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 1 2 4
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 4 prop_6_20
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 1 4
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 4 prop_6_21
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 4
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
deliver-msgs drop=(1,2,3,4,5,6,7)
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
## Create term 7 entries.
|
||||
campaign 5
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 5
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
stabilize 1 3 6 7
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
stabilize 5
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
propose 5 prop_7_20
|
||||
----
|
||||
ok
|
||||
|
||||
propose 5 prop_7_21
|
||||
----
|
||||
ok
|
||||
|
||||
propose 5 prop_7_22
|
||||
----
|
||||
ok
|
||||
|
||||
stabilize 5
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
deliver-msgs drop=(1,2,3,4,5,6,7)
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
|
||||
# Show the Raft log from each node.
|
||||
log-level info
|
||||
----
|
||||
ok
|
||||
|
||||
raft-log 1
|
||||
----
|
||||
1/11 EntryNormal ""
|
||||
1/12 EntryNormal "prop_1_12"
|
||||
1/13 EntryNormal "prop_1_13"
|
||||
4/14 EntryNormal ""
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
|
||||
raft-log 2
|
||||
----
|
||||
1/11 EntryNormal ""
|
||||
1/12 EntryNormal "prop_1_12"
|
||||
1/13 EntryNormal "prop_1_13"
|
||||
4/14 EntryNormal ""
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
|
||||
raft-log 3
|
||||
----
|
||||
1/11 EntryNormal ""
|
||||
1/12 EntryNormal "prop_1_12"
|
||||
1/13 EntryNormal "prop_1_13"
|
||||
4/14 EntryNormal ""
|
||||
|
||||
raft-log 4
|
||||
----
|
||||
1/11 EntryNormal ""
|
||||
1/12 EntryNormal "prop_1_12"
|
||||
1/13 EntryNormal "prop_1_13"
|
||||
4/14 EntryNormal ""
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
6/21 EntryNormal "prop_6_21"
|
||||
|
||||
raft-log 5
|
||||
----
|
||||
1/11 EntryNormal ""
|
||||
1/12 EntryNormal "prop_1_12"
|
||||
1/13 EntryNormal "prop_1_13"
|
||||
4/14 EntryNormal ""
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
7/19 EntryNormal ""
|
||||
7/20 EntryNormal "prop_7_20"
|
||||
7/21 EntryNormal "prop_7_21"
|
||||
7/22 EntryNormal "prop_7_22"
|
||||
|
||||
raft-log 6
|
||||
----
|
||||
1/11 EntryNormal ""
|
||||
1/12 EntryNormal "prop_1_12"
|
||||
1/13 EntryNormal "prop_1_13"
|
||||
4/14 EntryNormal ""
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
4/16 EntryNormal "prop_4_16"
|
||||
4/17 EntryNormal "prop_4_17"
|
||||
|
||||
raft-log 7
|
||||
----
|
||||
1/11 EntryNormal ""
|
||||
1/12 EntryNormal "prop_1_12"
|
||||
1/13 EntryNormal "prop_1_13"
|
||||
2/14 EntryNormal ""
|
||||
2/15 EntryNormal "prop_2_15"
|
||||
2/16 EntryNormal "prop_2_16"
|
||||
3/17 EntryNormal ""
|
||||
3/18 EntryNormal "prop_3_18"
|
||||
3/19 EntryNormal "prop_3_19"
|
||||
3/20 EntryNormal "prop_3_20"
|
||||
3/21 EntryNormal "prop_3_21"
|
||||
|
||||
|
||||
# Elect node 1 as leader and stabilize.
|
||||
campaign 1
|
||||
----
|
||||
INFO 1 is starting a new election at term 7
|
||||
INFO 1 became candidate at term 8
|
||||
INFO 1 received MsgVoteResp from 1 at term 8
|
||||
INFO 1 [logterm: 6, index: 20] sent MsgVote request to 2 at term 8
|
||||
INFO 1 [logterm: 6, index: 20] sent MsgVote request to 3 at term 8
|
||||
INFO 1 [logterm: 6, index: 20] sent MsgVote request to 4 at term 8
|
||||
INFO 1 [logterm: 6, index: 20] sent MsgVote request to 5 at term 8
|
||||
INFO 1 [logterm: 6, index: 20] sent MsgVote request to 6 at term 8
|
||||
INFO 1 [logterm: 6, index: 20] sent MsgVote request to 7 at term 8
|
||||
|
||||
## Get elected.
|
||||
stabilize 1
|
||||
----
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:0 State:StateCandidate
|
||||
HardState Term:8 Vote:1 Commit:18
|
||||
Messages:
|
||||
1->2 MsgVote Term:8 Log:6/20
|
||||
1->3 MsgVote Term:8 Log:6/20
|
||||
1->4 MsgVote Term:8 Log:6/20
|
||||
1->5 MsgVote Term:8 Log:6/20
|
||||
1->6 MsgVote Term:8 Log:6/20
|
||||
1->7 MsgVote Term:8 Log:6/20
|
||||
|
||||
stabilize 2 3 4 5 6 7
|
||||
----
|
||||
> 2 receiving messages
|
||||
1->2 MsgVote Term:8 Log:6/20
|
||||
INFO 2 [term: 6] received a MsgVote message with higher term from 1 [term: 8]
|
||||
INFO 2 became follower at term 8
|
||||
INFO 2 [logterm: 6, index: 19, vote: 0] cast MsgVote for 1 [logterm: 6, index: 20] at term 8
|
||||
> 3 receiving messages
|
||||
1->3 MsgVote Term:8 Log:6/20
|
||||
INFO 3 [term: 7] received a MsgVote message with higher term from 1 [term: 8]
|
||||
INFO 3 became follower at term 8
|
||||
INFO 3 [logterm: 4, index: 14, vote: 0] cast MsgVote for 1 [logterm: 6, index: 20] at term 8
|
||||
> 4 receiving messages
|
||||
1->4 MsgVote Term:8 Log:6/20
|
||||
INFO 4 [term: 6] received a MsgVote message with higher term from 1 [term: 8]
|
||||
INFO 4 became follower at term 8
|
||||
INFO 4 [logterm: 6, index: 21, vote: 0] rejected MsgVote from 1 [logterm: 6, index: 20] at term 8
|
||||
> 5 receiving messages
|
||||
1->5 MsgVote Term:8 Log:6/20
|
||||
INFO 5 [term: 7] received a MsgVote message with higher term from 1 [term: 8]
|
||||
INFO 5 became follower at term 8
|
||||
INFO 5 [logterm: 7, index: 22, vote: 0] rejected MsgVote from 1 [logterm: 6, index: 20] at term 8
|
||||
> 6 receiving messages
|
||||
1->6 MsgVote Term:8 Log:6/20
|
||||
INFO 6 [term: 7] received a MsgVote message with higher term from 1 [term: 8]
|
||||
INFO 6 became follower at term 8
|
||||
INFO 6 [logterm: 4, index: 17, vote: 0] cast MsgVote for 1 [logterm: 6, index: 20] at term 8
|
||||
> 7 receiving messages
|
||||
1->7 MsgVote Term:8 Log:6/20
|
||||
INFO 7 [term: 7] received a MsgVote message with higher term from 1 [term: 8]
|
||||
INFO 7 became follower at term 8
|
||||
INFO 7 [logterm: 3, index: 21, vote: 0] cast MsgVote for 1 [logterm: 6, index: 20] at term 8
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:0 State:StateFollower
|
||||
HardState Term:8 Vote:1 Commit:18
|
||||
Messages:
|
||||
2->1 MsgVoteResp Term:8 Log:0/0
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:8 Vote:1 Commit:14
|
||||
Messages:
|
||||
3->1 MsgVoteResp Term:8 Log:0/0
|
||||
> 4 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:0 State:StateFollower
|
||||
HardState Term:8 Commit:18
|
||||
Messages:
|
||||
4->1 MsgVoteResp Term:8 Log:0/0 Rejected (Hint: 0)
|
||||
> 5 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:0 State:StateFollower
|
||||
HardState Term:8 Commit:18
|
||||
Messages:
|
||||
5->1 MsgVoteResp Term:8 Log:0/0 Rejected (Hint: 0)
|
||||
> 6 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:8 Vote:1 Commit:15
|
||||
Messages:
|
||||
6->1 MsgVoteResp Term:8 Log:0/0
|
||||
> 7 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:8 Vote:1 Commit:13
|
||||
Messages:
|
||||
7->1 MsgVoteResp Term:8 Log:0/0
|
||||
|
||||
stabilize 1
|
||||
----
|
||||
> 1 receiving messages
|
||||
2->1 MsgVoteResp Term:8 Log:0/0
|
||||
INFO 1 received MsgVoteResp from 2 at term 8
|
||||
INFO 1 has received 2 MsgVoteResp votes and 0 vote rejections
|
||||
3->1 MsgVoteResp Term:8 Log:0/0
|
||||
INFO 1 received MsgVoteResp from 3 at term 8
|
||||
INFO 1 has received 3 MsgVoteResp votes and 0 vote rejections
|
||||
4->1 MsgVoteResp Term:8 Log:0/0 Rejected (Hint: 0)
|
||||
INFO 1 received MsgVoteResp rejection from 4 at term 8
|
||||
INFO 1 has received 3 MsgVoteResp votes and 1 vote rejections
|
||||
5->1 MsgVoteResp Term:8 Log:0/0 Rejected (Hint: 0)
|
||||
INFO 1 received MsgVoteResp rejection from 5 at term 8
|
||||
INFO 1 has received 3 MsgVoteResp votes and 2 vote rejections
|
||||
6->1 MsgVoteResp Term:8 Log:0/0
|
||||
INFO 1 received MsgVoteResp from 6 at term 8
|
||||
INFO 1 has received 4 MsgVoteResp votes and 2 vote rejections
|
||||
INFO 1 became leader at term 8
|
||||
7->1 MsgVoteResp Term:8 Log:0/0
|
||||
> 1 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateLeader
|
||||
Entries:
|
||||
8/21 EntryNormal ""
|
||||
Messages:
|
||||
1->2 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
1->3 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
1->4 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
1->5 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
1->6 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
1->7 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
|
||||
## Recover each follower, one by one.
|
||||
stabilize 1 2
|
||||
----
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
Lead:1 State:StateFollower
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:8 Log:6/20 Rejected (Hint: 19)
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:8 Log:6/20 Rejected (Hint: 19)
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:8 Log:6/19 Commit:18 Entries:[6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:8 Log:6/19 Commit:18 Entries:[6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
> 2 handling Ready
|
||||
Ready MustSync=true:
|
||||
Entries:
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgApp Term:8 Log:8/21 Commit:18
|
||||
> 2 receiving messages
|
||||
1->2 MsgApp Term:8 Log:8/21 Commit:18
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
2->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
2->1 MsgAppResp Term:8 Log:0/21
|
||||
|
||||
stabilize 1 3
|
||||
----
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
Lead:1 State:StateFollower
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:8 Log:4/20 Rejected (Hint: 14)
|
||||
> 1 receiving messages
|
||||
3->1 MsgAppResp Term:8 Log:4/20 Rejected (Hint: 14)
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->3 MsgApp Term:8 Log:4/14 Commit:18 Entries:[4/15 EntryNormal "prop_4_15", 5/16 EntryNormal "", 5/17 EntryNormal "prop_5_17", 6/18 EntryNormal "", 6/19 EntryNormal "prop_6_19", 6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:8 Log:4/14 Commit:18 Entries:[4/15 EntryNormal "prop_4_15", 5/16 EntryNormal "", 5/17 EntryNormal "prop_5_17", 6/18 EntryNormal "", 6/19 EntryNormal "prop_6_19", 6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:8 Vote:1 Commit:18
|
||||
Entries:
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
CommittedEntries:
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
3->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->3 MsgApp Term:8 Log:8/21 Commit:18
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:8 Log:8/21 Commit:18
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
3->1 MsgAppResp Term:8 Log:0/21
|
||||
|
||||
stabilize 1 4
|
||||
----
|
||||
> 4 receiving messages
|
||||
1->4 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
INFO found conflict at index 21 [existing term: 6, conflicting term: 8]
|
||||
INFO replace the unstable entries from index 21
|
||||
> 4 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
Entries:
|
||||
8/21 EntryNormal ""
|
||||
Messages:
|
||||
4->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
4->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:8 Vote:1 Commit:21
|
||||
CommittedEntries:
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
Messages:
|
||||
1->2 MsgApp Term:8 Log:8/21 Commit:21
|
||||
1->3 MsgApp Term:8 Log:8/21 Commit:21
|
||||
1->4 MsgApp Term:8 Log:8/21 Commit:21
|
||||
> 4 receiving messages
|
||||
1->4 MsgApp Term:8 Log:8/21 Commit:21
|
||||
> 4 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:8 Commit:21
|
||||
CommittedEntries:
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
Messages:
|
||||
4->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
4->1 MsgAppResp Term:8 Log:0/21
|
||||
|
||||
stabilize 1 5
|
||||
----
|
||||
> 5 receiving messages
|
||||
1->5 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
> 5 handling Ready
|
||||
Ready MustSync=false:
|
||||
Lead:1 State:StateFollower
|
||||
Messages:
|
||||
5->1 MsgAppResp Term:8 Log:6/20 Rejected (Hint: 18)
|
||||
> 1 receiving messages
|
||||
5->1 MsgAppResp Term:8 Log:6/20 Rejected (Hint: 18)
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->5 MsgApp Term:8 Log:6/18 Commit:21 Entries:[6/19 EntryNormal "prop_6_19", 6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
> 5 receiving messages
|
||||
1->5 MsgApp Term:8 Log:6/18 Commit:21 Entries:[6/19 EntryNormal "prop_6_19", 6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
INFO found conflict at index 19 [existing term: 7, conflicting term: 6]
|
||||
INFO replace the unstable entries from index 19
|
||||
> 5 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:8 Commit:21
|
||||
Entries:
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
CommittedEntries:
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
Messages:
|
||||
5->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
5->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->5 MsgApp Term:8 Log:8/21 Commit:21
|
||||
> 5 receiving messages
|
||||
1->5 MsgApp Term:8 Log:8/21 Commit:21
|
||||
> 5 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
5->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
5->1 MsgAppResp Term:8 Log:0/21
|
||||
|
||||
stabilize 1 6
|
||||
----
|
||||
> 6 receiving messages
|
||||
1->6 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
> 6 handling Ready
|
||||
Ready MustSync=false:
|
||||
Lead:1 State:StateFollower
|
||||
Messages:
|
||||
6->1 MsgAppResp Term:8 Log:4/20 Rejected (Hint: 17)
|
||||
> 1 receiving messages
|
||||
6->1 MsgAppResp Term:8 Log:4/20 Rejected (Hint: 17)
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->6 MsgApp Term:8 Log:4/15 Commit:21 Entries:[5/16 EntryNormal "", 5/17 EntryNormal "prop_5_17", 6/18 EntryNormal "", 6/19 EntryNormal "prop_6_19", 6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
> 6 receiving messages
|
||||
1->6 MsgApp Term:8 Log:4/15 Commit:21 Entries:[5/16 EntryNormal "", 5/17 EntryNormal "prop_5_17", 6/18 EntryNormal "", 6/19 EntryNormal "prop_6_19", 6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
INFO found conflict at index 16 [existing term: 4, conflicting term: 5]
|
||||
INFO replace the unstable entries from index 16
|
||||
> 6 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:8 Vote:1 Commit:21
|
||||
Entries:
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
CommittedEntries:
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
Messages:
|
||||
6->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
6->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->6 MsgApp Term:8 Log:8/21 Commit:21
|
||||
> 6 receiving messages
|
||||
1->6 MsgApp Term:8 Log:8/21 Commit:21
|
||||
> 6 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
6->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
6->1 MsgAppResp Term:8 Log:0/21
|
||||
|
||||
stabilize 1 7
|
||||
----
|
||||
> 7 receiving messages
|
||||
1->7 MsgApp Term:8 Log:6/20 Commit:18 Entries:[8/21 EntryNormal ""]
|
||||
> 7 handling Ready
|
||||
Ready MustSync=false:
|
||||
Lead:1 State:StateFollower
|
||||
Messages:
|
||||
7->1 MsgAppResp Term:8 Log:3/20 Rejected (Hint: 20)
|
||||
> 1 receiving messages
|
||||
7->1 MsgAppResp Term:8 Log:3/20 Rejected (Hint: 20)
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->7 MsgApp Term:8 Log:1/13 Commit:21 Entries:[4/14 EntryNormal "", 4/15 EntryNormal "prop_4_15", 5/16 EntryNormal "", 5/17 EntryNormal "prop_5_17", 6/18 EntryNormal "", 6/19 EntryNormal "prop_6_19", 6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
> 7 receiving messages
|
||||
1->7 MsgApp Term:8 Log:1/13 Commit:21 Entries:[4/14 EntryNormal "", 4/15 EntryNormal "prop_4_15", 5/16 EntryNormal "", 5/17 EntryNormal "prop_5_17", 6/18 EntryNormal "", 6/19 EntryNormal "prop_6_19", 6/20 EntryNormal "prop_6_20", 8/21 EntryNormal ""]
|
||||
INFO found conflict at index 14 [existing term: 2, conflicting term: 4]
|
||||
INFO replace the unstable entries from index 14
|
||||
> 7 handling Ready
|
||||
Ready MustSync=true:
|
||||
HardState Term:8 Vote:1 Commit:21
|
||||
Entries:
|
||||
4/14 EntryNormal ""
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
CommittedEntries:
|
||||
4/14 EntryNormal ""
|
||||
4/15 EntryNormal "prop_4_15"
|
||||
5/16 EntryNormal ""
|
||||
5/17 EntryNormal "prop_5_17"
|
||||
6/18 EntryNormal ""
|
||||
6/19 EntryNormal "prop_6_19"
|
||||
6/20 EntryNormal "prop_6_20"
|
||||
8/21 EntryNormal ""
|
||||
Messages:
|
||||
7->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
7->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->7 MsgApp Term:8 Log:8/21 Commit:21
|
||||
> 7 receiving messages
|
||||
1->7 MsgApp Term:8 Log:8/21 Commit:21
|
||||
> 7 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
7->1 MsgAppResp Term:8 Log:0/21
|
||||
> 1 receiving messages
|
||||
7->1 MsgAppResp Term:8 Log:0/21
|
||||
156
vendor/go.etcd.io/etcd/raft/v3/testdata/snapshot_succeed_via_app_resp.txt
generated
vendored
156
vendor/go.etcd.io/etcd/raft/v3/testdata/snapshot_succeed_via_app_resp.txt
generated
vendored
@ -1,156 +0,0 @@
|
||||
# TestSnapshotSucceedViaAppResp regression tests the situation in which a snap-
|
||||
# shot is sent to a follower at the most recent index (i.e. the snapshot index
|
||||
# is the leader's last index is the committed index). In that situation, a bug
|
||||
# in the past left the follower in probing status until the next log entry was
|
||||
# committed.
|
||||
#
|
||||
# See https://github.com/etcd-io/etcd/pull/10308 for additional background.
|
||||
|
||||
# Turn off output during the setup of the test.
|
||||
log-level none
|
||||
----
|
||||
ok
|
||||
|
||||
# Start with two nodes, but the config already has a third.
|
||||
add-nodes 2 voters=(1,2,3) index=10
|
||||
----
|
||||
ok
|
||||
|
||||
campaign 1
|
||||
----
|
||||
ok
|
||||
|
||||
# Fully replicate everything, including the leader's empty index.
|
||||
stabilize
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
compact 1 11
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
# Drop inflight messages to n3.
|
||||
deliver-msgs drop=(3)
|
||||
----
|
||||
ok (quiet)
|
||||
|
||||
# Show the Raft log messages from now on.
|
||||
log-level debug
|
||||
----
|
||||
ok
|
||||
|
||||
status 1
|
||||
----
|
||||
1: StateReplicate match=11 next=12 inactive
|
||||
2: StateReplicate match=11 next=12
|
||||
3: StateProbe match=0 next=11 paused inactive
|
||||
|
||||
# Add the node that will receive a snapshot (it has no state at all, does not
|
||||
# even have a config).
|
||||
add-nodes 1
|
||||
----
|
||||
INFO 3 switched to configuration voters=()
|
||||
INFO 3 became follower at term 0
|
||||
INFO newRaft 3 [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]
|
||||
|
||||
# Time passes on the leader so that it will try the previously missing follower
|
||||
# again.
|
||||
tick-heartbeat 1
|
||||
----
|
||||
ok
|
||||
|
||||
process-ready 1
|
||||
----
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->2 MsgHeartbeat Term:1 Log:0/0 Commit:11
|
||||
1->3 MsgHeartbeat Term:1 Log:0/0
|
||||
|
||||
# Iterate until no more work is done by the new peer. It receives the heartbeat
|
||||
# and responds.
|
||||
stabilize 3
|
||||
----
|
||||
> 3 receiving messages
|
||||
1->3 MsgHeartbeat Term:1 Log:0/0
|
||||
INFO 3 [term: 0] received a MsgHeartbeat message with higher term from 1 [term: 1]
|
||||
INFO 3 became follower at term 1
|
||||
> 3 handling Ready
|
||||
Ready MustSync=true:
|
||||
Lead:1 State:StateFollower
|
||||
HardState Term:1 Commit:0
|
||||
Messages:
|
||||
3->1 MsgHeartbeatResp Term:1 Log:0/0
|
||||
|
||||
# The leader in turn will realize that n3 needs a snapshot, which it initiates.
|
||||
stabilize 1
|
||||
----
|
||||
> 1 receiving messages
|
||||
3->1 MsgHeartbeatResp Term:1 Log:0/0
|
||||
DEBUG 1 [firstindex: 12, commit: 11] sent snapshot[index: 11, term: 1] to 3 [StateProbe match=0 next=11]
|
||||
DEBUG 1 paused sending replication messages to 3 [StateSnapshot match=0 next=11 paused pendingSnap=11]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->3 MsgSnap Term:1 Log:0/0 Snapshot: Index:11 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
|
||||
status 1
|
||||
----
|
||||
1: StateReplicate match=11 next=12 inactive
|
||||
2: StateReplicate match=11 next=12
|
||||
3: StateSnapshot match=0 next=11 paused pendingSnap=11
|
||||
|
||||
# Follower applies the snapshot. Note how it reacts with a MsgAppResp upon completion.
|
||||
# The snapshot fully catches the follower up (i.e. there are no more log entries it
|
||||
# needs to apply after). The bug was that the leader failed to realize that the follower
|
||||
# was now fully caught up.
|
||||
stabilize 3
|
||||
----
|
||||
> 3 receiving messages
|
||||
1->3 MsgSnap Term:1 Log:0/0 Snapshot: Index:11 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
INFO log [committed=0, applied=0, unstable.offset=1, len(unstable.Entries)=0] starts to restore snapshot [index: 11, term: 1]
|
||||
INFO 3 switched to configuration voters=(1 2 3)
|
||||
INFO 3 [commit: 11, lastindex: 11, lastterm: 1] restored snapshot [index: 11, term: 1]
|
||||
INFO 3 [commit: 11] restored snapshot [index: 11, term: 1]
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
HardState Term:1 Commit:11
|
||||
Snapshot Index:11 Term:1 ConfState:Voters:[1 2 3] VotersOutgoing:[] Learners:[] LearnersNext:[] AutoLeave:false
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/11
|
||||
|
||||
# The MsgAppResp lets the leader move the follower back to replicating state.
|
||||
# Leader sends another MsgAppResp, to communicate the updated commit index.
|
||||
stabilize 1
|
||||
----
|
||||
> 1 receiving messages
|
||||
3->1 MsgAppResp Term:1 Log:0/11
|
||||
DEBUG 1 recovered from needing snapshot, resumed sending replication messages to 3 [StateSnapshot match=11 next=12 paused pendingSnap=11]
|
||||
> 1 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
1->3 MsgApp Term:1 Log:1/11 Commit:11
|
||||
|
||||
status 1
|
||||
----
|
||||
1: StateReplicate match=11 next=12 inactive
|
||||
2: StateReplicate match=11 next=12
|
||||
3: StateReplicate match=11 next=12
|
||||
|
||||
# Let things settle.
|
||||
stabilize
|
||||
----
|
||||
> 2 receiving messages
|
||||
1->2 MsgHeartbeat Term:1 Log:0/0 Commit:11
|
||||
> 3 receiving messages
|
||||
1->3 MsgApp Term:1 Log:1/11 Commit:11
|
||||
> 2 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
2->1 MsgHeartbeatResp Term:1 Log:0/0
|
||||
> 3 handling Ready
|
||||
Ready MustSync=false:
|
||||
Messages:
|
||||
3->1 MsgAppResp Term:1 Log:0/11
|
||||
> 1 receiving messages
|
||||
2->1 MsgHeartbeatResp Term:1 Log:0/0
|
||||
3->1 MsgAppResp Term:1 Log:0/11
|
||||
189
vendor/go.etcd.io/etcd/raft/v3/tracker/inflights_test.go
generated
vendored
189
vendor/go.etcd.io/etcd/raft/v3/tracker/inflights_test.go
generated
vendored
@ -1,189 +0,0 @@
|
||||
// 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 tracker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInflightsAdd(t *testing.T) {
|
||||
// no rotating case
|
||||
in := &Inflights{
|
||||
size: 10,
|
||||
buffer: make([]uint64, 10),
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
wantIn := &Inflights{
|
||||
start: 0,
|
||||
count: 5,
|
||||
size: 10,
|
||||
// ↓------------
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 0, 0, 0, 0, 0},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn)
|
||||
}
|
||||
|
||||
for i := 5; i < 10; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
wantIn2 := &Inflights{
|
||||
start: 0,
|
||||
count: 10,
|
||||
size: 10,
|
||||
// ↓---------------------------
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn2) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn2)
|
||||
}
|
||||
|
||||
// rotating case
|
||||
in2 := &Inflights{
|
||||
start: 5,
|
||||
size: 10,
|
||||
buffer: make([]uint64, 10),
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
in2.Add(uint64(i))
|
||||
}
|
||||
|
||||
wantIn21 := &Inflights{
|
||||
start: 5,
|
||||
count: 5,
|
||||
size: 10,
|
||||
// ↓------------
|
||||
buffer: []uint64{0, 0, 0, 0, 0, 0, 1, 2, 3, 4},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in2, wantIn21) {
|
||||
t.Fatalf("in = %+v, want %+v", in2, wantIn21)
|
||||
}
|
||||
|
||||
for i := 5; i < 10; i++ {
|
||||
in2.Add(uint64(i))
|
||||
}
|
||||
|
||||
wantIn22 := &Inflights{
|
||||
start: 5,
|
||||
count: 10,
|
||||
size: 10,
|
||||
// -------------- ↓------------
|
||||
buffer: []uint64{5, 6, 7, 8, 9, 0, 1, 2, 3, 4},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in2, wantIn22) {
|
||||
t.Fatalf("in = %+v, want %+v", in2, wantIn22)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInflightFreeTo(t *testing.T) {
|
||||
// no rotating case
|
||||
in := NewInflights(10)
|
||||
for i := 0; i < 10; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
in.FreeLE(4)
|
||||
|
||||
wantIn := &Inflights{
|
||||
start: 5,
|
||||
count: 5,
|
||||
size: 10,
|
||||
// ↓------------
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn)
|
||||
}
|
||||
|
||||
in.FreeLE(8)
|
||||
|
||||
wantIn2 := &Inflights{
|
||||
start: 9,
|
||||
count: 1,
|
||||
size: 10,
|
||||
// ↓
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn2) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn2)
|
||||
}
|
||||
|
||||
// rotating case
|
||||
for i := 10; i < 15; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
in.FreeLE(12)
|
||||
|
||||
wantIn3 := &Inflights{
|
||||
start: 3,
|
||||
count: 2,
|
||||
size: 10,
|
||||
// ↓-----
|
||||
buffer: []uint64{10, 11, 12, 13, 14, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn3) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn3)
|
||||
}
|
||||
|
||||
in.FreeLE(14)
|
||||
|
||||
wantIn4 := &Inflights{
|
||||
start: 0,
|
||||
count: 0,
|
||||
size: 10,
|
||||
// ↓
|
||||
buffer: []uint64{10, 11, 12, 13, 14, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn4) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInflightFreeFirstOne(t *testing.T) {
|
||||
in := NewInflights(10)
|
||||
for i := 0; i < 10; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
in.FreeFirstOne()
|
||||
|
||||
wantIn := &Inflights{
|
||||
start: 1,
|
||||
count: 9,
|
||||
size: 10,
|
||||
// ↓------------------------
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn)
|
||||
}
|
||||
}
|
||||
189
vendor/go.etcd.io/etcd/raft/v3/tracker/progress_test.go
generated
vendored
189
vendor/go.etcd.io/etcd/raft/v3/tracker/progress_test.go
generated
vendored
@ -1,189 +0,0 @@
|
||||
// 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 tracker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInflightsAdd(t *testing.T) {
|
||||
// no rotating case
|
||||
in := &Inflights{
|
||||
size: 10,
|
||||
buffer: make([]uint64, 10),
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
wantIn := &Inflights{
|
||||
start: 0,
|
||||
count: 5,
|
||||
size: 10,
|
||||
// ↓------------
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 0, 0, 0, 0, 0},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn)
|
||||
}
|
||||
|
||||
for i := 5; i < 10; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
wantIn2 := &Inflights{
|
||||
start: 0,
|
||||
count: 10,
|
||||
size: 10,
|
||||
// ↓---------------------------
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn2) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn2)
|
||||
}
|
||||
|
||||
// rotating case
|
||||
in2 := &Inflights{
|
||||
start: 5,
|
||||
size: 10,
|
||||
buffer: make([]uint64, 10),
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
in2.Add(uint64(i))
|
||||
}
|
||||
|
||||
wantIn21 := &Inflights{
|
||||
start: 5,
|
||||
count: 5,
|
||||
size: 10,
|
||||
// ↓------------
|
||||
buffer: []uint64{0, 0, 0, 0, 0, 0, 1, 2, 3, 4},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in2, wantIn21) {
|
||||
t.Fatalf("in = %+v, want %+v", in2, wantIn21)
|
||||
}
|
||||
|
||||
for i := 5; i < 10; i++ {
|
||||
in2.Add(uint64(i))
|
||||
}
|
||||
|
||||
wantIn22 := &Inflights{
|
||||
start: 5,
|
||||
count: 10,
|
||||
size: 10,
|
||||
// -------------- ↓------------
|
||||
buffer: []uint64{5, 6, 7, 8, 9, 0, 1, 2, 3, 4},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in2, wantIn22) {
|
||||
t.Fatalf("in = %+v, want %+v", in2, wantIn22)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInflightFreeTo(t *testing.T) {
|
||||
// no rotating case
|
||||
in := NewInflights(10)
|
||||
for i := 0; i < 10; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
in.FreeLE(4)
|
||||
|
||||
wantIn := &Inflights{
|
||||
start: 5,
|
||||
count: 5,
|
||||
size: 10,
|
||||
// ↓------------
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn)
|
||||
}
|
||||
|
||||
in.FreeLE(8)
|
||||
|
||||
wantIn2 := &Inflights{
|
||||
start: 9,
|
||||
count: 1,
|
||||
size: 10,
|
||||
// ↓
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn2) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn2)
|
||||
}
|
||||
|
||||
// rotating case
|
||||
for i := 10; i < 15; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
in.FreeLE(12)
|
||||
|
||||
wantIn3 := &Inflights{
|
||||
start: 3,
|
||||
count: 2,
|
||||
size: 10,
|
||||
// ↓-----
|
||||
buffer: []uint64{10, 11, 12, 13, 14, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn3) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn3)
|
||||
}
|
||||
|
||||
in.FreeLE(14)
|
||||
|
||||
wantIn4 := &Inflights{
|
||||
start: 0,
|
||||
count: 0,
|
||||
size: 10,
|
||||
// ↓
|
||||
buffer: []uint64{10, 11, 12, 13, 14, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn4) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInflightFreeFirstOne(t *testing.T) {
|
||||
in := NewInflights(10)
|
||||
for i := 0; i < 10; i++ {
|
||||
in.Add(uint64(i))
|
||||
}
|
||||
|
||||
in.FreeFirstOne()
|
||||
|
||||
wantIn := &Inflights{
|
||||
start: 1,
|
||||
count: 9,
|
||||
size: 10,
|
||||
// ↓------------------------
|
||||
buffer: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, wantIn) {
|
||||
t.Fatalf("in = %+v, want %+v", in, wantIn)
|
||||
}
|
||||
}
|
||||
106
vendor/go.etcd.io/etcd/raft/v3/util_test.go
generated
vendored
106
vendor/go.etcd.io/etcd/raft/v3/util_test.go
generated
vendored
@ -1,106 +0,0 @@
|
||||
// Copyright 2015 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 raft
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
pb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
)
|
||||
|
||||
var testFormatter EntryFormatter = func(data []byte) string {
|
||||
return strings.ToUpper(string(data))
|
||||
}
|
||||
|
||||
func TestDescribeEntry(t *testing.T) {
|
||||
entry := pb.Entry{
|
||||
Term: 1,
|
||||
Index: 2,
|
||||
Type: pb.EntryNormal,
|
||||
Data: []byte("hello\x00world"),
|
||||
}
|
||||
|
||||
defaultFormatted := DescribeEntry(entry, nil)
|
||||
if defaultFormatted != "1/2 EntryNormal \"hello\\x00world\"" {
|
||||
t.Errorf("unexpected default output: %s", defaultFormatted)
|
||||
}
|
||||
|
||||
customFormatted := DescribeEntry(entry, testFormatter)
|
||||
if customFormatted != "1/2 EntryNormal HELLO\x00WORLD" {
|
||||
t.Errorf("unexpected custom output: %s", customFormatted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitSize(t *testing.T) {
|
||||
ents := []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 6}}
|
||||
tests := []struct {
|
||||
maxsize uint64
|
||||
wentries []pb.Entry
|
||||
}{
|
||||
{math.MaxUint64, []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 6}}},
|
||||
// even if maxsize is zero, the first entry should be returned
|
||||
{0, []pb.Entry{{Index: 4, Term: 4}}},
|
||||
// limit to 2
|
||||
{uint64(ents[0].Size() + ents[1].Size()), []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}}},
|
||||
// limit to 2
|
||||
{uint64(ents[0].Size() + ents[1].Size() + ents[2].Size()/2), []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}}},
|
||||
{uint64(ents[0].Size() + ents[1].Size() + ents[2].Size() - 1), []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}}},
|
||||
// all
|
||||
{uint64(ents[0].Size() + ents[1].Size() + ents[2].Size()), []pb.Entry{{Index: 4, Term: 4}, {Index: 5, Term: 5}, {Index: 6, Term: 6}}},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
if !reflect.DeepEqual(limitSize(ents, tt.maxsize), tt.wentries) {
|
||||
t.Errorf("#%d: entries = %v, want %v", i, limitSize(ents, tt.maxsize), tt.wentries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLocalMsg(t *testing.T) {
|
||||
tests := []struct {
|
||||
msgt pb.MessageType
|
||||
isLocal bool
|
||||
}{
|
||||
{pb.MsgHup, true},
|
||||
{pb.MsgBeat, true},
|
||||
{pb.MsgUnreachable, true},
|
||||
{pb.MsgSnapStatus, true},
|
||||
{pb.MsgCheckQuorum, true},
|
||||
{pb.MsgTransferLeader, false},
|
||||
{pb.MsgProp, false},
|
||||
{pb.MsgApp, false},
|
||||
{pb.MsgAppResp, false},
|
||||
{pb.MsgVote, false},
|
||||
{pb.MsgVoteResp, false},
|
||||
{pb.MsgSnap, false},
|
||||
{pb.MsgHeartbeat, false},
|
||||
{pb.MsgHeartbeatResp, false},
|
||||
{pb.MsgTimeoutNow, false},
|
||||
{pb.MsgReadIndex, false},
|
||||
{pb.MsgReadIndexResp, false},
|
||||
{pb.MsgPreVote, false},
|
||||
{pb.MsgPreVoteResp, false},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
got := IsLocalMsg(tt.msgt)
|
||||
if got != tt.isLocal {
|
||||
t.Errorf("#%d: got %v, want %v", i, got, tt.isLocal)
|
||||
}
|
||||
}
|
||||
}
|
||||
2
vendor/golang.org/x/net/http2/Dockerfile
generated
vendored
2
vendor/golang.org/x/net/http2/Dockerfile
generated
vendored
@ -6,7 +6,7 @@
|
||||
# Go tests use this curl binary for integration tests.
|
||||
#
|
||||
|
||||
FROM ubuntu:trusty@sha256:881afbae521c910f764f7187dbfbca3cc10c26f8bafa458c76dda009a901c29d
|
||||
FROM ubuntu:trusty
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
|
||||
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@ -3,6 +3,7 @@
|
||||
# github.com/Shopify/sarama v1.33.0
|
||||
## explicit; go 1.16
|
||||
github.com/Shopify/sarama
|
||||
github.com/Shopify/sarama/mocks
|
||||
# github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
|
||||
## explicit
|
||||
github.com/afex/hystrix-go/hystrix
|
||||
@ -364,7 +365,7 @@ github.com/spf13/cobra
|
||||
## explicit; go 1.13
|
||||
github.com/stretchr/testify/assert
|
||||
github.com/stretchr/testify/require
|
||||
# github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c
|
||||
# github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c => github.com/Cloudstriff/gorocksdb v1.0.1
|
||||
## explicit
|
||||
github.com/tecbot/gorocksdb
|
||||
# github.com/tklauser/go-sysconf v0.3.11
|
||||
@ -414,9 +415,6 @@ golang.org/x/net/trace
|
||||
## explicit
|
||||
golang.org/x/sync/errgroup
|
||||
golang.org/x/sync/singleflight
|
||||
## explicit
|
||||
golang.org/x/sync/errgroup
|
||||
golang.org/x/sync/singleflight
|
||||
# golang.org/x/sys v0.7.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/sys/internal/unsafeheader
|
||||
|
||||
Loading…
Reference in New Issue
Block a user