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