github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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/testing"
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  
    13  	corelease "github.com/juju/juju/core/lease"
    14  	"github.com/juju/juju/worker/lease"
    15  )
    16  
    17  type WaitUntilExpiredSuite struct {
    18  	testing.IsolationSuite
    19  }
    20  
    21  var _ = gc.Suite(&WaitUntilExpiredSuite{})
    22  
    23  func (s *WaitUntilExpiredSuite) TestLeadershipNotHeld(c *gc.C) {
    24  	fix := &Fixture{}
    25  	fix.RunTest(c, func(manager *lease.Manager, _ *testing.Clock) {
    26  		blockTest := newBlockTest(manager, "redis")
    27  		err := blockTest.assertUnblocked(c)
    28  		c.Check(err, jc.ErrorIsNil)
    29  	})
    30  }
    31  
    32  func (s *WaitUntilExpiredSuite) TestLeadershipExpires(c *gc.C) {
    33  	fix := &Fixture{
    34  		leases: map[string]corelease.Info{
    35  			"redis": corelease.Info{
    36  				Holder: "redis/0",
    37  				Expiry: offset(time.Second),
    38  			},
    39  		},
    40  		expectCalls: []call{{
    41  			method: "Refresh",
    42  		}, {
    43  			method: "ExpireLease",
    44  			args:   []interface{}{"redis"},
    45  			callback: func(leases map[string]corelease.Info) {
    46  				delete(leases, "redis")
    47  			},
    48  		}},
    49  	}
    50  	fix.RunTest(c, func(manager *lease.Manager, clock *testing.Clock) {
    51  		blockTest := newBlockTest(manager, "redis")
    52  		blockTest.assertBlocked(c)
    53  
    54  		// Trigger expiry.
    55  		clock.Advance(time.Second)
    56  		err := blockTest.assertUnblocked(c)
    57  		c.Check(err, jc.ErrorIsNil)
    58  	})
    59  }
    60  
    61  func (s *WaitUntilExpiredSuite) TestLeadershipChanged(c *gc.C) {
    62  	fix := &Fixture{
    63  		leases: map[string]corelease.Info{
    64  			"redis": corelease.Info{
    65  				Holder: "redis/0",
    66  				Expiry: offset(time.Second),
    67  			},
    68  		},
    69  		expectCalls: []call{{
    70  			method: "Refresh",
    71  		}, {
    72  			method: "ExpireLease",
    73  			args:   []interface{}{"redis"},
    74  			err:    corelease.ErrInvalid,
    75  			callback: func(leases map[string]corelease.Info) {
    76  				leases["redis"] = corelease.Info{
    77  					Holder: "redis/99",
    78  					Expiry: offset(time.Minute),
    79  				}
    80  			},
    81  		}},
    82  	}
    83  	fix.RunTest(c, func(manager *lease.Manager, clock *testing.Clock) {
    84  		blockTest := newBlockTest(manager, "redis")
    85  		blockTest.assertBlocked(c)
    86  
    87  		// Trigger abortive expiry.
    88  		clock.Advance(time.Second)
    89  		blockTest.assertBlocked(c)
    90  	})
    91  }
    92  
    93  func (s *WaitUntilExpiredSuite) TestLeadershipExpiredEarly(c *gc.C) {
    94  	fix := &Fixture{
    95  		leases: map[string]corelease.Info{
    96  			"redis": corelease.Info{
    97  				Holder: "redis/0",
    98  				Expiry: offset(time.Second),
    99  			},
   100  		},
   101  		expectCalls: []call{{
   102  			method: "Refresh",
   103  			callback: func(leases map[string]corelease.Info) {
   104  				delete(leases, "redis")
   105  			},
   106  		}},
   107  	}
   108  	fix.RunTest(c, func(manager *lease.Manager, clock *testing.Clock) {
   109  		blockTest := newBlockTest(manager, "redis")
   110  		blockTest.assertBlocked(c)
   111  
   112  		// Induce a refresh by making an unexpected check; it turns out the
   113  		// lease had already been expired by someone else.
   114  		manager.Token("redis", "redis/99").Check(nil)
   115  		err := blockTest.assertUnblocked(c)
   116  		c.Check(err, jc.ErrorIsNil)
   117  	})
   118  }
   119  
   120  func (s *WaitUntilExpiredSuite) TestMultiple(c *gc.C) {
   121  	fix := &Fixture{
   122  		leases: map[string]corelease.Info{
   123  			"redis": corelease.Info{
   124  				Holder: "redis/0",
   125  				Expiry: offset(time.Second),
   126  			},
   127  			"store": corelease.Info{
   128  				Holder: "store/0",
   129  				Expiry: offset(time.Second),
   130  			},
   131  		},
   132  		expectCalls: []call{{
   133  			method: "Refresh",
   134  		}, {
   135  			method: "ExpireLease",
   136  			args:   []interface{}{"redis"},
   137  			err:    corelease.ErrInvalid,
   138  			callback: func(leases map[string]corelease.Info) {
   139  				delete(leases, "redis")
   140  				leases["store"] = corelease.Info{
   141  					Holder: "store/9",
   142  					Expiry: offset(time.Minute),
   143  				}
   144  			},
   145  		}, {
   146  			method: "ExpireLease",
   147  			args:   []interface{}{"store"},
   148  			err:    corelease.ErrInvalid,
   149  		}},
   150  	}
   151  	fix.RunTest(c, func(manager *lease.Manager, clock *testing.Clock) {
   152  		redisTest1 := newBlockTest(manager, "redis")
   153  		redisTest1.assertBlocked(c)
   154  		redisTest2 := newBlockTest(manager, "redis")
   155  		redisTest2.assertBlocked(c)
   156  		storeTest1 := newBlockTest(manager, "store")
   157  		storeTest1.assertBlocked(c)
   158  		storeTest2 := newBlockTest(manager, "store")
   159  		storeTest2.assertBlocked(c)
   160  
   161  		// Induce attempted expiry; redis was expired already, store was
   162  		// refreshed and not expired.
   163  		clock.Advance(time.Second)
   164  		err := redisTest2.assertUnblocked(c)
   165  		c.Check(err, jc.ErrorIsNil)
   166  		err = redisTest1.assertUnblocked(c)
   167  		c.Check(err, jc.ErrorIsNil)
   168  		storeTest2.assertBlocked(c)
   169  		storeTest1.assertBlocked(c)
   170  	})
   171  }
   172  
   173  func (s *WaitUntilExpiredSuite) TestKillManager(c *gc.C) {
   174  	fix := &Fixture{
   175  		leases: map[string]corelease.Info{
   176  			"redis": corelease.Info{
   177  				Holder: "redis/0",
   178  				Expiry: offset(time.Second),
   179  			},
   180  		},
   181  	}
   182  	fix.RunTest(c, func(manager *lease.Manager, _ *testing.Clock) {
   183  		blockTest := newBlockTest(manager, "redis")
   184  		blockTest.assertBlocked(c)
   185  
   186  		manager.Kill()
   187  		err := blockTest.assertUnblocked(c)
   188  		c.Check(err, gc.ErrorMatches, "lease manager stopped")
   189  	})
   190  }
   191  
   192  // blockTest wraps a goroutine running WaitUntilExpired, and fails if it's used
   193  // more than a second after creation (which should be *plenty* of time).
   194  type blockTest struct {
   195  	manager   *lease.Manager
   196  	leaseName string
   197  	done      chan error
   198  	abort     <-chan time.Time
   199  }
   200  
   201  // newBlockTest starts a test goroutine blocking until the manager confirms
   202  // expiry of the named lease.
   203  func newBlockTest(manager *lease.Manager, leaseName string) *blockTest {
   204  	bt := &blockTest{
   205  		manager:   manager,
   206  		leaseName: leaseName,
   207  		done:      make(chan error),
   208  		abort:     time.After(time.Second),
   209  	}
   210  	go func() {
   211  		select {
   212  		case <-bt.abort:
   213  		case bt.done <- bt.manager.WaitUntilExpired(bt.leaseName):
   214  		}
   215  	}()
   216  	return bt
   217  }
   218  
   219  func (bt *blockTest) assertBlocked(c *gc.C) {
   220  	select {
   221  	case err := <-bt.done:
   222  		c.Fatalf("unblocked unexpectedly with %v", err)
   223  	default:
   224  	}
   225  }
   226  
   227  func (bt *blockTest) assertUnblocked(c *gc.C) error {
   228  	select {
   229  	case err := <-bt.done:
   230  		return err
   231  	case <-bt.abort:
   232  		c.Fatalf("timed out before unblocking")
   233  	}
   234  	panic("unreachable")
   235  }