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) } }