go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/tq/txn/spanner/lessor_test.go (about) 1 // Copyright 2021 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 spanner 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/common/spantest" 26 27 "go.chromium.org/luci/server/span" 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 40 Convey("leasing works", t, func() { 41 ctx := spantest.SpannerTestContext(t, cleanupDatabase) 42 now := clock.Now(ctx).UTC() 43 ctx, tclock := testclock.UseTime(ctx, now) 44 lessor := spanLessor{} 45 46 Convey("noop for save and load", func() { 47 _, err := span.ReadWriteTransaction(ctx, func(ctx context.Context) error { 48 l := save(ctx, sectionA, now.Add(time.Minute), nil, 1) 49 So(l.LeaseID, ShouldEqual, 0) 50 51 all, err := loadAll(ctx, sectionA) 52 So(err, ShouldBeNil) 53 So(len(all), ShouldEqual, 0) 54 return nil 55 }) 56 So(err, ShouldBeNil) 57 }) 58 59 var l1, l2, l3 *lease 60 // Save 3 leases with 1, 2, 3 minutes expiry, respectively. 61 _, err := span.ReadWriteTransaction(ctx, func(ctx context.Context) error { 62 l1 = save(ctx, sectionA, now.Add(time.Minute), partition.SortedPartitions{ 63 partition.FromInts(10, 15), 64 }, 0) 65 l2 = save(ctx, sectionA, now.Add(2*time.Minute), partition.SortedPartitions{ 66 partition.FromInts(20, 25), 67 }, 1) 68 l3 = save(ctx, sectionA, now.Add(3*time.Minute), partition.SortedPartitions{ 69 partition.FromInts(30, 35), 70 }, 2) 71 l1.parts = nil 72 l2.parts = nil 73 l3.parts = nil 74 return nil 75 }) 76 So(err, ShouldBeNil) 77 78 Convey("diff shard", func() { 79 all, err := loadAll(span.Single(ctx), sectionB) 80 So(err, ShouldBeNil) 81 So(len(all), ShouldEqual, 0) 82 83 Convey("WithLease sets context deadline at lease expiry", func() { 84 i := inLease{} 85 err = lessor.WithLease(ctx, sectionB, partition.FromInts(13, 33), time.Minute, i.clbk) 86 So(err, ShouldBeNil) 87 So(i.deadline(), ShouldEqual, clock.Now(ctx).Add(time.Minute)) 88 So(i.parts(), ShouldResemble, partition.SortedPartitions{partition.FromInts(13, 33)}) 89 }) 90 91 Convey("WithLease obeys context deadline", func() { 92 ctx, cancel := clock.WithTimeout(ctx, time.Second) 93 defer cancel() 94 i := inLease{} 95 err = lessor.WithLease(ctx, sectionB, partition.FromInts(13, 33), time.Minute, i.clbk) 96 So(err, ShouldBeNil) 97 So(i.deadline(), ShouldEqual, clock.Now(ctx).Add(time.Second)) 98 }) 99 }) 100 101 Convey("only active", func() { 102 all, err := loadAll(span.Single(ctx), sectionA) 103 So(err, ShouldBeNil) 104 active, expired := activeAndExpired(ctx, all) 105 So(sortLeases(active...), ShouldResemble, sortLeases(l1, l2, l3)) 106 So(len(expired), ShouldEqual, 0) 107 108 i := inLease{} 109 err = lessor.WithLease(ctx, sectionA, partition.FromInts(13, 33), time.Minute, i.clbk) 110 So(err, ShouldBeNil) 111 So(i.parts(), ShouldResemble, partition.SortedPartitions{ 112 partition.FromInts(15, 20), 113 partition.FromInts(25, 30), 114 }) 115 116 Convey("WithLease may lease no partitions", func() { 117 i := inLease{} 118 err = lessor.WithLease(ctx, sectionA, partition.FromInts(13, 15), time.Minute, i.clbk) 119 So(err, ShouldBeNil) 120 i.assertCalled() 121 So(len(i.parts()), ShouldEqual, 0) 122 }) 123 }) 124 125 tclock.Add(90 * time.Second) 126 Convey("active and expired", func() { 127 all, err := loadAll(span.Single(ctx), sectionA) 128 So(err, ShouldBeNil) 129 active, expired := activeAndExpired(ctx, all) 130 So(sortLeases(active...), ShouldResemble, sortLeases(l2, l3)) 131 So(sortLeases(expired...), ShouldResemble, sortLeases(l1)) 132 133 i := inLease{} 134 err = lessor.WithLease(ctx, sectionA, partition.FromInts(13, 33), time.Minute, i.clbk) 135 So(err, ShouldBeNil) 136 So(i.parts(), ShouldResemble, partition.SortedPartitions{ 137 partition.FromInts(13, 20), 138 partition.FromInts(25, 30), 139 }) 140 }) 141 142 tclock.Add(90 * time.Second) 143 Convey("only expired", func() { 144 all, err := loadAll(span.Single(ctx), sectionA) 145 So(err, ShouldBeNil) 146 active, expired := activeAndExpired(ctx, all) 147 So(len(active), ShouldEqual, 0) 148 So(sortLeases(expired...), ShouldResemble, sortLeases(l1, l2, l3)) 149 150 i := inLease{} 151 err = lessor.WithLease(ctx, sectionA, partition.FromInts(13, 33), time.Minute, i.clbk) 152 So(err, ShouldBeNil) 153 So(i.parts(), ShouldResemble, partition.SortedPartitions{partition.FromInts(13, 33)}) 154 }) 155 }) 156 } 157 158 func sortLeases(ls ...*lease) []*lease { 159 sort.Slice(ls, func(i, j int) bool { return ls[i].LeaseID < ls[j].LeaseID }) 160 return ls 161 } 162 163 // inLease captures WithLeaseCB args to assert on in test. 164 type inLease struct { 165 ctx context.Context 166 sp partition.SortedPartitions 167 } 168 169 func (i *inLease) clbk(ctx context.Context, sp partition.SortedPartitions) { 170 if i.ctx != nil { 171 panic("called twice") 172 } 173 i.ctx = ctx 174 i.sp = sp 175 } 176 177 func (i *inLease) assertCalled() { 178 if i.ctx == nil { 179 panic("clbk never called") 180 } 181 } 182 183 func (i *inLease) deadline() time.Time { 184 i.assertCalled() 185 d, ok := i.ctx.Deadline() 186 if !ok { 187 panic("deadline not set") 188 } 189 return d 190 } 191 192 func (i *inLease) parts() partition.SortedPartitions { 193 i.assertCalled() 194 return i.sp 195 }