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