Ctrl K
ComunidadGuías…

internal/issues/cluster_test.go

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

Archivo original ↗Preguntar a KiBot
package issues

import (
	"strconv"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	"github.com/sophotechlabs/spinoza/internal/api"
)

func clusterObject(kind, name string, spec, status map[string]any) object {
	obj := &unstructured.Unstructured{Object: map[string]any{
		"apiVersion": "v1",
		"kind":       kind,
		"metadata": map[string]any{
			"name":              name,
			"uid":               "uid-" + name,
			"creationTimestamp": testNow.Add(-time.Hour).Format(time.RFC3339),
		},
		"spec":   spec,
		"status": status,
	}}
	return object{obj: obj, desc: api.ResourceDescriptor{
		Version: "v1", Resource: kind + "s", Kind: kind,
	}}
}

func nodeWith(name string, conditions []any, unschedulable bool) object {
	spec := map[string]any{}
	if unschedulable {
		spec["unschedulable"] = true
	}
	return clusterObject(kindNode, name, spec, map[string]any{"conditions": conditions})
}

func onlyNodeFinding(t *testing.T, snap *snapshot) finding {
	t.Helper()
	found := nodeFindings(snap)
	if len(found) != 1 {
		t.Fatalf("produced %d findings, want 1", len(found))
	}
	return found[0]
}

func TestANodeThatIsNotReadyIsFatal(t *testing.T) {
	snap := snapshotOf(nodeWith("worker", []any{
		condition("Ready", conditionFalse, map[string]any{
			"reason": "KubeletNotReady", "message": "container runtime is down",
		}),
	}, false))

	found := onlyNodeFinding(t, snap)

	if found.severity != severityFatal {
		t.Fatalf("severity was %d, want fatal", found.severity)
	}
	if found.title != "KubeletNotReady" {
		t.Fatalf("title was %q", found.title)
	}
	if !contains(found.detail, "container runtime is down") {
		t.Fatalf("detail was %q, want the kubelet's own message", found.detail)
	}
}

func TestANodeWhoseKubeletWentQuietIsDegradedRatherThanFatal(t *testing.T) {
	snap := snapshotOf(nodeWith("worker", []any{
		condition("Ready", "Unknown", nil),
	}, false))

	found := onlyNodeFinding(t, snap)

	if found.severity != severityDegraded {
		t.Fatalf("severity was %d, want degraded for an Unknown Ready", found.severity)
	}
}

func TestAReadyNodeSaysNothing(t *testing.T) {
	snap := snapshotOf(nodeWith("worker", []any{condition("Ready", conditionTrue, nil)}, false))

	if found := nodeFindings(snap); len(found) != 0 {
		t.Fatalf("a ready node produced %d findings", len(found))
	}
}

func TestANodeUnderPressureIsReportedByTheConditionThatIsTrue(t *testing.T) {
	snap := snapshotOf(nodeWith("worker", []any{
		condition("Ready", conditionTrue, nil),
		condition("DiskPressure", conditionTrue, map[string]any{"message": "no space left"}),
	}, false))

	found := onlyNodeFinding(t, snap)

	if found.title != "DiskPressure" {
		t.Fatalf("title was %q", found.title)
	}
	if found.severity != severityDegraded {
		t.Fatalf("severity was %d, want degraded", found.severity)
	}
}

func TestACordonedNodeIsAWarningAndNotAFault(t *testing.T) {
	snap := snapshotOf(nodeWith("worker", []any{condition("Ready", conditionTrue, nil)}, true))

	found := onlyNodeFinding(t, snap)

	if found.title != "Cordoned" {
		t.Fatalf("title was %q", found.title)
	}
	if found.severity != severityWarning {
		t.Fatalf("severity was %d, want warning", found.severity)
	}
}

func TestBeingNotReadyOutranksBeingCordoned(t *testing.T) {
	snap := snapshotOf(nodeWith("worker", []any{condition("Ready", conditionFalse, nil)}, true))

	if found := onlyNodeFinding(t, snap); found.title == "Cordoned" {
		t.Fatal("a node that is down was reported only as cordoned")
	}
}

