github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/lease/manager_block_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lease_test 5 6 import ( 7 "time" 8 9 "github.com/juju/clock/testclock" 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 corelease "github.com/juju/juju/core/lease" 17 "github.com/juju/juju/worker/lease" 18 ) 19 20 type WaitUntilExpiredSuite struct { 21 testing.IsolationSuite 22 } 23 24 var _ = gc.Suite(&WaitUntilExpiredSuite{}) 25 26 func (s *WaitUntilExpiredSuite) SetUpTest(c *gc.C) { 27 s.IsolationSuite.SetUpTest(c) 28 logger := loggo.GetLogger("juju.worker.lease") 29 logger.SetLogLevel(loggo.TRACE) 30 logger = loggo.GetLogger("lease_test") 31 logger.SetLogLevel(loggo.TRACE) 32 } 33 34 func (s *WaitUntilExpiredSuite) TestLeadershipNoLeaseBlockEvaluatedNextTick(c *gc.C) { 35 fix := &Fixture{ 36 leases: map[corelease.Key]corelease.Info{ 37 key("postgresql"): { 38 Holder: "postgresql/0", 39 Expiry: offset(time.Second), 40 }, 41 }, 42 } 43 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 44 blockTest := newBlockTest(c, manager, key("redis")) 45 blockTest.assertBlocked(c) 46 47 // Check that *another* lease expiry causes the unassociated block to 48 // be checked and in the absence of its lease, get unblocked. 49 c.Assert(clock.WaitAdvance(2*time.Second, testing.ShortWait, 1), jc.ErrorIsNil) 50 err := blockTest.assertUnblocked(c) 51 c.Check(err, jc.ErrorIsNil) 52 }) 53 } 54 55 func (s *WaitUntilExpiredSuite) TestLeadershipExpires(c *gc.C) { 56 fix := &Fixture{ 57 leases: map[corelease.Key]corelease.Info{ 58 key("redis"): { 59 Holder: "redis/0", 60 Expiry: offset(time.Second), 61 }, 62 }, 63 } 64 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 65 blockTest := newBlockTest(c, manager, key("redis")) 66 blockTest.assertBlocked(c) 67 68 // Trigger expiry. 69 c.Assert(clock.WaitAdvance(2*time.Second, testing.ShortWait, 1), jc.ErrorIsNil) 70 err := blockTest.assertUnblocked(c) 71 c.Check(err, jc.ErrorIsNil) 72 }) 73 } 74 75 func (s *WaitUntilExpiredSuite) TestBlockChecksRescheduled(c *gc.C) { 76 fix := &Fixture{ 77 leases: map[corelease.Key]corelease.Info{ 78 key("postgresql"): { 79 Holder: "postgresql/0", 80 Expiry: offset(time.Second), 81 }, 82 key("mysql"): { 83 Holder: "mysql/0", 84 Expiry: offset(4 * time.Second), 85 }, 86 key("redis"): { 87 Holder: "redis/0", 88 Expiry: offset(7 * time.Second), 89 }, 90 }, 91 } 92 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 93 blockTest := newBlockTest(c, manager, key("redis")) 94 blockTest.assertBlocked(c) 95 96 // Advance past the first expiry. 97 c.Assert(clock.WaitAdvance(3*time.Second, testing.ShortWait, 1), jc.ErrorIsNil) 98 blockTest.assertBlocked(c) 99 100 // Advance past the second expiry. We should have had a check scheduled. 101 c.Assert(clock.WaitAdvance(3*time.Second, testing.ShortWait, 1), jc.ErrorIsNil) 102 blockTest.assertBlocked(c) 103 104 // Advance past the last expiry. We should have had a check scheduled 105 // that causes the redis lease to be unblocked. 106 c.Assert(clock.WaitAdvance(3*time.Second, testing.ShortWait, 1), jc.ErrorIsNil) 107 err := blockTest.assertUnblocked(c) 108 c.Check(err, jc.ErrorIsNil) 109 }) 110 } 111 112 func (s *WaitUntilExpiredSuite) TestLeadershipChanged(c *gc.C) { 113 fix := &Fixture{ 114 leases: map[corelease.Key]corelease.Info{ 115 key("redis"): { 116 Holder: "redis/0", 117 Expiry: offset(time.Second), 118 }, 119 }, 120 } 121 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 122 blockTest := newBlockTest(c, manager, key("redis")) 123 blockTest.assertBlocked(c) 124 125 // Trigger abortive expiry. 126 clock.Advance(time.Second) 127 blockTest.assertBlocked(c) 128 }) 129 } 130 131 func (s *WaitUntilExpiredSuite) TestLeadershipExpiredEarly(c *gc.C) { 132 fix := &Fixture{ 133 leases: map[corelease.Key]corelease.Info{ 134 // The lease is held by an entity other than the checker. 135 key("redis"): { 136 Holder: "redis/0", 137 Expiry: offset(time.Second), 138 }, 139 }, 140 } 141 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 142 blockTest := newBlockTest(c, manager, key("redis")) 143 blockTest.assertBlocked(c) 144 145 // Induce a scheduled block check by making an unexpected check; 146 // it turns out the lease had already been expired by someone else. 147 checker, err := manager.Checker("namespace", "model") 148 c.Assert(err, jc.ErrorIsNil) 149 err = checker.Token("redis", "redis/99").Check() 150 c.Assert(err, gc.ErrorMatches, "lease not held") 151 152 // Simulate the delayed synchronisation by removing the lease. 153 delete(fix.leases, key("redis")) 154 155 // When we notice that we are out of sync, we should queue up an 156 // expiration and update of blockers after a very short timeout. 157 err = clock.WaitAdvance(time.Second, testing.ShortWait, 1) 158 c.Assert(err, jc.ErrorIsNil) 159 160 err = blockTest.assertUnblocked(c) 161 c.Check(err, jc.ErrorIsNil) 162 }) 163 } 164 165 func (s *WaitUntilExpiredSuite) TestMultiple(c *gc.C) { 166 fix := &Fixture{ 167 leases: map[corelease.Key]corelease.Info{ 168 key("redis"): { 169 Holder: "redis/0", 170 Expiry: offset(2 * time.Second), 171 }, 172 key("store"): { 173 Holder: "store/0", 174 Expiry: offset(2 * time.Second), 175 }, 176 }, 177 } 178 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 179 redisTest1 := newBlockTest(c, manager, key("redis")) 180 redisTest1.assertBlocked(c) 181 redisTest2 := newBlockTest(c, manager, key("redis")) 182 redisTest2.assertBlocked(c) 183 storeTest1 := newBlockTest(c, manager, key("store")) 184 storeTest1.assertBlocked(c) 185 storeTest2 := newBlockTest(c, manager, key("store")) 186 storeTest2.assertBlocked(c) 187 188 // Induce a scheduled block check by making an unexpected check. 189 checker, err := manager.Checker("namespace", "model") 190 c.Assert(err, jc.ErrorIsNil) 191 err = checker.Token("redis", "redis/99").Check() 192 c.Assert(err, gc.ErrorMatches, "lease not held") 193 194 // Deleting the redis lease should cause unblocks for the redis 195 // blockers, but the store blocks should remain. 196 delete(fix.leases, key("redis")) 197 198 err = clock.WaitAdvance(time.Second, testing.ShortWait, 1) 199 c.Assert(err, jc.ErrorIsNil) 200 201 err = redisTest2.assertUnblocked(c) 202 c.Check(err, jc.ErrorIsNil) 203 204 err = redisTest1.assertUnblocked(c) 205 c.Check(err, jc.ErrorIsNil) 206 207 storeTest2.assertBlocked(c) 208 storeTest1.assertBlocked(c) 209 }) 210 } 211 212 func (s *WaitUntilExpiredSuite) TestKillManager(c *gc.C) { 213 fix := &Fixture{ 214 leases: map[corelease.Key]corelease.Info{ 215 key("redis"): { 216 Holder: "redis/0", 217 Expiry: offset(time.Second), 218 }, 219 }, 220 } 221 fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) { 222 blockTest := newBlockTest(c, manager, key("redis")) 223 blockTest.assertBlocked(c) 224 225 manager.Kill() 226 err := blockTest.assertUnblocked(c) 227 c.Check(err, gc.ErrorMatches, "lease manager stopped") 228 }) 229 } 230 231 func (s *WaitUntilExpiredSuite) TestCancelWait(c *gc.C) { 232 fix := &Fixture{ 233 leases: map[corelease.Key]corelease.Info{ 234 key("redis"): { 235 Holder: "redis/0", 236 Expiry: offset(time.Second), 237 }, 238 }, 239 } 240 fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) { 241 blockTest := newBlockTest(c, manager, key("redis")) 242 blockTest.assertBlocked(c) 243 blockTest.cancelWait() 244 245 err := blockTest.assertUnblocked(c) 246 c.Check(err, gc.Equals, corelease.ErrWaitCancelled) 247 c.Check(err, gc.ErrorMatches, "waiting for lease cancelled by client") 248 }) 249 } 250 251 // blockTest wraps a goroutine running WaitUntilExpired, and fails if it's used 252 // more than a second after creation (which should be *plenty* of time). 253 type blockTest struct { 254 manager *lease.Manager 255 done chan error 256 abort <-chan time.Time 257 cancel chan struct{} 258 } 259 260 // newBlockTest starts a test goroutine blocking until the manager confirms 261 // expiry of the named lease. 262 func newBlockTest(c *gc.C, manager *lease.Manager, key corelease.Key) *blockTest { 263 bt := &blockTest{ 264 manager: manager, 265 done: make(chan error), 266 abort: time.After(time.Second), 267 cancel: make(chan struct{}), 268 } 269 claimer, err := bt.manager.Claimer(key.Namespace, key.ModelUUID) 270 if err != nil { 271 c.Errorf("couldn't get claimer: %v", err) 272 } 273 started := make(chan struct{}) 274 go func() { 275 close(started) 276 select { 277 case <-bt.abort: 278 case bt.done <- claimer.WaitUntilExpired(key.Lease, bt.cancel): 279 case <-time.After(testing.LongWait): 280 c.Errorf("block not aborted or expired after %v", testing.LongWait) 281 } 282 }() 283 select { 284 case <-started: 285 case <-bt.abort: 286 c.Errorf("bt.aborted before even started") 287 } 288 return bt 289 } 290 291 func (bt *blockTest) cancelWait() { 292 close(bt.cancel) 293 } 294 295 func (bt *blockTest) assertBlocked(c *gc.C) { 296 select { 297 case err := <-bt.done: 298 c.Errorf("unblocked unexpectedly with %v", err) 299 case <-time.After(testing.ShortWait): 300 // Happy that we are still blocked; success. 301 } 302 } 303 304 func (bt *blockTest) assertUnblocked(c *gc.C) error { 305 lease.ManagerStore(bt.manager).(*Store).expireLeases() 306 select { 307 case err := <-bt.done: 308 return err 309 case <-bt.abort: 310 c.Errorf("timed out before unblocking") 311 return errors.Errorf("timed out") 312 } 313 }