{go/src/veyron2/storage,go/src/veyron2/services/store,go/src/veyron/services/store}: new transaction API
Primary description of new API:
https://docs.google.com/a/google.com/document/d/1Zob450G5rQDHmWXmg6UvVy3IH-hSiS8eE5L9IY62sW0/edit
Change-Id: I7272d389414ec27db8d599f0d562938a55e43a97
diff --git a/examples/bank/pbankd/main.go b/examples/bank/pbankd/main.go
index 07d0e4f..76acc17 100644
--- a/examples/bank/pbankd/main.go
+++ b/examples/bank/pbankd/main.go
@@ -26,11 +26,11 @@
"veyron2"
"veyron2/ipc"
+ "veyron2/naming"
"veyron2/rt"
"veyron2/security"
"veyron2/storage"
"veyron2/storage/vstore"
- "veyron2/storage/vstore/primitives"
"veyron2/vlog"
)
@@ -77,8 +77,8 @@
// Pointer to the store
store storage.Store
- // Current Transaction
- transaction storage.Transaction
+ // Current Transaction name; empty if there's no transaction
+ tname string
// The bank's private ID (used for blessing)
ID security.PrivateID
@@ -95,7 +95,11 @@
// InitializeBank bank details in the store; currently only initializes the root.
func (b *pbankd) initializeBank() {
- b.newTransaction()
+ if err := b.newTransaction(); err != nil {
+ vlog.Fatal(err)
+ }
+ // NOTE(sadovsky): initializeBankRoot ought to return an error. Currently,
+ // some errors (e.g. failed puts) could slip through unnoticed.
b.initializeBankRoot()
if err := b.commit(); err != nil {
vlog.Fatal(err)
@@ -110,11 +114,11 @@
for i, _ := range l {
fmt.Println(i)
prefix := filepath.Join(l[:i]...)
- o := b.store.Bind(prefix)
- if exist, err := o.Exists(runtime.TODOContext(), b.transaction); err != nil {
+ o := b.store.BindObject(naming.Join(b.tname, prefix))
+ if exist, err := o.Exists(runtime.TODOContext()); err != nil {
vlog.Infof("Error checking existence at %q: %s", prefix, err)
} else if !exist {
- if _, err := o.Put(runtime.TODOContext(), b.transaction, &schema.Dir{}); err != nil {
+ if _, err := o.Put(runtime.TODOContext(), &schema.Dir{}); err != nil {
vlog.Infof("Error creating parent %q: %s", prefix, err)
}
fmt.Printf("%q was created!\n", prefix)
@@ -124,11 +128,11 @@
}
// Add the bank schema to the store at BANK_ROOT, if necessary
- o := b.store.Bind(BANK_ROOT)
- if exist, err := o.Exists(runtime.TODOContext(), b.transaction); err != nil {
+ o := b.store.BindObject(naming.Join(b.tname, BANK_ROOT))
+ if exist, err := o.Exists(runtime.TODOContext()); err != nil {
vlog.Infof("Error checking existence at %q: %s", BANK_ROOT, err)
} else if !exist {
- _, err := o.Put(runtime.TODOContext(), b.transaction, &schema.Bank{})
+ _, err := o.Put(runtime.TODOContext(), &schema.Bank{})
if err != nil {
vlog.Infof("Error creating bank at %q: %s", BANK_ROOT, err)
}
@@ -146,7 +150,9 @@
} else {
fmt.Println("This client isn't blessed. Let's bless them!")
// Use the store
- b.newTransaction()
+ if err := b.newTransaction(); err != nil {
+ vlog.Fatal(err)
+ }
// Keep rolling until we get an unseen number
randID := rand.Int63n(MAX_ACCOUNT_NUMBER-MIN_ACCOUNT_NUMBER) + MIN_ACCOUNT_NUMBER
@@ -194,7 +200,9 @@
if user == 0 {
return fmt.Errorf("couldn't retrieve account number")
}
- b.newTransaction()
+ if err := b.newTransaction(); err != nil {
+ return err
+ }
if !b.isUser(user) {
return fmt.Errorf("user isn't registered")
} else if amount < 0 {
@@ -210,7 +218,9 @@
if user == 0 {
return fmt.Errorf("couldn't retrieve account number")
}
- b.newTransaction()
+ if err := b.newTransaction(); err != nil {
+ return err
+ }
if !b.isUser(user) {
return fmt.Errorf("user isn't registered")
} else if amount < 0 {
@@ -228,7 +238,9 @@
if user == 0 {
return fmt.Errorf("couldn't retrieve account number")
}
- b.newTransaction()
+ if err := b.newTransaction(); err != nil {
+ return err
+ }
if !b.isUser(user) {
return fmt.Errorf("user isn't registered")
} else if !b.isUser(accountNumber) {
@@ -261,14 +273,23 @@
*/
// newTransaction starts a new transaction.
-func (b *pbankd) newTransaction() {
- b.transaction = primitives.NewTransaction(runtime.TODOContext())
+func (b *pbankd) newTransaction() error {
+ tid, err := b.store.BindTransactionRoot("").CreateTransaction(runtime.TODOContext())
+ if err != nil {
+ b.tname = ""
+ return err
+ }
+ b.tname = tid // Transaction is rooted at "", so tname == tid.
+ return nil
}
// commit commits the current transaction.
func (b *pbankd) commit() error {
- err := b.transaction.Commit(runtime.TODOContext())
- b.transaction = nil
+ if b.tname == "" {
+ return errors.New("No transaction to commit")
+ }
+ err := b.store.BindTransaction(b.tname).Commit(runtime.TODOContext())
+ b.tname = ""
if err != nil {
return fmt.Errorf("Failed to commit transaction: %s", err)
}
@@ -279,8 +300,8 @@
func (b *pbankd) isUser(accountNumber int64) bool {
// If this is a user, their location in the store should exist.
prefix := filepath.Join(BANK_ROOT, ACCOUNTS, fmt.Sprintf("%d", accountNumber))
- o := b.store.Bind(prefix)
- exist, err := o.Exists(runtime.TODOContext(), b.transaction)
+ o := b.store.BindObject(naming.Join(b.tname, prefix))
+ exist, err := o.Exists(runtime.TODOContext())
if err != nil {
vlog.Infof("Error checking existence at %s: %s", prefix, err)
return false
@@ -319,8 +340,8 @@
func (b *pbankd) registerNewUser(user int64) {
// Create the user's account
prefix := filepath.Join(BANK_ROOT, ACCOUNTS, fmt.Sprintf("%d", user))
- o := b.store.Bind(prefix)
- if _, err := o.Put(runtime.TODOContext(), b.transaction, int64(0)); err != nil {
+ o := b.store.BindObject(naming.Join(b.tname, prefix))
+ if _, err := o.Put(runtime.TODOContext(), int64(0)); err != nil {
vlog.Infof("Error creating %s: %s", prefix, err)
}
}
@@ -328,8 +349,8 @@
// checkBalance gets the user's balance from the store
func (b *pbankd) checkBalance(user int64) int64 {
prefix := filepath.Join(BANK_ROOT, ACCOUNTS, fmt.Sprintf("%d", user))
- o := b.store.Bind(prefix)
- e, err := o.Get(runtime.TODOContext(), b.transaction)
+ o := b.store.BindObject(naming.Join(b.tname, prefix))
+ e, err := o.Get(runtime.TODOContext())
if err != nil {
vlog.Infof("Error getting %s: %s", prefix, err)
}
@@ -340,12 +361,12 @@
// changeBalance modifies the user's balance in the store
func (b *pbankd) changeBalance(user int64, amount int64) {
prefix := filepath.Join(BANK_ROOT, ACCOUNTS, fmt.Sprintf("%d", user))
- o := b.store.Bind(prefix)
- e, err := o.Get(runtime.TODOContext(), b.transaction)
+ o := b.store.BindObject(naming.Join(b.tname, prefix))
+ e, err := o.Get(runtime.TODOContext())
if err != nil {
vlog.Infof("Error getting %s: %s", prefix, err)
}
- if _, err := o.Put(runtime.TODOContext(), b.transaction, e.Value.(int64)+amount); err != nil {
+ if _, err := o.Put(runtime.TODOContext(), e.Value.(int64)+amount); err != nil {
vlog.Infof("Error changing %s: %s", prefix, err)
}
}
diff --git a/examples/boxes/android/src/boxesp2p/main.go b/examples/boxes/android/src/boxesp2p/main.go
index 67e7475..7580fff 100644
--- a/examples/boxes/android/src/boxesp2p/main.go
+++ b/examples/boxes/android/src/boxesp2p/main.go
@@ -80,7 +80,6 @@
iwatch "veyron2/services/watch"
"veyron2/storage"
"veyron2/storage/vstore"
- "veyron2/storage/vstore/primitives"
"veyron2/vom"
)
@@ -195,7 +194,8 @@
if err != nil {
panic(fmt.Errorf("Failed to init veyron store:%v", err))
}
- root := vst.Bind("/")
+ rootName := "/"
+ root := vst.BindObject(rootName)
// Watch for any box updates from the store
go func() {
@@ -224,21 +224,29 @@
}()
// Send any box updates to the store
- tr := primitives.NewTransaction(ctx)
- if _, err := root.Put(ctx, tr, ""); err != nil {
- panic(fmt.Errorf("Put for %s failed:%v", root, err))
+ tid, err := vst.BindTransactionRoot(rootName).CreateTransaction(ctx)
+ if err != nil {
+ panic(fmt.Errorf("CreateTransaction for %s failed:%v", rootName, err))
}
- if err := tr.Commit(ctx); err != nil {
+ tname := naming.Join(rootName, tid)
+ if _, err := vst.BindObject(tname).Put(ctx, ""); err != nil {
+ panic(fmt.Errorf("Put for %s failed:%v", tname, err))
+ }
+ if err := vst.BindTransaction(tname).Commit(ctx); err != nil {
panic(fmt.Errorf("Commit transaction failed:%v", err))
}
for {
box := <-gs.boxList
- boxes := vst.Bind("/" + box.BoxId)
- tr = primitives.NewTransaction(ctx)
- if _, err := boxes.Put(ctx, tr, box); err != nil {
- panic(fmt.Errorf("Put for %s failed:%v", boxes, err))
+ boxesName := "/" + box.BoxId
+ tid, err = vst.BindTransactionRoot(boxesName).CreateTransaction(ctx)
+ if err != nil {
+ panic(fmt.Errorf("CreateTransaction for %s failed:%v", boxesName, err))
}
- if err := tr.Commit(ctx); err != nil {
+ tname := naming.Join(boxesName, tid)
+ if _, err := vst.BindObject(tname).Put(ctx, box); err != nil {
+ panic(fmt.Errorf("Put for %s failed:%v", tname, err))
+ }
+ if err := vst.BindTransaction(tname).Commit(ctx); err != nil {
panic(fmt.Errorf("Commit transaction failed:%v", err))
}
}
diff --git a/examples/stfortune/stfortune/main.go b/examples/stfortune/stfortune/main.go
index 1de9047..500b27d 100644
--- a/examples/stfortune/stfortune/main.go
+++ b/examples/stfortune/stfortune/main.go
@@ -24,7 +24,6 @@
iwatch "veyron2/services/watch"
"veyron2/storage"
"veyron2/storage/vstore"
- "veyron2/storage/vstore/primitives"
"veyron2/vom"
)
@@ -58,7 +57,7 @@
paths := []string{appPath, fortunePath(""), userPath("")}
for _, path := range paths {
req := iwatch.GlobRequest{Pattern: ""}
- stream, err := store.Bind(path).WatchGlob(ctx, req)
+ stream, err := store.BindObject(path).WatchGlob(ctx, req)
if err != nil {
log.Fatalf("WatchGlob %s failed: %v", path, err)
}
@@ -89,7 +88,7 @@
fmt.Printf("Running as a Watcher monitoring new fortunes under %s...\n", path)
req := iwatch.GlobRequest{Pattern: "*"}
- stream, err := store.Bind(path).WatchGlob(ctx, req)
+ stream, err := store.BindObject(path).WatchGlob(ctx, req)
if err != nil {
log.Fatalf("watcher WatchGlob %s failed: %v", path, err)
}
@@ -123,27 +122,32 @@
// pickFortuneGlob uses Glob to find all available fortunes under the input
// path and then it chooses one randomly.
func pickFortuneGlob(store storage.Store, ctx context.T, path string) (string, error) {
- tr := primitives.NewTransaction(ctx)
- defer tr.Abort(ctx)
-
- results, err := store.Bind(path).GlobT(ctx, tr, "*")
+ // Transaction is rooted at "", so tname == tid.
+ tname, err := store.BindTransactionRoot("").CreateTransaction(ctx)
if err != nil {
return "", err
}
+
+ // This transaction is read-only, so we always abort it at the end.
+ defer store.BindTransaction(tname).Abort(ctx)
+
+ trPath := func(path string) string {
+ return naming.Join(tname, path)
+ }
+
+ results := store.BindObject(trPath(path)).Glob(ctx, "*")
var names []string
for results.Advance() {
- name := results.Value()
- names = append(names, name)
+ names = append(names, results.Value())
}
- results.Finish()
- if names == nil || len(names) < 1 {
- return "", nil
+ if err := results.Err(); err != nil || len(names) == 0 {
+ return "", err
}
// Get a random fortune using the glob results.
random := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
p := fortunePath(names[random.Intn(len(names))])
- f, err := store.Bind(p).Get(ctx, tr)
+ f, err := store.BindObject(trPath(p)).Get(ctx)
if err != nil {
return "", err
}
@@ -157,7 +161,7 @@
// pickFortuneQuery uses a query to find all available fortunes under the input
// path and choose one randomly.
func pickFortuneQuery(store storage.Store, ctx context.T, path string) (string, error) {
- results := store.Bind(path).Query(ctx, nil,
+ results := store.BindObject(path).Query(ctx,
query.Query{
"* |" + // Inspect all children of path.
"type FortuneData |" + // Include only objects of type FortuneData.
@@ -208,17 +212,27 @@
// user is created.
func addFortune(store storage.Store, fortune string, userName string) error {
ctx := rt.R().NewContext()
- tr := primitives.NewTransaction(ctx)
+
+ // Transaction is rooted at "", so tname == tid.
+ tname, err := store.BindTransactionRoot("").CreateTransaction(ctx)
+ if err != nil {
+ return err
+ }
+
committed := false
defer func() {
if !committed {
- tr.Abort(ctx)
+ store.BindTransaction(tname).Abort(ctx)
}
}()
+ trPath := func(path string) string {
+ return naming.Join(tname, path)
+ }
+
// Check if this fortune already exists. If yes, return.
hash := getMD5Hash(naming.Join(fortune, userName))
- exists, err := store.Bind(fortunePath(hash)).Exists(ctx, tr)
+ exists, err := store.BindObject(trPath(fortunePath(hash))).Exists(ctx)
if err != nil {
return err
}
@@ -227,21 +241,21 @@
}
// Check if the UserName exists. If yes, get its OID. If not, create a new user.
- o := store.Bind(userPath(userName))
- exists, err = o.Exists(ctx, tr)
+ o := store.BindObject(trPath(userPath(userName)))
+ exists, err = o.Exists(ctx)
if err != nil {
return err
}
var userid storage.ID
if !exists {
u := schema.User{Name: userName}
- stat, err := o.Put(ctx, tr, u)
+ stat, err := o.Put(ctx, u)
if err != nil {
return err
}
userid = stat.ID
} else {
- u, err := o.Get(ctx, tr)
+ u, err := o.Get(ctx)
if err != nil {
return err
}
@@ -250,14 +264,14 @@
// Create a new fortune entry.
f := schema.FortuneData{Fortune: fortune, UserName: userid}
- s, err := store.Bind(fortunePath(hash)).Put(ctx, tr, f)
+ s, err := store.BindObject(trPath(fortunePath(hash))).Put(ctx, f)
if err != nil {
return err
}
// Link the new fortune to UserName.
p := userPath(naming.Join(userName, hash))
- if _, err = store.Bind(p).Put(ctx, tr, s.ID); err != nil {
+ if _, err = store.BindObject(trPath(p)).Put(ctx, s.ID); err != nil {
return err
}
@@ -267,7 +281,7 @@
// locking. When the error for this scenario is
// exposed via the Commit API, one could retry the
// transaction.
- if err := tr.Commit(ctx); err != nil {
+ if err := store.BindTransaction(tname).Commit(ctx); err != nil {
return err
}
committed = true
diff --git a/examples/storage/mdb/mdb_init/main.go b/examples/storage/mdb/mdb_init/main.go
index 4cc4a83..28f8166 100644
--- a/examples/storage/mdb/mdb_init/main.go
+++ b/examples/storage/mdb/mdb_init/main.go
@@ -1,5 +1,6 @@
// mdb_init is a tool to initialize the store with an initial database. This is
-// really for demo purposes; in a real database, the contents would be persistant.
+// really for demo purposes; in a real database, the contents would be
+// persistent.
//
// The contents are loaded from JSON format. See mdb/templates/contents.json
// for the actual input.
@@ -22,10 +23,10 @@
"time"
"veyron/examples/storage/mdb/schema"
+ "veyron2/naming"
"veyron2/rt"
"veyron2/storage"
"veyron2/storage/vstore"
- "veyron2/storage/vstore/primitives"
"veyron2/vlog"
)
@@ -81,9 +82,9 @@
// state is the initial store state.
type state struct {
- store storage.Store
- transaction storage.Transaction
- idTable map[string]*value
+ store storage.Store
+ tname string // Current transaction name; empty if there's no transaction.
+ idTable map[string]*value
}
// value holds the ID and name of a stored value.
@@ -148,7 +149,7 @@
func (st *state) put(path string, v interface{}) {
vlog.Infof("Storing %q = %+v", path, v)
st.makeParentDirs(path)
- if _, err := st.store.Bind(path).Put(rt.R().TODOContext(), st.transaction, v); err != nil {
+ if _, err := st.store.BindObject(naming.Join(st.tname, path)).Put(rt.R().TODOContext(), v); err != nil {
vlog.Infof("put failed: %s: %s", path, err)
return
}
@@ -159,7 +160,7 @@
func (st *state) putNamed(name, path string, v interface{}) {
vlog.Infof("Storing %s: %q = %+v", name, path, v)
st.makeParentDirs(path)
- s, err := st.store.Bind(path).Put(rt.R().TODOContext(), st.transaction, v)
+ s, err := st.store.BindObject(naming.Join(st.tname, path)).Put(rt.R().TODOContext(), v)
if err != nil {
vlog.Infof("Put failed: %s: %s", path, err)
return
@@ -173,11 +174,11 @@
l := strings.Split(path, "/")
for i, _ := range l {
prefix := filepath.Join(l[:i]...)
- o := st.store.Bind(prefix)
- if exist, err := o.Exists(rt.R().TODOContext(), st.transaction); err != nil {
+ o := st.store.BindObject(naming.Join(st.tname, prefix))
+ if exist, err := o.Exists(rt.R().TODOContext()); err != nil {
vlog.Infof("Error checking existence at %q: %s", prefix, err)
} else if !exist {
- if _, err := o.Put(rt.R().TODOContext(), st.transaction, &schema.Dir{}); err != nil {
+ if _, err := o.Put(rt.R().TODOContext(), &schema.Dir{}); err != nil {
vlog.Infof("Error creating parent %q: %s", prefix, err)
}
}
@@ -186,15 +187,23 @@
// newTransaction starts a new transaction.
func (st *state) newTransaction() {
- st.transaction = primitives.NewTransaction(rt.R().TODOContext())
+ tid, err := st.store.BindTransactionRoot("").CreateTransaction(rt.R().TODOContext())
+ if err != nil {
+ vlog.Fatalf("Failed to create transaction: %s", err)
+ }
+ st.tname = tid // Transaction is rooted at "", so tname == tid.
}
// commit commits the current transaction.
func (st *state) commit() {
- if err := st.transaction.Commit(rt.R().TODOContext()); err != nil {
- vlog.Infof("Failed to commit transaction: %s", err)
+ if st.tname == "" {
+ vlog.Fatalf("No transaction to commit")
}
- st.transaction = nil
+ err := st.store.BindTransaction(st.tname).Commit(rt.R().TODOContext())
+ st.tname = ""
+ if err != nil {
+ vlog.Errorf("Failed to commit transaction: %s", err)
+ }
}
// storeContents saves each of the values in the Contents to the store.
diff --git a/examples/storage/viewer/value.go b/examples/storage/viewer/value.go
index d3cb79d..279df7a 100644
--- a/examples/storage/viewer/value.go
+++ b/examples/storage/viewer/value.go
@@ -21,15 +21,13 @@
// glob performs a glob expansion of the pattern. The results are sorted.
func glob(st storage.Store, path, pattern string) ([]string, error) {
- results, err := st.Bind(path).GlobT(rt.R().TODOContext(), nil, pattern)
- if err != nil {
- return nil, err
- }
- defer results.Finish()
+ results := st.BindObject(path).Glob(rt.R().TODOContext(), pattern)
names := []string{}
for results.Advance() {
- name := results.Value()
- names = append(names, "/"+name)
+ names = append(names, "/"+results.Value())
+ }
+ if err := results.Err(); err != nil {
+ return nil, err
}
sort.Strings(names)
return names, nil
@@ -68,7 +66,7 @@
// exist.
func (v *Value) Get(path string) interface{} {
path = v.fullpath(path)
- e, err := v.store.Bind(path).Get(rt.R().TODOContext(), nil)
+ e, err := v.store.BindObject(path).Get(rt.R().TODOContext())
if err != nil {
return nil
}
diff --git a/examples/storage/viewer/viewer.go b/examples/storage/viewer/viewer.go
index b97d499..f7cee5b 100644
--- a/examples/storage/viewer/viewer.go
+++ b/examples/storage/viewer/viewer.go
@@ -73,7 +73,7 @@
// is based on the type of the value, under /template/<pkgPath>/<typeName>.
func (s *server) loadTemplate(v interface{}) *template.Template {
path := templatePath(v)
- e, err := s.store.Bind(path).Get(rt.R().TODOContext(), nil)
+ e, err := s.store.BindObject(path).Get(rt.R().TODOContext())
if err != nil {
return nil
}
@@ -125,7 +125,7 @@
// ServeHTTP is the main HTTP handler.
func (s *server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path
- e, err := s.store.Bind(path).Get(rt.R().TODOContext(), nil)
+ e, err := s.store.BindObject(path).Get(rt.R().TODOContext())
if err != nil {
msg := fmt.Sprintf("<html><body><h1>%s</h1><h2>Error: %s</h2></body></html>",
html.EscapeString(path),
diff --git a/examples/todos/test/store_test.go b/examples/todos/test/store_test.go
index f1736cf..136c025 100644
--- a/examples/todos/test/store_test.go
+++ b/examples/todos/test/store_test.go
@@ -20,9 +20,9 @@
bb "veyron/lib/testutil/blackbox"
+ "veyron2/naming"
"veyron2/rt"
"veyron2/storage"
- "veyron2/storage/vstore/primitives"
"veyron2/vom"
)
@@ -95,9 +95,9 @@
////////////////////////////////////////////////////////////////////////////////
// Type-specific helpers
-func getList(t *testing.T, st storage.Store, tr storage.Transaction, path string) *List {
+func getList(t *testing.T, st storage.Store, path string) *List {
_, file, line, _ := runtime.Caller(1)
- v := get(t, st, tr, path)
+ v := get(t, st, path)
res, ok := v.(*List)
if !ok {
t.Fatalf("%s(%d): %s: not a List: %v", file, line, path, v)
@@ -105,9 +105,9 @@
return res
}
-func getTodo(t *testing.T, st storage.Store, tr storage.Transaction, path string) *Todo {
+func getTodo(t *testing.T, st storage.Store, path string) *Todo {
_, file, line, _ := runtime.Caller(1)
- v := get(t, st, tr, path)
+ v := get(t, st, path)
res, ok := v.(*Todo)
if !ok {
t.Fatalf("%s(%d): %s: not a Todo: %v", file, line, path, v)
@@ -120,42 +120,41 @@
func testTodos(t *testing.T, st storage.Store) {
ctx := rt.R().NewContext()
+
// Create lists.
{
// NOTE(sadovsky): Currently, we can't put /x/y until we put / and /x.
- tr := primitives.NewTransaction(ctx)
- put(t, st, tr, "/", newDir())
- put(t, st, tr, "/lists", newDir())
- put(t, st, tr, "/lists/drinks", newList())
- put(t, st, tr, "/lists/snacks", newList())
- commit(t, tr)
+ tname := createTransaction(t, st, "")
+ put(t, st, tname, newDir())
+ put(t, st, naming.Join(tname, "lists"), newDir())
+ put(t, st, naming.Join(tname, "lists/drinks"), newList())
+ put(t, st, naming.Join(tname, "lists/snacks"), newList())
+ commit(t, st, tname)
}
// Add some todos.
{
- tr := primitives.NewTransaction(ctx)
+ tname := createTransaction(t, st, "")
// NOTE(sadovsky): It feels awkward to create my own names (ids) for these
// Todo objects. I'd like some way to create them in some "directory"
// without explicitly naming them. I.e. in this case I want to think of the
// directory as a list, not a map.
- put(t, st, tr, "/lists/drinks/Todos/@", newTodo("milk"))
- put(t, st, tr, "/lists/drinks/Todos/@", newTodo("beer"))
- put(t, st, tr, "/lists/snacks/Todos/@", newTodo("chips"))
- commit(t, tr)
+ put(t, st, naming.Join(tname, "lists/drinks/Todos/@"), newTodo("milk"))
+ put(t, st, naming.Join(tname, "lists/drinks/Todos/@"), newTodo("beer"))
+ put(t, st, naming.Join(tname, "lists/snacks/Todos/@"), newTodo("chips"))
+ commit(t, st, tname)
}
// Verify some of the photos.
{
- tr := primitives.NewTransaction(ctx)
- todo := getTodo(t, st, tr, "/lists/drinks/Todos/0")
+ todo := getTodo(t, st, "/lists/drinks/Todos/0")
if todo.Text != "milk" {
t.Errorf("Expected %q, got %q", "milk", todo.Text)
}
}
{
- tr := primitives.NewTransaction(ctx)
- todo := getTodo(t, st, tr, "/lists/snacks/Todos/0")
+ todo := getTodo(t, st, "/lists/snacks/Todos/0")
if todo.Text != "chips" {
t.Errorf("Expected %q, got %q", "chips", todo.Text)
}
@@ -163,27 +162,24 @@
// Move a todo item from one list to another.
{
- tr := primitives.NewTransaction(ctx)
- todo := getTodo(t, st, tr, "/lists/drinks/Todos/1")
+ tname := createTransaction(t, st, "")
+ todo := getTodo(t, st, naming.Join(tname, "lists/drinks/Todos/1"))
// NOTE(sadovsky): Remove works for map entries, but not yet for slices.
// Instead, we read the list, prune it, and write it back.
//remove(t, st, tr, "/lists/drinks/Todos/1")
- list := getList(t, st, tr, "/lists/drinks")
+ list := getList(t, st, naming.Join(tname, "lists/drinks"))
list.Todos = list.Todos[:1]
- put(t, st, tr, "lists/drinks", list)
- put(t, st, tr, "/lists/snacks/Todos/@", todo)
- commit(t, tr)
+ put(t, st, naming.Join(tname, "lists/drinks"), list)
+ put(t, st, naming.Join(tname, "lists/snacks/Todos/@"), todo)
+ commit(t, st, tname)
}
// Verify that the original todo is no longer there.
// TODO(sadovsky): Use queries to verify that both lists have changed.
{
- tr := primitives.NewTransaction(ctx)
- // Note, this will be much prettier in veyron2.
- _, file, line, _ := runtime.Caller(1)
path := "/lists/drinks/1"
- if _, err := st.Bind(path).Get(ctx, tr); err == nil {
- t.Fatalf("%s(%d): got removed object %s", file, line, path)
+ if _, err := st.BindObject(path).Get(ctx); err == nil {
+ t.Fatalf("Got removed object: %s", path)
}
}
}
diff --git a/examples/todos/test/store_util.go b/examples/todos/test/store_util.go
index aff92b3..e08f711 100644
--- a/examples/todos/test/store_util.go
+++ b/examples/todos/test/store_util.go
@@ -4,37 +4,48 @@
"runtime"
"testing"
+ "veyron2/naming"
"veyron2/rt"
"veyron2/storage"
)
-func get(t *testing.T, st storage.Store, tr storage.Transaction, path string) interface{} {
+func createTransaction(t *testing.T, st storage.Store, name string) string {
_, file, line, _ := runtime.Caller(1)
- e, err := st.Bind(path).Get(rt.R().NewContext(), tr)
+ tid, err := st.BindTransactionRoot(name).CreateTransaction(rt.R().NewContext())
if err != nil {
- t.Fatalf("%s(%d): can't get %s: %s", file, line, path, err)
+ t.Fatalf("%s(%d): can't create transaction %s: %s", file, line, name, err)
+ }
+ return naming.Join(name, tid)
+}
+
+func get(t *testing.T, st storage.Store, name string) interface{} {
+ _, file, line, _ := runtime.Caller(1)
+ e, err := st.BindObject(name).Get(rt.R().NewContext())
+ if err != nil {
+ t.Fatalf("%s(%d): can't get %s: %s", file, line, name, err)
}
return e.Value
}
-func put(t *testing.T, st storage.Store, tr storage.Transaction, path string, v interface{}) storage.ID {
+func put(t *testing.T, st storage.Store, name string, v interface{}) storage.ID {
_, file, line, _ := runtime.Caller(1)
- stat, err := st.Bind(path).Put(rt.R().NewContext(), tr, v)
+ stat, err := st.BindObject(name).Put(rt.R().NewContext(), v)
if err != nil || !stat.ID.IsValid() {
- t.Fatalf("%s(%d): can't put %s: %s", file, line, path, err)
+ t.Fatalf("%s(%d): can't put %s: %s", file, line, name, err)
}
return stat.ID
}
-func remove(t *testing.T, st storage.Store, tr storage.Transaction, path string) {
- if err := st.Bind(path).Remove(rt.R().NewContext(), tr); err != nil {
- _, file, line, _ := runtime.Caller(1)
- t.Errorf("%s(%d): can't remove %s: %s", file, line, path, err)
+func remove(t *testing.T, st storage.Store, name string) {
+ _, file, line, _ := runtime.Caller(1)
+ if err := st.BindObject(name).Remove(rt.R().NewContext()); err != nil {
+ t.Errorf("%s(%d): can't remove %s: %s", file, line, name, err)
}
}
-func commit(t *testing.T, tr storage.Transaction) {
- if err := tr.Commit(rt.R().NewContext()); err != nil {
- t.Fatalf("Transaction aborted: %s", err)
+func commit(t *testing.T, st storage.Store, name string) {
+ _, file, line, _ := runtime.Caller(1)
+ if err := st.BindTransaction(name).Commit(rt.R().NewContext()); err != nil {
+ t.Fatalf("%s(%d): commit failed: %s", file, line, err)
}
}
diff --git a/examples/todos/todos_init/main.go b/examples/todos/todos_init/main.go
index 44db7c2..7013a0c 100644
--- a/examples/todos/todos_init/main.go
+++ b/examples/todos/todos_init/main.go
@@ -1,6 +1,6 @@
// todos_init is a tool to initialize the store with an initial database. This
// is really for demo purposes; in a real database, the contents would be
-// persistant.
+// persistent.
//
// The data is loaded from a JSON file, todos_init/data.json.
//
@@ -20,11 +20,11 @@
"veyron/examples/todos/schema"
"veyron2"
+ "veyron2/naming"
"veyron2/rt"
"veyron2/security"
"veyron2/storage"
"veyron2/storage/vstore"
- "veyron2/storage/vstore/primitives"
"veyron2/vlog"
)
@@ -60,8 +60,8 @@
// state is the initial store state.
type state struct {
- store storage.Store
- transaction storage.Transaction
+ store storage.Store
+ tname string // Current transaction name; nil if there's no transaction.
}
// newState returns a fresh state.
@@ -74,7 +74,7 @@
func (st *state) put(path string, v interface{}) {
vlog.Infof("Storing %q = %+v", path, v)
st.makeParentDirs(path)
- if _, err := st.store.Bind(path).Put(rt.R().TODOContext(), st.transaction, v); err != nil {
+ if _, err := st.store.BindObject(naming.Join(st.tname, path)).Put(rt.R().TODOContext(), v); err != nil {
vlog.Errorf("put failed: %s: %s", path, err)
return
}
@@ -86,9 +86,9 @@
l := strings.Split(path, "/")
for i, _ := range l {
prefix := filepath.Join(l[:i]...)
- o := st.store.Bind(prefix)
- if _, err := o.Get(rt.R().TODOContext(), st.transaction); err != nil {
- if _, err := o.Put(rt.R().TODOContext(), st.transaction, &schema.Dir{}); err != nil {
+ o := st.store.BindObject(naming.Join(st.tname, prefix))
+ if _, err := o.Get(rt.R().TODOContext()); err != nil {
+ if _, err := o.Put(rt.R().TODOContext(), &schema.Dir{}); err != nil {
vlog.Errorf("Error creating parent %q: %s", prefix, err)
}
}
@@ -97,15 +97,23 @@
// newTransaction starts a new transaction.
func (st *state) newTransaction() {
- st.transaction = primitives.NewTransaction(rt.R().TODOContext())
+ tid, err := st.store.BindTransactionRoot("").CreateTransaction(rt.R().TODOContext())
+ if err != nil {
+ vlog.Fatalf("Failed to create transaction: %s", err)
+ }
+ st.tname = tid // Transaction is rooted at "", so tname == tid.
}
// commit commits the current transaction.
func (st *state) commit() {
- if err := st.transaction.Commit(rt.R().TODOContext()); err != nil {
+ if st.tname == "" {
+ vlog.Fatalf("No transaction to commit")
+ }
+ err := st.store.BindTransaction(st.tname).Commit(rt.R().TODOContext())
+ st.tname = ""
+ if err != nil {
vlog.Errorf("Failed to commit transaction: %s", err)
}
- st.transaction = nil
}
// storeList saves a schema.List to the store with name /lists/<Name>, and also
diff --git a/services/mgmt/application/impl/invoker.go b/services/mgmt/application/impl/invoker.go
index 38e1c28..b0d376b 100644
--- a/services/mgmt/application/impl/invoker.go
+++ b/services/mgmt/application/impl/invoker.go
@@ -6,9 +6,9 @@
"strings"
"veyron2/ipc"
+ "veyron2/naming"
"veyron2/services/mgmt/application"
"veyron2/storage"
- "veyron2/storage/vstore/primitives"
"veyron2/vlog"
)
@@ -51,13 +51,13 @@
type dir struct{}
// makeParentNodes creates the parent nodes if they do not already exist.
-func makeParentNodes(context ipc.ServerContext, store storage.Store, transaction storage.Transaction, path string) error {
+func makeParentNodes(context ipc.ServerContext, store storage.Store, path string) error {
pathComponents := storage.ParsePath(path)
for i := 0; i < len(pathComponents); i++ {
name := pathComponents[:i].String()
- object := store.Bind(name)
- if _, err := object.Get(context, transaction); err != nil {
- if _, err := object.Put(context, transaction, &dir{}); err != nil {
+ object := store.BindObject(name)
+ if _, err := object.Get(context); err != nil {
+ if _, err := object.Put(context, &dir{}); err != nil {
return errOperationFailed
}
}
@@ -77,8 +77,7 @@
}
for _, profile := range profiles {
path := path.Join("/applications", name, profile, version)
- object := i.store.Bind(path)
- entry, err := object.Get(context, nil)
+ entry, err := i.store.BindObject(path).Get(context)
if err != nil {
continue
}
@@ -100,25 +99,29 @@
if version == "" {
return errInvalidSuffix
}
- transaction := primitives.NewTransaction(context)
+ // Transaction is rooted at "", so tname == tid.
+ tname, err := i.store.BindTransactionRoot("").CreateTransaction(context)
+ if err != nil {
+ return err
+ }
var entry storage.Stat
for _, profile := range profiles {
- path := path.Join("/applications", name, profile, version)
- if err := makeParentNodes(context, i.store, transaction, path); err != nil {
+ path := naming.Join(tname, path.Join("/applications", name, profile, version))
+ if err := makeParentNodes(context, i.store, path); err != nil {
return err
}
- object := i.store.Bind(path)
+ object := i.store.BindObject(path)
if !entry.ID.IsValid() {
- if entry, err = object.Put(context, transaction, envelope); err != nil {
+ if entry, err = object.Put(context, envelope); err != nil {
return errOperationFailed
}
} else {
- if _, err := object.Put(context, transaction, entry.ID); err != nil {
+ if _, err := object.Put(context, entry.ID); err != nil {
return errOperationFailed
}
}
}
- if err := transaction.Commit(context); err != nil {
+ if err := i.store.BindTransaction(tname).Commit(context); err != nil {
return errOperationFailed
}
return nil
@@ -130,23 +133,27 @@
if err != nil {
return err
}
- transaction := primitives.NewTransaction(context)
- path := path.Join("/applications", name, profile)
+ // Transaction is rooted at "", so tname == tid.
+ tname, err := i.store.BindTransactionRoot("").CreateTransaction(context)
+ if err != nil {
+ return err
+ }
+ path := naming.Join(tname, path.Join("/applications", name, profile))
if version != "" {
path += "/" + version
}
- object := i.store.Bind(path)
- found, err := object.Exists(context, transaction)
+ object := i.store.BindObject(path)
+ found, err := object.Exists(context)
if err != nil {
return errOperationFailed
}
if !found {
return errNotFound
}
- if err := object.Remove(context, transaction); err != nil {
+ if err := object.Remove(context); err != nil {
return errOperationFailed
}
- if err := transaction.Commit(context); err != nil {
+ if err := i.store.BindTransaction(tname).Commit(context); err != nil {
return errOperationFailed
}
return nil
diff --git a/services/mgmt/profile/impl/invoker.go b/services/mgmt/profile/impl/invoker.go
index aa95dcb..e1e2b4d 100644
--- a/services/mgmt/profile/impl/invoker.go
+++ b/services/mgmt/profile/impl/invoker.go
@@ -7,8 +7,8 @@
"veyron/services/mgmt/profile"
"veyron2/ipc"
+ "veyron2/naming"
"veyron2/storage"
- "veyron2/storage/vstore/primitives"
"veyron2/vlog"
)
@@ -38,13 +38,13 @@
type dir struct{}
// makeParentNodes creates the parent nodes if they do not already exist.
-func makeParentNodes(context ipc.ServerContext, store storage.Store, transaction storage.Transaction, path string) error {
+func makeParentNodes(context ipc.ServerContext, store storage.Store, path string) error {
pathComponents := storage.ParsePath(path)
for i := 0; i < len(pathComponents); i++ {
name := pathComponents[:i].String()
- object := store.Bind(name)
- if _, err := object.Get(context, transaction); err != nil {
- if _, err := object.Put(context, transaction, &dir{}); err != nil {
+ object := store.BindObject(name)
+ if _, err := object.Get(context); err != nil {
+ if _, err := object.Put(context, &dir{}); err != nil {
return errOperationFailed
}
}
@@ -54,16 +54,20 @@
func (i *invoker) Put(context ipc.ServerContext, profile profile.Specification) error {
vlog.VI(0).Infof("%v.Put(%v)", i.suffix, profile)
- transaction := primitives.NewTransaction(context)
- path := path.Join("/profiles", i.suffix)
- if err := makeParentNodes(context, i.store, transaction, path); err != nil {
+ // Transaction is rooted at "", so tname == tid.
+ tname, err := i.store.BindTransactionRoot("").CreateTransaction(context)
+ if err != nil {
return err
}
- object := i.store.Bind(path)
- if _, err := object.Put(context, transaction, profile); err != nil {
+ path := naming.Join(tname, path.Join("/profiles", i.suffix))
+ if err := makeParentNodes(context, i.store, path); err != nil {
+ return err
+ }
+ object := i.store.BindObject(path)
+ if _, err := object.Put(context, profile); err != nil {
return errOperationFailed
}
- if err := transaction.Commit(context); err != nil {
+ if err := i.store.BindTransaction(tname).Commit(context); err != nil {
return errOperationFailed
}
return nil
@@ -71,20 +75,24 @@
func (i *invoker) Remove(context ipc.ServerContext) error {
vlog.VI(0).Infof("%v.Remove()", i.suffix)
- transaction := primitives.NewTransaction(context)
- path := path.Join("/profiles", i.suffix)
- object := i.store.Bind(path)
- found, err := object.Exists(context, transaction)
+ // Transaction is rooted at "", so tname == tid.
+ tname, err := i.store.BindTransactionRoot("").CreateTransaction(context)
+ if err != nil {
+ return err
+ }
+ path := naming.Join(tname, path.Join("/profiles", i.suffix))
+ object := i.store.BindObject(path)
+ found, err := object.Exists(context)
if err != nil {
return errOperationFailed
}
if !found {
return errNotFound
}
- if err := object.Remove(context, transaction); err != nil {
+ if err := object.Remove(context); err != nil {
return errOperationFailed
}
- if err := transaction.Commit(context); err != nil {
+ if err := i.store.BindTransaction(tname).Commit(context); err != nil {
return errOperationFailed
}
return nil
@@ -95,8 +103,7 @@
func (i *invoker) lookup(context ipc.ServerContext) (profile.Specification, error) {
empty := profile.Specification{}
path := path.Join("/profiles", i.suffix)
- object := i.store.Bind(path)
- entry, err := object.Get(context, nil)
+ entry, err := i.store.BindObject(path).Get(context)
if err != nil {
return empty, errNotFound
}
diff --git a/services/security/discharger/revoker.go b/services/security/discharger/revoker.go
index 4f32574..e945a37 100644
--- a/services/security/discharger/revoker.go
+++ b/services/security/discharger/revoker.go
@@ -16,7 +16,6 @@
"veyron2/security"
"veyron2/storage"
"veyron2/storage/vstore"
- "veyron2/storage/vstore/primitives"
"veyron2/vlog"
"veyron2/vom"
)
@@ -40,11 +39,9 @@
func (cav revocationCaveat) Validate(security.Context) error {
// TODO(ashankar,mattr): Figure out how to get the context of an existing RPC here
rctx := rt.R().NewContext()
- revocation := revocationService.store.Bind(naming.Join(revocationService.pathInStore,
- hex.EncodeToString(cav[:])))
- tx := primitives.NewTransaction(rctx)
- defer tx.Abort(rctx)
- exists, err := revocation.Exists(rctx, tx)
+ revocation := revocationService.store.BindObject(
+ naming.Join(revocationService.pathInStore, hex.EncodeToString(cav[:])))
+ exists, err := revocation.Exists(rctx)
if err != nil {
return err
}
@@ -69,13 +66,11 @@
func (revoceationService *revocationServiceT) Revoke(ctx ipc.ServerContext, caveatPreimage ssecurity.RevocationToken) error {
caveatNonce := sha256.Sum256(caveatPreimage[:])
- tx := primitives.NewTransaction(ctx)
- revocation := revocationService.store.Bind(naming.Join(revocationService.pathInStore, hex.EncodeToString(caveatNonce[:])))
- if _, err := revocation.Put(ctx, tx, caveatPreimage[:]); err != nil {
- tx.Abort(ctx)
+ revocation := revocationService.store.BindObject(naming.Join(revocationService.pathInStore, hex.EncodeToString(caveatNonce[:])))
+ if _, err := revocation.Put(ctx, caveatPreimage[:]); err != nil {
return err
}
- return tx.Commit(ctx)
+ return nil
}
// NewRevoker returns a new revoker service that can be passed to a dispatcher.
@@ -94,7 +89,11 @@
}
rctx := rt.R().NewContext()
- tx := primitives.NewTransaction(rctx)
+ // Transaction is rooted at "", so tname == tid.
+ tname, err := revocationService.store.BindTransactionRoot("").CreateTransaction(rctx)
+ if err != nil {
+ return nil, err
+ }
// Create parent directories for the revoker root, if necessary
// TODO(tilaks,andreser): provide a `mkdir -p` equivalent in store
@@ -103,17 +102,17 @@
for i := 0; i <= len(l); i++ {
fmt.Println(i, filepath.Join(l[:i]...))
prefix := filepath.Join(l[:i]...)
- o := revocationService.store.Bind(prefix)
- if exist, err := o.Exists(rctx, tx); err != nil {
+ o := revocationService.store.BindObject(naming.Join(tname, prefix))
+ if exist, err := o.Exists(rctx); err != nil {
vlog.Infof("Error checking existence at %q: %s", prefix, err)
} else if !exist {
- if _, err := o.Put(rctx, tx, &Dir{}); err != nil {
+ if _, err := o.Put(rctx, &Dir{}); err != nil {
vlog.Infof("Error creating directory %q: %s", prefix, err)
}
}
}
- if err := tx.Commit(rctx); err != nil {
- vlog.Fatalf("Commit creation of revocer root et %s: %s", pathInStore, err)
+ if err := revocationService.store.BindTransaction(tname).Commit(rctx); err != nil {
+ vlog.Fatalf("Failed to commit creation of revoker root at %s: %s", pathInStore, err)
}
return ssecurity.NewServerRevoker(revocationService.revocationServiceT), nil
}
diff --git a/services/store/memstore/object.go b/services/store/memstore/object.go
index f130c7d..3c7d0a8 100644
--- a/services/store/memstore/object.go
+++ b/services/store/memstore/object.go
@@ -14,9 +14,9 @@
// path. This means that in different transactions, the object may refer to
// different values with different store.IDs.
//
-// TODO(jyh): Perhaps a more sensible alternative is to resolve the path at
-// Bind time, and have the object then refer to the value by storage.ID. However,
-// there are two problems with that,
+// TODO(jyh): Perhaps a more sensible alternative is to resolve the path at Bind
+// time, and have the object then refer to the value by storage.ID. However,
+// there are two problems with that:
//
// - We can't resolve the path for objects that don't yet exist.
// - Bind is transactionless, so the path resolution wouldn't be consistent.
@@ -107,7 +107,7 @@
return stream, nil
}
-// Glob returns the sequence of names that match the given pattern.
+// Glob returns names that match the given pattern.
func (o *object) Glob(pid security.PublicID, trans service.Transaction, pattern string) (service.GlobStream, error) {
tr, _, err := o.store.getTransaction(trans)
if err != nil {
diff --git a/services/store/raw/service.vdl b/services/store/raw/service.vdl
index de0d6b4..3384837 100644
--- a/services/store/raw/service.vdl
+++ b/services/store/raw/service.vdl
@@ -1,7 +1,8 @@
-/*
-Package raw defines a raw interface for the Veyron store.
-The raw interface supports synchronizing with remote stores by transporting Mutations.
-*/
+// Package raw defines a raw interface for the Veyron store.
+//
+// The raw interface supports synchronizing with remote stores by transporting
+// Mutations.
+
package raw
import (
diff --git a/services/store/raw/service.vdl.go b/services/store/raw/service.vdl.go
index e178cb9..d669a23 100644
--- a/services/store/raw/service.vdl.go
+++ b/services/store/raw/service.vdl.go
@@ -1,10 +1,6 @@
// This file was auto-generated by the veyron vdl tool.
// Source: service.vdl
-/*
-Package raw defines a raw interface for the Veyron store.
-The raw interface supports synchronizing with remote stores by transporting Mutations.
-*/
package raw
import (
diff --git a/services/store/server/object.go b/services/store/server/object.go
index dd33bb6..5f0c8a8 100644
--- a/services/store/server/object.go
+++ b/services/store/server/object.go
@@ -1,5 +1,8 @@
package server
+// This file defines object, which implements the server-side Object API from
+// veyron2/services/store/service.vdl.
+
import (
"veyron/services/store/service"
@@ -14,8 +17,9 @@
)
type object struct {
- name string
+ name string // will never contain a transaction id
obj service.Object
+ tid transactionID // may be nullTransactionID
server *Server
}
@@ -84,9 +88,25 @@
return uattrs
}
+// CreateTransaction creates a transaction.
+func (o *object) CreateTransaction(ctx ipc.ServerContext, opts []vdlutil.Any) (string, error) {
+ if o.tid != nullTransactionID {
+ return "", errNestedTransaction
+ }
+ return o.server.createTransaction(ctx, o.name)
+}
+
+func (o *object) Commit(ctx ipc.ServerContext) error {
+ return o.server.commitTransaction(ctx, o.tid)
+}
+
+func (o *object) Abort(ctx ipc.ServerContext) error {
+ return o.server.abortTransaction(ctx, o.tid)
+}
+
// Exists returns true iff the Entry has a value.
-func (o *object) Exists(ctx ipc.ServerContext, tid store.TransactionID) (bool, error) {
- t, err := o.server.findTransaction(ctx, tid)
+func (o *object) Exists(ctx ipc.ServerContext) (bool, error) {
+ t, err := o.server.findTransaction(ctx, o.tid)
if err != nil {
return false, err
}
@@ -96,8 +116,8 @@
// Get returns the value for the Object. The value returned is from the
// most recent mutation of the entry in the Transaction, or from the
// Transaction's snapshot if there is no mutation.
-func (o *object) Get(ctx ipc.ServerContext, tid store.TransactionID) (store.Entry, error) {
- t, err := o.server.findTransaction(ctx, tid)
+func (o *object) Get(ctx ipc.ServerContext) (store.Entry, error) {
+ t, err := o.server.findTransaction(ctx, o.tid)
if err != nil {
return nullEntry, err
}
@@ -109,8 +129,8 @@
}
// Put modifies the value of the Object.
-func (o *object) Put(ctx ipc.ServerContext, tid store.TransactionID, val vdlutil.Any) (store.Stat, error) {
- t, err := o.server.findTransaction(ctx, tid)
+func (o *object) Put(ctx ipc.ServerContext, val vdlutil.Any) (store.Stat, error) {
+ t, err := o.server.findTransaction(ctx, o.tid)
if err != nil {
return nullStat, err
}
@@ -122,8 +142,8 @@
}
// Remove removes the Object.
-func (o *object) Remove(ctx ipc.ServerContext, tid store.TransactionID) error {
- t, err := o.server.findTransaction(ctx, tid)
+func (o *object) Remove(ctx ipc.ServerContext) error {
+ t, err := o.server.findTransaction(ctx, o.tid)
if err != nil {
return err
}
@@ -133,8 +153,8 @@
// SetAttr changes the attributes of the entry, such as permissions and
// replication groups. Attributes are associated with the value, not the
// path.
-func (o *object) SetAttr(ctx ipc.ServerContext, tid store.TransactionID, attrs []vdlutil.Any) error {
- t, err := o.server.findTransaction(ctx, tid)
+func (o *object) SetAttr(ctx ipc.ServerContext, attrs []vdlutil.Any) error {
+ t, err := o.server.findTransaction(ctx, o.tid)
if err != nil {
return err
}
@@ -146,8 +166,8 @@
}
// Stat returns entry info.
-func (o *object) Stat(ctx ipc.ServerContext, tid store.TransactionID) (store.Stat, error) {
- t, err := o.server.findTransaction(ctx, tid)
+func (o *object) Stat(ctx ipc.ServerContext) (store.Stat, error) {
+ t, err := o.server.findTransaction(ctx, o.tid)
if err != nil {
return nullStat, err
}
@@ -159,8 +179,8 @@
}
// Query returns a sequence of objects that match the given query.
-func (o *object) Query(ctx ipc.ServerContext, tid store.TransactionID, q query.Query, stream store.ObjectServiceQueryStream) error {
- t, err := o.server.findTransaction(ctx, tid)
+func (o *object) Query(ctx ipc.ServerContext, q query.Query, stream store.ObjectServiceQueryStream) error {
+ t, err := o.server.findTransaction(ctx, o.tid)
if err != nil {
return err
}
@@ -189,12 +209,7 @@
// Glob streams a series of names that match the given pattern.
func (o *object) Glob(ctx ipc.ServerContext, pattern string, stream mounttable.GlobableServiceGlobStream) error {
- return o.GlobT(ctx, nullTransactionID, pattern, &globStreamAdapter{stream})
-}
-
-// Glob streams a series of names that match the given pattern.
-func (o *object) GlobT(ctx ipc.ServerContext, tid store.TransactionID, pattern string, stream store.ObjectServiceGlobTStream) error {
- t, err := o.server.findTransaction(ctx, tid)
+ t, err := o.server.findTransaction(ctx, o.tid)
if err != nil {
return err
}
@@ -202,11 +217,12 @@
if err != nil {
return err
}
+ gsa := &globStreamAdapter{stream}
for ; it.IsValid(); it.Next() {
if ctx.IsClosed() {
break
}
- if err := stream.Send(it.Name()); err != nil {
+ if err := gsa.Send(it.Name()); err != nil {
return err
}
}
diff --git a/services/store/server/server.go b/services/store/server/server.go
index 8eb4478..8b72fc4 100644
--- a/services/store/server/server.go
+++ b/services/store/server/server.go
@@ -1,8 +1,15 @@
// Package server implements a storage service.
+
package server
+// This file defines Server, which implements the server-side Store API from
+// veyron2/services/store/service.vdl.
+
import (
+ "fmt"
+ "math/rand"
"reflect"
+ "strconv"
"strings"
"sync"
"time"
@@ -15,12 +22,11 @@
"veyron2/ipc"
"veyron2/security"
"veyron2/services/store"
- "veyron2/vdl/vdlutil"
"veyron2/verror"
)
const (
- // transactionMAxLifetime is the maximum duration before a transaction will
+ // transactionMaxLifetime is the maximum duration before a transaction will
// be garbage collected.
//
// TODO(jyh): This should probably be a configuration parameter.
@@ -28,28 +34,28 @@
)
var (
- // Server implements the StoreService interface.
_ store.StoreService = (*Server)(nil)
- nullTransactionID store.TransactionID
-
- errTransactionAlreadyExists = verror.Existsf("transaction already exists")
- errTransactionDoesNotExist = verror.NotFoundf("transaction does not exist")
+ errNestedTransaction = verror.BadArgf("cannot create a nested Transaction")
+ // Note, this can happen e.g. due to expiration.
+ errTransactionDoesNotExist = verror.NotFoundf("transaction does not exist")
// Transaction exists, but may not be used by the caller.
errPermissionDenied = verror.NotAuthorizedf("permission denied")
+
+ rng = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+
+ nullTransactionID transactionID
)
-// Server stores the dictionary of all media items. It has a scanner.Scanner
-// for collecting files from the filesystem. For each file, a FileService is
-// registered to serve the file.
+// Server implements Store and uses memstore internally.
type Server struct {
mutex sync.RWMutex
// store is the actual store implementation.
store service.Store
- // transactions are the set of active transactions.
- transactions map[store.TransactionID]*transaction
+ // transactions is the set of active transactions.
+ transactions map[transactionID]*transaction
// Transaction garbage collection.
pending sync.WaitGroup
@@ -60,6 +66,13 @@
watcher service.Watcher
}
+// transactionID is an internal transaction identifier chosen by the server.
+//
+// TODO(jyh): Consider using a larger identifier space to reduce chance of
+// collisions. (Note, createTransaction handles collisions when generating
+// transactionIDs.)
+type transactionID uint64
+
// transactionContext defines the context in which a transaction is used. A
// transaction may be used only in the context that created it.
// transactionContext weakly identifies a session by the local and remote
@@ -67,9 +80,11 @@
// TODO(tilaks): Use the local and remote addresses to identify the session.
// Does a session with a mobile device break if the remote address changes?
type transactionContext interface {
- // LocalID returns the PublicID of the principal at the local end of the request.
+ // LocalID returns the PublicID of the principal at the local end of the
+ // request.
LocalID() security.PublicID
- // RemoteID returns the PublicID of the principal at the remote end of the request.
+ // RemoteID returns the PublicID of the principal at the remote end of the
+ // request.
RemoteID() security.PublicID
}
@@ -97,7 +112,7 @@
}
s := &Server{
store: mstore,
- transactions: make(map[store.TransactionID]*transaction),
+ transactions: make(map[transactionID]*transaction),
ticker: time.NewTicker(time.Second),
closed: make(chan struct{}),
watcher: mwatcher,
@@ -127,14 +142,82 @@
}
}
-// findTransaction returns the transaction for the TransactionID.
-func (s *Server) findTransaction(ctx transactionContext, id store.TransactionID) (service.Transaction, error) {
+// findTransactionComponent returns the (begin, end) offsets of the "$tid.*"
+// component in the given object name, or (-1, -1) if oname does not contain a
+// transaction component.
+func findTransactionComponent(oname string) (int, int) {
+ begin := 0
+ if !strings.HasPrefix(oname, "$tid") {
+ begin = strings.Index(oname, "/$tid")
+ }
+ if begin == -1 {
+ return -1, -1
+ }
+ end := strings.Index(oname[begin+1:], "/")
+ if end == -1 {
+ end = len(oname)
+ } else {
+ end += begin + 1
+ }
+ return begin, end
+}
+
+// TODO(sadovsky): One of the following:
+// - Reserve prefix string "$tid." for internal use.
+// - Reserve prefix char "$" for internal use.
+// - Require users to escape prefix char "$" when they are referring to their
+// own data, e.g. "\$foo".
+func makeTransactionComponent(id transactionID) string {
+ return fmt.Sprintf("$tid.%d", id)
+}
+
+// stripTransactionComponent returns the given object name with its "$tid.*"
+// component removed, and also returns the stripped transactionID.
+// Examples:
+// "/foo/$tid.123/bar" => {"/foo/bar", transactionID(123)}
+// "/foo/bar" => {"/foo/bar", nullTransactionID}
+func stripTransactionComponent(oname string) (string, transactionID, error) {
+ begin, end := findTransactionComponent(oname)
+ if begin == -1 {
+ return oname, nullTransactionID, nil
+ }
+ tc := oname[begin:end]
+ id, err := strconv.ParseInt(tc[strings.LastIndex(tc, ".")+1:], 10, 64)
+ if err != nil {
+ return "", nullTransactionID, fmt.Errorf("Failed to extract id from %q", tc)
+ }
+ return oname[:begin] + oname[end:], transactionID(id), nil
+}
+
+func (s *Server) createTransaction(ctx transactionContext, oname string) (string, error) {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ var id transactionID
+ for {
+ id = transactionID(rng.Int63())
+ _, ok := s.transactions[id]
+ if !ok {
+ break
+ }
+ }
+ info := &transaction{
+ trans: memstore.NewTransaction(),
+ expires: time.Now().Add(transactionMaxLifetime),
+ creatorCtx: ctx,
+ }
+ s.transactions[id] = info
+ return makeTransactionComponent(id), nil
+}
+
+// findTransaction returns the transaction for the given transaction ID.
+func (s *Server) findTransaction(ctx transactionContext, id transactionID) (service.Transaction, error) {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.findTransactionLocked(ctx, id)
}
-func (s *Server) findTransactionLocked(ctx transactionContext, id store.TransactionID) (service.Transaction, error) {
+func (s *Server) findTransactionLocked(ctx transactionContext, id transactionID) (service.Transaction, error) {
if id == nullTransactionID {
return nil, nil
}
@@ -150,6 +233,43 @@
return info.trans, nil
}
+// Commit commits the changes in the transaction to the store. The
+// operation is atomic, so all mutations are performed, or none. Returns an
+// error if the transaction aborted.
+func (s *Server) commitTransaction(ctx transactionContext, id transactionID) error {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ t, err := s.findTransactionLocked(ctx, id)
+ if err != nil {
+ return err
+ }
+ if t == nil {
+ return errTransactionDoesNotExist
+ }
+ err = t.Commit()
+ delete(s.transactions, id)
+ return err
+}
+
+// Abort discards a transaction. This is an optimization; transactions
+// eventually time out and get discarded. However, live transactions
+// consume resources, so if you know that you won't be using a transaction
+// anymore, you should discard it explicitly.
+func (s *Server) abortTransaction(ctx transactionContext, id transactionID) error {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ t, err := s.findTransactionLocked(ctx, id)
+ if err != nil {
+ return err
+ }
+ if t == nil {
+ return errTransactionDoesNotExist
+ }
+ err = t.Abort()
+ delete(s.transactions, id)
+ return err
+}
+
func (t *transaction) matchesContext(ctx transactionContext) bool {
creatorCtx := t.creatorCtx
return membersEqual(creatorCtx.LocalID().Names(), ctx.LocalID().Names()) &&
@@ -193,60 +313,6 @@
}
}
-// CreateTransaction creates a transaction.
-func (s *Server) CreateTransaction(ctx ipc.ServerContext, id store.TransactionID, opts []vdlutil.Any) error {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- info, ok := s.transactions[id]
- if ok {
- return errTransactionAlreadyExists
- }
- info = &transaction{
- trans: memstore.NewTransaction(),
- expires: time.Now().Add(transactionMaxLifetime),
- creatorCtx: ctx,
- }
- s.transactions[id] = info
- return nil
-}
-
-// Commit commits the changes in the transaction to the store. The
-// operation is atomic, so all mutations are performed, or none. Returns an
-// error if the transaction aborted.
-func (s *Server) Commit(ctx ipc.ServerContext, id store.TransactionID) error {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- t, err := s.findTransactionLocked(ctx, id)
- if err != nil {
- return err
- }
- if t == nil {
- return errTransactionDoesNotExist
- }
- err = t.Commit()
- delete(s.transactions, id)
- return err
-}
-
-// Abort discards a transaction.
-func (s *Server) Abort(ctx ipc.ServerContext, id store.TransactionID) error {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- t, err := s.findTransactionLocked(ctx, id)
- if err != nil {
- return err
- }
- if t == nil {
- return errTransactionDoesNotExist
- }
- err = t.Abort()
- delete(s.transactions, id)
- return err
-}
-
// Watch returns a stream of all changes.
func (s *Server) Watch(ctx ipc.ServerContext, req raw.Request, stream raw.StoreServiceWatchStream) error {
return s.watcher.WatchRaw(ctx, req, stream)
@@ -276,20 +342,37 @@
}
func (d *storeDispatcher) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) {
- return ipc.ReflectInvoker(d.lookupServer(suffix)), d.auth, nil
+ serv, err := d.lookupServer(suffix)
+ if err != nil {
+ return nil, nil, err
+ }
+ return ipc.ReflectInvoker(serv), d.auth, nil
}
-func (d *storeDispatcher) lookupServer(suffix string) interface{} {
+func (d *storeDispatcher) lookupServer(suffix string) (interface{}, error) {
+ // Strip leading "/" if present so that server internals can reliably use
+ // naming.Join(suffix, "foo").
+ suffix = strings.TrimPrefix(suffix, "/")
if strings.HasSuffix(suffix, store.StoreSuffix) {
- return store.NewServerStore(d.s)
+ return store.NewServerStore(d.s), nil
} else if strings.HasSuffix(suffix, raw.RawStoreSuffix) {
- return raw.NewServerStore(d.s)
+ return raw.NewServerStore(d.s), nil
} else {
- o := d.s.lookupObject(suffix)
- return store.NewServerObject(o)
+ // TODO(sadovsky): Create Object, Transaction, and TransactionRoot stubs,
+ // merge them, and return the result. See TODO in
+ // veyron2/services/store/service.vdl.
+ o, err := d.s.lookupObject(suffix)
+ if err != nil {
+ return nil, err
+ }
+ return store.NewServerObject(o), nil
}
}
-func (s *Server) lookupObject(name string) *object {
- return &object{name: name, obj: s.store.Bind(name), server: s}
+func (s *Server) lookupObject(name string) (*object, error) {
+ oname, tid, err := stripTransactionComponent(name)
+ if err != nil {
+ return nil, err
+ }
+ return &object{name: oname, obj: s.store.Bind(oname), tid: tid, server: s}, nil
}
diff --git a/services/store/server/server_test.go b/services/store/server/server_test.go
index e7bd4f7..b509acb 100644
--- a/services/store/server/server_test.go
+++ b/services/store/server/server_test.go
@@ -6,9 +6,11 @@
"log"
"os"
"reflect"
+ "runtime"
"testing"
"time"
+ _ "veyron/lib/testutil" // initialize vlog
watchtesting "veyron/services/store/memstore/testing"
"veyron/services/store/raw"
@@ -18,6 +20,7 @@
"veyron2/services/store"
"veyron2/services/watch"
"veyron2/storage"
+ _ "veyron2/vlog"
"veyron2/vom"
)
@@ -26,8 +29,6 @@
rootName = fmt.Sprintf("%s", rootPublicID)
blessedPublicId security.PublicID = security.FakePublicID("root/blessed")
- nextTransactionID store.TransactionID = 1
-
rootCtx ipc.ServerContext = &testContext{rootPublicID}
blessedCtx ipc.ServerContext = &testContext{blessedPublicId}
)
@@ -105,11 +106,6 @@
return &Dir{}
}
-func newTransaction() store.TransactionID {
- nextTransactionID++
- return nextTransactionID
-}
-
func closeTest(config ServerConfig, s *Server) {
s.Close()
os.Remove(config.DBName)
@@ -132,12 +128,56 @@
return s, closer
}
+func lookupObjectOrDie(s *Server, name string) *object {
+ o, err := s.lookupObject(name)
+ if err != nil {
+ panic(err)
+ }
+ return o
+}
+
+// createTransaction creates a new transaction and returns its store-relative
+// name.
+func createTransaction(t *testing.T, s *Server, ctx ipc.ServerContext, name string) string {
+ _, file, line, _ := runtime.Caller(1)
+ tid, err := lookupObjectOrDie(s, name).CreateTransaction(ctx, nil)
+ if err != nil {
+ t.Fatalf("%s(%d): can't create transaction %s: %s", file, line, name, err)
+ }
+ return naming.Join(name, tid)
+}
+
+func TestLookupInvalidTransactionName(t *testing.T) {
+ s, c := newServer()
+ defer c()
+
+ _, err := s.lookupObject("/$tid.bad/foo")
+ if err == nil {
+ t.Errorf("lookupObject should've failed, but didn't")
+ }
+}
+
+func TestNestedTransactionError(t *testing.T) {
+ s, c := newServer()
+ defer c()
+ tname := createTransaction(t, s, rootCtx, "/")
+ if _, err := lookupObjectOrDie(s, tname).CreateTransaction(rootCtx, nil); err == nil {
+ t.Fatalf("creating nested transaction at %s should've failed, but didn't", tname)
+ }
+ // Try again with a valid object in between the two $tid components;
+ // CreateTransaction should still fail.
+ lookupObjectOrDie(s, tname).Put(rootCtx, newValue())
+ foo := naming.Join(tname, "foo")
+ if _, err := lookupObjectOrDie(s, foo).CreateTransaction(rootCtx, nil); err == nil {
+ t.Fatalf("creating nested transaction at %s should've failed, but didn't", foo)
+ }
+}
+
func TestPutGetRemoveRoot(t *testing.T) {
s, c := newServer()
defer c()
- o := s.lookupObject("/")
- testPutGetRemove(t, s, o)
+ testPutGetRemove(t, s, "/")
}
func TestPutGetRemoveChild(t *testing.T) {
@@ -146,199 +186,158 @@
{
// Create a root.
- o := s.lookupObject("/")
+ name := "/"
value := newValue()
- tr1 := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr1, nil); err != nil {
+
+ tobj1 := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if _, err := tobj1.Put(rootCtx, value); err != nil {
t.Errorf("Unexpected error: %s", err)
}
- if _, err := o.Put(rootCtx, tr1, value); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if err := s.Commit(rootCtx, tr1); err != nil {
+ if err := tobj1.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
- tr2 := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr2, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if ok, err := o.Exists(rootCtx, tr2); !ok || err != nil {
+ tobj2 := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if ok, err := tobj2.Exists(rootCtx); !ok || err != nil {
t.Errorf("Should exist: %s", err)
}
- if _, err := o.Get(rootCtx, tr2); err != nil {
+ if _, err := tobj2.Get(rootCtx); err != nil {
t.Errorf("Object should exist: %s", err)
}
- if err := s.Abort(rootCtx, tr2); err != nil {
+ if err := tobj2.Abort(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
- o := s.lookupObject("/Entries/a")
- testPutGetRemove(t, s, o)
+ testPutGetRemove(t, s, "/Entries/a")
}
-func testPutGetRemove(t *testing.T, s *Server, o *object) {
+func testPutGetRemove(t *testing.T, s *Server, name string) {
value := newValue()
{
// Check that the object does not exist.
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if ok, err := o.Exists(rootCtx, tr); ok || err != nil {
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if ok, err := tobj.Exists(rootCtx); ok || err != nil {
t.Errorf("Should not exist: %s", err)
}
- if v, err := o.Get(rootCtx, tr); v.Stat.ID.IsValid() && err == nil {
+ if v, err := tobj.Get(rootCtx); v.Stat.ID.IsValid() && err == nil {
t.Errorf("Should not exist: %v, %s", v, err)
}
}
{
// Add the object.
- tr1 := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr1, nil); err != nil {
+ tobj1 := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if _, err := tobj1.Put(rootCtx, value); err != nil {
t.Errorf("Unexpected error: %s", err)
}
- if _, err := o.Put(rootCtx, tr1, value); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if ok, err := o.Exists(rootCtx, tr1); !ok || err != nil {
+ if ok, err := tobj1.Exists(rootCtx); !ok || err != nil {
t.Errorf("Should exist: %s", err)
}
- if _, err := o.Get(rootCtx, tr1); err != nil {
+ if _, err := tobj1.Get(rootCtx); err != nil {
t.Errorf("Object should exist: %s", err)
}
// Transactions are isolated.
- tr2 := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr2, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if ok, err := o.Exists(rootCtx, tr2); ok || err != nil {
+ tobj2 := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if ok, err := tobj2.Exists(rootCtx); ok || err != nil {
t.Errorf("Should not exist: %s", err)
}
- if v, err := o.Get(rootCtx, tr2); v.Stat.ID.IsValid() && err == nil {
+ if v, err := tobj2.Get(rootCtx); v.Stat.ID.IsValid() && err == nil {
t.Errorf("Should not exist: %v, %s", v, err)
}
- // Apply tr1.
- if err := s.Commit(rootCtx, tr1); err != nil {
+ // Apply tobj1.
+ if err := tobj1.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
- // tr2 is still isolated.
- if ok, err := o.Exists(rootCtx, tr2); ok || err != nil {
+ // tobj2 is still isolated.
+ if ok, err := tobj2.Exists(rootCtx); ok || err != nil {
t.Errorf("Should not exist: %s", err)
}
- if v, err := o.Get(rootCtx, tr2); v.Stat.ID.IsValid() && err == nil {
+ if v, err := tobj2.Get(rootCtx); v.Stat.ID.IsValid() && err == nil {
t.Errorf("Should not exist: %v, %s", v, err)
}
- // tr3 observes the commit.
- tr3 := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr3, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if ok, err := o.Exists(rootCtx, tr3); !ok || err != nil {
+ // tobj3 observes the commit.
+ tobj3 := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if ok, err := tobj3.Exists(rootCtx); !ok || err != nil {
t.Errorf("Should exist: %s", err)
}
- if _, err := o.Get(rootCtx, tr3); err != nil {
+ if _, err := tobj3.Get(rootCtx); err != nil {
t.Errorf("Object should exist: %s", err)
}
}
{
// Remove the object.
- tr1 := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr1, nil); err != nil {
+ tobj1 := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if err := tobj1.Remove(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
- if err := o.Remove(rootCtx, tr1); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if ok, err := o.Exists(rootCtx, tr1); ok || err != nil {
+ if ok, err := tobj1.Exists(rootCtx); ok || err != nil {
t.Errorf("Should not exist: %s", err)
}
- if v, err := o.Get(rootCtx, tr1); v.Stat.ID.IsValid() || err == nil {
+ if v, err := tobj1.Get(rootCtx); v.Stat.ID.IsValid() || err == nil {
t.Errorf("Object should not exist: %T, %v, %s", v, v, err)
}
// The removal is isolated.
- tr2 := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr2, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if ok, err := o.Exists(rootCtx, tr2); !ok || err != nil {
+ tobj2 := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if ok, err := tobj2.Exists(rootCtx); !ok || err != nil {
t.Errorf("Should exist: %s", err)
}
- if _, err := o.Get(rootCtx, tr2); err != nil {
+ if _, err := tobj2.Get(rootCtx); err != nil {
t.Errorf("Object should exist: %s", err)
}
- // Apply tr1.
- if err := s.Commit(rootCtx, tr1); err != nil {
+ // Apply tobj1.
+ if err := tobj1.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
// The removal is isolated.
- if ok, err := o.Exists(rootCtx, tr2); !ok || err != nil {
+ if ok, err := tobj2.Exists(rootCtx); !ok || err != nil {
t.Errorf("Should exist: %s", err)
}
- if _, err := o.Get(rootCtx, tr2); err != nil {
+ if _, err := tobj2.Get(rootCtx); err != nil {
t.Errorf("Object should exist: %s", err)
}
}
{
// Check that the object does not exist.
- tr1 := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr1, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if ok, err := o.Exists(rootCtx, tr1); ok || err != nil {
+ tobj1 := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+ if ok, err := tobj1.Exists(rootCtx); ok || err != nil {
t.Errorf("Should not exist")
}
- if v, err := o.Get(rootCtx, tr1); v.Stat.ID.IsValid() && err == nil {
+ if v, err := tobj1.Get(rootCtx); v.Stat.ID.IsValid() && err == nil {
t.Errorf("Should not exist: %v, %s", v, err)
}
}
}
-func TestNilTransaction(t *testing.T) {
- s, c := newServer()
- defer c()
-
- if err := s.Commit(rootCtx, nullTransactionID); err != errTransactionDoesNotExist {
- t.Errorf("Unexpected error: %v", err)
- }
-
- if err := s.Abort(rootCtx, nullTransactionID); err != errTransactionDoesNotExist {
- t.Errorf("Unexpected error: %v", err)
- }
-}
+// TODO(sadovsky): Add test cases for committing and aborting an expired
+// transaction. The client should get back errTransactionDoesNotExist.
func TestWatch(t *testing.T) {
s, c := newServer()
defer c()
- path1 := "/"
+ name1 := "/"
value1 := "v1"
var id1 storage.ID
// Before the watch request has been made, commit a transaction that puts /.
{
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- o := s.lookupObject(path1)
- st, err := o.Put(rootCtx, tr, value1)
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name1))
+ st, err := tobj.Put(rootCtx, value1)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
id1 = st.ID
- if err := s.Commit(rootCtx, tr); err != nil {
+ if err := tobj.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
@@ -361,23 +360,19 @@
watchtesting.ExpectMutationExistsNoVersionCheck(t, changes, id1, value1)
}
- path2 := "/a"
+ name2 := "/a"
value2 := "v2"
var id2 storage.ID
// Commit a second transaction that puts /a.
{
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- o := s.lookupObject(path2)
- st, err := o.Put(rootCtx, tr, value2)
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name2))
+ st, err := tobj.Put(rootCtx, value2)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
id2 = st.ID
- if err := s.Commit(rootCtx, tr); err != nil {
+ if err := tobj.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
@@ -409,21 +404,18 @@
value1 := "v1"
var id1 storage.ID
- o1 := s.lookupObject("/")
- o2 := s.lookupObject("/a")
+ name1, name2 := "/", "/a"
+ o1, o2 := lookupObjectOrDie(s, name1), lookupObjectOrDie(s, name2)
// Before the watch request has been made, commit a transaction that puts /.
{
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- st, err := o1.Put(rootCtx, tr, value1)
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name1))
+ st, err := tobj.Put(rootCtx, value1)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
id1 = st.ID
- if err := s.Commit(rootCtx, tr); err != nil {
+ if err := tobj.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
@@ -454,16 +446,13 @@
// Commit a second transaction that puts /a.
{
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- st, err := o2.Put(rootCtx, tr, value2)
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name2))
+ st, err := tobj.Put(rootCtx, value2)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
id2 = st.ID
- if err := s.Commit(rootCtx, tr); err != nil {
+ if err := tobj.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
@@ -505,23 +494,19 @@
s, c := newServer()
defer c()
- path1 := "/"
+ name1 := "/"
value1 := "v1"
var id1 storage.ID
// Before the watch request has been made, commit a transaction that puts /.
{
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- o := s.lookupObject(path1)
- st, err := o.Put(rootCtx, tr, value1)
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name1))
+ st, err := tobj.Put(rootCtx, value1)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
id1 = st.ID
- if err := s.Commit(rootCtx, tr); err != nil {
+ if err := tobj.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
@@ -544,23 +529,19 @@
watchtesting.ExpectMutationExistsNoVersionCheck(t, changes, id1, value1)
}
- path2 := "/a"
+ name2 := "/a"
value2 := "v2"
var id2 storage.ID
// Commit a second transaction that puts /a.
{
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- o := s.lookupObject(path2)
- st, err := o.Put(rootCtx, tr, value2)
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name2))
+ st, err := tobj.Put(rootCtx, value2)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
id2 = st.ID
- if err := s.Commit(rootCtx, tr); err != nil {
+ if err := tobj.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
@@ -586,15 +567,11 @@
// Commit a third transaction that removes /a.
{
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, "/a"))
+ if err := tobj.Remove(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
- o := s.lookupObject("/a")
- if err := o.Remove(rootCtx, tr); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- if err := s.Commit(rootCtx, tr); err != nil {
+ if err := tobj.Commit(rootCtx); err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
@@ -633,62 +610,52 @@
defer c()
// Create a root.
- o := s.lookupObject("/")
+ name := "/"
value := newValue()
// Create a transaction in the root's session.
- tr := newTransaction()
- if err := s.CreateTransaction(rootCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- // Check that the transaction cannot be created or accessed by the blessee.
- if err := s.CreateTransaction(blessedCtx, tr, nil); err != errTransactionAlreadyExists {
+ tobj := lookupObjectOrDie(s, createTransaction(t, s, rootCtx, name))
+
+ // Check that the transaction cannot be accessed by the blessee.
+ if _, err := tobj.Exists(blessedCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if _, err := o.Exists(blessedCtx, tr); err != errPermissionDenied {
+ if _, err := tobj.Get(blessedCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if _, err := o.Get(blessedCtx, tr); err != errPermissionDenied {
+ if _, err := tobj.Put(blessedCtx, value); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if _, err := o.Put(blessedCtx, tr, value); err != errPermissionDenied {
+ if err := tobj.Remove(blessedCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if err := o.Remove(blessedCtx, tr); err != errPermissionDenied {
+ if err := tobj.Abort(blessedCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if err := s.Abort(blessedCtx, tr); err != errPermissionDenied {
- t.Errorf("Unexpected error: %v", err)
- }
- if err := s.Commit(blessedCtx, tr); err != errPermissionDenied {
+ if err := tobj.Commit(blessedCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
// Create a transaction in the blessee's session.
- tr = newTransaction()
- if err := s.CreateTransaction(blessedCtx, tr, nil); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- // Check that the transaction cannot be created or accessed by the root.
- if err := s.CreateTransaction(rootCtx, tr, nil); err != errTransactionAlreadyExists {
+ tobj = lookupObjectOrDie(s, createTransaction(t, s, blessedCtx, name))
+
+ // Check that the transaction cannot be accessed by the root.
+ if _, err := tobj.Exists(rootCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if _, err := o.Exists(rootCtx, tr); err != errPermissionDenied {
+ if _, err := tobj.Get(rootCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if _, err := o.Get(rootCtx, tr); err != errPermissionDenied {
+ if _, err := tobj.Put(rootCtx, value); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if _, err := o.Put(rootCtx, tr, value); err != errPermissionDenied {
+ if err := tobj.Remove(rootCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if err := o.Remove(rootCtx, tr); err != errPermissionDenied {
+ if err := tobj.Abort(rootCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
- if err := s.Abort(rootCtx, tr); err != errPermissionDenied {
- t.Errorf("Unexpected error: %v", err)
- }
- if err := s.Commit(rootCtx, tr); err != errPermissionDenied {
+ if err := tobj.Commit(rootCtx); err != errPermissionDenied {
t.Errorf("Unexpected error: %v", err)
}
}
@@ -716,12 +683,16 @@
s, c := newServer()
defer c()
- // TODO(bprosnitz) Switch this to use just exported methods (using signature) once signature stabilizes.
+ // TODO(bprosnitz): Switch this to use just exported methods (using signature)
+ // once signature stabilizes.
d := NewStoreDispatcher(s, nil).(*storeDispatcher)
for _, test := range tests {
- srvr := d.lookupServer(test.name)
- if reflect.TypeOf(srvr) != test.t {
- t.Errorf("error looking up %s. got %T, expected %v", test.name, srvr, test.t)
+ serv, err := d.lookupServer(test.name)
+ if err != nil {
+ t.Errorf("error looking up %s: %s", test.name, err)
+ }
+ if reflect.TypeOf(serv) != test.t {
+ t.Errorf("error looking up %s. got %T, expected %v", test.name, serv, test.t)
}
}
}
diff --git a/services/store/service/service.go b/services/store/service/service.go
index 4f9758f..f91f206 100644
--- a/services/store/service/service.go
+++ b/services/store/service/service.go
@@ -1,5 +1,9 @@
package service
+// Defines the store server Go API.
+// NOTE(sadovsky): See comments in go/vcl/3292 for why we have this API. It may
+// no longer be necessary.
+
import (
"veyron/services/store/raw"
@@ -11,8 +15,8 @@
"veyron2/storage"
)
-// Transaction is like storage.Transaction, but doesn't include extra client-side
-// parameters.
+// Transaction is like storage.Transaction, but doesn't include extra
+// client-side parameters.
type Transaction interface {
// Commit commits the changes (the Set and Delete operations) in the
// transaction to the store. The operation is atomic, so all Set/Delete
@@ -30,7 +34,7 @@
}
// Object is like storage.Object, but it include extra server-side parameters.
-// In perticular, each method takes the identity of the client.
+// In particular, each method takes the identity of the client.
type Object interface {
// Exists returns true iff the Entry has a value.
Exists(clientID security.PublicID, t Transaction) (bool, error)
@@ -60,7 +64,7 @@
// Query returns entries matching the given query.
Query(clientID security.PublicID, t Transaction, q query.Query) (QueryStream, error)
- // Glob returns the sequence of names that match the given pattern.
+ // Glob returns names that match the given pattern.
Glob(clientID security.PublicID, t Transaction, pattern string) (GlobStream, error)
}
diff --git a/services/wsprd/lib/remove_this.go b/services/wsprd/lib/remove_this.go
index 716da94..a135df7 100644
--- a/services/wsprd/lib/remove_this.go
+++ b/services/wsprd/lib/remove_this.go
@@ -15,7 +15,6 @@
vom.Register(store.NestedResult(0))
vom.Register(store.QueryResult{})
vom.Register(store.Stat{})
- vom.Register(store.TransactionID(0))
vom.Register(watch.GlobRequest{})
vom.Register(watch.QueryRequest{})
vom.Register(watch.ChangeBatch{})