syncbased: add flag to allow the creation of an initial database when
setting up a new service instance

This works similarly to how we set up the initial permissions the very
first time we run syncbased on a blank root dir.

MultiPart: 2/2
Change-Id: I8914c135556aef49586faa4f1ff3b65e9b7c6963
diff --git a/services/syncbase/server/service.go b/services/syncbase/server/service.go
index 7941db5..fde8964 100644
--- a/services/syncbase/server/service.go
+++ b/services/syncbase/server/service.go
@@ -60,7 +60,7 @@
 
 // ServiceOptions configures a service.
 type ServiceOptions struct {
-	// Service-level permissions. Used only when creating a brand-new storage
+	// Service-level permissions. Used only when creating a brand new storage
 	// instance.
 	Perms access.Permissions
 	// Root dir for data storage. If empty, we write to a fresh directory created
@@ -74,6 +74,9 @@
 	// Whether to run in development mode; required for RPCs such as
 	// Service.DevModeUpdateVClock.
 	DevMode bool
+	// InitialDB, if not blank, specifies an initial database to create when
+	// creating a brand new storage instance.
+	InitialDB wire.Id
 }
 
 // defaultPerms returns a permissions object that grants all permissions to the
@@ -143,6 +146,7 @@
 	}
 
 	var sd ServiceData
+	newService := false
 	if err := store.Get(ctx, st, s.stKey(), &sd); verror.ErrorID(err) != verror.ErrNoExist.ID {
 		if err != nil {
 			return nil, err
@@ -166,6 +170,7 @@
 			return nil, verror.New(verror.ErrInternal, ctx, err)
 		}
 	} else {
+		newService = true
 		perms := opts.Perms
 		// Service does not exist.
 		if perms == nil {
@@ -187,6 +192,13 @@
 		return nil, err
 	}
 
+	if newService && opts.InitialDB != (wire.Id{}) {
+		ctx.Info("Creating initial database:", opts.InitialDB)
+		if err := s.createDatabase(ctx, nil, opts.InitialDB, nil, nil); err != nil {
+			return nil, err
+		}
+	}
+
 	// With Sync and the pre-existing DBs initialized, the store can start a
 	// Sync watcher for each DB store, similar to the flow of a DB creation.
 	for _, d := range s.dbs {
@@ -403,8 +415,17 @@
 	}
 
 	// 1. Check serviceData perms.
-	sData := &ServiceData{}
-	if err := util.GetWithAuth(ctx, call, s.st, s.stKey(), sData); err != nil {
+	getServiceData := func() (sd *ServiceData, err error) {
+		sd = new(ServiceData)
+		if call != nil {
+			err = util.GetWithAuth(ctx, call, s.st, s.stKey(), sd)
+		} else {
+			err = store.Get(ctx, s.st, s.stKey(), sd)
+		}
+		return
+	}
+	sData, err := getServiceData()
+	if err != nil {
 		return err
 	}
 
@@ -458,11 +479,9 @@
 		// Note: To avoid a race, we must re-check service perms, and make sure the
 		// perms version hasn't changed, inside the transaction that makes the new
 		// database visible.
-		sDataRepeat := &ServiceData{}
-		if err := util.GetWithAuth(ctx, call, s.st, s.stKey(), sDataRepeat); err != nil {
+		if sDataRepeat, err := getServiceData(); err != nil {
 			return err
-		}
-		if sData.Version != sDataRepeat.Version {
+		} else if sData.Version != sDataRepeat.Version {
 			return verror.NewErrBadVersion(ctx)
 		}
 		// Check for "database already exists".
diff --git a/services/syncbase/syncbased/doc.go b/services/syncbase/syncbased/doc.go
index 43589e4..9237fcf 100644
--- a/services/syncbase/syncbased/doc.go
+++ b/services/syncbase/syncbased/doc.go
@@ -21,6 +21,11 @@
  -engine=
    Storage engine to use: memstore or leveldb. If empty, we use the default
    storage engine, currently leveldb.
+ -initial-db=
+   If specified, a new database with the given id is created when setting up a
+   brand new storage instance. Permissions for the database will be the service
+   permissions. Format must conform to v.io/services/syncbase.Id.String:
+   blessing,name
  -name=
    Name to mount at.
  -root-dir=
diff --git a/services/syncbase/syncbaselib/opts.go b/services/syncbase/syncbaselib/opts.go
index eb6bfa3..1626851 100644
--- a/services/syncbase/syncbaselib/opts.go
+++ b/services/syncbase/syncbaselib/opts.go
@@ -15,6 +15,7 @@
 	SkipPublishInNh bool
 	DevMode         bool
 	CpuProfile      string
+	InitialDB       string
 }
 
 // Note: Where possible, we have flag default values be zero values, so that
@@ -26,4 +27,5 @@
 	f.BoolVar(&o.SkipPublishInNh, "skip-publish-in-nh", false, "Whether to skip publishing in the neighborhood.")
 	f.BoolVar(&o.DevMode, "dev", false, "Whether to run in development mode; required for RPCs such as Service.DevModeUpdateVClock.")
 	f.StringVar(&o.CpuProfile, "cpuprofile", "", "If specified, write the cpu profile to the given filename.")
+	f.StringVar(&o.InitialDB, "initial-db", "", "If specified, a new database with the given id is created when setting up a brand new storage instance. Permissions for the database will be the service permissions. Format must conform to v.io/services/syncbase.Id.String: blessing,name")
 }
diff --git a/services/syncbase/syncbaselib/serve.go b/services/syncbase/syncbaselib/serve.go
index 8a988b4..dea394a 100644
--- a/services/syncbase/syncbaselib/serve.go
+++ b/services/syncbase/syncbaselib/serve.go
@@ -13,6 +13,8 @@
 	"v.io/v23/context"
 	"v.io/v23/options"
 	"v.io/v23/rpc"
+	wire "v.io/v23/services/syncbase"
+	pubutil "v.io/v23/syncbase/util"
 	"v.io/x/ref/lib/dispatcher"
 	"v.io/x/ref/lib/security/securityflag"
 	"v.io/x/ref/services/syncbase/server"
@@ -54,12 +56,22 @@
 	if perms != nil {
 		ctx.Infof("Read permissions from command line flag: %v", server.PermsString(ctx, perms))
 	}
+	var initialDB wire.Id
+	if opts.InitialDB != "" {
+		if initialDB, err = wire.ParseId(opts.InitialDB); err != nil {
+			ctx.Fatalf("ParseId(%s) failed: %v", opts.InitialDB, err)
+		}
+		if err := pubutil.ValidateId(initialDB); err != nil {
+			ctx.Fatalf("ValidateId(%v) failed: %v", initialDB, err)
+		}
+	}
 	service, err := server.NewService(ctx, server.ServiceOptions{
 		Perms:           perms,
 		RootDir:         opts.RootDir,
 		Engine:          opts.Engine,
 		SkipPublishInNh: opts.SkipPublishInNh,
 		DevMode:         opts.DevMode,
+		InitialDB:       initialDB,
 	})
 	if err != nil {
 		ctx.Fatal("server.NewService() failed: ", err)