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