github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/raftlease/store_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package raftlease_test 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/clock/testclock" 11 "github.com/juju/errors" 12 "github.com/juju/pubsub" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/names.v2" 17 18 "github.com/juju/juju/core/globalclock" 19 "github.com/juju/juju/core/lease" 20 "github.com/juju/juju/core/raftlease" 21 coretesting "github.com/juju/juju/testing" 22 ) 23 24 type storeSuite struct { 25 testing.IsolationSuite 26 27 clock *testclock.Clock 28 fsm *fakeFSM 29 hub *pubsub.StructuredHub 30 store *raftlease.Store 31 } 32 33 var _ = gc.Suite(&storeSuite{}) 34 35 func (s *storeSuite) SetUpTest(c *gc.C) { 36 s.IsolationSuite.SetUpTest(c) 37 38 startTime, err := time.Parse(time.RFC3339, "2018-08-08T08:08:08+08:00") 39 c.Assert(err, jc.ErrorIsNil) 40 s.clock = testclock.NewClock(startTime) 41 s.fsm = &fakeFSM{ 42 leases: make(map[lease.Key]lease.Info), 43 globalTime: s.clock.Now(), 44 } 45 s.hub = pubsub.NewStructuredHub(nil) 46 s.store = raftlease.NewStore(raftlease.StoreConfig{ 47 FSM: s.fsm, 48 Hub: s.hub, 49 Trapdoor: FakeTrapdoor, 50 RequestTopic: "lease.request", 51 ResponseTopic: func(reqID uint64) string { 52 return fmt.Sprintf("lease.request.%d", reqID) 53 }, 54 Clock: s.clock, 55 ForwardTimeout: time.Second, 56 }) 57 } 58 59 func (s *storeSuite) TestClaim(c *gc.C) { 60 s.handleHubRequest(c, 61 func() { 62 err := s.store.ClaimLease( 63 lease.Key{"warframe", "rhino", "prime"}, 64 lease.Request{"lotus", time.Second}, 65 ) 66 c.Assert(err, jc.ErrorIsNil) 67 }, 68 69 raftlease.Command{ 70 Version: 1, 71 Operation: raftlease.OperationClaim, 72 Namespace: "warframe", 73 ModelUUID: "rhino", 74 Lease: "prime", 75 Holder: "lotus", 76 Duration: time.Second, 77 }, 78 func(req raftlease.ForwardRequest) { 79 _, err := s.hub.Publish( 80 req.ResponseTopic, 81 raftlease.ForwardResponse{}, 82 ) 83 c.Check(err, jc.ErrorIsNil) 84 }, 85 ) 86 } 87 88 func (s *storeSuite) TestClaimTimeout(c *gc.C) { 89 s.handleHubRequest(c, 90 func() { 91 errChan := make(chan error) 92 go func() { 93 errChan <- s.store.ClaimLease( 94 lease.Key{"warframe", "vauban", "prime"}, 95 lease.Request{"vor", time.Second}, 96 ) 97 }() 98 // Jump time forward further than the 1-second forward 99 // timeout. 100 c.Assert(s.clock.WaitAdvance(2*time.Second, coretesting.LongWait, 1), jc.ErrorIsNil) 101 102 select { 103 case err := <-errChan: 104 c.Assert(err, jc.Satisfies, lease.IsTimeout) 105 case <-time.After(coretesting.LongWait): 106 c.Fatalf("timed out waiting for claim error") 107 } 108 }, 109 110 raftlease.Command{ 111 Version: 1, 112 Operation: raftlease.OperationClaim, 113 Namespace: "warframe", 114 ModelUUID: "vauban", 115 Lease: "prime", 116 Holder: "vor", 117 Duration: time.Second, 118 }, 119 func(req raftlease.ForwardRequest) { 120 // We never send a response, to trigger a timeout. 121 }, 122 ) 123 } 124 125 func (s *storeSuite) TestClaimInvalid(c *gc.C) { 126 s.handleHubRequest(c, 127 func() { 128 err := s.store.ClaimLease( 129 lease.Key{"warframe", "volt", "umbra"}, 130 lease.Request{"maroo", 3 * time.Second}, 131 ) 132 c.Assert(err, jc.Satisfies, lease.IsInvalid) 133 }, 134 135 raftlease.Command{ 136 Version: 1, 137 Operation: raftlease.OperationClaim, 138 Namespace: "warframe", 139 ModelUUID: "volt", 140 Lease: "umbra", 141 Holder: "maroo", 142 Duration: 3 * time.Second, 143 }, 144 func(req raftlease.ForwardRequest) { 145 _, err := s.hub.Publish( 146 req.ResponseTopic, 147 raftlease.ForwardResponse{ 148 Error: &raftlease.ResponseError{ 149 Code: "invalid", 150 }, 151 }, 152 ) 153 c.Check(err, jc.ErrorIsNil) 154 }, 155 ) 156 } 157 158 func (s *storeSuite) TestExtend(c *gc.C) { 159 s.handleHubRequest(c, 160 func() { 161 err := s.store.ExtendLease( 162 lease.Key{"warframe", "frost", "prime"}, 163 lease.Request{"konzu", time.Second}, 164 ) 165 c.Assert(err, jc.ErrorIsNil) 166 }, 167 168 raftlease.Command{ 169 Version: 1, 170 Operation: raftlease.OperationExtend, 171 Namespace: "warframe", 172 ModelUUID: "frost", 173 Lease: "prime", 174 Holder: "konzu", 175 Duration: time.Second, 176 }, 177 func(req raftlease.ForwardRequest) { 178 _, err := s.hub.Publish( 179 req.ResponseTopic, 180 raftlease.ForwardResponse{}, 181 ) 182 c.Check(err, jc.ErrorIsNil) 183 }, 184 ) 185 } 186 187 func (s *storeSuite) TestExpire(c *gc.C) { 188 err := s.store.ExpireLease( 189 lease.Key{"warframe", "oberon", "prime"}, 190 ) 191 c.Assert(err, jc.Satisfies, lease.IsInvalid) 192 } 193 194 func (s *storeSuite) TestLeases(c *gc.C) { 195 in5Seconds := s.clock.Now().Add(5 * time.Second) 196 in10Seconds := s.clock.Now().Add(10 * time.Second) 197 lease1 := lease.Key{"quam", "olim", "abrahe"} 198 lease2 := lease.Key{"la", "cry", "mosa"} 199 s.fsm.leases[lease1] = lease.Info{ 200 Holder: "verdi", 201 Expiry: in10Seconds, 202 } 203 s.fsm.leases[lease2] = lease.Info{ 204 Holder: "mozart", 205 Expiry: in5Seconds, 206 } 207 result := s.store.Leases() 208 c.Assert(len(result), gc.Equals, 2) 209 210 r1 := result[lease1] 211 c.Assert(r1.Holder, gc.Equals, "verdi") 212 c.Assert(r1.Expiry, gc.Equals, in10Seconds) 213 214 // Can't compare trapdoors directly. 215 var out string 216 err := r1.Trapdoor(0, &out) 217 c.Assert(err, jc.ErrorIsNil) 218 c.Assert(out, gc.Equals, "{quam olim abrahe} held by verdi") 219 220 r2 := result[lease2] 221 c.Assert(r2.Holder, gc.Equals, "mozart") 222 c.Assert(r2.Expiry, gc.Equals, in5Seconds) 223 224 err = r2.Trapdoor(0, &out) 225 c.Assert(err, jc.ErrorIsNil) 226 c.Assert(out, gc.Equals, "{la cry mosa} held by mozart") 227 } 228 229 func (s *storeSuite) TestLeasesFilter(c *gc.C) { 230 lease1 := lease.Key{Namespace: "quam", ModelUUID: "olim", Lease: "abrahe"} 231 lease2 := lease.Key{Namespace: "la", ModelUUID: "cry", Lease: "mosa"} 232 233 _ = s.store.Leases(lease1, lease2) 234 s.fsm.CheckCallNames(c, "Leases") 235 c.Check(s.fsm.Calls()[0].Args[1], jc.SameContents, []lease.Key{lease1, lease2}) 236 } 237 238 func (s *storeSuite) TestPin(c *gc.C) { 239 machine := names.NewMachineTag("0").String() 240 s.handleHubRequest(c, 241 func() { 242 err := s.store.PinLease( 243 lease.Key{"warframe", "frost", "prime"}, 244 machine, 245 ) 246 c.Assert(err, jc.ErrorIsNil) 247 }, 248 raftlease.Command{ 249 Version: 1, 250 Operation: raftlease.OperationPin, 251 Namespace: "warframe", 252 ModelUUID: "frost", 253 Lease: "prime", 254 PinEntity: machine, 255 }, 256 func(req raftlease.ForwardRequest) { 257 _, err := s.hub.Publish( 258 req.ResponseTopic, 259 raftlease.ForwardResponse{}, 260 ) 261 c.Check(err, jc.ErrorIsNil) 262 }, 263 ) 264 } 265 266 func (s *storeSuite) TestUnpin(c *gc.C) { 267 machine := names.NewMachineTag("0").String() 268 s.handleHubRequest(c, 269 func() { 270 err := s.store.UnpinLease( 271 lease.Key{"warframe", "frost", "prime"}, 272 machine, 273 ) 274 c.Assert(err, jc.ErrorIsNil) 275 }, 276 raftlease.Command{ 277 Version: 1, 278 Operation: raftlease.OperationUnpin, 279 Namespace: "warframe", 280 ModelUUID: "frost", 281 Lease: "prime", 282 PinEntity: machine, 283 }, 284 func(req raftlease.ForwardRequest) { 285 _, err := s.hub.Publish( 286 req.ResponseTopic, 287 raftlease.ForwardResponse{}, 288 ) 289 c.Check(err, jc.ErrorIsNil) 290 }, 291 ) 292 } 293 294 func (s *storeSuite) TestPinned(c *gc.C) { 295 s.fsm.pinned = map[lease.Key][]string{} 296 c.Check(s.store.Pinned(), gc.DeepEquals, s.fsm.pinned) 297 s.fsm.CheckCallNames(c, "Pinned") 298 } 299 300 // handleHubRequest takes the action that triggers the request, the 301 // expected command, and a function that will be run to make checks on 302 // the request and send the response back. 303 func (s *storeSuite) handleHubRequest( 304 c *gc.C, 305 action func(), 306 expectCommand raftlease.Command, 307 responder func(raftlease.ForwardRequest), 308 ) { 309 expected := marshal(c, expectCommand) 310 called := make(chan struct{}) 311 unsubscribe, err := s.hub.Subscribe( 312 "lease.request", 313 func(_ string, req raftlease.ForwardRequest, err error) { 314 defer close(called) 315 c.Check(err, jc.ErrorIsNil) 316 c.Check(req.Command, gc.DeepEquals, expected) 317 responder(req) 318 }, 319 ) 320 c.Assert(err, jc.ErrorIsNil) 321 defer unsubscribe() 322 323 action() 324 select { 325 case <-called: 326 case <-time.After(coretesting.LongWait): 327 c.Fatalf("timed out waiting for hub message") 328 } 329 } 330 331 func (s *storeSuite) TestAdvance(c *gc.C) { 332 fromTime := s.clock.Now() 333 334 s.handleHubRequest(c, 335 func() { 336 err := s.store.Advance(10 * time.Second) 337 c.Assert(err, jc.ErrorIsNil) 338 }, 339 raftlease.Command{ 340 Version: 1, 341 Operation: raftlease.OperationSetTime, 342 OldTime: fromTime, 343 NewTime: fromTime.Add(10 * time.Second), 344 }, 345 func(req raftlease.ForwardRequest) { 346 c.Check(req.ResponseTopic, gc.Equals, "lease.request.1") 347 _, err := s.hub.Publish( 348 req.ResponseTopic, 349 raftlease.ForwardResponse{}, 350 ) 351 c.Check(err, jc.ErrorIsNil) 352 }, 353 ) 354 // The store time advances, as seen in the next update. 355 s.handleHubRequest(c, 356 func() { 357 err := s.store.Advance(5 * time.Second) 358 c.Assert(err, jc.ErrorIsNil) 359 }, 360 raftlease.Command{ 361 Version: 1, 362 Operation: raftlease.OperationSetTime, 363 OldTime: fromTime.Add(10 * time.Second), 364 NewTime: fromTime.Add(15 * time.Second), 365 }, 366 func(req raftlease.ForwardRequest) { 367 c.Check(req.ResponseTopic, gc.Equals, "lease.request.2") 368 _, err := s.hub.Publish( 369 req.ResponseTopic, 370 raftlease.ForwardResponse{}, 371 ) 372 c.Check(err, jc.ErrorIsNil) 373 }, 374 ) 375 } 376 377 func (s *storeSuite) TestAdvanceConcurrentUpdate(c *gc.C) { 378 fromTime := s.clock.Now() 379 plus5Sec := fromTime.Add(5 * time.Second) 380 plus10Sec := fromTime.Add(10 * time.Second) 381 s.fsm.globalTime = plus5Sec 382 383 s.handleHubRequest(c, 384 func() { 385 err := s.store.Advance(10 * time.Second) 386 c.Assert(err, jc.Satisfies, globalclock.IsConcurrentUpdate) 387 }, 388 raftlease.Command{ 389 Version: 1, 390 Operation: raftlease.OperationSetTime, 391 OldTime: fromTime, 392 NewTime: plus10Sec, 393 }, 394 func(req raftlease.ForwardRequest) { 395 _, err := s.hub.Publish( 396 req.ResponseTopic, 397 raftlease.ForwardResponse{ 398 Error: &raftlease.ResponseError{ 399 Code: "concurrent-update", 400 }, 401 }, 402 ) 403 c.Check(err, jc.ErrorIsNil) 404 }, 405 ) 406 407 // Check that the store updates time from the FSM for when we try 408 // again. 409 s.handleHubRequest(c, 410 func() { 411 err := s.store.Advance(10 * time.Second) 412 c.Assert(err, jc.ErrorIsNil) 413 }, 414 raftlease.Command{ 415 Version: 1, 416 Operation: raftlease.OperationSetTime, 417 OldTime: plus5Sec, 418 NewTime: fromTime.Add(15 * time.Second), 419 }, 420 func(req raftlease.ForwardRequest) { 421 c.Check(req.ResponseTopic, gc.Equals, "lease.request.2") 422 _, err := s.hub.Publish( 423 req.ResponseTopic, 424 raftlease.ForwardResponse{}, 425 ) 426 c.Check(err, jc.ErrorIsNil) 427 }, 428 ) 429 } 430 431 func (s *storeSuite) TestAdvanceTimeout(c *gc.C) { 432 fromTime := s.clock.Now() 433 s.handleHubRequest(c, 434 func() { 435 errChan := make(chan error) 436 go func() { 437 errChan <- s.store.Advance(10 * time.Second) 438 }() 439 440 // Move time forward to trigger the timeout. 441 c.Assert(s.clock.WaitAdvance(2*time.Second, coretesting.LongWait, 1), jc.ErrorIsNil) 442 443 select { 444 case err := <-errChan: 445 c.Assert(err, jc.Satisfies, globalclock.IsTimeout) 446 case <-time.After(coretesting.LongWait): 447 c.Fatalf("timed out waiting for advance error") 448 } 449 }, 450 raftlease.Command{ 451 Version: 1, 452 Operation: raftlease.OperationSetTime, 453 OldTime: fromTime, 454 NewTime: fromTime.Add(10 * time.Second), 455 }, 456 func(raftlease.ForwardRequest) { 457 // No response sent, to trigger the timeout. 458 }) 459 } 460 461 func (s *storeSuite) TestAsResponseError(c *gc.C) { 462 c.Assert( 463 raftlease.AsResponseError(lease.ErrInvalid), 464 gc.DeepEquals, 465 &raftlease.ResponseError{ 466 "invalid lease operation", 467 "invalid", 468 }, 469 ) 470 c.Assert( 471 raftlease.AsResponseError(globalclock.ErrConcurrentUpdate), 472 gc.DeepEquals, 473 &raftlease.ResponseError{ 474 "clock was updated concurrently, retry", 475 "concurrent-update", 476 }, 477 ) 478 c.Assert( 479 raftlease.AsResponseError(errors.Errorf("generic")), 480 gc.DeepEquals, 481 &raftlease.ResponseError{ 482 "generic", 483 "error", 484 }, 485 ) 486 } 487 488 func (s *storeSuite) TestRecoverError(c *gc.C) { 489 c.Assert(raftlease.RecoverError(nil), gc.Equals, nil) 490 re := func(msg, code string) error { 491 return raftlease.RecoverError(&raftlease.ResponseError{ 492 Message: msg, 493 Code: code, 494 }) 495 } 496 c.Assert(re("", "invalid"), jc.Satisfies, lease.IsInvalid) 497 c.Assert(re("", "concurrent-update"), jc.Satisfies, globalclock.IsConcurrentUpdate) 498 c.Assert(re("something", "else"), gc.ErrorMatches, "something") 499 } 500 501 type fakeFSM struct { 502 testing.Stub 503 leases map[lease.Key]lease.Info 504 globalTime time.Time 505 pinned map[lease.Key][]string 506 } 507 508 func (f *fakeFSM) Leases(t func() time.Time, keys ...lease.Key) map[lease.Key]lease.Info { 509 f.AddCall("Leases", t(), keys) 510 return f.leases 511 } 512 513 func (f *fakeFSM) Pinned() map[lease.Key][]string { 514 f.AddCall("Pinned") 515 return f.pinned 516 } 517 518 func (f *fakeFSM) GlobalTime() time.Time { 519 return f.globalTime 520 } 521 522 func FakeTrapdoor(key lease.Key, holder string) lease.Trapdoor { 523 return func(attempt int, out interface{}) error { 524 if s, ok := out.(*string); ok { 525 *s = fmt.Sprintf("%v held by %s", key, holder) 526 return nil 527 } 528 return errors.Errorf("bad input") 529 } 530 } 531 532 func marshal(c *gc.C, command raftlease.Command) string { 533 result, err := command.Marshal() 534 c.Assert(err, jc.ErrorIsNil) 535 return string(result) 536 }