github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/raftlease/fsm_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 "bytes" 8 "io" 9 "time" 10 11 "github.com/hashicorp/raft" 12 "github.com/juju/errors" 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 "gopkg.in/yaml.v2" 18 19 "github.com/juju/juju/core/globalclock" 20 "github.com/juju/juju/core/lease" 21 "github.com/juju/juju/core/raftlease" 22 ) 23 24 var zero time.Time 25 26 type fsmSuite struct { 27 testing.IsolationSuite 28 29 fsm *raftlease.FSM 30 } 31 32 var _ = gc.Suite(&fsmSuite{}) 33 34 func (s *fsmSuite) SetUpTest(c *gc.C) { 35 s.IsolationSuite.SetUpTest(c) 36 s.fsm = raftlease.NewFSM() 37 } 38 39 func (s *fsmSuite) apply(c *gc.C, command raftlease.Command) raftlease.FSMResponse { 40 data, err := command.Marshal() 41 c.Assert(err, jc.ErrorIsNil) 42 result := s.fsm.Apply(&raft.Log{Data: data}) 43 response, ok := result.(raftlease.FSMResponse) 44 c.Assert(ok, gc.Equals, true) 45 return response 46 } 47 48 func (s *fsmSuite) TestClaim(c *gc.C) { 49 command := raftlease.Command{ 50 Version: 1, 51 Operation: raftlease.OperationClaim, 52 Namespace: "ns", 53 ModelUUID: "model", 54 Lease: "lease", 55 Holder: "me", 56 Duration: time.Second, 57 } 58 resp := s.apply(c, command) 59 c.Assert(resp.Error(), jc.ErrorIsNil) 60 assertClaimed(c, resp, lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "lease"}, "me") 61 62 c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals, 63 map[lease.Key]lease.Info{ 64 {"ns", "model", "lease"}: { 65 Holder: "me", 66 Expiry: offset(time.Second), 67 }, 68 }, 69 ) 70 71 // Can't claim it again. 72 resp = s.apply(c, command) 73 c.Assert(resp.Error(), jc.Satisfies, lease.IsInvalid) 74 assertNoNotifications(c, resp) 75 76 // Someone else trying to claim the lease. 77 command.Holder = "you" 78 resp = s.apply(c, command) 79 c.Assert(resp.Error(), jc.Satisfies, lease.IsInvalid) 80 assertNoNotifications(c, resp) 81 } 82 83 func offset(d time.Duration) time.Time { 84 return zero.Add(d) 85 } 86 87 func (s *fsmSuite) TestExtend(c *gc.C) { 88 // Can't extend unless we've previously claimed. 89 command := raftlease.Command{ 90 Version: 1, 91 Operation: raftlease.OperationExtend, 92 Namespace: "ns", 93 ModelUUID: "model", 94 Lease: "lease", 95 Holder: "me", 96 Duration: time.Second, 97 } 98 resp := s.apply(c, command) 99 c.Assert(resp.Error(), jc.Satisfies, lease.IsInvalid) 100 assertNoNotifications(c, resp) 101 102 // Ok, so we'll claim it. 103 command.Operation = raftlease.OperationClaim 104 resp = s.apply(c, command) 105 c.Assert(resp.Error(), jc.ErrorIsNil) 106 assertClaimed(c, resp, lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "lease"}, "me") 107 108 // Now we can extend it. 109 command.Operation = raftlease.OperationExtend 110 command.Duration = 2 * time.Second 111 resp = s.apply(c, command) 112 c.Assert(resp.Error(), jc.ErrorIsNil) 113 assertNoNotifications(c, resp) 114 115 c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals, 116 map[lease.Key]lease.Info{ 117 {"ns", "model", "lease"}: { 118 Holder: "me", 119 Expiry: offset(2 * time.Second), 120 }, 121 }, 122 ) 123 124 // Extending by a time less than the remaining duration doesn't 125 // shorten the lease (but does succeed). 126 command.Duration = time.Millisecond 127 resp = s.apply(c, command) 128 c.Assert(resp.Error(), jc.ErrorIsNil) 129 assertNoNotifications(c, resp) 130 131 c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals, 132 map[lease.Key]lease.Info{ 133 {"ns", "model", "lease"}: { 134 Holder: "me", 135 Expiry: offset(2 * time.Second), 136 }, 137 }, 138 ) 139 140 // Someone else can't extend it. 141 command.Holder = "you" 142 resp = s.apply(c, command) 143 c.Assert(resp.Error(), jc.Satisfies, lease.IsInvalid) 144 assertNoNotifications(c, resp) 145 } 146 147 func (s *fsmSuite) TestSetTime(c *gc.C) { 148 // Time always starts at 0. 149 resp := s.apply(c, raftlease.Command{ 150 Version: 1, 151 Operation: raftlease.OperationSetTime, 152 OldTime: zero, 153 NewTime: zero.Add(2 * time.Second), 154 }) 155 c.Assert(resp.Error(), jc.ErrorIsNil) 156 assertNoNotifications(c, resp) 157 c.Assert(s.fsm.GlobalTime(), gc.Equals, zero.Add(2*time.Second)) 158 159 c.Assert(s.apply(c, raftlease.Command{ 160 Version: 1, 161 Operation: raftlease.OperationSetTime, 162 OldTime: zero, 163 NewTime: zero.Add(time.Second), 164 }).Error(), jc.Satisfies, globalclock.IsConcurrentUpdate) 165 } 166 167 func (s *fsmSuite) TestSetTimeExpiresLeases(c *gc.C) { 168 c.Assert(s.apply(c, raftlease.Command{ 169 Version: 1, 170 Operation: raftlease.OperationSetTime, 171 OldTime: zero, 172 NewTime: offset(2 * time.Second), 173 }).Error(), jc.ErrorIsNil) 174 c.Assert(s.apply(c, raftlease.Command{ 175 Version: 1, 176 Operation: raftlease.OperationClaim, 177 Namespace: "ns", 178 ModelUUID: "model", 179 Lease: "much-earlier", 180 Holder: "you", 181 Duration: time.Second, 182 }).Error(), jc.ErrorIsNil) 183 c.Assert(s.apply(c, raftlease.Command{ 184 Version: 1, 185 Operation: raftlease.OperationClaim, 186 Namespace: "ns", 187 ModelUUID: "model", 188 Lease: "just-before", 189 Holder: "you", 190 Duration: (2 * time.Second) - time.Nanosecond, 191 }).Error(), jc.ErrorIsNil) 192 c.Assert(s.apply(c, raftlease.Command{ 193 Version: 1, 194 Operation: raftlease.OperationClaim, 195 Namespace: "ns", 196 ModelUUID: "model", 197 Lease: "bang-on", 198 Holder: "you", 199 Duration: 2 * time.Second, 200 }).Error(), jc.ErrorIsNil) 201 c.Assert(s.apply(c, raftlease.Command{ 202 Version: 1, 203 Operation: raftlease.OperationClaim, 204 Namespace: "ns", 205 ModelUUID: "model", 206 Lease: "well-after", 207 Holder: "them", 208 Duration: time.Minute, 209 }).Error(), jc.ErrorIsNil) 210 211 // Advance time by another 2 seconds, and two of the leases are 212 // autoexpired. 213 resp := s.apply(c, raftlease.Command{ 214 Version: 1, 215 Operation: raftlease.OperationSetTime, 216 OldTime: offset(2 * time.Second), 217 NewTime: offset(4 * time.Second), 218 }) 219 c.Assert(resp.Error(), jc.ErrorIsNil) 220 assertExpired(c, resp, 221 lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "much-earlier"}, 222 lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "just-before"}, 223 ) 224 225 // Using the same local time as global time to keep things clear. 226 c.Assert(s.fsm.Leases(timeDelegate(offset(4*time.Second))), gc.DeepEquals, 227 map[lease.Key]lease.Info{ 228 {"ns", "model", "bang-on"}: { 229 Holder: "you", 230 Expiry: offset(4 * time.Second), 231 }, 232 {"ns", "model", "well-after"}: { 233 Holder: "them", 234 Expiry: offset(62 * time.Second), 235 }, 236 }, 237 ) 238 } 239 240 func (s *fsmSuite) TestPinUnpin(c *gc.C) { 241 c.Assert(s.apply(c, raftlease.Command{ 242 Version: 1, 243 Operation: raftlease.OperationSetTime, 244 OldTime: zero, 245 NewTime: offset(2 * time.Second), 246 }).Error(), jc.ErrorIsNil) 247 c.Assert(s.apply(c, raftlease.Command{ 248 Version: 1, 249 Operation: raftlease.OperationClaim, 250 Namespace: "ns", 251 ModelUUID: "model", 252 Lease: "lease", 253 Holder: "me", 254 Duration: time.Second, 255 }).Error(), jc.ErrorIsNil) 256 257 machine := names.NewMachineTag("0").String() 258 c.Assert(s.apply(c, raftlease.Command{ 259 Version: 1, 260 Operation: raftlease.OperationPin, 261 Namespace: "ns", 262 ModelUUID: "model", 263 Lease: "lease", 264 PinEntity: machine, 265 }).Error(), jc.ErrorIsNil) 266 267 // Pinned lease does not expire. 268 resp := s.apply(c, raftlease.Command{ 269 Version: 1, 270 Operation: raftlease.OperationSetTime, 271 OldTime: offset(2 * time.Second), 272 NewTime: offset(4 * time.Second), 273 }) 274 c.Assert(resp.Error(), jc.ErrorIsNil) 275 assertExpired(c, resp) 276 277 exp := map[lease.Key][]string{{Namespace: "ns", ModelUUID: "model", Lease: "lease"}: {machine}} 278 c.Assert(s.fsm.Pinned(), gc.DeepEquals, exp) 279 280 c.Assert(s.apply(c, raftlease.Command{ 281 Version: 1, 282 Operation: raftlease.OperationUnpin, 283 Namespace: "ns", 284 ModelUUID: "model", 285 Lease: "lease", 286 PinEntity: machine, 287 }).Error(), jc.ErrorIsNil) 288 289 // Unpinned lease expires when time advances. 290 resp = s.apply(c, raftlease.Command{ 291 Version: 1, 292 Operation: raftlease.OperationSetTime, 293 OldTime: offset(4 * time.Second), 294 NewTime: offset(6 * time.Second), 295 }) 296 c.Assert(resp.Error(), jc.ErrorIsNil) 297 assertExpired(c, resp, lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "lease"}) 298 299 c.Assert(s.fsm.Pinned(), gc.DeepEquals, map[lease.Key][]string{}) 300 } 301 302 func (s *fsmSuite) TestPinUnpinMultipleHoldersNoExpiry(c *gc.C) { 303 c.Assert(s.apply(c, raftlease.Command{ 304 Version: 1, 305 Operation: raftlease.OperationSetTime, 306 OldTime: zero, 307 NewTime: offset(2 * time.Second), 308 }).Error(), jc.ErrorIsNil) 309 c.Assert(s.apply(c, raftlease.Command{ 310 Version: 1, 311 Operation: raftlease.OperationClaim, 312 Namespace: "ns", 313 ModelUUID: "model", 314 Lease: "lease", 315 Holder: "me", 316 Duration: time.Second, 317 }).Error(), jc.ErrorIsNil) 318 319 // Two different entities pin the same lease. 320 m0 := names.NewMachineTag("0").String() 321 c.Assert(s.apply(c, raftlease.Command{ 322 Version: 1, 323 Operation: raftlease.OperationPin, 324 Namespace: "ns", 325 ModelUUID: "model", 326 Lease: "lease", 327 PinEntity: m0, 328 }).Error(), jc.ErrorIsNil) 329 330 m1 := names.NewMachineTag("1").String() 331 c.Assert(s.apply(c, raftlease.Command{ 332 Version: 1, 333 Operation: raftlease.OperationPin, 334 Namespace: "ns", 335 ModelUUID: "model", 336 Lease: "lease", 337 PinEntity: m1, 338 }).Error(), jc.ErrorIsNil) 339 340 exp := map[lease.Key][]string{{Namespace: "ns", ModelUUID: "model", Lease: "lease"}: {m0, m1}} 341 c.Assert(s.fsm.Pinned(), gc.DeepEquals, exp) 342 343 // One entity releases. 344 c.Assert(s.apply(c, raftlease.Command{ 345 Version: 1, 346 Operation: raftlease.OperationUnpin, 347 Namespace: "ns", 348 ModelUUID: "model", 349 Lease: "lease", 350 PinEntity: m0, 351 }).Error(), jc.ErrorIsNil) 352 353 exp = map[lease.Key][]string{{Namespace: "ns", ModelUUID: "model", Lease: "lease"}: {m1}} 354 c.Assert(s.fsm.Pinned(), gc.DeepEquals, exp) 355 356 // Lease does not expire, as there is still one pin. 357 resp := s.apply(c, raftlease.Command{ 358 Version: 1, 359 Operation: raftlease.OperationSetTime, 360 OldTime: offset(2 * time.Second), 361 NewTime: offset(4 * time.Second), 362 }) 363 c.Assert(resp.Error(), jc.ErrorIsNil) 364 assertExpired(c, resp) 365 } 366 367 func (s *fsmSuite) TestLeases(c *gc.C) { 368 c.Assert(s.apply(c, raftlease.Command{ 369 Version: 1, 370 Operation: raftlease.OperationClaim, 371 Namespace: "ns", 372 ModelUUID: "model", 373 Lease: "lease", 374 Holder: "me", 375 Duration: time.Second, 376 }).Error(), jc.ErrorIsNil) 377 c.Assert(s.apply(c, raftlease.Command{ 378 Version: 1, 379 Operation: raftlease.OperationClaim, 380 Namespace: "ns2", 381 ModelUUID: "model2", 382 Lease: "lease", 383 Holder: "you", 384 Duration: 4 * time.Second, 385 }).Error(), jc.ErrorIsNil) 386 387 c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals, 388 map[lease.Key]lease.Info{ 389 {"ns", "model", "lease"}: { 390 Holder: "me", 391 Expiry: offset(time.Second), 392 }, 393 {"ns2", "model2", "lease"}: { 394 Holder: "you", 395 Expiry: offset(4 * time.Second), 396 }, 397 }, 398 ) 399 } 400 401 func (s *fsmSuite) TestLeasesFilter(c *gc.C) { 402 c.Assert(s.apply(c, raftlease.Command{ 403 Version: 1, 404 Operation: raftlease.OperationClaim, 405 Namespace: "ns", 406 ModelUUID: "model", 407 Lease: "lease", 408 Holder: "me", 409 Duration: time.Second, 410 }).Error(), jc.ErrorIsNil) 411 c.Assert(s.apply(c, raftlease.Command{ 412 Version: 1, 413 Operation: raftlease.OperationClaim, 414 Namespace: "ns2", 415 ModelUUID: "model2", 416 Lease: "lease", 417 Holder: "you", 418 Duration: 4 * time.Second, 419 }).Error(), jc.ErrorIsNil) 420 421 c.Assert( 422 s.fsm.Leases(timeDelegate(zero), lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "lease"}), 423 gc.DeepEquals, 424 map[lease.Key]lease.Info{ 425 {"ns", "model", "lease"}: { 426 Holder: "me", 427 Expiry: offset(time.Second), 428 }, 429 }, 430 ) 431 } 432 433 func (s *fsmSuite) TestLeasesPinnedFutureExpiry(c *gc.C) { 434 c.Assert(s.apply(c, raftlease.Command{ 435 Version: 1, 436 Operation: raftlease.OperationClaim, 437 Namespace: "ns", 438 ModelUUID: "model", 439 Lease: "lease", 440 Holder: "me", 441 Duration: time.Second, 442 }).Error(), jc.ErrorIsNil) 443 c.Assert(s.apply(c, raftlease.Command{ 444 Version: 1, 445 Operation: raftlease.OperationPin, 446 Namespace: "ns", 447 ModelUUID: "model", 448 Lease: "lease", 449 PinEntity: names.NewMachineTag("0").String(), 450 }).Error(), jc.ErrorIsNil) 451 452 // Even though the lease duration is only one second, 453 // expiry should be represented as 30 seconds in the future. 454 c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals, 455 map[lease.Key]lease.Info{ 456 {"ns", "model", "lease"}: { 457 Holder: "me", 458 Expiry: offset(30 * time.Second), 459 }, 460 }, 461 ) 462 } 463 464 func (s *fsmSuite) TestLeasesDifferentTime(c *gc.C) { 465 c.Assert(s.apply(c, raftlease.Command{ 466 Version: 1, 467 Operation: raftlease.OperationClaim, 468 Namespace: "ns", 469 ModelUUID: "model", 470 Lease: "lease", 471 Holder: "me", 472 Duration: 5 * time.Second, 473 }).Error(), jc.ErrorIsNil) 474 c.Assert(s.apply(c, raftlease.Command{ 475 Version: 1, 476 Operation: raftlease.OperationClaim, 477 Namespace: "ns2", 478 ModelUUID: "model2", 479 Lease: "lease", 480 Holder: "you", 481 Duration: 7 * time.Second, 482 }).Error(), jc.ErrorIsNil) 483 c.Assert(s.apply(c, raftlease.Command{ 484 Version: 1, 485 Operation: raftlease.OperationSetTime, 486 OldTime: zero, 487 NewTime: zero.Add(2 * time.Second), 488 }).Error(), jc.ErrorIsNil) 489 490 // Global time is 00:00:02, but we think it's only 00:00:01 491 c.Assert(s.fsm.Leases(timeDelegate(offset(time.Second))), gc.DeepEquals, 492 map[lease.Key]lease.Info{ 493 {"ns", "model", "lease"}: { 494 Holder: "me", 495 Expiry: offset(4 * time.Second), 496 }, 497 {"ns2", "model2", "lease"}: { 498 Holder: "you", 499 Expiry: offset(6 * time.Second), 500 }, 501 }, 502 ) 503 504 // Global time is 00:00:02, but we think it's 00:00:04! 505 c.Assert(s.fsm.Leases(timeDelegate(offset(4*time.Second))), gc.DeepEquals, 506 map[lease.Key]lease.Info{ 507 {"ns", "model", "lease"}: { 508 Holder: "me", 509 Expiry: offset(7 * time.Second), 510 }, 511 {"ns2", "model2", "lease"}: { 512 Holder: "you", 513 Expiry: offset(9 * time.Second), 514 }, 515 }, 516 ) 517 } 518 519 func (s *fsmSuite) TestApplyInvalidCommand(c *gc.C) { 520 c.Assert(s.apply(c, raftlease.Command{ 521 Version: 300, 522 Operation: raftlease.OperationSetTime, 523 OldTime: zero, 524 NewTime: zero.Add(2 * time.Second), 525 }).Error(), jc.Satisfies, errors.IsNotValid) 526 c.Assert(s.apply(c, raftlease.Command{ 527 Version: 1, 528 Operation: "libera-me", 529 }).Error(), jc.Satisfies, errors.IsNotValid) 530 } 531 532 func (s *fsmSuite) TestSnapshot(c *gc.C) { 533 c.Assert(s.apply(c, raftlease.Command{ 534 Version: 1, 535 Operation: raftlease.OperationClaim, 536 Namespace: "ns", 537 ModelUUID: "model", 538 Lease: "lease", 539 Holder: "me", 540 Duration: 3 * time.Second, 541 }).Error(), jc.ErrorIsNil) 542 c.Assert(s.apply(c, raftlease.Command{ 543 Version: 1, 544 Operation: raftlease.OperationSetTime, 545 OldTime: zero, 546 NewTime: zero.Add(2 * time.Second), 547 }).Error(), jc.ErrorIsNil) 548 c.Assert(s.apply(c, raftlease.Command{ 549 Version: 1, 550 Operation: raftlease.OperationClaim, 551 Namespace: "ns2", 552 ModelUUID: "model2", 553 Lease: "lease", 554 Holder: "you", 555 Duration: 4 * time.Second, 556 }).Error(), jc.ErrorIsNil) 557 558 machineTag := names.NewMachineTag("0") 559 c.Assert(s.apply(c, raftlease.Command{ 560 Version: 1, 561 Operation: raftlease.OperationPin, 562 Namespace: "ns", 563 ModelUUID: "model", 564 Lease: "lease", 565 PinEntity: machineTag.String(), 566 }).Error(), jc.ErrorIsNil) 567 568 snapshot, err := s.fsm.Snapshot() 569 c.Assert(err, jc.ErrorIsNil) 570 c.Assert(snapshot, gc.DeepEquals, &raftlease.Snapshot{ 571 Version: 1, 572 Entries: map[raftlease.SnapshotKey]raftlease.SnapshotEntry{ 573 {"ns", "model", "lease"}: { 574 Holder: "me", 575 Start: zero, 576 Duration: 3 * time.Second, 577 }, 578 {"ns2", "model2", "lease"}: { 579 Holder: "you", 580 Start: zero.Add(2 * time.Second), 581 Duration: 4 * time.Second, 582 }, 583 }, 584 GlobalTime: zero.Add(2 * time.Second), 585 Pinned: map[raftlease.SnapshotKey][]string{ 586 {"ns", "model", "lease"}: {machineTag.String()}, 587 }, 588 }) 589 } 590 591 func (s *fsmSuite) TestRestore(c *gc.C) { 592 c.Assert(s.apply(c, raftlease.Command{ 593 Version: 1, 594 Operation: raftlease.OperationClaim, 595 Namespace: "ns", 596 ModelUUID: "model", 597 Lease: "lease", 598 Holder: "me", 599 Duration: time.Second, 600 }).Error(), jc.ErrorIsNil) 601 602 // Restoring overwrites the state. 603 reader := closer{Reader: bytes.NewBuffer([]byte(snapshotYaml))} 604 err := s.fsm.Restore(&reader) 605 c.Assert(err, jc.ErrorIsNil) 606 607 expected := &raftlease.Snapshot{ 608 Version: 1, 609 Entries: map[raftlease.SnapshotKey]raftlease.SnapshotEntry{ 610 {"ns", "model", "lease"}: { 611 Holder: "me", 612 Start: zero, 613 Duration: 5 * time.Second, 614 }, 615 {"ns2", "model2", "lease"}: { 616 Holder: "you", 617 Start: zero.Add(2 * time.Second), 618 Duration: 10 * time.Second, 619 }, 620 }, 621 GlobalTime: zero.Add(3 * time.Second), 622 Pinned: map[raftlease.SnapshotKey][]string{ 623 {"ns", "model", "lease"}: {names.NewMachineTag("0").String()}, 624 }, 625 } 626 627 actual, err := s.fsm.Snapshot() 628 c.Assert(err, jc.ErrorIsNil) 629 c.Assert(actual, gc.DeepEquals, expected) 630 } 631 632 func (s *fsmSuite) TestSnapshotPersist(c *gc.C) { 633 snapshot := &raftlease.Snapshot{ 634 Version: 1, 635 Entries: map[raftlease.SnapshotKey]raftlease.SnapshotEntry{ 636 {"ns", "model", "lease"}: { 637 Holder: "me", 638 Start: zero, 639 Duration: time.Second, 640 }, 641 {"ns2", "model2", "lease"}: { 642 Holder: "you", 643 Start: zero.Add(2 * time.Second), 644 Duration: 4 * time.Second, 645 }, 646 }, 647 Pinned: map[raftlease.SnapshotKey][]string{ 648 {"ns", "model", "lease"}: {names.NewMachineTag("0").String()}, 649 }, 650 GlobalTime: zero.Add(2 * time.Second), 651 } 652 var buffer bytes.Buffer 653 sink := fakeSnapshotSink{Writer: &buffer} 654 err := snapshot.Persist(&sink) 655 c.Assert(err, gc.ErrorMatches, "quam olim abrahe") 656 c.Assert(sink.cancelled, gc.Equals, true) 657 658 // Don't compare buffer bytes in output yaml directly, it's 659 // dependent on map ordering. 660 decoder := yaml.NewDecoder(&buffer) 661 var loaded raftlease.Snapshot 662 err = decoder.Decode(&loaded) 663 c.Assert(err, jc.ErrorIsNil) 664 c.Assert(&loaded, gc.DeepEquals, snapshot) 665 } 666 667 func (s *fsmSuite) TestCommandValidationClaim(c *gc.C) { 668 command := raftlease.Command{ 669 Version: 1, 670 Operation: raftlease.OperationClaim, 671 Namespace: "namespace", 672 ModelUUID: "model", 673 Lease: "lease", 674 Holder: "you", 675 Duration: time.Second, 676 } 677 c.Assert(command.Validate(), gc.Equals, nil) 678 command.OldTime = time.Now() 679 c.Assert(command.Validate(), gc.ErrorMatches, "claim with old time not valid") 680 command.OldTime = time.Time{} 681 command.Lease = "" 682 c.Assert(command.Validate(), gc.ErrorMatches, "claim with empty lease not valid") 683 } 684 685 func (s *fsmSuite) TestCommandValidationExtend(c *gc.C) { 686 command := raftlease.Command{ 687 Version: 1, 688 Operation: raftlease.OperationExtend, 689 Namespace: "namespace", 690 ModelUUID: "model", 691 Lease: "lease", 692 Holder: "you", 693 Duration: time.Second, 694 } 695 c.Assert(command.Validate(), gc.Equals, nil) 696 command.NewTime = time.Now() 697 c.Assert(command.Validate(), gc.ErrorMatches, "extend with new time not valid") 698 command.OldTime = time.Time{} 699 command.Namespace = "" 700 c.Assert(command.Validate(), gc.ErrorMatches, "extend with empty namespace not valid") 701 } 702 703 func (s *fsmSuite) TestCommandValidationSetTime(c *gc.C) { 704 command := raftlease.Command{ 705 Version: 1, 706 Operation: raftlease.OperationSetTime, 707 OldTime: time.Now(), 708 NewTime: time.Now(), 709 } 710 c.Assert(command.Validate(), gc.Equals, nil) 711 command.Duration = time.Minute 712 c.Assert(command.Validate(), gc.ErrorMatches, "setTime with duration not valid") 713 command.Duration = 0 714 command.NewTime = time.Time{} 715 c.Assert(command.Validate(), gc.ErrorMatches, "setTime with zero new time not valid") 716 } 717 718 func (s *fsmSuite) TestCommandValidationPin(c *gc.C) { 719 command := raftlease.Command{ 720 Version: 1, 721 Operation: raftlease.OperationPin, 722 Namespace: "namespace", 723 ModelUUID: "model", 724 Lease: "lease", 725 PinEntity: names.NewMachineTag("0").String(), 726 } 727 c.Assert(command.Validate(), gc.Equals, nil) 728 command.NewTime = time.Now() 729 c.Assert(command.Validate(), gc.ErrorMatches, "pin with new time not valid") 730 command.NewTime = time.Time{} 731 command.Namespace = "" 732 c.Assert(command.Validate(), gc.ErrorMatches, "pin with empty namespace not valid") 733 command.Namespace = "namespace" 734 command.Duration = time.Minute 735 c.Assert(command.Validate(), gc.ErrorMatches, "pin with duration not valid") 736 command.Duration = 0 737 command.PinEntity = "" 738 c.Assert(command.Validate(), gc.ErrorMatches, "pin with empty pin entity not valid") 739 } 740 741 func assertClaimed(c *gc.C, resp raftlease.FSMResponse, key lease.Key, holder string) { 742 var target fakeTarget 743 resp.Notify(&target) 744 c.Assert(target.Calls(), gc.HasLen, 1) 745 target.CheckCall(c, 0, "Claimed", key, holder) 746 } 747 748 func assertExpired(c *gc.C, resp raftlease.FSMResponse, keys ...lease.Key) { 749 // Don't assume the keys are expired in the order given. 750 keySet := make(map[lease.Key]bool, len(keys)) 751 for _, key := range keys { 752 keySet[key] = true 753 } 754 var target fakeTarget 755 resp.Notify(&target) 756 c.Assert(target.Calls(), gc.HasLen, len(keys)) 757 for _, call := range target.Calls() { 758 c.Assert(call.FuncName, gc.Equals, "Expired") 759 c.Assert(call.Args, gc.HasLen, 1) 760 key, ok := call.Args[0].(lease.Key) 761 c.Assert(ok, gc.Equals, true) 762 _, found := keySet[key] 763 c.Assert(found, gc.Equals, true) 764 delete(keySet, key) 765 } 766 } 767 768 func assertNoNotifications(c *gc.C, resp raftlease.FSMResponse) { 769 var target fakeTarget 770 resp.Notify(&target) 771 c.Assert(target.Calls(), gc.HasLen, 0) 772 } 773 774 type fakeTarget struct { 775 testing.Stub 776 } 777 778 func (t *fakeTarget) Claimed(key lease.Key, holder string) { 779 t.AddCall("Claimed", key, holder) 780 } 781 782 func (t *fakeTarget) Expired(key lease.Key) { 783 t.AddCall("Expired", key) 784 } 785 786 type fakeSnapshotSink struct { 787 io.Writer 788 cancelled bool 789 } 790 791 func (s *fakeSnapshotSink) ID() string { 792 return "fakeSink" 793 } 794 795 func (s *fakeSnapshotSink) Cancel() error { 796 s.cancelled = true 797 return nil 798 } 799 800 func (s *fakeSnapshotSink) Close() error { 801 return errors.Errorf("quam olim abrahe") 802 } 803 804 type closer struct { 805 io.Reader 806 closed bool 807 } 808 809 func (c *closer) Close() error { 810 c.closed = true 811 return nil 812 } 813 814 var snapshotYaml = ` 815 version: 1 816 entries: 817 ? namespace: ns 818 model-uuid: model 819 lease: lease 820 : holder: me 821 start: 0001-01-01T00:00:00Z 822 duration: 5s 823 ? namespace: ns2 824 model-uuid: model2 825 lease: lease 826 : holder: you 827 start: 0001-01-01T00:00:02Z 828 duration: 10s 829 global-time: 0001-01-01T00:00:03Z 830 pinned: 831 ? namespace: ns 832 model-uuid: model 833 lease: lease 834 : [machine-0] 835 `[1:] 836 837 // timeDelegate is a convenience wrapper for turning a time into a delegate 838 // returning the input time. 839 // It is intended for use with static time values in testing, so we don't care 840 // that it does not do run-time evaluation. 841 func timeDelegate(t time.Time) func() time.Time { 842 return func() time.Time { return t } 843 }