istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/leaderelection/k8sleaderelection/leaderelection_test.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // nolint 18 package k8sleaderelection 19 20 import ( 21 "context" 22 "encoding/json" 23 "fmt" 24 "sync" 25 "testing" 26 "time" 27 28 coordinationv1 "k8s.io/api/coordination/v1" 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/api/equality" 31 "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/util/diff" 35 "k8s.io/client-go/kubernetes/fake" 36 fakeclient "k8s.io/client-go/testing" 37 "k8s.io/client-go/tools/record" 38 "k8s.io/utils/clock" 39 40 rl "istio.io/istio/pilot/pkg/leaderelection/k8sleaderelection/k8sresourcelock" 41 ) 42 43 func createLockObject(t *testing.T, objectType, namespace, name string, record *rl.LeaderElectionRecord) (obj runtime.Object) { 44 objectMeta := metav1.ObjectMeta{ 45 Namespace: namespace, 46 Name: name, 47 } 48 if record != nil { 49 recordBytes, _ := json.Marshal(record) 50 objectMeta.Annotations = map[string]string{ 51 rl.LeaderElectionRecordAnnotationKey: string(recordBytes), 52 } 53 } 54 switch objectType { 55 case "endpoints": 56 obj = &corev1.Endpoints{ObjectMeta: objectMeta} 57 case "configmaps": 58 obj = &corev1.ConfigMap{ObjectMeta: objectMeta} 59 case "leases": 60 var spec coordinationv1.LeaseSpec 61 if record != nil { 62 spec = rl.LeaderElectionRecordToLeaseSpec(record) 63 } 64 obj = &coordinationv1.Lease{ObjectMeta: objectMeta, Spec: spec} 65 default: 66 t.Fatal("unexpected objType:" + objectType) 67 } 68 return 69 } 70 71 // Will test leader election using endpoints as the resource 72 func TestTryAcquireOrRenewEndpoints(t *testing.T) { 73 testTryAcquireOrRenew(t, "endpoints") 74 } 75 76 type Reactor struct { 77 verb string 78 objectType string 79 reaction fakeclient.ReactionFunc 80 } 81 82 func testTryAcquireOrRenew(t *testing.T, objectType string) { 83 future := time.Now().Add(1000 * time.Hour) 84 past := time.Now().Add(-1000 * time.Hour) 85 86 tests := []struct { 87 name string 88 observedRecord rl.LeaderElectionRecord 89 observedTime time.Time 90 reactors []Reactor 91 92 key string 93 keyComparisonFunc KeyComparisonFunc 94 95 expectSuccess bool 96 transitionLeader bool 97 outHolder string 98 }{ 99 { 100 name: "acquire from no object", 101 reactors: []Reactor{ 102 { 103 verb: "get", 104 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 105 return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) 106 }, 107 }, 108 { 109 verb: "create", 110 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 111 return true, action.(fakeclient.CreateAction).GetObject(), nil 112 }, 113 }, 114 }, 115 expectSuccess: true, 116 outHolder: "baz", 117 }, 118 { 119 name: "acquire from object without annotations", 120 reactors: []Reactor{ 121 { 122 verb: "get", 123 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 124 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), nil), nil 125 }, 126 }, 127 { 128 verb: "update", 129 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 130 return true, action.(fakeclient.CreateAction).GetObject(), nil 131 }, 132 }, 133 }, 134 expectSuccess: true, 135 transitionLeader: true, 136 outHolder: "baz", 137 }, 138 { 139 name: "acquire from unled object", 140 reactors: []Reactor{ 141 { 142 verb: "get", 143 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 144 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil 145 }, 146 }, 147 { 148 verb: "update", 149 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 150 return true, action.(fakeclient.CreateAction).GetObject(), nil 151 }, 152 }, 153 }, 154 155 expectSuccess: true, 156 transitionLeader: true, 157 outHolder: "baz", 158 }, 159 { 160 name: "acquire from led, unacked object", 161 reactors: []Reactor{ 162 { 163 verb: "get", 164 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 165 // nolint: lll 166 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 167 }, 168 }, 169 { 170 verb: "update", 171 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 172 return true, action.(fakeclient.CreateAction).GetObject(), nil 173 }, 174 }, 175 }, 176 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"}, 177 observedTime: past, 178 179 expectSuccess: true, 180 transitionLeader: true, 181 outHolder: "baz", 182 }, 183 { 184 name: "acquire from empty led, acked object", 185 reactors: []Reactor{ 186 { 187 verb: "get", 188 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 189 // nolint: lll 190 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: ""}), nil 191 }, 192 }, 193 { 194 verb: "update", 195 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 196 return true, action.(fakeclient.CreateAction).GetObject(), nil 197 }, 198 }, 199 }, 200 observedTime: future, 201 202 expectSuccess: true, 203 transitionLeader: true, 204 outHolder: "baz", 205 }, 206 { 207 name: "don't acquire from led, acked object", 208 reactors: []Reactor{ 209 { 210 verb: "get", 211 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 212 // nolint: lll 213 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 214 }, 215 }, 216 }, 217 observedTime: future, 218 219 expectSuccess: false, 220 outHolder: "bing", 221 }, 222 { 223 name: "don't acquire from led, acked object with key", 224 reactors: []Reactor{ 225 { 226 verb: "get", 227 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 228 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), 229 &rl.LeaderElectionRecord{HolderIdentity: "bing", HolderKey: "a"}), nil 230 }, 231 }, 232 }, 233 observedTime: future, 234 235 expectSuccess: false, 236 outHolder: "bing", 237 }, 238 // Uncomment when https://github.com/kubernetes/kubernetes/pull/103442/files#r715818684 is resolved. 239 //{ 240 // name: "don't acquire from led, acked object with key when our key is smaller", 241 // reactors: []Reactor{ 242 // { 243 // verb: "get", 244 // reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 245 // return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), 246 // &rl.LeaderElectionRecord{HolderIdentity: "bing", HolderKey: "zzzz"}), nil 247 // }, 248 // }, 249 // }, 250 // observedTime: future, 251 // 252 // key: "aaa", 253 // keyComparisonFunc: func(existingKey string) bool { 254 // return "aaa" > existingKey 255 // }, 256 // 257 // expectSuccess: false, 258 // outHolder: "bing", 259 //}, 260 { 261 name: "steal from led object with key when our key is larger", 262 reactors: []Reactor{ 263 { 264 verb: "get", 265 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 266 // nolint: lll 267 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), 268 &rl.LeaderElectionRecord{HolderIdentity: "bing", HolderKey: "aaa"}), nil 269 }, 270 }, 271 { 272 verb: "update", 273 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 274 return true, action.(fakeclient.CreateAction).GetObject(), nil 275 }, 276 }, 277 }, 278 observedTime: future, 279 280 key: "zzz", 281 keyComparisonFunc: func(existingKey string) bool { 282 return "zzz" > existingKey 283 }, 284 285 transitionLeader: true, 286 expectSuccess: true, 287 outHolder: "baz", 288 }, 289 { 290 name: "handle led object with no key", 291 reactors: []Reactor{ 292 { 293 verb: "get", 294 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 295 // nolint: lll 296 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), 297 &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 298 }, 299 }, 300 }, 301 observedTime: future, 302 303 key: "zzz", 304 keyComparisonFunc: func(existingKey string) bool { 305 return existingKey != "" && "zzz" > existingKey 306 }, 307 308 expectSuccess: false, 309 outHolder: "bing", 310 }, 311 { 312 name: "renew already acquired object", 313 reactors: []Reactor{ 314 { 315 verb: "get", 316 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 317 // nolint: lll 318 return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil 319 }, 320 }, 321 { 322 verb: "update", 323 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 324 return true, action.(fakeclient.CreateAction).GetObject(), nil 325 }, 326 }, 327 }, 328 observedTime: future, 329 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "baz"}, 330 331 expectSuccess: true, 332 outHolder: "baz", 333 }, 334 } 335 336 for i := range tests { 337 test := &tests[i] 338 t.Run(test.name, func(t *testing.T) { 339 // OnNewLeader is called async so we have to wait for it. 340 var wg sync.WaitGroup 341 wg.Add(1) 342 var reportedLeader string 343 var lock rl.Interface 344 345 objectMeta := metav1.ObjectMeta{Namespace: "foo", Name: "bar"} 346 resourceLockConfig := rl.ResourceLockConfig{ 347 Identity: "baz", 348 Key: test.key, 349 EventRecorder: &record.FakeRecorder{}, 350 } 351 c := &fake.Clientset{} 352 for _, reactor := range test.reactors { 353 c.AddReactor(reactor.verb, objectType, reactor.reaction) 354 } 355 c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) { 356 t.Errorf("unreachable action. testclient called too many times: %+v", action) 357 return true, nil, fmt.Errorf("unreachable action") 358 }) 359 360 switch objectType { 361 case "endpoints": 362 lock = &rl.EndpointsLock{ 363 EndpointsMeta: objectMeta, 364 LockConfig: resourceLockConfig, 365 Client: c.CoreV1(), 366 } 367 case "configmaps": 368 lock = &rl.ConfigMapLock{ 369 ConfigMapMeta: objectMeta, 370 LockConfig: resourceLockConfig, 371 Client: c.CoreV1(), 372 } 373 case "leases": 374 lock = &rl.LeaseLock{ 375 LeaseMeta: objectMeta, 376 LockConfig: resourceLockConfig, 377 Client: c.CoordinationV1(), 378 } 379 } 380 381 lec := LeaderElectionConfig{ 382 Lock: lock, 383 KeyComparison: test.keyComparisonFunc, 384 LeaseDuration: 10 * time.Second, 385 Callbacks: LeaderCallbacks{ 386 OnNewLeader: func(l string) { 387 defer wg.Done() 388 reportedLeader = l 389 }, 390 }, 391 } 392 observedRawRecord := GetRawRecordOrDie(t, objectType, test.observedRecord) 393 le := &LeaderElector{ 394 config: lec, 395 observedRecord: test.observedRecord, 396 observedRawRecord: observedRawRecord, 397 observedTime: test.observedTime, 398 clock: clock.RealClock{}, 399 } 400 if test.expectSuccess != le.tryAcquireOrRenew(context.Background()) { 401 t.Errorf("unexpected result of tryAcquireOrRenew: [succeeded=%v]", !test.expectSuccess) 402 } 403 404 le.observedRecord.AcquireTime = metav1.Time{} 405 le.observedRecord.RenewTime = metav1.Time{} 406 if le.observedRecord.HolderIdentity != test.outHolder { 407 t.Errorf("expected holder:\n\t%+v\ngot:\n\t%+v", test.outHolder, le.observedRecord.HolderIdentity) 408 } 409 if len(test.reactors) != len(c.Actions()) { 410 t.Errorf("wrong number of api interactions") 411 } 412 if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 { 413 t.Errorf("leader should have transitioned but did not") 414 } 415 if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 { 416 t.Errorf("leader should not have transitioned but did") 417 } 418 419 le.maybeReportTransition() 420 wg.Wait() 421 if reportedLeader != test.outHolder { 422 t.Errorf("reported leader was not the new leader. expected %q, got %q", test.outHolder, reportedLeader) 423 } 424 }) 425 } 426 } 427 428 // Will test leader election using configmap as the resource 429 func TestTryAcquireOrRenewConfigMaps(t *testing.T) { 430 testTryAcquireOrRenew(t, "configmaps") 431 } 432 433 // Will test leader election using lease as the resource 434 func TestTryAcquireOrRenewLeases(t *testing.T) { 435 testTryAcquireOrRenew(t, "leases") 436 } 437 438 func TestLeaseSpecToLeaderElectionRecordRoundTrip(t *testing.T) { 439 holderIdentity := "foo" 440 leaseDurationSeconds := int32(10) 441 leaseTransitions := int32(1) 442 oldSpec := coordinationv1.LeaseSpec{ 443 HolderIdentity: &holderIdentity, 444 LeaseDurationSeconds: &leaseDurationSeconds, 445 AcquireTime: &metav1.MicroTime{Time: time.Now()}, 446 RenewTime: &metav1.MicroTime{Time: time.Now()}, 447 LeaseTransitions: &leaseTransitions, 448 } 449 450 oldRecord := rl.LeaseSpecToLeaderElectionRecord(&oldSpec) 451 newSpec := rl.LeaderElectionRecordToLeaseSpec(oldRecord) 452 453 if !equality.Semantic.DeepEqual(oldSpec, newSpec) { 454 t.Errorf("diff: %v", diff.ObjectReflectDiff(oldSpec, newSpec)) 455 } 456 457 newRecord := rl.LeaseSpecToLeaderElectionRecord(&newSpec) 458 459 if !equality.Semantic.DeepEqual(oldRecord, newRecord) { 460 t.Errorf("diff: %v", diff.ObjectReflectDiff(oldRecord, newRecord)) 461 } 462 } 463 464 func multiLockType(t *testing.T, objectType string) (primaryType, secondaryType string) { 465 switch objectType { 466 case rl.EndpointsLeasesResourceLock: 467 return rl.EndpointsResourceLock, rl.LeasesResourceLock 468 case rl.ConfigMapsLeasesResourceLock: 469 return rl.ConfigMapsResourceLock, rl.LeasesResourceLock 470 default: 471 t.Fatal("unexpected objType:" + objectType) 472 } 473 return 474 } 475 476 func GetRawRecordOrDie(t *testing.T, objectType string, ler rl.LeaderElectionRecord) (ret []byte) { 477 var err error 478 switch objectType { 479 case "endpoints", "configmaps", "leases": 480 ret, err = json.Marshal(ler) 481 if err != nil { 482 t.Fatalf("lock %s get raw record %v failed: %v", objectType, ler, err) 483 } 484 case "endpointsleases", "configmapsleases": 485 recordBytes, err := json.Marshal(ler) 486 if err != nil { 487 t.Fatalf("lock %s get raw record %v failed: %v", objectType, ler, err) 488 } 489 ret = rl.ConcatRawRecord(recordBytes, recordBytes) 490 default: 491 t.Fatal("unexpected objType:" + objectType) 492 } 493 return 494 } 495 496 func testTryAcquireOrRenewMultiLock(t *testing.T, objectType string) { 497 future := time.Now().Add(1000 * time.Hour) 498 past := time.Now().Add(-1000 * time.Hour) 499 primaryType, secondaryType := multiLockType(t, objectType) 500 tests := []struct { 501 name string 502 observedRecord rl.LeaderElectionRecord 503 observedRawRecord []byte 504 observedTime time.Time 505 reactors []Reactor 506 507 expectSuccess bool 508 transitionLeader bool 509 outHolder string 510 }{ 511 { 512 name: "acquire from no object", 513 reactors: []Reactor{ 514 { 515 verb: "get", 516 objectType: primaryType, 517 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 518 // nolint: lll 519 return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) 520 }, 521 }, 522 { 523 verb: "create", 524 objectType: primaryType, 525 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 526 return true, action.(fakeclient.CreateAction).GetObject(), nil 527 }, 528 }, 529 { 530 verb: "create", 531 objectType: secondaryType, 532 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 533 return true, action.(fakeclient.CreateAction).GetObject(), nil 534 }, 535 }, 536 }, 537 expectSuccess: true, 538 outHolder: "baz", 539 }, 540 { 541 name: "acquire from unled old object", 542 reactors: []Reactor{ 543 { 544 verb: "get", 545 objectType: primaryType, 546 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 547 // nolint: lll 548 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil 549 }, 550 }, 551 { 552 verb: "get", 553 objectType: secondaryType, 554 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 555 // nolint: lll 556 return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) 557 }, 558 }, 559 { 560 verb: "update", 561 objectType: primaryType, 562 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 563 return true, action.(fakeclient.UpdateAction).GetObject(), nil 564 }, 565 }, 566 { 567 verb: "get", 568 objectType: secondaryType, 569 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 570 // nolint: lll 571 return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) 572 }, 573 }, 574 { 575 verb: "create", 576 objectType: secondaryType, 577 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 578 return true, action.(fakeclient.CreateAction).GetObject(), nil 579 }, 580 }, 581 }, 582 expectSuccess: true, 583 transitionLeader: true, 584 outHolder: "baz", 585 }, 586 { 587 name: "acquire from unled transition object", 588 reactors: []Reactor{ 589 { 590 verb: "get", 591 objectType: primaryType, 592 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 593 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil 594 }, 595 }, 596 { 597 verb: "get", 598 objectType: secondaryType, 599 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 600 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil 601 }, 602 }, 603 { 604 verb: "update", 605 objectType: primaryType, 606 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 607 return true, action.(fakeclient.UpdateAction).GetObject(), nil 608 }, 609 }, 610 { 611 verb: "get", 612 objectType: secondaryType, 613 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 614 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil 615 }, 616 }, 617 { 618 verb: "update", 619 objectType: secondaryType, 620 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 621 return true, action.(fakeclient.UpdateAction).GetObject(), nil 622 }, 623 }, 624 }, 625 expectSuccess: true, 626 transitionLeader: true, 627 outHolder: "baz", 628 }, 629 { 630 name: "acquire from led, unack old object", 631 reactors: []Reactor{ 632 { 633 verb: "get", 634 objectType: primaryType, 635 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 636 // nolint: lll 637 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 638 }, 639 }, 640 { 641 verb: "get", 642 objectType: secondaryType, 643 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 644 // nolint: lll 645 return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) 646 }, 647 }, 648 { 649 verb: "update", 650 objectType: primaryType, 651 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 652 return true, action.(fakeclient.UpdateAction).GetObject(), nil 653 }, 654 }, 655 { 656 verb: "get", 657 objectType: secondaryType, 658 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 659 // nolint: lll 660 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 661 }, 662 }, 663 { 664 verb: "create", 665 objectType: secondaryType, 666 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 667 return true, action.(fakeclient.CreateAction).GetObject(), nil 668 }, 669 }, 670 }, 671 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"}, 672 observedRawRecord: GetRawRecordOrDie(t, primaryType, rl.LeaderElectionRecord{HolderIdentity: "bing"}), 673 observedTime: past, 674 675 expectSuccess: true, 676 transitionLeader: true, 677 outHolder: "baz", 678 }, 679 { 680 name: "acquire from led, unack transition object", 681 reactors: []Reactor{ 682 { 683 verb: "get", 684 objectType: primaryType, 685 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 686 // nolint: lll 687 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 688 }, 689 }, 690 { 691 verb: "get", 692 objectType: secondaryType, 693 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 694 // nolint: lll 695 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 696 }, 697 }, 698 { 699 verb: "update", 700 objectType: primaryType, 701 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 702 return true, action.(fakeclient.UpdateAction).GetObject(), nil 703 }, 704 }, 705 { 706 verb: "get", 707 objectType: secondaryType, 708 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 709 // nolint: lll 710 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 711 }, 712 }, 713 { 714 verb: "update", 715 objectType: secondaryType, 716 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 717 return true, action.(fakeclient.UpdateAction).GetObject(), nil 718 }, 719 }, 720 }, 721 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"}, 722 observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: "bing"}), 723 observedTime: past, 724 725 expectSuccess: true, 726 transitionLeader: true, 727 outHolder: "baz", 728 }, 729 { 730 name: "acquire from conflict led, ack transition object", 731 reactors: []Reactor{ 732 { 733 verb: "get", 734 objectType: primaryType, 735 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 736 // nolint: lll 737 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 738 }, 739 }, 740 { 741 verb: "get", 742 objectType: secondaryType, 743 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 744 // nolint: lll 745 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil 746 }, 747 }, 748 }, 749 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"}, 750 observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: "bing"}), 751 observedTime: future, 752 753 expectSuccess: false, 754 outHolder: rl.UnknownLeader, 755 }, 756 { 757 name: "acquire from led, unack unknown object", 758 reactors: []Reactor{ 759 { 760 verb: "get", 761 objectType: primaryType, 762 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 763 // nolint: lll 764 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}), nil 765 }, 766 }, 767 { 768 verb: "get", 769 objectType: secondaryType, 770 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 771 // nolint: lll 772 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}), nil 773 }, 774 }, 775 { 776 verb: "update", 777 objectType: primaryType, 778 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 779 return true, action.(fakeclient.UpdateAction).GetObject(), nil 780 }, 781 }, 782 { 783 verb: "get", 784 objectType: secondaryType, 785 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 786 // nolint: lll 787 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}), nil 788 }, 789 }, 790 { 791 verb: "update", 792 objectType: secondaryType, 793 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 794 return true, action.(fakeclient.UpdateAction).GetObject(), nil 795 }, 796 }, 797 }, 798 observedRecord: rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}, 799 observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}), 800 observedTime: past, 801 802 expectSuccess: true, 803 transitionLeader: true, 804 outHolder: "baz", 805 }, 806 { 807 name: "don't acquire from led, ack old object", 808 reactors: []Reactor{ 809 { 810 verb: "get", 811 objectType: primaryType, 812 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 813 // nolint: lll 814 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 815 }, 816 }, 817 { 818 verb: "get", 819 objectType: secondaryType, 820 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 821 return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) 822 }, 823 }, 824 }, 825 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"}, 826 observedRawRecord: GetRawRecordOrDie(t, primaryType, rl.LeaderElectionRecord{HolderIdentity: "bing"}), 827 observedTime: future, 828 829 expectSuccess: false, 830 outHolder: "bing", 831 }, 832 { 833 name: "don't acquire from led, acked new object, observe new record", 834 reactors: []Reactor{ 835 { 836 verb: "get", 837 objectType: primaryType, 838 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 839 // nolint: lll 840 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil 841 }, 842 }, 843 { 844 verb: "get", 845 objectType: secondaryType, 846 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 847 // nolint: lll 848 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 849 }, 850 }, 851 }, 852 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"}, 853 observedRawRecord: GetRawRecordOrDie(t, secondaryType, rl.LeaderElectionRecord{HolderIdentity: "bing"}), 854 observedTime: future, 855 856 expectSuccess: false, 857 outHolder: rl.UnknownLeader, 858 }, 859 { 860 name: "don't acquire from led, acked new object, observe transition record", 861 reactors: []Reactor{ 862 { 863 verb: "get", 864 objectType: primaryType, 865 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 866 // nolint: lll 867 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 868 }, 869 }, 870 { 871 verb: "get", 872 objectType: secondaryType, 873 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 874 // nolint: lll 875 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil 876 }, 877 }, 878 }, 879 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"}, 880 observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: "bing"}), 881 observedTime: future, 882 883 expectSuccess: false, 884 outHolder: "bing", 885 }, 886 { 887 name: "renew already required object", 888 reactors: []Reactor{ 889 { 890 verb: "get", 891 objectType: primaryType, 892 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 893 // nolint: lll 894 return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil 895 }, 896 }, 897 { 898 verb: "get", 899 objectType: secondaryType, 900 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 901 // nolint: lll 902 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil 903 }, 904 }, 905 { 906 verb: "update", 907 objectType: primaryType, 908 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 909 return true, action.(fakeclient.UpdateAction).GetObject(), nil 910 }, 911 }, 912 { 913 verb: "get", 914 objectType: secondaryType, 915 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 916 // nolint: lll 917 return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil 918 }, 919 }, 920 { 921 verb: "update", 922 objectType: secondaryType, 923 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 924 return true, action.(fakeclient.UpdateAction).GetObject(), nil 925 }, 926 }, 927 }, 928 observedRecord: rl.LeaderElectionRecord{HolderIdentity: "baz"}, 929 observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: "baz"}), 930 observedTime: future, 931 932 expectSuccess: true, 933 outHolder: "baz", 934 }, 935 } 936 937 for i := range tests { 938 test := &tests[i] 939 t.Run(test.name, func(t *testing.T) { 940 // OnNewLeader is called async so we have to wait for it. 941 var wg sync.WaitGroup 942 wg.Add(1) 943 var reportedLeader string 944 var lock rl.Interface 945 946 objectMeta := metav1.ObjectMeta{Namespace: "foo", Name: "bar"} 947 resourceLockConfig := rl.ResourceLockConfig{ 948 Identity: "baz", 949 EventRecorder: &record.FakeRecorder{}, 950 } 951 c := &fake.Clientset{} 952 for _, reactor := range test.reactors { 953 c.AddReactor(reactor.verb, reactor.objectType, reactor.reaction) 954 } 955 c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) { 956 t.Errorf("unreachable action. testclient called too many times: %+v", action) 957 return true, nil, fmt.Errorf("unreachable action") 958 }) 959 960 switch objectType { 961 case rl.EndpointsLeasesResourceLock: 962 lock = &rl.MultiLock{ 963 Primary: &rl.EndpointsLock{ 964 EndpointsMeta: objectMeta, 965 LockConfig: resourceLockConfig, 966 Client: c.CoreV1(), 967 }, 968 Secondary: &rl.LeaseLock{ 969 LeaseMeta: objectMeta, 970 LockConfig: resourceLockConfig, 971 Client: c.CoordinationV1(), 972 }, 973 } 974 case rl.ConfigMapsLeasesResourceLock: 975 lock = &rl.MultiLock{ 976 Primary: &rl.ConfigMapLock{ 977 ConfigMapMeta: objectMeta, 978 LockConfig: resourceLockConfig, 979 Client: c.CoreV1(), 980 }, 981 Secondary: &rl.LeaseLock{ 982 LeaseMeta: objectMeta, 983 LockConfig: resourceLockConfig, 984 Client: c.CoordinationV1(), 985 }, 986 } 987 } 988 989 lec := LeaderElectionConfig{ 990 Lock: lock, 991 LeaseDuration: 10 * time.Second, 992 Callbacks: LeaderCallbacks{ 993 OnNewLeader: func(l string) { 994 defer wg.Done() 995 reportedLeader = l 996 }, 997 }, 998 } 999 le := &LeaderElector{ 1000 config: lec, 1001 observedRecord: test.observedRecord, 1002 observedRawRecord: test.observedRawRecord, 1003 observedTime: test.observedTime, 1004 clock: clock.RealClock{}, 1005 } 1006 if test.expectSuccess != le.tryAcquireOrRenew(context.Background()) { 1007 t.Errorf("unexpected result of tryAcquireOrRenew: [succeeded=%v]", !test.expectSuccess) 1008 } 1009 1010 le.observedRecord.AcquireTime = metav1.Time{} 1011 le.observedRecord.RenewTime = metav1.Time{} 1012 if le.observedRecord.HolderIdentity != test.outHolder { 1013 t.Errorf("expected holder:\n\t%+v\ngot:\n\t%+v", test.outHolder, le.observedRecord.HolderIdentity) 1014 } 1015 if len(test.reactors) != len(c.Actions()) { 1016 t.Errorf("wrong number of api interactions") 1017 } 1018 if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 { 1019 t.Errorf("leader should have transitioned but did not") 1020 } 1021 if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 { 1022 t.Errorf("leader should not have transitioned but did") 1023 } 1024 1025 le.maybeReportTransition() 1026 wg.Wait() 1027 if reportedLeader != test.outHolder { 1028 t.Errorf("reported leader was not the new leader. expected %q, got %q", test.outHolder, reportedLeader) 1029 } 1030 }) 1031 } 1032 } 1033 1034 // Will test leader election using endpointsleases as the resource 1035 func TestTryAcquireOrRenewEndpointsLeases(t *testing.T) { 1036 testTryAcquireOrRenewMultiLock(t, "endpointsleases") 1037 } 1038 1039 // Will test leader election using configmapsleases as the resource 1040 func TestTryAcquireOrRenewConfigMapsLeases(t *testing.T) { 1041 testTryAcquireOrRenewMultiLock(t, "configmapsleases") 1042 } 1043 1044 func testReleaseLease(t *testing.T, objectType string) { 1045 tests := []struct { 1046 name string 1047 observedRecord rl.LeaderElectionRecord 1048 observedTime time.Time 1049 reactors []Reactor 1050 1051 expectSuccess bool 1052 transitionLeader bool 1053 outHolder string 1054 }{ 1055 { 1056 name: "release acquired lock from no object", 1057 reactors: []Reactor{ 1058 { 1059 verb: "get", 1060 objectType: objectType, 1061 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 1062 // nolint: lll 1063 return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) 1064 }, 1065 }, 1066 { 1067 verb: "create", 1068 objectType: objectType, 1069 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 1070 return true, action.(fakeclient.CreateAction).GetObject(), nil 1071 }, 1072 }, 1073 { 1074 verb: "update", 1075 objectType: objectType, 1076 reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 1077 return true, action.(fakeclient.UpdateAction).GetObject(), nil 1078 }, 1079 }, 1080 }, 1081 expectSuccess: true, 1082 outHolder: "", 1083 }, 1084 } 1085 1086 for i := range tests { 1087 test := &tests[i] 1088 t.Run(test.name, func(t *testing.T) { 1089 // OnNewLeader is called async so we have to wait for it. 1090 var wg sync.WaitGroup 1091 wg.Add(1) 1092 var reportedLeader string 1093 var lock rl.Interface 1094 1095 objectMeta := metav1.ObjectMeta{Namespace: "foo", Name: "bar"} 1096 resourceLockConfig := rl.ResourceLockConfig{ 1097 Identity: "baz", 1098 EventRecorder: &record.FakeRecorder{}, 1099 } 1100 c := &fake.Clientset{} 1101 for _, reactor := range test.reactors { 1102 c.AddReactor(reactor.verb, objectType, reactor.reaction) 1103 } 1104 c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) { 1105 t.Errorf("unreachable action. testclient called too many times: %+v", action) 1106 return true, nil, fmt.Errorf("unreachable action") 1107 }) 1108 1109 switch objectType { 1110 case "endpoints": 1111 lock = &rl.EndpointsLock{ 1112 EndpointsMeta: objectMeta, 1113 LockConfig: resourceLockConfig, 1114 Client: c.CoreV1(), 1115 } 1116 case "configmaps": 1117 lock = &rl.ConfigMapLock{ 1118 ConfigMapMeta: objectMeta, 1119 LockConfig: resourceLockConfig, 1120 Client: c.CoreV1(), 1121 } 1122 case "leases": 1123 lock = &rl.LeaseLock{ 1124 LeaseMeta: objectMeta, 1125 LockConfig: resourceLockConfig, 1126 Client: c.CoordinationV1(), 1127 } 1128 } 1129 1130 lec := LeaderElectionConfig{ 1131 Lock: lock, 1132 LeaseDuration: 10 * time.Second, 1133 Callbacks: LeaderCallbacks{ 1134 OnNewLeader: func(l string) { 1135 defer wg.Done() 1136 reportedLeader = l 1137 }, 1138 }, 1139 } 1140 observedRawRecord := GetRawRecordOrDie(t, objectType, test.observedRecord) 1141 le := &LeaderElector{ 1142 config: lec, 1143 observedRecord: test.observedRecord, 1144 observedRawRecord: observedRawRecord, 1145 observedTime: test.observedTime, 1146 clock: clock.RealClock{}, 1147 } 1148 if !le.tryAcquireOrRenew(context.Background()) { 1149 t.Errorf("unexpected result of tryAcquireOrRenew: [succeeded=%v]", true) 1150 } 1151 1152 le.maybeReportTransition() 1153 1154 // Wait for a response to the leader transition, and add 1 so that we can track the final transition. 1155 wg.Wait() 1156 wg.Add(1) 1157 1158 if test.expectSuccess != le.release() { 1159 t.Errorf("unexpected result of release: [succeeded=%v]", !test.expectSuccess) 1160 } 1161 1162 le.observedRecord.AcquireTime = metav1.Time{} 1163 le.observedRecord.RenewTime = metav1.Time{} 1164 if le.observedRecord.HolderIdentity != test.outHolder { 1165 t.Errorf("expected holder:\n\t%+v\ngot:\n\t%+v", test.outHolder, le.observedRecord.HolderIdentity) 1166 } 1167 if len(test.reactors) != len(c.Actions()) { 1168 t.Errorf("wrong number of api interactions") 1169 } 1170 if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 { 1171 t.Errorf("leader should have transitioned but did not") 1172 } 1173 if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 { 1174 t.Errorf("leader should not have transitioned but did") 1175 } 1176 le.maybeReportTransition() 1177 wg.Wait() 1178 if reportedLeader != test.outHolder { 1179 t.Errorf("reported leader was not the new leader. expected %q, got %q", test.outHolder, reportedLeader) 1180 } 1181 }) 1182 } 1183 } 1184 1185 // Will test leader election using endpoints as the resource 1186 func TestReleaseLeaseEndpoints(t *testing.T) { 1187 testReleaseLease(t, "endpoints") 1188 } 1189 1190 // Will test leader election using endpoints as the resource 1191 func TestReleaseLeaseConfigMaps(t *testing.T) { 1192 testReleaseLease(t, "configmaps") 1193 } 1194 1195 // Will test leader election using endpoints as the resource 1196 func TestReleaseLeaseLeases(t *testing.T) { 1197 testReleaseLease(t, "leases") 1198 } 1199 1200 func TestReleaseOnCancellation_Endpoints(t *testing.T) { 1201 testReleaseOnCancellation(t, "endpoints") 1202 } 1203 1204 func TestReleaseOnCancellation_ConfigMaps(t *testing.T) { 1205 testReleaseOnCancellation(t, "configmaps") 1206 } 1207 1208 func TestReleaseOnCancellation_Leases(t *testing.T) { 1209 testReleaseOnCancellation(t, "leases") 1210 } 1211 1212 func testReleaseOnCancellation(t *testing.T, objectType string) { 1213 var ( 1214 onNewLeader = make(chan struct{}) 1215 onRenewCalled = make(chan struct{}) 1216 onRenewResume = make(chan struct{}) 1217 onRelease = make(chan struct{}) 1218 1219 lockObj runtime.Object 1220 updates int 1221 ) 1222 1223 resourceLockConfig := rl.ResourceLockConfig{ 1224 Identity: "baz", 1225 EventRecorder: &record.FakeRecorder{}, 1226 } 1227 c := &fake.Clientset{} 1228 1229 c.AddReactor("get", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 1230 if lockObj != nil { 1231 return true, lockObj, nil 1232 } 1233 return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName()) 1234 }) 1235 1236 // create lock 1237 c.AddReactor("create", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 1238 lockObj = action.(fakeclient.CreateAction).GetObject() 1239 return true, lockObj, nil 1240 }) 1241 1242 c.AddReactor("update", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) { 1243 updates++ 1244 1245 // Second update (first renew) should return our canceled error 1246 // FakeClient doesn't do anything with the context so we're doing this ourselves 1247 if updates == 2 { 1248 close(onRenewCalled) 1249 <-onRenewResume 1250 return true, nil, context.Canceled 1251 } else if updates == 3 { 1252 close(onRelease) 1253 } 1254 1255 lockObj = action.(fakeclient.UpdateAction).GetObject() 1256 return true, lockObj, nil 1257 }) 1258 1259 c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) { 1260 t.Errorf("unreachable action. testclient called too many times: %+v", action) 1261 return true, nil, fmt.Errorf("unreachable action") 1262 }) 1263 1264 lock, err := rl.New(objectType, "foo", "bar", c.CoreV1(), c.CoordinationV1(), resourceLockConfig) 1265 if err != nil { 1266 t.Fatal("resourcelock.New() = ", err) 1267 } 1268 1269 lec := LeaderElectionConfig{ 1270 Lock: lock, 1271 LeaseDuration: 40 * time.Millisecond, 1272 RenewDeadline: 20 * time.Millisecond, 1273 RetryPeriod: 10 * time.Millisecond, 1274 1275 // This is what we're testing 1276 ReleaseOnCancel: true, 1277 1278 Callbacks: LeaderCallbacks{ 1279 OnNewLeader: func(identity string) {}, 1280 OnStoppedLeading: func() {}, 1281 OnStartedLeading: func(context.Context) { 1282 close(onNewLeader) 1283 }, 1284 }, 1285 } 1286 1287 elector, err := NewLeaderElector(lec) 1288 if err != nil { 1289 t.Fatal("Failed to create leader elector: ", err) 1290 } 1291 1292 ctx, cancel := context.WithCancel(context.Background()) 1293 1294 go elector.Run(ctx) 1295 1296 // Wait for us to become the leader 1297 select { 1298 case <-onNewLeader: 1299 case <-time.After(1 * time.Second): 1300 t.Fatal("failed to become the leader") 1301 } 1302 1303 // Wait for renew (update) to be invoked 1304 select { 1305 case <-onRenewCalled: 1306 case <-time.After(1 * time.Second): 1307 t.Fatal("the elector failed to renew the lock") 1308 } 1309 1310 // Cancel the context - stopping the elector while 1311 // it's running 1312 cancel() 1313 1314 // Resume the update call to return the cancellation 1315 // which should trigger the release flow 1316 close(onRenewResume) 1317 1318 select { 1319 case <-onRelease: 1320 case <-time.After(1 * time.Second): 1321 t.Fatal("the lock was not released") 1322 } 1323 }