Adds state store support for atomic KVS ops.
This commit is contained in:
parent
a1a59bee73
commit
dca00c96f7
|
@ -578,3 +578,138 @@ func (s *StateStore) kvsUnlockTxn(tx *memdb.Txn, idx uint64, entry *structs.DirE
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KVSAtomicUpdate performs a series of updates atomically, all inside a single
|
||||||
|
// transaction that only succeeds if all the operations succeed.
|
||||||
|
func (s *StateStore) KVSAtomicUpdate(idx uint64, ops structs.KVSAtomicOps) (structs.DirEntries, structs.IndexedErrors) {
|
||||||
|
tx := s.db.Txn(true)
|
||||||
|
defer tx.Abort()
|
||||||
|
|
||||||
|
// Dispatch all of the operations inside the transaction.
|
||||||
|
entries := make(structs.DirEntries, 0, len(ops))
|
||||||
|
errors := make(structs.IndexedErrors, 0, len(ops))
|
||||||
|
for i, op := range ops {
|
||||||
|
var entry *structs.DirEntry
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch op.Op {
|
||||||
|
case structs.KVSSet:
|
||||||
|
entry = &op.DirEnt
|
||||||
|
err = s.kvsSetTxn(tx, idx, entry, false)
|
||||||
|
|
||||||
|
case structs.KVSDelete:
|
||||||
|
err = s.kvsDeleteTxn(tx, idx, op.DirEnt.Key)
|
||||||
|
|
||||||
|
case structs.KVSDeleteCAS:
|
||||||
|
var ok bool
|
||||||
|
ok, err = s.kvsDeleteCASTxn(tx, idx, op.DirEnt.ModifyIndex, op.DirEnt.Key)
|
||||||
|
if !ok && err == nil {
|
||||||
|
err = fmt.Errorf("failed to delete key %q, index is stale", op.DirEnt.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
case structs.KVSDeleteTree:
|
||||||
|
err = s.kvsDeleteTreeTxn(tx, idx, op.DirEnt.Key)
|
||||||
|
|
||||||
|
case structs.KVSCAS:
|
||||||
|
var ok bool
|
||||||
|
entry = &op.DirEnt
|
||||||
|
ok, err = s.kvsSetCASTxn(tx, idx, entry)
|
||||||
|
if !ok && err == nil {
|
||||||
|
err = fmt.Errorf("failed to set key %q, index is stale", op.DirEnt.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
case structs.KVSLock:
|
||||||
|
var ok bool
|
||||||
|
entry = &op.DirEnt
|
||||||
|
ok, err = s.kvsLockTxn(tx, idx, entry)
|
||||||
|
if !ok && err == nil {
|
||||||
|
err = fmt.Errorf("failed to lock key %q, lock is already held", op.DirEnt.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
case structs.KVSUnlock:
|
||||||
|
var ok bool
|
||||||
|
entry = &op.DirEnt
|
||||||
|
ok, err = s.kvsUnlockTxn(tx, idx, entry)
|
||||||
|
if !ok && err == nil {
|
||||||
|
err = fmt.Errorf("failed to unlock key %q, lock isn't held, or is held by another session", op.DirEnt.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
case structs.KVSAtomicGet:
|
||||||
|
_, entry, err = s.kvsGetTxn(tx, op.DirEnt.Key)
|
||||||
|
|
||||||
|
case structs.KVSAtomicCheckSession:
|
||||||
|
entry, err = s.kvsCheckSessionTxn(tx, op.DirEnt.Key, op.DirEnt.Session)
|
||||||
|
|
||||||
|
case structs.KVSAtomicCheckIndex:
|
||||||
|
entry, err = s.kvsCheckIndexTxn(tx, op.DirEnt.Key, op.DirEnt.ModifyIndex)
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown operation %q", op.Op)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate the entries. For a GET we keep the value, otherwise
|
||||||
|
// we clone and blank out the value (we have to clone so we don't
|
||||||
|
// modify the entry being used by the state store).
|
||||||
|
if entry != nil {
|
||||||
|
if op.Op == structs.KVSAtomicGet {
|
||||||
|
entries = append(entries, entry)
|
||||||
|
} else {
|
||||||
|
clone := entry.Clone()
|
||||||
|
clone.Value = nil
|
||||||
|
entries = append(entries, clone)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entries = append(entries, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture any error along with the index of the operation that
|
||||||
|
// failed.
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, &structs.IndexedError{i, err})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return nil, errors
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// kvsCheckSessionTxn checks to see if the given session matches the current
|
||||||
|
// entry for a key.
|
||||||
|
func (s *StateStore) kvsCheckSessionTxn(tx *memdb.Txn, key string, session string) (*structs.DirEntry, error) {
|
||||||
|
entry, err := tx.First("kvs", "id", key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed kvs lookup: %s", err)
|
||||||
|
}
|
||||||
|
if entry == nil {
|
||||||
|
return nil, fmt.Errorf("failed to check session, key %q doesn't exist", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := entry.(*structs.DirEntry)
|
||||||
|
if e.Session != session {
|
||||||
|
return nil, fmt.Errorf("failed session check for key %q, current session %q != %q", key, e.Session, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// kvsCheckIndexTxn checks to see if the given modify index matches the current
|
||||||
|
// entry for a key.
|
||||||
|
func (s *StateStore) kvsCheckIndexTxn(tx *memdb.Txn, key string, cidx uint64) (*structs.DirEntry, error) {
|
||||||
|
entry, err := tx.First("kvs", "id", key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed kvs lookup: %s", err)
|
||||||
|
}
|
||||||
|
if entry == nil {
|
||||||
|
return nil, fmt.Errorf("failed to check index, key %q doesn't exist", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := entry.(*structs.DirEntry)
|
||||||
|
if e.ModifyIndex != cidx {
|
||||||
|
return nil, fmt.Errorf("failed index check for key %q, current modify index %d != %d", key, e.ModifyIndex, cidx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
|
@ -890,7 +890,7 @@ func TestStateStore_KVSSetCAS(t *testing.T) {
|
||||||
func TestStateStore_KVSDeleteTree(t *testing.T) {
|
func TestStateStore_KVSDeleteTree(t *testing.T) {
|
||||||
s := testStateStore(t)
|
s := testStateStore(t)
|
||||||
|
|
||||||
// Create kvs entries in the state store
|
// Create kvs entries in the state store.
|
||||||
testSetKey(t, s, 1, "foo/bar", "bar")
|
testSetKey(t, s, 1, "foo/bar", "bar")
|
||||||
testSetKey(t, s, 2, "foo/bar/baz", "baz")
|
testSetKey(t, s, 2, "foo/bar/baz", "baz")
|
||||||
testSetKey(t, s, 3, "foo/bar/zip", "zip")
|
testSetKey(t, s, 3, "foo/bar/zip", "zip")
|
||||||
|
@ -1214,6 +1214,446 @@ func TestStateStore_KVSUnlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateStore_KVS_Atomic(t *testing.T) {
|
||||||
|
s := testStateStore(t)
|
||||||
|
|
||||||
|
// Create kvs entries in the state store.
|
||||||
|
testSetKey(t, s, 1, "foo/delete", "bar")
|
||||||
|
testSetKey(t, s, 2, "foo/bar/baz", "baz")
|
||||||
|
testSetKey(t, s, 3, "foo/bar/zip", "zip")
|
||||||
|
testSetKey(t, s, 4, "foo/zorp", "zorp")
|
||||||
|
testSetKey(t, s, 5, "foo/update", "stale")
|
||||||
|
|
||||||
|
// Make a real session.
|
||||||
|
testRegisterNode(t, s, 6, "node1")
|
||||||
|
session := testUUID()
|
||||||
|
if err := s.SessionCreate(7, &structs.Session{ID: session, Node: "node1"}); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up a transaction that hits every operation.
|
||||||
|
ops := structs.KVSAtomicOps{
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSSet,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/new",
|
||||||
|
Value: []byte("one"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSDelete,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/zorp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSDeleteCAS,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/delete",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
ModifyIndex: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSDeleteTree,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicGet,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicCheckIndex,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
ModifyIndex: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSCAS,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
Value: []byte("new"),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
ModifyIndex: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicGet,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicCheckIndex,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicGet,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSLock,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: session,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicCheckSession,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: session,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSUnlock,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: session,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicCheckSession,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
entries, errors := s.KVSAtomicUpdate(8, ops)
|
||||||
|
if len(errors) > 0 {
|
||||||
|
t.Fatalf("err: %v", errors)
|
||||||
|
}
|
||||||
|
if len(entries) != len(ops) {
|
||||||
|
t.Fatalf("bad len: %d != %d", len(entries), len(ops))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the response looks as expected.
|
||||||
|
expected := structs.DirEntries{
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/new",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 8,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil, // delete
|
||||||
|
nil, // delete tree
|
||||||
|
nil, // delete CAS
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
Value: []byte("stale"),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 5,
|
||||||
|
ModifyIndex: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 5,
|
||||||
|
ModifyIndex: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 5,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
Value: []byte("new"),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 5,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 5,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil, // get on foo/lock before it's created
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: session,
|
||||||
|
LockIndex: 1,
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 8,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: session,
|
||||||
|
LockIndex: 1,
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 8,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
LockIndex: 1,
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 8,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
LockIndex: 1,
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 8,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if len(entries) != len(expected) {
|
||||||
|
t.Fatalf("bad: %v", entries)
|
||||||
|
}
|
||||||
|
for i, _ := range entries {
|
||||||
|
if !reflect.DeepEqual(entries[i], expected[i]) {
|
||||||
|
t.Fatalf("bad %d: %v != %v", i, *(entries[i]), *(expected[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull the resulting state store contents.
|
||||||
|
idx, actual, err := s.KVSList("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if idx != 8 {
|
||||||
|
t.Fatalf("bad index: %d", idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure it looks as expected.
|
||||||
|
expected = structs.DirEntries{
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
LockIndex: 1,
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 8,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/new",
|
||||||
|
Value: []byte("one"),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 8,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
Value: []byte("new"),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 5,
|
||||||
|
ModifyIndex: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if len(actual) != len(expected) {
|
||||||
|
t.Fatalf("bad len: %d != %d", len(actual), len(expected))
|
||||||
|
}
|
||||||
|
for i, _ := range actual {
|
||||||
|
if !reflect.DeepEqual(actual[i], expected[i]) {
|
||||||
|
t.Fatalf("bad %d: %v != %v", i, *(actual[i]), *(expected[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateStore_KVS_Atomic_Rollback(t *testing.T) {
|
||||||
|
s := testStateStore(t)
|
||||||
|
|
||||||
|
// Create kvs entries in the state store.
|
||||||
|
testSetKey(t, s, 1, "foo/delete", "bar")
|
||||||
|
testSetKey(t, s, 2, "foo/update", "stale")
|
||||||
|
|
||||||
|
testRegisterNode(t, s, 3, "node1")
|
||||||
|
session := testUUID()
|
||||||
|
if err := s.SessionCreate(4, &structs.Session{ID: session, Node: "node1"}); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
ok, err := s.KVSLock(5, &structs.DirEntry{Key: "foo/lock", Value: []byte("foo"), Session: session})
|
||||||
|
if !ok || err != nil {
|
||||||
|
t.Fatalf("didn't get the lock: %v %s", ok, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bogus := testUUID()
|
||||||
|
if err := s.SessionCreate(6, &structs.Session{ID: bogus, Node: "node1"}); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function verifies that the state store wasn't changed.
|
||||||
|
verifyStateStore := func(desc string) {
|
||||||
|
idx, actual, err := s.KVSList("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err (%s): %s", desc, err)
|
||||||
|
}
|
||||||
|
if idx != 5 {
|
||||||
|
t.Fatalf("bad index (%s): %d", desc, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure it looks as expected.
|
||||||
|
expected := structs.DirEntries{
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/delete",
|
||||||
|
Value: []byte("bar"),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 1,
|
||||||
|
ModifyIndex: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Value: []byte("foo"),
|
||||||
|
LockIndex: 1,
|
||||||
|
Session: session,
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 5,
|
||||||
|
ModifyIndex: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
Value: []byte("stale"),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 2,
|
||||||
|
ModifyIndex: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if len(actual) != len(expected) {
|
||||||
|
t.Fatalf("bad len (%s): %d != %d", desc, len(actual), len(expected))
|
||||||
|
}
|
||||||
|
for i, _ := range actual {
|
||||||
|
if !reflect.DeepEqual(actual[i], expected[i]) {
|
||||||
|
t.Fatalf("bad %d (%s): %v != %v", desc, i, *(actual[i]), *(expected[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verifyStateStore("initial")
|
||||||
|
|
||||||
|
// Set up a transaction that fails every operation.
|
||||||
|
ops := structs.KVSAtomicOps{
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSCAS,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/update",
|
||||||
|
Value: []byte("new"),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
ModifyIndex: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSLock,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: bogus,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSUnlock,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: bogus,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicCheckSession,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
Session: bogus,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicCheckSession,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "nope",
|
||||||
|
Session: bogus,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicCheckIndex,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/lock",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
ModifyIndex: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: structs.KVSAtomicCheckIndex,
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "nope",
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
ModifyIndex: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.KVSAtomicOp{
|
||||||
|
Op: "nope",
|
||||||
|
DirEnt: structs.DirEntry{
|
||||||
|
Key: "foo/delete",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
entries, errors := s.KVSAtomicUpdate(7, ops)
|
||||||
|
if len(errors) != len(ops) {
|
||||||
|
t.Fatalf("bad len: %d != %d", len(errors), len(ops))
|
||||||
|
}
|
||||||
|
if len(entries) != 0 {
|
||||||
|
t.Fatalf("bad len: %d != 0", len(entries), 0)
|
||||||
|
}
|
||||||
|
verifyStateStore("after")
|
||||||
|
|
||||||
|
// Make sure the errors look reasonable.
|
||||||
|
expected := []string{
|
||||||
|
"index is stale",
|
||||||
|
"lock is already held",
|
||||||
|
"lock isn't held, or is held by another session",
|
||||||
|
"current session",
|
||||||
|
`key "nope" doesn't exist`,
|
||||||
|
"current modify index",
|
||||||
|
`key "nope" doesn't exist`,
|
||||||
|
"unknown operation",
|
||||||
|
}
|
||||||
|
if len(errors) != len(expected) {
|
||||||
|
t.Fatalf("bad len: %d != %d", len(errors), len(expected))
|
||||||
|
}
|
||||||
|
for i, msg := range expected {
|
||||||
|
if errors[i].OpIndex != i {
|
||||||
|
t.Fatalf("bad index: %d != %d", i, errors[i].OpIndex)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errors[i].Error.Error(), msg) {
|
||||||
|
t.Fatalf("bad %i: %v", i, errors[i].Error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStateStore_KVS_Snapshot_Restore(t *testing.T) {
|
func TestStateStore_KVS_Snapshot_Restore(t *testing.T) {
|
||||||
s := testStateStore(t)
|
s := testStateStore(t)
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ const (
|
||||||
TombstoneRequestType
|
TombstoneRequestType
|
||||||
CoordinateBatchUpdateType
|
CoordinateBatchUpdateType
|
||||||
PreparedQueryRequestType
|
PreparedQueryRequestType
|
||||||
|
KVSAtomicRequestType
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -533,6 +534,12 @@ const (
|
||||||
KVSCAS = "cas" // Check-and-set
|
KVSCAS = "cas" // Check-and-set
|
||||||
KVSLock = "lock" // Lock a key
|
KVSLock = "lock" // Lock a key
|
||||||
KVSUnlock = "unlock" // Unlock a key
|
KVSUnlock = "unlock" // Unlock a key
|
||||||
|
|
||||||
|
// KVSAtomic* operations are only available in KVSAtomicRequest
|
||||||
|
// transactions.
|
||||||
|
KVSAtomicGet = "get" // Read the key during the transaction.
|
||||||
|
KVSAtomicCheckSession = "check-session" // Check the session holds the key.
|
||||||
|
KVSAtomicCheckIndex = "check-index" // Check the modify index of the key.
|
||||||
)
|
)
|
||||||
|
|
||||||
// KVSRequest is used to operate on the Key-Value store
|
// KVSRequest is used to operate on the Key-Value store
|
||||||
|
@ -547,6 +554,43 @@ func (r *KVSRequest) RequestDatacenter() string {
|
||||||
return r.Datacenter
|
return r.Datacenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KVSAtomicOp is used to define a single operation within an multi-key
|
||||||
|
// transaction.
|
||||||
|
type KVSAtomicOp struct {
|
||||||
|
Op KVSOp
|
||||||
|
DirEnt DirEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// KVSAtomicOps is a list of atomic operations.
|
||||||
|
type KVSAtomicOps []*KVSAtomicOp
|
||||||
|
|
||||||
|
// KVSAtomicRequest is used to perform atomic multi-key operations on the
|
||||||
|
// Key-Value store.
|
||||||
|
type KVSAtomicRequest struct {
|
||||||
|
Datacenter string
|
||||||
|
Ops KVSAtomicOps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KVSAtomicRequest) RequestDatacenter() string {
|
||||||
|
return r.Datacenter
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexedError is used to return information about an error for a specific
|
||||||
|
// operation.
|
||||||
|
type IndexedError struct {
|
||||||
|
OpIndex int
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexedErrors is a list of IndexedError entries.
|
||||||
|
type IndexedErrors []*IndexedError
|
||||||
|
|
||||||
|
// KVSAtomicResponse is the structure returned by a KVSAtomicRequest.
|
||||||
|
type KVSAtomicResponse struct {
|
||||||
|
Errors IndexedErrors
|
||||||
|
Results DirEntries
|
||||||
|
}
|
||||||
|
|
||||||
// KeyRequest is used to request a key, or key prefix
|
// KeyRequest is used to request a key, or key prefix
|
||||||
type KeyRequest struct {
|
type KeyRequest struct {
|
||||||
Datacenter string
|
Datacenter string
|
||||||
|
|
Loading…
Reference in a new issue