internal/issues/stall_test.go
Rama main · réplica de lectura del repositorio original.
package issues
import (
"errors"
"slices"
"strconv"
"testing"
"time"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/sophotechlabs/spinoza/internal/api"
)
func buildWith(t *testing.T, lister *stubLister, events *stubEvents, descs map[string]api.ResourceDescriptor) api.IssueQueue {
t.Helper()
return buildLimited(t, lister, events, descs, testLimits())
}
func silentPod(name string) *stubLister {
pod := newPod(
name,
withPhase(phasePending),
withStartTime(testNow.Add(-10*time.Minute)),
)
return &stubLister{items: itemsOf("pods", pod)}
}
func TestAPodThatWentQuietAfterBindingIsReported(t *testing.T) {
lister := silentPod("web-1")
events := &stubEvents{}
queue := buildWith(t, lister, events, catalog(podDescriptor()))
row, ok := rowNamed(queue, "web-1")
if !ok || row.Detector != detectorStall {
t.Fatalf("row = %+v, want the stall row", row)
}
if !row.Uncertain {
t.Fatalf("row = %+v, want the guess stated as a guess", row)
}
if !contains(row.Detail, "bound to node-a") || !contains(row.Detail, "no events at all") {
t.Fatalf("detail = %q, want what was observed", row.Detail)
}
if !contains(row.Detail, "10m ago") {
t.Fatalf("detail = %q, want the elapsed time in interface units", row.Detail)
}
if row.Severity != api.SeverityLow {
t.Fatalf("severity = %q, want warning", row.Severity)
}
}
func TestAPodWithEventsIsNotAStall(t *testing.T) {
lister := silentPod("web-2")
events := &stubEvents{byUID: map[string][]api.Event{
"uid-web-2": {{Reason: "Pulling"}},
}}
if queue := buildWith(t, lister, events, catalog(podDescriptor())); len(queue.Rows) != 0 {
t.Fatalf("rows = %+v, want none once the kubelet has said something", queue.Rows)
}
}
func TestAnEventLookupThatFailsReportsNothingAndNamesTheGap(t *testing.T) {
lister := silentPod("web-3")
events := &stubEvents{err: errors.New("forbidden")}
queue := buildWith(t, lister, events, catalog(podDescriptor()))
if len(queue.Rows) != 0 {
t.Fatalf("rows = %+v, want silence rather than a guess", queue.Rows)
}
if !contains(queue.Error, "events") || !contains(queue.Error, "forbidden") {
t.Fatalf("error = %q, want the missing event evidence named", queue.Error)
}
}
func TestAnEventLookupPanicReportsNothingAndNamesTheGap(t *testing.T) {
lister := silentPod("web-3")
events := &stubEvents{panics: true}
queue := buildWith(t, lister, events, catalog(podDescriptor()))
if len(queue.Rows) != 0 {
t.Fatalf("rows = %+v, want silence rather than a guess", queue.Rows)
}
if !contains(queue.Error, "events") {
t.Fatalf("error = %q, want the panicking event reader named", queue.Error)
}
}
func TestAPodInsideTheGraceIsNotAStall(t *testing.T) {
pod := newPod("web-4", withPhase(phasePending), withStartTime(testNow.Add(-time.Minute)))
lister := &stubLister{items: itemsOf("pods", pod)}
if queue := buildWith(t, lister, &stubEvents{}, catalog(podDescriptor())); len(queue.Rows) != 0 {
t.Fatalf("rows = %+v, want the pod given its grace", queue.Rows)
}
}
func TestAnUnboundPodIsNotAStall(t *testing.T) {
pod := newPod(
"web-5",
withPhase(phasePending),
withNode(""),
withStartTime(testNow.Add(-time.Hour)),
)
lister := &stubLister{items: itemsOf("pods", pod)}
if queue := buildWith(t, lister, &stubEvents{}, catalog(podDescriptor())); len(queue.Rows) != 0 {
t.Fatalf("rows = %+v, want an unscheduled pod left to the scheduler", queue.Rows)
}
}
func TestAPodWithARunningContainerIsNotAStall(t *testing.T) {
pod := newPod(
"web-6",
withStartTime(testNow.Add(-time.Hour)),
withContainer("app", map[string]any{"running": map[string]any{}}),
)
lister := &stubLister{items: itemsOf("pods", pod)}
if queue := buildWith(t, lister, &stubEvents{}, catalog(podDescriptor())); len(queue.Rows) != 0 {
t.Fatalf("rows = %+v, want none", queue.Rows)
}
}
func TestAPodAlreadyReportedIsNotAskedAboutAgain(t *testing.T) {
pod := newPod(
"web-7",
withPhase(phasePending),
withStartTime(testNow.Add(-time.Hour)),
withContainer("app", map[string]any{
"waiting": map[string]any{"reason": "ImagePullBackOff"},
}),
)
lister := &stubLister{items: itemsOf("pods", pod)}
events := &stubEvents{}
queue := buildWith(t, lister, events, catalog(podDescriptor()))
if len(events.asked) != 0 {
t.Fatalf("asked = %v, want no event lookup for a pod already explained", events.asked)
}
row, _ := rowNamed(queue, "web-7")
if row.Detector != detectorStartup {
t.Fatalf("detector = %q, want the startup detector", row.Detector)
}
}
func TestATerminatingPodIsNotAStall(t *testing.T) {
pod := newPod(
"web-8",
withPhase(phasePending),
withStartTime(testNow.Add(-time.Hour)),
withDeleted(),
)
lister := &stubLister{items: itemsOf("pods", pod)}
if queue := buildWith(t, lister, &stubEvents{}, catalog(podDescriptor())); len(queue.Rows) != 0 {
t.Fatalf("rows = %+v, want none", queue.Rows)
}
}
func TestOnlyTheOldestCandidatesAreAskedAbout(t *testing.T) {
pods := make([]*unstructured.Unstructured, 0, defaultCandidates+5)
for index := range defaultCandidates + 5 {
name := "web-" + strconv.Itoa(index)
pods = append(pods, newPod(
name,
withPhase(phasePending),
withStartTime(testNow.Add(-time.Duration(index+10)*time.Minute)),
))
}
lister := &stubLister{items: itemsOf("pods", pods...)}
events := &stubEvents{}
queue := buildWith(t, lister, events, catalog(podDescriptor()))
asked := events.askedAbout()
if len(asked) != defaultCandidates {
t.Fatalf("asked = %d pods, want the %d oldest", len(asked), defaultCandidates)
}
if len(queue.Rows) != defaultCandidates {
t.Fatalf("rows = %d, want one per candidate asked about", len(queue.Rows))
}
if !slices.Contains(asked, "uid-web-24") {
t.Fatalf("asked = %v, want the oldest candidate among them", asked)
}
if slices.Contains(asked, "uid-web-0") {
t.Fatalf("asked = %v, want the newest candidates left out", asked)
}
}
func TestAPodTheKubeletComplainedAboutIsReportedInItsOwnWords(t *testing.T) {
lister := silentPod("web-5")
events := &stubEvents{byUID: map[string][]api.Event{
"uid-web-5": {{
Type: "Warning",
Reason: "FailedMount",
Message: `MountVolume.SetUp failed for volume "creds": secret "api" not found`,
}},
}}
queue := buildWith(t, lister, events, catalog(podDescriptor()))
row, ok := rowNamed(queue, "web-5")
if !ok {
t.Fatalf("rows = %+v, want the pod reported", queue.Rows)
}
if row.Title != "FailedMount" {
t.Fatalf("title = %q, want the kubelet's own reason", row.Title)
}
if !contains(row.Detail, `secret "api" not found`) {
t.Fatalf("detail = %q, want the kubelet's own message", row.Detail)
}
if row.Severity != api.SeverityHigh {
t.Fatalf("severity = %q, want fatal", row.Severity)
}
if row.Uncertain {
t.Fatal("a message the kubelet sent was reported as a guess")
}
}
func TestTheNewestWarningIsTheOneReported(t *testing.T) {
lister := silentPod("web-6")
events := &stubEvents{byUID: map[string][]api.Event{
"uid-web-6": {
{Type: "Normal", Reason: "Scheduled"},
{Type: "Warning", Reason: "FailedAttachVolume", Message: "still attaching"},
{Type: "Warning", Reason: "FailedMount", Message: "older"},
},
}}
queue := buildWith(t, lister, events, catalog(podDescriptor()))
row, _ := rowNamed(queue, "web-6")
if row.Title != "FailedAttachVolume" {
t.Fatalf("title = %q, want the first warning the reader was given", row.Title)
}
}