syncbase: make storage engine somewhat configurable

Change-Id: I7ec6f794ca3c379050bbd6cfe91c431e6e630792
diff --git a/services/syncbase/server/app.go b/services/syncbase/server/app.go
index af92c09..aa4bfb3 100644
--- a/services/syncbase/server/app.go
+++ b/services/syncbase/server/app.go
@@ -5,6 +5,7 @@
 package server
 
 import (
+	"path"
 	"sync"
 
 	wire "v.io/syncbase/v23/services/syncbase"
@@ -148,7 +149,11 @@
 	if perms == nil {
 		perms = aData.Perms
 	}
-	d, err := nosql.NewDatabase(ctx, call, a, dbName, perms)
+	d, err := nosql.NewDatabase(ctx, call, a, dbName, nosql.DatabaseOptions{
+		Perms:   perms,
+		RootDir: path.Join(a.s.opts.RootDir, "apps", a.name, dbName),
+		Engine:  a.s.opts.Engine,
+	})
 	if err != nil {
 		return err
 	}
diff --git a/services/syncbase/server/nosql/database.go b/services/syncbase/server/nosql/database.go
index 40cc85b..1f3cbd3 100644
--- a/services/syncbase/server/nosql/database.go
+++ b/services/syncbase/server/nosql/database.go
@@ -5,12 +5,13 @@
 package nosql
 
 import (
+	"path"
+
 	wire "v.io/syncbase/v23/services/syncbase/nosql"
 	"v.io/syncbase/x/ref/services/syncbase/server/interfaces"
 	"v.io/syncbase/x/ref/services/syncbase/server/util"
 	"v.io/syncbase/x/ref/services/syncbase/server/watchable"
 	"v.io/syncbase/x/ref/services/syncbase/store"
-	"v.io/syncbase/x/ref/services/syncbase/store/memstore"
 	"v.io/v23/context"
 	"v.io/v23/rpc"
 	"v.io/v23/security/access"
@@ -31,15 +32,27 @@
 	_ util.Layer                 = (*database)(nil)
 )
 
+type DatabaseOptions struct {
+	// Database-level permissions.
+	Perms access.Permissions
+	// Root dir for data storage.
+	RootDir string
+	// Storage engine to use.
+	Engine string
+}
+
 // NewDatabase creates a new database instance and returns it.
 // Returns a VDL-compatible error.
 // Designed for use from within App.CreateNoSQLDatabase.
-func NewDatabase(ctx *context.T, call rpc.ServerCall, a interfaces.App, name string, perms access.Permissions) (*database, error) {
-	if perms == nil {
+func NewDatabase(ctx *context.T, call rpc.ServerCall, a interfaces.App, name string, opts DatabaseOptions) (*database, error) {
+	if opts.Perms == nil {
 		return nil, verror.New(verror.ErrInternal, ctx, "perms must be specified")
 	}
-	// TODO(sadovsky): Make storage engine pluggable.
-	st, err := watchable.Wrap(memstore.New(), &watchable.Options{
+	st, err := util.OpenStore(opts.Engine, path.Join(opts.RootDir, opts.Engine))
+	if err != nil {
+		return nil, err
+	}
+	st, err = watchable.Wrap(st, &watchable.Options{
 		ManagedPrefixes: []string{util.RowPrefix},
 	})
 	if err != nil {
@@ -52,7 +65,7 @@
 	}
 	data := &databaseData{
 		Name:  d.name,
-		Perms: perms,
+		Perms: opts.Perms,
 	}
 	if err := util.Put(ctx, call, d.st, d, data); err != nil {
 		return nil, err
diff --git a/services/syncbase/server/service.go b/services/syncbase/server/service.go
index b352a27..fd3e3b8 100644
--- a/services/syncbase/server/service.go
+++ b/services/syncbase/server/service.go
@@ -9,13 +9,13 @@
 // preserve privacy.
 
 import (
+	"path"
 	"sync"
 
 	wire "v.io/syncbase/v23/services/syncbase"
 	"v.io/syncbase/x/ref/services/syncbase/server/interfaces"
 	"v.io/syncbase/x/ref/services/syncbase/server/util"
 	"v.io/syncbase/x/ref/services/syncbase/store"
-	"v.io/syncbase/x/ref/services/syncbase/store/memstore"
 	"v.io/syncbase/x/ref/services/syncbase/vsync"
 	"v.io/v23/context"
 	"v.io/v23/rpc"
@@ -23,9 +23,19 @@
 	"v.io/v23/verror"
 )
 
+type ServiceOptions struct {
+	// Service-level permissions.
+	Perms access.Permissions
+	// Root dir for data storage.
+	RootDir string
+	// Storage engine to use (for service and per-database engines).
+	Engine string
+}
+
 type service struct {
 	st   store.Store // keeps track of which apps and databases exist, etc.
 	sync interfaces.SyncServerMethods
+	opts ServiceOptions
 	// Guards the fields below. Held during app Create, Delete, and
 	// SetPermissions.
 	mu   sync.Mutex
@@ -40,28 +50,28 @@
 
 // NewService creates a new service instance and returns it.
 // Returns a VDL-compatible error.
-func NewService(ctx *context.T, call rpc.ServerCall, perms access.Permissions) (*service, error) {
-	if perms == nil {
+func NewService(ctx *context.T, call rpc.ServerCall, opts ServiceOptions) (*service, error) {
+	if opts.Perms == nil {
 		return nil, verror.New(verror.ErrInternal, ctx, "perms must be specified")
 	}
-	// TODO(sadovsky): Make storage engine pluggable.
+	st, err := util.OpenStore(opts.Engine, path.Join(opts.RootDir, opts.Engine))
+	if err != nil {
+		return nil, err
+	}
 	s := &service{
-		st:   memstore.New(),
+		st:   st,
+		opts: opts,
 		apps: map[string]*app{},
 	}
-
 	data := &serviceData{
-		Perms: perms,
+		Perms: opts.Perms,
 	}
 	if err := util.Put(ctx, call, s.st, s, data); err != nil {
 		return nil, err
 	}
-
-	var err error
 	if s.sync, err = vsync.New(ctx, call, s); err != nil {
 		return nil, err
 	}
-
 	return s, nil
 }
 
diff --git a/services/syncbase/server/util/store_util.go b/services/syncbase/server/util/store_util.go
index 8e289d6..ebee090 100644
--- a/services/syncbase/server/util/store_util.go
+++ b/services/syncbase/server/util/store_util.go
@@ -5,9 +5,12 @@
 package util
 
 import (
+	"os"
 	"strconv"
 
 	"v.io/syncbase/x/ref/services/syncbase/store"
+	"v.io/syncbase/x/ref/services/syncbase/store/leveldb"
+	"v.io/syncbase/x/ref/services/syncbase/store/memstore"
 	"v.io/v23/context"
 	"v.io/v23/rpc"
 	"v.io/v23/security/access"
@@ -104,7 +107,7 @@
 }
 
 ////////////////////////////////////////////////////////////
-// RPC-oblivious, lower-level get/put
+// RPC-oblivious, lower-level helpers
 
 func GetObject(st store.StoreReader, k string, v interface{}) error {
 	bytes, err := st.Get([]byte(k), nil)
@@ -121,3 +124,17 @@
 	}
 	return st.Put([]byte(k), bytes)
 }
+
+func OpenStore(engine, path string) (store.Store, error) {
+	switch engine {
+	case "memstore":
+		return memstore.New(), nil
+	case "leveldb":
+		if err := os.MkdirAll(path, 0700); err != nil {
+			return nil, verror.New(verror.ErrInternal, nil, err)
+		}
+		return leveldb.Open(path)
+	default:
+		return nil, verror.New(verror.ErrBadArg, nil, engine)
+	}
+}
diff --git a/services/syncbase/syncbased/main.go b/services/syncbase/syncbased/main.go
index 44865a3..01f9070 100644
--- a/services/syncbase/syncbased/main.go
+++ b/services/syncbase/syncbased/main.go
@@ -23,7 +23,9 @@
 )
 
 var (
-	name = flag.String("name", "", "Name to mount at.")
+	name    = flag.String("name", "", "Name to mount at.")
+	rootDir = flag.String("root-dir", "/var/lib/syncbase", "Root dir for storage engines and other data")
+	engine  = flag.String("engine", "memstore", "Storage engine to use. Currently supported: memstore and leveldb.")
 )
 
 // defaultPerms returns a permissions object that grants all permissions to the
@@ -62,7 +64,11 @@
 		perms = defaultPerms(security.DefaultBlessingPatterns(v23.GetPrincipal(ctx)))
 	}
 
-	service, err := server.NewService(nil, nil, perms)
+	service, err := server.NewService(nil, nil, server.ServiceOptions{
+		Perms:   perms,
+		RootDir: *rootDir,
+		Engine:  *engine,
+	})
 	if err != nil {
 		vlog.Fatal("server.NewService() failed: ", err)
 	}