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 }