github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/testing" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/juju/names.v2" 14 "gopkg.in/juju/worker.v1" 15 "gopkg.in/juju/worker.v1/workertest" 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 // Stop the tracker before trying to look at its mocks. 116 workertest.CleanKill(c, tracker) 117 118 s.claimer.CheckCalls(c, []testing.StubCall{{ 119 FuncName: "ClaimLeadership", 120 Args: []interface{}{ 121 "led-service", "led-service/123", leaseDuration, 122 }, 123 }, { 124 FuncName: "BlockUntilLeadershipReleased", 125 Args: []interface{}{ 126 "led-service", 127 }, 128 }}) 129 } 130 131 func (s *TrackerSuite) TestOnLeaderError(c *gc.C) { 132 s.claimer.Stub.SetErrors(errors.New("pow")) 133 tracker := s.newTrackerDirtyKill() 134 135 // Check the ticket fails. 136 assertClaimLeader(c, tracker, false) 137 138 // Stop the tracker before trying to look at its mocks. 139 err := worker.Stop(tracker) 140 c.Check(err, gc.ErrorMatches, "leadership failure: pow") 141 s.claimer.CheckCalls(c, []testing.StubCall{{ 142 FuncName: "ClaimLeadership", 143 Args: []interface{}{ 144 "led-service", "led-service/123", leaseDuration, 145 }, 146 }}) 147 } 148 149 func (s *TrackerSuite) TestLoseLeadership(c *gc.C) { 150 s.claimer.Stub.SetErrors(nil, coreleadership.ErrClaimDenied, nil) 151 tracker := s.newTracker() 152 153 // Check the first ticket succeeds. 154 assertClaimLeader(c, tracker, true) 155 156 // Wait long enough for a single refresh, to trigger ErrClaimDenied; then 157 // check the next ticket fails. 158 s.refreshes(1) 159 assertClaimLeader(c, tracker, false) 160 161 // Stop the tracker before trying to look at its stub. 162 workertest.CleanKill(c, tracker) 163 164 s.claimer.CheckCalls(c, []testing.StubCall{{ 165 FuncName: "ClaimLeadership", 166 Args: []interface{}{ 167 "led-service", "led-service/123", leaseDuration, 168 }, 169 }, { 170 FuncName: "ClaimLeadership", 171 Args: []interface{}{ 172 "led-service", "led-service/123", leaseDuration, 173 }, 174 }, { 175 FuncName: "BlockUntilLeadershipReleased", 176 Args: []interface{}{ 177 "led-service", 178 }, 179 }}) 180 } 181 182 func (s *TrackerSuite) TestGainLeadership(c *gc.C) { 183 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil, nil) 184 tracker := s.newTracker() 185 186 // Check initial ticket fails. 187 assertClaimLeader(c, tracker, false) 188 189 // Unblock the release goroutine... 190 s.unblockRelease(c) 191 192 // advance the clock a small amount, but not enough to trigger a check 193 s.refreshes(0) 194 195 // ...then check the next ticket succeeds. 196 assertClaimLeader(c, tracker, true) 197 198 // Stop the tracker before trying to look at its stub. 199 workertest.CleanKill(c, tracker) 200 s.claimer.CheckCalls(c, []testing.StubCall{{ 201 FuncName: "ClaimLeadership", 202 Args: []interface{}{ 203 "led-service", "led-service/123", leaseDuration, 204 }, 205 }, { 206 FuncName: "BlockUntilLeadershipReleased", 207 Args: []interface{}{ 208 "led-service", 209 }, 210 }, { 211 FuncName: "ClaimLeadership", 212 Args: []interface{}{ 213 "led-service", "led-service/123", leaseDuration, 214 }, 215 }}) 216 } 217 218 func (s *TrackerSuite) TestFailGainLeadership(c *gc.C) { 219 s.claimer.Stub.SetErrors( 220 coreleadership.ErrClaimDenied, nil, coreleadership.ErrClaimDenied, nil, 221 ) 222 tracker := s.newTracker() 223 224 // Check initial ticket fails. 225 assertClaimLeader(c, tracker, false) 226 227 // Unblock the release goroutine... 228 s.unblockRelease(c) 229 230 // advance the clock a small amount, but not enough to trigger a check 231 s.refreshes(0) 232 233 // ...then check the next ticket fails again. 234 assertClaimLeader(c, tracker, false) 235 236 // This time, advance far enough that a refresh would trigger if it were 237 // going to... 238 s.refreshes(1) 239 240 // ...but it won't, because we Stop the tracker... 241 workertest.CleanKill(c, tracker) 242 243 s.claimer.CheckCalls(c, []testing.StubCall{{ 244 FuncName: "ClaimLeadership", 245 Args: []interface{}{ 246 "led-service", "led-service/123", leaseDuration, 247 }, 248 }, { 249 FuncName: "BlockUntilLeadershipReleased", 250 Args: []interface{}{ 251 "led-service", 252 }, 253 }, { 254 FuncName: "ClaimLeadership", 255 Args: []interface{}{ 256 "led-service", "led-service/123", leaseDuration, 257 }, 258 }, { 259 FuncName: "BlockUntilLeadershipReleased", 260 Args: []interface{}{ 261 "led-service", 262 }, 263 }}) 264 } 265 266 func (s *TrackerSuite) TestWaitLeaderAlreadyLeader(c *gc.C) { 267 tracker := s.newTracker() 268 269 // Check the ticket succeeds. 270 assertWaitLeader(c, tracker, true) 271 272 // Stop the tracker before trying to look at its stub. 273 workertest.CleanKill(c, tracker) 274 s.claimer.CheckCalls(c, []testing.StubCall{{ 275 FuncName: "ClaimLeadership", 276 Args: []interface{}{ 277 "led-service", "led-service/123", leaseDuration, 278 }, 279 }}) 280 } 281 282 func (s *TrackerSuite) TestWaitLeaderBecomeLeader(c *gc.C) { 283 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil, nil) 284 tracker := s.newTracker() 285 286 // Check initial ticket fails. 287 assertWaitLeader(c, tracker, false) 288 289 // Unblock the release goroutine... 290 s.unblockRelease(c) 291 292 // advance the clock a small amount, but not enough to trigger a check 293 s.refreshes(0) 294 295 // ...then check the next ticket succeeds. 296 assertWaitLeader(c, tracker, true) 297 298 // Stop the tracker before trying to look at its stub. 299 workertest.CleanKill(c, tracker) 300 s.claimer.CheckCalls(c, []testing.StubCall{{ 301 FuncName: "ClaimLeadership", 302 Args: []interface{}{ 303 "led-service", "led-service/123", leaseDuration, 304 }, 305 }, { 306 FuncName: "BlockUntilLeadershipReleased", 307 Args: []interface{}{ 308 "led-service", 309 }, 310 }, { 311 FuncName: "ClaimLeadership", 312 Args: []interface{}{ 313 "led-service", "led-service/123", leaseDuration, 314 }, 315 }}) 316 } 317 318 func (s *TrackerSuite) TestWaitLeaderNeverBecomeLeader(c *gc.C) { 319 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil) 320 tracker := s.newTracker() 321 322 // Check initial ticket fails. 323 assertWaitLeader(c, tracker, false) 324 325 // Get a new ticket and stop the tracker while it's pending. 326 ticket := tracker.WaitLeader() 327 workertest.CleanKill(c, tracker) 328 329 // Check the ticket got closed without sending true. 330 assertTicket(c, ticket, false) 331 assertTicket(c, ticket, false) 332 333 s.claimer.CheckCalls(c, []testing.StubCall{{ 334 FuncName: "ClaimLeadership", 335 Args: []interface{}{ 336 "led-service", "led-service/123", leaseDuration, 337 }, 338 }, { 339 FuncName: "BlockUntilLeadershipReleased", 340 Args: []interface{}{ 341 "led-service", 342 }, 343 }}) 344 } 345 346 func (s *TrackerSuite) TestWaitMinionAlreadyMinion(c *gc.C) { 347 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, nil) 348 tracker := s.newTracker() 349 350 // Check initial ticket is closed immediately. 351 assertWaitLeader(c, tracker, false) 352 353 // Stop the tracker before trying to look at its stub. 354 workertest.CleanKill(c, tracker) 355 s.claimer.CheckCalls(c, []testing.StubCall{{ 356 FuncName: "ClaimLeadership", 357 Args: []interface{}{ 358 "led-service", "led-service/123", leaseDuration, 359 }, 360 }, { 361 FuncName: "BlockUntilLeadershipReleased", 362 Args: []interface{}{ 363 "led-service", 364 }, 365 }}) 366 } 367 368 func (s *TrackerSuite) TestWaitMinionClaimerFails(c *gc.C) { 369 s.claimer.Stub.SetErrors(coreleadership.ErrClaimDenied, errors.New("mein leben!")) 370 tracker := s.newTrackerDirtyKill() 371 s.unblockRelease(c) 372 373 err := workertest.CheckKilled(c, tracker) 374 c.Assert(err, gc.ErrorMatches, "error while led-service/123 waiting for led-service leadership release: mein leben!") 375 } 376 377 func (s *TrackerSuite) TestWaitMinionBecomeMinion(c *gc.C) { 378 s.claimer.Stub.SetErrors(nil, coreleadership.ErrClaimDenied, nil) 379 tracker := s.newTracker() 380 381 // Check the first ticket stays open. 382 assertWaitMinion(c, tracker, false) 383 384 // Wait long enough for a single refresh, to trigger ErrClaimDenied; then 385 // check the next ticket is closed. 386 s.refreshes(1) 387 assertWaitMinion(c, tracker, true) 388 389 // Stop the tracker before trying to look at its stub. 390 workertest.CleanKill(c, tracker) 391 392 s.claimer.CheckCalls(c, []testing.StubCall{{ 393 FuncName: "ClaimLeadership", 394 Args: []interface{}{ 395 "led-service", "led-service/123", leaseDuration, 396 }, 397 }, { 398 FuncName: "ClaimLeadership", 399 Args: []interface{}{ 400 "led-service", "led-service/123", leaseDuration, 401 }, 402 }, { 403 FuncName: "BlockUntilLeadershipReleased", 404 Args: []interface{}{ 405 "led-service", 406 }, 407 }}) 408 } 409 410 func (s *TrackerSuite) TestWaitMinionNeverBecomeMinion(c *gc.C) { 411 tracker := s.newTracker() 412 413 ticket := tracker.WaitMinion() 414 s.refreshes(2) 415 416 select { 417 case <-ticket.Ready(): 418 c.Fatalf("got unexpected readiness: %v", ticket.Wait()) 419 default: 420 // fallthrough 421 } 422 423 s.claimer.CheckCalls(c, []testing.StubCall{{ 424 FuncName: "ClaimLeadership", 425 Args: []interface{}{ 426 "led-service", "led-service/123", leaseDuration, 427 }, 428 }, { 429 FuncName: "ClaimLeadership", 430 Args: []interface{}{ 431 "led-service", "led-service/123", leaseDuration, 432 }, 433 }, { 434 FuncName: "ClaimLeadership", 435 Args: []interface{}{ 436 "led-service", "led-service/123", leaseDuration, 437 }, 438 }}) 439 } 440 441 func assertClaimLeader(c *gc.C, tracker *leadership.Tracker, expect bool) { 442 // Grab a ticket... 443 ticket := tracker.ClaimLeader() 444 445 // ...and check that it gives the expected result every time it's checked. 446 assertTicket(c, ticket, expect) 447 assertTicket(c, ticket, expect) 448 } 449 450 func assertWaitLeader(c *gc.C, tracker *leadership.Tracker, expect bool) { 451 ticket := tracker.WaitLeader() 452 if expect { 453 assertTicket(c, ticket, true) 454 assertTicket(c, ticket, true) 455 return 456 } 457 select { 458 case <-time.After(coretesting.ShortWait): 459 // This wait needs to be small, compared to the resolution we run the 460 // tests at, so as not to disturb client timing too much. 461 case <-ticket.Ready(): 462 c.Fatalf("got unexpected readiness: %v", ticket.Wait()) 463 } 464 } 465 466 func assertWaitMinion(c *gc.C, tracker *leadership.Tracker, expect bool) { 467 ticket := tracker.WaitMinion() 468 if expect { 469 assertTicket(c, ticket, false) 470 assertTicket(c, ticket, false) 471 return 472 } 473 select { 474 case <-time.After(coretesting.ShortWait): 475 // This wait needs to be small, compared to the resolution we run the 476 // tests at, so as not to disturb client timing too much. 477 case <-ticket.Ready(): 478 c.Fatalf("got unexpected readiness: %v", ticket.Wait()) 479 } 480 } 481 482 func assertTicket(c *gc.C, ticket coreleadership.Ticket, expect bool) { 483 // Wait for the ticket to give a value... 484 select { 485 case <-time.After(coretesting.LongWait): 486 c.Fatalf("value not sent") 487 case <-ticket.Ready(): 488 c.Assert(ticket.Wait(), gc.Equals, expect) 489 } 490 }