func TestAPendingClaimIsFatal(t *testing.T) {
	snap := snapshotOf(clusterObject(kindClaim, "data", nil, map[string]any{"phase": "Pending"}))

	found := claimFindings(snap)

	if len(found) != 1 || found[0].title != "ClaimPending" {
		t.Fatalf("produced %v", found)
	}
	if found[0].severity != severityFatal {
		t.Fatalf("severity was %d, want fatal", found[0].severity)
	}
}

func TestALostClaimIsReported(t *testing.T) {
	snap := snapshotOf(clusterObject(kindClaim, "data", nil, map[string]any{"phase": "Lost"}))

	if found := claimFindings(snap); len(found) != 1 || found[0].title != "ClaimLost" {
		t.Fatalf("produced %v", found)
	}
}

func TestABoundClaimSaysNothing(t *testing.T) {
	snap := snapshotOf(clusterObject(kindClaim, "data", nil, map[string]any{"phase": "Bound"}))

	if found := claimFindings(snap); len(found) != 0 {
		t.Fatalf("a bound claim produced %d findings", len(found))
	}
}

func TestReadyAddressesIgnoreMalformedEndpointSubsets(t *testing.T) {
	endpoints := clusterObject(kindEndpoints, "web", nil, nil).obj
	setNested(endpoints, []any{
		"not a subset",
		map[string]any{"addresses": "not a list"},
		map[string]any{"addresses": []any{map[string]any{"ip": "10.0.0.1"}}},
	}, "subsets")

	if got := readyAddresses(endpoints); got != 1 {
		t.Fatalf("ready addresses = %d, want 1", got)
	}
}

func TestAClaimOnItsWayOutIsNotReportedAsPending(t *testing.T) {
	item := clusterObject(kindClaim, "data", nil, map[string]any{"phase": "Pending"})
	item.obj.SetDeletionTimestamp(&metav1.Time{Time: testNow})

	if found := claimFindings(snapshotOf(item)); len(found) != 0 {
		t.Fatal("a claim being deleted was reported as pending")
	}
}

func terminating(kind, name string, ago time.Duration, grace int64, finalizers []string) object {
	spec := map[string]any{}
	if grace > 0 {
		spec["terminationGracePeriodSeconds"] = grace
	}
	item := clusterObject(kind, name, spec, nil)
	item.obj.SetDeletionTimestamp(&metav1.Time{Time: testNow.Add(-ago)})
	if len(finalizers) > 0 {
		item.obj.SetFinalizers(finalizers)
	}
	return item
}

func TestAPodStuckTerminatingIsReportedOnceItsGraceIsSpent(t *testing.T) {
	snap := snapshotOf(terminating(kindPod, "api", 19*time.Minute+31*time.Second, 30, []string{"kubernetes.io/pvc-protection"}))

	found := terminatingFindings(snap, testNow)

	if len(found) != 1 {
		t.Fatalf("produced %d findings, want 1", len(found))
	}
	if !contains(found[0].detail, "kubernetes.io/pvc-protection") {
		t.Fatalf("detail was %q, want the finalizer named", found[0].detail)
	}
	if !contains(found[0].detail, "20m ago") {
		t.Fatalf("detail was %q, want the elapsed time in interface units", found[0].detail)
	}
}

func TestAPodInsideItsGracePeriodIsLeftAlone(t *testing.T) {
	snap := snapshotOf(terminating(kindPod, "api", 10*time.Second, 30, nil))

	if found := terminatingFindings(snap, testNow); len(found) != 0 {
		t.Fatal("a pod still inside its grace period was reported as stuck")
	}
}

func TestALongGracePeriodIsWaitedOutBeforeReporting(t *testing.T) {
	patient := terminating(kindPod, "api", 6*time.Minute, 3600, nil)

	if found := terminatingFindings(snapshotOf(patient), testNow); len(found) != 0 {
		t.Fatal("a pod with an hour of grace was called stuck after six minutes")
	}
}

