Files
client-go/tools/leaderelection/resourcelock/leaselock_test.go
DerekFrank 048fbed845 gofmt and review feedback
Kubernetes-commit: 2180b441dd748bcaf9c1c8a28d20f6565e14f189
2025-08-18 09:52:15 -07:00

378 lines
12 KiB
Go

/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourcelock
import (
"context"
"encoding/json"
"fmt"
"testing"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
)
// mockRecorder implements record.EventRecorder for testing
type mockRecorder struct {
events []string
}
func (r *mockRecorder) Event(object runtime.Object, eventtype, reason, message string) {
r.events = append(r.events, message)
}
func (r *mockRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
r.events = append(r.events, fmt.Sprintf(messageFmt, args...))
}
func (r *mockRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
r.events = append(r.events, fmt.Sprintf(messageFmt, args...))
}
const (
testNamespace = "test-namespace"
testName = "test-name"
testIdentity = "test-identity"
)
var (
testTime = time.Now()
client *fake.Clientset
leaseLock *LeaseLock
recorder *mockRecorder
testRecord LeaderElectionRecord
)
// setup initializes the test variables before each test
func setup() {
client = fake.NewClientset()
recorder = &mockRecorder{}
leaseLock = &LeaseLock{
LeaseMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: testName,
},
Client: client.CoordinationV1(),
LockConfig: ResourceLockConfig{
Identity: testIdentity,
},
}
testRecord = LeaderElectionRecord{
HolderIdentity: testIdentity,
LeaseDurationSeconds: 15,
AcquireTime: metav1.Time{Time: testTime},
RenewTime: metav1.Time{Time: testTime},
LeaderTransitions: 1,
PreferredHolder: "preferred-holder",
Strategy: "strategy",
}
}
func TestGetError(t *testing.T) {
setup()
// Test Get on non-existent lease
_, _, err := leaseLock.Get(context.Background())
if err == nil {
t.Error("Expected error getting non-existent lease, got nil")
}
if !apierrors.IsNotFound(err) {
t.Errorf("Expected NotFound error, got %v", err)
}
}
func TestGet(t *testing.T) {
setup()
// Create a lease first
if err := leaseLock.Create(context.Background(), testRecord); err != nil {
t.Fatalf("Failed to create lease: %v", err)
}
// Test Get
record, recordBytes, err := leaseLock.Get(context.Background())
if err != nil {
t.Fatalf("Failed to get lease: %v", err)
}
// Verify record matches what we created
if record.HolderIdentity != testRecord.HolderIdentity {
t.Errorf("HolderIdentity mismatch, got %q want %q", record.HolderIdentity, testRecord.HolderIdentity)
}
if record.LeaseDurationSeconds != testRecord.LeaseDurationSeconds {
t.Errorf("LeaseDurationSeconds mismatch, got %d want %d", record.LeaseDurationSeconds, testRecord.LeaseDurationSeconds)
}
if record.LeaderTransitions != testRecord.LeaderTransitions {
t.Errorf("LeaderTransitions mismatch, got %d want %d", record.LeaderTransitions, testRecord.LeaderTransitions)
}
if record.PreferredHolder != testRecord.PreferredHolder {
t.Errorf("PreferredHolder mismatch, got %q want %q", record.PreferredHolder, testRecord.PreferredHolder)
}
if record.Strategy != testRecord.Strategy {
t.Errorf("Strategy mismatch, got %q want %q", record.Strategy, testRecord.Strategy)
}
// Verify we got valid JSON bytes
var unmarshalled LeaderElectionRecord
if err := json.Unmarshal(recordBytes, &unmarshalled); err != nil {
t.Errorf("Failed to unmarshal record bytes: %v", err)
}
}
func TestRecordEventSuccess(t *testing.T) {
setup()
leaseLock.LockConfig.EventRecorder = recorder
// Create a lease first
if err := leaseLock.Create(context.Background(), testRecord); err != nil {
t.Fatalf("Failed to create lease: %v", err)
}
// Test recording an event
leaseLock.RecordEvent("test event")
// Verify event was recorded
if len(recorder.events) != 1 {
t.Errorf("Expected 1 event, got %d", len(recorder.events))
}
expectedEvent := fmt.Sprintf("%v %v", testIdentity, "test event")
if recorder.events[0] != expectedEvent {
t.Errorf("Event mismatch, got %q want %q", recorder.events[0], expectedEvent)
}
}
func TestRecordEventNilRecorder(t *testing.T) {
setup()
// Create a lease first
if err := leaseLock.Create(context.Background(), testRecord); err != nil {
t.Fatalf("Failed to create lease: %v", err)
}
// Verify nil recorder doesn't panic
leaseLock.LockConfig.EventRecorder = nil
leaseLock.RecordEvent("should not panic")
}
func TestCreateError(t *testing.T) {
setup()
// Create initial lease
if err := leaseLock.Create(context.Background(), testRecord); err != nil {
t.Fatalf("Failed to create lease: %v", err)
}
// Attempt to create same lease again
err := leaseLock.Create(context.Background(), testRecord)
if err == nil {
t.Error("Expected error creating duplicate lease, got nil")
}
if !apierrors.IsAlreadyExists(err) {
t.Errorf("Expected AlreadyExists error, got %v", err)
}
}
func TestUpdateError(t *testing.T) {
setup()
// Attempt to update without initializing lease
err := leaseLock.Update(context.Background(), testRecord)
if err == nil {
t.Error("Expected error updating uninitialized lease, got nil")
}
}
func TestDescribe(t *testing.T) {
setup()
expected := fmt.Sprintf("%v/%v", testNamespace, testName)
if got := leaseLock.Describe(); got != expected {
t.Errorf("Describe() = %q, want %q", got, expected)
}
}
func TestLeaseConversion(t *testing.T) {
setup()
// Convert LeaderElectionRecord to LeaseSpec
leaseSpec := LeaderElectionRecordToLeaseSpec(&testRecord)
// Verify all fields were converted correctly
if *leaseSpec.HolderIdentity != testRecord.HolderIdentity {
t.Errorf("HolderIdentity mismatch, got %q want %q", *leaseSpec.HolderIdentity, testRecord.HolderIdentity)
}
if *leaseSpec.LeaseDurationSeconds != int32(testRecord.LeaseDurationSeconds) {
t.Errorf("LeaseDurationSeconds mismatch, got %d want %d", *leaseSpec.LeaseDurationSeconds, testRecord.LeaseDurationSeconds)
}
if leaseSpec.AcquireTime.Time != testRecord.AcquireTime.Time {
t.Errorf("AcquireTime mismatch, got %v want %v", leaseSpec.AcquireTime.Time, testRecord.AcquireTime.Time)
}
if leaseSpec.RenewTime.Time != testRecord.RenewTime.Time {
t.Errorf("RenewTime mismatch, got %v want %v", leaseSpec.RenewTime.Time, testRecord.RenewTime.Time)
}
if *leaseSpec.LeaseTransitions != int32(testRecord.LeaderTransitions) {
t.Errorf("LeaseTransitions mismatch, got %d want %d", *leaseSpec.LeaseTransitions, testRecord.LeaderTransitions)
}
if *leaseSpec.PreferredHolder != testRecord.PreferredHolder {
t.Errorf("PreferredHolder mismatch, got %q want %q", *leaseSpec.PreferredHolder, testRecord.PreferredHolder)
}
if *leaseSpec.Strategy != testRecord.Strategy {
t.Errorf("Strategy mismatch, got %q want %q", *leaseSpec.Strategy, testRecord.Strategy)
}
// Convert back to LeaderElectionRecord
convertedRecord := LeaseSpecToLeaderElectionRecord(&leaseSpec)
// Verify round-trip conversion preserved all fields
if convertedRecord.HolderIdentity != testRecord.HolderIdentity {
t.Errorf("HolderIdentity lost in conversion, got %q want %q", convertedRecord.HolderIdentity, testRecord.HolderIdentity)
}
if convertedRecord.LeaseDurationSeconds != testRecord.LeaseDurationSeconds {
t.Errorf("LeaseDurationSeconds lost in conversion, got %d want %d", convertedRecord.LeaseDurationSeconds, testRecord.LeaseDurationSeconds)
}
if !convertedRecord.AcquireTime.Equal(&testRecord.AcquireTime) {
t.Errorf("AcquireTime lost in conversion, got %v want %v", convertedRecord.AcquireTime, testRecord.AcquireTime)
}
if !convertedRecord.RenewTime.Equal(&testRecord.RenewTime) {
t.Errorf("RenewTime lost in conversion, got %v want %v", convertedRecord.RenewTime, testRecord.RenewTime)
}
if convertedRecord.LeaderTransitions != testRecord.LeaderTransitions {
t.Errorf("LeaderTransitions lost in conversion, got %d want %d", convertedRecord.LeaderTransitions, testRecord.LeaderTransitions)
}
if convertedRecord.PreferredHolder != testRecord.PreferredHolder {
t.Errorf("PreferredHolder lost in conversion, got %q want %q", convertedRecord.PreferredHolder, testRecord.PreferredHolder)
}
if convertedRecord.Strategy != testRecord.Strategy {
t.Errorf("Strategy lost in conversion, got %q want %q", convertedRecord.Strategy, testRecord.Strategy)
}
}
func TestUpdateWithNilLabelsOnLease(t *testing.T) {
setup()
// Create initial lease
if err := leaseLock.Create(context.Background(), testRecord); err != nil {
t.Fatalf("Failed to create lease: %v", err)
}
// Get the lease to initialize leaseLock.lease
if _, _, err := leaseLock.Get(context.Background()); err != nil {
t.Fatalf("Failed to get lease: %v", err)
}
leaseLock.lease.Labels = nil
leaseLock.Labels = map[string]string{"custom-key": "custom-val"}
// Update should succeed even with nil Labels on the lease itself
if err := leaseLock.Update(context.Background(), testRecord); err != nil {
t.Errorf("Update failed with nil Labels: %v", err)
}
}
func TestUpdateWithNilLabelsOnLeaseLock(t *testing.T) {
setup()
// Create initial lease
if err := leaseLock.Create(context.Background(), testRecord); err != nil {
t.Fatalf("Failed to create lease: %v", err)
}
// Get the lease to initialize leaseLock.lease
if _, _, err := leaseLock.Get(context.Background()); err != nil {
t.Fatalf("Failed to get lease: %v", err)
}
leaseLock.Labels = nil
leaseLock.lease.Labels = map[string]string{"custom-key": "custom-val"}
// Update should succeed even with nil Labels on the leaselock
if err := leaseLock.Update(context.Background(), testRecord); err != nil {
t.Errorf("Update failed with nil Labels: %v", err)
}
}
func TestLabelUpdate(t *testing.T) {
setup()
labels := map[string]string{"custom-key": "custom-val"}
leaseLock.Labels = labels
// Create initial lease
if err := leaseLock.Create(context.Background(), testRecord); err != nil {
t.Fatalf("Failed to create lease: %v", err)
}
// Update label on actual lease with new value
leaseLock.lease.Labels["custom-key"] = "new-custom-val"
// Update extra label on lease
leaseLock.lease.Labels["custom-key-2"] = "custom-val-2"
// Update labels
lease, err := leaseLock.Client.Leases(testNamespace).Update(context.Background(), leaseLock.lease, metav1.UpdateOptions{})
if err != nil {
t.Fatalf("Failed to update lease labels: %v", err)
}
// verify the labels were updated
val, exists := lease.Labels["custom-key"]
if !exists {
t.Error("Label was not set on the lease")
}
if val != "new-custom-val" {
t.Errorf("Label value mismatch, got %q want %q", val, "custom-val")
}
val, exists = lease.Labels["custom-key-2"]
if !exists {
t.Error("Label was not set on the lease")
}
if val != "custom-val-2" {
t.Errorf("Label value mismatch, got %q want %q", val, "custom-val")
}
// Update the lease
if err := leaseLock.Update(context.Background(), testRecord); err != nil {
t.Fatalf("Failed to update lease: %v", err)
}
// Verify labels are preserved
lease, err = leaseLock.Client.Leases(testNamespace).Get(context.Background(), testName, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed to update lease labels: %v", err)
}
val, exists = lease.Labels["custom-key"]
if !exists {
t.Error("Label was not set on the lease")
}
if val != "custom-val" {
t.Errorf("Label value mismatch, got %q want %q", val, "custom-val")
}
val, exists = lease.Labels["custom-key-2"]
if !exists {
t.Error("Label was not set on the lease")
}
if val != "custom-val-2" {
t.Errorf("Label value mismatch, got %q want %q", val, "custom-val-2")
}
}