github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/leadership/tracker_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package leadership_test 5 6 import ( 7 "time" 8 9 "github.com/juju/clock/testclock" 10 "github.com/juju/errors" 11 "github.com/juju/names/v5" 12 "github.com/juju/testing" 13 "github.com/juju/worker/v3" 14 "github.com/juju/worker/v3/workertest" 15 gc "gopkg.in/check.v1" 16 17 coreleadership "github.com/juju/juju/core/leadership" 18 coretesting "github.com/juju/juju/testing" 19 "github.com/juju/juju/worker/leadership" 20 ) 21 22 type TrackerSuite struct { 23 testing.IsolationSuite 24 unitTag names.UnitTag 25 claimer *StubClaimer 26 clock *testclock.Clock 27 } 28 29 var _ = gc.Suite(&TrackerSuite{}) 30 31 const ( 32 trackerDuration = 30 * time.Second 33 leaseDuration = trackerDuration * 2 34 ) 35 36 func (s *TrackerSuite) refreshes(count int) { 37 halfDuration := trackerDuration / 2 38 halfRefreshes := (2 * count) + 1 39 // The worker often checks against the current time 40 // and adds delay to that time. Here we advance the clock 41 // in small jumps, and then wait a short time to allow the 42 // worker to do stuff. 43 for i := 0; i < halfRefreshes; i++ { 44 s.clock.Advance(halfDuration) 45 <-time.After(coretesting.ShortWait) 46 } 47 } 48 49 func (s *TrackerSuite) SetUpTest(c *gc.C) { 50 s.IsolationSuite.SetUpTest(c) 51 s.unitTag = names.NewUnitTag("led-service/123") 52 s.clock = testclock.NewClock(time.Date(2016, 10, 9, 12, 0, 0, 0, time.UTC)) 53 s.claimer = &StubClaimer{ 54 Stub: &testing.Stub{}, 55 releases: make(chan struct{}), 56 } 57 } 58 59 func (s *TrackerSuite) unblockRelease(c *gc.C) { 60 select { 61 case s.claimer.releases <- struct{}{}: 62 case <-time.After(coretesting.LongWait): 63 c.Fatalf("did nobody call BlockUntilLeadershipReleased?") 64 } 65 } 66 67 func (s *TrackerSuite) newTrackerInner() *leadership.Tracker { 68 return leadership.NewTracker(s.unitTag, s.claimer, s.clock, trackerDuration) 69 } 70 71 func (s *TrackerSuite) newTracker() *leadership.Tracker { 72 tracker := s.newTrackerInner() 73 s.AddCleanup(func(c *gc.C) { 74 workertest.CleanKill(c, tracker) 75 }) 76 return tracker 77 } 78 79 func (s *TrackerSuite) newTrackerDirtyKill() *leadership.Tracker { 80 tracker := s.newTrackerInner() 81 s.AddCleanup(func(c *gc.C) { 82 workertest.DirtyKill(c, tracker) 83 }) 84 return tracker 85 } 86 87 func (s *TrackerSuite) TestApplicationName(c *gc.C) { 88 tracker := s.newTracker() 89 c.Assert(tracker.ApplicationName(), gc.Equals, "led-service") 90 } 91 92 func (s *TrackerSuite) TestOnLeaderSuccess(c *gc.C) { 93 tracker := s.newTracker() 94 95 // Check the ticket succeeds. 96 assertClaimLeader(c, tracker, true) 97 98 // Stop the tracker before trying to look at its stub. 99 workertest.CleanKill(c, tracker) 100 s.claimer.CheckCalls(c, []testing.StubCall{{ 101 FuncName: "ClaimLeadership", 102 Args: []interface{}{ 103 "led-service", "led-service/123", leaseDuration, 104 }, 105 }}) 106 } 107 108 func (s *TrackerSuite) TestOnLeaderFailure(c *gc.C) { 109 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil) 110 tracker := s.newTracker() 111 112 // Check the ticket fails. 113 assertClaimLeader(c, tracker, false) 114 115 // wait for calls to stabilize before killing the worker and inspecting the calls. 116 timeout := time.After(testing.LongWait) 117 next := time.After(0) 118 for len(s.claimer.Stub.Calls()) < 2 { 119 select { 120 case <-next: 121 next = time.After(testing.ShortWait) 122 case <-timeout: 123 c.Fatalf("timed out waiting %s for 2 calls", testing.LongWait) 124 } 125 } 126 // Stop the tracker before trying to look at its mocks. 127 workertest.CleanKill(c, tracker) 128 129 s.claimer.CheckCalls(c, []testing.StubCall{{ 130 FuncName: "ClaimLeadership", 131 Args: []interface{}{ 132 "led-service", "led-service/123", leaseDuration, 133 }, 134 }, { 135 FuncName: "BlockUntilLeadershipReleased", 136 Args: []interface{}{ 137 "led-service", 138 }, 139 }}) 140 } 141 142 func (s *TrackerSuite) TestOnLeaderError(c *gc.C) { 143 s.claimer.Stub.SetErrors(errors.New("pow")) 144 tracker := s.newTrackerDirtyKill() 145 146 // Check the ticket fails. 147 assertClaimLeader(c, tracker, false) 148 149 // Stop the tracker before trying to look at its mocks. 150 err := worker.Stop(tracker) 151 c.Check(err, gc.ErrorMatches, "leadership failure: pow") 152 s.claimer.CheckCalls(c, []testing.StubCall{{ 153 FuncName: "ClaimLeadership", 154 Args: []interface{}{ 155 "led-service", "led-service/123", leaseDuration, 156 }, 157 }}) 158 } 159 160 func (s *TrackerSuite) TestLoseLeadership(c *gc.C) { 161 s.claimer.Stub.SetErrors(nil, coreleadership.ErrClaimDenied, nil) 162 tracker := s.newTracker() 163 164 // Check the first ticket succeeds. 165 assertClaimLeader(c, tracker, true) 166 167 // Wait long enough for a single refresh, to trigger ErrClaimDenied; then 168 // check the next ticket fails. 169 s.refreshes(1) 170 assertClaimLeader(c, tracker, false) 171 172 // Stop the tracker before trying to look at its stub. 173 workertest.CleanKill(c, tracker) 174 175 s.claimer.CheckCalls(c, []testing.StubCall{{ 176 FuncName: "ClaimLeadership", 177 Args: []interface{}{ 178 "led-service", "led-service/123", leaseDuration, 179 }, 180 }, { 181 FuncName: "ClaimLeadership", 182 Args: []interface{}{ 183 "led-service", "led-service/123", leaseDuration, 184 }, 185 }, { 186 FuncName: "BlockUntilLeadershipReleased", 187 Args: []interface{}{ 188 "led-service", 189 }, 190 }}) 191 } 192 193 func (s *TrackerSuite) TestGainLeadership(c *gc.C) { 194 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil, nil) 195 tracker := s.newTracker() 196 197 // Check initial ticket fails. 198 assertClaimLeader(c, tracker, false) 199 200 // Unblock the release goroutine... 201 s.unblockRelease(c) 202 203 // advance the clock a small amount, but not enough to trigger a check 204 s.refreshes(0) 205 206 // ...then check the next ticket succeeds. 207 assertClaimLeader(c, tracker, true) 208 209 // Stop the tracker before trying to look at its stub. 210 workertest.CleanKill(c, tracker) 211 s.claimer.CheckCalls(c, []testing.StubCall{{ 212 FuncName: "ClaimLeadership", 213 Args: []interface{}{ 214 "led-service", "led-service/123", leaseDuration, 215 }, 216 }, { 217 FuncName: "BlockUntilLeadershipReleased", 218 Args: []interface{}{ 219 "led-service", 220 }, 221 }, { 222 FuncName: "ClaimLeadership", 223 Args: []interface{}{ 224 "led-service", "led-service/123", leaseDuration, 225 }, 226 }}) 227 } 228 229 func (s *TrackerSuite) TestFailGainLeadership(c *gc.C) { 230 s.claimer.Stub.SetErrors( 231 coreleadership.ErrClaimDenied, nil, coreleadership.ErrClaimDenied, nil, 232 ) 233 tracker := s.newTracker() 234 235 // Check initial ticket fails. 236 assertClaimLeader(c, tracker, false) 237 238 // Unblock the release goroutine... 239 s.unblockRelease(c) 240 241 // advance the clock a small amount, but not enough to trigger a check 242 s.refreshes(0) 243 244 // ...then check the next ticket fails again. 245 assertClaimLeader(c, tracker, false) 246 247 // This time, advance far enough that a refresh would trigger if it were 248 // going to... 249 s.refreshes(1) 250 251 // ...but it won't, because we Stop the tracker... 252 workertest.CleanKill(c, tracker) 253 254 s.claimer.CheckCalls(c, []testing.StubCall{{ 255 FuncName: "ClaimLeadership", 256 Args: []interface{}{ 257 "led-service", "led-service/123", leaseDuration, 258 }, 259 }, { 260 FuncName: "BlockUntilLeadershipReleased", 261 Args: []interface{}{ 262 "led-service", 263 }, 264 }, { 265 FuncName: "ClaimLeadership", 266 Args: []interface{}{ 267 "led-service", "led-service/123", leaseDuration, 268 }, 269 }, { 270 FuncName: "BlockUntilLeadershipReleased", 271 Args: []interface{}{ 272 "led-service", 273 }, 274 }}) 275 } 276 277 func (s *TrackerSuite) TestWaitLeaderAlreadyLeader(c *gc.C) { 278 tracker := s.newTracker() 279 280 // Check the ticket succeeds. 281 assertWaitLeader(c, tracker, true) 282 283 // Stop the tracker before trying to look at its stub. 284 workertest.CleanKill(c, tracker) 285 s.claimer.CheckCalls(c, []testing.StubCall{{ 286 FuncName: "ClaimLeadership", 287 Args: []interface{}{ 288 "led-service", "led-service/123", leaseDuration, 289 }, 290 }}) 291 } 292 293 func (s *TrackerSuite) TestWaitLeaderBecomeLeader(c *gc.C) { 294 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil, nil) 295 tracker := s.newTracker() 296 297 // Check initial ticket fails. 298 assertWaitLeader(c, tracker, false) 299 300 // Unblock the release goroutine... 301 s.unblockRelease(c) 302 303 // advance the clock a small amount, but not enough to trigger a check 304 s.refreshes(0) 305 306 // ...then check the next ticket succeeds. 307 assertWaitLeader(c, tracker, true) 308 309 // Stop the tracker before trying to look at its stub. 310 workertest.CleanKill(c, tracker) 311 s.claimer.CheckCalls(c, []testing.StubCall{{ 312 FuncName: "ClaimLeadership", 313 Args: []interface{}{ 314 "led-service", "led-service/123", leaseDuration, 315 }, 316 }, { 317 FuncName: "BlockUntilLeadershipReleased", 318 Args: []interface{}{ 319 "led-service", 320 }, 321 }, { 322 FuncName: "ClaimLeadership", 323 Args: []interface{}{ 324 "led-service", "led-service/123", leaseDuration, 325 }, 326 }}) 327 } 328 329 func (s *TrackerSuite) TestWaitLeaderNeverBecomeLeader(c *gc.C) { 330 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil) 331 tracker := s.newTracker() 332 333 // Check initial ticket fails. 334 assertWaitLeader(c, tracker, false) 335 336 // Get a new ticket and stop the tracker while it's pending. 337 ticket := tracker.WaitLeader() 338 workertest.CleanKill(c, tracker) 339 340 // Check the ticket got closed without sending true. 341 assertTicket(c, ticket, false) 342 assertTicket(c, ticket, false) 343 344 s.claimer.CheckCalls(c, []testing.StubCall{{ 345 FuncName: "ClaimLeadership", 346 Args: []interface{}{ 347 "led-service", "led-service/123", leaseDuration, 348 }, 349 }, { 350 FuncName: "BlockUntilLeadershipReleased", 351 Args: []interface{}{ 352 "led-service", 353 }, 354 }}) 355 } 356 357 func (s *TrackerSuite) TestWaitMinionAlreadyMinion(c *gc.C) { 358 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil) 359 tracker := s.newTracker() 360 361 // Check initial ticket is closed immediately. 362 assertWaitLeader(c, tracker, false) 363 364 // Stop the tracker before trying to look at its stub. 365 workertest.CleanKill(c, tracker) 366 s.claimer.CheckCalls(c, []testing.StubCall{{ 367 FuncName: "ClaimLeadership", 368 Args: []interface{}{ 369 "led-service", "led-service/123", leaseDuration, 370 }, 371 }, { 372 FuncName: "BlockUntilLeadershipReleased", 373 Args: []interface{}{ 374 "led-service", 375 }, 376 }}) 377 } 378 379 func (s *TrackerSuite) TestWaitMinionClaimerFails(c *gc.C) { 380 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, errors.New("mein leben!")) 381 tracker := s.newTrackerDirtyKill() 382 s.unblockRelease(c) 383 384 err := workertest.CheckKilled(c, tracker) 385 c.Assert(err, gc.ErrorMatches, "error while led-service/123 waiting for led-service leadership release: mein leben!") 386 } 387 388 func (s *TrackerSuite) TestWaitMinionBecomeMinion(c *gc.C) { 389 s.claimer.Stub.SetErrors(nil, coreleadership.ErrClaimDenied, nil) 390 tracker := s.newTracker() 391 392 // Check the first ticket stays open. 393 assertWaitMinion(c, tracker, false) 394 395 // Wait long enough for a single refresh, to trigger ErrClaimDenied; then 396 // check the next ticket is closed. 397 s.refreshes(1) 398 assertWaitMinion(c, tracker, true) 399 400 // Stop the tracker before trying to look at its stub. 401 workertest.CleanKill(c, tracker) 402 403 s.claimer.CheckCalls(c, []testing.StubCall{{ 404 FuncName: "ClaimLeadership", 405 Args: []interface{}{ 406 "led-service", "led-service/123", leaseDuration, 407 }, 408 }, { 409 FuncName: "ClaimLeadership", 410 Args: []interface{}{ 411 "led-service", "led-service/123", leaseDuration, 412 }, 413 }, { 414 FuncName: "BlockUntilLeadershipReleased", 415 Args: []interface{}{ 416 "led-service", 417 }, 418 }}) 419 } 420 421 func (s *TrackerSuite) TestWaitMinionNeverBecomeMinion(c *gc.C) { 422 tracker := s.newTracker() 423 424 ticket := tracker.WaitMinion() 425 s.refreshes(2) 426 427 select { 428 case <-ticket.Ready(): 429 c.Fatalf("got unexpected readiness: %v", ticket.Wait()) 430 default: 431 // fallthrough 432 } 433 434 s.claimer.CheckCalls(c, []testing.StubCall{{ 435 FuncName: "ClaimLeadership", 436 Args: []interface{}{ 437 "led-service", "led-service/123", leaseDuration, 438 }, 439 }, { 440 FuncName: "ClaimLeadership", 441 Args: []interface{}{ 442 "led-service", "led-service/123", leaseDuration, 443 }, 444 }, { 445 FuncName: "ClaimLeadership", 446 Args: []interface{}{ 447 "led-service", "led-service/123", leaseDuration, 448 }, 449 }}) 450 } 451 452 func assertClaimLeader(c *gc.C, tracker *leadership.Tracker, expect bool) { 453 // Grab a ticket... 454 ticket := tracker.ClaimLeader() 455 456 // ...and check that it gives the expected result every time it's checked. 457 assertTicket(c, ticket, expect) 458 assertTicket(c, ticket, expect) 459 } 460 461 func assertWaitLeader(c *gc.C, tracker *leadership.Tracker, expect bool) { 462 ticket := tracker.WaitLeader() 463 if expect { 464 assertTicket(c, ticket, true) 465 assertTicket(c, ticket, true) 466 return 467 } 468 select { 469 case <-time.After(coretesting.ShortWait): 470 // This wait needs to be small, compared to the resolution we run the 471 // tests at, so as not to disturb client timing too much. 472 case <-ticket.Ready(): 473 c.Fatalf("got unexpected readiness: %v", ticket.Wait()) 474 } 475 } 476 477 func assertWaitMinion(c *gc.C, tracker *leadership.Tracker, expect bool) { 478 ticket := tracker.WaitMinion() 479 if expect { 480 assertTicket(c, ticket, false) 481 assertTicket(c, ticket, false) 482 return 483 } 484 select { 485 case <-time.After(coretesting.ShortWait): 486 // This wait needs to be small, compared to the resolution we run the 487 // tests at, so as not to disturb client timing too much. 488 case <-ticket.Ready(): 489 c.Fatalf("got unexpected readiness: %v", ticket.Wait()) 490 } 491 } 492 493 func assertTicket(c *gc.C, ticket coreleadership.Ticket, expect bool) { 494 // Wait for the ticket to give a value... 495 select { 496 case <-time.After(coretesting.LongWait): 497 c.Fatalf("value not sent") 498 case <-ticket.Ready(): 499 c.Assert(ticket.Wait(), gc.Equals, expect) 500 } 501 }