func TestANamespaceStuckTerminatingIsReported(t *testing.T) {
	snap := snapshotOf(terminating(kindNamespace, "old", 30*time.Minute, 0, nil))

	found := terminatingFindings(snap, testNow)

	if len(found) != 1 || found[0].kind != kindNamespace {
		t.Fatalf("produced %v", found)
	}
	if !contains(found[0].detail, "and still here") {
		t.Fatalf("detail was %q", found[0].detail)
	}
}

func TestSomethingNobodyAskedToGoIsNotStuck(t *testing.T) {
	snap := snapshotOf(clusterObject(kindNamespace, "live", nil, nil))

	if found := terminatingFindings(snap, testNow); len(found) != 0 {
		t.Fatal("a namespace nobody deleted was reported as terminating")
	}
}

func TestTheClusterDetectorsRunTogether(t *testing.T) {
	snap := snapshotOf(
		nodeWith("worker", []any{condition("Ready", conditionFalse, nil)}, false),
		clusterObject(kindClaim, "data", nil, map[string]any{"phase": "Pending"}),
		terminating(kindNamespace, "old", 30*time.Minute, 0, nil),
	)

	found := clusterFindings(snap, testNow)

	if len(found) != 3 {
		t.Fatalf("produced %d findings, want one from each detector", len(found))
	}
}

func TestANodeThatReportsNoReadyConditionAtAllSaysNothing(t *testing.T) {
	snap := snapshotOf(nodeWith("joining", []any{}, false))

	if found := nodeFindings(snap); len(found) != 0 {
		t.Fatalf("a node with no conditions yet produced %d findings", len(found))
	}
}

func crd(name string, conditions []any) object {
	obj := &unstructured.Unstructured{Object: map[string]any{
		"apiVersion": apiExtensionsGroup + "/v1",
		"kind":       kindCRD,
		"metadata": map[string]any{
			"name":              name,
			"uid":               "uid-" + name,
			"creationTimestamp": testNow.Add(-time.Hour).Format(time.RFC3339),
		},
		"status": map[string]any{"conditions": conditions},
	}}
	return object{obj: obj, desc: api.ResourceDescriptor{
		Group: apiExtensionsGroup, Version: "v1", Resource: "customresourcedefinitions", Kind: kindCRD,
	}}
}

func serviceObject(name string, selector map[string]any) object {
	item := clusterObject(kindService, name, map[string]any{"selector": selector}, nil)
	item.obj.SetNamespace("default")
	return item
}

func endpointsFor(name string, addresses int) object {
	listed := make([]any, 0, addresses)
	for at := range addresses {
		listed = append(listed, map[string]any{"ip": "10.0.0." + strconv.Itoa(at+1)})
	}
	item := clusterObject(kindEndpoints, name, nil, nil)
	item.obj.SetNamespace("default")
	item.obj.Object["subsets"] = []any{map[string]any{"addresses": listed}}
	return item
}

func TestACustomResourceDefinitionTheApiserverRefusedIsFatal(t *testing.T) {
	snap := snapshotOf(crd("widgets.example.com", []any{
		condition("Established", conditionFalse, map[string]any{
			"reason": "SchemaInvalid", "message": "spec.versions[0].schema is invalid",
		}),
	}))

	found := definitionFindings(snap)

	if len(found) != 1 || found[0].title != "SchemaInvalid" {
		t.Fatalf("produced %v", found)
	}
	if found[0].severity != severityFatal {
		t.Fatalf("severity was %d, want fatal", found[0].severity)
	}
}

func TestAnEstablishedDefinitionSaysNothing(t *testing.T) {
	snap := snapshotOf(crd("widgets.example.com", []any{condition("Established", conditionTrue, nil)}))

	if found := definitionFindings(snap); len(found) != 0 {
		t.Fatalf("an established CRD produced %d findings", len(found))
	}
}

func TestADefinitionWithNoConditionsYetIsLeftAlone(t *testing.T) {
	if found := definitionFindings(snapshotOf(crd("widgets.example.com", nil))); len(found) != 0 {
		t.Fatal("a CRD the apiserver has not judged yet was reported")
	}
}

