From e1617f98ba91f0442cdef20d1cbf3e28e8427c45 Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz Date: Fri, 1 Sep 2023 09:55:13 +0200 Subject: [PATCH] server: Test txn checking Signed-off-by: Marek Siarkowicz --- server/etcdserver/txn/txn_test.go | 238 ++++++++++++++++++++++++++++++ server/lease/lessor.go | 16 +- 2 files changed, 251 insertions(+), 3 deletions(-) diff --git a/server/etcdserver/txn/txn_test.go b/server/etcdserver/txn/txn_test.go index 52937a8a0..fd4fa9128 100644 --- a/server/etcdserver/txn/txn_test.go +++ b/server/etcdserver/txn/txn_test.go @@ -22,6 +22,8 @@ import ( "go.uber.org/zap/zaptest" + "go.etcd.io/etcd/pkg/v3/traceutil" + "go.etcd.io/etcd/api/v3/authpb" pb "go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/server/v3/auth" @@ -35,6 +37,242 @@ import ( "github.com/stretchr/testify/require" ) +func TestCheckTxn(t *testing.T) { + + var futureRev int64 = 1000 + + tcs := []struct { + name string + compactRevision int64 + setupLease int64 + setupKey []byte + txn *pb.TxnRequest + + expectError string + }{ + { + name: "Range with revision 0 should succeed", + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestRange{ + RequestRange: &pb.RangeRequest{ + Revision: 0, + }, + }, + }, + }, + }, + }, + { + name: "Range on future rev should fail", + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestRange{ + RequestRange: &pb.RangeRequest{ + Revision: futureRev, + }, + }, + }, + }, + }, + expectError: "mvcc: required revision is a future revision", + }, + { + name: "Range on compacted rev should fail", + compactRevision: 10, + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestRange{ + RequestRange: &pb.RangeRequest{ + Revision: 9, + }, + }, + }, + }, + }, + expectError: "mvcc: required revision has been compacted", + }, + { + name: "Invalid range on Failed path should succeed", + txn: &pb.TxnRequest{ + Failure: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestRange{ + RequestRange: &pb.RangeRequest{ + Revision: futureRev, + }, + }, + }, + }, + }, + }, + { + name: "Invalid range subtransaction should fail", + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestTxn{ + RequestTxn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestRange{ + RequestRange: &pb.RangeRequest{ + Revision: futureRev, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectError: "mvcc: required revision is a future revision", + }, + { + name: "Put without lease should succeed", + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{}, + }, + }, + }, + }, + }, + { + name: "Put with non-existing lease should fail", + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{ + Lease: 123, + }, + }, + }, + }, + }, + expectError: "lease not found", + }, + { + name: "Put with existing lease should succeed", + setupLease: 123, + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{ + Lease: 123, + }, + }, + }, + }, + }, + }, + { + name: "Put with ignore value without previous key should fail", + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{ + IgnoreValue: true, + }, + }, + }, + }, + }, + expectError: "etcdserver: key not found", + }, + { + name: "Put with ignore lease without previous key should fail", + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{ + IgnoreLease: true, + }, + }, + }, + }, + }, + expectError: "etcdserver: key not found", + }, + { + name: "Put with ignore value with previous key should succeeded", + setupKey: []byte("ignore-value"), + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{ + IgnoreValue: true, + Key: []byte("ignore-value"), + }, + }, + }, + }, + }, + }, + { + name: "Put with ignore lease with previous key should succeed ", + setupKey: []byte("ignore-lease"), + txn: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{ + IgnoreLease: true, + Key: []byte("ignore-lease"), + }, + }, + }, + }, + }, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + b, _ := betesting.NewDefaultTmpBackend(t) + defer betesting.Close(t, b) + lessor := &lease.FakeLessor{LeaseSet: map[lease.LeaseID]struct{}{}} + s := mvcc.NewStore(zaptest.NewLogger(t), b, lessor, mvcc.StoreConfig{}) + defer s.Close() + + if tc.compactRevision != 0 { + for i := 0; int64(i) < tc.compactRevision; i++ { + s.Put([]byte("a"), []byte("b"), 0) + } + s.Compact(traceutil.TODO(), tc.compactRevision) + } + if tc.setupLease != 0 { + lessor.Grant(lease.LeaseID(tc.setupLease), 0) + } + if len(tc.setupKey) != 0 { + s.Put(tc.setupKey, []byte("b"), 0) + } + + _, _, err := Txn(ctx, zaptest.NewLogger(t), tc.txn, false, s, lessor) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + if gotErr != tc.expectError { + t.Errorf("Error not matching, got %q, expected %q", gotErr, tc.expectError) + } + }) + } +} + func TestReadonlyTxnError(t *testing.T) { b, _ := betesting.NewDefaultTmpBackend(t) defer betesting.Close(t, b) diff --git a/server/lease/lessor.go b/server/lease/lessor.go index 8aa1e6e87..db738efcc 100644 --- a/server/lease/lessor.go +++ b/server/lease/lessor.go @@ -816,13 +816,18 @@ func (le *lessor) initAndRecover() { // FakeLessor is a fake implementation of Lessor interface. // Used for testing only. -type FakeLessor struct{} +type FakeLessor struct { + LeaseSet map[LeaseID]struct{} +} func (fl *FakeLessor) SetRangeDeleter(dr RangeDeleter) {} func (fl *FakeLessor) SetCheckpointer(cp Checkpointer) {} -func (fl *FakeLessor) Grant(id LeaseID, ttl int64) (*Lease, error) { return nil, nil } +func (fl *FakeLessor) Grant(id LeaseID, ttl int64) (*Lease, error) { + fl.LeaseSet[id] = struct{}{} + return nil, nil +} func (fl *FakeLessor) Revoke(id LeaseID) error { return nil } @@ -839,7 +844,12 @@ func (fl *FakeLessor) Demote() {} func (fl *FakeLessor) Renew(id LeaseID) (int64, error) { return 10, nil } -func (fl *FakeLessor) Lookup(id LeaseID) *Lease { return nil } +func (fl *FakeLessor) Lookup(id LeaseID) *Lease { + if _, ok := fl.LeaseSet[id]; ok { + return &Lease{ID: id} + } + return nil +} func (fl *FakeLessor) Leases() []*Lease { return nil }