github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/lease/manager_block_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lease_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock/testclock"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	corelease "github.com/juju/juju/core/lease"
    17  	"github.com/juju/juju/worker/lease"
    18  )
    19  
    20  type WaitUntilExpiredSuite struct {
    21  	testing.IsolationSuite
    22  }
    23  
    24  var _ = gc.Suite(&WaitUntilExpiredSuite{})
    25  
    26  func (s *WaitUntilExpiredSuite) SetUpTest(c *gc.C) {
    27  	s.IsolationSuite.SetUpTest(c)
    28  	logger := loggo.GetLogger("juju.worker.lease")
    29  	logger.SetLogLevel(loggo.TRACE)
    30  	logger = loggo.GetLogger("lease_test")
    31  	logger.SetLogLevel(loggo.TRACE)
    32  }
    33  
    34  func (s *WaitUntilExpiredSuite) TestLeadershipNoLeaseBlockEvaluatedNextTick(c *gc.C) {
    35  	fix := &Fixture{
    36  		leases: map[corelease.Key]corelease.Info{
    37  			key("postgresql"): {
    38  				Holder: "postgresql/0",
    39  				Expiry: offset(time.Second),
    40  			},
    41  		},
    42  	}
    43  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
    44  		blockTest := newBlockTest(c, manager, key("redis"))
    45  		blockTest.assertBlocked(c)
    46  
    47  		// Check that *another* lease expiry causes the unassociated block to
    48  		// be checked and in the absence of its lease, get unblocked.
    49  		c.Assert(clock.WaitAdvance(2*time.Second, testing.ShortWait, 1), jc.ErrorIsNil)
    50  		err := blockTest.assertUnblocked(c)
    51  		c.Check(err, jc.ErrorIsNil)
    52  	})
    53  }
    54  
    55  func (s *WaitUntilExpiredSuite) TestLeadershipExpires(c *gc.C) {
    56  	fix := &Fixture{
    57  		leases: map[corelease.Key]corelease.Info{
    58  			key("redis"): {
    59  				Holder: "redis/0",
    60  				Expiry: offset(time.Second),
    61  			},
    62  		},
    63  	}
    64  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
    65  		blockTest := newBlockTest(c, manager, key("redis"))
    66  		blockTest.assertBlocked(c)
    67  
    68  		// Trigger expiry.
    69  		c.Assert(clock.WaitAdvance(2*time.Second, testing.ShortWait, 1), jc.ErrorIsNil)
    70  		err := blockTest.assertUnblocked(c)
    71  		c.Check(err, jc.ErrorIsNil)
    72  	})
    73  }
    74  
    75  func (s *WaitUntilExpiredSuite) TestBlockChecksRescheduled(c *gc.C) {
    76  	fix := &Fixture{
    77  		leases: map[corelease.Key]corelease.Info{
    78  			key("postgresql"): {
    79  				Holder: "postgresql/0",
    80  				Expiry: offset(time.Second),
    81  			},
    82  			key("mysql"): {
    83  				Holder: "mysql/0",
    84  				Expiry: offset(4 * time.Second),
    85  			},
    86  			key("redis"): {
    87  				Holder: "redis/0",
    88  				Expiry: offset(7 * time.Second),
    89  			},
    90  		},
    91  	}
    92  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
    93  		blockTest := newBlockTest(c, manager, key("redis"))
    94  		blockTest.assertBlocked(c)
    95  
    96  		// Advance past the first expiry.
    97  		c.Assert(clock.WaitAdvance(3*time.Second, testing.ShortWait, 1), jc.ErrorIsNil)
    98  		blockTest.assertBlocked(c)
    99  
   100  		// Advance past the second expiry. We should have had a check scheduled.
   101  		c.Assert(clock.WaitAdvance(3*time.Second, testing.ShortWait, 1), jc.ErrorIsNil)
   102  		blockTest.assertBlocked(c)
   103  
   104  		// Advance past the last expiry. We should have had a check scheduled
   105  		// that causes the redis lease to be unblocked.
   106  		c.Assert(clock.WaitAdvance(3*time.Second, testing.ShortWait, 1), jc.ErrorIsNil)
   107  		err := blockTest.assertUnblocked(c)
   108  		c.Check(err, jc.ErrorIsNil)
   109  	})
   110  }
   111  
   112  func (s *WaitUntilExpiredSuite) TestLeadershipChanged(c *gc.C) {
   113  	fix := &Fixture{
   114  		leases: map[corelease.Key]corelease.Info{
   115  			key("redis"): {
   116  				Holder: "redis/0",
   117  				Expiry: offset(time.Second),
   118  			},
   119  		},
   120  	}
   121  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   122  		blockTest := newBlockTest(c, manager, key("redis"))
   123  		blockTest.assertBlocked(c)
   124  
   125  		// Trigger abortive expiry.
   126  		clock.Advance(time.Second)
   127  		blockTest.assertBlocked(c)
   128  	})
   129  }
   130  
   131  func (s *WaitUntilExpiredSuite) TestLeadershipExpiredEarly(c *gc.C) {
   132  	fix := &Fixture{
   133  		leases: map[corelease.Key]corelease.Info{
   134  			// The lease is held by an entity other than the checker.
   135  			key("redis"): {
   136  				Holder: "redis/0",
   137  				Expiry: offset(time.Second),
   138  			},
   139  		},
   140  	}
   141  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   142  		blockTest := newBlockTest(c, manager, key("redis"))
   143  		blockTest.assertBlocked(c)
   144  
   145  		// Induce a scheduled block check by making an unexpected check;
   146  		// it turns out the lease had already been expired by someone else.
   147  		checker, err := manager.Checker("namespace", "model")
   148  		c.Assert(err, jc.ErrorIsNil)
   149  		err = checker.Token("redis", "redis/99").Check()
   150  		c.Assert(err, gc.ErrorMatches, "lease not held")
   151  
   152  		// Simulate the delayed synchronisation by removing the lease.
   153  		delete(fix.leases, key("redis"))
   154  
   155  		// When we notice that we are out of sync, we should queue up an
   156  		// expiration and update of blockers after a very short timeout.
   157  		err = clock.WaitAdvance(time.Second, testing.ShortWait, 1)
   158  		c.Assert(err, jc.ErrorIsNil)
   159  
   160  		err = blockTest.assertUnblocked(c)
   161  		c.Check(err, jc.ErrorIsNil)
   162  	})
   163  }
   164  
   165  func (s *WaitUntilExpiredSuite) TestMultiple(c *gc.C) {
   166  	fix := &Fixture{
   167  		leases: map[corelease.Key]corelease.Info{
   168  			key("redis"): {
   169  				Holder: "redis/0",
   170  				Expiry: offset(2 * time.Second),
   171  			},
   172  			key("store"): {
   173  				Holder: "store/0",
   174  				Expiry: offset(2 * time.Second),
   175  			},
   176  		},
   177  	}
   178  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   179  		redisTest1 := newBlockTest(c, manager, key("redis"))
   180  		redisTest1.assertBlocked(c)
   181  		redisTest2 := newBlockTest(c, manager, key("redis"))
   182  		redisTest2.assertBlocked(c)
   183  		storeTest1 := newBlockTest(c, manager, key("store"))
   184  		storeTest1.assertBlocked(c)
   185  		storeTest2 := newBlockTest(c, manager, key("store"))
   186  		storeTest2.assertBlocked(c)
   187  
   188  		// Induce a scheduled block check by making an unexpected check.
   189  		checker, err := manager.Checker("namespace", "model")
   190  		c.Assert(err, jc.ErrorIsNil)
   191  		err = checker.Token("redis", "redis/99").Check()
   192  		c.Assert(err, gc.ErrorMatches, "lease not held")
   193  
   194  		// Deleting the redis lease should cause unblocks for the redis
   195  		// blockers, but the store blocks should remain.
   196  		delete(fix.leases, key("redis"))
   197  
   198  		err = clock.WaitAdvance(time.Second, testing.ShortWait, 1)
   199  		c.Assert(err, jc.ErrorIsNil)
   200  
   201  		err = redisTest2.assertUnblocked(c)
   202  		c.Check(err, jc.ErrorIsNil)
   203  
   204  		err = redisTest1.assertUnblocked(c)
   205  		c.Check(err, jc.ErrorIsNil)
   206  
   207  		storeTest2.assertBlocked(c)
   208  		storeTest1.assertBlocked(c)
   209  	})
   210  }
   211  
   212  func (s *WaitUntilExpiredSuite) TestKillManager(c *gc.C) {
   213  	fix := &Fixture{
   214  		leases: map[corelease.Key]corelease.Info{
   215  			key("redis"): {
   216  				Holder: "redis/0",
   217  				Expiry: offset(time.Second),
   218  			},
   219  		},
   220  	}
   221  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
   222  		blockTest := newBlockTest(c, manager, key("redis"))
   223  		blockTest.assertBlocked(c)
   224  
   225  		manager.Kill()
   226  		err := blockTest.assertUnblocked(c)
   227  		c.Check(err, gc.ErrorMatches, "lease manager stopped")
   228  	})
   229  }
   230  
   231  func (s *WaitUntilExpiredSuite) TestCancelWait(c *gc.C) {
   232  	fix := &Fixture{
   233  		leases: map[corelease.Key]corelease.Info{
   234  			key("redis"): {
   235  				Holder: "redis/0",
   236  				Expiry: offset(time.Second),
   237  			},
   238  		},
   239  	}
   240  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
   241  		blockTest := newBlockTest(c, manager, key("redis"))
   242  		blockTest.assertBlocked(c)
   243  		blockTest.cancelWait()
   244  
   245  		err := blockTest.assertUnblocked(c)
   246  		c.Check(err, gc.Equals, corelease.ErrWaitCancelled)
   247  		c.Check(err, gc.ErrorMatches, "waiting for lease cancelled by client")
   248  	})
   249  }
   250  
   251  // blockTest wraps a goroutine running WaitUntilExpired, and fails if it's used
   252  // more than a second after creation (which should be *plenty* of time).
   253  type blockTest struct {
   254  	manager *lease.Manager
   255  	done    chan error
   256  	abort   <-chan time.Time
   257  	cancel  chan struct{}
   258  }
   259  
   260  // newBlockTest starts a test goroutine blocking until the manager confirms
   261  // expiry of the named lease.
   262  func newBlockTest(c *gc.C, manager *lease.Manager, key corelease.Key) *blockTest {
   263  	bt := &blockTest{
   264  		manager: manager,
   265  		done:    make(chan error),
   266  		abort:   time.After(time.Second),
   267  		cancel:  make(chan struct{}),
   268  	}
   269  	claimer, err := bt.manager.Claimer(key.Namespace, key.ModelUUID)
   270  	if err != nil {
   271  		c.Errorf("couldn't get claimer: %v", err)
   272  	}
   273  	started := make(chan struct{})
   274  	go func() {
   275  		close(started)
   276  		select {
   277  		case <-bt.abort:
   278  		case bt.done <- claimer.WaitUntilExpired(key.Lease, bt.cancel):
   279  		case <-time.After(testing.LongWait):
   280  			c.Errorf("block not aborted or expired after %v", testing.LongWait)
   281  		}
   282  	}()
   283  	select {
   284  	case <-started:
   285  	case <-bt.abort:
   286  		c.Errorf("bt.aborted before even started")
   287  	}
   288  	return bt
   289  }
   290  
   291  func (bt *blockTest) cancelWait() {
   292  	close(bt.cancel)
   293  }
   294  
   295  func (bt *blockTest) assertBlocked(c *gc.C) {
   296  	select {
   297  	case err := <-bt.done:
   298  		c.Errorf("unblocked unexpectedly with %v", err)
   299  	case <-time.After(testing.ShortWait):
   300  		// Happy that we are still blocked; success.
   301  	}
   302  }
   303  
   304  func (bt *blockTest) assertUnblocked(c *gc.C) error {
   305  	lease.ManagerStore(bt.manager).(*Store).expireLeases()
   306  	select {
   307  	case err := <-bt.done:
   308  		return err
   309  	case <-bt.abort:
   310  		c.Errorf("timed out before unblocking")
   311  		return errors.Errorf("timed out")
   312  	}
   313  }