vault/physical/raft/snapshot_test.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

961 lines
21 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package raft
import (
"bytes"
"context"
"fmt"
"hash/crc64"
"io"
"io/ioutil"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"runtime"
"testing"
"time"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/raft"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/physical"
"github.com/hashicorp/vault/sdk/plugin/pb"
)
type idAddr struct {
id string
}
func (a *idAddr) Network() string { return "inmem" }
func (a *idAddr) String() string { return a.id }
func addPeer(t *testing.T, leader, follower *RaftBackend) {
t.Helper()
if err := leader.AddPeer(context.Background(), follower.NodeID(), follower.NodeID()); err != nil {
t.Fatal(err)
}
peers, err := leader.Peers(context.Background())
if err != nil {
t.Fatal(err)
}
err = follower.Bootstrap(peers)
if err != nil {
t.Fatal(err)
}
err = follower.SetupCluster(context.Background(), SetupOpts{})
if err != nil {
t.Fatal(err)
}
leader.raftTransport.(*raft.InmemTransport).Connect(raft.ServerAddress(follower.NodeID()), follower.raftTransport)
follower.raftTransport.(*raft.InmemTransport).Connect(raft.ServerAddress(leader.NodeID()), leader.raftTransport)
}
func TestRaft_Snapshot_Loading(t *testing.T) {
raft, dir := GetRaft(t, true, false)
defer os.RemoveAll(dir)
// Write some data
for i := 0; i < 1000; i++ {
err := raft.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
readCloser, writeCloser := io.Pipe()
metaReadCloser, metaWriteCloser := io.Pipe()
go func() {
raft.fsm.writeTo(context.Background(), metaWriteCloser, writeCloser)
}()
// Create a CRC64 hash
stateHash := crc64.New(crc64.MakeTable(crc64.ECMA))
// Compute the hash
size1, err := io.Copy(stateHash, metaReadCloser)
if err != nil {
t.Fatal(err)
}
computed1 := stateHash.Sum(nil)
// Create a CRC64 hash
stateHash = crc64.New(crc64.MakeTable(crc64.ECMA))
// Compute the hash
size2, err := io.Copy(stateHash, readCloser)
if err != nil {
t.Fatal(err)
}
computed2 := stateHash.Sum(nil)
if size1 != size2 {
t.Fatal("sizes did not match")
}
if !bytes.Equal(computed1, computed2) {
t.Fatal("hashes did not match")
}
snapFuture := raft.raft.Snapshot()
if err := snapFuture.Error(); err != nil {
t.Fatal(err)
}
meta, reader, err := snapFuture.Open()
if err != nil {
t.Fatal(err)
}
if meta.Size != size1 {
t.Fatal("meta size did not match expected")
}
// Create a CRC64 hash
stateHash = crc64.New(crc64.MakeTable(crc64.ECMA))
// Compute the hash
size3, err := io.Copy(stateHash, reader)
if err != nil {
t.Fatal(err)
}
computed3 := stateHash.Sum(nil)
if size1 != size3 {
t.Fatal("sizes did not match")
}
if !bytes.Equal(computed1, computed3) {
t.Fatal("hashes did not match")
}
}
func TestRaft_Snapshot_Index(t *testing.T) {
raft, dir := GetRaft(t, true, false)
defer os.RemoveAll(dir)
err := raft.Put(context.Background(), &physical.Entry{
Key: "key",
Value: []byte("value"),
})
if err != nil {
t.Fatal(err)
}
// Get index
index, _ := raft.fsm.LatestState()
if index.Term != 2 {
t.Fatalf("unexpected term, got %d expected 2", index.Term)
}
if index.Index != 3 {
t.Fatalf("unexpected index, got %d expected 3", index.Term)
}
// Write some data
for i := 0; i < 100; i++ {
err := raft.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
// Get index
index, _ = raft.fsm.LatestState()
if index.Term != 2 {
t.Fatalf("unexpected term, got %d expected 2", index.Term)
}
if index.Index != 103 {
t.Fatalf("unexpected index, got %d expected 103", index.Term)
}
// Take a snapshot
snapFuture := raft.raft.Snapshot()
if err := snapFuture.Error(); err != nil {
t.Fatal(err)
}
meta, reader, err := snapFuture.Open()
if err != nil {
t.Fatal(err)
}
io.Copy(ioutil.Discard, reader)
if meta.Index != index.Index {
t.Fatalf("indexes did not match, got %d expected %d", meta.Index, index.Index)
}
if meta.Term != index.Term {
t.Fatalf("term did not match, got %d expected %d", meta.Term, index.Term)
}
// Write some more data
for i := 0; i < 100; i++ {
err := raft.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
// Open the same snapshot again
meta, reader, err = raft.snapStore.Open(meta.ID)
if err != nil {
t.Fatal(err)
}
io.Copy(ioutil.Discard, reader)
// Make sure the meta data has updated to the new values
if meta.Index != 203 {
t.Fatalf("unexpected snapshot index %d", meta.Index)
}
if meta.Term != 2 {
t.Fatalf("unexpected snapshot term %d", meta.Term)
}
}
func TestRaft_Snapshot_Peers(t *testing.T) {
raft1, dir := GetRaft(t, true, false)
raft2, dir2 := GetRaft(t, false, false)
raft3, dir3 := GetRaft(t, false, false)
defer os.RemoveAll(dir)
defer os.RemoveAll(dir2)
defer os.RemoveAll(dir3)
// Write some data
for i := 0; i < 1000; i++ {
err := raft1.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
// Force a snapshot
snapFuture := raft1.raft.Snapshot()
if err := snapFuture.Error(); err != nil {
t.Fatal(err)
}
commitIdx := raft1.CommittedIndex()
// Add raft2 to the cluster
addPeer(t, raft1, raft2)
ensureCommitApplied(t, commitIdx, raft2)
// Make sure the snapshot was applied correctly on the follower
if err := compareDBs(t, raft1.fsm.getDB(), raft2.fsm.getDB(), false); err != nil {
t.Fatal(err)
}
// Write some more data
for i := 1000; i < 2000; i++ {
err := raft1.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
snapFuture = raft1.raft.Snapshot()
if err := snapFuture.Error(); err != nil {
t.Fatal(err)
}
commitIdx = raft1.CommittedIndex()
// Add raft3 to the cluster
addPeer(t, raft1, raft3)
ensureCommitApplied(t, commitIdx, raft2)
ensureCommitApplied(t, commitIdx, raft3)
// Make sure all stores are the same
compareFSMs(t, raft1.fsm, raft2.fsm)
compareFSMs(t, raft1.fsm, raft3.fsm)
}
func ensureCommitApplied(t *testing.T, leaderCommitIdx uint64, backend *RaftBackend) {
t.Helper()
timeout := time.Now().Add(10 * time.Second)
for {
if time.Now().After(timeout) {
t.Fatal("timeout reached while verifying applied index on raft backend")
}
if backend.AppliedIndex() >= leaderCommitIdx {
break
}
time.Sleep(1 * time.Second)
}
}
func TestRaft_Snapshot_Restart(t *testing.T) {
raft1, dir := GetRaft(t, true, false)
defer os.RemoveAll(dir)
raft2, dir2 := GetRaft(t, false, false)
defer os.RemoveAll(dir2)
// Write some data
for i := 0; i < 100; i++ {
err := raft1.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
// Take a snapshot
snapFuture := raft1.raft.Snapshot()
if err := snapFuture.Error(); err != nil {
t.Fatal(err)
}
// Advance FSM's index past configuration change
raft1.Put(context.Background(), &physical.Entry{
Key: "key",
Value: []byte("value"),
})
// Add raft2 to the cluster
addPeer(t, raft1, raft2)
time.Sleep(2 * time.Second)
peers, err := raft2.Peers(context.Background())
if err != nil {
t.Fatal(err)
}
if len(peers) != 2 {
t.Fatal(peers)
}
// Finalize raft1
if err := raft1.TeardownCluster(nil); err != nil {
t.Fatal(err)
}
// Start Raft
err = raft1.SetupCluster(context.Background(), SetupOpts{})
if err != nil {
t.Fatal(err)
}
peers, err = raft1.Peers(context.Background())
if err != nil {
t.Fatal(err)
}
if len(peers) != 2 {
t.Fatal(peers)
}
compareFSMs(t, raft1.fsm, raft2.fsm)
}
/*
func TestRaft_Snapshot_ErrorRecovery(t *testing.T) {
raft1, dir := GetRaft(t, true, false)
raft2, dir2 := GetRaft(t, false, false)
raft3, dir3 := GetRaft(t, false, false)
defer os.RemoveAll(dir)
defer os.RemoveAll(dir2)
defer os.RemoveAll(dir3)
// Add raft2 to the cluster
addPeer(t, raft1, raft2)
// Write some data
for i := 0; i < 100; i++ {
err := raft1.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
// Take a snapshot on each node to ensure we no longer have older logs
snapFuture := raft1.raft.Snapshot()
if err := snapFuture.Error(); err != nil {
t.Fatal(err)
}
stepDownLeader(t, raft1)
leader := waitForLeader(t, raft1, raft2)
snapFuture = leader.raft.Snapshot()
if err := snapFuture.Error(); err != nil {
t.Fatal(err)
}
// Advance FSM's index past snapshot index
leader.Put(context.Background(), &physical.Entry{
Key: "key",
Value: []byte("value"),
})
// Error on snapshot restore
raft3.fsm.testSnapshotRestoreError = true
// Add raft3 to the cluster
addPeer(t, leader, raft3)
time.Sleep(2 * time.Second)
// Restart the failing node to make sure fresh state does not have invalid
// values.
if err := raft3.TeardownCluster(nil); err != nil {
t.Fatal(err)
}
// Ensure the databases are not equal
if err := compareFSMsWithErr(t, leader.fsm, raft3.fsm); err == nil {
t.Fatal("nil error")
}
// Remove error and make sure we can reconcile state
raft3.fsm.testSnapshotRestoreError = false
// Step down leader node
stepDownLeader(t, leader)
leader = waitForLeader(t, raft1, raft2)
// Start Raft3
if err := raft3.SetupCluster(context.Background(), SetupOpts{}); err != nil {
t.Fatal(err)
}
connectPeers(raft1, raft2, raft3)
waitForLeader(t, raft1, raft2)
time.Sleep(5 * time.Second)
// Make sure state gets re-replicated.
compareFSMs(t, raft1.fsm, raft3.fsm)
}*/
func TestRaft_Snapshot_Take_Restore(t *testing.T) {
raft1, dir := GetRaft(t, true, false)
defer os.RemoveAll(dir)
raft2, dir2 := GetRaft(t, false, false)
defer os.RemoveAll(dir2)
addPeer(t, raft1, raft2)
// Write some data
for i := 0; i < 100; i++ {
err := raft1.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
recorder := httptest.NewRecorder()
snap := logical.NewHTTPResponseWriter(recorder)
err := raft1.Snapshot(snap, nil)
if err != nil {
t.Fatal(err)
}
// Write some more data
for i := 100; i < 200; i++ {
err := raft1.Put(context.Background(), &physical.Entry{
Key: fmt.Sprintf("key-%d", i),
Value: []byte(fmt.Sprintf("value-%d", i)),
})
if err != nil {
t.Fatal(err)
}
}
snapFile, cleanup, metadata, err := raft1.WriteSnapshotToTemp(ioutil.NopCloser(recorder.Body), nil)
if err != nil {
t.Fatal(err)
}
defer cleanup()
err = raft1.RestoreSnapshot(context.Background(), metadata, snapFile)
if err != nil {
t.Fatal(err)
}
// make sure we don't have the second batch of writes
for i := 100; i < 200; i++ {
{
value, err := raft1.Get(context.Background(), fmt.Sprintf("key-%d", i))
if err != nil {
t.Fatal(err)
}
if value != nil {
t.Fatal("didn't remove data")
}
}
{
value, err := raft2.Get(context.Background(), fmt.Sprintf("key-%d", i))
if err != nil {
t.Fatal(err)
}
if value != nil {
t.Fatal("didn't remove data")
}
}
}
time.Sleep(10 * time.Second)
compareFSMs(t, raft1.fsm, raft2.fsm)
}
func TestBoltSnapshotStore_CreateSnapshotMissingParentDir(t *testing.T) {
parent, err := ioutil.TempDir("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer os.RemoveAll(parent)
dir, err := ioutil.TempDir(parent, "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
logger := hclog.New(&hclog.LoggerOptions{
Name: "raft",
Level: hclog.Trace,
})
snap, err := NewBoltSnapshotStore(dir, logger, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
os.RemoveAll(parent)
_, trans := raft.NewInmemTransport(raft.NewInmemAddr())
sink, err := snap.Create(raft.SnapshotVersionMax, 10, 3, raft.Configuration{}, 0, trans)
if err != nil {
t.Fatal(err)
}
defer sink.Cancel()
_, err = sink.Write([]byte("test"))
if err != nil {
t.Fatalf("should not fail when using non existing parent: %s", err)
}
// Ensure the snapshot file exists
_, err = os.Stat(filepath.Join(snap.path, sink.ID()+tmpSuffix, databaseFilename))
if err != nil {
t.Fatal(err)
}
}
func TestBoltSnapshotStore_Listing(t *testing.T) {
// Create a test dir
parent, err := ioutil.TempDir("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer os.RemoveAll(parent)
dir, err := ioutil.TempDir(parent, "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
logger := hclog.New(&hclog.LoggerOptions{
Name: "raft",
Level: hclog.Trace,
})
fsm, err := NewFSM(parent, "", logger)
if err != nil {
t.Fatal(err)
}
snap, err := NewBoltSnapshotStore(dir, logger, fsm)
if err != nil {
t.Fatalf("err: %v", err)
}
// FSM has no data, should have empty snapshot list
snaps, err := snap.List()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(snaps) != 0 {
t.Fatalf("expect 0 snapshots: %v", snaps)
}
// Move the fsm forward
err = fsm.witnessSnapshot(&raft.SnapshotMeta{
Index: 100,
Term: 20,
Configuration: raft.Configuration{},
ConfigurationIndex: 0,
})
if err != nil {
t.Fatal(err)
}
snaps, err = snap.List()
if err != nil {
t.Fatal(err)
}
if len(snaps) != 1 {
t.Fatalf("expect 1 snapshots: %v", snaps)
}
if snaps[0].Index != 100 || snaps[0].Term != 20 {
t.Fatalf("bad snapshot: %+v", snaps[0])
}
if snaps[0].ID != boltSnapshotID {
t.Fatalf("bad snapshot: %+v", snaps[0])
}
}
func TestBoltSnapshotStore_CreateInstallSnapshot(t *testing.T) {
// Create a test dir
parent, err := ioutil.TempDir("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer os.RemoveAll(parent)
dir, err := ioutil.TempDir(parent, "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
logger := hclog.New(&hclog.LoggerOptions{
Name: "raft",
Level: hclog.Trace,
})
fsm, err := NewFSM(parent, "", logger)
if err != nil {
t.Fatal(err)
}
defer fsm.Close()
snap, err := NewBoltSnapshotStore(dir, logger, fsm)
if err != nil {
t.Fatalf("err: %v", err)
}
// Check no snapshots
snaps, err := snap.List()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(snaps) != 0 {
t.Fatalf("did not expect any snapshots: %v", snaps)
}
// Create a new sink
var configuration raft.Configuration
configuration.Servers = append(configuration.Servers, raft.Server{
Suffrage: raft.Voter,
ID: raft.ServerID("my id"),
Address: raft.ServerAddress("over here"),
})
_, trans := raft.NewInmemTransport(raft.NewInmemAddr())
sink, err := snap.Create(raft.SnapshotVersionMax, 10, 3, configuration, 2, trans)
if err != nil {
t.Fatalf("err: %v", err)
}
protoWriter := NewDelimitedWriter(sink)
err = fsm.Put(context.Background(), &physical.Entry{
Key: "test-key",
Value: []byte("test-value"),
})
if err != nil {
t.Fatal(err)
}
err = fsm.Put(context.Background(), &physical.Entry{
Key: "test-key1",
Value: []byte("test-value1"),
})
if err != nil {
t.Fatal(err)
}
// Write to the sink
err = protoWriter.WriteMsg(&pb.StorageEntry{
Key: "test-key",
Value: []byte("test-value"),
})
if err != nil {
t.Fatalf("err: %v", err)
}
err = protoWriter.WriteMsg(&pb.StorageEntry{
Key: "test-key1",
Value: []byte("test-value1"),
})
if err != nil {
t.Fatalf("err: %v", err)
}
// Done!
err = sink.Close()
if err != nil {
t.Fatalf("err: %v", err)
}
// Read the snapshot
meta, r, err := snap.Open(sink.ID())
if err != nil {
t.Fatalf("err: %v", err)
}
// Check the latest
if meta.Index != 10 {
t.Fatalf("bad snapshot: %+v", meta)
}
if meta.Term != 3 {
t.Fatalf("bad snapshot: %+v", meta)
}
if !reflect.DeepEqual(meta.Configuration, configuration) {
t.Fatalf("bad snapshot: %+v", meta)
}
if meta.ConfigurationIndex != 2 {
t.Fatalf("bad snapshot: %+v", meta)
}
installer, ok := r.(*boltSnapshotInstaller)
if !ok {
t.Fatal("expected snapshot installer object")
}
newFSM, err := NewFSM(filepath.Dir(installer.Filename()), "", logger)
if err != nil {
t.Fatal(err)
}
err = compareDBs(t, fsm.getDB(), newFSM.getDB(), true)
if err != nil {
t.Fatal(err)
}
// Make sure config data is different
err = compareDBs(t, fsm.getDB(), newFSM.getDB(), false)
if err == nil {
t.Fatal("expected error")
}
if err := newFSM.Close(); err != nil {
t.Fatal(err)
}
err = fsm.Restore(installer)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 2; i++ {
latestIndex, latestConfigRaw := fsm.LatestState()
latestConfigIndex, latestConfig := protoConfigurationToRaftConfiguration(latestConfigRaw)
if latestIndex.Index != 10 {
t.Fatalf("bad install: %+v", latestIndex)
}
if latestIndex.Term != 3 {
t.Fatalf("bad install: %+v", latestIndex)
}
if !reflect.DeepEqual(latestConfig, configuration) {
t.Fatalf("bad install: %+v", latestConfig)
}
if latestConfigIndex != 2 {
t.Fatalf("bad install: %+v", latestConfigIndex)
}
v, err := fsm.Get(context.Background(), "test-key")
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(v.Value, []byte("test-value")) {
t.Fatalf("bad: %+v", v)
}
v, err = fsm.Get(context.Background(), "test-key1")
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(v.Value, []byte("test-value1")) {
t.Fatalf("bad: %+v", v)
}
// Close/Reopen the db and make sure we still match
fsm.Close()
fsm, err = NewFSM(parent, "", logger)
if err != nil {
t.Fatal(err)
}
}
}
func TestBoltSnapshotStore_CancelSnapshot(t *testing.T) {
// Create a test dir
dir, err := ioutil.TempDir("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer os.RemoveAll(dir)
logger := hclog.New(&hclog.LoggerOptions{
Name: "raft",
Level: hclog.Trace,
})
snap, err := NewBoltSnapshotStore(dir, logger, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
_, trans := raft.NewInmemTransport(raft.NewInmemAddr())
sink, err := snap.Create(raft.SnapshotVersionMax, 10, 3, raft.Configuration{}, 0, trans)
if err != nil {
t.Fatal(err)
}
_, err = sink.Write([]byte("test"))
if err != nil {
t.Fatalf("should not fail when using non existing parent: %s", err)
}
// Ensure the snapshot file exists
_, err = os.Stat(filepath.Join(snap.path, sink.ID()+tmpSuffix, databaseFilename))
if err != nil {
t.Fatal(err)
}
// Cancel the snapshot! Should delete
err = sink.Cancel()
if err != nil {
t.Fatalf("err: %v", err)
}
// Ensure the snapshot file does not exist
_, err = os.Stat(filepath.Join(snap.path, sink.ID()+tmpSuffix, databaseFilename))
if !os.IsNotExist(err) {
t.Fatal(err)
}
// Make sure future writes fail
_, err = sink.Write([]byte("test"))
if err == nil {
t.Fatal("expected write to fail")
}
}
func TestBoltSnapshotStore_BadPerm(t *testing.T) {
var err error
if runtime.GOOS == "windows" {
t.Skip("skipping file permission test on windows")
}
// Create a temp dir
var dir1 string
dir1, err = ioutil.TempDir("", "raft")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir1)
// Create a sub dir and remove all permissions
var dir2 string
dir2, err = ioutil.TempDir(dir1, "badperm")
if err != nil {
t.Fatalf("err: %s", err)
}
if err = os.Chmod(dir2, 0o00); err != nil {
t.Fatalf("err: %s", err)
}
defer os.Chmod(dir2, 777) // Set perms back for delete
logger := hclog.New(&hclog.LoggerOptions{
Name: "raft",
Level: hclog.Trace,
})
_, err = NewBoltSnapshotStore(dir2, logger, nil)
if err == nil {
t.Fatalf("should fail to use dir with bad perms")
}
}
func TestBoltSnapshotStore_CloseFailure(t *testing.T) {
// Create a test dir
dir, err := ioutil.TempDir("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer os.RemoveAll(dir)
logger := hclog.New(&hclog.LoggerOptions{
Name: "raft",
Level: hclog.Trace,
})
snap, err := NewBoltSnapshotStore(dir, logger, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
_, trans := raft.NewInmemTransport(raft.NewInmemAddr())
sink, err := snap.Create(raft.SnapshotVersionMax, 10, 3, raft.Configuration{}, 0, trans)
if err != nil {
t.Fatal(err)
}
// This should stash an error value
_, err = sink.Write([]byte("test"))
if err != nil {
t.Fatalf("should not fail when using non existing parent: %s", err)
}
// Cancel the snapshot! Should delete
err = sink.Close()
if err == nil {
t.Fatalf("expected error")
}
// Ensure the snapshot file does not exist
_, err = os.Stat(filepath.Join(snap.path, sink.ID()+tmpSuffix, databaseFilename))
if !os.IsNotExist(err) {
t.Fatal(err)
}
// Make sure future writes fail
_, err = sink.Write([]byte("test"))
if err == nil {
t.Fatal("expected write to fail")
}
}