416 lines
8.7 KiB
Go
416 lines
8.7 KiB
Go
package raftbbolt
|
|
|
|
import (
|
|
"bytes"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/raft"
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
func testStore(t testing.TB) *Store {
|
|
fh, err := ioutil.TempFile("", "bolt")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
os.Remove(fh.Name())
|
|
|
|
// Successfully creates and returns a store
|
|
store, err := NewStore(fh.Name())
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return store
|
|
}
|
|
|
|
func testRaftLog(idx uint64, data string) *raft.Log {
|
|
return &raft.Log{
|
|
Data: []byte(data),
|
|
Index: idx,
|
|
}
|
|
}
|
|
|
|
func TestStore_Implements(t *testing.T) {
|
|
var store interface{} = &Store{}
|
|
if _, ok := store.(raft.StableStore); !ok {
|
|
t.Fatalf("Store does not implement raft.StableStore")
|
|
}
|
|
if _, ok := store.(raft.LogStore); !ok {
|
|
t.Fatalf("Store does not implement raft.LogStore")
|
|
}
|
|
}
|
|
|
|
func TestOptionsTimeout(t *testing.T) {
|
|
fh, err := ioutil.TempFile("", "bolt")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
os.Remove(fh.Name())
|
|
defer os.Remove(fh.Name())
|
|
options := Options{
|
|
Path: fh.Name(),
|
|
BoltOptions: &bbolt.Options{
|
|
Timeout: time.Second / 10,
|
|
},
|
|
}
|
|
store, err := New(options)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer store.Close()
|
|
// trying to open it again should timeout
|
|
doneCh := make(chan error, 1)
|
|
go func() {
|
|
_, err := New(options)
|
|
doneCh <- err
|
|
}()
|
|
select {
|
|
case err := <-doneCh:
|
|
if err == nil || err.Error() != "timeout" {
|
|
t.Errorf("Expected timeout error but got %v", err)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Errorf("Gave up waiting for timeout response")
|
|
}
|
|
}
|
|
|
|
func TestOptionsReadOnly(t *testing.T) {
|
|
fh, err := ioutil.TempFile("", "bolt")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer os.Remove(fh.Name())
|
|
store, err := NewStore(fh.Name())
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
// Create the log
|
|
log := &raft.Log{
|
|
Data: []byte("log1"),
|
|
Index: 1,
|
|
}
|
|
// Attempt to store the log
|
|
if err := store.StoreLog(log); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
store.Close()
|
|
options := Options{
|
|
Path: fh.Name(),
|
|
BoltOptions: &bbolt.Options{
|
|
Timeout: time.Second / 10,
|
|
ReadOnly: true,
|
|
},
|
|
}
|
|
roStore, err := New(options)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer roStore.Close()
|
|
result := new(raft.Log)
|
|
if err := roStore.GetLog(1, result); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Ensure the log comes back the same
|
|
if !reflect.DeepEqual(log, result) {
|
|
t.Errorf("bad: %v", result)
|
|
}
|
|
// Attempt to store the log, should fail on a read-only store
|
|
err = roStore.StoreLog(log)
|
|
if err != bbolt.ErrDatabaseReadOnly {
|
|
t.Errorf("expecting error %v, but got %v", bbolt.ErrDatabaseReadOnly, err)
|
|
}
|
|
}
|
|
|
|
func TestNewStore(t *testing.T) {
|
|
fh, err := ioutil.TempFile("", "bolt")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
os.Remove(fh.Name())
|
|
defer os.Remove(fh.Name())
|
|
|
|
// Successfully creates and returns a store
|
|
store, err := NewStore(fh.Name())
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Ensure the file was created
|
|
if store.path != fh.Name() {
|
|
t.Fatalf("unexpected file path %q", store.path)
|
|
}
|
|
if _, err := os.Stat(fh.Name()); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Close the store so we can open again
|
|
if err := store.Close(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Ensure our tables were created
|
|
db, err := bbolt.Open(fh.Name(), dbFileMode, nil)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
tx, err := db.Begin(true)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if _, err := tx.CreateBucket([]byte(dbLogs)); err != bbolt.ErrBucketExists {
|
|
t.Fatalf("bad: %v", err)
|
|
}
|
|
if _, err := tx.CreateBucket([]byte(dbConf)); err != bbolt.ErrBucketExists {
|
|
t.Fatalf("bad: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestStore_FirstIndex(t *testing.T) {
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
defer os.Remove(store.path)
|
|
|
|
// Should get 0 index on empty log
|
|
idx, err := store.FirstIndex()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if idx != 0 {
|
|
t.Fatalf("bad: %v", idx)
|
|
}
|
|
|
|
// Set a mock raft log
|
|
logs := []*raft.Log{
|
|
testRaftLog(1, "log1"),
|
|
testRaftLog(2, "log2"),
|
|
testRaftLog(3, "log3"),
|
|
}
|
|
if err := store.StoreLogs(logs); err != nil {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
|
|
// Fetch the first Raft index
|
|
idx, err = store.FirstIndex()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if idx != 1 {
|
|
t.Fatalf("bad: %d", idx)
|
|
}
|
|
}
|
|
|
|
func TestStore_LastIndex(t *testing.T) {
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
defer os.Remove(store.path)
|
|
|
|
// Should get 0 index on empty log
|
|
idx, err := store.LastIndex()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if idx != 0 {
|
|
t.Fatalf("bad: %v", idx)
|
|
}
|
|
|
|
// Set a mock raft log
|
|
logs := []*raft.Log{
|
|
testRaftLog(1, "log1"),
|
|
testRaftLog(2, "log2"),
|
|
testRaftLog(3, "log3"),
|
|
}
|
|
if err := store.StoreLogs(logs); err != nil {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
|
|
// Fetch the last Raft index
|
|
idx, err = store.LastIndex()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if idx != 3 {
|
|
t.Fatalf("bad: %d", idx)
|
|
}
|
|
}
|
|
|
|
func TestStore_GetLog(t *testing.T) {
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
defer os.Remove(store.path)
|
|
|
|
log := new(raft.Log)
|
|
|
|
// Should return an error on non-existent log
|
|
if err := store.GetLog(1, log); err != raft.ErrLogNotFound {
|
|
t.Fatalf("expected raft log not found error, got: %v", err)
|
|
}
|
|
|
|
// Set a mock raft log
|
|
logs := []*raft.Log{
|
|
testRaftLog(1, "log1"),
|
|
testRaftLog(2, "log2"),
|
|
testRaftLog(3, "log3"),
|
|
}
|
|
if err := store.StoreLogs(logs); err != nil {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
|
|
// Should return the proper log
|
|
if err := store.GetLog(2, log); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if !reflect.DeepEqual(log, logs[1]) {
|
|
t.Fatalf("bad: %#v", log)
|
|
}
|
|
}
|
|
|
|
func TestStore_SetLog(t *testing.T) {
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
defer os.Remove(store.path)
|
|
|
|
// Create the log
|
|
log := &raft.Log{
|
|
Data: []byte("log1"),
|
|
Index: 1,
|
|
}
|
|
|
|
// Attempt to store the log
|
|
if err := store.StoreLog(log); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Retrieve the log again
|
|
result := new(raft.Log)
|
|
if err := store.GetLog(1, result); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Ensure the log comes back the same
|
|
if !reflect.DeepEqual(log, result) {
|
|
t.Fatalf("bad: %v", result)
|
|
}
|
|
}
|
|
|
|
func TestStore_SetLogs(t *testing.T) {
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
defer os.Remove(store.path)
|
|
|
|
// Create a set of logs
|
|
logs := []*raft.Log{
|
|
testRaftLog(1, "log1"),
|
|
testRaftLog(2, "log2"),
|
|
}
|
|
|
|
// Attempt to store the logs
|
|
if err := store.StoreLogs(logs); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Ensure we stored them all
|
|
result1, result2 := new(raft.Log), new(raft.Log)
|
|
if err := store.GetLog(1, result1); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if !reflect.DeepEqual(logs[0], result1) {
|
|
t.Fatalf("bad: %#v", result1)
|
|
}
|
|
if err := store.GetLog(2, result2); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if !reflect.DeepEqual(logs[1], result2) {
|
|
t.Fatalf("bad: %#v", result2)
|
|
}
|
|
}
|
|
|
|
func TestStore_DeleteRange(t *testing.T) {
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
defer os.Remove(store.path)
|
|
|
|
// Create a set of logs
|
|
log1 := testRaftLog(1, "log1")
|
|
log2 := testRaftLog(2, "log2")
|
|
log3 := testRaftLog(3, "log3")
|
|
logs := []*raft.Log{log1, log2, log3}
|
|
|
|
// Attempt to store the logs
|
|
if err := store.StoreLogs(logs); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Attempt to delete a range of logs
|
|
if err := store.DeleteRange(1, 2); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Ensure the logs were deleted
|
|
if err := store.GetLog(1, new(raft.Log)); err != raft.ErrLogNotFound {
|
|
t.Fatalf("should have deleted log1")
|
|
}
|
|
if err := store.GetLog(2, new(raft.Log)); err != raft.ErrLogNotFound {
|
|
t.Fatalf("should have deleted log2")
|
|
}
|
|
}
|
|
|
|
func TestStore_Set_Get(t *testing.T) {
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
defer os.Remove(store.path)
|
|
|
|
// Returns error on non-existent key
|
|
if _, err := store.Get([]byte("bad")); err != ErrKeyNotFound {
|
|
t.Fatalf("expected not found error, got: %q", err)
|
|
}
|
|
|
|
k, v := []byte("hello"), []byte("world")
|
|
|
|
// Try to set a k/v pair
|
|
if err := store.Set(k, v); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Try to read it back
|
|
val, err := store.Get(k)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if !bytes.Equal(val, v) {
|
|
t.Fatalf("bad: %v", val)
|
|
}
|
|
}
|
|
|
|
func TestStore_SetUint64_GetUint64(t *testing.T) {
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
defer os.Remove(store.path)
|
|
|
|
// Returns error on non-existent key
|
|
if _, err := store.GetUint64([]byte("bad")); err != ErrKeyNotFound {
|
|
t.Fatalf("expected not found error, got: %q", err)
|
|
}
|
|
|
|
k, v := []byte("abc"), uint64(123)
|
|
|
|
// Attempt to set the k/v pair
|
|
if err := store.SetUint64(k, v); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Read back the value
|
|
val, err := store.GetUint64(k)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if val != v {
|
|
t.Fatalf("bad: %v", val)
|
|
}
|
|
}
|