initial commit

This commit is contained in:
ston1th 2020-11-14 17:44:52 +01:00
commit 9210ab3133
8 changed files with 791 additions and 0 deletions

416
store_test.go Normal file
View file

@ -0,0 +1,416 @@
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)
}
}