go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/common/lease/lease_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 lease 16 17 import ( 18 "context" 19 "math/rand" 20 "testing" 21 "time" 22 23 "go.chromium.org/luci/common/clock" 24 "go.chromium.org/luci/common/data/rand/mathrand" 25 "go.chromium.org/luci/common/errors" 26 "go.chromium.org/luci/common/retry" 27 28 "go.chromium.org/luci/cv/internal/cvtesting" 29 30 . "github.com/smartystreets/goconvey/convey" 31 . "go.chromium.org/luci/common/testing/assertions" 32 ) 33 34 func TestLease(t *testing.T) { 35 t.Parallel() 36 37 Convey("Apply", t, func() { 38 ct := cvtesting.Test{} 39 ctx, cancel := ct.SetUp(t) 40 defer cancel() 41 ctx = mathrand.Set(ctx, rand.New(rand.NewSource(1))) 42 43 rid := ResourceID("foo/1") 44 now := clock.Now(ctx).UTC().Truncate(time.Second) 45 application := Application{ 46 ResourceID: rid, 47 Holder: "holder", 48 Payload: []byte("Hey!"), 49 ExpireTime: now.Add(1 * time.Minute), 50 } 51 Convey("Works for new lease", func() { 52 actual, err := Apply(ctx, application) 53 So(err, ShouldBeNil) 54 expected := &Lease{ 55 ResourceID: rid, 56 Holder: "holder", 57 Payload: []byte("Hey!"), 58 ExpireTime: now.Add(1 * time.Minute), 59 Token: []byte{82, 253, 252, 7, 33, 130, 101, 79}, 60 } 61 So(actual, ShouldResemble, expected) 62 So(mustLoadLease(ctx, rid), ShouldResemble, expected) 63 64 Convey("Returns AlreadyInLeaseErr if existing lease is still active", func() { 65 ct.Clock.Add(30 * time.Second) // lease expires after 1 minute 66 _, err := Apply(ctx, application) 67 So(err, ShouldErrLike, &AlreadyInLeaseErr{ 68 ExpireTime: now.Add(1 * time.Minute), // original lease expiry time 69 Holder: "holder", 70 ResourceID: rid, 71 }) 72 }) 73 74 Convey("Succeed if existing lease has expired", func() { 75 ct.Clock.Add(2 * time.Minute) // lease expires after 1 minute 76 now := clock.Now(ctx).UTC().Truncate(time.Second) 77 application := Application{ 78 ResourceID: rid, 79 Holder: "holder2", 80 Payload: []byte("Heyo!"), 81 ExpireTime: now.Add(1 * time.Minute), 82 } 83 actual, err := Apply(ctx, application) 84 So(err, ShouldBeNil) 85 expected := &Lease{ 86 ResourceID: rid, 87 Holder: "holder2", 88 Payload: []byte("Heyo!"), 89 ExpireTime: now.Add(1 * time.Minute), 90 Token: []byte{22, 63, 95, 15, 154, 98, 29, 114}, 91 } 92 So(actual, ShouldResemble, expected) 93 So(mustLoadLease(ctx, rid), ShouldResemble, expected) 94 }) 95 }) 96 97 Convey("Truncates to millisecond", func() { 98 application.ExpireTime = now.Add(1 * time.Minute).Add(3141593 * time.Nanosecond) // 3.141593 ms 99 actual, err := Apply(ctx, application) 100 So(err, ShouldBeNil) 101 expected := &Lease{ 102 ResourceID: rid, 103 Holder: "holder", 104 Payload: []byte("Hey!"), 105 ExpireTime: now.Add(1 * time.Minute).Add(3 * time.Millisecond), 106 Token: []byte{82, 253, 252, 7, 33, 130, 101, 79}, 107 } 108 So(actual, ShouldResemble, expected) 109 }) 110 }) 111 112 Convey("Terminate", t, func() { 113 ct := cvtesting.Test{} 114 ctx, cancel := ct.SetUp(t) 115 defer cancel() 116 117 rid := ResourceID("foo/1") 118 now := clock.Now(ctx).UTC().Truncate(time.Second) 119 l, err := Apply(ctx, Application{ 120 ResourceID: rid, 121 Holder: "holder", 122 ExpireTime: now.Add(1 * time.Minute), 123 }) 124 So(err, ShouldBeNil) 125 126 Convey("Works", func() { 127 err := l.Terminate(ctx) 128 So(err, ShouldBeNil) 129 So(mustLoadLease(ctx, rid), ShouldBeNil) 130 131 Convey("No-op if lease is already terminated", func() { 132 err := l.Terminate(ctx) 133 So(err, ShouldBeNil) 134 So(mustLoadLease(ctx, rid), ShouldBeNil) 135 }) 136 }) 137 138 Convey("Errors if lease is not current", func() { 139 ct.Clock.Add(2 * time.Minute) 140 now := clock.Now(ctx).UTC().Truncate(time.Second) 141 _, err := Apply(ctx, Application{ 142 ResourceID: rid, 143 Holder: "holder2", 144 ExpireTime: now.Add(1 * time.Minute), 145 }) 146 So(err, ShouldBeNil) 147 So(l.Terminate(ctx), ShouldResemble, &AlreadyInLeaseErr{ 148 ExpireTime: now.Add(1 * time.Minute), // original lease expiry time 149 Holder: "holder2", 150 ResourceID: rid, 151 }) 152 }) 153 }) 154 155 Convey("Extend", t, func() { 156 ct := cvtesting.Test{} 157 ctx, cancel := ct.SetUp(t) 158 defer cancel() 159 ctx = mathrand.Set(ctx, rand.New(rand.NewSource(1))) 160 161 rid := ResourceID("foo/1") 162 now := clock.Now(ctx).UTC().Truncate(time.Second) 163 l, err := Apply(ctx, Application{ 164 ResourceID: rid, 165 Holder: "holder", 166 ExpireTime: now.Add(1 * time.Minute), 167 Payload: []byte("stuff"), 168 }) 169 So(err, ShouldBeNil) 170 171 Convey("Works", func() { 172 err := l.Extend(ctx, 1*time.Minute) 173 So(err, ShouldBeNil) 174 expected := &Lease{ 175 ResourceID: rid, 176 Holder: "holder", 177 ExpireTime: now.Add(2 * time.Minute), 178 Token: []byte{22, 63, 95, 15, 154, 98, 29, 114}, 179 Payload: []byte("stuff"), 180 } 181 So(l, ShouldResemble, expected) 182 So(mustLoadLease(ctx, rid), ShouldResemble, expected) 183 }) 184 185 Convey("Truncates to millisecond", func() { 186 err := l.Extend(ctx, 3141593*time.Nanosecond) // 3.141593 ms 187 So(err, ShouldBeNil) 188 expected := &Lease{ 189 ResourceID: rid, 190 Holder: "holder", 191 ExpireTime: now.Add(1 * time.Minute).Add(3 * time.Millisecond), 192 Token: []byte{22, 63, 95, 15, 154, 98, 29, 114}, 193 Payload: []byte("stuff"), 194 } 195 So(l, ShouldResemble, expected) 196 }) 197 198 Convey("Errors if lease has expired", func() { 199 ct.Clock.Add(2 * time.Minute) 200 err := l.Extend(ctx, 1*time.Minute) 201 So(err, ShouldErrLike, "can't extend an expired lease") 202 }) 203 204 Convey("Errors if lease doesn't exist in Datastore", func() { 205 ct.Clock.Add(30 * time.Second) 206 So(l.Terminate(ctx), ShouldBeNil) 207 err := l.Extend(ctx, 1*time.Minute) 208 So(err, ShouldErrLike, "target lease doesn't exist in datastore") 209 }) 210 211 Convey("Errors if lease is not current in Datastore", func() { 212 ct.Clock.Add(30 * time.Second) 213 So(l.Terminate(ctx), ShouldBeNil) 214 _, err := Apply(ctx, Application{ 215 ResourceID: rid, 216 Holder: "holder2", 217 ExpireTime: now.UTC().Add(1 * time.Minute), 218 }) 219 So(err, ShouldBeNil) 220 err = l.Extend(ctx, 1*time.Minute) 221 So(err, ShouldResemble, &AlreadyInLeaseErr{ 222 ExpireTime: now.Add(1 * time.Minute), 223 Holder: "holder2", 224 ResourceID: rid, 225 }) 226 }) 227 }) 228 } 229 230 func mustLoadLease(ctx context.Context, rid ResourceID) *Lease { 231 ret, err := Load(ctx, rid) 232 if err != nil { 233 panic(err) 234 } 235 return ret 236 } 237 238 func TestRetryIfLeased(t *testing.T) { 239 t.Parallel() 240 241 Convey("RetryIfLeased", t, func() { 242 ct := cvtesting.Test{} 243 ctx, cancel := ct.SetUp(t) 244 defer cancel() 245 246 innerDelay, leaseDur := 2*time.Minute, 3*time.Minute 247 innerPolicy := func() retry.Iterator { 248 return &retry.Limited{ 249 Delay: innerDelay, 250 Retries: 1, 251 } 252 } 253 expire := clock.Now(ctx).UTC().Truncate(time.Second).Add(leaseDur) 254 leaseErr := &AlreadyInLeaseErr{ExpireTime: expire} 255 notLeaseErr := errors.New("successful error") 256 257 Convey("with nil inner", func() { 258 it := RetryIfLeased(nil)() 259 260 Convey("returns stop, if not AlreadyInLeaseErr", func() { 261 So(it.Next(ctx, notLeaseErr), ShouldEqual, retry.Stop) 262 }) 263 Convey("returns the lease expiration, if AlreadyInLeaseErr", func() { 264 So(it.Next(ctx, leaseErr).Truncate(time.Second), ShouldEqual, leaseDur) 265 }) 266 }) 267 268 Convey("with limited inner", func() { 269 it := RetryIfLeased(innerPolicy)() 270 271 Convey("returns inner.next(), if not AlreadyInLeaseErr", func() { 272 So(it.Next(ctx, notLeaseErr), ShouldEqual, innerDelay) 273 So(it.Next(ctx, notLeaseErr), ShouldEqual, retry.Stop) 274 }) 275 Convey("returns whichever comes earlier", func() { 276 Convey("inner.Delay < expiration", func() { 277 So(it.Next(ctx, leaseErr), ShouldEqual, innerDelay) 278 }) 279 Convey("inner.Delay > expiration", func() { 280 it.(*retryIfLeasedIterator).inner.(*retry.Limited).Delay *= 2 281 So(it.Next(ctx, leaseErr), ShouldEqual, leaseDur) 282 }) 283 Convey("inner.Delay > 0 but inner returns stop", func() { 284 So(it.Next(ctx, notLeaseErr), ShouldEqual, innerDelay) 285 So(it.Next(ctx, leaseErr), ShouldEqual, retry.Stop) 286 }) 287 }) 288 }) 289 }) 290 }