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  }