Issue #2905: Add check-not-exists to TXN endpoint

This patch adds support for asserting that a given
key does not exist in the KV store.

Fixes #2905
This commit is contained in:
Frank Schröder 2017-04-20 17:50:52 -07:00 committed by GitHub
parent 9f8f258d4d
commit 5f0a967507
4 changed files with 91 additions and 24 deletions

View File

@ -49,17 +49,18 @@ type KVPairs []*KVPair
type KVOp string
const (
KVSet KVOp = "set"
KVDelete KVOp = "delete"
KVDeleteCAS KVOp = "delete-cas"
KVDeleteTree KVOp = "delete-tree"
KVCAS KVOp = "cas"
KVLock KVOp = "lock"
KVUnlock KVOp = "unlock"
KVGet KVOp = "get"
KVGetTree KVOp = "get-tree"
KVCheckSession KVOp = "check-session"
KVCheckIndex KVOp = "check-index"
KVSet KVOp = "set"
KVDelete KVOp = "delete"
KVDeleteCAS KVOp = "delete-cas"
KVDeleteTree KVOp = "delete-tree"
KVCAS KVOp = "cas"
KVLock KVOp = "lock"
KVUnlock KVOp = "unlock"
KVGet KVOp = "get"
KVGetTree KVOp = "get-tree"
KVCheckSession KVOp = "check-session"
KVCheckIndex KVOp = "check-index"
KVCheckNotExists KVOp = "check-not-exists"
)
// KVTxnOp defines a single operation inside a transaction.

View File

@ -79,6 +79,12 @@ func (s *StateStore) txnKVS(tx *memdb.Txn, idx uint64, op *structs.TxnKVOp) (str
case api.KVCheckIndex:
entry, err = s.kvsCheckIndexTxn(tx, op.DirEnt.Key, op.DirEnt.ModifyIndex)
case api.KVCheckNotExists:
_, entry, err = s.kvsGetTxn(tx, nil, op.DirEnt.Key)
if entry != nil && err == nil {
err = fmt.Errorf("key %q exists", op.DirEnt.Key)
}
default:
err = fmt.Errorf("unknown KV verb %q", op.Verb)
}

View File

@ -14,6 +14,57 @@ import (
"github.com/hashicorp/net-rpc-msgpackrpc"
)
func TestTxn_CheckNotExists(t *testing.T) {
dir1, s1 := testServer(t)
defer os.RemoveAll(dir1)
defer s1.Shutdown()
codec := rpcClient(t, s1)
defer codec.Close()
testrpc.WaitForLeader(t, s1.RPC, "dc1")
apply := func(arg *structs.TxnRequest) (*structs.TxnResponse, error) {
out := new(structs.TxnResponse)
err := msgpackrpc.CallWithCodec(codec, "Txn.Apply", arg, out)
return out, err
}
checkKeyNotExists := &structs.TxnRequest{
Datacenter: "dc1",
Ops: structs.TxnOps{
{
KV: &structs.TxnKVOp{
Verb: api.KVCheckNotExists,
DirEnt: structs.DirEntry{Key: "test"},
},
},
},
}
createKey := &structs.TxnRequest{
Datacenter: "dc1",
Ops: structs.TxnOps{
{
KV: &structs.TxnKVOp{
Verb: api.KVSet,
DirEnt: structs.DirEntry{Key: "test"},
},
},
},
}
if _, err := apply(checkKeyNotExists); err != nil {
t.Fatalf("testing for non-existent key failed: %s", err)
}
if _, err := apply(createKey); err != nil {
t.Fatalf("creating new key failed: %s", err)
}
out, err := apply(checkKeyNotExists)
if err != nil || out == nil || len(out.Errors) != 1 || out.Errors[0].Error() != `op 0: key "test" exists` {
t.Fatalf("testing for existent key failed: %#v", out)
}
}
func TestTxn_Apply(t *testing.T) {
dir1, s1 := testServer(t)
defer os.RemoveAll(dir1)
@ -234,6 +285,14 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
},
},
},
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: api.KVCheckNotExists,
DirEnt: structs.DirEntry{
Key: "nope",
},
},
},
},
WriteRequest: structs.WriteRequest{
Token: id,

View File

@ -149,16 +149,17 @@ look like this:
The following table summarizes the available verbs and the fields that apply to
that operation ("X" means a field is required and "O" means it is optional):
| Verb | Operation | Key | Value | Flags | Index | Session |
| --------------- | -------------------------------------------- | :--: | :---: | :---: | :---: | :-----: |
| `set` | Sets the `Key` to the given `Value` | `x` | `x` | `o` | | |
| `cas` | Sets, but with CAS semantics | `x` | `x` | `o` | `x` | |
| `lock` | Lock with the given `Session` | `x` | `x` | `o` | | `x` |
| `unlock` | Unlock with the given `Session` | `x` | `x` | `o` | | `x` |
| `get` | Get the key, fails if it does not exist | `x` | | | | |
| `get-tree` | Gets all keys with the prefix | `x` | | | | |
| `check-index` | Fail if modify index != index | `x` | | | `x` | |
| `check-session` | Fail if not locked by session | `x` | | | | `x` |
| `delete` | Delete the key | `x` | | | | |
| `delete-tree` | Delete all keys with a prefix | `x` | | | | |
| `delete-cas` | Delete, but with CAS semantics | `x` | | | `x` | |
| Verb | Operation | Key | Value | Flags | Index | Session |
| ------------------ | -------------------------------------------- | :--: | :---: | :---: | :---: | :-----: |
| `set` | Sets the `Key` to the given `Value` | `x` | `x` | `o` | | |
| `cas` | Sets, but with CAS semantics | `x` | `x` | `o` | `x` | |
| `lock` | Lock with the given `Session` | `x` | `x` | `o` | | `x` |
| `unlock` | Unlock with the given `Session` | `x` | `x` | `o` | | `x` |
| `get` | Get the key, fails if it does not exist | `x` | | | | |
| `get-tree` | Gets all keys with the prefix | `x` | | | | |
| `check-index` | Fail if modify index != index | `x` | | | `x` | |
| `check-session` | Fail if not locked by session | `x` | | | | `x` |
| `check-not-exists` | Fail if key exists | `x` | | | | |
| `delete` | Delete the key | `x` | | | | |
| `delete-tree` | Delete all keys with a prefix | `x` | | | | |
| `delete-cas` | Delete, but with CAS semantics | `x` | | | `x` | |