Skip to content

Commit

Permalink
Adds a get-tree verb to KV transaction operations.
Browse files Browse the repository at this point in the history
  • Loading branch information
James Phillips committed May 13, 2016
1 parent 4bbaf1c commit 778b975
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 15 deletions.
1 change: 1 addition & 0 deletions api/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
KVLock = "lock"
KVUnlock = "unlock"
KVGet = "get"
KVGetTree = "get-tree"
KVCheckSession = "check-session"
KVCheckIndex = "check-index"
)
Expand Down
22 changes: 19 additions & 3 deletions command/agent/txn_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ func TestTxnEndpoint_KV_Actions(t *testing.T) {
"Verb": "get",
"Key": "key"
}
},
{
"KV": {
"Verb": "get-tree",
"Key": "key"
}
}
]
`))
Expand Down Expand Up @@ -274,9 +280,6 @@ func TestTxnEndpoint_KV_Actions(t *testing.T) {
if !ok {
t.Fatalf("bad type: %T", obj)
}
if len(txnResp.Results) != 1 {
t.Fatalf("bad: %v", txnResp)
}
expected := structs.TxnReadResponse{
TxnResponse: structs.TxnResponse{
Results: structs.TxnResults{
Expand All @@ -293,6 +296,19 @@ func TestTxnEndpoint_KV_Actions(t *testing.T) {
},
},
},
&structs.TxnResult{
KV: &structs.DirEntry{
Key: "key",
Value: []byte("hello world"),
Flags: 23,
Session: id,
LockIndex: 1,
RaftIndex: structs.RaftIndex{
CreateIndex: index,
ModifyIndex: index,
},
},
},
},
},
QueryMeta: structs.QueryMeta{
Expand Down
2 changes: 1 addition & 1 deletion consul/kvs_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func kvsPreApply(srv *Server, acl acl.ACL, op structs.KVSOp, dirEnt *structs.Dir
return false, permissionDeniedErr
}

case structs.KVSGet:
case structs.KVSGet, structs.KVSGetTree:
// Filtering for GETs is done on the output side.

case structs.KVSCheckSession, structs.KVSCheckIndex:
Expand Down
6 changes: 6 additions & 0 deletions consul/state/kvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ func (s *StateStore) KVSList(prefix string) (uint64, structs.DirEntries, error)
tx := s.db.Txn(false)
defer tx.Abort()

return s.kvsListTxn(tx, prefix)
}

// kvsListTxn is the inner method that gets a list of KVS entries matching a
// prefix.
func (s *StateStore) kvsListTxn(tx *memdb.Txn, prefix string) (uint64, structs.DirEntries, error) {
// Get the table indexes.
idx := maxIndexTxn(tx, "kvs", "tombstones")

Expand Down
12 changes: 12 additions & 0 deletions consul/state/txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ func (s *StateStore) txnKVS(tx *memdb.Txn, idx uint64, op *structs.TxnKVOp) (str
err = fmt.Errorf("key %q doesn't exist", op.DirEnt.Key)
}

case structs.KVSGetTree:
var entries structs.DirEntries
_, entries, err = s.kvsListTxn(tx, op.DirEnt.Key)
if err == nil {
results := make(structs.TxnResults, 0, len(entries))
for _, e := range entries {
result := structs.TxnResult{KV: e}
results = append(results, &result)
}
return results, nil
}

case structs.KVSCheckSession:
entry, err = s.kvsCheckSessionTxn(tx, op.DirEnt.Key, op.DirEnt.Session)

Expand Down
59 changes: 56 additions & 3 deletions consul/state/txn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ func TestStateStore_Txn_KVS(t *testing.T) {

// Set up a transaction that hits every operation.
ops := structs.TxnOps{
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: structs.KVSGetTree,
DirEnt: structs.DirEntry{
Key: "foo/bar",
},
},
},
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: structs.KVSSet,
Expand Down Expand Up @@ -157,6 +165,26 @@ func TestStateStore_Txn_KVS(t *testing.T) {

// Make sure the response looks as expected.
expected := structs.TxnResults{
&structs.TxnResult{
KV: &structs.DirEntry{
Key: "foo/bar/baz",
Value: []byte("baz"),
RaftIndex: structs.RaftIndex{
CreateIndex: 2,
ModifyIndex: 2,
},
},
},
&structs.TxnResult{
KV: &structs.DirEntry{
Key: "foo/bar/zip",
Value: []byte("zip"),
RaftIndex: structs.RaftIndex{
CreateIndex: 3,
ModifyIndex: 3,
},
},
},
&structs.TxnResult{
KV: &structs.DirEntry{
Key: "foo/new",
Expand Down Expand Up @@ -517,6 +545,14 @@ func TestStateStore_Txn_KVS_RO(t *testing.T) {

// Set up a transaction that hits all the read-only operations.
ops := structs.TxnOps{
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: structs.KVSGetTree,
DirEnt: structs.DirEntry{
Key: "foo/bar",
},
},
},
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: structs.KVSGet,
Expand Down Expand Up @@ -550,12 +586,29 @@ func TestStateStore_Txn_KVS_RO(t *testing.T) {
if len(errors) > 0 {
t.Fatalf("err: %v", errors)
}
if len(results) != len(ops) {
t.Fatalf("bad len: %d != %d", len(results), len(ops))
}

// Make sure the response looks as expected.
expected := structs.TxnResults{
&structs.TxnResult{
KV: &structs.DirEntry{
Key: "foo/bar/baz",
Value: []byte("baz"),
RaftIndex: structs.RaftIndex{
CreateIndex: 2,
ModifyIndex: 2,
},
},
},
&structs.TxnResult{
KV: &structs.DirEntry{
Key: "foo/bar/zip",
Value: []byte("zip"),
RaftIndex: structs.RaftIndex{
CreateIndex: 3,
ModifyIndex: 3,
},
},
},
&structs.TxnResult{
KV: &structs.DirEntry{
Key: "foo",
Expand Down
3 changes: 2 additions & 1 deletion consul/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,14 +538,15 @@ const (
// The following operations are only available inside of atomic
// transactions via the Txn request.
KVSGet = "get" // Read the key during the transaction.
KVSGetTree = "get-tree" // Read all keys with the given prefix during the transaction.
KVSCheckSession = "check-session" // Check the session holds the key.
KVSCheckIndex = "check-index" // Check the modify index of the key.
)

// IsWrite returns true if the given operation alters the state store.
func (op KVSOp) IsWrite() bool {
switch op {
case KVSGet, KVSCheckSession, KVSCheckIndex:
case KVSGet, KVSGetTree, KVSCheckSession, KVSCheckIndex:
return false

default:
Expand Down
20 changes: 18 additions & 2 deletions consul/txn_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
},
},
},
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: structs.KVSGetTree,
DirEnt: structs.DirEntry{
Key: "nope",
},
},
},
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: structs.KVSCheckSession,
Expand Down Expand Up @@ -239,7 +247,7 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
var expected structs.TxnResponse
for i, op := range arg.Ops {
switch op.KV.Verb {
case structs.KVSGet:
case structs.KVSGet, structs.KVSGetTree:
// These get filtered but won't result in an error.

default:
Expand Down Expand Up @@ -455,6 +463,14 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
},
},
},
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: structs.KVSGetTree,
DirEnt: structs.DirEntry{
Key: "nope",
},
},
},
&structs.TxnOp{
KV: &structs.TxnKVOp{
Verb: structs.KVSCheckSession,
Expand Down Expand Up @@ -489,7 +505,7 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
}
for i, op := range arg.Ops {
switch op.KV.Verb {
case structs.KVSGet:
case structs.KVSGet, structs.KVSGetTree:
// These get filtered but won't result in an error.

default:
Expand Down
19 changes: 14 additions & 5 deletions website/source/docs/agent/http/kv.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ operation ("X" means a field is required and "O" means it is optional):
</tr>
<tr>
<td>unlock</td>
<td>Unlocks the `Key` with the given `Session`. The `Key` will only release the lock if the `Session` is valid and currently has it locked..</td>
<td>Unlocks the `Key` with the given `Session`. The `Key` will only release the lock if the `Session` is valid and currently has it locked.</td>
<td align="center">X</td>
<td align="center">X</td>
<td align="center">O</td>
Expand All @@ -271,7 +271,16 @@ operation ("X" means a field is required and "O" means it is optional):
</tr>
<tr>
<td>get</td>
<td>Gets the `Key` during the transaction. This fails the transaction if the `Key` doesn't exist.</td>
<td>Gets the `Key` during the transaction. This fails the transaction if the `Key` doesn't exist. The key may not be present in the results if ACLs do not permit it to be read.</td>
<td align="center">X</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td>get-tree</td>
<td>Gets all keys with a prefix of `Key` during the transaction. This does not fail the transaction if the `Key` doesn't exist. Not all keys may be present in the results if ACLs do not permit them to be read.</td>
<td align="center">X</td>
<td align="center"></td>
<td align="center"></td>
Expand Down Expand Up @@ -355,9 +364,9 @@ back. If either of these status codes are returned, the response will look like
```

`Results` has entries for some operations if the transaction was successful. To save
space, the `Value` will be `null` for any `Verb` other than "get". Like the `/v1/kv/<key>`
endpoint, `Value` will be Base64-encoded if it is present. Also, no result entries will be
added for verbs that delete keys.
space, the `Value` will be `null` for any `Verb` other than "get" or "get-tree". Like
the `/v1/kv/<key>` endpoint, `Value` will be Base64-encoded if it is present. Also,
no result entries will be added for verbs that delete keys.

`Errors` has entries describing which operations failed if the transaction was rolled
back. The `OpIndex` gives the index of the failed operation in the transaction, and
Expand Down

0 comments on commit 778b975

Please sign in to comment.