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  }