github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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) TestLeadershipNotHeld(c *gc.C) { 35 fix := &Fixture{} 36 fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) { 37 blockTest := newBlockTest(c, manager, key("redis")) 38 err := blockTest.assertUnblocked(c) 39 c.Check(err, jc.ErrorIsNil) 40 }) 41 } 42 43 func (s *WaitUntilExpiredSuite) TestLeadershipExpires(c *gc.C) { 44 fix := &Fixture{ 45 leases: map[corelease.Key]corelease.Info{ 46 key("redis"): { 47 Holder: "redis/0", 48 Expiry: offset(time.Second), 49 }, 50 }, 51 expectCalls: []call{{ 52 method: "Refresh", 53 }, { 54 method: "ExpireLease", 55 args: []interface{}{key("redis")}, 56 callback: func(leases map[corelease.Key]corelease.Info) { 57 delete(leases, key("redis")) 58 }, 59 }}, 60 } 61 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 62 blockTest := newBlockTest(c, manager, key("redis")) 63 blockTest.assertBlocked(c) 64 65 // Trigger expiry. 66 c.Assert(clock.WaitAdvance(time.Second, testing.ShortWait, 1), jc.ErrorIsNil) 67 err := blockTest.assertUnblocked(c) 68 c.Check(err, jc.ErrorIsNil) 69 }) 70 } 71 72 func (s *WaitUntilExpiredSuite) TestLeadershipChanged(c *gc.C) { 73 fix := &Fixture{ 74 leases: map[corelease.Key]corelease.Info{ 75 key("redis"): { 76 Holder: "redis/0", 77 Expiry: offset(time.Second), 78 }, 79 }, 80 expectCalls: []call{{ 81 method: "Refresh", 82 }, { 83 method: "ExpireLease", 84 args: []interface{}{key("redis")}, 85 err: corelease.ErrInvalid, 86 callback: func(leases map[corelease.Key]corelease.Info) { 87 leases[key("redis")] = corelease.Info{ 88 Holder: "redis/99", 89 Expiry: offset(time.Minute), 90 } 91 }, 92 }}, 93 } 94 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 95 blockTest := newBlockTest(c, manager, key("redis")) 96 blockTest.assertBlocked(c) 97 98 // Trigger abortive expiry. 99 clock.Advance(time.Second) 100 blockTest.assertBlocked(c) 101 }) 102 } 103 104 func (s *WaitUntilExpiredSuite) TestLeadershipExpiredEarly(c *gc.C) { 105 fix := &Fixture{ 106 leases: map[corelease.Key]corelease.Info{ 107 key("redis"): { 108 Holder: "redis/0", 109 Expiry: offset(time.Second), 110 }, 111 }, 112 expectCalls: []call{{ 113 method: "Refresh", // Called when we get inconsistent results 114 callback: func(leases map[corelease.Key]corelease.Info) { 115 delete(leases, key("redis")) 116 }, 117 }, { 118 method: "Refresh", // Called at the newly injected 'expire' test 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 // Induce a refresh by making an unexpected check; it turns out the 126 // lease had already been expired by someone else. 127 checker, err := manager.Checker("namespace", "model") 128 c.Assert(err, jc.ErrorIsNil) 129 checker.Token("redis", "redis/99").Check(0, nil) 130 // When we notice that we are out of sync, we should queue up an expiration 131 // and update of blockers after a very short timeout 132 clock.WaitAdvance(time.Millisecond, testing.ShortWait, 1) 133 err = blockTest.assertUnblocked(c) 134 c.Check(err, jc.ErrorIsNil) 135 }) 136 } 137 138 func (s *WaitUntilExpiredSuite) TestMultiple(c *gc.C) { 139 fix := &Fixture{ 140 leases: map[corelease.Key]corelease.Info{ 141 key("redis"): { 142 Holder: "redis/0", 143 Expiry: offset(time.Second), 144 }, 145 key("store"): { 146 Holder: "store/0", 147 Expiry: offset(time.Second), 148 }, 149 }, 150 expectCalls: []call{{ 151 method: "Refresh", 152 }, { 153 method: "ExpireLease", 154 args: []interface{}{key("redis")}, 155 err: corelease.ErrInvalid, 156 callback: func(leases map[corelease.Key]corelease.Info) { 157 delete(leases, key("redis")) 158 leases[key("store")] = corelease.Info{ 159 Holder: "store/9", 160 Expiry: offset(time.Minute), 161 } 162 }, 163 }, { 164 method: "ExpireLease", 165 args: []interface{}{key("store")}, 166 err: corelease.ErrInvalid, 167 }}, 168 } 169 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 170 redisTest1 := newBlockTest(c, manager, key("redis")) 171 redisTest1.assertBlocked(c) 172 redisTest2 := newBlockTest(c, manager, key("redis")) 173 redisTest2.assertBlocked(c) 174 storeTest1 := newBlockTest(c, manager, key("store")) 175 storeTest1.assertBlocked(c) 176 storeTest2 := newBlockTest(c, manager, key("store")) 177 storeTest2.assertBlocked(c) 178 179 // Induce attempted expiry; redis was expired already, store was 180 // refreshed and not expired. 181 clock.Advance(time.Second) 182 err := redisTest2.assertUnblocked(c) 183 c.Check(err, jc.ErrorIsNil) 184 err = redisTest1.assertUnblocked(c) 185 c.Check(err, jc.ErrorIsNil) 186 storeTest2.assertBlocked(c) 187 storeTest1.assertBlocked(c) 188 }) 189 } 190 191 func (s *WaitUntilExpiredSuite) TestKillManager(c *gc.C) { 192 fix := &Fixture{ 193 leases: map[corelease.Key]corelease.Info{ 194 key("redis"): { 195 Holder: "redis/0", 196 Expiry: offset(time.Second), 197 }, 198 }, 199 } 200 fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) { 201 blockTest := newBlockTest(c, manager, key("redis")) 202 blockTest.assertBlocked(c) 203 204 manager.Kill() 205 err := blockTest.assertUnblocked(c) 206 c.Check(err, gc.ErrorMatches, "lease manager stopped") 207 }) 208 } 209 210 func (s *WaitUntilExpiredSuite) TestCancelWait(c *gc.C) { 211 fix := &Fixture{ 212 leases: map[corelease.Key]corelease.Info{ 213 key("redis"): { 214 Holder: "redis/0", 215 Expiry: offset(time.Second), 216 }, 217 }, 218 } 219 fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) { 220 blockTest := newBlockTest(c, manager, key("redis")) 221 blockTest.assertBlocked(c) 222 blockTest.cancelWait() 223 224 err := blockTest.assertUnblocked(c) 225 c.Check(err, gc.Equals, corelease.ErrWaitCancelled) 226 c.Check(err, gc.ErrorMatches, "waiting for lease cancelled by client") 227 }) 228 } 229 230 // blockTest wraps a goroutine running WaitUntilExpired, and fails if it's used 231 // more than a second after creation (which should be *plenty* of time). 232 type blockTest struct { 233 manager *lease.Manager 234 done chan error 235 abort <-chan time.Time 236 cancel chan struct{} 237 } 238 239 // newBlockTest starts a test goroutine blocking until the manager confirms 240 // expiry of the named lease. 241 func newBlockTest(c *gc.C, manager *lease.Manager, key corelease.Key) *blockTest { 242 bt := &blockTest{ 243 manager: manager, 244 done: make(chan error), 245 abort: time.After(time.Second), 246 cancel: make(chan struct{}), 247 } 248 claimer, err := bt.manager.Claimer(key.Namespace, key.ModelUUID) 249 if err != nil { 250 c.Errorf("couldn't get claimer: %v", err) 251 } 252 started := make(chan struct{}) 253 go func() { 254 close(started) 255 select { 256 case <-bt.abort: 257 case bt.done <- claimer.WaitUntilExpired(key.Lease, bt.cancel): 258 case <-time.After(testing.LongWait): 259 c.Errorf("block not aborted or expired after %v", testing.LongWait) 260 } 261 }() 262 select { 263 case <-started: 264 case <-bt.abort: 265 c.Errorf("bt.aborted before even started") 266 } 267 return bt 268 } 269 270 func (bt *blockTest) cancelWait() { 271 close(bt.cancel) 272 } 273 274 func (bt *blockTest) assertBlocked(c *gc.C) { 275 select { 276 case err := <-bt.done: 277 c.Errorf("unblocked unexpectedly with %v", err) 278 case <-time.After(time.Millisecond): 279 // happy that we are still blocked, success 280 // TODO(jam): 2019-02-05 should this be testing.ShortWait? It used to be 281 // just plain 'default:', which didn't even give the helper goroutine 282 // a timeslice to start to even evaluate if WaitUntilExpired had returned. 283 } 284 } 285 286 func (bt *blockTest) assertUnblocked(c *gc.C) error { 287 select { 288 case err := <-bt.done: 289 return err 290 case <-bt.abort: 291 c.Errorf("timed out before unblocking") 292 return errors.Errorf("timed out") 293 } 294 }