Ctrl K
ComunidadGuías…

pkg/health/readiness_gates_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package health

import (
	"encoding/json"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"os"
	"testing"
	"time"
)

func TestPodReadinessGateFailure(t *testing.T) {
	now := time.Date(2026, 10, 25, 12, 0, 0, 0, time.UTC)
	for _, tc := range []struct {
		name, gate, reason, phase                                               string
		readyAge, containersAge, creationAge                                    time.Duration
		unknownTimes, noGates, notContainersReady, want, unknownOnset, deleting bool
	}{
		{name: "missing gate", want: true}, {name: "false gate", gate: "False", want: true}, {name: "passed gate", gate: "True"}, {name: "unknown gate", gate: "Unknown", want: true},
		{name: "terminating", deleting: true}, {name: "no declaration", noGates: true}, {name: "other ready failure", reason: "NodeNotReady"}, {name: "containers unready", notContainersReady: true},
		{name: "recent ready", readyAge: time.Minute}, {name: "long startup recently container ready", containersAge: time.Minute}, {name: "future containers ready", containersAge: -10 * time.Minute, want: true, unknownOnset: true},
		{name: "future ready", readyAge: -10 * time.Minute, want: true, unknownOnset: true},
		{name: "fresh transition with invalid other", readyAge: -10 * time.Minute, containersAge: time.Minute},
		{name: "unknown transitions future creation", unknownTimes: true, creationAge: -10 * time.Minute, want: true, unknownOnset: true},
		{name: "unknown transitions old creation", unknownTimes: true, want: true, unknownOnset: true},
		{name: "unknown transitions young creation", unknownTimes: true, creationAge: time.Minute},
		{name: "succeeded", phase: "Succeeded"}, {name: "pending", phase: "Pending"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			readyAge, containersAge, creationAge := tc.readyAge, tc.containersAge, tc.creationAge
			if readyAge == 0 {
				readyAge = 10 * time.Minute
			}
			if containersAge == 0 {
				containersAge = 7 * time.Minute
			}
			if creationAge == 0 {
				creationAge = time.Hour
			}
			reason := tc.reason
			if reason == "" {
				reason = "ReadinessGatesNotReady"
			}
			phase := tc.phase
			if phase == "" {
				phase = "Running"
			}
			p := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.NewTime(now.Add(-creationAge))}, Spec: corev1.PodSpec{ReadinessGates: []corev1.PodReadinessGate{{ConditionType: "example.com/ready"}}}, Status: corev1.PodStatus{Phase: corev1.PodPhase(phase), Conditions: []corev1.PodCondition{
				{Type: corev1.PodReady, Status: corev1.ConditionFalse, Reason: reason, Message: "gate has not reported ready", LastTransitionTime: metav1.NewTime(now.Add(-readyAge))},
				{Type: corev1.ContainersReady, Status: corev1.ConditionTrue, LastTransitionTime: metav1.NewTime(now.Add(-containersAge))},
			}, ContainerStatuses: []corev1.ContainerStatus{{Name: "app", Ready: true, State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}}}}}
			if tc.gate != "" {
				p.Status.Conditions = append(p.Status.Conditions, corev1.PodCondition{Type: "example.com/ready", Status: corev1.ConditionStatus(tc.gate)})
			}
			if tc.deleting {
				t := metav1.NewTime(now.Add(-time.Minute))
				p.DeletionTimestamp = &t
			}

			if tc.noGates {
				p.Spec.ReadinessGates = nil
			}
			if tc.notContainersReady {
				p.Status.Conditions[1].Status = corev1.ConditionFalse
			}
			if tc.unknownTimes {
				for i := range p.Status.Conditions {
					p.Status.Conditions[i].LastTransitionTime = metav1.Time{}
				}
			}
			since, got := PodReadinessGateFailure(p, now)
			if got != tc.want {
				t.Fatalf("failure=%v want%v", got, tc.want)
			}
			if tc.deleting {
				p.Status.ContainerStatuses[0].State = corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{Reason: "ImagePullBackOff"}}
				if v := Pod(p, now); v.Level != LevelUnhealthy || v.Reason != "ImagePullBackOff" {
					t.Fatalf("deletion masked hard failure: %+v", v)
				}
			}
			if tc.want {
				wantSince := now.Add(-7 * time.Minute)
				if tc.unknownOnset {
					wantSince = time.Time{}
				}
				if !since.Equal(wantSince) {
					t.Errorf("onset=%v want%v", since, wantSince)
				}
				v := Pod(p, now)
				if v.Level != LevelDegraded || v.Reason != "ReadinessGatesNotReady" || v.Message != "gate has not reported ready" {
					t.Errorf("verdict %+v", v)
				}

				if tc.name == "missing gate" {
					cs := &p.Status.ContainerStatuses[0]
					cs.RestartCount = 1
					cs.State = corev1.ContainerState{Running: &corev1.ContainerStateRunning{StartedAt: metav1.NewTime(now.Add(-time.Minute))}}
					cs.LastTerminationState = corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{Reason: "OOMKilled", ExitCode: 137, FinishedAt: metav1.NewTime(now.Add(-2 * time.Minute))}}
					if v := Pod(p, now); v.Level != LevelUnhealthy || v.Reason != "OOMKilled" {
						t.Errorf("gate masked active OOM: %+v", v)
					}
					cs.RestartCount = 2
					cs.LastTerminationState = corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{Reason: "Error", ExitCode: 1, FinishedAt: metav1.NewTime(now.Add(-10 * time.Second))}}
					cs.State = corev1.ContainerState{Running: &corev1.ContainerStateRunning{StartedAt: metav1.NewTime(now.Add(-5 * time.Second))}}
					if v := Pod(p, now); v.Reason != "CrashLoopBackOff" {
						t.Errorf("gate masked stable crash loop: %+v", v)
					}
					cs.LastTerminationState = corev1.ContainerState{}
				}
				p.Status.ContainerStatuses[0].State = corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{Reason: "ImagePullBackOff"}}
				if v := Pod(p, now); v.Level != LevelUnhealthy || v.Reason != "ImagePullBackOff" {
					t.Errorf("masked fatal failure %+v", v)
				}
			}
		})
	}
}

func TestReadinessGateClockVectors(t *testing.T) {
	raw, err := os.ReadFile("testdata/readiness_gate_clock.json")
	if err != nil {
		t.Fatal(err)
	}
	var file struct {
		Now     time.Time `json:"now"`
		Vectors []struct {
			Name    string     `json:"name"`
			Blocked bool       `json:"blocked"`
			Object  corev1.Pod `json:"object"`
		} `json:"vectors"`
	}
	if err := json.Unmarshal(raw, &file); err != nil {
		t.Fatal(err)
	}
	for _, v := range file.Vectors {
		t.Run(v.Name, func(t *testing.T) {
			since, got := PodReadinessGateFailure(&v.Object, file.Now)
			if got != v.Blocked || !since.IsZero() {
				t.Fatalf("blocked=%v onset=%v; want blocked=%v without known onset", got, since, v.Blocked)
			}
		})
	}
}