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