open-vault/physical/raft/chunking_test.go

179 lines
4.1 KiB
Go

package raft
import (
"os"
"testing"
proto "github.com/golang/protobuf/proto"
"github.com/hashicorp/go-raftchunking"
raftchunkingtypes "github.com/hashicorp/go-raftchunking/types"
"github.com/hashicorp/raft"
raftboltdb "github.com/hashicorp/vault/physical/raft/logstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// This chunks encoded data and then performing out-of-order applies of half
// the logs. It then snapshots, restores to a new FSM, and applies the rest.
// The goal is to verify that chunking snapshotting works as expected.
func TestRaft_Chunking_Lifecycle(t *testing.T) {
t.Parallel()
require := require.New(t)
assert := assert.New(t)
b, dir := getRaft(t, true, false)
defer os.RemoveAll(dir)
t.Log("applying configuration")
b.applyConfigSettings(raft.DefaultConfig())
t.Log("chunking")
buf := []byte("let's see how this goes, shall we?")
logData := &LogData{
Operations: []*LogOperation{
{
OpType: putOp,
Key: "foobar",
Value: buf,
},
},
}
cmdBytes, err := proto.Marshal(logData)
require.NoError(err)
var logs []*raft.Log
for i, b := range cmdBytes {
// Stage multiple operations so we can test restoring across multiple opnums
for j := 0; j < 10; j++ {
chunkInfo := &raftchunkingtypes.ChunkInfo{
OpNum: uint64(32 + j),
SequenceNum: uint32(i),
NumChunks: uint32(len(cmdBytes)),
}
chunkBytes, err := proto.Marshal(chunkInfo)
require.NoError(err)
logs = append(logs, &raft.Log{
Data: []byte{b},
Extensions: chunkBytes,
})
}
}
t.Log("applying half of the logs")
// The reason for the skipping is to test out-of-order applies which are
// theoretically possible. Some of these will actually finish though!
for i := 0; i < len(logs); i += 2 {
resp := b.fsm.chunker.Apply(logs[i])
if resp != nil {
_, ok := resp.(raftchunking.ChunkingSuccess)
assert.True(ok)
}
}
t.Log("tearing down cluster")
require.NoError(b.TeardownCluster(nil))
require.NoError(b.fsm.db.Close())
require.NoError(b.stableStore.(*raftboltdb.BoltStore).Close())
t.Log("starting new backend")
backendRaw, err := NewRaftBackend(b.conf, b.logger)
require.NoError(err)
b = backendRaw.(*RaftBackend)
t.Log("applying rest of the logs")
// Apply the rest of the logs
var resp interface{}
for i := 1; i < len(logs); i += 2 {
resp = b.fsm.chunker.Apply(logs[i])
if resp != nil {
_, ok := resp.(raftchunking.ChunkingSuccess)
assert.True(ok)
}
}
assert.NotNil(resp)
_, ok := resp.(raftchunking.ChunkingSuccess)
assert.True(ok)
}
func TestFSM_Chunking_TermChange(t *testing.T) {
t.Parallel()
require := require.New(t)
assert := assert.New(t)
b, dir := getRaft(t, true, false)
defer os.RemoveAll(dir)
t.Log("applying configuration")
b.applyConfigSettings(raft.DefaultConfig())
t.Log("chunking")
buf := []byte("let's see how this goes, shall we?")
logData := &LogData{
Operations: []*LogOperation{
{
OpType: putOp,
Key: "foobar",
Value: buf,
},
},
}
cmdBytes, err := proto.Marshal(logData)
require.NoError(err)
// Only need two chunks to test this
chunks := [][]byte{
cmdBytes[0:2],
cmdBytes[2:],
}
var logs []*raft.Log
for i, b := range chunks {
chunkInfo := &raftchunkingtypes.ChunkInfo{
OpNum: uint64(32),
SequenceNum: uint32(i),
NumChunks: uint32(len(chunks)),
}
chunkBytes, err := proto.Marshal(chunkInfo)
if err != nil {
t.Fatal(err)
}
logs = append(logs, &raft.Log{
Term: uint64(i),
Data: b,
Extensions: chunkBytes,
})
}
// We should see nil for both
for _, log := range logs {
resp := b.fsm.chunker.Apply(log)
assert.Nil(resp)
}
// Now verify the other baseline, that when the term doesn't change we see
// non-nil. First make the chunker have a clean state, then set the terms
// to be the same.
b.fsm.chunker.RestoreState(nil)
logs[1].Term = uint64(0)
// We should see nil only for the first one
for i, log := range logs {
resp := b.fsm.chunker.Apply(log)
if i == 0 {
assert.Nil(resp)
}
if i == 1 {
assert.NotNil(resp)
_, ok := resp.(raftchunking.ChunkingSuccess)
assert.True(ok)
}
}
}