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