go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/tq/txn/datastore/lessor_test.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package datastore 16 17 import ( 18 "context" 19 "sort" 20 "testing" 21 "time" 22 23 "go.chromium.org/luci/common/clock" 24 "go.chromium.org/luci/common/clock/testclock" 25 "go.chromium.org/luci/gae/impl/memory" 26 ds "go.chromium.org/luci/gae/service/datastore" 27 28 "go.chromium.org/luci/server/tq/internal/partition" 29 30 . "github.com/smartystreets/goconvey/convey" 31 ) 32 33 const ( 34 sectionA = "sectionA" 35 sectionB = "sectionB" 36 ) 37 38 func TestLeasing(t *testing.T) { 39 t.Parallel() 40 41 epoch := ds.RoundTime(testclock.TestRecentTimeLocal) 42 43 Convey("leasing works", t, func() { 44 ctx := memory.Use(context.Background()) 45 ctx, tclock := testclock.UseTime(ctx, epoch) 46 lessor := dsLessor{} 47 48 Convey("noop for save and load", func() { 49 l, err := save(ctx, sectionA, epoch.Add(time.Minute), nil) 50 So(err, ShouldBeNil) 51 So(l.Id, ShouldEqual, 0) 52 53 active, expired, err := loadAll(ctx, sectionA) 54 So(err, ShouldBeNil) 55 So(len(active)+len(expired), ShouldEqual, 0) 56 }) 57 58 // Save 3 leases with 1, 2, 3 minutes expiry, respectively. 59 l1, err := save(ctx, sectionA, epoch.Add(time.Minute), partition.SortedPartitions{ 60 partition.FromInts(10, 15), 61 }) 62 So(err, ShouldBeNil) 63 l2, err := save(ctx, sectionA, epoch.Add(2*time.Minute), partition.SortedPartitions{ 64 partition.FromInts(20, 25), 65 }) 66 So(err, ShouldBeNil) 67 l3, err := save(ctx, sectionA, epoch.Add(3*time.Minute), partition.SortedPartitions{ 68 partition.FromInts(30, 35), 69 }) 70 So(err, ShouldBeNil) 71 l1.parts = nil 72 l2.parts = nil 73 l3.parts = nil 74 75 Convey("diff shard", func() { 76 active, expired, err := loadAll(ctx, sectionB) 77 So(err, ShouldBeNil) 78 So(len(active)+len(expired), ShouldEqual, 0) 79 80 Convey("WithLease sets context deadline at lease expiry", func() { 81 i := inLease{} 82 err = lessor.WithLease(ctx, sectionB, partition.FromInts(13, 33), time.Minute, i.clbk) 83 So(err, ShouldBeNil) 84 So(i.deadline(), ShouldEqual, clock.Now(ctx).Add(time.Minute)) 85 So(i.parts(), ShouldResemble, partition.SortedPartitions{partition.FromInts(13, 33)}) 86 }) 87 88 Convey("WithLease obeys context deadline", func() { 89 ctx, cancel := clock.WithTimeout(ctx, time.Second) 90 defer cancel() 91 i := inLease{} 92 err = lessor.WithLease(ctx, sectionB, partition.FromInts(13, 33), time.Minute, i.clbk) 93 So(err, ShouldBeNil) 94 So(i.deadline(), ShouldEqual, clock.Now(ctx).Add(time.Second)) 95 }) 96 }) 97 98 Convey("only active", func() { 99 active, expired, err := loadAll(ctx, sectionA) 100 So(err, ShouldBeNil) 101 So(sortLeases(active...), ShouldResemble, sortLeases(l1, l2, l3)) 102 So(len(expired), ShouldEqual, 0) 103 104 i := inLease{} 105 err = lessor.WithLease(ctx, sectionA, partition.FromInts(13, 33), time.Minute, i.clbk) 106 So(err, ShouldBeNil) 107 So(i.parts(), ShouldResemble, partition.SortedPartitions{ 108 partition.FromInts(15, 20), 109 partition.FromInts(25, 30), 110 }) 111 112 Convey("WithLease may lease no partitions", func() { 113 i := inLease{} 114 err = lessor.WithLease(ctx, sectionA, partition.FromInts(13, 15), time.Minute, i.clbk) 115 So(err, ShouldBeNil) 116 i.assertCalled() 117 So(len(i.parts()), ShouldEqual, 0) 118 }) 119 }) 120 121 tclock.Add(90 * time.Second) 122 Convey("active and expired", func() { 123 active, expired, err := loadAll(ctx, sectionA) 124 So(err, ShouldBeNil) 125 So(sortLeases(active...), ShouldResemble, sortLeases(l2, l3)) 126 So(sortLeases(expired...), ShouldResemble, sortLeases(l1)) 127 128 i := inLease{} 129 err = lessor.WithLease(ctx, sectionA, partition.FromInts(13, 33), time.Minute, i.clbk) 130 So(err, ShouldBeNil) 131 So(i.parts(), ShouldResemble, partition.SortedPartitions{ 132 partition.FromInts(13, 20), 133 partition.FromInts(25, 30), 134 }) 135 }) 136 137 tclock.Add(90 * time.Second) 138 Convey("only expired", func() { 139 active, expired, err := loadAll(ctx, sectionA) 140 So(err, ShouldBeNil) 141 So(len(active), ShouldEqual, 0) 142 So(sortLeases(expired...), ShouldResemble, sortLeases(l1, l2, l3)) 143 144 i := inLease{} 145 err = lessor.WithLease(ctx, sectionA, partition.FromInts(13, 33), time.Minute, i.clbk) 146 So(err, ShouldBeNil) 147 So(i.parts(), ShouldResemble, partition.SortedPartitions{partition.FromInts(13, 33)}) 148 }) 149 }) 150 } 151 152 func sortLeases(ls ...*lease) []*lease { 153 sort.Slice(ls, func(i, j int) bool { return ls[i].Id < ls[j].Id }) 154 return ls 155 } 156 157 // inLease captures WithLeaseCB args to assert on in test. 158 type inLease struct { 159 ctx context.Context 160 sp partition.SortedPartitions 161 } 162 163 func (i *inLease) clbk(ctx context.Context, sp partition.SortedPartitions) { 164 if i.ctx != nil { 165 panic("called twice") 166 } 167 i.ctx = ctx 168 i.sp = sp 169 } 170 171 func (i *inLease) assertCalled() { 172 if i.ctx == nil { 173 panic("clbk never called") 174 } 175 } 176 177 func (i *inLease) deadline() time.Time { 178 i.assertCalled() 179 d, ok := i.ctx.Deadline() 180 if !ok { 181 panic("deadline not set") 182 } 183 return d 184 } 185 186 func (i *inLease) parts() partition.SortedPartitions { 187 i.assertCalled() 188 return i.sp 189 }