github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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) TestLeadershipNotHeld(c *gc.C) {
    35  	fix := &Fixture{}
    36  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
    37  		blockTest := newBlockTest(c, manager, key("redis"))
    38  		err := blockTest.assertUnblocked(c)
    39  		c.Check(err, jc.ErrorIsNil)
    40  	})
    41  }
    42  
    43  func (s *WaitUntilExpiredSuite) TestLeadershipExpires(c *gc.C) {
    44  	fix := &Fixture{
    45  		leases: map[corelease.Key]corelease.Info{
    46  			key("redis"): {
    47  				Holder: "redis/0",
    48  				Expiry: offset(time.Second),
    49  			},
    50  		},
    51  		expectCalls: []call{{
    52  			method: "Refresh",
    53  		}, {
    54  			method: "ExpireLease",
    55  			args:   []interface{}{key("redis")},
    56  			callback: func(leases map[corelease.Key]corelease.Info) {
    57  				delete(leases, key("redis"))
    58  			},
    59  		}},
    60  	}
    61  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
    62  		blockTest := newBlockTest(c, manager, key("redis"))
    63  		blockTest.assertBlocked(c)
    64  
    65  		// Trigger expiry.
    66  		c.Assert(clock.WaitAdvance(time.Second, testing.ShortWait, 1), jc.ErrorIsNil)
    67  		err := blockTest.assertUnblocked(c)
    68  		c.Check(err, jc.ErrorIsNil)
    69  	})
    70  }
    71  
    72  func (s *WaitUntilExpiredSuite) TestLeadershipChanged(c *gc.C) {
    73  	fix := &Fixture{
    74  		leases: map[corelease.Key]corelease.Info{
    75  			key("redis"): {
    76  				Holder: "redis/0",
    77  				Expiry: offset(time.Second),
    78  			},
    79  		},
    80  		expectCalls: []call{{
    81  			method: "Refresh",
    82  		}, {
    83  			method: "ExpireLease",
    84  			args:   []interface{}{key("redis")},
    85  			err:    corelease.ErrInvalid,
    86  			callback: func(leases map[corelease.Key]corelease.Info) {
    87  				leases[key("redis")] = corelease.Info{
    88  					Holder: "redis/99",
    89  					Expiry: offset(time.Minute),
    90  				}
    91  			},
    92  		}},
    93  	}
    94  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
    95  		blockTest := newBlockTest(c, manager, key("redis"))
    96  		blockTest.assertBlocked(c)
    97  
    98  		// Trigger abortive expiry.
    99  		clock.Advance(time.Second)
   100  		blockTest.assertBlocked(c)
   101  	})
   102  }
   103  
   104  func (s *WaitUntilExpiredSuite) TestLeadershipExpiredEarly(c *gc.C) {
   105  	fix := &Fixture{
   106  		leases: map[corelease.Key]corelease.Info{
   107  			key("redis"): {
   108  				Holder: "redis/0",
   109  				Expiry: offset(time.Second),
   110  			},
   111  		},
   112  		expectCalls: []call{{
   113  			method: "Refresh", // Called when we get inconsistent results
   114  			callback: func(leases map[corelease.Key]corelease.Info) {
   115  				delete(leases, key("redis"))
   116  			},
   117  		}, {
   118  			method: "Refresh", // Called at the newly injected 'expire' test
   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  		// Induce a refresh by making an unexpected check; it turns out the
   126  		// lease had already been expired by someone else.
   127  		checker, err := manager.Checker("namespace", "model")
   128  		c.Assert(err, jc.ErrorIsNil)
   129  		checker.Token("redis", "redis/99").Check(0, nil)
   130  		// When we notice that we are out of sync, we should queue up an expiration
   131  		// and update of blockers after a very short timeout
   132  		clock.WaitAdvance(time.Millisecond, testing.ShortWait, 1)
   133  		err = blockTest.assertUnblocked(c)
   134  		c.Check(err, jc.ErrorIsNil)
   135  	})
   136  }
   137  
   138  func (s *WaitUntilExpiredSuite) TestMultiple(c *gc.C) {
   139  	fix := &Fixture{
   140  		leases: map[corelease.Key]corelease.Info{
   141  			key("redis"): {
   142  				Holder: "redis/0",
   143  				Expiry: offset(time.Second),
   144  			},
   145  			key("store"): {
   146  				Holder: "store/0",
   147  				Expiry: offset(time.Second),
   148  			},
   149  		},
   150  		expectCalls: []call{{
   151  			method: "Refresh",
   152  		}, {
   153  			method: "ExpireLease",
   154  			args:   []interface{}{key("redis")},
   155  			err:    corelease.ErrInvalid,
   156  			callback: func(leases map[corelease.Key]corelease.Info) {
   157  				delete(leases, key("redis"))
   158  				leases[key("store")] = corelease.Info{
   159  					Holder: "store/9",
   160  					Expiry: offset(time.Minute),
   161  				}
   162  			},
   163  		}, {
   164  			method: "ExpireLease",
   165  			args:   []interface{}{key("store")},
   166  			err:    corelease.ErrInvalid,
   167  		}},
   168  	}
   169  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   170  		redisTest1 := newBlockTest(c, manager, key("redis"))
   171  		redisTest1.assertBlocked(c)
   172  		redisTest2 := newBlockTest(c, manager, key("redis"))
   173  		redisTest2.assertBlocked(c)
   174  		storeTest1 := newBlockTest(c, manager, key("store"))
   175  		storeTest1.assertBlocked(c)
   176  		storeTest2 := newBlockTest(c, manager, key("store"))
   177  		storeTest2.assertBlocked(c)
   178  
   179  		// Induce attempted expiry; redis was expired already, store was
   180  		// refreshed and not expired.
   181  		clock.Advance(time.Second)
   182  		err := redisTest2.assertUnblocked(c)
   183  		c.Check(err, jc.ErrorIsNil)
   184  		err = redisTest1.assertUnblocked(c)
   185  		c.Check(err, jc.ErrorIsNil)
   186  		storeTest2.assertBlocked(c)
   187  		storeTest1.assertBlocked(c)
   188  	})
   189  }
   190  
   191  func (s *WaitUntilExpiredSuite) TestKillManager(c *gc.C) {
   192  	fix := &Fixture{
   193  		leases: map[corelease.Key]corelease.Info{
   194  			key("redis"): {
   195  				Holder: "redis/0",
   196  				Expiry: offset(time.Second),
   197  			},
   198  		},
   199  	}
   200  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
   201  		blockTest := newBlockTest(c, manager, key("redis"))
   202  		blockTest.assertBlocked(c)
   203  
   204  		manager.Kill()
   205  		err := blockTest.assertUnblocked(c)
   206  		c.Check(err, gc.ErrorMatches, "lease manager stopped")
   207  	})
   208  }
   209  
   210  func (s *WaitUntilExpiredSuite) TestCancelWait(c *gc.C) {
   211  	fix := &Fixture{
   212  		leases: map[corelease.Key]corelease.Info{
   213  			key("redis"): {
   214  				Holder: "redis/0",
   215  				Expiry: offset(time.Second),
   216  			},
   217  		},
   218  	}
   219  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
   220  		blockTest := newBlockTest(c, manager, key("redis"))
   221  		blockTest.assertBlocked(c)
   222  		blockTest.cancelWait()
   223  
   224  		err := blockTest.assertUnblocked(c)
   225  		c.Check(err, gc.Equals, corelease.ErrWaitCancelled)
   226  		c.Check(err, gc.ErrorMatches, "waiting for lease cancelled by client")
   227  	})
   228  }
   229  
   230  // blockTest wraps a goroutine running WaitUntilExpired, and fails if it's used
   231  // more than a second after creation (which should be *plenty* of time).
   232  type blockTest struct {
   233  	manager *lease.Manager
   234  	done    chan error
   235  	abort   <-chan time.Time
   236  	cancel  chan struct{}
   237  }
   238  
   239  // newBlockTest starts a test goroutine blocking until the manager confirms
   240  // expiry of the named lease.
   241  func newBlockTest(c *gc.C, manager *lease.Manager, key corelease.Key) *blockTest {
   242  	bt := &blockTest{
   243  		manager: manager,
   244  		done:    make(chan error),
   245  		abort:   time.After(time.Second),
   246  		cancel:  make(chan struct{}),
   247  	}
   248  	claimer, err := bt.manager.Claimer(key.Namespace, key.ModelUUID)
   249  	if err != nil {
   250  		c.Errorf("couldn't get claimer: %v", err)
   251  	}
   252  	started := make(chan struct{})
   253  	go func() {
   254  		close(started)
   255  		select {
   256  		case <-bt.abort:
   257  		case bt.done <- claimer.WaitUntilExpired(key.Lease, bt.cancel):
   258  		case <-time.After(testing.LongWait):
   259  			c.Errorf("block not aborted or expired after %v", testing.LongWait)
   260  		}
   261  	}()
   262  	select {
   263  	case <-started:
   264  	case <-bt.abort:
   265  		c.Errorf("bt.aborted before even started")
   266  	}
   267  	return bt
   268  }
   269  
   270  func (bt *blockTest) cancelWait() {
   271  	close(bt.cancel)
   272  }
   273  
   274  func (bt *blockTest) assertBlocked(c *gc.C) {
   275  	select {
   276  	case err := <-bt.done:
   277  		c.Errorf("unblocked unexpectedly with %v", err)
   278  	case <-time.After(time.Millisecond):
   279  		// happy that we are still blocked, success
   280  		// TODO(jam): 2019-02-05 should this be testing.ShortWait? It used to be
   281  		//  just plain 'default:', which didn't even give the helper goroutine
   282  		//  a timeslice to start to even evaluate if WaitUntilExpired had returned.
   283  	}
   284  }
   285  
   286  func (bt *blockTest) assertUnblocked(c *gc.C) error {
   287  	select {
   288  	case err := <-bt.done:
   289  		return err
   290  	case <-bt.abort:
   291  		c.Errorf("timed out before unblocking")
   292  		return errors.Errorf("timed out")
   293  	}
   294  }