rafttest

package module
v0.0.0-...-ecdf110 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 20, 2019 License: Apache-2.0 Imports: 17 Imported by: 3

README

raft-test Build Status Coverage Status Go Report Card GoDoc

This repository provides the rafttest package, which contains helpers to test code based on the raft Go package from Hashicorp.

Documentation

The documentation for this package can be found on Godoc.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Duration

func Duration(duration time.Duration) time.Duration

Duration is a convenience to scale the given duration according to the GO_RAFT_TEST_LATENCY environment variable.

func FSM

func FSM() raft.FSM

FSM create a dummy FSMs.

func FSMs

func FSMs(n int) []raft.FSM

FSMs creates the given number of dummy FSMs.

func Server

func Server(t *testing.T, fsm raft.FSM, options ...Option) (*raft.Raft, func())

Server is a convenience for creating a cluster with a single raft.Raft server that immediately be elected as leader.

The default network address of a test node is "0".

Dependencies can be replaced or mutated using the various options.

func WaitLeader

func WaitLeader(t testing.TB, raft *raft.Raft, timeout time.Duration)

WaitLeader blocks until the given raft instance sets a leader (which could possibly be the instance itself).

It fails the test if this doesn't happen within the specified timeout.

Types

type Action

type Action struct {
	// contains filtered or unexported fields
}

Action defines what should happen when the event defined in the term occurs.

func (*Action) Depose

func (a *Action) Depose()

Depose makes the action depose the current leader.

func (*Action) Snapshot

func (a *Action) Snapshot()

Snapshot makes the action trigger a snapshot on the leader.

The typical use is to take the snapshot after a certain command log gets committed (see Dispatch.Committed()).

type Control

type Control struct {
	// contains filtered or unexported fields
}

Control the events happening in a cluster of raft servers, such has leadership changes, failures and shutdowns.

func Cluster

func Cluster(t testing.TB, fsms []raft.FSM, options ...Option) (map[raft.ServerID]*raft.Raft, *Control)

Cluster creates n raft servers, one for each of the given FSMs, and returns a Control object that can be used to create deterministic test scenarios, deciding which server is elected as leader and if and when a failure should happen during its term.

Each raft.Raft instance is created with sane test-oriented default dependencies, which include:

- very low configuration timeouts - in-memory transports - in-memory log and stable stores - in-memory snapshot stores

You can tweak the default dependencies using the Config, Transport and LogStore options.

All created raft servers will be part of the cluster and act as voting servers, unless the Servers option is used.

If a GO_RAFT_TEST_LATENCY environment is found, the default configuration timeouts will be scaled up accordingly (useful when running tests on slow hardware). A latency of 1.0 is a no-op, since it just keeps the default values unchanged. A value greater than 1.0 increases the default timeouts by that factor. See also the Duration helper.

func (*Control) Barrier

func (c *Control) Barrier()

Barrier is used to wait for the cluster to settle to a stable state, where all in progress Apply() commands are committed across all FSM associated with servers that are not disconnected and all in progress snapshots and restores have been performed.

Usually you don't wan't to concurrently keep invoking Apply() on the cluster raft instances while Barrier() is running.

func (*Control) Close

func (c *Control) Close()

Close the control for this raft cluster, shutting down all servers and stopping all monitoring goroutines.

It must be called by every test creating a test cluster with Cluster().

func (*Control) Commands

func (c *Control) Commands(id raft.ServerID) uint64

Commands returns the total number of command logs applied by the FSM of the server with the given ID.

Example

A three-server raft cluster is created, the first server gets elected as leader, but when it tries to append the second FSM command log the two followers disconnect just before acknowledging the leader that they have appended the command.

t := &testing.T{}

// Create 3 dummy raft FSMs. This are just for the example, you should
// use you own FSMs implementation.
fsms := rafttest.FSMs(3)

// Create a cluster of 3 raft servers, using the dummy FSMs.
rafts, control := rafttest.Cluster(t, fsms)
defer control.Close()

// Elect the first server as leader, and set it up to lose leadership
// when the second FSM command log is appended to the two follower
// servers. Both servers will append the log, but they will disconnect
// from the leader before they can report the successful append.
control.Elect("0").When().Command(2).Appended().Depose()

// The raft server with server ID "0" is now the leader.
r := rafts["0"]

// Apply the first command log, which succeeds.
if err := r.Apply([]byte{}, time.Second).Error(); err != nil {
	log.Fatal("failed to apply first FSM command log", err)
}

// Apply the second command log, which fails.
err := r.Apply([]byte{}, time.Second).Error()
if err == nil {
	log.Fatal("applyig the second FSM command log did not fail")
}
if err != raft.ErrLeadershipLost {
	log.Fatal("wrong error when applying the second FSM command log", err)
}

// Elect the second server as leader and let it catch up with
// logs. Since the second FSM command log has reached a quorum it will
// be committed everywhere.
control.Elect("1")
control.Barrier()
Output:

number of commands applied by the first FSM: 2
number of commands applied by the second FSM: 2
number of commands applied by the third FSM: 2

func (*Control) Depose

func (c *Control) Depose()

Depose the current leader.

When calling this method a leader must have been previously elected with Elect().

It must not be called if the current term has scheduled a depose action with Action.Depose().

func (*Control) Elect

func (c *Control) Elect(id raft.ServerID) *Term

Elect a server as leader.

