github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/lease/client_race_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  	jc "github.com/juju/testing/checkers"
    10  	jujutxn "github.com/juju/txn"
    11  	txntesting "github.com/juju/txn/testing"
    12  	"github.com/juju/utils/clock"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	corelease "github.com/juju/juju/core/lease"
    16  	"github.com/juju/juju/state/lease"
    17  )
    18  
    19  // ClientSimpleRaceSuite tests what happens when two clients interfere with
    20  // each other when creating clients and/or leases.
    21  type ClientSimpleRaceSuite struct {
    22  	FixtureSuite
    23  }
    24  
    25  var _ = gc.Suite(&ClientSimpleRaceSuite{})
    26  
    27  func (s *ClientSimpleRaceSuite) TestNewClient_WorksDespite_CreateClockRace(c *gc.C) {
    28  	config := func(id string) lease.ClientConfig {
    29  		return lease.ClientConfig{
    30  			Id:         id,
    31  			Namespace:  "ns",
    32  			Collection: "leases",
    33  			Mongo:      NewMongo(s.db),
    34  			Clock:      clock.WallClock,
    35  		}
    36  	}
    37  	sutConfig := config("sut")
    38  	sutRunner := sutConfig.Mongo.(*Mongo).runner
    39  
    40  	// Set up a hook to create the clock doc (and write some important data to
    41  	// it)  by creating another client before the SUT gets a chance.
    42  	defer txntesting.SetBeforeHooks(c, sutRunner, func() {
    43  		client, err := lease.NewClient(config("blocker"))
    44  		c.Check(err, jc.ErrorIsNil)
    45  		err = client.ClaimLease("somewhere", corelease.Request{"someone", time.Minute})
    46  		c.Check(err, jc.ErrorIsNil)
    47  	})()
    48  
    49  	// Create a client against an apparently-empty namespace.
    50  	client, err := lease.NewClient(sutConfig)
    51  	c.Check(err, jc.ErrorIsNil)
    52  
    53  	// Despite the scramble, it's generated with recent lease data and no error.
    54  	leases := client.Leases()
    55  	info, found := leases["somewhere"]
    56  	c.Check(found, jc.IsTrue)
    57  	c.Check(info.Holder, gc.Equals, "someone")
    58  }
    59  
    60  func (s *ClientSimpleRaceSuite) TestClaimLease_BlockedBy_ClaimLease(c *gc.C) {
    61  	sut := s.EasyFixture(c)
    62  	blocker := s.NewFixture(c, FixtureParams{Id: "blocker"})
    63  
    64  	// Set up a hook to grab the lease "name" just before the next txn runs.
    65  	defer txntesting.SetBeforeHooks(c, sut.Runner, func() {
    66  		err := blocker.Client.ClaimLease("name", corelease.Request{"ha-haa", time.Minute})
    67  		c.Check(err, jc.ErrorIsNil)
    68  	})()
    69  
    70  	// Try to grab the lease "name", and fail.
    71  	err := sut.Client.ClaimLease("name", corelease.Request{"trying", time.Second})
    72  	c.Check(err, gc.Equals, corelease.ErrInvalid)
    73  
    74  	// The client that failed has refreshed state (as it had to, in order
    75  	// to discover the reason for the invalidity).
    76  	c.Check("name", sut.Holder(), "ha-haa")
    77  	c.Check("name", sut.Expiry(), sut.Zero.Add(time.Minute))
    78  }
    79  
    80  func (s *ClientSimpleRaceSuite) TestClaimLease_Pathological(c *gc.C) {
    81  	sut := s.EasyFixture(c)
    82  	blocker := s.NewFixture(c, FixtureParams{Id: "blocker"})
    83  
    84  	// Set up hooks to claim a lease just before every transaction, but remove
    85  	// it again before the SUT goes and looks to figure out what it should do.
    86  	interfere := jujutxn.TestHook{
    87  		Before: func() {
    88  			err := blocker.Client.ClaimLease("name", corelease.Request{"ha-haa", time.Second})
    89  			c.Check(err, jc.ErrorIsNil)
    90  		},
    91  		After: func() {
    92  			blocker.Clock.Advance(time.Minute)
    93  			err := blocker.Client.ExpireLease("name")
    94  			c.Check(err, jc.ErrorIsNil)
    95  		},
    96  	}
    97  	defer txntesting.SetTestHooks(
    98  		c, sut.Runner,
    99  		interfere, interfere, interfere,
   100  	)()
   101  
   102  	// Try to claim, and watch the poor thing collapse in exhaustion.
   103  	err := sut.Client.ClaimLease("name", corelease.Request{"trying", time.Minute})
   104  	c.Check(err, gc.ErrorMatches, "cannot satisfy request: state changing too quickly; try again soon")
   105  }
   106  
   107  // ClientTrickyRaceSuite tests what happens when two clients interfere with
   108  // each other when extending and/or expiring leases.
   109  type ClientTrickyRaceSuite struct {
   110  	FixtureSuite
   111  	sut     *Fixture
   112  	blocker *Fixture
   113  }
   114  
   115  var _ = gc.Suite(&ClientTrickyRaceSuite{})
   116  
   117  func (s *ClientTrickyRaceSuite) SetUpTest(c *gc.C) {
   118  	s.FixtureSuite.SetUpTest(c)
   119  	s.sut = s.EasyFixture(c)
   120  	err := s.sut.Client.ClaimLease("name", corelease.Request{"holder", time.Minute})
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	s.blocker = s.NewFixture(c, FixtureParams{Id: "blocker"})
   123  }
   124  
   125  func (s *ClientTrickyRaceSuite) TestExtendLease_WorksDespite_ShorterExtendLease(c *gc.C) {
   126  
   127  	shorterRequest := 90 * time.Second
   128  	longerRequest := 120 * time.Second
   129  
   130  	// Set up hooks to extend the lease by a little, before the SUT's extend
   131  	// gets a chance; and then to verify state after it's applied its retry.
   132  	defer txntesting.SetRetryHooks(c, s.sut.Runner, func() {
   133  		err := s.blocker.Client.ExtendLease("name", corelease.Request{"holder", shorterRequest})
   134  		c.Check(err, jc.ErrorIsNil)
   135  	}, func() {
   136  		err := s.blocker.Client.Refresh()
   137  		c.Check(err, jc.ErrorIsNil)
   138  		c.Check("name", s.blocker.Expiry(), s.blocker.Zero.Add(longerRequest))
   139  	})()
   140  
   141  	// Extend the lease.
   142  	err := s.sut.Client.ExtendLease("name", corelease.Request{"holder", longerRequest})
   143  	c.Check(err, jc.ErrorIsNil)
   144  }
   145  
   146  func (s *ClientTrickyRaceSuite) TestExtendLease_WorksDespite_LongerExtendLease(c *gc.C) {
   147  
   148  	shorterRequest := 90 * time.Second
   149  	longerRequest := 120 * time.Second
   150  
   151  	// Set up hooks to extend the lease by a lot, before the SUT's extend can.
   152  	defer txntesting.SetBeforeHooks(c, s.sut.Runner, func() {
   153  		err := s.blocker.Client.ExtendLease("name", corelease.Request{"holder", longerRequest})
   154  		c.Check(err, jc.ErrorIsNil)
   155  	})()
   156  
   157  	// Extend the lease by a little.
   158  	err := s.sut.Client.ExtendLease("name", corelease.Request{"holder", shorterRequest})
   159  	c.Check(err, jc.ErrorIsNil)
   160  
   161  	// The SUT was refreshed, and knows that the lease is really valid for longer.
   162  	c.Check("name", s.sut.Expiry(), s.sut.Zero.Add(longerRequest))
   163  }
   164  
   165  func (s *ClientTrickyRaceSuite) TestExtendLease_BlockedBy_ExpireLease(c *gc.C) {
   166  
   167  	// Set up a hook to expire the lease before the extend gets a chance.
   168  	defer txntesting.SetBeforeHooks(c, s.sut.Runner, func() {
   169  		s.blocker.Clock.Advance(90 * time.Second)
   170  		err := s.blocker.Client.ExpireLease("name")
   171  		c.Check(err, jc.ErrorIsNil)
   172  	})()
   173  
   174  	// Try to extend; check it aborts.
   175  	err := s.sut.Client.ExtendLease("name", corelease.Request{"holder", 2 * time.Minute})
   176  	c.Check(err, gc.Equals, corelease.ErrInvalid)
   177  
   178  	// The SUT has been refreshed, and you can see why the operation was invalid.
   179  	c.Check("name", s.sut.Holder(), "")
   180  }
   181  
   182  func (s *ClientTrickyRaceSuite) TestExtendLease_BlockedBy_ExpireThenReclaimDifferentHolder(c *gc.C) {
   183  
   184  	// Set up a hook to expire and reclaim the lease before the extend gets a
   185  	// chance.
   186  	defer txntesting.SetBeforeHooks(c, s.sut.Runner, func() {
   187  		s.blocker.Clock.Advance(90 * time.Second)
   188  		err := s.blocker.Client.ExpireLease("name")
   189  		c.Check(err, jc.ErrorIsNil)
   190  		err = s.blocker.Client.ClaimLease("name", corelease.Request{"different-holder", time.Minute})
   191  		c.Check(err, jc.ErrorIsNil)
   192  	})()
   193  
   194  	// Try to extend; check it aborts.
   195  	err := s.sut.Client.ExtendLease("name", corelease.Request{"holder", 2 * time.Minute})
   196  	c.Check(err, gc.Equals, corelease.ErrInvalid)
   197  
   198  	// The SUT has been refreshed, and you can see why the operation was invalid.
   199  	c.Check("name", s.sut.Holder(), "different-holder")
   200  }
   201  
   202  func (s *ClientTrickyRaceSuite) TestExtendLease_WorksDespite_ExpireThenReclaimSameHolder(c *gc.C) {
   203  
   204  	// Set up hooks to expire and reclaim the lease before the extend gets a
   205  	// chance; and to verify that the second attempt successfully extends.
   206  	defer txntesting.SetRetryHooks(c, s.sut.Runner, func() {
   207  		s.blocker.Clock.Advance(90 * time.Second)
   208  		err := s.blocker.Client.ExpireLease("name")
   209  		c.Check(err, jc.ErrorIsNil)
   210  		err = s.blocker.Client.ClaimLease("name", corelease.Request{"holder", time.Minute})
   211  		c.Check(err, jc.ErrorIsNil)
   212  	}, func() {
   213  		err := s.blocker.Client.Refresh()
   214  		c.Check(err, jc.ErrorIsNil)
   215  		c.Check("name", s.blocker.Expiry(), s.blocker.Zero.Add(5*time.Minute))
   216  	})()
   217  
   218  	// Try to extend; check it worked.
   219  	err := s.sut.Client.ExtendLease("name", corelease.Request{"holder", 5 * time.Minute})
   220  	c.Check(err, jc.ErrorIsNil)
   221  }
   222  
   223  func (s *ClientTrickyRaceSuite) TestExtendLease_Pathological(c *gc.C) {
   224  
   225  	// Set up hooks to remove the lease just before every transaction, but
   226  	// replace it before the SUT goes and looks to figure out what it should do.
   227  	interfere := jujutxn.TestHook{
   228  		Before: func() {
   229  			s.blocker.Clock.Advance(time.Minute + time.Second)
   230  			err := s.blocker.Client.ExpireLease("name")
   231  			c.Check(err, jc.ErrorIsNil)
   232  		},
   233  		After: func() {
   234  			err := s.blocker.Client.ClaimLease("name", corelease.Request{"holder", time.Second})
   235  			c.Check(err, jc.ErrorIsNil)
   236  		},
   237  	}
   238  	defer txntesting.SetTestHooks(
   239  		c, s.sut.Runner,
   240  		interfere, interfere, interfere,
   241  	)()
   242  
   243  	// Try to extend, and watch the poor thing collapse in exhaustion.
   244  	err := s.sut.Client.ExtendLease("name", corelease.Request{"holder", time.Minute})
   245  	c.Check(err, gc.ErrorMatches, "cannot satisfy request: state changing too quickly; try again soon")
   246  }
   247  
   248  func (s *ClientTrickyRaceSuite) TestExpireLease_BlockedBy_ExtendLease(c *gc.C) {
   249  
   250  	// Set up a hook to extend the lease before the expire gets a chance.
   251  	defer txntesting.SetBeforeHooks(c, s.sut.Runner, func() {
   252  		s.blocker.Clock.Advance(90 * time.Second)
   253  		err := s.blocker.Client.ExtendLease("name", corelease.Request{"holder", 30 * time.Second})
   254  		c.Check(err, jc.ErrorIsNil)
   255  	})()
   256  
   257  	// Try to expire; check it aborts.
   258  	s.sut.Clock.Advance(90 * time.Second)
   259  	err := s.sut.Client.ExpireLease("name")
   260  	c.Check(err, gc.Equals, corelease.ErrInvalid)
   261  
   262  	// The SUT has been refreshed, and you can see why the operation was invalid.
   263  	c.Check("name", s.sut.Expiry(), s.sut.Zero.Add(2*time.Minute))
   264  }
   265  
   266  func (s *ClientTrickyRaceSuite) TestExpireLease_BlockedBy_ExpireLease(c *gc.C) {
   267  
   268  	// Set up a hook to expire the lease before the SUT gets a chance.
   269  	defer txntesting.SetBeforeHooks(c, s.sut.Runner, func() {
   270  		s.blocker.Clock.Advance(90 * time.Second)
   271  		err := s.blocker.Client.ExpireLease("name")
   272  		c.Check(err, jc.ErrorIsNil)
   273  	})()
   274  
   275  	// Try to expire; check it aborts.
   276  	s.sut.Clock.Advance(90 * time.Second)
   277  	err := s.sut.Client.ExpireLease("name")
   278  	c.Check(err, gc.Equals, corelease.ErrInvalid)
   279  
   280  	// The SUT has been refreshed, and you can see why the operation was invalid.
   281  	c.Check("name", s.sut.Holder(), "")
   282  }
   283  
   284  func (s *ClientTrickyRaceSuite) TestExpireLease_BlockedBy_ExpireThenReclaim(c *gc.C) {
   285  
   286  	// Set up a hook to expire the lease and then reclaim it.
   287  	defer txntesting.SetBeforeHooks(c, s.sut.Runner, func() {
   288  		s.blocker.Clock.Advance(90 * time.Second)
   289  		err := s.blocker.Client.ExpireLease("name")
   290  		c.Check(err, jc.ErrorIsNil)
   291  		err = s.blocker.Client.ClaimLease("name", corelease.Request{"holder", time.Minute})
   292  		c.Check(err, jc.ErrorIsNil)
   293  	})()
   294  
   295  	// Try to expire; check it aborts.
   296  	s.sut.Clock.Advance(90 * time.Second)
   297  	err := s.sut.Client.ExpireLease("name")
   298  	c.Check(err, gc.Equals, corelease.ErrInvalid)
   299  
   300  	// The SUT has been refreshed, and you can see why the operation was invalid.
   301  	c.Check("name", s.sut.Expiry(), s.sut.Zero.Add(150*time.Second))
   302  }
   303  
   304  // ClientNTPSuite tests what happens when ntp messes with the clock.
   305  type ClientNTPSuite struct {
   306  	FixtureSuite
   307  }
   308  
   309  var _ = gc.Suite(&ClientNTPSuite{})
   310  
   311  func (s *ClientNTPSuite) TestTimeGoesForwards(c *gc.C) {
   312  	f := s.EasyFixture(c)
   313  	f.Clock.step = 2 * time.Millisecond
   314  	err := f.Client.Refresh()
   315  	c.Assert(err, jc.ErrorIsNil)
   316  }
   317  
   318  func (s *ClientNTPSuite) TestTimeGoesBackwardsALittle(c *gc.C) {
   319  	f := s.EasyFixture(c)
   320  	f.Clock.step = -2 * time.Millisecond
   321  	err := f.Client.Refresh()
   322  	c.Assert(err, jc.ErrorIsNil)
   323  }
   324  
   325  func (s *ClientNTPSuite) TestTimeGoesBackwardsALot(c *gc.C) {
   326  	f := s.EasyFixture(c)
   327  	f.Clock.step = -20 * time.Millisecond
   328  	err := f.Client.Refresh()
   329  	c.Assert(err.Error(), gc.Equals, "end of read window preceded beginning (20ms)")
   330  }