func TestAServiceWithNoReadyBackendIsFatal(t *testing.T) {
	snap := snapshotOf(serviceObject("api", map[string]any{"app": "api"}))

	found := routingFindings(snap)

	if len(found) != 1 || found[0].title != "NoEndpoints" {
		t.Fatalf("produced %v", found)
	}
}

func TestAServiceWithABackendSaysNothing(t *testing.T) {
	snap := snapshotOf(serviceObject("api", map[string]any{"app": "api"}), endpointsFor("api", 2))

	if found := routingFindings(snap); len(found) != 0 {
		t.Fatalf("a service with endpoints produced %d findings", len(found))
	}
}

func TestEndpointsWithNoAddressesDoNotCountAsABackend(t *testing.T) {
	snap := snapshotOf(serviceObject("api", map[string]any{"app": "api"}), endpointsFor("api", 0))

	if found := routingFindings(snap); len(found) != 1 {
		t.Fatal("an Endpoints object holding no addresses was read as a backend")
	}
}

func TestAServiceThatSelectsNothingIsNotJudgedOnEndpoints(t *testing.T) {
	external := serviceObject("db", nil)

	if found := routingFindings(snapshotOf(external)); len(found) != 0 {
		t.Fatal("a selectorless service was asked for endpoints")
	}
}

func certificate(name, notAfter string) object {
	obj := &unstructured.Unstructured{Object: map[string]any{
		"apiVersion": "cert-manager.io/v1",
		"kind":       "Certificate",
		"metadata": map[string]any{
			"name": name, "namespace": "default", "uid": "uid-" + name,
			"creationTimestamp": testNow.Add(-time.Hour).Format(time.RFC3339),
		},
		"status": map[string]any{"notAfter": notAfter},
	}}
	return object{obj: obj, desc: api.ResourceDescriptor{
		Group: "cert-manager.io", Version: "v1", Resource: "certificates", Kind: "Certificate",
	}}
}

func TestACertificateNearingItsEndIsReported(t *testing.T) {
	snap := snapshotOf(certificate("wildcard", testNow.Add(71*time.Hour+31*time.Minute).Format(time.RFC3339)))

	found := expiryFindings(snap, testNow)

	if len(found) != 1 || found[0].title != "ExpiringSoon" {
		t.Fatalf("produced %v", found)
	}
	if found[0].severity != severityDegraded {
		t.Fatalf("severity was %d, want degraded", found[0].severity)
	}
	if found[0].detail != "the certificate is valid for another 3d" {
		t.Fatalf("detail = %q, want the remaining days", found[0].detail)
	}
}

func TestACertificateAlreadyPastItsEndIsFatal(t *testing.T) {
	snap := snapshotOf(certificate("wildcard", testNow.Add(-time.Hour-31*time.Minute).Format(time.RFC3339)))

	found := expiryFindings(snap, testNow)

	if len(found) != 1 || found[0].title != "Expired" {
		t.Fatalf("produced %v", found)
	}
	if found[0].severity != severityFatal {
		t.Fatalf("severity was %d, want fatal", found[0].severity)
	}
	if found[0].detail != "the certificate expired 2h ago" {
		t.Fatalf("detail = %q, want the elapsed hours", found[0].detail)
	}
}

func TestACertificateWithMonthsLeftSaysNothing(t *testing.T) {
	snap := snapshotOf(certificate("wildcard", testNow.Add(60*24*time.Hour).Format(time.RFC3339)))

	if found := expiryFindings(snap, testNow); len(found) != 0 {
		t.Fatalf("a certificate with two months left produced %d findings", len(found))
	}
}

func TestSomethingWithNoExpiryAtAllIsNotJudgedOnOne(t *testing.T) {
	snap := snapshotOf(clusterObject(kindNamespace, "live", nil, nil))

	if found := expiryFindings(snap, testNow); len(found) != 0 {
		t.Fatal("an object publishing no notAfter was judged on its expiry")
	}
}

func TestAnUnparsableExpiryIsIgnored(t *testing.T) {
	snap := snapshotOf(certificate("wildcard", "whenever"))

	if found := expiryFindings(snap, testNow); len(found) != 0 {
		t.Fatal("an expiry nobody can parse produced a finding")
	}
}