When calling this method there must be no leader in the cluster and server transports must all be disconnected from eacher.

func (*Control) Restores

func (c *Control) Restores(id raft.ServerID) uint64

Restores returns the total number of restores performed by the FSM of the server with the given ID.

func (*Control) Snapshots

func (c *Control) Snapshots(id raft.ServerID) uint64

Snapshots returns the total number of snapshots performed by the FSM of the server with the given ID.

Example

A three-server raft cluster is created, the first server gets elected as leader, after it commits the first FSM log command, one of the followers disconnects. The leader applies another four FSM log commands, taking a snapshot after the third is committed, and reconnecting the follower after the fourth is committed. After reconnection the follower will restore its FSM state from the leader's snapshot, since the TrailingLogs config is set to 1 by default.

t := &testing.T{}

// Create 3 dummy raft FSMs. This are just for the example, you should
// use you own FSMs implementation.
fsms := rafttest.FSMs(3)

// Create a cluster of 3 raft servers, using the dummy FSMs.
rafts, control := rafttest.Cluster(t, fsms)
defer control.Close()

// Elect the first server as leader
term := control.Elect("0")

// Set up the leader to take a snapshot after committing the fifth FSM
// command log.
term.When().Command(4).Committed().Snapshot()

// The raft server with server ID "0" is now the leader.
r := rafts["0"]

// Apply four command logs, which all succeed. The fourth log is what
// triggers the snapshot.
for i := 0; i < 4; i++ {
	if err := r.Apply([]byte{}, time.Second).Error(); err != nil {
		log.Fatal("failed to apply first FSM command log", err)
	}
	if i == 0 {
		term.Disconnect("1")
	}
}

// Wait for the cluster to settle, in particular for the snapshot to
// complete.
control.Barrier()

// Apply the fifth log, which will reconnect the disconnected follower.
if err := r.Apply([]byte{}, time.Second).Error(); err != nil {
	log.Fatal("failed to apply first FSM command log", err)
}
term.Reconnect("1")

// Wait for the cluster to settle, in particular for all FSMs to catch
// up (the disconnected follower will restore from the snapshot).
control.Barrier()
Output:

number of snapshots performed by the first server: 1
number of restores performed by the second server: 1
number of commands applied by the first FSM: 5
number of commands applied by the second FSM: 5
number of commands applied by the third FSM: 5

type Dispatch

type Dispatch struct {
	// contains filtered or unexported fields
}

Dispatch defines at which phase of the dispatch process a command log event should fire.

func (*Dispatch) Appended

func (d *Dispatch) Appended() *Action

Appended configures the command log event to occurr when the command log is appended by all followers, but not yet committed by the leader.

func (*Dispatch) Committed

func (d *Dispatch) Committed() *Action

Committed configures the command log event to occurr when the command log is committed.

func (*Dispatch) Enqueued

func (d *Dispatch) Enqueued() *Action

Enqueued configures the command log event to occurr when the command log is enqueued, but not yet appended by the followers.

type Event

type Event struct {
	// contains filtered or unexported fields
}

Event that is expected to happen during a Term.

func (*Event) Command

func (e *Event) Command(n uint64) *Dispatch

Command schedules the event to occur when the Raft.Apply() method is called on the leader raft instance in order to apply the n'th command log during the current term.

type Option

type Option func([]*dependencies)

Option can be used to tweak the dependencies of test Raft servers created with Cluster() or Server().

func Config

func Config(f func(int, *raft.Config)) Option

Config sets a hook for tweaking the raft configuration of individual nodes.

func DiscardLogger

func DiscardLogger() Option

DiscardLogger is a convenience around Config that sets the output stream of raft's logger to ioutil.Discard.

func Latency

func Latency(factor float64) Option

Latency is a convenience around Config that scales the values of the various raft timeouts that would be set by default by Cluster.

This option is orthogonal to the GO_RAFT_TEST_LATENCY environment variable. If this option is used and GO_RAFT_TEST_LATENCY is set, they will compound. E.g. passing a factor of 2.0 to this option and setting GO_RAFT_TEST_LATENCY to 3.0 will have the net effect that default timeouts are scaled by a factor of 6.0.

func LogStore

func LogStore(factory func(int) raft.LogStore) Option

LogStore can be used to create custom log stores.

The given function takes a node index as argument and returns the LogStore that the node should use.

func Servers

func Servers(indexes ...int) Option

Servers can be used to indicate which nodes should be initially part of the created cluster.

If this option is not used, the default is to have all nodes be part of the cluster.

func Transport

func Transport(factory func(int) raft.Transport) Option

Transport can be used to create custom transports.

The given function takes a node index as argument and returns the Transport that the node should use.

If the transports returned by the factory do not implement LoopbackTransport, the Disconnect API won't work.

type Term

type Term struct {
	// contains filtered or unexported fields
}

A Term holds information about an event that should happen while a certain node is the leader.

func (*Term) Disconnect

func (t *Term) Disconnect(id raft.ServerID)

Disconnect a follower, which will stop receiving RPCs.

func (*Term) Reconnect

func (t *Term) Reconnect(id raft.ServerID)

Reconnect a previously disconnected follower.

func (*Term) Snapshot

func (t *Term) Snapshot(id raft.ServerID)

Snapshot performs a snapshot on the given server.

func (*Term) When

func (t *Term) When() *Event

When can be used to schedule a certain action when a certain expected event occurs in the cluster during this Term.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL