diff --git a/clientv3/integration/kv_test.go b/clientv3/integration/kv_test.go index ee6e3358e..dc32005d4 100644 --- a/clientv3/integration/kv_test.go +++ b/clientv3/integration/kv_test.go @@ -146,6 +146,47 @@ func TestKVPutWithIgnoreValue(t *testing.T) { } } +// TestKVPutWithIgnoreLease ensures that Put with WithIgnoreLease does not affect the existing lease for the key. +func TestKVPutWithIgnoreLease(t *testing.T) { + defer testutil.AfterTest(t) + + clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1}) + defer clus.Terminate(t) + + kv := clientv3.NewKV(clus.RandClient()) + + lapi := clientv3.NewLease(clus.RandClient()) + defer lapi.Close() + + resp, err := lapi.Grant(context.Background(), 10) + if err != nil { + t.Errorf("failed to create lease %v", err) + } + + if _, err := kv.Put(context.TODO(), "zoo", "bar", clientv3.WithIgnoreLease()); err != rpctypes.ErrKeyNotFound { + t.Fatalf("err expected %v, got %v", rpctypes.ErrKeyNotFound, err) + } + + if _, err := kv.Put(context.TODO(), "zoo", "bar", clientv3.WithLease(resp.ID)); err != nil { + t.Fatal(err) + } + + if _, err := kv.Put(context.TODO(), "zoo", "bar1", clientv3.WithIgnoreLease()); err != nil { + t.Fatal(err) + } + + rr, rerr := kv.Get(context.TODO(), "zoo") + if rerr != nil { + t.Fatal(rerr) + } + if len(rr.Kvs) != 1 { + t.Fatalf("len(rr.Kvs) expected 1, got %d", len(rr.Kvs)) + } + if rr.Kvs[0].Lease != int64(resp.ID) { + t.Fatalf("lease expected %v, got %v", resp.ID, rr.Kvs[0].Lease) + } +} + func TestKVPutWithRequireLeader(t *testing.T) { defer testutil.AfterTest(t) diff --git a/clientv3/kv.go b/clientv3/kv.go index 7b529ef76..6578dbe35 100644 --- a/clientv3/kv.go +++ b/clientv3/kv.go @@ -148,7 +148,7 @@ func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) { } case tPut: var resp *pb.PutResponse - r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue} + r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease} resp, err = kv.remote.Put(ctx, r) if err == nil { return OpResponse{put: (*PutResponse)(resp)}, nil diff --git a/clientv3/op.go b/clientv3/op.go index f917af7c8..1e58b4088 100644 --- a/clientv3/op.go +++ b/clientv3/op.go @@ -54,6 +54,7 @@ type Op struct { // for put ignoreValue bool + ignoreLease bool // progressNotify is for progress updates. progressNotify bool @@ -97,7 +98,7 @@ func (op Op) toRequestOp() *pb.RequestOp { case tRange: return &pb.RequestOp{Request: &pb.RequestOp_RequestRange{RequestRange: op.toRangeRequest()}} case tPut: - r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue} + r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease} return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}} case tDeleteRange: r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV} @@ -372,6 +373,15 @@ func WithIgnoreValue() OpOption { } } +// WithIgnoreLease updates the key using its current lease. +// Empty lease should be passed when ignore_lease is set. +// Returns an error if the key does not exist. +func WithIgnoreLease() OpOption { + return func(op *Op) { + op.ignoreLease = true + } +} + // LeaseOp represents an Operation that lease can execute. type LeaseOp struct { id LeaseID