ref/services/groups: adding leveldb-based persistent layer implementation
Change-Id: Ia802b8fd50eabe9eb67f10cf8f8a8a1c94484be3
diff --git a/services/groups/groupsd/main.go b/services/groups/groupsd/main.go
index f37c64a..a1ef298 100644
--- a/services/groups/groupsd/main.go
+++ b/services/groups/groupsd/main.go
@@ -23,7 +23,7 @@
"v.io/x/ref/lib/xrpc"
_ "v.io/x/ref/runtime/factories/roaming"
"v.io/x/ref/services/groups/internal/server"
- "v.io/x/ref/services/groups/internal/store/memstore"
+ "v.io/x/ref/services/groups/internal/store/mem"
)
var flagName string
@@ -68,7 +68,7 @@
ctx.Infof("No permissions flag provided. Giving local principal all permissions.")
perms = defaultPerms(security.DefaultBlessingPatterns(v23.GetPrincipal(ctx)))
}
- m := server.NewManager(memstore.New(), perms)
+ m := server.NewManager(mem.New(), perms)
server, err := xrpc.NewDispatchingServer(ctx, flagName, m)
if err != nil {
fmt.Errorf("NewDispatchingServer(%v) failed: %v", flagName, err)
diff --git a/services/groups/internal/server/server_test.go b/services/groups/internal/server/server_test.go
index 11d81b1..1a68b70 100644
--- a/services/groups/internal/server/server_test.go
+++ b/services/groups/internal/server/server_test.go
@@ -23,10 +23,19 @@
"v.io/x/ref/services/groups/internal/server"
"v.io/x/ref/services/groups/internal/store"
"v.io/x/ref/services/groups/internal/store/gkv"
- "v.io/x/ref/services/groups/internal/store/memstore"
+ "v.io/x/ref/services/groups/internal/store/leveldb"
+ "v.io/x/ref/services/groups/internal/store/mem"
"v.io/x/ref/test/testutil"
)
+type backend int
+
+const (
+ gkvstore backend = iota
+ leveldbstore
+ memstore
+)
+
func Fatal(t *testing.T, args ...interface{}) {
debug.PrintStack()
t.Fatal(args...)
@@ -89,20 +98,18 @@
return reflect.DeepEqual(a, b)
}
-// TODO(sadovsky): Write storage engine tests, then maybe drop this constant.
-const useMemstore = false
-
-func newServer(ctx *context.T) (string, func()) {
+func newServer(ctx *context.T, be backend) (string, func()) {
// TODO(sadovsky): Pass in perms and test perms-checking in Group.Create().
perms := access.Permissions{}
var st store.Store
- var file *os.File
+ var path string
var err error
- if useMemstore {
- st = memstore.New()
- } else {
- file, err = ioutil.TempFile("", "")
+ switch be {
+ case memstore:
+ st = mem.New()
+ case gkvstore:
+ file, err := ioutil.TempFile("", "")
if err != nil {
ctx.Fatal("ioutil.TempFile() failed: ", err)
}
@@ -110,6 +117,18 @@
if err != nil {
ctx.Fatal("gkv.New() failed: ", err)
}
+ path = file.Name()
+ case leveldbstore:
+ path, err = ioutil.TempDir("", "")
+ if err != nil {
+ ctx.Fatal("ioutil.TempDir() failed: ", err)
+ }
+ st, err = leveldb.Open(path)
+ if err != nil {
+ ctx.Fatal("leveldb.Open() failed: ", err)
+ }
+ default:
+ ctx.Fatal("unknown backend: ", be)
}
m := server.NewManager(st, perms)
@@ -122,13 +141,13 @@
name := server.Status().Endpoints[0].Name()
return name, func() {
server.Stop()
- if file != nil {
- os.Remove(file.Name())
+ if path != "" {
+ os.RemoveAll(path)
}
}
}
-func setupOrDie() (clientCtx *context.T, serverName string, cleanup func()) {
+func setupOrDie(be backend) (clientCtx *context.T, serverName string, cleanup func()) {
ctx, shutdown := v23.Init()
cp, sp := testutil.NewPrincipal("client"), testutil.NewPrincipal("server")
@@ -156,7 +175,7 @@
clientCtx.Fatal("v23.WithPrincipal() failed: ", err)
}
- serverName, stopServer := newServer(serverCtx)
+ serverName, stopServer := newServer(serverCtx, be)
cleanup = func() {
stopServer()
shutdown()
@@ -167,8 +186,20 @@
////////////////////////////////////////
// Test cases
-func TestCreate(t *testing.T) {
- ctx, serverName, cleanup := setupOrDie()
+func TestCreateGkvStore(t *testing.T) {
+ testCreateHelper(t, gkvstore)
+}
+
+func TestCreateLevelDBStore(t *testing.T) {
+ testCreateHelper(t, leveldbstore)
+}
+
+func TestCreateMemStore(t *testing.T) {
+ testCreateHelper(t, memstore)
+}
+
+func testCreateHelper(t *testing.T, be backend) {
+ ctx, serverName, cleanup := setupOrDie(be)
defer cleanup()
// Create a group with a default perms and no entries.
@@ -219,8 +250,20 @@
}
}
-func TestDelete(t *testing.T) {
- ctx, serverName, cleanup := setupOrDie()
+func TestDeleteGkvStore(t *testing.T) {
+ testDeleteHelper(t, gkvstore)
+}
+
+func TestDeleteLevelDBStore(t *testing.T) {
+ testDeleteHelper(t, leveldbstore)
+}
+
+func TestDeleteMemStore(t *testing.T) {
+ testDeleteHelper(t, memstore)
+}
+
+func testDeleteHelper(t *testing.T, be backend) {
+ ctx, serverName, cleanup := setupOrDie(be)
defer cleanup()
// Create a group with a default perms and no entries, check that we can
@@ -279,8 +322,20 @@
}
}
-func TestPerms(t *testing.T) {
- ctx, serverName, cleanup := setupOrDie()
+func TestPermsGkvStore(t *testing.T) {
+ testPermsHelper(t, gkvstore)
+}
+
+func TestPermsLevelDBStore(t *testing.T) {
+ testPermsHelper(t, leveldbstore)
+}
+
+func TestPermsMemStore(t *testing.T) {
+ testPermsHelper(t, memstore)
+}
+
+func testPermsHelper(t *testing.T, be backend) {
+ ctx, serverName, cleanup := setupOrDie(be)
defer cleanup()
// Create a group with a default perms.
@@ -384,9 +439,21 @@
}
}
-// Mirrors TestRemove.
-func TestAdd(t *testing.T) {
- ctx, serverName, cleanup := setupOrDie()
+func TestAddGkvStore(t *testing.T) {
+ testAddHelper(t, gkvstore)
+}
+
+func TestAddLevelDBStore(t *testing.T) {
+ testAddHelper(t, leveldbstore)
+}
+
+func TestAddMemStore(t *testing.T) {
+ testAddHelper(t, memstore)
+}
+
+// testAddHelper tests mirror testRemoveHelper tests.
+func testAddHelper(t *testing.T, be backend) {
+ ctx, serverName, cleanup := setupOrDie(be)
defer cleanup()
// Create a group with a default perms and no entries.
@@ -469,9 +536,21 @@
}
}
-// Mirrors TestAdd.
-func TestRemove(t *testing.T) {
- ctx, serverName, cleanup := setupOrDie()
+func TestRemoveGkvStore(t *testing.T) {
+ testRemoveHelper(t, gkvstore)
+}
+
+func TestRemoveLevelDBStore(t *testing.T) {
+ testRemoveHelper(t, leveldbstore)
+}
+
+func TestRemoveMemStore(t *testing.T) {
+ testRemoveHelper(t, memstore)
+}
+
+// testRemoveHelper tests mirror testAddHelper tests.
+func testRemoveHelper(t *testing.T, be backend) {
+ ctx, serverName, cleanup := setupOrDie(be)
defer cleanup()
// Create a group with a default perms and two entries.
diff --git a/services/groups/internal/store/leveldb/store.go b/services/groups/internal/store/leveldb/store.go
new file mode 100644
index 0000000..5f38f52
--- /dev/null
+++ b/services/groups/internal/store/leveldb/store.go
@@ -0,0 +1,134 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package leveldb provides an implementation of the groups server
+// Store interface that uses the levelDB-based syncbase storage layer.
+package leveldb
+
+import (
+ "strconv"
+
+ istore "v.io/syncbase/x/ref/services/syncbase/store"
+ "v.io/syncbase/x/ref/services/syncbase/store/leveldb"
+ "v.io/v23/vdl"
+ "v.io/v23/verror"
+ "v.io/v23/vom"
+ "v.io/x/ref/services/groups/internal/store"
+)
+
+type entry struct {
+ Value interface{}
+ Version uint64
+}
+
+type T struct {
+ db istore.Store
+}
+
+var _ store.Store = (*T)(nil)
+
+// Open opens a groups server store located at the given path,
+// creating it if it doesn't exist.
+func Open(path string) (store.Store, error) {
+ db, err := leveldb.Open(path)
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return &T{db: db}, nil
+}
+
+func (st *T) Get(key string, valbuf interface{}) (version string, err error) {
+ e, err := get(st.db, key)
+ if err != nil {
+ return "", err
+ }
+ if err := vdl.Convert(valbuf, e.Value); err != nil {
+ return "", convertError(err)
+ }
+ return strconv.FormatUint(e.Version, 10), nil
+}
+
+func (st *T) Insert(key string, value interface{}) error {
+ return istore.RunInTransaction(st.db, func(db istore.StoreReadWriter) error {
+ if _, err := get(db, key); verror.ErrorID(err) != store.ErrUnknownKey.ID {
+ if err != nil {
+ return err
+ }
+ return verror.New(store.ErrKeyExists, nil, key)
+ }
+ return put(db, key, &entry{Value: value})
+ })
+}
+
+func (st *T) Update(key string, value interface{}, version string) error {
+ return istore.RunInTransaction(st.db, func(db istore.StoreReadWriter) error {
+ e, err := get(db, key)
+ if err != nil {
+ return err
+ }
+ if err := e.checkVersion(version); err != nil {
+ return err
+ }
+ return put(db, key, &entry{Value: value, Version: e.Version + 1})
+ })
+}
+
+func (st *T) Delete(key string, version string) error {
+ return istore.RunInTransaction(st.db, func(db istore.StoreReadWriter) error {
+ e, err := get(db, key)
+ if err != nil {
+ return err
+ }
+ if err := e.checkVersion(version); err != nil {
+ return err
+ }
+ return delete(db, key)
+ })
+}
+
+func (st *T) Close() error {
+ return convertError(st.db.Close())
+}
+
+func get(db istore.StoreReadWriter, key string) (*entry, error) {
+ bytes, _ := db.Get([]byte(key), nil)
+ if bytes == nil {
+ return nil, verror.New(store.ErrUnknownKey, nil, key)
+ }
+ e := &entry{}
+ if err := vom.Decode(bytes, e); err != nil {
+ return nil, convertError(err)
+ }
+ return e, nil
+}
+
+func put(db istore.StoreReadWriter, key string, e *entry) error {
+ bytes, err := vom.Encode(e)
+ if err != nil {
+ return convertError(err)
+ }
+ if err := db.Put([]byte(key), bytes); err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+func delete(db istore.StoreReadWriter, key string) error {
+ if err := db.Delete([]byte(key)); err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+func (e *entry) checkVersion(version string) error {
+ newVersion := strconv.FormatUint(e.Version, 10)
+ if version != newVersion {
+ return verror.NewErrBadVersion(nil)
+ }
+ return nil
+}
+
+func convertError(err error) error {
+ return verror.Convert(verror.IDAction{}, nil, err)
+}
diff --git a/services/groups/internal/store/memstore/store.go b/services/groups/internal/store/mem/store.go
similarity index 92%
rename from services/groups/internal/store/memstore/store.go
rename to services/groups/internal/store/mem/store.go
index c086c44..f8ddd49 100644
--- a/services/groups/internal/store/memstore/store.go
+++ b/services/groups/internal/store/mem/store.go
@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package memstore provides a simple, in-memory implementation of server.Store.
-// Since it's a prototype implementation, it doesn't bother with entry-level
-// locking.
-package memstore
+// Package mem provides a simple, in-memory implementation of
+// server.Store. Since it's a prototype implementation, it doesn't
+// bother with entry-level locking.
+package mem
import (
"strconv"