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