blob: ecd6aa8234e7e65d28891e60dc808b3e5571e9bd [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build foundationdb
package foundationdb
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"testing"
"time"
log "github.com/hashicorp/go-hclog"
uuid "github.com/hashicorp/go-uuid"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/physical"
dockertest "gopkg.in/ory-am/dockertest.v3"
)
func connectToFoundationDB(clusterFile string) (*fdb.Database, error) {
if err := fdb.APIVersion(520); err != nil {
return nil, fmt.Errorf("failed to set FDB API version: %w", err)
}
db, err := fdb.Open(clusterFile, []byte("DB"))
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
return &db, nil
}
func cleanupTopDir(clusterFile, topDir string) error {
db, err := connectToFoundationDB(clusterFile)
if err != nil {
return fmt.Errorf("could not connect to FDB for cleanup: %w", err)
}
if _, err := directory.Root().Remove(db, []string{topDir}); err != nil {
return fmt.Errorf("could not remove directory: %w", err)
}
return nil
}
func TestFoundationDBPathDecoration(t *testing.T) {
cases := map[string][]byte{
"foo": []byte("/\x01foo"),
"foo/": []byte("/\x01foo/"),
"foo/bar": []byte("/\x02foo/\x01bar"),
"foo/bar/": []byte("/\x02foo/\x01bar/"),
"foo/bar/baz": []byte("/\x02foo/\x02bar/\x01baz"),
"foo/bar/baz/": []byte("/\x02foo/\x02bar/\x01baz/"),
"foo/bar/baz/quux": []byte("/\x02foo/\x02bar/\x02baz/\x01quux"),
}
for path, expected := range cases {
decorated, err := decoratePath(path)
if err != nil {
t.Fatalf("path %s error: %s", path, err)
}
if !bytes.Equal(expected, decorated) {
t.Fatalf("path %s expected %v got %v", path, expected, decorated)
}
undecorated := undecoratePath(decorated)
if undecorated != path {
t.Fatalf("expected %s got %s", path, undecorated)
}
}
}
func TestFoundationDBBackend(t *testing.T) {
if testing.Short() {
t.Skipf("skipping in short mode")
}
testUUID, err := uuid.GenerateUUID()
if err != nil {
t.Fatalf("foundationdb: could not generate UUID to top-level directory: %s", err)
}
topDir := fmt.Sprintf("vault-test-%s", testUUID)
var clusterFile string
clusterFile = os.Getenv("FOUNDATIONDB_CLUSTER_FILE")
if clusterFile == "" {
var cleanup func()
cleanup, clusterFile = prepareFoundationDBTestDirectory(t, topDir)
defer cleanup()
}
// Remove the test data once done
defer func() {
if err := cleanupTopDir(clusterFile, topDir); err != nil {
t.Fatalf("foundationdb: could not cleanup test data at end of test: %s", err)
}
}()
// Remove any leftover test data before starting
if err := cleanupTopDir(clusterFile, topDir); err != nil {
t.Fatalf("foundationdb: could not cleanup test data before starting test: %s", err)
}
// Run vault tests
logger := logging.NewVaultLogger(log.Debug)
config := map[string]string{
"path": topDir,
"api_version": "520",
"cluster_file": clusterFile,
}
b, err := NewFDBBackend(config, logger)
if err != nil {
t.Fatalf("foundationdb: failed to create new backend: %s", err)
}
b2, err := NewFDBBackend(config, logger)
if err != nil {
t.Fatalf("foundationdb: failed to create new backend: %s", err)
}
physical.ExerciseBackend(t, b)
physical.ExerciseBackend_ListPrefix(t, b)
physical.ExerciseTransactionalBackend(t, b)
physical.ExerciseHABackend(t, b.(physical.HABackend), b2.(physical.HABackend))
}
func prepareFoundationDBTestDirectory(t *testing.T, topDir string) (func(), string) {
pool, err := dockertest.NewPool("")
if err != nil {
t.Fatalf("foundationdb: failed to connect to docker: %s", err)
}
resource, err := pool.Run("foundationdb", "5.1.7", nil)
if err != nil {
t.Fatalf("foundationdb: could not start container: %s", err)
}
tmpFile, err := ioutil.TempFile("", topDir)
if err != nil {
t.Fatalf("foundationdb: could not create temporary file for cluster file: %s", err)
}
clusterFile := tmpFile.Name()
cleanup := func() {
var err error
for i := 0; i < 10; i++ {
err = pool.Purge(resource)
if err == nil {
break
}
time.Sleep(1 * time.Second)
}
os.Remove(clusterFile)
if err != nil {
t.Fatalf("Failed to cleanup local container: %s", err)
}
}
setup := func() error {
connectString := fmt.Sprintf("foundationdb:foundationdb@127.0.0.1:%s", resource.GetPort("4500/tcp"))
if err := tmpFile.Truncate(0); err != nil {
return fmt.Errorf("could not truncate cluster file: %w", err)
}
_, err := tmpFile.WriteAt([]byte(connectString), 0)
if err != nil {
return fmt.Errorf("could not write cluster file: %w", err)
}
if _, err := connectToFoundationDB(clusterFile); err != nil {
return fmt.Errorf("could not connect to FoundationDB after starting container: %s", err)
}
return nil
}
if pool.Retry(setup); err != nil {
cleanup()
t.Fatalf("foundationdb: could not setup container: %s", err)
}
tmpFile.Close()
return cleanup, clusterFile
}