diff --git a/api/kv.go b/api/kv.go index 4b3ed0640..ba74057fc 100644 --- a/api/kv.go +++ b/api/kv.go @@ -193,27 +193,44 @@ func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOpti // Delete is used to delete a single key func (k *KV) Delete(key string, w *WriteOptions) (*WriteMeta, error) { - return k.deleteInternal(key, nil, w) + _, qm, err := k.deleteInternal(key, nil, w) + return qm, err +} + +// DeleteCAS is used for a Delete Check-And-Set operation. The Key +// and ModifyIndex are respected. Returns true on success or false on failures. +func (k *KV) DeleteCAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) { + params := map[string]string{ + "cas": strconv.FormatUint(p.ModifyIndex, 10), + } + return k.deleteInternal(p.Key, params, q) } // DeleteTree is used to delete all keys under a prefix func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) { - return k.deleteInternal(prefix, []string{"recurse"}, w) + _, qm, err := k.deleteInternal(prefix, map[string]string{"recurse": ""}, w) + return qm, err } -func (k *KV) deleteInternal(key string, params []string, q *WriteOptions) (*WriteMeta, error) { +func (k *KV) deleteInternal(key string, params map[string]string, q *WriteOptions) (bool, *WriteMeta, error) { r := k.c.newRequest("DELETE", "/v1/kv/"+key) r.setWriteOptions(q) - for _, param := range params { - r.params.Set(param, "") + for param, val := range params { + r.params.Set(param, val) } rtt, resp, err := requireOK(k.c.doRequest(r)) if err != nil { - return nil, err + return false, nil, err } - resp.Body.Close() + defer resp.Body.Close() qm := &WriteMeta{} qm.RequestTime = rtt - return qm, nil + + var buf bytes.Buffer + if _, err := io.Copy(&buf, resp.Body); err != nil { + return false, nil, fmt.Errorf("Failed to read response: %v", err) + } + res := strings.Contains(string(buf.Bytes()), "true") + return res, qm, nil } diff --git a/api/kv_test.go b/api/kv_test.go index 1ed751320..8f2b54945 100644 --- a/api/kv_test.go +++ b/api/kv_test.go @@ -117,6 +117,51 @@ func TestClient_List_DeleteRecurse(t *testing.T) { } } +func TestClient_DeleteCAS(t *testing.T) { + c, s := makeClient(t) + defer s.stop() + + kv := c.KV() + + // Put the key + key := testKey() + value := []byte("test") + p := &KVPair{Key: key, Value: value} + if work, _, err := kv.CAS(p, nil); err != nil { + t.Fatalf("err: %v", err) + } else if !work { + t.Fatalf("CAS failure") + } + + // Get should work + pair, meta, err := kv.Get(key, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + if pair == nil { + t.Fatalf("expected value: %#v", pair) + } + if meta.LastIndex == 0 { + t.Fatalf("unexpected value: %#v", meta) + } + + // CAS update with bad index + p.ModifyIndex = 1 + if work, _, err := kv.DeleteCAS(p, nil); err != nil { + t.Fatalf("err: %v", err) + } else if work { + t.Fatalf("unexpected CAS") + } + + // CAS update with valid index + p.ModifyIndex = meta.LastIndex + if work, _, err := kv.DeleteCAS(p, nil); err != nil { + t.Fatalf("err: %v", err) + } else if !work { + t.Fatalf("unexpected CAS failure") + } +} + func TestClient_CAS(t *testing.T) { c, s := makeClient(t) defer s.stop()